Android音视频开发系列-MediaPlayer开发问题汇总

1,137 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

前言

在实现MediaPlayer简单使用后在实际开发中还是遇到不少问题,这里对问题做总结以记录原因和解决办法算是开发归纳了。

问题汇总

TrackInfo资源信息查询

在开发过程中加载资源并执行prepareAsync方法后在回调方法中获取资源TrackInfo。原本希望通过该方法获取到视频尺寸信息,例如MediaExtractor通过方法getTrackFormat来获取资源信息。 image.png TrackInfo内部可获取到MediaFormat对象,但在实际源码中会发现返回MediaFormat是个空。

public MediaFormat getFormat() {
    if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT
            || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
        return mFormat;
    }
    return null;
}

因此在使用MediaPlayer内部估计无法直接获取到资源文件则只能通过MediaExtractor外部获取了。

pause之后start失败

image.pngMediaPlayer时序图可以看到调用方法,若调用了pause之后再调用start是没有问题的。若时序在stop则必须重新执行prepare方法等待重新准备资源后再播放。

  • 问题1 实际开发中遇到的问题是当播放器的Activity处于后台情况时,Activity会调用到生命周期的onStop方法,因为MediaPlayer生命周期和页面绑定了也会调用stop。最后就知道了原因在哪了MediaPlayer目前是stop状态,所以要重新为MediaPlayer执行prepare方法播放。

问题1是解决了后台返回后播放器可以重新播放资源,但是绑定SurfaceView却是黑屏情况。

  • 问题2 绑定SurfaceView黑屏情况实质上和问题1相似。因为SurfaceView同样也有生命周期,当Activity处于后台后调用了surfaceDestroyed,原先SurfaceView和播放器绑定的Surface已经失效需要重新再绑定处理。

原逻辑是在post方法中只初始化一次并绑定,这样看来只在post方法中绑定做法并不合适。

surfaceView.post(new Runnable() {
    @Override
    public void run() {
        AndroidMediaPlayer.Builder builder = new AndroidMediaPlayer.Builder(TestSimpleMediaPlayerActivity.this,uri,listener);
        builder.withSurface(surfaceView.getHolder().getSurface());
        androidMediaPlayer = builder.createPlayer();
        androidMediaPlayer.prepareAsync();
    }
});

surfaceView的回调方法中surfaceCreated进行Surface绑定工作,从而保证每次从后台进入页面都能刷新绑定。

surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        if(androidMediaPlayer != null)
        androidMediaPlayer.setSurface(surfaceView.getHolder().getSurface());
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
        Log.d("<> surfaceView","surfaceChanged");

    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        Log.d("<> surfaceView","surfaceDestroyed");
    }
});
  • 问题3 MediaPlayer还有个问题是没有提供播放进度回调接口。虽然MediaPlayergetCurrentPosition方法可以获取到当前播放进度,但无法封装提供做到实时获取的能力,因此需要开发者自行实现实时获取的方法。

通过Thread不断循环调用播放器getCurrentPosition方法获取到当前播放毫秒值,另外也可以根据实际业务需要添加延迟处理。

private class PlayerThread extends Thread{
    private boolean toGet = false;
    public void startGet(){
        toGet = true;
    }
    public void stopGet(){
        toGet = true;
    }
    @Override
    public void run() {
        super.run();
        while (toGet){
            if(mMediaPlayer != null){
               int position =  mMediaPlayer.getCurrentPosition();
               if(mListener != null){
                   playerInfo.playerProgress = position;
                   mListener.onPlayerInfoCallBack(playerInfo);
               }
            }
        }
    }
}

总结

MediaPlayer使用虽然简单,但一些细节问题在实现开发中还是会暴露出来。若真正要开发实现完成完整播放器功能还是需要完善和考虑许多功能场景,MediaPlayer还是有一些底层能力可以深挖。