使用PlayerBase实现Android 视频跨页面无缝衔接

关于PlayerBase的使用说明和项目源码请参考
Android播放器基础封装库PlayerBase
GitHub源码

顺便说一句:感觉这个PlayerBase真的是与众不同。膜拜大神的杰作。

其实对于这种无缝的续播,原理很简单。就是一个解码实例更换不同的渲染视图即可。可以简单比作一个MediaPlayer去不断设置不同的surface呈现播放。如果自己处理这个过程的话想对比较繁琐,你需要处理Render的回调并关联给解码器,还需要自己处理Render的测量以及显示比例、角度等等问题。

RelationAssist 就是为了简化这个过程而设计的。在不同的页面或视图切换播放时,您只需要提供并传入对应位置的视图容器(ViewGroup类型)即可。内部复杂的设置项和关联由RelationAssist完成。

PlayerBase中提供了一个关联助手(RelationAssist)可以用来实现视频跨页面无缝衔接的效果。但是官方demo中并没有提供相关的源码示例,今天就使用PlayerBase实现这个效果。

先看个效果图,视频转gif使用的工具是 gifrocket

SVID_20190523_084108_1.gif

1.新建App,进行必要的初始化。

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        //初始化播放器,默认使用系统的MediaPlayer
        PlayerLibrary.init(this);
    }

}
  1. 新建RelationAssistSingleton类,维护RelationAssist单个实例
public class RelationAssistSingleton {

    private static RelationAssist mAssist;

    private RelationAssistSingleton() {
    }

    public static RelationAssist getAssist(Context context) {
        if (mAssist == null) {
            synchronized (RelationAssistSingleton.class) {
                if (mAssist == null) {
                    mAssist = new RelationAssist(context);
                }
            }
        }
        return mAssist;
    }

    public static void releaseAssist() {
        if (mAssist != null) {
            mAssist.destroy();
            mAssist = null;
        }
    }
}
  1. 新建TestToDetailActivity,从这界面跳转到详情界面。
  • 布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".ui.TestToDetailActivity">

    <FrameLayout
        android:id="@+id/flVideoContainer"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="#FFFFFF" />

    <Button
        android:id="@+id/btnToDetail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="跳转至详情界面无缝播放" />

</LinearLayout>
  • 代码
public class TestToDetailActivity extends AppCompatActivity {

    private static final String TAG = "TestToDetailActivity";
    //播放视频的container
    private FrameLayout flVideoContainer;
    private Button btnToDetail;

    private RelationAssist mAssist;
    private ReceiverGroup receiverGroup;

    //用来接收播放过程中产生的事件,可以参考官方demo
    private OnAssistPlayEventHandler eventHandler = new OnAssistPlayEventHandler() {
        @Override
        public void onAssistHandle(AssistPlay assist, int eventCode, Bundle bundle) {
            super.onAssistHandle(assist, eventCode, bundle);
            switch (eventCode) {
                case DataInter.Event.EVENT_CODE_REQUEST_BACK:
                    onBackPressed();
                    break;
                case DataInter.Event.EVENT_CODE_ERROR_SHOW:
                    mAssist.stop();
                    break;
                default:
                    break;
            }
        }
    };

