Android SDK 提供的3套音频播放的API之玩转MediaPlayer

1,551 阅读9分钟

欢迎关注:「Android进化之路」

前言

Android SDK 提供了3套音频播放的API,分别是:MediaPlayer,SoundPool,AudioTrack,本文重点说下MediaPlayer。

Android 多媒体框架支持播放各种常见媒体类型,以便您轻松地将音频、视频和图片集成到应用中。 image

一、MediaPlayer使用方式

可以使用 MediaPlayer API,播放存储在应用资源(原始资源)内的媒体文件、文件系统中的独立文件或者通过网络连接获得的数据流中的音频或视频。

MediaPlayer对象能够获取、解码以及播放音频和视频,它支持多种不同的媒体源。

1、MediaPlayer加载音频的方式

1.1、静态方式

MediaPlayer提供的静态方式加载音频文件如下

1.1.1、第一种方法:

参数resid一般是我们在资源文件夹res/raw(该文件夹需要自己创建)存放的媒体文件id

public static MediaPlayer create(Context context, int resid);

下面代码时如何播放作为本地原始资源(保存在应用的 res/raw/ 目录中)提供的音频

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
    
1.1.2、第二种方法:

参数Uri,常用的有使用Uri指向网络资源,也可指向本地文件。

public static MediaPlayer create(Context context, Uri uri);
1.2、 动态方式
1.2.1、setDataSource (String path)

通过 HTTP 流式传输并播放远程网址上的内容或者本地SD卡音频文件地址播放

// Sets the data source (file-path or http/rtsp URL) to use.

//String url = "http://........"; //从网路加载音乐
String url = "../music/samsara.mp3"; //从sd卡中加载音乐

MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();
    
1.2.2、setDataSource(AssetFileDescriptor afd) & setDataSource (FileDescriptor fd, long offset, long length)

播放assets目录下的音频文件

setDataSource(AssetFileDescriptor afd)

//将音频资源文件放在assets文件夹
AssetFileDescriptor fd = getAssets().openFd("music.mp3");
 
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.prepare() ;

注意:setDataSource(AssetFileDescriptor afd)针对API版本有要求,SDK版本必须是24以上才可以用 所以播放assets目录下的音频文件建议使用下面的方法

setDataSource (FileDescriptor fd, long offset, long length)

//需将资源文件放在assets文件夹
AssetFileDescriptor fd = getAssets().openFd("music.mp3");

MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(fd, fd.getStartOffset(), fd.getLegth())
mediaPlayer.prepare() ;

1.2.3、setDataSource (Context context, Uri uri)

此方法一般通过ContentProvider获取Android系统提供 的共享music获取uri,然后设置数据播放

Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
    
  • 总结:
  • 1、静态方式加载简单,但每一次调用都会创建一个MediaPlayer对象,适合一次性播放的任务场景。
  • 2、当使用MediaPlayer进行循环播放的时候,不适合使用静态方式了,可以使用动态加载的方式,使用一个MediaPlayer对象去循环加载资源文件。
  • 3、需要注意的是使用动态加载之后一定要调用prepare()方法去预装文件

二、MediaPlayer音频播放封装

1、MediaPlayer的控制方法

MediaPlayer有三个主要的方法进行播放控制

  • start():开始或者恢复播放
  • stop():停止播放
  • pause():暂停播放

2、MediaPlayer音频播放封装

2.1、MediaPlayer的播放控制

创建MediaManager,类管理MediaPlayer添加播放控制方法 参数:需要播放音频的地址 & 听播放完成的监听

public  class MediaManager {
    public static MediaPlayer mMediaPlayer;
    private static boolean isPause;
    
