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的内核实现类代码如下所示
1public class IjkVideoPlayer extends AbstractVideoPlayer { 2 3 protected IjkMediaPlayer mMediaPlayer; 4 private int mBufferedPercent; 5 private Context mAppContext; 6 7 public IjkVideoPlayer(Context context) { 8 if (context instanceof Application){ 9 mAppContext = context; 10 } else { 11 mAppContext = context.getApplicationContext(); 12 } 13 } 14 15 @Override 16 public void initPlayer() { 17 mMediaPlayer = new IjkMediaPlayer(); 18 //native日志 19 IjkMediaPlayer.native_setLogLevel(VideoLogUtils.isIsLog() 20 ? IjkMediaPlayer.IJK_LOG_INFO : IjkMediaPlayer.IJK_LOG_SILENT); 21 setOptions(); 22 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 23 initListener(); 24 } 25 26 @Override 27 public void setOptions() { 28 } 29 30 /** 31 * ijk视频播放器监听listener 32 */ 33 private void initListener() { 34 // 设置监听,可以查看ijk中的IMediaPlayer源码监听事件 35 // 设置视频错误监听器 36 mMediaPlayer.setOnErrorListener(onErrorListener); 37 // 设置视频播放完成监听事件 38 mMediaPlayer.setOnCompletionListener(onCompletionListener); 39 // 设置视频信息监听器 40 mMediaPlayer.setOnInfoListener(onInfoListener); 41 // 设置视频缓冲更新监听事件 42 mMediaPlayer.setOnBufferingUpdateListener(onBufferingUpdateListener); 43 // 设置准备视频播放监听事件 44 mMediaPlayer.setOnPreparedListener(onPreparedListener); 45 // 设置视频大小更改监听器 46 mMediaPlayer.setOnVideoSizeChangedListener(onVideoSizeChangedListener); 47 // 设置视频seek完成监听事件 48 mMediaPlayer.setOnSeekCompleteListener(onSeekCompleteListener); 49 // 设置时间文本监听器 50 mMediaPlayer.setOnTimedTextListener(onTimedTextListener); 51 mMediaPlayer.setOnNativeInvokeListener(new IjkMediaPlayer.OnNativeInvokeListener() { 52 @Override 53 public boolean onNativeInvoke(int i, Bundle bundle) { 54 return true; 55 } 56 }); 57 } 58 59 /** 60 * 设置播放地址 61 * 62 * @param path 播放地址 63 * @param headers 播放地址请求头 64 */ 65 @Override 66 public void setDataSource(String path, Map<String, String> headers) { 67 // 设置dataSource 68 if(path==null || path.length()==0){ 69 if (mPlayerEventListener!=null){ 70 mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_URL_NULL, 0); 71 } 72 return; 73 } 74 try { 75 //解析path 76 Uri uri = Uri.parse(path); 77 if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) { 78 RawDataSourceProvider rawDataSourceProvider = RawDataSourceProvider.create(mAppContext, uri); 79 mMediaPlayer.setDataSource(rawDataSourceProvider); 80 } else { 81 //处理UA问题 82 if (headers != null) { 83 String userAgent = headers.get("User-Agent"); 84 if (!TextUtils.isEmpty(userAgent)) { 85 mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "user_agent", userAgent); 86 } 87 } 88 mMediaPlayer.setDataSource(mAppContext, uri, headers); 89 } 90 } catch (Exception e) { 91 mPlayerEventListener.onError(); 92 } 93 } 94 95 /** 96 * 用于播放raw和asset里面的视频文件 97 */ 98 @Override 99 public void setDataSource(AssetFileDescriptor fd) { 100 try { 101 mMediaPlayer.setDataSource(new RawDataSourceProvider(fd)); 102 } catch (Exception e) { 103 mPlayerEventListener.onError(); 104 } 105 } 106 107 /** 108 * 设置渲染视频的View,主要用于TextureView 109 * @param surface surface 110 */ 111 @Override 112 public void setSurface(Surface surface) { 113 mMediaPlayer.setSurface(surface); 114 } 115 116 /** 117 * 准备开始播放(异步) 118 */ 119 @Override 120 public void prepareAsync() { 121 try { 122 mMediaPlayer.prepareAsync(); 123 } catch (IllegalStateException e) { 124 mPlayerEventListener.onError(); 125 } 126 } 127 128 /** 129 * 暂停 130 */ 131 @Override 132 public void pause() { 133 try { 134 mMediaPlayer.pause(); 135 } catch (IllegalStateException e) { 136 mPlayerEventListener.onError(); 137 } 138 } 139 140 /** 141 * 播放 142 */ 143 @Override 144 public void start() { 145 try { 146 mMediaPlayer.start(); 147 } catch (IllegalStateException e) { 148 mPlayerEventListener.onError(); 149 } 150 } 151 152 /** 153 * 停止 154 */ 155 @Override 156 public void stop() { 157 try { 158 mMediaPlayer.stop(); 159 } catch (IllegalStateException e) { 160 mPlayerEventListener.onError(); 161 } 162 } 163 164 /** 165 * 重置播放器 166 */ 167 @Override 168 public void reset() { 169 mMediaPlayer.reset(); 170 mMediaPlayer.setOnVideoSizeChangedListener(onVideoSizeChangedListener); 171 setOptions(); 172 } 173 174 /** 175 * 是否正在播放 176 */ 177 @Override 178 public boolean isPlaying() { 179 return mMediaPlayer.isPlaying(); 180 } 181 182 183 /** 184 * 调整进度 185 */ 186 @Override 187 public void seekTo(long time) { 188 try { 189 mMediaPlayer.seekTo((int) time); 190 } catch (IllegalStateException e) { 191 mPlayerEventListener.onError(); 192 } 193 } 194 195 /** 196 * 释放播放器 197 */ 198 @Override 199 public void release() { 200 mMediaPlayer.setOnErrorListener(null); 201 mMediaPlayer.setOnCompletionListener(null); 202 mMediaPlayer.setOnInfoListener(null); 203 mMediaPlayer.setOnBufferingUpdateListener(null); 204 mMediaPlayer.setOnPreparedListener(null); 205 mMediaPlayer.setOnVideoSizeChangedListener(null); 206 new Thread() { 207 @Override 208 public void run() { 209 try { 210 mMediaPlayer.release(); 211 } catch (Exception e) { 212 e.printStackTrace(); 213 } 214 } 215 }.start(); 216 } 217 218 /** 219 * 获取当前播放的位置 220 */ 221 @Override 222 public long getCurrentPosition() { 223 return mMediaPlayer.getCurrentPosition(); 224 } 225 226 /** 227 * 获取视频总时长 228 */ 229 @Override 230 public long getDuration() { 231 return mMediaPlayer.getDuration(); 232 } 233 234 /** 235 * 获取缓冲百分比 236 */ 237 @Override 238 public int getBufferedPercentage() { 239 return mBufferedPercent; 240 } 241 242 /** 243 * 设置渲染视频的View,主要用于SurfaceView 244 */ 245 @Override 246 public void setDisplay(SurfaceHolder holder) { 247 mMediaPlayer.setDisplay(holder); 248 } 249 250 /** 251 * 设置音量 252 */ 253 @Override 254 public void setVolume(float v1, float v2) { 255 mMediaPlayer.setVolume(v1, v2); 256 } 257 258 /** 259 * 设置是否循环播放 260 */ 261 @Override 262 public void setLooping(boolean isLooping) { 263 mMediaPlayer.setLooping(isLooping); 264 } 265 266 /** 267 * 设置播放速度 268 */ 269 @Override 270 public void setSpeed(float speed) { 271 mMediaPlayer.setSpeed(speed); 272 } 273 274 /** 275 * 获取播放速度 276 */ 277 @Override 278 public float getSpeed() { 279 return mMediaPlayer.getSpeed(0); 280 } 281 282 /** 283 * 获取当前缓冲的网速 284 */ 285 @Override 286 public long getTcpSpeed() { 287 return mMediaPlayer.getTcpSpeed(); 288 } 289 290 /** 291 * 设置视频错误监听器 292 * int MEDIA_INFO_VIDEO_RENDERING_START = 3;//视频准备渲染 293 * int MEDIA_INFO_BUFFERING_START = 701;//开始缓冲 294 * int MEDIA_INFO_BUFFERING_END = 702;//缓冲结束 295 * int MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001;//视频选择信息 296 * int MEDIA_ERROR_SERVER_DIED = 100;//视频中断,一般是视频源异常或者不支持的视频类型。 297 * int MEDIA_ERROR_IJK_PLAYER = -10000,//一般是视频源有问题或者数据格式不支持,比如音频不是AAC之类的 298 * int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;//数据错误没有有效的回收 299 */ 300 private IMediaPlayer.OnErrorListener onErrorListener = new IMediaPlayer.OnErrorListener() { 301 @Override 302 public boolean onError(IMediaPlayer iMediaPlayer, int framework_err, int impl_err) { 303 mPlayerEventListener.onError(); 304 VideoLogUtils.d("IjkVideoPlayer----listener---------onError ——> STATE_ERROR ———— what:" + framework_err + ", extra: " + impl_err); 305 return true; 306 } 307 }; 308 309 /** 310 * 设置视频播放完成监听事件 311 */ 312 private IMediaPlayer.OnCompletionListener onCompletionListener = new IMediaPlayer.OnCompletionListener() { 313 @Override 314 public void onCompletion(IMediaPlayer iMediaPlayer) { 315 mPlayerEventListener.onCompletion(); 316 VideoLogUtils.d("IjkVideoPlayer----listener---------onCompletion ——> STATE_COMPLETED"); 317 } 318 }; 319 320 321 /** 322 * 设置视频信息监听器 323 */ 324 private IMediaPlayer.OnInfoListener onInfoListener = new IMediaPlayer.OnInfoListener() { 325 @Override 326 public boolean onInfo(IMediaPlayer iMediaPlayer, int what, int extra) { 327 mPlayerEventListener.onInfo(what, extra); 328 VideoLogUtils.d("IjkVideoPlayer----listener---------onInfo ——> ———— what:" + what + ", extra: " + extra); 329 return true; 330 } 331 }; 332 333 /** 334 * 设置视频缓冲更新监听事件 335 */ 336 private IMediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new IMediaPlayer.OnBufferingUpdateListener() { 337 @Override 338 public void onBufferingUpdate(IMediaPlayer iMediaPlayer, int percent) { 339 mBufferedPercent = percent; 340 } 341 }; 342 343 344 /** 345 * 设置准备视频播放监听事件 346 */ 347 private IMediaPlayer.OnPreparedListener onPreparedListener = new IMediaPlayer.OnPreparedListener() { 348 @Override 349 public void onPrepared(IMediaPlayer iMediaPlayer) { 350 mPlayerEventListener.onPrepared(); 351 VideoLogUtils.d("IjkVideoPlayer----listener---------onPrepared ——> STATE_PREPARED"); 352 } 353 }; 354 355 /** 356 * 设置视频大小更改监听器 357 */ 358 private IMediaPlayer.OnVideoSizeChangedListener onVideoSizeChangedListener = new IMediaPlayer.OnVideoSizeChangedListener() { 359 @Override 360 public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int width, int height, 361 int sar_num, int sar_den) { 362 int videoWidth = iMediaPlayer.getVideoWidth(); 363 int videoHeight = iMediaPlayer.getVideoHeight(); 364 if (videoWidth != 0 && videoHeight != 0) { 365 mPlayerEventListener.onVideoSizeChanged(videoWidth, videoHeight); 366 } 367 VideoLogUtils.d("IjkVideoPlayer----listener---------onVideoSizeChanged ——> WIDTH:" + width + ", HEIGHT:" + height); 368 } 369 }; 370 371 /** 372 * 设置时间文本监听器 373 */ 374 private IMediaPlayer.OnTimedTextListener onTimedTextListener = new IMediaPlayer.OnTimedTextListener() { 375 @Override 376 public void onTimedText(IMediaPlayer iMediaPlayer, IjkTimedText ijkTimedText) { 377 378 } 379 }; 380 381 /** 382 * 设置视频seek完成监听事件 383 */ 384 private IMediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new IMediaPlayer.OnSeekCompleteListener() { 385 @Override 386 public void onSeekComplete(IMediaPlayer iMediaPlayer) { 387 388 } 389 }; 390}
05.看一下exo的内核实现类
-
exo的内核实现类代码如下所示,和ijk的api有些区别
1public class ExoMediaPlayer extends AbstractVideoPlayer implements VideoListener, Player.EventListener { 2 3 protected Context mAppContext; 4 protected SimpleExoPlayer mInternalPlayer; 5 protected MediaSource mMediaSource; 6 protected ExoMediaSourceHelper mMediaSourceHelper; 7 private PlaybackParameters mSpeedPlaybackParameters; 8 private int mLastReportedPlaybackState = Player.STATE_IDLE; 9 private boolean mLastReportedPlayWhenReady = false; 10 private boolean mIsPreparing; 11 private boolean mIsBuffering; 12 13 private LoadControl mLoadControl; 14 private RenderersFactory mRenderersFactory; 15 private TrackSelector mTrackSelector; 16 17 public ExoMediaPlayer(Context context) { 18 if (context instanceof Application){ 19 mAppContext = context; 20 } else { 21 mAppContext = context.getApplicationContext(); 22 } 23 mMediaSourceHelper = ExoMediaSourceHelper.getInstance(context); 24 } 25 26 @Override 27 public void initPlayer() { 28 //创建exo播放器 29 mInternalPlayer = new SimpleExoPlayer.Builder( 30 mAppContext, 31 mRenderersFactory == null ? mRenderersFactory = new DefaultRenderersFactory(mAppContext) : mRenderersFactory, 32 mTrackSelector == null ? mTrackSelector = new DefaultTrackSelector(mAppContext) : mTrackSelector, 33 mLoadControl == null ? mLoadControl = new DefaultLoadControl() : mLoadControl, 34 DefaultBandwidthMeter.getSingletonInstance(mAppContext), 35 Util.getLooper(), 36 new AnalyticsCollector(Clock.DEFAULT), 37 /* useLazyPreparation= */ true, 38 Clock.DEFAULT) 39 .build(); 40 setOptions(); 41 42 //播放器日志 43 if (VideoLogUtils.isIsLog() && mTrackSelector instanceof MappingTrackSelector) { 44 mInternalPlayer.addAnalyticsListener(new EventLogger((MappingTrackSelector) mTrackSelector, "ExoPlayer")); 45 } 46 initListener(); 47 } 48 49 /** 50 * exo视频播放器监听listener 51 */ 52 private void initListener() { 53 mInternalPlayer.addListener(this); 54 mInternalPlayer.addVideoListener(this); 55 } 56 57 public void setTrackSelector(TrackSelector trackSelector) { 58 mTrackSelector = trackSelector; 59 } 60 61 public void setRenderersFactory(RenderersFactory renderersFactory) { 62 mRenderersFactory = renderersFactory; 63 } 64 65 public void setLoadControl(LoadControl loadControl) { 66 mLoadControl = loadControl; 67 } 68 69 /** 70 * 设置播放地址 71 * 72 * @param path 播放地址 73 * @param headers 播放地址请求头 74 */ 75 @Override 76 public void setDataSource(String path, Map<String, String> headers) { 77 // 设置dataSource 78 if(path==null || path.length()==0){ 79 if (mPlayerEventListener!=null){ 80 mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_URL_NULL, 0); 81 } 82 return; 83 } 84 mMediaSource = mMediaSourceHelper.getMediaSource(path, headers); 85 } 86 87 @Override 88 public void setDataSource(AssetFileDescriptor fd) { 89 //no support 90 } 91 92 /** 93 * 准备开始播放(异步) 94 */ 95 @Override 96 public void prepareAsync() { 97 if (mInternalPlayer == null){ 98 return; 99 } 100 if (mMediaSource == null){ 101 return; 102 } 103 if (mSpeedPlaybackParameters != null) { 104 mInternalPlayer.setPlaybackParameters(mSpeedPlaybackParameters); 105 } 106 mIsPreparing = true; 107 mMediaSource.addEventListener(new Handler(), mMediaSourceEventListener); 108 //准备播放 109 mInternalPlayer.prepare(mMediaSource); 110 } 111 112 /** 113 * 播放 114 */ 115 @Override 116 public void start() { 117 if (mInternalPlayer == null){ 118 return; 119 } 120 mInternalPlayer.setPlayWhenReady(true); 121 } 122 123 /** 124 * 暂停 125 */ 126 @Override 127 public void pause() { 128 if (mInternalPlayer == null){ 129 return; 130 } 131 mInternalPlayer.setPlayWhenReady(false); 132 } 133 134 /** 135 * 停止 136 */ 137 @Override 138 public void stop() { 139 if (mInternalPlayer == null){ 140 return; 141 } 142 mInternalPlayer.stop(); 143 } 144 145 private MediaSourceEventListener mMediaSourceEventListener = new MediaSourceEventListener() { 146 @Override 147 public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { 148 if (mPlayerEventListener != null && mIsPreparing) { 149 mPlayerEventListener.onPrepared(); 150 } 151 } 152 }; 153 154 /** 155 * 重置播放器 156 */ 157 @Override 158 public void reset() { 159 if (mInternalPlayer != null) { 160 mInternalPlayer.stop(true); 161 mInternalPlayer.setVideoSurface(null); 162 mIsPreparing = false; 163 mIsBuffering = false; 164 mLastReportedPlaybackState = Player.STATE_IDLE; 165 mLastReportedPlayWhenReady = false; 166 } 167 } 168 169 /** 170 * 是否正在播放 171 */ 172 @Override 173 public boolean isPlaying() { 174 if (mInternalPlayer == null){ 175 return false; 176 } 177 int state = mInternalPlayer.getPlaybackState(); 178 switch (state) { 179 case Player.STATE_BUFFERING: 180 case Player.STATE_READY: 181 return mInternalPlayer.getPlayWhenReady(); 182 case Player.STATE_IDLE: 183 case Player.STATE_ENDED: 184 default: 185 return false; 186 } 187 } 188 189 /** 190 * 调整进度 191 */ 192 @Override 193 public void seekTo(long time) { 194 if (mInternalPlayer == null){ 195 return; 196 } 197 mInternalPlayer.seekTo(time); 198 } 199 200 /** 201 * 释放播放器 202 */ 203 @Override 204 public void release() { 205 if (mInternalPlayer != null) { 206 mInternalPlayer.removeListener(this); 207 mInternalPlayer.removeVideoListener(this); 208 final SimpleExoPlayer player = mInternalPlayer; 209 mInternalPlayer = null; 210 new Thread() { 211 @Override 212 public void run() { 213 //异步释放,防止卡顿 214 player.release(); 215 } 216 }.start(); 217 } 218 219 mIsPreparing = false; 220 mIsBuffering = false; 221 mLastReportedPlaybackState = Player.STATE_IDLE; 222 mLastReportedPlayWhenReady = false; 223 mSpeedPlaybackParameters = null; 224 } 225 226 /** 227 * 获取当前播放的位置 228 */ 229 @Override 230 public long getCurrentPosition() { 231 if (mInternalPlayer == null){ 232 return 0; 233 } 234 return mInternalPlayer.getCurrentPosition(); 235 } 236 237 /** 238 * 获取视频总时长 239 */ 240 @Override 241 public long getDuration() { 242 if (mInternalPlayer == null){ 243 return 0; 244 } 245 return mInternalPlayer.getDuration(); 246 } 247 248 /** 249 * 获取缓冲百分比 250 */ 251 @Override 252 public int getBufferedPercentage() { 253 return mInternalPlayer == null ? 0 : mInternalPlayer.getBufferedPercentage(); 254 } 255 256 /** 257 * 设置渲染视频的View,主要用于SurfaceView 258 */ 259 @Override 260 public void setSurface(Surface surface) { 261 if (mInternalPlayer != null) { 262 mInternalPlayer.setVideoSurface(surface); 263 } 264 } 265 266 @Override 267 public void setDisplay(SurfaceHolder holder) { 268 if (holder == null){ 269 setSurface(null); 270 } else{ 271 setSurface(holder.getSurface()); 272 } 273 } 274 275 /** 276 * 设置音量 277 */ 278 @Override 279 public void setVolume(float leftVolume, float rightVolume) { 280 if (mInternalPlayer != null){ 281 mInternalPlayer.setVolume((leftVolume + rightVolume) / 2); 282 } 283 } 284 285 /** 286 * 设置是否循环播放 287 */ 288 @Override 289 public void setLooping(boolean isLooping) { 290 if (mInternalPlayer != null){ 291 mInternalPlayer.setRepeatMode(isLooping ? Player.REPEAT_MODE_ALL : Player.REPEAT_MODE_OFF); 292 } 293 } 294 295 @Override 296 public void setOptions() { 297 //准备好就开始播放 298 mInternalPlayer.setPlayWhenReady(true); 299 } 300 301 /** 302 * 设置播放速度 303 */ 304 @Override 305 public void setSpeed(float speed) { 306 PlaybackParameters playbackParameters = new PlaybackParameters(speed); 307 mSpeedPlaybackParameters = playbackParameters; 308 if (mInternalPlayer != null) { 309 mInternalPlayer.setPlaybackParameters(playbackParameters); 310 } 311 } 312 313 /** 314 * 获取播放速度 315 */ 316 @Override 317 public float getSpeed() { 318 if (mSpeedPlaybackParameters != null) { 319 return mSpeedPlaybackParameters.speed; 320 } 321 return 1f; 322 } 323 324 /** 325 * 获取当前缓冲的网速 326 */ 327 @Override 328 public long getTcpSpeed() { 329 // no support 330 return 0; 331 } 332 333 @Override 334 public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { 335 if (mPlayerEventListener == null){ 336 return; 337 } 338 if (mIsPreparing){ 339 return; 340 } 341 if (mLastReportedPlayWhenReady != playWhenReady || mLastReportedPlaybackState != playbackState) { 342 switch (playbackState) { 343 //最开始调用的状态 344 case Player.STATE_IDLE: 345 break; 346 //开始缓充 347 case Player.STATE_BUFFERING: 348 mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_BUFFERING_START, getBufferedPercentage()); 349 mIsBuffering = true; 350 break; 351 //开始播放 352 case Player.STATE_READY: 353 if (mIsBuffering) { 354 mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_BUFFERING_END, getBufferedPercentage()); 355 mIsBuffering = false; 356 } 357 break; 358 //播放器已经播放完了媒体 359 case Player.STATE_ENDED: 360 mPlayerEventListener.onCompletion(); 361 break; 362 default: 363 break; 364 } 365 mLastReportedPlaybackState = playbackState; 366 mLastReportedPlayWhenReady = playWhenReady; 367 } 368 } 369 370 @Override 371 public void onPlayerError(ExoPlaybackException error) { 372 if (mPlayerEventListener != null) { 373 mPlayerEventListener.onError(); 374 } 375 } 376 377 @Override 378 public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { 379 if (mPlayerEventListener != null) { 380 mPlayerEventListener.onVideoSizeChanged(width, height); 381 if (unappliedRotationDegrees > 0) { 382 mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_VIDEO_ROTATION_CHANGED, unappliedRotationDegrees); 383 } 384 } 385 } 386 387 @Override 388 public void onRenderedFirstFrame() { 389 if (mPlayerEventListener != null && mIsPreparing) { 390 mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_VIDEO_RENDERING_START, 0); 391 mIsPreparing = false; 392 } 393 } 394}
06.如何创建不同内核播放器
-
先来看一下创建不同内核播放器的代码,只需要开发者传入一个类型参数,即可创建不同类的实例对象。代码如下所示
1/** 2 * 获取PlayerFactory具体实现类,获取内核 3 * 创建对象的时候只需要传递类型type,而不需要对应的工厂,即可创建具体的产品对象 4 * TYPE_IJK IjkPlayer,基于IjkPlayer封装播放器 5 * TYPE_NATIVE MediaPlayer,基于原生自带的播放器控件 6 * TYPE_EXO 基于谷歌视频播放器 7 * TYPE_RTC 基于RTC视频播放器 8 * @param type 类型 9 * @return 10 */ 11public static AbstractVideoPlayer getVideoPlayer(Context context,@PlayerConstant.PlayerType int type){ 12 if (type == PlayerConstant.PlayerType.TYPE_EXO){ 13 return ExoPlayerFactory.create().createPlayer(context); 14 } else if (type == PlayerConstant.PlayerType.TYPE_IJK){ 15 return IjkPlayerFactory.create().createPlayer(context); 16 } else if (type == PlayerConstant.PlayerType.TYPE_NATIVE){ 17 return MediaPlayerFactory.create().createPlayer(context); 18 } else if (type == PlayerConstant.PlayerType.TYPE_RTC){ 19 return IjkPlayerFactory.create().createPlayer(context); 20 } else { 21 return IjkPlayerFactory.create().createPlayer(context); 22 } 23} -
使用工厂模式创建不同对象的动机是什么,为何要这样使用?
- 一个视频播放器可以提供多个内核Player(如ijk、exo、media,rtc等等), 这些player都源自同一个基类,不过在继承基类后不同的子类修改了部分属性从而使得它们可以呈现不同的外观。
- 如果希望在使用这些内核player时,不需要知道这些具体内核的名字,只需要知道表示该内核类的一个参数,并提供一个调用方便的方法,把该参数传入方法即可返回一个相应的内核对象,此时,就可以使用工厂模式。
-
首先定义一个工厂抽象类,然后不同的内核播放器分别创建其具体的工厂实现具体类
- PlayerFactory:抽象工厂,担任这个角色的是工厂方法模式的核心,任何在模式中创建对象的工厂类必须实现这个接口
- ExoPlayerFactory:具体工厂,具体工厂角色含有与业务密切相关的逻辑,并且受到使用者的调用以创建具体产品对象。
-
如何使用,分为三步,具体操作如下所示
- 1.先调用具体工厂对象中的方法createPlayer方法;2.根据传入产品类型参数获得具体的产品对象;3.返回产品对象并使用。
- 简而言之,创建对象的时候只需要传递类型type,而不需要对应的工厂,即可创建具体的产品对象
07.看一下工厂类实现代码
-
抽象工厂类,代码如下所示
1public abstract class PlayerFactory<T extends AbstractVideoPlayer> { 2 public abstract T createPlayer(Context context); 3} -
具体实现工厂类,代码如下所示
1public class ExoPlayerFactory extends PlayerFactory<ExoMediaPlayer> { 2 3 public static ExoPlayerFactory create() { 4 return new ExoPlayerFactory(); 5 } 6 7 @Override 8 public ExoMediaPlayer createPlayer(Context context) { 9 return new ExoMediaPlayer(context); 10 } 11} -
这种创建对象最大优点
- 工厂方法用来创建所需要的产品,同时隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
- 加入新的产品时,比如后期新加一个阿里播放器内核,这个时候就只需要添加一个具体工厂和具体产品就可以。系统的可扩展性也就变得非常好,完全符合“开闭原则”
08.后期如何添加新的内核
-
比如后期想要添加一个腾讯视频内核的播放器。代码如下所示,这个是简化的
1public class TxPlayerFactory extends PlayerFactory<TxMediaPlayer> { 2 3 public static TxPlayerFactory create() { 4 return new TxPlayerFactory(); 5 } 6 7 @Override 8 public TxMediaPlayer createPlayer(Context context) { 9 return new TxMediaPlayer(context); 10 } 11} 12 13public class TxMediaPlayer extends AbstractVideoPlayer { 14 //省略接口的实现方法代码 15}