    public static void launch(Context context) {
        Intent intent = new Intent(context, TestToDetailActivity.class);
        context.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_to_detail);
        //保持屏幕常亮
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        flVideoContainer = findViewById(R.id.flVideoContainer);
        btnToDetail = findViewById(R.id.btnToDetail);
        btnToDetail.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //跳转到另一个播放界面,注意这里我们使用的是startActivityForResult
                Intent intent = new Intent(TestToDetailActivity.this,
                                       DetailSeamlessPlayActivity.class);
                startActivityForResult(intent, 100);
            }
        });
        //获取并初始化关联助手
        mAssist = RelationAssistSingleton.getAssist(getApplicationContext());
        mAssist.getSuperContainer().setBackgroundColor(Color.BLACK);
        mAssist.setEventAssistHandler(eventHandler);
        receiverGroup = ReceiverGroupManager.get().getLiteReceiverGroup(this); 
    receiverGroup.getGroupValue().putBoolean(DataInter.Key.KEY_NETWORK_RESOURCE, true);

        //注册事件接收组
        mAssist.setReceiverGroup(receiverGroup);
        //设置播放源
        DataSource dataSource = new DataSource();
        dataSource.setData("https://mov.bn.netease.com/open-movie/nos/mp4/2016/01/11/SBC46Q9DV_hd.mp4");
        dataSource.setTitle("神奇的珊瑚");

        mAssist.setDataSource(dataSource);
       //默认标记用户是手动播放的,标记此变量的作用是为了:
       //如果用户在一个界面手动暂停播放了,那么跳转到另一个界面的时候应该保持暂停状态。此变量默认是true。
        mAssist.getReceiverGroup().getGroupValue().putBoolean(GroupValueMap.USER_START_PLAY, true);
        //关联播放容器并开始播放。
        mAssist.attachContainer(flVideoContainer);
        mAssist.play();
    }

    @Override
    protected void onResume() {
        Log.d(TAG, "onResume: ");
        super.onResume();
        int state = mAssist.getState();
        if (state == IPlayer.STATE_PLAYBACK_COMPLETE)
            return;
         //onResume的时候判断用户是否手动播放了,如果是手动播放则继续播放。
        //如果用户手动暂停了,则暂停播放。
        boolean userStart = receiverGroup.getGroupValue().getBoolean(GroupValueMap.USER_START_PLAY);
        if (mAssist.isInPlaybackState() && userStart) {
            mAssist.resume();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.d(TAG, "onActivityResult: ");
        //从另一个播放界面返回的时候,重新关联当前界面的播放容器。
        mAssist.attachContainer(flVideoContainer);
    }

    @Override
    protected void onPause() {
        super.onPause();
        int state = mAssist.getState();
        if (state == IPlayer.STATE_PLAYBACK_COMPLETE)
            return;
        if (mAssist.isInPlaybackState()) {
            mAssist.pause();
        } else {
            mAssist.stop();
        }
    }

    @Override
    protected void onDestroy() {
        //释放RelationAssist
        RelationAssistSingleton.releaseAssist();
        mAssist = null;
        super.onDestroy();
    }
}

  1. 新建DetailSeamlessPlayActivity,这个界面充当详情界面。
  • 布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.DetailSeamlessPlayActivity">

    <FrameLayout
        android:id="@+id/flDetailVideoContainer"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_centerVertical="true"
        android:background="#FFFFFF" />

</RelativeLayout>
  • 代码
public class DetailSeamlessPlayActivity extends AppCompatActivity {

    private FrameLayout flDetailVideoContainer;
    private RelationAssist mAssist;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail_seamless_play);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        flDetailVideoContainer = findViewById(R.id.flDetailVideoContainer);
        //获取关联助手,关联播放容器。
        mAssist = RelationAssistSingleton.getAssist(getApplicationContext());
        mAssist.attachContainer(flDetailVideoContainer);
    }

    @Override
    protected void onResume() {
        super.onResume();
        int state = mAssist.getState();
        if (state == IPlayer.STATE_PLAYBACK_COMPLETE)
            return;
         //onResume的时候判断用户是否手动播放了,如果是手动播放则继续播放。
        //如果用户手动暂停了,则暂停播放。
        boolean userStart = mAssist.getReceiverGroup().getGroupValue().getBoolean(GroupValueMap.USER_START_PLAY);
        if (mAssist.isInPlaybackState() && userStart) {
            mAssist.resume();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        int state = mAssist.getState();
        if (state == IPlayer.STATE_PLAYBACK_COMPLETE)
            return;
        if (mAssist.isInPlaybackState()) {
            mAssist.pause();
        } else {
            mAssist.stop();
        }
    }
}

RelationAssist源码分析

RelationAssist的继承结构

public final class RelationAssist implements AssistPlay {
      //...
}

AssistPlay接口

public interface AssistPlay {

    void setOnPlayerEventListener(OnPlayerEventListener onPlayerEventListener);
    void setOnErrorEventListener(OnErrorEventListener onErrorEventListener);
    void setOnReceiverEventListener(OnReceiverEventListener onReceiverEventListener);

    void setOnProviderListener(IDataProvider.OnProviderListener onProviderListener);
    void setDataProvider(IDataProvider dataProvider);
    boolean switchDecoder(int decoderPlanId);

    void setRenderType(int renderType);
    void setAspectRatio(AspectRatio aspectRatio);

    void setVolume(float left, float right);
    void setSpeed(float speed);

    void setReceiverGroup(IReceiverGroup receiverGroup);

    void attachContainer(ViewGroup userContainer);

    void setDataSource(DataSource dataSource);

    void play();
    void play(boolean updateRender);

