今天被问到视频播放,录音的问题,又想起了曾经开发音视频播放,遇到的那些坑。其实多媒体的知识比较多,客户端开发难度也大,毕竟android应用层用java开发,对于音频开发,最好是有强大的C/C++功底才好研究底层,任重道远。
一起学习吧,先从音乐播放器开始。

一,先贴后台播放音乐的service部分代码

不一定要这样写,仅供参考。

/**
 * 可播放音视频的 MediaPlayerService duqian
 * 既可以activity绑定该service,也可以直接start,实现后台播放音乐
 * 2016/01/08
 */
public class AudioPlayerService extends Service {
    private static final String TAG = AudioPlayerService.class.getSimpleName();
    private MyBinder myBinder;
    private String music_name;
    public AudioPlayerService() {
        super();
    }
    private static VideoView mediaPlayer;

    /**
     * 初始化播放器控件
     * @param videoView
     */
    private void initPlayer(VideoView videoView) {
        mediaPlayer = videoView;
    }

    /**
     * 播放音乐
     */
    public void play(){
        if(mediaPlayer != null){
            mediaPlayer.start();
            notifyChange(MediaStaticVar.ACTION_PREPARED, MediaStaticVar.DATA_PREPARED);
            //showNotification();
        }
    }

    /**
     * 暂停音乐
     */
    public void pause(){
        if(mediaPlayer != null){
            mediaPlayer.pause();
        }
        //stopNotification();
    }

    /**
     * 跳到指定播放位置
     * @param progress
     */
    private void seekTo(int progress) {
        MediaStaticVar.progress = progress;
        if(mediaPlayer != null){
            mediaPlayer.seekTo(progress);
        }
    }

    //是否正在播放
    public boolean isPlaying(){
        boolean playing =false;
        if(mediaPlayer != null){
            playing= mediaPlayer.isPlaying();
        }
        return playing;
    }
    //总时长
    private long getDuration(){
        long duration = 1000*60*6;
        if (mediaPlayer!=null){
            duration = mediaPlayer.getDuration();
        }
        return duration;
    }
    //当前时长
    private long getCurrentPosition(){
        long currentPosition = 0;
        if (mediaPlayer!=null){
            currentPosition = mediaPlayer.getCurrentPosition();
        }
        return currentPosition;
    }

    /**
     * 创建播放器
     * @param path
     */
    private void startPlay(String path,Context context) {
        if (path == null||TextUtils.isEmpty(path)) {
            return;
        } else {
            MediaStaticVar.path = path;
            if (mediaPlayer==null){
                LogUtils.debug(TAG, "mediaPlayer==null");
                mediaPlayer = new VideoView(context);
            }
            mediaPlayer.setVideoPath(path);
            mediaPlayer.seekTo(MediaStaticVar.progress);//上次播放的位置
            mediaPlayer.requestFocus();
            mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mediaPlayer) {
                    mediaPlayer.setPlaybackSpeed(1.0f);
                    play();
                    //notifyChange(MediaStaticVar.ACTION_PREPARED, MediaStaticVar.DATA_PREPARED);
                }
            });
            mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mp) {
                    notifyChange(MediaStaticVar.ACTION_PREPARED,MediaStaticVar.DATA_COMPLETED);
                }
            });
            mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
                @Override
                public boolean onError(MediaPlayer mp, int what, int extra) {
                    notifyChange(MediaStaticVar.ACTION_PREPARED,MediaStaticVar.DATA_ERROR);
                    LogUtils.debug(what + "" + extra + "error");
                    return true;
                }
            });
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        LogUtils.debug(TAG,"onBind");
        myBinder = new MyBinder();
        return myBinder;//使用Binder对象,要返回
    }


    /**
     * 定义一个类 继承 Binder,定义一个对象和调用者通信
     */
    private class MyBinder extends Binder implements IMusicPlayerService
    {

        @Override
        public void callInitPlayer(VideoView videoView) {
            initPlayer(videoView);
        }

        @Override
        public void callPlayer(String path, Context mContext) {
            startPlay(path,mContext);
        }

        @Override
        public void callPause() {
            pause();
        }

        @Override
        public void callPlay() {
            play();
        }

        @Override
        public boolean callIsPlaying() {
            return isPlaying();
        }

        @Override
        public void callSeekTo(int progress) {
            seekTo(progress);
        }

        @Override
        public void callPrevious() {

        }

        @Override
        public void callNext() {

        }

        @Override
        public long callGetDuration() {
            return getDuration();
        }

        @Override
        public long callGetCurrentPosition() {
            return getCurrentPosition();
        }

        @Override
        public void callNotifyChange(String nofity, int action) {
            notifyChange(nofity,action);
        }

        @Override
        public void stopNotification() {
            stopNotification();
        }
    }


    @Override
    public boolean onUnbind(Intent intent) {
        LogUtils.debug(TAG,"on  unbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mediaPlayer != null) {
            try {
                mediaPlayer.stopPlayback();
                mediaPlayer = null;
                LogUtils.debug(TAG,"mediaPlayer stop");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        stopNotification();
        MediaStaticVar.progress = 0;
        MediaStaticVar.path = "";
    }
    /**
     * 发消息
     * @param nofity
     */
    private void notifyChange(String nofity,int action){
        Intent intent = new Intent();
        if (nofity==null) {
            intent.setAction(MediaStaticVar.ACTION_PREPARED);
        }else{
            intent.setAction(nofity);
        }
        intent.putExtra("data",action);
        sendBroadcast(intent);//发广播
    }
}

二,IMusicPlayerService接口

如果activity绑定了该service,控制后台音乐的播放,暂停,下一曲,上一曲。那么可以写一个类似的接口, 定义一个类继承 Binder,实现IMusicPlayerService接口,以便service和调用者通信。

public interface IMusicPlayerService {
    public void callInitPlayer(VideoView videoView);
    public void callPlayer(String path,Context mContext);
    public void callPause();
    public void callPlay();
    public boolean callIsPlaying();
    public void callSeekTo(int progress);
    public void callPrevious();
    public void callNext();
    public long callGetDuration();
    public long callGetCurrentPosition();
    public void callNotifyChange(String nofity,int action);
    public void stopNotification();
}

三,启动/绑定service

以下代码不全,仅供参考。

//开启服务
    private IMusicPlayerService iservice; //服务
    private MyConn conn;
    private void startService(Class clazz) {
        //1.开启服务,与本activity不绑定
         Intent service = new Intent(context, clazz);
 service.setPackage(context.getPackageName());//这里你需要设置你应用的包名,5.0新要求
        context.startService(service);//多次调用startService并不会启动多个service 而是会多次调用onStart
    //2.开启服务与本activity绑定
        Intent intent = new Intent(this,clazz);
        conn = new MyConn();
        bindService(intent,conn,Context.BIND_AUTO_CREATE);
    }

    private boolean isServiceConnected;
    //定义绑定服务的接口
    private class MyConn implements ServiceConnection{
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //当绑定服务成功后,会回调此方法
            isServiceConnected = true;
            iservice = (IMusicPlayerService)service;
           iservice.callPlayer(MediaStaticVar.path,context);//高清音乐,开始播放
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //当取消绑定服务成功后,会回调这个方法
            iservice = null;
        }
    }

