Skip to content

Instantly share code, notes, and snippets.

@AvatarQing
Last active December 3, 2020 17:01
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save AvatarQing/9106775 to your computer and use it in GitHub Desktop.
Save AvatarQing/9106775 to your computer and use it in GitHub Desktop.
MediaPlayer播放音频封装类
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