    boolean isInPlaybackState();
    boolean isPlaying();
    int getCurrentPosition();
    int getDuration();
    int getAudioSessionId();
    int getBufferPercentage();
    int getState();

    void rePlay(int msc);

    void pause();
    void resume();
    void seekTo(int msc);
    void stop();
    void reset();
    void destroy();

}

为了简单,在下文中使用A表示启动详情界面的界面(TestToDetailActivity),B界面表示详情界面(DetailSeamlessPlayActivity)。

先看一下A界面的关键代码

 //要播放视频的布局
 flVideoContainer = findViewById(R.id.flVideoContainer);
 //获取RelationAssist实例
 mAssist = RelationAssistSingleton.getAssist(getApplicationContext());
 //构建接收者组
 receiverGroup = ReceiverGroupManager.get().getLiteReceiverGroup(this);
 //RelationAssist设置接收者组,内部会根据优先级进行排序
 mAssist.setReceiverGroup(receiverGroup);
 //构建播放资源
 DataSource dataSource = new DataSource();
 dataSource.setData("https://mov.bn.netease.com/open-movie/nos/mp4/2016/01/11/SBC46Q9DV_hd.mp4");
 dataSource.setTitle("神奇的珊瑚");
 //RelationAssist设置播放资源
 mAssist.setDataSource(dataSource);
 //关联要播放视频的View
 mAssist.attachContainer(flVideoContainer);
 //开始播放
mAssist.play();

首先我们要获取RelationAssist实例,我们看一下RelationAssist类部分代码和构造方法。

public final class RelationAssist implements AssistPlay {

    private final String TAG = "RelationAssist";

    private Context mContext;
    //播放器
    private AVPlayer mPlayer;

    /**
     * SuperContainer for ReceiverGroup and Render.
     */
    private SuperContainer mSuperContainer;

    /**
     * ReceiverGroup from out setting.
     */
    private IReceiverGroup mReceiverGroup;
    //默认的渲染类型,我们选择SurfaceView
    private int mRenderType = IRender.RENDER_TYPE_SURFACE_VIEW;
    private boolean mRenderTypeChange;
    private IRender mRender;
    //视频画面缩放模式
    private AspectRatio mAspectRatio = AspectRatio.AspectRatio_FIT_PARENT;
    private int mVideoWidth;
    private int mVideoHeight;
    private int mVideoSarNum;
    private int mVideoSarDen;
    private int mVideoRotation;

    private IRender.IRenderHolder mRenderHolder;

    private DataSource mDataSource;

    private boolean isBuffering;

    private OnPlayerEventListener mOnPlayerEventListener;
    private OnErrorEventListener mOnErrorEventListener;
    private OnReceiverEventListener mOnReceiverEventListener;

    private OnAssistPlayEventHandler mOnEventAssistHandler;

    public RelationAssist(Context context){
        this(context, null);
    }

    public RelationAssist(Context context, SuperContainer superContainer){
        this.mContext = context;
        //初始化播放器
        mPlayer = new AVPlayer();
        //初始化超级容器
        if(superContainer == null){
            superContainer = new SuperContainer(context);
        }
        //默认是 false
        if(PlayerConfig.isUseDefaultNetworkEventProducer())
            superContainer.addEventProducer(new NetworkEventProducer(context));
        mSuperContainer = superContainer;
        mSuperContainer.setStateGetter(mInternalStateGetter);
    }
}

在RelationAssist的构造函数中,我们初始化了超级容器,我们进去看一下SuperContainer的构造函数

public SuperContainer(Context context) {
        super(context);
        init(context);
    }

    private void init(Context context) {
        initBaseInfo(context);
        initGesture(context);
        //初始化渲染容器
        initRenderContainer(context);
        //初始化cover容器
        initReceiverContainer(context);
    }


初始化渲染容器mRenderContainer,这个容器是最先加入到SuperContainer中的,在视图的最底层,用来渲染视频。