四,MediaPlayer相关介绍

对于背景音乐,将MediaPlayer封装在Service中即可实现。
MediaPlayer支持元数据、音频文件、音频流等形式的源数据的播放。
(1)播放元数据
所谓元数据即raw文件夹下的资源文件,调用方法如下:

mMediaPlayer=MediaPlayer.create(this, R.raw.test_cbr);
mMediaPlayer.start();

(2)播放音频文件
对于本地文件,其数据源可以通过Uri来表示,在开始播放前需要设置播放类型并加载缓冲,示例如下:

Uri myUri="/path/to/localfile";
MediaPlayer mediaPlayer=new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();    //加载缓冲
mediaPlayer.start();

(3)播放流
MediaPlayer支持基于网络的流播放,其支持的网络协议包括RSTP(RTP、SDP)、HTTP渐进流(HTTP Progressive Streaming)、HTTP生活流(HTTP Live Streaming Draft Protocol,仅Honeycomb版本中用到)等。Android4.0开始支持HTTPS协议,在网络播放和本地播放上略有不同,示例如下:

String url="http://duqian.net.cn/demo.mp3;
MediaPlayer mediaPlayer=new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);                                      mediaPlayer.setDataSource(url);
mediaPlayer.prepare();    //加载缓冲
mediaPlayer.start();

五,mediaPlayer设置监听

准备完成,播放完成,发生播放错误等,都可以监听。
不能在UI主线程中调用prepare方法。应通过prepareAsync方法将加载缓冲的工作放置在非UI主线程中进行,当准备工作完成时,MediaPlayer通过MediaPlayer.OnPreparedListener监听器可以监听到该事件。设置监听器的方法如下:

MediaPlayer.OnPreParedListener mPreparedListener=new MediaPlayer.OnPreparedListener(){
    public void onPrepared(MediaPlayer mp){                                                               
        mediaPlayer.start();                                                        
        }
}

当播放结束时,通过MediaPlayer.OnCompletionListener可以监听到播放结束的消息,示例如下:

mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener(){
public void onCompletion(MediaPlayer mediaPlayer){
//stop  or next
}
});

MediaPlayer还存在一个出错的状态,即MEDIA_PLAYER_STATE_ERROR。监听出错的监听器为MediaPlayer.OnErrorListener。

mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
   @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
    LogUtils.debug(what + "" + extra + "error");
    return true;
    }
});

六,个人的一点总结

其实android 系统API中的mediaPlayer使用起来还是挺方便的。当然也可以使用第三方多媒体框架,实现音视频的播放。
杜乾,Dusan,