05.视频播放器内核切换封装
目录介绍
- 01.视频播放器内核封装需求
- 02.播放器内核架构图
- 03.如何兼容不同内核播放器
- 04.看一下ijk的内核实现类
- 05.看一下exo的内核实现类
- 06.如何创建不同内核播放器
- 07.看一下工厂类实现代码
- 08.后期如何添加新的内核
00.视频播放器通用框架
- 基础封装视频播放器player,可以在ExoPlayer、MediaPlayer,声网RTC视频播放器内核,原生MediaPlayer可以自由切换
- 对于视图状态切换和后期维护拓展,避免功能和业务出现耦合。比如需要支持播放器UI高度定制,而不是该lib库中UI代码
- 针对视频播放,音频播放,播放回放,以及视频直播的功能。使用简单,代码拓展性强,封装性好,主要是和业务彻底解耦,暴露接口监听给开发者处理业务具体逻辑
- 该播放器整体架构:播放器内核(自由切换) + 视频播放器 + 边播边缓存 + 高度定制播放器UI视图层
- 项目地址:https://github.com/yangchong2...
- 关于视频播放器整体功能介绍文档:https://juejin.im/post/688345...
01.视频播放器内核封装需求
播放器内核难以切换
- 不同的视频播放器内核,由于api不一样,所以难以切换操作。要是想兼容内核切换,就必须自己制定一个视频接口+实现类的播放器
一定要解耦合
- 播放器内核与播放器解耦: 支持更多的播放场景、以及新的播放业务快速接入,并且不影响其他播放业务,比如后期添加阿里云播放器内核,或者腾讯播放器内核
传入不同类型方便创建不同内核
- 隐藏内核播放器创建具体细节,开发者只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体播放器类的类名。需要符合开闭原则
02.播放器内核架构图
03.如何兼容不同内核播放器
提问:针对不同内核播放器,比如谷歌的ExoPlayer,B站的IjkPlayer,还有原生的MediaPlayer,有些api不一样,那使用的时候如何统一api呢?
- 比如说,ijk和exo的视频播放listener监听api就完全不同,这个时候需要做兼容处理
- 定义接口,然后各个不同内核播放器实现接口,重写抽象方法。调用的时候,获取接口对象调用api,这样就可以统一Api
定义一个接口,这个接口有什么呢?这个接口定义通用视频播放器方法,比如常见的有:视频初始化,设置url,加载,以及播放状态,简单来说可以分为三个部分。
- 第一部分:视频初始化实例对象方法,主要包括:initPlayer初始化视频,setDataSource设置视频播放器地址,setSurface设置视频播放器渲染view,prepareAsync开始准备播放操作
- 第二部分:视频播放器状态方法,主要包括:播放,暂停,恢复,重制,设置进度,释放资源,获取进度,设置速度,设置音量
- 第三部分:player绑定view后,需要监听播放状态,比如播放异常,播放完成,播放准备,播放size变化,还有播放准备
04.看一下ijk的内核实现类
ijk的内核实现类代码如下所示
public class IjkVideoPlayer extends AbstractVideoPlayer { protected IjkMediaPlayer mMediaPlayer; private int mBufferedPercent; private Context mAppContext; public IjkVideoPlayer(Context context) { if (context instanceof Application){ mAppContext = context; } else { mAppContext = context.getApplicationContext(); } } @Override public void initPlayer() { mMediaPlayer = new IjkMediaPlayer(); //native日志 IjkMediaPlayer.native_setLogLevel(VideoLogUtils.isIsLog() ? IjkMediaPlayer.IJK_LOG_INFO : IjkMediaPlayer.IJK_LOG_SILENT); setOptions(); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); initListener(); } @Override public void setOptions() { } /** * ijk视频播放器监听listener */ private void initListener() { // 设置监听,可以查看ijk中的IMediaPlayer源码监听事件 // 设置视频错误监听器 mMediaPlayer.setOnErrorListener(onErrorListener); // 设置视频播放完成监听事件 mMediaPlayer.setOnCompletionListener(onCompletionListener); // 设置视频信息监听器 mMediaPlayer.setOnInfoListener(onInfoListener); // 设置视频缓冲更新监听事件 mMediaPlayer.setOnBufferingUpdateListener(onBufferingUpdateListener); // 设置准备视频播放监听事件 mMediaPlayer.setOnPreparedListener(onPreparedListener); // 设置视频大小更改监听器 mMediaPlayer.setOnVideoSizeChangedListener(onVideoSizeChangedListener); // 设置视频seek完成监听事件 mMediaPlayer.setOnSeekCompleteListener(onSeekCompleteListener); // 设置时间文本监听器 mMediaPlayer.setOnTimedTextListener(onTimedTextListener); mMediaPlayer.setOnNativeInvokeListener(new IjkMediaPlayer.OnNativeInvokeListener() { @Override public boolean onNativeInvoke(int i, Bundle bundle) { return true; } }); } /** * 设置播放地址 * * @param path 播放地址 * @param headers 播放地址请求头 */ @Override public void setDataSource(String path, Map
headers) { // 设置dataSource if(path==null || path.length()==0){ if (mPlayerEventListener!=null){ mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_URL_NULL, 0); } return; } try { //解析path Uri uri = Uri.parse(path); if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) { RawDataSourceProvider rawDataSourceProvider = RawDataSourceProvider.create(mAppContext, uri); mMediaPlayer.setDataSource(rawDataSourceProvider); } else { //处理UA问题 if (headers != null) { String userAgent = headers.get("User-Agent"); if (!TextUtils.isEmpty(userAgent)) { mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "user_agent", userAgent); } } mMediaPlayer.setDataSource(mAppContext, uri, headers); } } catch (Exception e) { mPlayerEventListener.onError(); } } /** * 用于播放raw和asset里面的视频文件 */ @Override public void setDataSource(AssetFileDescriptor fd) { try { mMediaPlayer.setDataSource(new RawDataSourceProvider(fd)); } catch (Exception e) { mPlayerEventListener.onError(); } } /** * 设置渲染视频的View,主要用于TextureView * @param surface surface */ @Override public void setSurface(Surface surface) { mMediaPlayer.setSurface(surface); } /** * 准备开始播放(异步) */ @Override public void prepareAsync() { try { mMediaPlayer.prepareAsync(); } catch (IllegalStateException e) { mPlayerEventListener.onError(); } } /** * 暂停 */ @Override public void pause() { try { mMediaPlayer.pause(); } catch (IllegalStateException e) { mPlayerEventListener.onError(); } } /** * 播放 */ @Override public void start() { try { mMediaPlayer.start(); } catch (IllegalStateException e) { mPlayerEventListener.onError(); } } /** * 停止 */ @Override public void stop() { try { mMediaPlayer.stop(); } catch (IllegalStateException e) { mPlayerEventListener.onError(); } } /** * 重置播放器 */ @Override public void reset() { mMediaPlayer.reset(); mMediaPlayer.setOnVideoSizeChangedListener(onVideoSizeChangedListener); setOptions(); } /** * 是否正在播放 */ @Override public boolean isPlaying() { return mMediaPlayer.isPlaying(); } /** * 调整进度 */ @Override public void seekTo(long time) { try { mMediaPlayer.seekTo((int) time); } catch (IllegalStateException e) { mPlayerEventListener.onError(); } } /** * 释放播放器 */ @Override public void release() { mMediaPlayer.setOnErrorListener(null); mMediaPlayer.setOnCompletionListener(null); mMediaPlayer.setOnInfoListener(null); mMediaPlayer.setOnBufferingUpdateListener(null); mMediaPlayer.setOnPreparedListener(null); mMediaPlayer.setOnVideoSizeChangedListener(null); new Thread() { @Override public void run() { try { mMediaPlayer.release(); } catch (Exception e) { e.printStackTrace(); } } }.start(); } /** * 获取当前播放的位置 */ @Override public long getCurrentPosition() { return mMediaPlayer.getCurrentPosition(); } /** * 获取视频总时长 */ @Override public long getDuration() { return mMediaPlayer.getDuration(); } /** * 获取缓冲百分比 */ @Override public int getBufferedPercentage() { return mBufferedPercent; } /** * 设置渲染视频的View,主要用于SurfaceView */ @Override public void setDisplay(SurfaceHolder holder) { mMediaPlayer.setDisplay(holder); } /** * 设置音量 */ @Override public void setVolume(float v1, float v2) { mMediaPlayer.setVolume(v1, v2); } /** * 设置是否循环播放 */ @Override public void setLooping(boolean isLooping) { mMediaPlayer.setLooping(isLooping); } /** * 设置播放速度 */ @Override public void setSpeed(float speed) { mMediaPlayer.setSpeed(speed); } /** * 获取播放速度 */ @Override public float getSpeed() { return mMediaPlayer.getSpeed(0); } /** * 获取当前缓冲的网速 */ @Override public long getTcpSpeed() { return mMediaPlayer.getTcpSpeed(); } /** * 设置视频错误监听器 * int MEDIA_INFO_VIDEO_RENDERING_START = 3;//视频准备渲染 * int MEDIA_INFO_BUFFERING_START = 701;//开始缓冲 * int MEDIA_INFO_BUFFERING_END = 702;//缓冲结束 * int MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001;//视频选择信息 * int MEDIA_ERROR_SERVER_DIED = 100;//视频中断,一般是视频源异常或者不支持的视频类型。 * int MEDIA_ERROR_IJK_PLAYER = -10000,//一般是视频源有问题或者数据格式不支持,比如音频不是AAC之类的 * int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;//数据错误没有有效的回收 */ private IMediaPlayer.OnErrorListener onErrorListener = new IMediaPlayer.OnErrorListener() { @Override public boolean onError(IMediaPlayer iMediaPlayer, int framework_err, int impl_err) { mPlayerEventListener.onError(); VideoLogUtils.d("IjkVideoPlayer----listener---------onError ——> STATE_ERROR ———— what:" + framework_err + ", extra: " + impl_err); return true; } }; /** * 设置视频播放完成监听事件 */ private IMediaPlayer.OnCompletionListener onCompletionListener = new IMediaPlayer.OnCompletionListener() { @Override public void onCompletion(IMediaPlayer iMediaPlayer) { mPlayerEventListener.onCompletion(); VideoLogUtils.d("IjkVideoPlayer----listener---------onCompletion ——> STATE_COMPLETED"); } }; /** * 设置视频信息监听器 */ private IMediaPlayer.OnInfoListener onInfoListener = new IMediaPlayer.OnInfoListener() { @Override public boolean onInfo(IMediaPlayer iMediaPlayer, int what, int extra) { mPlayerEventListener.onInfo(what, extra); VideoLogUtils.d("IjkVideoPlayer----listener---------onInfo ——> ———— what:" + what + ", extra: " + extra); return true; } }; /** * 设置视频缓冲更新监听事件 */ private IMediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new IMediaPlayer.OnBufferingUpdateListener() { @Override public void onBufferingUpdate(IMediaPlayer iMediaPlayer, int percent) { mBufferedPercent = percent; } }; /** * 设置准备视频播放监听事件 */ private IMediaPlayer.OnPreparedListener onPreparedListener = new IMediaPlayer.OnPreparedListener() { @Override public void onPrepared(IMediaPlayer iMediaPlayer) { mPlayerEventListener.onPrepared(); VideoLogUtils.d("IjkVideoPlayer----listener---------onPrepared ——> STATE_PREPARED"); } }; /** * 设置视频大小更改监听器 */ private IMediaPlayer.OnVideoSizeChangedListener onVideoSizeChangedListener = new IMediaPlayer.OnVideoSizeChangedListener() { @Override public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int width, int height, int sar_num, int sar_den) { int videoWidth = iMediaPlayer.getVideoWidth(); int videoHeight = iMediaPlayer.getVideoHeight(); if (videoWidth != 0 && videoHeight != 0) { mPlayerEventListener.onVideoSizeChanged(videoWidth, videoHeight); } VideoLogUtils.d("IjkVideoPlayer----listener---------onVideoSizeChanged ——> WIDTH:" + width + ", HEIGHT:" + height); } }; /**
}; /**
- 设置视频seek完成监听事件 */ private IMediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new IMediaPlayer.OnSeekCompleteListener() { @Override public void onSeekComplete(IMediaPlayer iMediaPlayer) { }
}; } `
05.看一下exo的内核实现类
exo的内核实现类代码如下所示,和ijk的api有些区别
public class ExoMediaPlayer extends AbstractVideoPlayer implements VideoListener, Player.EventListener { protected Context mAppContext; protected SimpleExoPlayer mInternalPlayer; protected MediaSource mMediaSource; protected ExoMediaSourceHelper mMediaSourceHelper; private PlaybackParameters mSpeedPlaybackParameters; private int mLastReportedPlaybackState = Player.STATE_IDLE; private boolean mLastReportedPlayWhenReady = false; private boolean mIsPreparing; private boolean mIsBuffering; private LoadControl mLoadControl; private RenderersFactory mRenderersFactory; private TrackSelector mTrackSelector; public ExoMediaPlayer(Context context) { if (context instanceof Application){ mAppContext = context; } else { mAppContext = context.getApplicationContext(); } mMediaSourceHelper = ExoMediaSourceHelper.getInstance(context); } @Override public void initPlayer() { //创建exo播放器 mInternalPlayer = new SimpleExoPlayer.Builder( mAppContext, mRenderersFactory == null ? mRenderersFactory = new DefaultRenderersFactory(mAppContext) : mRenderersFactory, mTrackSelector == null ? mTrackSelector = new DefaultTrackSelector(mAppContext) : mTrackSelector, mLoadControl == null ? mLoadControl = new DefaultLoadControl() : mLoadControl, DefaultBandwidthMeter.getSingletonInstance(mAppContext), Util.getLooper(), new AnalyticsCollector(Clock.DEFAULT), /* useLazyPreparation= */ true, Clock.DEFAULT) .build(); setOptions(); //播放器日志 if (VideoLogUtils.isIsLog() && mTrackSelector instanceof MappingTrackSelector) { mInternalPlayer.addAnalyticsListener(new EventLogger((MappingTrackSelector) mTrackSelector, "ExoPlayer")); } initListener(); } /**
mTrackSelector = trackSelector; } public void setRenderersFactory(RenderersFactory renderersFactory) {
mRenderersFactory = renderersFactory; } public void setLoadControl(LoadControl loadControl) {
mLoadControl = loadControl; } /**
- 设置播放地址 @param path 播放地址 @param headers 播放地址请求头 / @Override public void setDataSource(String path, Map
headers) { // 设置dataSource if(path==null || path.length()==0){ if (mPlayerEventListener!=null){ mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_URL_NULL, 0); } return; } mMediaSource = mMediaSourceHelper.getMediaSource(path, headers); } @Override
public void setDataSource(AssetFileDescriptor fd) { //no support } /**
- 准备开始播放(异步) / @Override public void prepareAsync() { if (mInternalPlayer == null){ return; } if (mMediaSource == null){ return; } if (mSpeedPlaybackParameters != null) { mInternalPlayer.setPlaybackParameters(mSpeedPlaybackParameters); } mIsPreparing = true; mMediaSource.addEventListener(new Handler(), mMediaSourceEventListener); //准备播放 mInternalPlayer.prepare(mMediaSource); } /*
- 播放 / @Override public void start() { if (mInternalPlayer == null){ return; } mInternalPlayer.setPlayWhenReady(true); } /*
- 暂停 / @Override public void pause() { if (mInternalPlayer == null){ return; } mInternalPlayer.setPlayWhenReady(false); } /*
- 停止 */ @Override public void stop() { if (mInternalPlayer == null){ return; } mInternalPlayer.stop(); } private MediaSourceEventListener mMediaSourceEventListener = new MediaSourceEventListener() {
@Override public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { if (mPlayerEventListener != null && mIsPreparing) { mPlayerEventListener.onPrepared(); } } }; /**
- 重置播放器 / @Override public void reset() { if (mInternalPlayer != null) { mInternalPlayer.stop(true); mInternalPlayer.setVideoSurface(null); mIsPreparing = false; mIsBuffering = false; mLastReportedPlaybackState = Player.STATE_IDLE; mLastReportedPlayWhenReady = false; } } /*
- 是否正在播放 / @Override public boolean isPlaying() { if (mInternalPlayer == null){ return false; } int state = mInternalPlayer.getPlaybackState(); switch (state) { case Player.STATE_BUFFERING: case Player.STATE_READY: return mInternalPlayer.getPlayWhenReady(); case Player.STATE_IDLE: case Player.STATE_ENDED: default: return false; } } /*
- 调整进度 / @Override public void seekTo(long time) { if (mInternalPlayer == null){ return; } mInternalPlayer.seekTo(time); } /*
- 释放播放器 */ @Override public void release() { if (mInternalPlayer != null) { mInternalPlayer.removeListener(this); mInternalPlayer.removeVideoListener(this); final SimpleExoPlayer player = mInternalPlayer; mInternalPlayer = null; new Thread() { @Override public void run() { //异步释放,防止卡顿 player.release(); } }.start(); } mIsPreparing = false;
mIsBuffering = false; mLastReportedPlaybackState = Player.STATE_IDLE; mLastReportedPlayWhenReady = false; mSpeedPlaybackParameters = null; } /**
- 获取当前播放的位置 / @Override public long getCurrentPosition() { if (mInternalPlayer == null){ return 0; } return mInternalPlayer.getCurrentPosition(); } /*
- 获取视频总时长 / @Override public long getDuration() { if (mInternalPlayer == null){ return 0; } return mInternalPlayer.getDuration(); } /*
- 获取缓冲百分比 / @Override public int getBufferedPercentage() { return mInternalPlayer == null ? 0 : mInternalPlayer.getBufferedPercentage(); } /*
- 设置渲染视频的View,主要用于SurfaceView */ @Override public void setSurface(Surface surface) { if (mInternalPlayer != null) { mInternalPlayer.setVideoSurface(surface); } } @Override
public void setDisplay(SurfaceHolder holder) { if (holder == null){ setSurface(null); } else{ setSurface(holder.getSurface()); } } /**
- 设置音量 / @Override public void setVolume(float leftVolume, float rightVolume) { if (mInternalPlayer != null){ mInternalPlayer.setVolume((leftVolume + rightVolume) / 2); } } /*
- 设置是否循环播放 */ @Override public void setLooping(boolean isLooping) { if (mInternalPlayer != null){ mInternalPlayer.setRepeatMode(isLooping ? Player.REPEAT_MODE_ALL : Player.REPEAT_MODE_OFF); } } @Override
public void setOptions() { //准备好就开始播放 mInternalPlayer.setPlayWhenReady(true); } /**
- 设置播放速度 / @Override public void setSpeed(float speed) { PlaybackParameters playbackParameters = new PlaybackParameters(speed); mSpeedPlaybackParameters = playbackParameters; if (mInternalPlayer != null) { mInternalPlayer.setPlaybackParameters(playbackParameters); } } /*
- 获取播放速度 / @Override public float getSpeed() { if (mSpeedPlaybackParameters != null) { return mSpeedPlaybackParameters.speed; } return 1f; } /*
- 获取当前缓冲的网速 */ @Override public long getTcpSpeed() { // no support return 0; } @Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { if (mPlayerEventListener == null){ return; } if (mIsPreparing){ return; } if (mLastReportedPlayWhenReady != playWhenReady || mLastReportedPlaybackState != playbackState) { switch (playbackState) { //最开始调用的状态 case Player.STATE_IDLE: break; //开始缓充 case Player.STATE_BUFFERING: mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_BUFFERING_START, getBufferedPercentage()); mIsBuffering = true; break; //开始播放 case Player.STATE_READY: if (mIsBuffering) { mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_BUFFERING_END, getBufferedPercentage()); mIsBuffering = false; } break; //播放器已经播放完了媒体 case Player.STATE_ENDED: mPlayerEventListener.onCompletion(); break; default: break; } mLastReportedPlaybackState = playbackState; mLastReportedPlayWhenReady = playWhenReady; } } @Override
public void onPlayerError(ExoPlaybackException error) { if (mPlayerEventListener != null) { mPlayerEventListener.onError(); } } @Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { if (mPlayerEventListener != null) { mPlayerEventListener.onVideoSizeChanged(width, height); if (unappliedRotationDegrees > 0) { mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_VIDEO_ROTATION_CHANGED, unappliedRotationDegrees); } } } @Override
public void onRenderedFirstFrame() { if (mPlayerEventListener != null && mIsPreparing) { mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_VIDEO_RENDERING_START, 0); mIsPreparing = false; } } } `
06.如何创建不同内核播放器
先来看一下创建不同内核播放器的代码,只需要开发者传入一个类型参数,即可创建不同类的实例对象。代码如下所示
/** * 获取PlayerFactory具体实现类,获取内核 * 创建对象的时候只需要传递类型type,而不需要对应的工厂,即可创建具体的产品对象 * TYPE_IJK IjkPlayer,基于IjkPlayer封装播放器 * TYPE_NATIVE MediaPlayer,基于原生自带的播放器控件 * TYPE_EXO 基于谷歌视频播放器 * TYPE_RTC 基于RTC视频播放器 * @param type 类型 * @return */ public static AbstractVideoPlayer getVideoPlayer(Context context,@PlayerConstant.PlayerType int type){ if (type == PlayerConstant.PlayerType.TYPE_EXO){ return ExoPlayerFactory.create().createPlayer(context); } else if (type == PlayerConstant.PlayerType.TYPE_IJK){ return IjkPlayerFactory.create().createPlayer(context); } else if (type == PlayerConstant.PlayerType.TYPE_NATIVE){ return MediaPlayerFactory.create().createPlayer(context); } else if (type == PlayerConstant.PlayerType.TYPE_RTC){ return IjkPlayerFactory.create().createPlayer(context); } else { return IjkPlayerFactory.create().createPlayer(context); } } ```- 使用工厂模式创建不同对象的动机是什么,为何要这样使用? - 一个视频播放器可以提供多个内核Player(如ijk、exo、media,rtc等等), 这些player都源自同一个基类,不过在继承基类后不同的子类修改了部分属性从而使得它们可以呈现不同的外观。
首先定义一个工厂抽象类,然后不同的内核播放器分别创建其具体的工厂实现具体类
- PlayerFactory:抽象工厂,担任这个角色的是工厂方法模式的核心,任何在模式中创建对象的工厂类必须实现这个接口
- ExoPlayerFactory:具体工厂,具体工厂角色含有与业务密切相关的逻辑,并且受到使用者的调用以创建具体产品对象。
如何使用,分为三步,具体操作如下所示
- 1.先调用具体工厂对象中的方法createPlayer方法;2.根据传入产品类型参数获得具体的产品对象;3.返回产品对象并使用。
- 简而言之,创建对象的时候只需要传递类型type,而不需要对应的工厂,即可创建具体的产品对象
07.看一下工厂类实现代码
抽象工厂类,代码如下所示
public abstract class PlayerFactory
{ public abstract T createPlayer(Context context); } ```- 具体实现工厂类,代码如下所示 public class ExoPlayerFactory extends PlayerFactory
{ public static ExoPlayerFactory create() {
return new ExoPlayerFactory(); } @Override
public ExoMediaPlayer createPlayer(Context context) { return new ExoMediaPlayer(context); } }`
- 这种创建对象最大优点- 工厂方法用来创建所需要的产品,同时隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
- 加入新的产品时,比如后期新加一个阿里播放器内核,这个时候就只需要添加一个具体工厂和具体产品就可以。系统的可扩展性也就变得非常好,完全符合“开闭原则”
08.后期如何添加新的内核
比如后期想要添加一个腾讯视频内核的播放器。代码如下所示,这个是简化的
public class TxPlayerFactory extends PlayerFactory
{ public static TxPlayerFactory create() { return new TxPlayerFactory(); } @Override public TxMediaPlayer createPlayer(Context context) { return new TxMediaPlayer(context); } } public class TxMediaPlayer extends AbstractVideoPlayer {