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.播放器内核架构图 image03.如何兼容不同内核播放器 提问:针对不同内核播放器,比如谷歌的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 { 代码语言: txt
复制 protected IjkMediaPlayer mMediaPlayer;
代码语言: txt
复制 private int mBufferedPercent;
代码语言: txt
复制 private Context mAppContext;
代码语言: txt
复制 public IjkVideoPlayer(Context context) {
代码语言: txt
复制 if (context instanceof Application){
代码语言: txt
复制 mAppContext = context;
代码语言: txt
复制 } else {
代码语言: txt
复制 mAppContext = context.getApplicationContext();
代码语言: txt
复制 }
代码语言: txt
复制 }
代码语言: txt
复制 @Override
代码语言: txt
复制 public void initPlayer() {
代码语言: txt
复制 mMediaPlayer = new IjkMediaPlayer();
代码语言: txt
复制 //native日志
代码语言: txt
复制 IjkMediaPlayer.native_setLogLevel(VideoLogUtils.isIsLog()
代码语言: txt
复制 ? IjkMediaPlayer.IJK_LOG_INFO : IjkMediaPlayer.IJK_LOG_SILENT);
代码语言: txt
复制 setOptions();
代码语言: txt
复制 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
代码语言: txt
复制 initListener();
代码语言: txt
复制 }
代码语言: txt
复制 @Override
代码语言: txt
复制 public void setOptions() {
代码语言: txt
复制 }
代码语言: txt
复制 /**
代码语言: txt
复制 * 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;
}
});
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置播放地址
*
* @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();
}
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 用于播放raw和asset里面的视频文件
*/
@Override
public void setDataSource(AssetFileDescriptor fd) {
try {
mMediaPlayer.setDataSource(new RawDataSourceProvider(fd));
} catch (Exception e) {
mPlayerEventListener.onError();
}
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置渲染视频的View,主要用于TextureView
* @param surface surface
*/
@Override
public void setSurface(Surface surface) {
mMediaPlayer.setSurface(surface);
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 准备开始播放(异步)
*/
@Override
public void prepareAsync() {
try {
mMediaPlayer.prepareAsync();
} catch (IllegalStateException e) {
mPlayerEventListener.onError();
}
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 暂停
*/
@Override
public void pause() {
try {
mMediaPlayer.pause();
} catch (IllegalStateException e) {
mPlayerEventListener.onError();
}
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 播放
*/
@Override
public void start() {
try {
mMediaPlayer.start();
} catch (IllegalStateException e) {
mPlayerEventListener.onError();
}
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 停止
*/
@Override
public void stop() {
try {
mMediaPlayer.stop();
} catch (IllegalStateException e) {
mPlayerEventListener.onError();
}
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 重置播放器
*/
@Override
public void reset() {
mMediaPlayer.reset();
mMediaPlayer.setOnVideoSizeChangedListener(onVideoSizeChangedListener);
setOptions();
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 是否正在播放
*/
@Override
public boolean isPlaying() {
return mMediaPlayer.isPlaying();
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 调整进度
*/
@Override
public void seekTo(long time) {
try {
mMediaPlayer.seekTo((int) time);
} catch (IllegalStateException e) {
mPlayerEventListener.onError();
}
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 释放播放器
*/
@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();
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 获取当前播放的位置
*/
@Override
public long getCurrentPosition() {
return mMediaPlayer.getCurrentPosition();
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 获取视频总时长
*/
@Override
public long getDuration() {
return mMediaPlayer.getDuration();
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 获取缓冲百分比
*/
@Override
public int getBufferedPercentage() {
return mBufferedPercent;
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置渲染视频的View,主要用于SurfaceView
*/
@Override
public void setDisplay(SurfaceHolder holder) {
mMediaPlayer.setDisplay(holder);
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置音量
*/
@Override
public void setVolume(float v1, float v2) {
mMediaPlayer.setVolume(v1, v2);
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置是否循环播放
*/
@Override
public void setLooping(boolean isLooping) {
mMediaPlayer.setLooping(isLooping);
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置播放速度
*/
@Override
public void setSpeed(float speed) {
mMediaPlayer.setSpeed(speed);
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 获取播放速度
*/
@Override
public float getSpeed() {
return mMediaPlayer.getSpeed(0);
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 获取当前缓冲的网速
*/
@Override
public long getTcpSpeed() {
return mMediaPlayer.getTcpSpeed();
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置视频错误监听器
* 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;
}
};
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置视频播放完成监听事件
*/
private IMediaPlayer.OnCompletionListener onCompletionListener = new IMediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(IMediaPlayer iMediaPlayer) {
mPlayerEventListener.onCompletion();
VideoLogUtils.d("IjkVideoPlayer----listener---------onCompletion ——> STATE_COMPLETED");
}
};
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置视频信息监听器
*/
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;
}
};
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置视频缓冲更新监听事件
*/
private IMediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new IMediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(IMediaPlayer iMediaPlayer, int percent) {
mBufferedPercent = percent;
}
};
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置准备视频播放监听事件
*/
private IMediaPlayer.OnPreparedListener onPreparedListener = new IMediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(IMediaPlayer iMediaPlayer) {
mPlayerEventListener.onPrepared();
VideoLogUtils.d("IjkVideoPlayer----listener---------onPrepared ——> STATE_PREPARED");
}
};
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置视频大小更改监听器
*/
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);
}
};
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置时间文本监听器
*/
private IMediaPlayer.OnTimedTextListener onTimedTextListener = new IMediaPlayer.OnTimedTextListener() {
@Override
public void onTimedText(IMediaPlayer iMediaPlayer, IjkTimedText ijkTimedText) {
代码语言: txt
复制 }
代码语言: txt
复制 };
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置视频seek完成监听事件
*/
private IMediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new IMediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(IMediaPlayer iMediaPlayer) {
代码语言: txt
复制 }
代码语言: txt
复制 };
代码语言: txt
复制 }
代码语言: txt
复制 ```
05.看一下exo的内核实现类 exo的内核实现类代码如下所示,和ijk的api有些区别public class ExoMediaPlayer extends AbstractVideoPlayer implements VideoListener, Player.EventListener { 代码语言: txt
复制 protected Context mAppContext;
代码语言: txt
复制 protected SimpleExoPlayer mInternalPlayer;
代码语言: txt
复制 protected MediaSource mMediaSource;
代码语言: txt
复制 protected ExoMediaSourceHelper mMediaSourceHelper;
代码语言: txt
复制 private PlaybackParameters mSpeedPlaybackParameters;
代码语言: txt
复制 private int mLastReportedPlaybackState = Player.STATE_IDLE;
代码语言: txt
复制 private boolean mLastReportedPlayWhenReady = false;
代码语言: txt
复制 private boolean mIsPreparing;
代码语言: txt
复制 private boolean mIsBuffering;
代码语言: txt
复制 private LoadControl mLoadControl;
代码语言: txt
复制 private RenderersFactory mRenderersFactory;
代码语言: txt
复制 private TrackSelector mTrackSelector;
代码语言: txt
复制 public ExoMediaPlayer(Context context) {
代码语言: txt
复制 if (context instanceof Application){
代码语言: txt
复制 mAppContext = context;
代码语言: txt
复制 } else {
代码语言: txt
复制 mAppContext = context.getApplicationContext();
代码语言: txt
复制 }
代码语言: txt
复制 mMediaSourceHelper = ExoMediaSourceHelper.getInstance(context);
代码语言: txt
复制 }
代码语言: txt
复制 @Override
代码语言: txt
复制 public void initPlayer() {
代码语言: txt
复制 //创建exo播放器
代码语言: txt
复制 mInternalPlayer = new SimpleExoPlayer.Builder(
代码语言: txt
复制 mAppContext,
代码语言: txt
复制 mRenderersFactory == null ? mRenderersFactory = new DefaultRenderersFactory(mAppContext) : mRenderersFactory,
代码语言: txt
复制 mTrackSelector == null ? mTrackSelector = new DefaultTrackSelector(mAppContext) : mTrackSelector,
代码语言: txt
复制 mLoadControl == null ? mLoadControl = new DefaultLoadControl() : mLoadControl,
代码语言: txt
复制 DefaultBandwidthMeter.getSingletonInstance(mAppContext),
代码语言: txt
复制 Util.getLooper(),
代码语言: txt
复制 new AnalyticsCollector(Clock.DEFAULT),
代码语言: txt
复制 /* useLazyPreparation= */ true,
代码语言: txt
复制 Clock.DEFAULT)
代码语言: txt
复制 .build();
代码语言: txt
复制 setOptions();
代码语言: txt
复制 //播放器日志
代码语言: txt
复制 if (VideoLogUtils.isIsLog() && mTrackSelector instanceof MappingTrackSelector) {
代码语言: txt
复制 mInternalPlayer.addAnalyticsListener(new EventLogger((MappingTrackSelector) mTrackSelector, "ExoPlayer"));
代码语言: txt
复制 }
代码语言: txt
复制 initListener();
代码语言: txt
复制 }
代码语言: txt
复制 /**
代码语言: txt
复制 * exo视频播放器监听listener
*/
private void initListener() {
mInternalPlayer.addListener(this);
mInternalPlayer.addVideoListener(this);
}
代码语言: txt
复制 public void setTrackSelector(TrackSelector trackSelector) {
代码语言: txt
复制 mTrackSelector = trackSelector;
代码语言: txt
复制 }
代码语言: txt
复制 public void setRenderersFactory(RenderersFactory renderersFactory) {
代码语言: txt
复制 mRenderersFactory = renderersFactory;
代码语言: txt
复制 }
代码语言: txt
复制 public void setLoadControl(LoadControl loadControl) {
代码语言: txt
复制 mLoadControl = loadControl;
代码语言: txt
复制 }
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置播放地址
*
* @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;
}
mMediaSource = mMediaSourceHelper.getMediaSource(path, headers);
}
代码语言: txt
复制 @Override
代码语言: txt
复制 public void setDataSource(AssetFileDescriptor fd) {
代码语言: txt
复制 //no support
代码语言: txt
复制 }
代码语言: txt
复制 /**
代码语言: txt
复制 * 准备开始播放(异步)
*/
@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);
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 播放
*/
@Override
public void start() {
if (mInternalPlayer == null){
return;
}
mInternalPlayer.setPlayWhenReady(true);
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 暂停
*/
@Override
public void pause() {
if (mInternalPlayer == null){
return;
}
mInternalPlayer.setPlayWhenReady(false);
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 停止
*/
@Override
public void stop() {
if (mInternalPlayer == null){
return;
}
mInternalPlayer.stop();
}
代码语言: txt
复制 private MediaSourceEventListener mMediaSourceEventListener = new MediaSourceEventListener() {
代码语言: txt
复制 @Override
代码语言: txt
复制 public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
代码语言: txt
复制 if (mPlayerEventListener != null && mIsPreparing) {
代码语言: txt
复制 mPlayerEventListener.onPrepared();
代码语言: txt
复制 }
代码语言: txt
复制 }
代码语言: txt
复制 };
代码语言: txt
复制 /**
代码语言: txt
复制 * 重置播放器
*/
@Override
public void reset() {
if (mInternalPlayer != null) {
mInternalPlayer.stop(true);
mInternalPlayer.setVideoSurface(null);
mIsPreparing = false;
mIsBuffering = false;
mLastReportedPlaybackState = Player.STATE_IDLE;
mLastReportedPlayWhenReady = false;
}
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 是否正在播放
*/
@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;
}
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 调整进度
*/
@Override
public void seekTo(long time) {
if (mInternalPlayer == null){
return;
}
mInternalPlayer.seekTo(time);
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 释放播放器
*/
@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();
}
代码语言: txt
复制 mIsPreparing = false;
代码语言: txt
复制 mIsBuffering = false;
代码语言: txt
复制 mLastReportedPlaybackState = Player.STATE_IDLE;
代码语言: txt
复制 mLastReportedPlayWhenReady = false;
代码语言: txt
复制 mSpeedPlaybackParameters = null;
代码语言: txt
复制 }
代码语言: txt
复制 /**
代码语言: txt
复制 * 获取当前播放的位置
*/
@Override
public long getCurrentPosition() {
if (mInternalPlayer == null){
return 0;
}
return mInternalPlayer.getCurrentPosition();
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 获取视频总时长
*/
@Override
public long getDuration() {
if (mInternalPlayer == null){
return 0;
}
return mInternalPlayer.getDuration();
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 获取缓冲百分比
*/
@Override
public int getBufferedPercentage() {
return mInternalPlayer == null ? 0 : mInternalPlayer.getBufferedPercentage();
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置渲染视频的View,主要用于SurfaceView
*/
@Override
public void setSurface(Surface surface) {
if (mInternalPlayer != null) {
mInternalPlayer.setVideoSurface(surface);
}
}
代码语言: txt
复制 @Override
代码语言: txt
复制 public void setDisplay(SurfaceHolder holder) {
代码语言: txt
复制 if (holder == null){
代码语言: txt
复制 setSurface(null);
代码语言: txt
复制 } else{
代码语言: txt
复制 setSurface(holder.getSurface());
代码语言: txt
复制 }
代码语言: txt
复制 }
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置音量
*/
@Override
public void setVolume(float leftVolume, float rightVolume) {
if (mInternalPlayer != null){
mInternalPlayer.setVolume((leftVolume rightVolume) / 2);
}
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置是否循环播放
*/
@Override
public void setLooping(boolean isLooping) {
if (mInternalPlayer != null){
mInternalPlayer.setRepeatMode(isLooping ? Player.REPEAT_MODE_ALL : Player.REPEAT_MODE_OFF);
}
}
代码语言: txt
复制 @Override
代码语言: txt
复制 public void setOptions() {
代码语言: txt
复制 //准备好就开始播放
代码语言: txt
复制 mInternalPlayer.setPlayWhenReady(true);
代码语言: txt
复制 }
代码语言: txt
复制 /**
代码语言: txt
复制 * 设置播放速度
*/
@Override
public void setSpeed(float speed) {
PlaybackParameters playbackParameters = new PlaybackParameters(speed);
mSpeedPlaybackParameters = playbackParameters;
if (mInternalPlayer != null) {
mInternalPlayer.setPlaybackParameters(playbackParameters);
}
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 获取播放速度
*/
@Override
public float getSpeed() {
if (mSpeedPlaybackParameters != null) {
return mSpeedPlaybackParameters.speed;
}
return 1f;
}
代码语言: txt
复制 /**
代码语言: txt
复制 * 获取当前缓冲的网速
*/
@Override
public long getTcpSpeed() {
// no support
return 0;
}
代码语言: txt
复制 @Override
代码语言: txt
复制 public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
代码语言: txt
复制 if (mPlayerEventListener == null){
代码语言: txt
复制 return;
代码语言: txt
复制 }
代码语言: txt
复制 if (mIsPreparing){
代码语言: txt
复制 return;
代码语言: txt
复制 }
代码语言: txt
复制 if (mLastReportedPlayWhenReady != playWhenReady || mLastReportedPlaybackState != playbackState) {
代码语言: txt
复制 switch (playbackState) {
代码语言: txt
复制 //最开始调用的状态
代码语言: txt
复制 case Player.STATE_IDLE:
代码语言: txt
复制 break;
代码语言: txt
复制 //开始缓充
代码语言: txt
复制 case Player.STATE_BUFFERING:
代码语言: txt
复制 mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_BUFFERING_START, getBufferedPercentage());
代码语言: txt
复制 mIsBuffering = true;
代码语言: txt
复制 break;
代码语言: txt
复制 //开始播放
代码语言: txt
复制 case Player.STATE_READY:
代码语言: txt
复制 if (mIsBuffering) {
代码语言: txt
复制 mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_BUFFERING_END, getBufferedPercentage());
代码语言: txt
复制 mIsBuffering = false;
代码语言: txt
复制 }
代码语言: txt
复制 break;
代码语言: txt
复制 //播放器已经播放完了媒体
代码语言: txt
复制 case Player.STATE_ENDED:
代码语言: txt
复制 mPlayerEventListener.onCompletion();
代码语言: txt
复制 break;
代码语言: txt
复制 default:
代码语言: txt
复制 break;
代码语言: txt
复制 }
代码语言: txt
复制 mLastReportedPlaybackState = playbackState;
代码语言: txt
复制 mLastReportedPlayWhenReady = playWhenReady;
代码语言: txt
复制 }
代码语言: txt
复制 }
代码语言: txt
复制 @Override
代码语言: txt
复制 public void onPlayerError(ExoPlaybackException error) {
代码语言: txt
复制 if (mPlayerEventListener != null) {
代码语言: txt
复制 mPlayerEventListener.onError();
代码语言: txt
复制 }
代码语言: txt
复制 }
代码语言: txt
复制 @Override
代码语言: txt
复制 public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
代码语言: txt
复制 if (mPlayerEventListener != null) {
代码语言: txt
复制 mPlayerEventListener.onVideoSizeChanged(width, height);
代码语言: txt
复制 if (unappliedRotationDegrees > 0) {
代码语言: txt
复制 mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_VIDEO_ROTATION_CHANGED, unappliedRotationDegrees);
代码语言: txt
复制 }
代码语言: txt
复制 }
代码语言: txt
复制 }
代码语言: txt
复制 @Override
代码语言: txt
复制 public void onRenderedFirstFrame() {
代码语言: txt
复制 if (mPlayerEventListener != null && mIsPreparing) {
代码语言: txt
复制 mPlayerEventListener.onInfo(PlayerConstant.MEDIA_INFO_VIDEO_RENDERING_START, 0);
代码语言: txt
复制 mIsPreparing = false;
代码语言: txt
复制 }
代码语言: txt
复制 }
代码语言: txt
复制 }
代码语言: txt
复制 ```
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> { 代码语言: txt
复制 public static ExoPlayerFactory create() {
代码语言: txt
复制 return new ExoPlayerFactory();
代码语言: txt
复制 }
代码语言: txt
复制 @Override
代码语言: txt
复制 public ExoMediaPlayer createPlayer(Context context) {
代码语言: txt
复制 return new ExoMediaPlayer(context);
代码语言: txt
复制 }
代码语言: txt
复制 }
代码语言: txt
复制 ```
这种创建对象最大优点工厂方法用来创建所需要的产品,同时隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。 加入新的产品时,比如后期新加一个阿里播放器内核,这个时候就只需要添加一个具体工厂和具体产品就可以。系统的可扩展性也就变得非常好,完全符合“开闭原则” 08.后期如何添加新的内核 比如后期想要添加一个腾讯视频内核的播放器。代码如下所示,这个是简化的public class TxPlayerFactory extends PlayerFactory<TxMediaPlayer> { 代码语言: txt
复制 public static TxPlayerFactory create() {
代码语言: txt
复制 return new TxPlayerFactory();
代码语言: txt
复制 }
代码语言: txt
复制 @Override
代码语言: txt
复制 public TxMediaPlayer createPlayer(Context context) {
代码语言: txt
复制 return new TxMediaPlayer(context);
代码语言: txt
复制 }
代码语言: txt
复制 }
代码语言: txt
复制 public class TxMediaPlayer extends AbstractVideoPlayer {
代码语言: txt
复制 //省略接口的实现方法代码
代码语言: txt
复制 }
代码语言: txt
复制 ```