private void initRenderContainer(Context context) {
        //用来渲染视频
        mRenderContainer = new FrameLayout(context);
        addView(mRenderContainer,
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
    }

初始化cover容器,cover容器是在渲染视图之上的。

private void initReceiverContainer(Context context) {
        mCoverStrategy = getCoverStrategy(context);
        addView(mCoverStrategy.getContainerView(),
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
    }

protected ICoverStrategy getCoverStrategy(Context context){
        return new DefaultLevelCoverContainer(context);
    }

DefaultLevelCoverContainer类部分代码,这个类里面包括 low,medium,high三种级别的容器用来放置不同级别的cover。

public class DefaultLevelCoverContainer extends BaseLevelCoverContainer {

    private final String TAG = "DefaultLevelCoverContainer";

    /**
     * the low level covers container .
     */
    private FrameLayout mLevelLowCoverContainer;
    /**
     * the medium level covers container .
     */
    private FrameLayout mLevelMediumCoverContainer;
    /**
     * the high level covers container .
     */
    private FrameLayout mLevelHighCoverContainer;

    public DefaultLevelCoverContainer(Context context) {
        super(context);
    }

    @Override
    protected void initLevelContainers(Context context) {
        //add low cover container
        mLevelLowCoverContainer = new FrameLayout(context);
        mLevelLowCoverContainer.setBackgroundColor(Color.TRANSPARENT);
        addLevelContainerView(mLevelLowCoverContainer,null);

        //add medium cover container
        mLevelMediumCoverContainer = new FrameLayout(context);
        mLevelMediumCoverContainer.setBackgroundColor(Color.TRANSPARENT);
        addLevelContainerView(mLevelMediumCoverContainer,null);

        //add high cover container
        mLevelHighCoverContainer = new FrameLayout(context);
        mLevelHighCoverContainer.setBackgroundColor(Color.TRANSPARENT);
        addLevelContainerView(mLevelHighCoverContainer,null);
    }
//...
}

A界面中的代码,关联要播放视频的View

//注释1处关联要播放视频的View
mAssist.attachContainer(flVideoContainer);
@Override
public void attachContainer(ViewGroup userContainer) {
    attachContainer(userContainer, false);
}
public void attachContainer(ViewGroup userContainer, boolean updateRender){
        attachPlayerListener();
        //注释2处,先从超级容器中分离
        detachSuperContainer();
        if(mReceiverGroup!=null){
            mSuperContainer.setReceiverGroup(mReceiverGroup);
        }
        //第一次调用的时候isNeedForceUpdateRender()为true
        if(updateRender || isNeedForceUpdateRender()){
            //释放渲染器
            releaseRender();
            //注释3处,更新渲染器
            updateRender();
        }
        //注释4处,连接SuperContainer
        if(userContainer!=null){
            userContainer.addView(mSuperContainer,
                    new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                            ViewGroup.LayoutParams.MATCH_PARENT));
        }
    }

RelationAssist的detachSuperContainer方法

private void detachSuperContainer(){
     ViewParent parent = mSuperContainer.getParent();
     if(parent!=null && parent instanceof ViewGroup){
         ((ViewGroup) parent).removeView(mSuperContainer);
     }
}

RelationAssist的updateRender方法

private void updateRender(){
    if(isNeedForceUpdateRender()){
        mRenderTypeChange = false;
        releaseRender();
        //根据类型创建SurfaceView或者TextureView
        switch (mRenderType){
            case IRender.RENDER_TYPE_SURFACE_VIEW:
                 mRender = new RenderSurfaceView(mContext);
                break;
            case IRender.RENDER_TYPE_TEXTURE_VIEW:
            default:
                    mRender = new RenderTextureView(mContext);
                    ((RenderTextureView)mRender).setTakeOverSurfaceTexture(true);
                    break;
            }
            mRenderHolder = null;
            //播放器设置 surface为null
            mPlayer.setSurface(null);
            //设置渲染比例
            mRender.updateAspectRatio(mAspectRatio);
            mRender.setRenderCallback(mRenderCallback);
            //update some params for render type change
            mRender.updateVideoSize(mVideoWidth, mVideoHeight);
            mRender.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen);
            //update video rotation
            mRender.setVideoRotation(mVideoRotation);
            //将渲染视图加入到mSuperContainer
            mSuperContainer.setRenderView(mRender.getRenderView());
        }
    }

SuperContainer的setRenderView方法

public final void setRenderView(View view){
        removeRender();
        //must set WRAP_CONTENT and CENTER for render aspect ratio and measure.
        LayoutParams lp = new LayoutParams(
                LayoutParams.WRAP_CONTENT,
                LayoutParams.WRAP_CONTENT,
                Gravity.CENTER);
        //添加渲染视图,这里我们选择的是RenderSurfaceView
        mRenderContainer.addView(view,lp);
    }

然后回到注释4处,将mSuperContainer添加到我们传入的要播放视频的布局

