Last active
December 3, 2020 17:01
-
-
Save AvatarQing/9106775 to your computer and use it in GitHub Desktop.
MediaPlayer播放音频封装类
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 characters
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); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment