05.视频播放器内核切换封装

目录介绍

  • 01.视频播放器内核封装需求
  • 02.播放器内核架构图
  • 03.如何兼容不同内核播放器
  • 04.看一下ijk的内核实现类
  • 05.看一下exo的内核实现类
  • 06.如何创建不同内核播放器
  • 07.看一下工厂类实现代码
  • 08.后期如何添加新的内核

00.视频播放器通用框架

  • 基础封装视频播放器player,可以在ExoPlayer、MediaPlayer,声网RTC视频播放器内核,原生MediaPlayer可以自由切换
  • 对于视图状态切换和后期维护拓展,避免功能和业务出现耦合。比如需要支持播放器UI高度定制,而不是该lib库中UI代码
  • 针对视频播放,音频播放,播放回放,以及视频直播的功能。使用简单,代码拓展性强,封装性好,主要是和业务彻底解耦,暴露接口监听给开发者处理业务具体逻辑
  • 该播放器整体架构:播放器内核(自由切换) + 视频播放器 + 边播边缓存 + 高度定制播放器UI视图层
  • 项目地址:https://github.com/yangchong211/YCVideoPlayer
  • 关于视频播放器整体功能介绍文档:https://juejin.im/post/6883457444752654343

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();}}@Overridepublic 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();}@Overridepublic 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() {@Overridepublic boolean onNativeInvoke(int i, Bundle bundle) {return true;}});}/*** 设置播放地址** @param path    播放地址* @param headers 播放地址请求头*/@Overridepublic void setDataSource(String path, Map<String, String> headers) {// 设置dataSourceif(path==null || path.length()==0){if (mPlayerEventListener!=null){mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_URL_NULL, 0);}return;}try {//解析pathUri 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里面的视频文件*/@Overridepublic void setDataSource(AssetFileDescriptor fd) {try {mMediaPlayer.setDataSource(new RawDataSourceProvider(fd));} catch (Exception e) {mPlayerEventListener.onError();}}/*** 设置渲染视频的View,主要用于TextureView* @param surface                           surface*/@Overridepublic void setSurface(Surface surface) {mMediaPlayer.setSurface(surface);}/*** 准备开始播放(异步)*/@Overridepublic void prepareAsync() {try {mMediaPlayer.prepareAsync();} catch (IllegalStateException e) {mPlayerEventListener.onError();}}/*** 暂停*/@Overridepublic void pause() {try {mMediaPlayer.pause();} catch (IllegalStateException e) {mPlayerEventListener.onError();}}/*** 播放*/@Overridepublic void start() {try {mMediaPlayer.start();} catch (IllegalStateException e) {mPlayerEventListener.onError();}}/*** 停止*/@Overridepublic void stop() {try {mMediaPlayer.stop();} catch (IllegalStateException e) {mPlayerEventListener.onError();}}/*** 重置播放器*/@Overridepublic void reset() {mMediaPlayer.reset();mMediaPlayer.setOnVideoSizeChangedListener(onVideoSizeChangedListener);setOptions();}/*** 是否正在播放*/@Overridepublic boolean isPlaying() {return mMediaPlayer.isPlaying();}/*** 调整进度*/@Overridepublic void seekTo(long time) {try {mMediaPlayer.seekTo((int) time);} catch (IllegalStateException e) {mPlayerEventListener.onError();}}/*** 释放播放器*/@Overridepublic void release() {mMediaPlayer.setOnErrorListener(null);mMediaPlayer.setOnCompletionListener(null);mMediaPlayer.setOnInfoListener(null);mMediaPlayer.setOnBufferingUpdateListener(null);mMediaPlayer.setOnPreparedListener(null);mMediaPlayer.setOnVideoSizeChangedListener(null);new Thread() {@Overridepublic void run() {try {mMediaPlayer.release();} catch (Exception e) {e.printStackTrace();}}}.start();}/*** 获取当前播放的位置*/@Overridepublic long getCurrentPosition() {return mMediaPlayer.getCurrentPosition();}/*** 获取视频总时长*/@Overridepublic long getDuration() {return mMediaPlayer.getDuration();}/*** 获取缓冲百分比*/@Overridepublic int getBufferedPercentage() {return mBufferedPercent;}/*** 设置渲染视频的View,主要用于SurfaceView*/@Overridepublic void setDisplay(SurfaceHolder holder) {mMediaPlayer.setDisplay(holder);}/*** 设置音量*/@Overridepublic void setVolume(float v1, float v2) {mMediaPlayer.setVolume(v1, v2);}/*** 设置是否循环播放*/@Overridepublic void setLooping(boolean isLooping) {mMediaPlayer.setLooping(isLooping);}/*** 设置播放速度*/@Overridepublic void setSpeed(float speed) {mMediaPlayer.setSpeed(speed);}/*** 获取播放速度*/@Overridepublic float getSpeed() {return mMediaPlayer.getSpeed(0);}/*** 获取当前缓冲的网速*/@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void onCompletion(IMediaPlayer iMediaPlayer) {mPlayerEventListener.onCompletion();VideoLogUtils.d("IjkVideoPlayer----listener---------onCompletion ——> STATE_COMPLETED");}};/*** 设置视频信息监听器*/private IMediaPlayer.OnInfoListener onInfoListener = new IMediaPlayer.OnInfoListener() {@Overridepublic 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() {@Overridepublic void onBufferingUpdate(IMediaPlayer iMediaPlayer, int percent) {mBufferedPercent = percent;}};/*** 设置准备视频播放监听事件*/private IMediaPlayer.OnPreparedListener onPreparedListener = new IMediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(IMediaPlayer iMediaPlayer) {mPlayerEventListener.onPrepared();VideoLogUtils.d("IjkVideoPlayer----listener---------onPrepared ——> STATE_PREPARED");}};/*** 设置视频大小更改监听器*/private IMediaPlayer.OnVideoSizeChangedListener onVideoSizeChangedListener = new IMediaPlayer.OnVideoSizeChangedListener() {@Overridepublic 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);}};/*** 设置时间文本监听器*/private IMediaPlayer.OnTimedTextListener onTimedTextListener = new IMediaPlayer.OnTimedTextListener() {@Overridepublic void onTimedText(IMediaPlayer iMediaPlayer, IjkTimedText ijkTimedText) {}};/*** 设置视频seek完成监听事件*/private IMediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new IMediaPlayer.OnSeekCompleteListener() {@Overridepublic 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);}@Overridepublic 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();}/*** exo视频播放器监听listener*/private void initListener() {mInternalPlayer.addListener(this);mInternalPlayer.addVideoListener(this);}public void setTrackSelector(TrackSelector trackSelector) {mTrackSelector = trackSelector;}public void setRenderersFactory(RenderersFactory renderersFactory) {mRenderersFactory = renderersFactory;}public void setLoadControl(LoadControl loadControl) {mLoadControl = loadControl;}/*** 设置播放地址** @param path    播放地址* @param headers 播放地址请求头*/@Overridepublic void setDataSource(String path, Map<String, String> headers) {// 设置dataSourceif(path==null || path.length()==0){if (mPlayerEventListener!=null){mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_URL_NULL, 0);}return;}mMediaSource = mMediaSourceHelper.getMediaSource(path, headers);}@Overridepublic void setDataSource(AssetFileDescriptor fd) {//no support}/*** 准备开始播放(异步)*/@Overridepublic 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);}/*** 播放*/@Overridepublic void start() {if (mInternalPlayer == null){return;}mInternalPlayer.setPlayWhenReady(true);}/*** 暂停*/@Overridepublic void pause() {if (mInternalPlayer == null){return;}mInternalPlayer.setPlayWhenReady(false);}/*** 停止*/@Overridepublic void stop() {if (mInternalPlayer == null){return;}mInternalPlayer.stop();}private MediaSourceEventListener mMediaSourceEventListener = new MediaSourceEventListener() {@Overridepublic void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {if (mPlayerEventListener != null && mIsPreparing) {mPlayerEventListener.onPrepared();}}};/*** 重置播放器*/@Overridepublic void reset() {if (mInternalPlayer != null) {mInternalPlayer.stop(true);mInternalPlayer.setVideoSurface(null);mIsPreparing = false;mIsBuffering = false;mLastReportedPlaybackState = Player.STATE_IDLE;mLastReportedPlayWhenReady = false;}}/*** 是否正在播放*/@Overridepublic 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;}}/*** 调整进度*/@Overridepublic void seekTo(long time) {if (mInternalPlayer == null){return;}mInternalPlayer.seekTo(time);}/*** 释放播放器*/@Overridepublic void release() {if (mInternalPlayer != null) {mInternalPlayer.removeListener(this);mInternalPlayer.removeVideoListener(this);final SimpleExoPlayer player = mInternalPlayer;mInternalPlayer = null;new Thread() {@Overridepublic void run() {//异步释放,防止卡顿player.release();}}.start();}mIsPreparing = false;mIsBuffering = false;mLastReportedPlaybackState = Player.STATE_IDLE;mLastReportedPlayWhenReady = false;mSpeedPlaybackParameters = null;}/*** 获取当前播放的位置*/@Overridepublic long getCurrentPosition() {if (mInternalPlayer == null){return 0;}return mInternalPlayer.getCurrentPosition();}/*** 获取视频总时长*/@Overridepublic long getDuration() {if (mInternalPlayer == null){return 0;}return mInternalPlayer.getDuration();}/*** 获取缓冲百分比*/@Overridepublic int getBufferedPercentage() {return mInternalPlayer == null ? 0 : mInternalPlayer.getBufferedPercentage();}/*** 设置渲染视频的View,主要用于SurfaceView*/@Overridepublic void setSurface(Surface surface) {if (mInternalPlayer != null) {mInternalPlayer.setVideoSurface(surface);}}@Overridepublic void setDisplay(SurfaceHolder holder) {if (holder == null){setSurface(null);} else{setSurface(holder.getSurface());}}/*** 设置音量*/@Overridepublic void setVolume(float leftVolume, float rightVolume) {if (mInternalPlayer != null){mInternalPlayer.setVolume((leftVolume + rightVolume) / 2);}}/*** 设置是否循环播放*/@Overridepublic void setLooping(boolean isLooping) {if (mInternalPlayer != null){mInternalPlayer.setRepeatMode(isLooping ? Player.REPEAT_MODE_ALL : Player.REPEAT_MODE_OFF);}}@Overridepublic void setOptions() {//准备好就开始播放mInternalPlayer.setPlayWhenReady(true);}/*** 设置播放速度*/@Overridepublic void setSpeed(float speed) {PlaybackParameters playbackParameters = new PlaybackParameters(speed);mSpeedPlaybackParameters = playbackParameters;if (mInternalPlayer != null) {mInternalPlayer.setPlaybackParameters(playbackParameters);}}/*** 获取播放速度*/@Overridepublic float getSpeed() {if (mSpeedPlaybackParameters != null) {return mSpeedPlaybackParameters.speed;}return 1f;}/*** 获取当前缓冲的网速*/@Overridepublic long getTcpSpeed() {// no supportreturn 0;}@Overridepublic 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;}}@Overridepublic void onPlayerError(ExoPlaybackException error) {if (mPlayerEventListener != null) {mPlayerEventListener.onError();}}@Overridepublic 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);}}}@Overridepublic 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都源自同一个基类,不过在继承基类后不同的子类修改了部分属性从而使得它们可以呈现不同的外观。
    • 如果希望在使用这些内核player时,不需要知道这些具体内核的名字,只需要知道表示该内核类的一个参数,并提供一个调用方便的方法,把该参数传入方法即可返回一个相应的内核对象,此时,就可以使用工厂模式。
  • 首先定义一个工厂抽象类,然后不同的内核播放器分别创建其具体的工厂实现具体类
    • PlayerFactory:抽象工厂,担任这个角色的是工厂方法模式的核心,任何在模式中创建对象的工厂类必须实现这个接口
    • ExoPlayerFactory:具体工厂,具体工厂角色含有与业务密切相关的逻辑,并且受到使用者的调用以创建具体产品对象。
  • 如何使用,分为三步,具体操作如下所示
    • 1.先调用具体工厂对象中的方法createPlayer方法;2.根据传入产品类型参数获得具体的产品对象;3.返回产品对象并使用。
    • 简而言之,创建对象的时候只需要传递类型type,而不需要对应的工厂,即可创建具体的产品对象

07.看一下工厂类实现代码

  • 抽象工厂类,代码如下所示

    public abstract class PlayerFactory<T extends AbstractVideoPlayer> {public abstract T createPlayer(Context context);
    }
    
  • 具体实现工厂类,代码如下所示
    public class ExoPlayerFactory extends PlayerFactory<ExoMediaPlayer> {public static ExoPlayerFactory create() {return new ExoPlayerFactory();}@Overridepublic ExoMediaPlayer createPlayer(Context context) {return new ExoMediaPlayer(context);}
    }
    
  • 这种创建对象最大优点
    • 工厂方法用来创建所需要的产品,同时隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
    • 加入新的产品时,比如后期新加一个阿里播放器内核,这个时候就只需要添加一个具体工厂和具体产品就可以。系统的可扩展性也就变得非常好,完全符合“开闭原则”

08.后期如何添加新的内核

  • 比如后期想要添加一个腾讯视频内核的播放器。代码如下所示,这个是简化的

    public class TxPlayerFactory extends PlayerFactory<TxMediaPlayer> {public static TxPlayerFactory create() {return new TxPlayerFactory();}@Overridepublic TxMediaPlayer createPlayer(Context context) {return new TxMediaPlayer(context);}
    }public class TxMediaPlayer extends AbstractVideoPlayer {//省略接口的实现方法代码
    }
    

05.视频播放器内核切换封装相关推荐

  1. Android FFmpeg视频播放器三 音频封装格式解码播放

    Android FFmpeg视频播放器一解封装 Android Android FFmpeg视频播放器二 视频封装格式解码播放 视频解封装之后就会得到音频流和视频流,解封状得到的数据是AVPackag ...

  2. android中使用SurfaceView做视频播放器--视频切换无缝连接

    Android中的视频用到的有view.SurfaceHolder和view.SurfaceView,还有就是用到了MediaPlayer.OnBufferingUpdateListener和Medi ...

  3. 04.视频播放器通用架构实践

    04.视频播放器通用架构实践 目录介绍 01.视频播放器的痛点 02.业务需求的目标 03.该播放器框架特点 04.播放器内核封装 05.播放器UI层封装 06.如何简单使用 07.如何自定义播放器 ...

  4. android视频播放器api,03.视频播放器Api说明

    03.视频播放器Api说明 目录介绍01.最简单的播放 02.如何切换视频内核 03.切换视频模式 04.切换视频清晰度 05.视频播放监听 06.列表中播放处理 07.悬浮窗口播放 08.其他重要功 ...

  5. 02.视频播放器整体结构

    02.视频播放器整体结构 目录介绍 01.视频常见的布局视图 02.后期可能涉及的视图 03.需要达到的目的和效果 04.视频视图层级示意图 05.整体架构思路分析流程 06.如何创建不同播放器 07 ...

  6. 年底了,一起来撸个视频播放器吧!

    /   今日科技快讯   / 近日,有消息传出,百度回港第二上市正式启动,并确定了高盛和中信为其上市保荐团队,并计划春节后正式在港提交上市申请.目前,百度集团对此消息尚无回应. 早在2020年7月下旬 ...

  7. 只有你项目不到,Electron也可以开发视频播放器

    一.桌面版视频播放器 今天又发现一款强大的开源软件,electron 开发的一个可以播放国内主流视频(腾讯.爱奇艺.优酷.芒果.乐视)的播放器.而且播放视频可直接跳过广告.好的开源项目第一时间分享给大 ...

  8. 只有你想不到,Electron也可以开发视频播放器

    ## 一.桌面版视频播放器 今天又发现一款强大的开源软件,electron 开发的一个可以播放国内主流视频(腾讯.爱奇艺.优酷.芒果.乐视)的播放器.而且播放视频可直接跳过广告.好的开源项目第一时间分 ...

  9. Android进阶:自定义视频播放器开发(上)

    随着快手,抖音,西瓜视频等视频APP的崛起,视频播放已经成为主流,此时作为Android研发的你,想要提高自己的能力还不知道怎么开发视频播放器怎么行?所以今天就带着大家一起开发一个简易播放器:Smal ...

最新文章

  1. layer.js 弹窗组件API文档
  2. 使用JDBC连接SQL Server 2000 命名实例(Named Instance)
  3. 云炬随笔20210930
  4. Netty 源码走读
  5. 安卓创建快捷方式相关问题 Intent Intent-filter
  6. c语言函数有两个参数,C中子函数最多有几个形参
  7. redis的操作笔记
  8. Atom:一些有用的Packages和插件
  9. python 隐藏其他窗口_python控制窗口显示隐藏
  10. 对待二手车过户要理性
  11. 把kali装到U盘里
  12. springMVC中前端同名name与后端接收
  13. 1、swift开发iOS——基础
  14. MFC使用第三方CSpreadSheet
  15. Huawei mobile phone downgrade from HarmonyOS2 system to EMUI10.1 | Mobile phone system
  16. ProE与UG的比较
  17. 转:自由、责任、世界观
  18. json spr路驾驶技术视频api_TED视频下载及API接口
  19. 2022年低压电工操作证考试题库及答案
  20. 战场模拟器过检测集体凉凉?没关系,你还可以这样电脑玩吃鸡匹配手机

热门文章

  1. C(X):vcruntime.h头文件
  2. vue 导出 excel ,并兼容ie
  3. 计算机软著申请费用,软著申请费用多少
  4. 抖音短视频零基础能做到百万粉丝吗?国仁楠哥
  5. kkfileview在预览word文档中文乱码
  6. 【PMP备考经验】PMP 项目管理考试 5A 大神备考经验汇总(1)
  7. 百度地图加载空白颜色_本地地图标点的html实现
  8. PPT做的又慢又难看,收下这4个模板资源站,10分钟搞定高逼格PPT
  9. office 部署工具安装office办公软件
  10. oracle 11g 新特性