if(userContainer!=null){
    userContainer.addView(mSuperContainer, new ViewGroup.LayoutParams(
                            ViewGroup.LayoutParams.MATCH_PARENT,
                            ViewGroup.LayoutParams.MATCH_PARENT));
}

到这里,我们我们已经把要播放视频的mSuperContainer添加到我们传入的要播放视频的布局中去了。继续看是怎么播放的。

RelationAssist的play方法

@Override
public void play() {
    play(false);
}
@Override
public void play(boolean updateRender) {
    if(updateRender){
        releaseRender();
        updateRender();
    }
    if(mDataSource!=null){
        //设置资源,播放
        onInternalSetDataSource(mDataSource);
        onInternalStart();
    }
}

RelationAssist的onInternalSetDataSource方法

private void onInternalSetDataSource(DataSource dataSource){
    //调用AVPlayer的setDataSource方法
    mPlayer.setDataSource(dataSource);
}

AVPlayer的setDataSource方法

@Override
public void setDataSource(DataSource dataSource) {
    this.mDataSource = dataSource;
    //when data source update, attach listener.
    initListener();
    //if not set DataProvider,will be set data to decoder.
    if(!useProvider())
        //AVPlayer就是一个包装器,内部的player设置播放资源
        interPlayerSetDataSource(dataSource);

}

AVPlayer的interPlayerSetDataSource方法

private void interPlayerSetDataSource(DataSource dataSource){
    if(isPlayerAvailable()){
        if(isPlayRecordOpen())
            mRecordProxyPlayer.onDataSourceReady(dataSource);
        //AVPlayer就是一个包装器,内部的player设置播放资源
        mInternalPlayer.setDataSource(dataSource);
    }
}

我们这里默认的播放器是SysMediaPlayer,SysMediaPlayer的setDataSource方法我们暂时不看。

我们回到RelationAssist的onInternalStart方法

private void onInternalStart(){
    //播放器开始播放
    mPlayer.start();
}

AVPlayer的start方法

    @Override
    public void start() {
        int record = getRecord(mDataSource);
        if(useProvider()){
            mDataSource.setStartPos(record);
            mDataProvider.handleSourceData(mDataSource);
        }else{
            //调用内置player播放
            internalPlayerStart(record);
        }
    }

SysMediaPlayer的start(int msc)方法

@Override
    public void start(int msc) {
        if(available()){
            if(msc > 0){
                startSeekPos = msc;
            }
            start();
        }
    }

SysMediaPlayer的start()方法

@Override
public void start() {
    try {
        if(available() &&
                (getState()== STATE_PREPARED
                        || getState()== STATE_PAUSED
                        || getState()== STATE_PLAYBACK_COMPLETE)){
            mMediaPlayer.start();
            updateStatus(STATE_STARTED);
            //通知开始播放了
            submitPlayerEvent(OnPlayerEventListener.PLAYER_EVENT_ON_START, null);
        }
    }catch (Exception e){
        handleException(e);
    }
    mTargetState = STATE_STARTED;
}

具体怎么播放的我们不去研究了,接下来我们看看进入B界面的时候的代码。

 flDetailVideoContainer = findViewById(R.id.flDetailVideoContainer);
 mAssist = RelationAssistSingleton.getAssist(getApplicationContext());
//我们仅仅是调用了attachContainer方法而已。
 mAssist.attachContainer(flDetailVideoContainer);

我们再看一遍RelationAssist的attachContainer方法
RelationAssist内部会调用到两个参数的重载函数,这个时候对我们来说,最关键的代码就两行。

