Skip to content

Instantly share code, notes, and snippets.

@AvatarQing
Last active December 3, 2020 17:01

Revisions

  1. Avatar Qing revised this gist Mar 2, 2016. 1 changed file with 449 additions and 537 deletions.
    986 changes: 449 additions & 537 deletions AudioPlayer.java
    Original file line number Diff line number Diff line change
    @@ -1,548 +1,460 @@
    package utils;

    import java.io.FileDescriptor;
    package com.hk.agg.im.utils;

    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 android.text.TextUtils;
    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));
    }
    }

    /**
    * 返回当前MediaPlayer的状态
    *
    * @return 当前MediaPlayer的状态
    *
    * */
    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
    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);

    // 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() {

    @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;
    }
    }
    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);
    }
  2. Avatar Qing revised this gist Feb 20, 2014. 1 changed file with 205 additions and 74 deletions.
    279 changes: 205 additions & 74 deletions AudioPlayer.java
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,6 @@
    package util;
    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 com.joymeng.alarmclock.application.MyApplication;
    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 = AudioPlayer.class.getName()
    + ".ACTION_PLAY";
    public static final String ACTION_PLAY = ACTION_PREFFIX + ".ACTION_PLAY";

    /** 一个广播ACTION,歌曲暂停播放时发送此广播 */
    public static final String ACTION_PAUSE = AudioPlayer.class.getName()
    + ".ACTION_PAUSE";
    public static final String ACTION_PAUSE = ACTION_PREFFIX + ".ACTION_PAUSE";

    /** 一个广播ACTION,歌曲停止播放发送此广播 */
    public static final String ACTION_STOP = AudioPlayer.class.getName()
    + ".ACTION_STOP";
    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为最大),而不是停止播放
    /** 丢失音频焦点,我们为媒体播放设置一个低音量(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;
    /** 需要的上下文环境,引用的是一个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;

    public static AudioPlayer getInstance() {
    /**
    * 获取音频控制器实例
    *
    * @param context
    * 上下文环境
    */
    public static AudioPlayer getInstance(Context context) {
    if (mController == null) {
    mController = new AudioPlayer();
    mController = new AudioPlayer(context);
    }
    return mController;
    }

    public void release() {
    processStopRequest(true);
    /**
    * 请求播放音频
    *
    * @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);
    }
    }

    public Uri getPlayingUri() {
    return mPlayingUri;
    }

    public void processPlayRequest(String filePath) {
    processPlayRequest(Uri.parse(filePath));
    }
    /**
    * 请求播放音频,提供两个数据源,哪个不为空就用哪个播放
    *
    * @param uri
    * 音频文件的URI,音频可以来自存储卡、ContentProvider或者网络。
    * @param fd
    * 音频文件的文件描述符,音频可以来自原assets目录下。
    * @param loop
    * 是否循环播放
    */
    private void processPlayRequest(Uri uri, FileDescriptor fd, boolean loop) {
    Log.i(TAG, "processPlayRequest()");

    public void processPlayRequest(Uri uri) {
    Log.i(TAG, "processPlayRequest");
    mAudioFocusHelper.tryToGetAudioFocus();
    if (mState == STATE_PAUSED) {
    mAudioFocusHelper.tryToGetAudioFocus();
    configAndStartMediaPlayer();
    mState = STATE_PLAYING;
    } else if (mState != STATE_PREPARING) {
    playSong(uri);
    mPlayingUri = uri;
    mAudioFocusHelper.tryToGetAudioFocus();
    if (uri != null) {
    play(uri, loop);
    } else if (fd != null) {
    play(fd, loop);
    }
    }
    }

    public void processPauseRequest() {
    Log.i(TAG, "processPauseRequest");
    if (mState == STATE_PLAYING) {
    mState = STATE_PAUSED;
    mMediaPlayer.pause();
    mLocalBroadcastManager.sendBroadcast(new Intent(ACTION_PAUSE));
    }
    /**
    * 播放音频
    *
    * @param uri
    * 音频来源
    * @param looping
    * 是否循环播放
    */
    private void play(Uri uri, boolean looping) {
    play(uri, null, looping);
    }

    public void processStopRequest() {
    processStopRequest(false);
    /**
    * 播放音频
    *
    * @param fd
    * 音频文件的文件描述符
    * @param looping
    * 是否循环播放
    */
    private void play(FileDescriptor fd, boolean looping) {
    play(null, fd, looping);
    }

    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;
    }
    }
    /**
    * 播放音频,提供两个音频数据来源,哪个不为空就用哪个
    *
    * @param uri
    * 音频来源
    * @param fd
    * 音频文件的文件描述符
    * @param looping
    * 是否循环播放
    */
    private void play(Uri uri, FileDescriptor fd, boolean looping) {
    Log.i(TAG, "play()");

    private void playSong(Uri uri) {
    Log.i(TAG, "playSong");
    if (uri == null) {
    return;
    // 检查数据源是否有效
    if (uri == null && fd == null) {
    Log.d(TAG, "Invalid data source.Failed to play.");
    }

    // 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 {
    // 尝试初始化MediaPlayer
    createMediaPlayerIfNeeded();
    mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);

    mMediaPlayer.setDataSource(MyApplication.CONTEXT, uri);
    // 设置播放数据源
    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("MusicService",
    "IOException playing next song: " + ex.getMessage());
    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); // 设置一个较为安静的音量
    // 设置一个较为安静的音量
    mMediaPlayer.setVolume(DUCK_VOLUME, DUCK_VOLUME);
    else {
    mMediaPlayer.setVolume(1.0f, 1.0f); // 设置大声播放
    // 设置大声播放
    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);
    mPlayingUri = null;
    return true; // 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);
    mPlayingUri = null;
    }

    private MusicFocusable mMusicFocusable = new MusicFocusable() {
  3. Avatar Qing created this gist Feb 20, 2014.
    417 changes: 417 additions & 0 deletions AudioPlayer.java
    Original 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;
    }
    }
    }