|
@@ -1,4 +1,4 @@
|
|
|
-# 06.播放器UI抽取封装
|
|
|
+# 06.视频播放器UI抽取封装
|
|
|
#### 目录介绍
|
|
|
- 01.视频播放器UI封装需求
|
|
|
- 02.播放器UI架构图
|
|
@@ -11,6 +11,19 @@
|
|
|
- 09.视频播放器重力感应监听
|
|
|
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+### 00.视频播放器通用框架
|
|
|
+- 基础封装视频播放器player,可以在ExoPlayer、MediaPlayer,声网RTC视频播放器内核,原生MediaPlayer可以自由切换
|
|
|
+- 对于视图状态切换和后期维护拓展,避免功能和业务出现耦合。比如需要支持播放器UI高度定制,而不是该lib库中UI代码
|
|
|
+- 针对视频播放,音频播放,播放回放,以及视频直播的功能。使用简单,代码拓展性强,封装性好,主要是和业务彻底解耦,暴露接口监听给开发者处理业务具体逻辑
|
|
|
+- 该播放器整体架构:播放器内核(自由切换) + 视频播放器 + 边播边缓存 + 高度定制播放器UI视图层
|
|
|
+- 项目地址:https://github.com/yangchong211/YCVideoPlayer
|
|
|
+- 关于视频播放器整体功能介绍文档:https://juejin.im/post/6883457444752654343
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
### 01.视频播放器UI封装需求
|
|
|
- 发展中遇到的问题
|
|
|
- 播放器可支持多种场景下的播放,多个产品会用到同一个播放器,这样就会带来一个问题,一个播放业务播放器状态发生变化,其他播放业务必须同步更新播放状态,各个播放业务之间互相交叉,随着播放业务的增多,开发和维护成本会急剧增加, 导致后续开发不可持续。
|
|
@@ -29,6 +42,8 @@
|
|
|
|
|
|
|
|
|
### 02.播放器UI架构图
|
|
|
+
|
|
|
+
|
|
|
|
|
|
|
|
|
|
|
@@ -56,13 +71,606 @@
|
|
|
|
|
|
|
|
|
### 04.VideoPlayer如何实现
|
|
|
+- 代码如下所示,省略了部分代码,具体看demo
|
|
|
+ ``` java
|
|
|
+ public class VideoPlayer<P extends AbstractVideoPlayer> extends FrameLayout
|
|
|
+ implements InterVideoPlayer, VideoPlayerListener {
|
|
|
+
|
|
|
+ private Context mContext;
|
|
|
+ /**
|
|
|
+ * 播放器
|
|
|
+ */
|
|
|
+ protected P mMediaPlayer;
|
|
|
+ /**
|
|
|
+ * 实例化播放核心
|
|
|
+ */
|
|
|
+ protected PlayerFactory<P> mPlayerFactory;
|
|
|
+ /**
|
|
|
+ * 控制器
|
|
|
+ */
|
|
|
+ @Nullable
|
|
|
+ protected BaseVideoController mVideoController;
|
|
|
+ /**
|
|
|
+ * 真正承载播放器视图的容器
|
|
|
+ */
|
|
|
+ protected FrameLayout mPlayerContainer;
|
|
|
+
|
|
|
+ public VideoPlayer(@NonNull Context context) {
|
|
|
+ this(context, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ public VideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs) {
|
|
|
+ this(context, attrs, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ public VideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
|
|
+ super(context, attrs, defStyleAttr);
|
|
|
+ mContext = context;
|
|
|
+ init(attrs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void init(AttributeSet attrs) {
|
|
|
+ BaseToast.init(mContext.getApplicationContext());
|
|
|
+ //读取全局配置
|
|
|
+ initConfig();
|
|
|
+ //读取xml中的配置,并综合全局配置
|
|
|
+ initAttrs(attrs);
|
|
|
+ initView();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void initConfig() {
|
|
|
+ VideoPlayerConfig config = VideoViewManager.getConfig();
|
|
|
+ mEnableAudioFocus = config.mEnableAudioFocus;
|
|
|
+ mProgressManager = config.mProgressManager;
|
|
|
+ mPlayerFactory = config.mPlayerFactory;
|
|
|
+ mCurrentScreenScaleType = config.mScreenScaleType;
|
|
|
+ mRenderViewFactory = config.mRenderViewFactory;
|
|
|
+ //设置是否打印日志
|
|
|
+ VideoLogUtils.setIsLog(config.mIsEnableLog);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected Parcelable onSaveInstanceState() {
|
|
|
+ VideoLogUtils.d("onSaveInstanceState: " + mCurrentPosition);
|
|
|
+ //activity切到后台后可能被系统回收,故在此处进行进度保存
|
|
|
+ saveProgress();
|
|
|
+ return super.onSaveInstanceState();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void initAttrs(AttributeSet attrs) {
|
|
|
+ TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.VideoPlayer);
|
|
|
+ mEnableAudioFocus = a.getBoolean(R.styleable.VideoPlayer_enableAudioFocus, mEnableAudioFocus);
|
|
|
+ mIsLooping = a.getBoolean(R.styleable.VideoPlayer_looping, false);
|
|
|
+ mCurrentScreenScaleType = a.getInt(R.styleable.VideoPlayer_screenScaleType, mCurrentScreenScaleType);
|
|
|
+ mPlayerBackgroundColor = a.getColor(R.styleable.VideoPlayer_playerBackgroundColor, Color.BLACK);
|
|
|
+ a.recycle();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化播放器视图
|
|
|
+ */
|
|
|
+ protected void initView() {
|
|
|
+ mPlayerContainer = new FrameLayout(getContext());
|
|
|
+ //设置背景颜色,目前设置为纯黑色
|
|
|
+ mPlayerContainer.setBackgroundColor(mPlayerBackgroundColor);
|
|
|
+ LayoutParams params = new LayoutParams(
|
|
|
+ ViewGroup.LayoutParams.MATCH_PARENT,
|
|
|
+ ViewGroup.LayoutParams.MATCH_PARENT);
|
|
|
+ //将布局添加到该视图中
|
|
|
+ this.addView(mPlayerContainer, params);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置控制器,传null表示移除控制器
|
|
|
+ * @param mediaController controller
|
|
|
+ */
|
|
|
+ public void setController(@Nullable BaseVideoController mediaController) {
|
|
|
+ mPlayerContainer.removeView(mVideoController);
|
|
|
+ mVideoController = mediaController;
|
|
|
+ if (mediaController != null) {
|
|
|
+ mediaController.setMediaPlayer(this);
|
|
|
+ LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
|
|
+ ViewGroup.LayoutParams.MATCH_PARENT);
|
|
|
+ mPlayerContainer.addView(mVideoController, params);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 开始播放,注意:调用此方法后必须调用{@link #release()}释放播放器,否则会导致内存泄漏
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void start() {
|
|
|
+ if (mVideoController==null){
|
|
|
+ //在调用start方法前,请先初始化视频控制器,调用setController方法
|
|
|
+ throw new VideoException(VideoException.CODE_NOT_SET_CONTROLLER,
|
|
|
+ "Controller must not be null , please setController first");
|
|
|
+ }
|
|
|
+ boolean isStarted = false;
|
|
|
+ if (isInIdleState() || isInStartAbortState()) {
|
|
|
+ isStarted = startPlay();
|
|
|
+ } else if (isInPlaybackState()) {
|
|
|
+ startInPlaybackState();
|
|
|
+ isStarted = true;
|
|
|
+ }
|
|
|
+ if (isStarted) {
|
|
|
+ mPlayerContainer.setKeepScreenOn(true);
|
|
|
+ if (mAudioFocusHelper != null){
|
|
|
+ mAudioFocusHelper.requestFocus();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 第一次播放
|
|
|
+ * @return 是否成功开始播放
|
|
|
+ */
|
|
|
+ protected boolean startPlay() {
|
|
|
+ //如果要显示移动网络提示则不继续播放
|
|
|
+ if (showNetWarning()) {
|
|
|
+ //中止播放
|
|
|
+ setPlayState(ConstantKeys.CurrentState.STATE_START_ABORT);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ //监听音频焦点改变
|
|
|
+ if (mEnableAudioFocus) {
|
|
|
+ mAudioFocusHelper = new AudioFocusHelper(this);
|
|
|
+ }
|
|
|
+ //读取播放进度
|
|
|
+ if (mProgressManager != null) {
|
|
|
+ mCurrentPosition = mProgressManager.getSavedProgress(mUrl);
|
|
|
+ }
|
|
|
+ initPlayer();
|
|
|
+ addDisplay();
|
|
|
+ startPrepare(false);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化播放器
|
|
|
+ */
|
|
|
+ protected void initPlayer() {
|
|
|
+ //通过工厂模式创建对象
|
|
|
+ mMediaPlayer = mPlayerFactory.createPlayer(mContext);
|
|
|
+ mMediaPlayer.setPlayerEventListener(this);
|
|
|
+ setInitOptions();
|
|
|
+ mMediaPlayer.initPlayer();
|
|
|
+ setOptions();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化视频渲染View
|
|
|
+ */
|
|
|
+ protected void addDisplay() {
|
|
|
+ if (mRenderView != null) {
|
|
|
+ mPlayerContainer.removeView(mRenderView.getView());
|
|
|
+ mRenderView.release();
|
|
|
+ }
|
|
|
+ //创建TextureView对象
|
|
|
+ mRenderView = mRenderViewFactory.createRenderView(mContext);
|
|
|
+ //绑定mMediaPlayer对象
|
|
|
+ mRenderView.attachToPlayer(mMediaPlayer);
|
|
|
+ LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
|
|
+ ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER);
|
|
|
+ mPlayerContainer.addView(mRenderView.getView(), 0, params);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 开始准备播放(直接播放)
|
|
|
+ */
|
|
|
+ protected void startPrepare(boolean reset) {
|
|
|
+ if (reset) {
|
|
|
+ mMediaPlayer.reset();
|
|
|
+ //重新设置option,media player reset之后,option会失效
|
|
|
+ setOptions();
|
|
|
+ }
|
|
|
+ if (prepareDataSource()) {
|
|
|
+ mMediaPlayer.prepareAsync();
|
|
|
+ setPlayState(ConstantKeys.CurrentState.STATE_PREPARING);
|
|
|
+ setPlayerState(isFullScreen() ? ConstantKeys.PlayMode.MODE_FULL_SCREEN :
|
|
|
+ isTinyScreen() ? ConstantKeys.PlayMode.MODE_TINY_WINDOW : ConstantKeys.PlayMode.MODE_NORMAL);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置播放数据
|
|
|
+ * @return 播放数据是否设置成功
|
|
|
+ */
|
|
|
+ protected boolean prepareDataSource() {
|
|
|
+ if (mAssetFileDescriptor != null) {
|
|
|
+ mMediaPlayer.setDataSource(mAssetFileDescriptor);
|
|
|
+ return true;
|
|
|
+ } else if (!TextUtils.isEmpty(mUrl)) {
|
|
|
+ mMediaPlayer.setDataSource(mUrl, mHeaders);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 向Controller设置播放状态,用于控制Controller的ui展示
|
|
|
+ * 这里使用注解限定符,不要使用1,2这种直观数字,不方便知道意思
|
|
|
+ * 播放状态,主要是指播放器的各种状态
|
|
|
+ * -1 播放错误
|
|
|
+ * 0 播放未开始
|
|
|
+ * 1 播放准备中
|
|
|
+ * 2 播放准备就绪
|
|
|
+ * 3 正在播放
|
|
|
+ * 4 暂停播放
|
|
|
+ * 5 正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,缓冲区数据足够后恢复播放)
|
|
|
+ * 6 暂停缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,此时暂停播放器,继续缓冲,缓冲区数据足够后恢复暂停
|
|
|
+ * 7 播放完成
|
|
|
+ * 8 开始播放中止
|
|
|
+ */
|
|
|
+ protected void setPlayState(@ConstantKeys.CurrentStateType int playState) {
|
|
|
+ mCurrentPlayState = playState;
|
|
|
+ if (mVideoController != null) {
|
|
|
+ mVideoController.setPlayState(playState);
|
|
|
+ }
|
|
|
+ if (mOnStateChangeListeners != null) {
|
|
|
+ for (OnVideoStateListener l : PlayerUtils.getSnapshot(mOnStateChangeListeners)) {
|
|
|
+ if (l != null) {
|
|
|
+ l.onPlayStateChanged(playState);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 向Controller设置播放器状态,包含全屏状态和非全屏状态
|
|
|
+ * 播放模式
|
|
|
+ * 普通模式,小窗口模式,正常模式三种其中一种
|
|
|
+ * MODE_NORMAL 普通模式
|
|
|
+ * MODE_FULL_SCREEN 全屏模式
|
|
|
+ * MODE_TINY_WINDOW 小屏模式
|
|
|
+ */
|
|
|
+ protected void setPlayerState(@ConstantKeys.PlayModeType int playerState) {
|
|
|
+ mCurrentPlayerState = playerState;
|
|
|
+ if (mVideoController != null) {
|
|
|
+ mVideoController.setPlayerState(playerState);
|
|
|
+ }
|
|
|
+ if (mOnStateChangeListeners != null) {
|
|
|
+ for (OnVideoStateListener l : PlayerUtils.getSnapshot(mOnStateChangeListeners)) {
|
|
|
+ if (l != null) {
|
|
|
+ l.onPlayerStateChanged(playerState);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * OnStateChangeListener的空实现。用的时候只需要重写需要的方法
|
|
|
+ */
|
|
|
+ public static class SimpleOnStateChangeListener implements OnVideoStateListener {
|
|
|
+ @Override
|
|
|
+ public void onPlayerStateChanged(@ConstantKeys.PlayModeType int playerState) {}
|
|
|
+ @Override
|
|
|
+ public void onPlayStateChanged(int playState) {}
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加一个播放状态监听器,播放状态发生变化时将会调用。
|
|
|
+ */
|
|
|
+ public void addOnStateChangeListener(@NonNull OnVideoStateListener listener) {
|
|
|
+ if (mOnStateChangeListeners == null) {
|
|
|
+ mOnStateChangeListeners = new ArrayList<>();
|
|
|
+ }
|
|
|
+ mOnStateChangeListeners.add(listener);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 移除某个播放状态监听
|
|
|
+ */
|
|
|
+ public void removeOnStateChangeListener(@NonNull OnVideoStateListener listener) {
|
|
|
+ if (mOnStateChangeListeners != null) {
|
|
|
+ mOnStateChangeListeners.remove(listener);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置一个播放状态监听器,播放状态发生变化时将会调用,
|
|
|
+ * 如果你想同时设置多个监听器,推荐 {@link #addOnStateChangeListener(OnVideoStateListener)}。
|
|
|
+ */
|
|
|
+ public void setOnStateChangeListener(@NonNull OnVideoStateListener listener) {
|
|
|
+ if (mOnStateChangeListeners == null) {
|
|
|
+ mOnStateChangeListeners = new ArrayList<>();
|
|
|
+ } else {
|
|
|
+ mOnStateChangeListeners.clear();
|
|
|
+ }
|
|
|
+ mOnStateChangeListeners.add(listener);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 移除所有播放状态监听
|
|
|
+ */
|
|
|
+ public void clearOnStateChangeListeners() {
|
|
|
+ if (mOnStateChangeListeners != null) {
|
|
|
+ mOnStateChangeListeners.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 改变返回键逻辑,用于activity
|
|
|
+ */
|
|
|
+ public boolean onBackPressed() {
|
|
|
+ return mVideoController != null && mVideoController.onBackPressed();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ ```
|
|
|
|
|
|
|
|
|
### 05.VideoController实现
|
|
|
+- 代码如下所示,代码太长,省略部分代码,具体看demo
|
|
|
+ ``` java
|
|
|
+ public abstract class BaseVideoController extends FrameLayout implements InterVideoController,
|
|
|
+ OrientationHelper.OnOrientationChangeListener {
|
|
|
+
|
|
|
+ //播放器包装类,集合了MediaPlayerControl的api和IVideoController的api
|
|
|
+ protected ControlWrapper mControlWrapper;
|
|
|
|
|
|
+ public BaseVideoController(@NonNull Context context) {
|
|
|
+ //创建
|
|
|
+ this(context, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ public BaseVideoController(@NonNull Context context, @Nullable AttributeSet attrs) {
|
|
|
+ //创建
|
|
|
+ this(context, attrs, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ public BaseVideoController(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
|
|
|
+ super(context, attrs, defStyleAttr);
|
|
|
+ initView(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void initView(Context context) {
|
|
|
+ if (getLayoutId() != 0) {
|
|
|
+ LayoutInflater.from(getContext()).inflate(getLayoutId(), this, true);
|
|
|
+ }
|
|
|
+ mOrientationHelper = new OrientationHelper(context.getApplicationContext());
|
|
|
+ mEnableOrientation = VideoViewManager.getConfig().mEnableOrientation;
|
|
|
+ mAdaptCutout = VideoViewManager.getConfig().mAdaptCutout;
|
|
|
+ mShowAnim = new AlphaAnimation(0f, 1f);
|
|
|
+ mShowAnim.setDuration(300);
|
|
|
+ mHideAnim = new AlphaAnimation(1f, 0f);
|
|
|
+ mHideAnim.setDuration(300);
|
|
|
+ mActivity = PlayerUtils.scanForActivity(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置控制器布局文件,子类必须实现
|
|
|
+ */
|
|
|
+ protected abstract int getLayoutId();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重要:此方法用于将{@link VideoPlayer} 和控制器绑定
|
|
|
+ */
|
|
|
+ @CallSuper
|
|
|
+ public void setMediaPlayer(InterVideoPlayer mediaPlayer) {
|
|
|
+ mControlWrapper = new ControlWrapper(mediaPlayer, this);
|
|
|
+ //绑定ControlComponent和Controller
|
|
|
+ for (Map.Entry<InterControlView, Boolean> next : mControlComponents.entrySet()) {
|
|
|
+ InterControlView component = next.getKey();
|
|
|
+ component.attach(mControlWrapper);
|
|
|
+ }
|
|
|
+ //开始监听设备方向
|
|
|
+ mOrientationHelper.setOnOrientationChangeListener(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加控制组件,最后面添加的在最下面,合理组织添加顺序,可让ControlComponent位于不同的层级
|
|
|
+ */
|
|
|
+ public void addControlComponent(InterControlView... component) {
|
|
|
+ for (InterControlView item : component) {
|
|
|
+ addControlComponent(item, false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加控制组件,最后面添加的在最下面,合理组织添加顺序,可让ControlComponent位于不同的层级
|
|
|
+ *
|
|
|
+ * @param isPrivate 是否为独有的组件,如果是就不添加到控制器中
|
|
|
+ */
|
|
|
+ public void addControlComponent(InterControlView component, boolean isPrivate) {
|
|
|
+ mControlComponents.put(component, isPrivate);
|
|
|
+ if (mControlWrapper != null) {
|
|
|
+ component.attach(mControlWrapper);
|
|
|
+ }
|
|
|
+ View view = component.getView();
|
|
|
+ if (view != null && !isPrivate) {
|
|
|
+ addView(view, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 移除控制组件
|
|
|
+ */
|
|
|
+ public void removeControlComponent(InterControlView component) {
|
|
|
+ removeView(component.getView());
|
|
|
+ mControlComponents.remove(component);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 移除所有的组件
|
|
|
+ */
|
|
|
+ public void removeAllControlComponent() {
|
|
|
+ for (Map.Entry<InterControlView, Boolean> next : mControlComponents.entrySet()) {
|
|
|
+ removeView(next.getKey().getView());
|
|
|
+ }
|
|
|
+ mControlComponents.clear();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void removeAllPrivateComponents() {
|
|
|
+ Iterator<Map.Entry<InterControlView, Boolean>> it = mControlComponents.entrySet().iterator();
|
|
|
+ while (it.hasNext()) {
|
|
|
+ Map.Entry<InterControlView, Boolean> next = it.next();
|
|
|
+ if (next.getValue()) {
|
|
|
+ it.remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@link VideoPlayer}调用此方法向控制器设置播放状态。
|
|
|
+ * 这里使用注解限定符,不要使用1,2这种直观数字,不方便知道意思
|
|
|
+ * 播放状态,主要是指播放器的各种状态
|
|
|
+ * -1 播放错误
|
|
|
+ * 0 播放未开始
|
|
|
+ * 1 播放准备中
|
|
|
+ * 2 播放准备就绪
|
|
|
+ * 3 正在播放
|
|
|
+ * 4 暂停播放
|
|
|
+ * 5 正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,缓冲区数据足够后恢复播放)
|
|
|
+ * 6 暂停缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,此时暂停播放器,继续缓冲,缓冲区数据足够后恢复暂停
|
|
|
+ * 7 播放完成
|
|
|
+ * 8 开始播放中止
|
|
|
+ */
|
|
|
+ @CallSuper
|
|
|
+ public void setPlayState(@ConstantKeys.CurrentStateType int playState) {
|
|
|
+ //设置播放器的状态
|
|
|
+ handlePlayStateChanged(playState);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * {@link VideoPlayer}调用此方法向控制器设置播放器状态
|
|
|
+ * 播放模式
|
|
|
+ * 普通模式,小窗口模式,正常模式三种其中一种
|
|
|
+ * MODE_NORMAL 普通模式
|
|
|
+ * MODE_FULL_SCREEN 全屏模式
|
|
|
+ * MODE_TINY_WINDOW 小屏模式
|
|
|
+ */
|
|
|
+ @CallSuper
|
|
|
+ public void setPlayerState(@ConstantKeys.PlayModeType final int playerState) {
|
|
|
+ //调用此方法向控制器设置播放器状态
|
|
|
+ handlePlayerStateChanged(playerState);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleVisibilityChanged(boolean isVisible, Animation anim) {
|
|
|
+ if (!mIsLocked) {
|
|
|
+ //没锁住时才向ControlComponent下发此事件
|
|
|
+ for (Map.Entry<InterControlView, Boolean> next : mControlComponents.entrySet()) {
|
|
|
+ InterControlView component = next.getKey();
|
|
|
+ component.onVisibilityChanged(isVisible, anim);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ onVisibilityChanged(isVisible, anim);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handlePlayStateChanged(int playState) {
|
|
|
+ for (Map.Entry<InterControlView, Boolean> next : mControlComponents.entrySet()) {
|
|
|
+ InterControlView component = next.getKey();
|
|
|
+ component.onPlayStateChanged(playState);
|
|
|
+ }
|
|
|
+ onPlayStateChanged(playState);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 子类重写此方法并在其中更新控制器在不同播放状态下的ui
|
|
|
+ */
|
|
|
+ @CallSuper
|
|
|
+ protected void onPlayStateChanged(int playState) {
|
|
|
+ switch (playState) {
|
|
|
+ case ConstantKeys.CurrentState.STATE_IDLE:
|
|
|
+ mOrientationHelper.disable();
|
|
|
+ mOrientation = 0;
|
|
|
+ mIsLocked = false;
|
|
|
+ mShowing = false;
|
|
|
+ removeAllPrivateComponents();
|
|
|
+ break;
|
|
|
+ case ConstantKeys.CurrentState.STATE_BUFFERING_PLAYING:
|
|
|
+ mIsLocked = false;
|
|
|
+ mShowing = false;
|
|
|
+ break;
|
|
|
+ case ConstantKeys.CurrentState.STATE_ERROR:
|
|
|
+ mShowing = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 播放器状态改变
|
|
|
+ * @param playerState 播放器状态
|
|
|
+ */
|
|
|
+ private void handlePlayerStateChanged(int playerState) {
|
|
|
+ for (Map.Entry<InterControlView, Boolean> next : mControlComponents.entrySet()) {
|
|
|
+ InterControlView component = next.getKey();
|
|
|
+ component.onPlayerStateChanged(playerState);
|
|
|
+ }
|
|
|
+ onPlayerStateChanged(playerState);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 子类重写此方法并在其中更新控制器在不同播放器状态下的ui
|
|
|
+ * 普通模式,小窗口模式,正常模式三种其中一种
|
|
|
+ * MODE_NORMAL 普通模式
|
|
|
+ * MODE_FULL_SCREEN 全屏模式
|
|
|
+ * MODE_TINY_WINDOW 小屏模式
|
|
|
+ */
|
|
|
+ @CallSuper
|
|
|
+ protected void onPlayerStateChanged(@ConstantKeys.PlayMode int playerState) {
|
|
|
+ switch (playerState) {
|
|
|
+ case ConstantKeys.PlayMode.MODE_NORMAL:
|
|
|
+ //视频正常播放是设置监听
|
|
|
+ if (mEnableOrientation) {
|
|
|
+ //检查系统是否开启自动旋转
|
|
|
+ mOrientationHelper.enable();
|
|
|
+ } else {
|
|
|
+ //取消监听
|
|
|
+ mOrientationHelper.disable();
|
|
|
+ }
|
|
|
+ if (hasCutout()) {
|
|
|
+ StatesCutoutUtils.adaptCutoutAboveAndroidP(getContext(), false);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case ConstantKeys.PlayMode.MODE_FULL_SCREEN:
|
|
|
+ //在全屏时强制监听设备方向
|
|
|
+ mOrientationHelper.enable();
|
|
|
+ if (hasCutout()) {
|
|
|
+ StatesCutoutUtils.adaptCutoutAboveAndroidP(getContext(), true);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case ConstantKeys.PlayMode.MODE_TINY_WINDOW:
|
|
|
+ //小窗口取消重力感应监听
|
|
|
+ mOrientationHelper.disable();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ```
|
|
|
|
|
|
|
|
|
### 06.播放Player和UI通信
|
|
|
+- 比如,在自定义view视图中,我想调用VideoPlayer的api又能调用BaseVideoController的api,该如何实现呢?
|
|
|
+ - 当创建了下面的对象,就可以同时拿到player和controller中的api方法呢,这里面省略一部分代码,具体看demo案例
|
|
|
+ ``` java
|
|
|
+ public class ControlWrapper implements InterVideoPlayer, InterVideoController {
|
|
|
+
|
|
|
+ private InterVideoPlayer mVideoPlayer;
|
|
|
+ private InterVideoController mController;
|
|
|
+
|
|
|
+ public ControlWrapper(@NonNull InterVideoPlayer videoPlayer, @NonNull InterVideoController controller) {
|
|
|
+ mVideoPlayer = videoPlayer;
|
|
|
+ mController = controller;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void start() {
|
|
|
+ mVideoPlayer.start();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean isShowing() {
|
|
|
+ return mController.isShowing();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void setLocked(boolean locked) {
|
|
|
+ mController.setLocked(locked);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ ```
|
|
|
|
|
|
|
|
|
|