public void attachContainer(ViewGroup userContainer, boolean updateRender){
    attachPlayerListener();
    //1. 先把mSuperContainer从上一个布局文件中分离
    detachSuperContainer();
    if(mReceiverGroup!=null){
        mSuperContainer.setReceiverGroup(mReceiverGroup);
    }
    //此时这个条件不满足
    if(updateRender || isNeedForceUpdateRender()){
        releaseRender();
        //update render view.
        updateRender();
    }
    //2. 重新把mSuperContainer添加到我们传入的新的布局文件中。
    if(userContainer!=null){
        userContainer.addView(mSuperContainer,
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
    }
}

接下来我们要去看一下在重新把mSuperContainer添加到我们传入的新的布局文件中的时候SysMediaPlayer和RenderSurfaceView的一些细节。

public class RenderSurfaceView extends SurfaceView implements IRender {
  
    private IRenderCallback mRenderCallback;
    private RenderMeasure mRenderMeasure;
    private boolean isReleased;

    public RenderSurfaceView(Context context) {
        this(context, null);
    }

    public RenderSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mRenderMeasure = new RenderMeasure();
        //监听SurfaceHolder的生命周期
        getHolder().addCallback(new InternalSurfaceHolderCallback());
    }

    @Override
    public void setRenderCallback(IRenderCallback renderCallback) {
        this.mRenderCallback = renderCallback;
    }
    
    private static final class InternalRenderHolder implements IRenderHolder{

        private WeakReference<SurfaceHolder> mSurfaceHolder;

        public InternalRenderHolder(SurfaceHolder surfaceHolder){
            this.mSurfaceHolder = new WeakReference<>(surfaceHolder);
        }

        @Override
        public void bindPlayer(IPlayer player) {
            if(player!=null && mSurfaceHolder.get()!=null){
                //设置播放器的SurfaceHolder用来显示画面
                player.setDisplay(mSurfaceHolder.get());
            }
        }
    }

    private class InternalSurfaceHolderCallback implements SurfaceHolder.Callback{

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            PLog.d(TAG,"<---surfaceCreated---->");
            if(mRenderCallback!=null){
                mRenderCallback.onSurfaceCreated(new InternalRenderHolder(holder),0,0);
            }
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            PLog.d(TAG,"surfaceChanged : width = " + width + " height = " + height);
            if(mRenderCallback!=null){
                mRenderCallback.onSurfaceChanged(new InternalRenderHolder(holder),format, width,height);
            }
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            PLog.d(TAG,"***surfaceDestroyed***");
            if(mRenderCallback!=null){
                mRenderCallback.onSurfaceDestroy(new InternalRenderHolder(holder));
            }
        }
    }
}

RelationAssist的部分代码

//...
mRender.setRenderCallback(mRenderCallback);
//...
private IRender.IRenderCallback mRenderCallback =new IRender.IRenderCallback() {
        @Override
        public void onSurfaceCreated(IRender.IRenderHolder renderHolder,
                                     int width, int height) {
            PLog.d(TAG,"onSurfaceCreated : width = " + width + ", height = " + height);
            //当 surface 创建的时候关联播放器
            mRenderHolder = renderHolder;
            bindRenderHolder(mRenderHolder);
        }
        @Override
        public void onSurfaceChanged(IRender.IRenderHolder renderHolder,
                                     int format, int width, int height) {
            //not handle some...
        }
        @Override
        public void onSurfaceDestroy(IRender.IRenderHolder renderHolder) {
            PLog.d(TAG,"onSurfaceDestroy...");
            //on surface destroy detach player
            mRenderHolder = null;
        }
    };

private void bindRenderHolder(IRender.IRenderHolder renderHolder){
    if(renderHolder!=null)
        renderHolder.bindPlayer(mPlayer);
}

第一次开始播放的时候RenderSurfaceView的SurfaceHolder的生命周期

com.kk.taurus.avplayer D/RenderSurfaceView: <---surfaceCreated---->
com.kk.taurus.avplayer D/RenderSurfaceView: surfaceChanged : width = 1080 height = 600
com.kk.taurus.avplayer D/RenderSurfaceView: surfaceChanged : width = 960 height = 540


当我们更换视频播放的父级布局的时候RenderSurfaceView的SurfaceHolder的生命周期

com.kk.taurus.avplayer D/RenderSurfaceView: ***surfaceDestroyed***
com.kk.taurus.avplayer D/RenderSurfaceView: <---surfaceCreated---->
com.kk.taurus.avplayer D/RenderSurfaceView: surfaceChanged : width = 960 height = 540

SurfaceHolder会Destroyed,然后会重新创建,而这时,RelationAssit只是把播放器和重新创建好的SurfaceHolder关联而已。

@Override
public void bindPlayer(IPlayer player) {
      if(player!=null && mSurfaceHolder.get()!=null){
          //设置播放器的SurfaceHolder用来显示画面
          player.setDisplay(mSurfaceHolder.get());
     }
}

总结,其实对于这种无缝的续播,原理很简单。就是不同的渲染视图使用同一个解码实例即可。可以简单比作一个MediaPlayer去不断设置不同的surface呈现播放。有兴趣可以自己试着实现一把,不使用PlayerBase来完成这个功能。

参考链接:

  1. Android播放器基础封装库PlayerBase
  2. GitHub源码
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348