    public  void playSound(String filePath, MediaPlayer.OnCompletionListener onCompletionListener){
        if(mMediaPlayer == null){
            mMediaPlayer = new MediaPlayer();
        }else{
            mMediaPlayer.reset();
        }
        try {
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setOnCompletionListener(onCompletionListener);
            mMediaPlayer.setDataSource(filePath);
            mMediaPlayer.prepare();
            mMediaPlayer.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
}
2.2、MediaPlayer的状态控制
//暂停播放控制
public void pause(){
    if(mMediaPlayer != null && mMediaPlayer.isPlaying()){
        mMediaPlayer.pause();
        isPause = true;
    }
}
//重新播放控制
public void resume(){
    if(mMediaPlayer != null && isPause){
        mMediaPlayer.start();
        isPause = false;
    }
}
2.3、MediaPlayer的释放
  • MediaPlayer 会占用宝贵的系统资源。
  • 因此,您应该始终采取额外的预防措施,确保 MediaPlayer 实例保留的时间不会过长。
  • 完成该操作后,您应始终调用 release() 以确保分配给它的所有系统资源均已正确释放。
public void release(){
    if(mMediaPlayer != null){
        mMediaPlayer.release();
        mMediaPlayer = null;
    }
}

即如下完整代码:

public  class MediaManager {

    public static MediaPlayer mMediaPlayer;
    private static boolean isPause;

    public  void playSound(String filePath, MediaPlayer.OnCompletionListener onCompletionListener){
        if(mMediaPlayer == null){
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
                @Override
                public boolean onError(MediaPlayer mp, int what, int extra) {
                    mMediaPlayer.reset();
                    return false;
                }
            });
        }else{
            mMediaPlayer.reset();
        }
        try {
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setOnCompletionListener(onCompletionListener);
            mMediaPlayer.setDataSource(filePath);
            mMediaPlayer.prepare();
            mMediaPlayer.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void pause(){
        if(mMediaPlayer != null && mMediaPlayer.isPlaying()){
            mMediaPlayer.pause();
            isPause = true;
        }
    }

    public void resume(){
        if(mMediaPlayer != null && isPause){
            mMediaPlayer.start();
            isPause = false;
        }
    }

    public void release(){
        if(mMediaPlayer != null){
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
    }
}

三、MediaPlayer异步初始化

  • 原则上,使用 MediaPlayer 会非常简单。但务必注意,要正确地将其与正式音频播放需求的 Android 应用集成,还需执行一些额外操作。
  • 如对 prepare() 的调用可能需要很长时间来执行,因为它可能涉及获取和解码媒体数据。
  • 因此,与任何可能需要很长时间来执行的方法一样,切勿从应用的界面线程中调用它。
  • 这样做会导致界面有时会明显的暂停,直到系统返回该方法,这是一种非常糟糕的用户体验,并且可能会导致 ANR(应用无响应)错误。

考虑到网络情况的发杂性,以及获取数据和解码的时间比较长,不推荐在播放流时通过 prepare() 方法加载缓冲,尤其不能再UI主线程中调用。

MediaPlayer框架通过 prepareAsync() 方法提供了一种完成此任务的便捷方式。

此方法会在后台开始准备媒体,并立即返回。

当媒体准备就绪后,系统会调用通过 setOnPreparedListener() 配置的 MediaPlayer.OnPreparedListener 的 onPrepared() 方法。

1、状态管理

在编写与 MediaPlayer 对象交互的代码时,可根据其内部的状态进行相应的控制。如下图状态管理:

MediaPlayer内部状态

  • 1、当您创建新的 MediaPlayer 时,它处于“Idle”状态。
  • 2、此时,您应该通过调用 setDataSource() 初始化该类,使其处于“Initialized”状态。
  • 3、然后,您必须使用 prepare() 或 prepareAsync() 方法完成准备工作。
  • 4、当 MediaPlayer 准备就绪后,它便会进入“Prepared”状态,这也意味着您可以通过调用 start() 使其播放媒体内容。
  • 5、此时,如图所示,您可以通过调用 start()、pause() 和 seekTo() 等方法在“Started”、“Paused”和“PlaybackCompleted”状态之间切换。
  • 6、不过请注意,当您调用 stop() 时,除非您再次准备 MediaPlayer,否则将无法再次调用 start()。

2、事件监听

在MediaPlayer中主要有四个常用的监听方法:

2.1、准备事件监听:OnPreparedListener

上面提到的系统会调用通过 setOnPreparedListener() 配置的 MediaPlayer.OnPreparedListener 的 onPrepared() 方法。

OnPreparedListener接口源码:

/**
 * Interface definition for a callback to be invoked when the media
 * source is ready for playback.
 */
public interface OnPreparedListener
{
    /**
     * Called when the media file is ready for playback.
     *
     * @param mp the MediaPlayer that is ready for playback
     */
    void onPrepared(MediaPlayer mp);
}

使用方式:

mMediaPlayer.setOnPreparedListener(this);
2.2、完成监听事件:OnCompletionListener

媒体文件播放完毕时回调,系统会调用通过 setOnCompletionListener() 配置的 MediaPlayer.OnCompletionListener 的 onCompletion() 方法。

OnCompletionListener接口源码:

/**
 * Interface definition for a callback to be invoked when playback of
 * a media source has completed.
 */
public interface OnCompletionListener
{
    /**
     * Called when the end of a media source is reached during playback.
     *
     * @param mp the MediaPlayer that reached the end of the file
     */
    void onCompletion(MediaPlayer mp);
}

使用方式:

mMediaPlayer.setOnCompletionListener(this);
2.3、网络源流加载状态监听事件:OnBufferingUpdateListener

加载网络资源音频时缓存进度监听,系统会调用通过 setOnBufferingUpdateListener() 配置的 MediaPlayer.OnBufferingUpdateListener 的 onBufferingUpdate() 方法。

OnBufferingUpdateListener接口源码:

/**
 * Interface definition of a callback to be invoked indicating buffering
 * status of a media resource being streamed over the network.
 */
public interface OnBufferingUpdateListener
{
    /**
     * Called to update status in buffering a media stream received through
     * progressive HTTP download. The received buffering percentage
     * indicates how much of the content has been buffered or played.
     * For example a buffering update of 80 percent when half the content
     * has already been played indicates that the next 30 percent of the
     * content to play has been buffered.
     *
     * @param mp      the MediaPlayer the update pertains to
     * @param percent the percentage (0-100) of the content
     *                that has been buffered or played thus far
     */
    void onBufferingUpdate(MediaPlayer mp, int percent);
}

使用方式:

mMediaPlayer.setOnBufferingUpdateListener(this);
2.4、错误监听事件:OnErrorListener

当播放过程中发生错误时进行回调。,系统会调用通过 setOnErrorListener() 配置的 MediaPlayer.OnErrorListener 的 onError() 方法。

OnErrorListener接口源码:

/**
 * Interface definition of a callback to be invoked when there
 * has been an error during an asynchronous operation (other errors
 * will throw exceptions at method call time).
 */
public interface OnErrorListener
{
    /**
     * Called to indicate an error.
     *
     * @param mp      the MediaPlayer the error pertains to
     * @param what    the type of error that has occurred:
     * <ul>
     * <li>{@link #MEDIA_ERROR_UNKNOWN}
     * <li>{@link #MEDIA_ERROR_SERVER_DIED}
     * </ul>
     * @param extra an extra code, specific to the error. Typically
     * implementation dependent.
     * <ul>
     * <li>{@link #MEDIA_ERROR_IO}
     * <li>{@link #MEDIA_ERROR_MALFORMED}
     * <li>{@link #MEDIA_ERROR_UNSUPPORTED}
     * <li>{@link #MEDIA_ERROR_TIMED_OUT}
     * <li><code>MEDIA_ERROR_SYSTEM (-2147483648)</code> - low-level system error.
     * </ul>
     * @return True if the method handled the error, false if it didn't.
     * Returning false, or not having an OnErrorListener at all, will
     * cause the OnCompletionListener to be called.
     */
    boolean onError(MediaPlayer mp, int what, int extra);
}

使用方式:

mMediaPlayer.setOnErrorListener(this);

四、MediaPlayer音频播放状态管理&监听的封装

根据如上总结,对MediaPlayer播放状态管理&监听封装,源码如下:

/**
 * 音频播放控制类
 */
public class Mp3Player implements OnPreparedListener, OnCompletionListener, OnErrorListener, OnBufferingUpdateListener {
    /**
     * 状态-未初始化
     */
    public static final int STATE_UNINITIALIZED = 0;
    /**
     * 状态-初始化完毕
     */
    public static final int STATE_INITIALIZED = 1;
    /**
     * 状态-准备完毕
     */
    public static final int STATE_PREPARED = 2;
    /**
     * 状态-播放
     */
    public static final int STATE_PLAYING = 3;
    /**
     * 状态-暂停
     */
    public static final int STATE_PAUSE = 4;
    /**
     * 状态-停止
     */
    public static final int STATE_STOP = 5;

    public int mState;
    private int mSampleTime;

    private static Mp3Player mInstance;
    public MediaPlayer mMediaPlayer;
    private AudioPlayerListener mAudioPlayerListener;

    private final Handler mHandler;
    private Runnable mUndatePlayPositionRunnable = new Runnable() {

        @Override
        public void run() {
            undatePosition();
        }
    };
    public String path;

    public void setmAudioPlayerListener(AudioPlayerListener mAudioPlayerListener) {
        this.mAudioPlayerListener = mAudioPlayerListener;
    }

    private Mp3Player() {
        super();
        mState = STATE_UNINITIALIZED;
        mSampleTime = 100;

        if (Looper.myLooper() == null) {
            Looper.prepare();
            mHandler = new Handler();
            Looper.loop();
        } else {
            mHandler = new Handler();
        }
    }

    public static Mp3Player getInstance() {
        mInstance = new Mp3Player();
        return mInstance;
    }

    /**
     * 初始化MediaPlayer
     */
    public void init(AudioPlayerListener listener) {
        if (mState == STATE_UNINITIALIZED) {
            if (listener == null) {
                throw new RuntimeException("AudioPlayerListener not null");
            }
            if (mMediaPlayer == null) {
                mMediaPlayer = new MediaPlayer();
            }
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setOnPreparedListener(this);
            mMediaPlayer.setOnCompletionListener(this);
            mMediaPlayer.setOnBufferingUpdateListener(this);
            mMediaPlayer.setOnErrorListener(this);
            mState = STATE_INITIALIZED;
        }
        this.mAudioPlayerListener = listener;
    }


    public boolean isPlaying(){
        if (mMediaPlayer!=null){
            return mMediaPlayer.isPlaying();
        }else {
            return false;
        }
    }

    /**
     * 设置音频源(异步)
     *
     * @param path
     * @return 返回Duration
     * @throws IllegalArgumentException
     * @throws SecurityException
     * @throws IllegalStateException
     * @throws IOException
     */
    public boolean setDataSource(String path) throws IllegalArgumentException, SecurityException, IllegalStateException, IOException {
        this.path = path;
        if (mState == STATE_UNINITIALIZED) {
            throw new RuntimeException("设置音频源之前请进行初始化");
        }
        if (mState == STATE_PAUSE || mState == STATE_PLAYING) {
            stop();
        }
        if (TextUtils.isEmpty(path)) {
            return false;
        }
        mMediaPlayer.reset();
        mMediaPlayer.setDataSource(path);
        mMediaPlayer.prepareAsync();
        return true;
    }


    public String getPath() {
        return path;
    }

    /**
     * 设置音频源(异步)
     *
     * @param fileDescriptor
     * @return 返回Duration
     * @throws IllegalArgumentException
     * @throws SecurityException
     * @throws IllegalStateException
     * @throws IOException
     */
    public boolean setDataSource(AssetFileDescriptor fileDescriptor) throws IllegalArgumentException, SecurityException, IllegalStateException, IOException {

        if (mState == STATE_UNINITIALIZED) {
            throw new RuntimeException("设置音频源之前请进行初始化");
        }
        if (mState == STATE_PAUSE || mState == STATE_PLAYING) {
            stop();
        }
        if (fileDescriptor == null) {
            return false;
        }
        mMediaPlayer.reset();
        mMediaPlayer.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(),
                fileDescriptor.getLength());
        mMediaPlayer.prepareAsync();
        return true;
    }

    public void play() throws IllegalStateException, IOException {
        if (mState == STATE_UNINITIALIZED) {
            throw new RuntimeException("播放前请进行初始化");
        }
        if (mState == STATE_INITIALIZED) {
            throw new RuntimeException("播放前请设置音频源");
        }
        if (!mMediaPlayer.isPlaying()) {
            mMediaPlayer.start();
            mState = STATE_PLAYING;
            undatePosition();
        }
    }

    public void pause() {
        try {
            if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
                mMediaPlayer.pause();
                mState = STATE_PAUSE;
            }
        } catch (Exception e) {

        }
    }

    public void stop() {
        if (mMediaPlayer != null && (mMediaPlayer.isPlaying() || mState == STATE_PAUSE)) {
            mMediaPlayer.stop();
            mState = STATE_STOP;
        }
    }

    public void release() {
        mState = STATE_UNINITIALIZED;
        stop();
        if (mMediaPlayer != null) {
            mMediaPlayer.release();
        }
        mMediaPlayer = null;
        mAudioPlayerListener = null;
        mInstance = null;
    }

    public void seekTo(int msec) {
        if (mMediaPlayer != null) {
            mMediaPlayer.seekTo(msec);
        }
    }

    /**
     * 获取当前播放位置
     *
     * @return
     */
    public int getCurrentPostion() {
        return mMediaPlayer == null ? -1 : mMediaPlayer.getCurrentPosition();
    }

    /**
     * 获取总时间
     *
     * @return
     */
    public int getDuration() {

        return mMediaPlayer == null ? -1 : mMediaPlayer.getDuration();
    }

    /**
     * 获取当前状态
     *
     * @return
     */
    public int getState() {
        return mState;
    }

    /**
     * 更新进度
     */
    private void undatePosition() {
        if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
            mAudioPlayerListener.onUpdateCurrentPosition(mMediaPlayer.getCurrentPosition());
            mHandler.postDelayed(mUndatePlayPositionRunnable, mSampleTime);
        }
    }

    @Override
    public void onBufferingUpdate(MediaPlayer mp, int percent) {
        mAudioPlayerListener.onBufferingUpdate(mp, percent);
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        mState = STATE_PREPARED;
        mAudioPlayerListener.onPrepared();
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        mState = STATE_STOP;
        mAudioPlayerListener.onCompletion();
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        mAudioPlayerListener.onError(mp, what, extra);
        return true;
    }

    public void reset() {
        if (mMediaPlayer!=null){
            mMediaPlayer.reset();
        }
    }

    public interface AudioPlayerListener {
        /**
         * AudioPlayer准备完成时回调
         */
        void onPrepared();

        /**
         * AudioPlayer播放完成时回调
         */
        void onCompletion();

        /**
         * AudioPlayer播放期间每个设置的取样时间间隔回调一次
         *
         * @param position 当前播放位置
         */
        void onUpdateCurrentPosition(int position);

        /**
         * 缓存进度回调
         *
         * @param mp
         * @param percent
         */
        void onBufferingUpdate(MediaPlayer mp, int percent);

        /**
         * Called to indicate an error.
         *
         * @param mp
         * @param what
         * @param extra
         */
        void onError(MediaPlayer mp, int what, int extra);

    }
}


欢迎关注:「Android进化之路」