Last active
December 3, 2020 17:01
Revisions
-
Avatar Qing revised this gist
Mar 2, 2016 . 1 changed file with 449 additions and 537 deletions.There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,548 +1,460 @@ package com.hk.agg.im.utils; import android.content.Context; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnPreparedListener; import android.os.PowerManager; import android.text.TextUtils; import android.util.Log; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class AudioPlayer implements OnCompletionListener, OnPreparedListener, OnErrorListener, MusicFocusable { // The volume we set the media player to when we lose audio focus, but are allowed to reduce // the volume instead of stopping playback. public static final float DUCK_VOLUME = 0.1f; // our media player private MediaPlayer mPlayer = null; // our AudioFocusHelper object, if it's available (it's available on SDK level >= 8) // If not available, this will be null. Always check for null before using! private AudioFocusHelper mAudioFocusHelper = null; // indicates the state our service: private enum State { Stopped, // media player is stopped and not prepared to play Preparing, // media player is preparing... Playing, // playback active (media player ready!). (but the media player may actually be // paused in this state if we don't have audio focus. But we stay in this state // so that we know we have to resume playback once we get focus back) Paused // playback paused (media player ready!) } private State mState = State.Stopped; // do we have audio focus? private enum AudioFocus { NoFocusNoDuck, // we don't have audio focus, and can't duck NoFocusCanDuck, // we don't have focus, but can play at a low volume ("ducking") Focused // we have full audio focus } private AudioFocus mAudioFocus = AudioFocus.NoFocusNoDuck; private Context mContext = null; private List<PlayListener> mPlayListeners = new ArrayList<>(); private String mCurrentPlayUri = null; private Object mCurrentPlayId = null; private static AudioPlayer sAudioPlayer = null; public static AudioPlayer getInstance(Context context) { if (sAudioPlayer == null) { sAudioPlayer = new AudioPlayer(context); } return sAudioPlayer; } private AudioPlayer(Context context) { mContext = context.getApplicationContext(); } public void onCreate() { Log.i(getLogTag(), "onCreate()"); Context context = getContext(); mAudioFocusHelper = new AudioFocusHelper(context, this); } public void processTogglePlaybackRequest() { if (mState == State.Paused || mState == State.Stopped) { processPlayRequest(mCurrentPlayId, null); } else { processPauseRequest(); } } public void processPlayRequest(Object playId, String uri) { boolean playNewSong = mState == State.Stopped || mState == State.Playing || (mState == State.Paused && !TextUtils.isEmpty(mCurrentPlayUri) && !mCurrentPlayUri.equals(uri)); boolean resumeFromPause = mState == State.Paused && !TextUtils.isEmpty(mCurrentPlayUri) && mCurrentPlayUri.equals(uri); if (playNewSong) { Log.i(getLogTag(), "Playing from URL/path: " + uri); tryToGetAudioFocus(); playSong(playId, uri); } else if (resumeFromPause) { tryToGetAudioFocus(); mState = State.Playing; configAndStartMediaPlayer(); } } public void processPauseRequest() { if (mState == State.Playing) { // Pause media player and cancel the 'foreground service' state. mState = State.Paused; mPlayer.pause(); relaxResources(false); // while paused, we always retain the MediaPlayer for (PlayListener listener : mPlayListeners) { listener.onPaused(mCurrentPlayId, mCurrentPlayUri); } // do not give up audio focus } } public void processStopRequest() { processStopRequest(false); } public void processStopRequest(boolean force) { if (mState == State.Playing || mState == State.Paused || force) { mState = State.Stopped; // let go of all resources... relaxResources(true); giveUpAudioFocus(); for (PlayListener listener : mPlayListeners) { listener.onStopped(mCurrentPlayId, mCurrentPlayUri); } mCurrentPlayUri = null; mCurrentPlayId = null; } } public void addPlayListener(PlayListener listener) { mPlayListeners.add(listener); } public void removePlayListener(PlayListener listener) { mPlayListeners.remove(listener); } public String getCurrentPlayUri() { return mCurrentPlayUri; } public Object getCurrentPlayId() { return mCurrentPlayId; } public State getState() { return mState; } public boolean isPlaying() { return mState == State.Playing; } public boolean isPaused() { return mState == State.Paused; } /** * Releases resources used by the service for playback. This includes the "foreground service" * status and notification, the wake locks and possibly the MediaPlayer. * * @param releaseMediaPlayer Indicates whether the Media Player should also be released or not */ private void relaxResources(boolean releaseMediaPlayer) { // stop and release the Media Player, if it's available if (releaseMediaPlayer && mPlayer != null) { mPlayer.reset(); mPlayer.release(); mPlayer = null; } } private void giveUpAudioFocus() { if (mAudioFocus == AudioFocus.Focused && mAudioFocusHelper != null && mAudioFocusHelper.abandonFocus()) { mAudioFocus = AudioFocus.NoFocusNoDuck; } } /** * Reconfigures MediaPlayer according to audio focus settings and starts/restarts it. This * method starts/restarts the MediaPlayer respecting the current audio focus state. So if * we have focus, it will play normally; if we don't have focus, it will either leave the * MediaPlayer paused or set it to a low volume, depending on what is allowed by the * current focus settings. This method assumes mPlayer != null, so if you are calling it, * you have to do so from a context where you are sure this is the case. */ private void configAndStartMediaPlayer() { if (mAudioFocus == AudioFocus.NoFocusNoDuck) { // If we don't have audio focus and can't duck, we have to pause, even if mState // is State.Playing. But we stay in the Playing state so that we know we have to resume // playback once we get the focus back. if (mPlayer.isPlaying()) { mPlayer.pause(); Log.w(getLogTag(), "configAndStartMediaPlayer()-> NoFocusNoDuck. pause"); for (PlayListener listener : mPlayListeners) { listener.onPaused(mCurrentPlayId, mCurrentPlayUri); } } else { Log.e(getLogTag(), "configAndStartMediaPlayer()-> NoFocusNoDuck "); for (PlayListener listener : mPlayListeners) { listener.onError(mCurrentPlayId, "NoFocusNoDuck"); } } return; } else if (mAudioFocus == AudioFocus.NoFocusCanDuck) { mPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME); // we'll be relatively quiet } else { mPlayer.setVolume(1.0f, 1.0f); // we can be loud } if (!mPlayer.isPlaying()) { Log.i(getLogTag(), "configAndStartMediaPlayer()-> start"); mPlayer.start(); for (PlayListener listener : mPlayListeners) { listener.onPlayed(mCurrentPlayId, mCurrentPlayUri); } } else { Log.i(getLogTag(), "configAndStartMediaPlayer()-> is playing"); } } private void tryToGetAudioFocus() { if (mAudioFocus != AudioFocus.Focused && mAudioFocusHelper != null && mAudioFocusHelper.requestFocus()) { mAudioFocus = AudioFocus.Focused; } } /** * Starts playing the next song. If manualUrl is null, the next song will be randomly selected * from our Media Retriever (that is, it will be a random song in the user's device). If * manualUrl is non-null, then it specifies the URL or path to the song that will be played * next. */ private void playSong(Object playId, String manualUrl) { if (mState == State.Playing || mState == State.Paused) { for (PlayListener listener : mPlayListeners) { listener.onStopped(mCurrentPlayId, mCurrentPlayUri); } mCurrentPlayUri = null; mCurrentPlayId = null; } mState = State.Stopped; relaxResources(false); // release everything except MediaPlayer try { if (manualUrl != null) { // set the source of the media player to a manual URL or path createMediaPlayerIfNeeded(); mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mPlayer.setDataSource(manualUrl); mCurrentPlayUri = manualUrl; mCurrentPlayId = playId; } else { Log.e(getLogTag(), "No available music to play. Place some music on your external storage \"\n" + "\t\t\t\t\t\t\t\t\t+ \"device (e.g. your SD card) and try again."); processStopRequest(true); // stop everything! for (PlayListener listener : mPlayListeners) { listener.onError(playId, "No available music to play"); } return; } mState = State.Preparing; // starts preparing the media player in the background. When it's done, it will call // our OnPreparedListener (that is, the onPrepared() method on this class, since we set // the listener to 'this'). // // Until the media player is prepared, we *cannot* call start() on it! mPlayer.prepareAsync(); } catch (IOException ex) { String msg = "IOException playing next song: " + ex.getMessage(); Log.e(getLogTag(), msg); ex.printStackTrace(); for (PlayListener listener : mPlayListeners) { listener.onError(mCurrentPlayId, msg); } } } /** * Makes sure the media player exists and has been reset. This will create the media player * if needed, or reset the existing media player if one already exists. */ private void createMediaPlayerIfNeeded() { if (mPlayer == null) { mPlayer = new MediaPlayer(); // Make sure the media player will acquire a wake-lock while playing. If we don't do // that, the CPU might go to sleep while the song is playing, causing playback to stop. // // Remember that to use this, we have to declare the android.permission.WAKE_LOCK // permission in AndroidManifest.xml. mPlayer.setWakeMode(getContext(), PowerManager.PARTIAL_WAKE_LOCK); // we want the media player to notify us when it's ready preparing, and when it's done // playing: mPlayer.setOnPreparedListener(this); mPlayer.setOnCompletionListener(this); mPlayer.setOnErrorListener(this); } else { mPlayer.reset(); } } /** Called when media player is done playing current song. */ @Override public void onCompletion(MediaPlayer player) { Log.i(getLogTag(), "onCompletion"); processStopRequest(); } /** Called when media player is done preparing. */ @Override public void onPrepared(MediaPlayer player) { Log.i(getLogTag(), "onPrepared"); // The media player is done preparing. That means we can start playing! mState = State.Playing; configAndStartMediaPlayer(); } /** * Called when there's an error playing media. When this happens, the media player goes to * the Error state. We warn the user about the error and reset the media player. */ @Override public boolean onError(MediaPlayer mp, int what, int extra) { String msg = "Media player error! Resetting." + "Error: what=" + String.valueOf(what) + ", extra=" + String.valueOf(extra); Log.e(getLogTag(), msg); mState = State.Stopped; relaxResources(true); giveUpAudioFocus(); for (PlayListener listener : mPlayListeners) { listener.onError(mCurrentPlayId, msg); } return true; // true indicates we handled the error } @Override public void onGainedAudioFocus() { Log.i(getLogTag(), "gained audio focus."); mAudioFocus = AudioFocus.Focused; // restart media player with new focus settings if (mState == State.Playing) { configAndStartMediaPlayer(); } } @Override public void onLostAudioFocus(boolean canDuck) { Log.e(getLogTag(), "lost audio focus." + (canDuck ? "can duck" : "no duck")); mAudioFocus = canDuck ? AudioFocus.NoFocusCanDuck : AudioFocus.NoFocusNoDuck; // start/restart/pause media player with new focus settings if (mPlayer != null && mPlayer.isPlaying()) configAndStartMediaPlayer(); } public void onDestroy() { // Service is being killed, so make sure we release our resources processStopRequest(true); } public String getLogTag() { return getClass().getSimpleName(); } private Context getContext() { return mContext; } private static class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener { AudioManager mAM; MusicFocusable mFocusable; public AudioFocusHelper(Context ctx, MusicFocusable focusable) { mAM = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE); mFocusable = focusable; } /** Requests audio focus. Returns whether request was successful or not. */ public boolean requestFocus() { return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAM.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); } /** Abandons audio focus. Returns whether request was successful or not. */ public boolean abandonFocus() { return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAM.abandonAudioFocus(this); } /** * Called by AudioManager on audio focus changes. We implement this by calling our * MusicFocusable appropriately to relay the message. */ public void onAudioFocusChange(int focusChange) { if (mFocusable == null) return; switch (focusChange) { case AudioManager.AUDIOFOCUS_GAIN: mFocusable.onGainedAudioFocus(); break; case AudioManager.AUDIOFOCUS_LOSS: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: mFocusable.onLostAudioFocus(false); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: mFocusable.onLostAudioFocus(true); break; default: } } } public static class SimplePlayListener implements PlayListener { @Override public void onPlayed(Object playId, String uri) { } @Override public void onPaused(Object playId, String uri) { } @Override public void onStopped(Object playId, String uri) { } @Override public void onError(Object playId, String error) { } } public interface PlayListener { void onPlayed(Object playId, String uri); void onPaused(Object playId, String uri); void onStopped(Object playId, String uri); void onError(Object playId, String error); } } interface MusicFocusable { /** Signals that audio focus was gained. */ void onGainedAudioFocus(); /** * Signals that audio focus was lost. * * @param canDuck If true, audio can continue in "ducked" mode (low volume). Otherwise, all * audio must stop. */ void onLostAudioFocus(boolean canDuck); } -
Avatar Qing revised this gist
Feb 20, 2014 . 1 changed file with 205 additions and 74 deletions.There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,6 @@ package utils; import java.io.FileDescriptor; import android.content.Context; import android.content.Intent; @@ -10,66 +12,154 @@ import android.net.Uri; import android.os.PowerManager; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; public class AudioPlayer implements OnCompletionListener, OnPreparedListener, OnErrorListener { private static final String TAG = AudioPlayer.class.getSimpleName(); /** 广播ACTION名称的前缀 */ private static final String ACTION_PREFFIX = "org.tools.audioplayer"; /** 一个广播ACTION,歌曲开始播放时发送此广播 */ public static final String ACTION_PLAY = ACTION_PREFFIX + ".ACTION_PLAY"; /** 一个广播ACTION,歌曲暂停播放时发送此广播 */ public static final String ACTION_PAUSE = ACTION_PREFFIX + ".ACTION_PAUSE"; /** 一个广播ACTION,歌曲停止播放发送此广播 */ public static final String ACTION_STOP = ACTION_PREFFIX + ".ACTION_STOP"; /* * 歌曲播放的状态(Preparing,Playing,Paused,Stopped) */ public static final int STATE_STOPPED = 0; // MediaPlayer已经停止工作,不再准备播放 public static final int STATE_PREPARING = 1; // MediaPlayer正在准备中 public static final int STATE_PLAYING = 2; // 正在播放(MediaPlayer已经准备好了) // (但是当丢失音频焦点时,MediaPlayer在此状态下实际上也许已经暂停了, // 但是我们仍然保持这个状态,以表明我们必须在一获得音频焦点时就返回播放状态) public static final int STATE_PAUSED = 3; // 播放暂停 (MediaPlayer处于准备好了的状态) /** 當前播放狀態 */ private int mState = STATE_STOPPED; /** 丢失音频焦点,我们为媒体播放设置一个低音量(1.0f为最大),而不是停止播放 */ private static final float DUCK_VOLUME = 0.1f; /** 音频焦点辅助类 */ private AudioFocusHelper mAudioFocusHelper = null; /** 用于音频播放 */ private MediaPlayer mMediaPlayer = null; /** 本地广播管理器,用于发送本地广播 */ private LocalBroadcastManager mLocalBroadcastManager = null; /** 需要的上下文环境,引用的是一个ApplicationContext,以防止内存泄漏 */ private Context mContext = null; /** 构造方法私有化,以便实现单例构造 */ private AudioPlayer(Context context) { mContext = context.getApplicationContext(); mAudioFocusHelper = new AudioFocusHelper(mContext, mMusicFocusable); mLocalBroadcastManager = LocalBroadcastManager.getInstance(mContext); } /** 本类单例 */ private static AudioPlayer mController = null; /** * 获取音频控制器实例 * * @param context * 上下文环境 */ public static AudioPlayer getInstance(Context context) { if (mController == null) { mController = new AudioPlayer(context); } return mController; } /** * 请求播放音频 * * @param filePath * 音频文件路径 * @param looping * 是否循环播放 */ public void processPlayRequest(String filePath, boolean looping) { processPlayRequest(Uri.parse(filePath), looping); } /** * 请求播放音频 * * @param uri * 音频uri * @param looping * 是否循环播放 */ public void processPlayRequest(Uri uri, boolean looping) { processPlayRequest(uri, null, looping); } /** * 请求播放音频 * * @param fd * 音频文件文件描述符,可以播放来自assets下的音频文件 * @param looping * 是否循环播放 */ public void processPlayRequest(FileDescriptor fd, boolean looping) { processPlayRequest(null, fd, looping); } /** 请求暂停音频播放 */ public void processPauseRequest() { Log.i(TAG, "processPauseRequest()"); if (mState == STATE_PLAYING) { // 标记当前播放状态为"暂停" mState = STATE_PAUSED; // 暂停播放 mMediaPlayer.pause(); // 发送广播通知音频播放已经暂停 mLocalBroadcastManager.sendBroadcast(new Intent(ACTION_PAUSE)); } } /** 请求停止音频播放 */ public void processStopRequest() { processStopRequest(false); } /** * 请求停止音频播放 * * @param force * 传递true强制停止播放,不管当前处于什么状态 */ public void processStopRequest(boolean force) { Log.i(TAG, "processStopRequest()"); if (mState != STATE_STOPPED || force) { // 标记当前播放状态为“停止” mState = STATE_STOPPED; // 释放所有持有的资源 relaxResources(); // 放弃音频焦点的控制 mAudioFocusHelper.giveUpAudioFocus(); // 发送广播通知音频播放已经停止 mLocalBroadcastManager.sendBroadcast(new Intent(ACTION_STOP)); } } /** @@ -82,106 +172,143 @@ public int getState() { return mState; } /** * 获取当前播放进度 * * @return 当前播放位置(单位:毫秒)。如果当前没有播放,返回0。 */ public int getPlayingProgress() { if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { return mMediaPlayer.getCurrentPosition(); } return 0; } /** * 获取正在播放的音频长度 * * @return 音频长度(单位:毫秒) */ public int getDuration() { if (mMediaPlayer != null && mState != STATE_STOPPED) { return mMediaPlayer.getDuration(); } return 0; } /** * 跳转到某个指定进度 * * @param newPosition * 要跳转到的进度(单位:毫秒) */ public void seekTo(int newPosition) { if (mMediaPlayer != null && (mState == STATE_PLAYING || mState == STATE_PAUSED)) { mMediaPlayer.seekTo(newPosition); } } /** * 请求播放音频,提供两个数据源,哪个不为空就用哪个播放 * * @param uri * 音频文件的URI,音频可以来自存储卡、ContentProvider或者网络。 * @param fd * 音频文件的文件描述符,音频可以来自原assets目录下。 * @param loop * 是否循环播放 */ private void processPlayRequest(Uri uri, FileDescriptor fd, boolean loop) { Log.i(TAG, "processPlayRequest()"); if (mState == STATE_PAUSED) { mAudioFocusHelper.tryToGetAudioFocus(); configAndStartMediaPlayer(); mState = STATE_PLAYING; } else if (mState != STATE_PREPARING) { mAudioFocusHelper.tryToGetAudioFocus(); if (uri != null) { play(uri, loop); } else if (fd != null) { play(fd, loop); } } } /** * 播放音频 * * @param uri * 音频来源 * @param looping * 是否循环播放 */ private void play(Uri uri, boolean looping) { play(uri, null, looping); } /** * 播放音频 * * @param fd * 音频文件的文件描述符 * @param looping * 是否循环播放 */ private void play(FileDescriptor fd, boolean looping) { play(null, fd, looping); } /** * 播放音频,提供两个音频数据来源,哪个不为空就用哪个 * * @param uri * 音频来源 * @param fd * 音频文件的文件描述符 * @param looping * 是否循环播放 */ private void play(Uri uri, FileDescriptor fd, boolean looping) { Log.i(TAG, "play()"); // 检查数据源是否有效 if (uri == null && fd == null) { Log.d(TAG, "Invalid data source.Failed to play."); } // 先标示为停止播放状态 mState = STATE_STOPPED; try { // 尝试初始化MediaPlayer createMediaPlayerIfNeeded(); // 设置播放数据源 if (uri != null) { mMediaPlayer.setDataSource(mContext, uri); } else if (fd != null) { mMediaPlayer.setDataSource(fd); } // 设置MediaPlayer mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setLooping(looping); mState = STATE_PREPARING; // 在后台准备MediaPlayer,准备完成后会调用OnPreparedListener的onPrepared()方法。 // 在MediaPlayer准备好之前,我们不能调用其start()方法 mMediaPlayer.prepareAsync(); } catch (Exception ex) { Log.e(TAG, "IOException while request to play."); ex.printStackTrace(); } } /** * 释放本服务所使用的资源 */ private void relaxResources() { // 停止并释放MediaPlayer @@ -230,9 +357,11 @@ private void configAndStartMediaPlayer() { } return; } else if (mAudioFocusHelper.getAudioFocus() == AudioFocusHelper.NoFocusCanDuck) // 设置一个较为安静的音量 mMediaPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME); else { // 设置大声播放 mMediaPlayer.setVolume(1.0f, 1.0f); } if (!mMediaPlayer.isPlaying()) { mMediaPlayer.start(); @@ -266,23 +395,25 @@ public final boolean onError(MediaPlayer mp, int what, int extra) { } processStopRequest(true); // true表示我们处理了发生的错误 return true; } @Override public final void onPrepared(MediaPlayer mp) { // 准备完成了,可以播放歌曲了 mState = STATE_PLAYING; configAndStartMediaPlayer(); mLocalBroadcastManager.sendBroadcast(new Intent(ACTION_PLAY)); } @Override public final void onCompletion(MediaPlayer mp) { // 播放完成时,我们释放所有资源,重置播放状态 processStopRequest(true); } private MusicFocusable mMusicFocusable = new MusicFocusable() { -
Avatar Qing created this gist
Feb 20, 2014 .There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,417 @@ package util; import android.content.Context; import android.content.Intent; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnPreparedListener; import android.net.Uri; import android.os.PowerManager; import android.support.v4.content.LocalBroadcastManager; import com.joymeng.alarmclock.application.MyApplication; public class AudioPlayer implements OnCompletionListener, OnPreparedListener, OnErrorListener { private static final String TAG = AudioPlayer.class.getSimpleName(); /** 一个广播ACTION,歌曲开始播放时发送此广播 */ public static final String ACTION_PLAY = AudioPlayer.class.getName() + ".ACTION_PLAY"; /** 一个广播ACTION,歌曲暂停播放时发送此广播 */ public static final String ACTION_PAUSE = AudioPlayer.class.getName() + ".ACTION_PAUSE"; /** 一个广播ACTION,歌曲停止播放发送此广播 */ public static final String ACTION_STOP = AudioPlayer.class.getName() + ".ACTION_STOP"; /** * 歌曲播放的状态(Preparing,Playing,Paused,Stopped) * */ public static final int STATE_STOPPED = 0; // MediaPlayer已经停止工作,不再准备播放 public static final int STATE_PREPARING = 1; // MediaPlayer正在准备中 public static final int STATE_PLAYING = 2; // 正在播放(MediaPlayer已经准备好了) // (但是当丢失音频焦点时,MediaPlayer在此状态下实际上也许已经暂停了, // 但是我们仍然保持这个状态,以表明我们必须在一获得音频焦点时就返回播放状态) public static final int STATE_PAUSED = 3; // 播放暂停 (MediaPlayer处于准备好了的状态) private int mState = STATE_STOPPED; // 丢失音频焦点,我们为媒体播放设置一个低音量(1.0f为最大),而不是停止播放 private static final float DUCK_VOLUME = 0.1f; private Context mContext = null; private AudioFocusHelper mAudioFocusHelper = null; private Uri mPlayingUri = null; private MediaPlayer mMediaPlayer = null; private LocalBroadcastManager mLocalBroadcastManager = null; private AudioPlayer() { mContext = MyApplication.CONTEXT; mAudioFocusHelper = new AudioFocusHelper(mContext, mMusicFocusable); mLocalBroadcastManager = LocalBroadcastManager.getInstance(mContext); } private static AudioPlayer mController = null; public static AudioPlayer getInstance() { if (mController == null) { mController = new AudioPlayer(); } return mController; } public void release() { processStopRequest(true); } /** * 返回当前MediaPlayer的状态 * * @return 当前MediaPlayer的状态 * * */ public int getState() { return mState; } public int getPlayingProgress() { if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { return mMediaPlayer.getCurrentPosition(); } return 0; } public int getDuration() { if (mMediaPlayer != null && mState != STATE_STOPPED) { return mMediaPlayer.getDuration(); } return 0; } public void seekTo(int newPosition) { if (mMediaPlayer != null && (mState == STATE_PLAYING || mState == STATE_PAUSED)) { mMediaPlayer.seekTo(newPosition); } } public Uri getPlayingUri() { return mPlayingUri; } public void processPlayRequest(String filePath) { processPlayRequest(Uri.parse(filePath)); } public void processPlayRequest(Uri uri) { Log.i(TAG, "processPlayRequest"); mAudioFocusHelper.tryToGetAudioFocus(); if (mState == STATE_PAUSED) { configAndStartMediaPlayer(); mState = STATE_PLAYING; } else if (mState != STATE_PREPARING) { playSong(uri); mPlayingUri = uri; } } public void processPauseRequest() { Log.i(TAG, "processPauseRequest"); if (mState == STATE_PLAYING) { mState = STATE_PAUSED; mMediaPlayer.pause(); mLocalBroadcastManager.sendBroadcast(new Intent(ACTION_PAUSE)); } } public void processStopRequest() { processStopRequest(false); } public void processStopRequest(boolean force) { Log.i(TAG, "processStopRequest"); if (mState != STATE_STOPPED || force) { mState = STATE_STOPPED; // 释放所有持有的资源 relaxResources(); mAudioFocusHelper.giveUpAudioFocus(); mLocalBroadcastManager.sendBroadcast(new Intent(ACTION_STOP)); mPlayingUri = null; } } private void playSong(Uri uri) { Log.i(TAG, "playSong"); if (uri == null) { return; } // File file = new File(path); // if (!file.exists() && !file.isDirectory()) { // Log.i(TAG, "source file does not existed"); // processStopRequest(true); // return; // } mState = STATE_STOPPED; try { createMediaPlayerIfNeeded(); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setDataSource(MyApplication.CONTEXT, uri); mState = STATE_PREPARING; // 在后台准备MediaPlayer,准备完成后会调用OnPreparedListener的onPrepared()方法。 // 在MediaPlayer准备好之前,我们不能调用其start()方法 mMediaPlayer.prepareAsync(); } catch (Exception ex) { Log.e("MusicService", "IOException playing next song: " + ex.getMessage()); ex.printStackTrace(); } } /** * 释放本服务所使用的资源, */ private void relaxResources() { // 停止并释放MediaPlayer if (mMediaPlayer != null) { mMediaPlayer.reset(); mMediaPlayer.release(); mMediaPlayer = null; } } /** * 确保MediaPlayer存在,并且已经被重置。 这个方法将会在需要时创建一个MediaPlayer, * 或者重置一个已存在的MediaPlayer。 */ private void createMediaPlayerIfNeeded() { if (mMediaPlayer == null) { mMediaPlayer = new MediaPlayer(); // 确保我们的MediaPlayer在播放时获取了一个唤醒锁, // 如果不这样做,当歌曲播放很久时,CPU进入休眠从而导致播放停止 // 要使用唤醒锁,要确保在AndroidManifest.xml中声明了android.permission.WAKE_LOCK权限 mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK); // 在MediaPlayer在它准备完成时、完成播放时、发生错误时通过监听器通知我们, // 以便我们做出相应处理 mMediaPlayer.setOnPreparedListener(this); mMediaPlayer.setOnCompletionListener(this); mMediaPlayer.setOnErrorListener(this); } else { mMediaPlayer.reset(); } } /** * 根据音频焦点的设置重新设置MediaPlayer的参数,然后启动或者重启它。 如果我们拥有音频焦点,则正常播放; * 如果没有音频焦点,根据当前的焦点设置将MediaPlayer切换为“暂停”状态或者低声播放。 * 这个方法已经假设mPlayer不为空,所以如果要调用此方法,确保正确的使用它。 */ private void configAndStartMediaPlayer() { if (mAudioFocusHelper.getAudioFocus() == AudioFocusHelper.NoFocusNoDuck) { // 如果丢失了音频焦点也不允许低声播放,我们必须让播放暂停,即使mState处于Playing状态。 // 但是我们并不修改mState的状态,因为我们会在获得音频焦点时返回立即返回播放状态。 if (mMediaPlayer.isPlaying()) { mMediaPlayer.pause(); mLocalBroadcastManager.sendBroadcast(new Intent(ACTION_PAUSE)); } return; } else if (mAudioFocusHelper.getAudioFocus() == AudioFocusHelper.NoFocusCanDuck) mMediaPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME); // 设置一个较为安静的音量 else { mMediaPlayer.setVolume(1.0f, 1.0f); // 设置大声播放 } if (!mMediaPlayer.isPlaying()) { mMediaPlayer.start(); mLocalBroadcastManager.sendBroadcast(new Intent(ACTION_PLAY)); } } @Override public final boolean onError(MediaPlayer mp, int what, int extra) { switch (what) { case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: Log.e(TAG, "Error: " + "MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK" + ", extra=" + String.valueOf(extra)); break; case MediaPlayer.MEDIA_ERROR_SERVER_DIED: Log.e(TAG, "Error: " + "MEDIA_ERROR_SERVER_DIED" + ", extra=" + String.valueOf(extra)); break; case MediaPlayer.MEDIA_ERROR_UNKNOWN: Log.e(TAG, "Error: " + "MEDIA_ERROR_UNKNOWN" + ", extra=" + String.valueOf(extra)); break; case -38: Log.e(TAG, "Error: what" + what + ", extra=" + extra); break; default: Log.e(TAG, "Error: what" + what + ", extra=" + extra); break; } processStopRequest(true); mPlayingUri = null; return true; // true表示我们处理了发生的错误 } @Override public final void onPrepared(MediaPlayer mp) { // 准备完成了,可以播放歌曲了 mState = STATE_PLAYING; configAndStartMediaPlayer(); mLocalBroadcastManager.sendBroadcast(new Intent(ACTION_PLAY)); } @Override public final void onCompletion(MediaPlayer mp) { processStopRequest(true); mPlayingUri = null; } private MusicFocusable mMusicFocusable = new MusicFocusable() { @Override public final void onGainedAudioFocus() { Log.i(TAG, "gained audio focus."); // 用新的音频焦点状态来重置MediaPlayer if (mState == STATE_PLAYING) { configAndStartMediaPlayer(); } } @Override public final void onLostAudioFocus() { Log.i(TAG, "lost audio focus."); // 以新的焦点参数启动/重启/暂停MediaPlayer if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { configAndStartMediaPlayer(); } } }; public interface MusicFocusable { /** Signals that audio focus was gained. */ public void onGainedAudioFocus(); /** Signals that audio focus was lost. */ public void onLostAudioFocus(); } /** * Convenience class to deal with audio focus. This class deals with * everything related to audio focus: it can request and abandon focus, and * will intercept focus change events and deliver them to a MusicFocusable * interface (which, in our case, is implemented by {@link MusicService}). * * This class can only be used on SDK level 8 and above, since it uses API * features that are not available on previous SDK's. */ class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener { /** * Represents something that can react to audio focus events. We * implement this instead of just using * AudioManager.OnAudioFocusChangeListener because that interface is * only available in SDK level 8 and above, and we want our application * to work on previous SDKs. */ AudioManager mAM; MusicFocusable mFocusable; // do we have audio focus? public static final int NoFocusNoDuck = 0; // we don't have audio focus, // and // can't duck public static final int NoFocusCanDuck = 1; // we don't have focus, but // can // play at a low volume // ("ducking") public static final int Focused = 2; // we have full audio focus private int mAudioFocus = NoFocusNoDuck; public AudioFocusHelper(Context ctx, MusicFocusable focusable) { if (android.os.Build.VERSION.SDK_INT >= 8) { mAM = (AudioManager) ctx .getSystemService(Context.AUDIO_SERVICE); mFocusable = focusable; } else { mAudioFocus = Focused; // no focus feature, so we always "have" // audio focus } } /** Requests audio focus. Returns whether request was successful or not. */ public boolean requestFocus() { return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAM .requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); } /** Abandons audio focus. Returns whether request was successful or not. */ public boolean abandonFocus() { return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAM .abandonAudioFocus(this); } public void giveUpAudioFocus() { if (mAudioFocus == Focused && android.os.Build.VERSION.SDK_INT >= 8 && abandonFocus()) mAudioFocus = NoFocusNoDuck; } public void tryToGetAudioFocus() { if (mAudioFocus != Focused && android.os.Build.VERSION.SDK_INT >= 8 && requestFocus()) mAudioFocus = Focused; } /** * Called by AudioManager on audio focus changes. We implement this by * calling our MusicFocusable appropriately to relay the message. */ public void onAudioFocusChange(int focusChange) { if (mFocusable == null) return; switch (focusChange) { case AudioManager.AUDIOFOCUS_GAIN: mAudioFocus = Focused; mFocusable.onGainedAudioFocus(); break; case AudioManager.AUDIOFOCUS_LOSS: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: mAudioFocus = NoFocusNoDuck; mFocusable.onLostAudioFocus(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: mAudioFocus = NoFocusCanDuck; mFocusable.onLostAudioFocus(); break; default: } } public int getAudioFocus() { return mAudioFocus; } } }