05.视频播放器内核切换封装
目录介绍
- 01.视频播放器内核封装需求
- 02.播放器内核架构图
- 03.如何兼容不同内核播放器
- 04.看一下ijk的内核实现类
- 05.看一下exo的内核实现类
- 06.如何创建不同内核播放器
- 07.看一下工厂类实现代码
- 08.后期如何添加新的内核
00.视频播放器通用框架
- 基础封装视频播放器player,可以在ExoPlayer、MediaPlayer,声网RTC视频播放器内核,原生MediaPlayer可以自由切换
- 对于视图状态切换和后期维护拓展,避免功能和业务出现耦合。比如需要支持播放器UI高度定制,而不是该lib库中UI代码
- 针对视频播放,音频播放,播放回放,以及视频直播的功能。使用简单,代码拓展性强,封装性好,主要是和业务彻底解耦,暴露接口监听给开发者处理业务具体逻辑
- 该播放器整体架构:播放器内核(自由切换) + 视频播放器 + 边播边缓存 + 高度定制播放器UI视图层
- 项目地址:github.com/yangchong21…
- 关于视频播放器整体功能介绍文档: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<String, String> 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); }