123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650 |
- package org.yczbj.ycvideoplayerlib.controller;
- import android.app.Activity;
- import android.content.Context;
- import android.content.pm.ActivityInfo;
- import android.util.AttributeSet;
- import android.view.LayoutInflater;
- import android.view.OrientationEventListener;
- import android.view.View;
- import android.view.animation.AlphaAnimation;
- import android.view.animation.Animation;
- import android.widget.FrameLayout;
- import androidx.annotation.AttrRes;
- import androidx.annotation.CallSuper;
- import androidx.annotation.NonNull;
- import androidx.annotation.Nullable;
- import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
- import org.yczbj.ycvideoplayerlib.player.manager.VideoViewManager;
- import org.yczbj.ycvideoplayerlib.player.video.VideoView;
- import org.yczbj.ycvideoplayerlib.tool.utils.StatesCutoutUtils;
- import org.yczbj.ycvideoplayerlib.tool.utils.NetworkUtils;
- import org.yczbj.ycvideoplayerlib.tool.utils.PlayerUtils;
- import org.yczbj.ycvideoplayerlib.tool.utils.VideoLogUtils;
- import java.util.Iterator;
- import java.util.LinkedHashMap;
- import java.util.Map;
- /**
- * 控制器基类
- * 此类集成各种事件的处理逻辑,包括
- * 1.播放器状态改变: {@link #handlePlayerStateChanged(int)}
- * 2.播放状态改变: {@link #handlePlayStateChanged(int)}
- * 3.控制视图的显示和隐藏: {@link #handleVisibilityChanged(boolean, Animation)}
- * 4.播放进度改变: {@link #handleSetProgress(int, int)}
- * 5.锁定状态改变: {@link #handleLockStateChanged(boolean)}
- * 6.设备方向监听: {@link #onOrientationChanged(int)}
- */
- public abstract class BaseVideoController extends FrameLayout implements IVideoController,
- OrientationHelper.OnOrientationChangeListener {
- //播放器包装类,集合了MediaPlayerControl的api和IVideoController的api
- protected ControlWrapper mControlWrapper;
- @Nullable
- protected Activity mActivity;
- //控制器是否处于显示状态
- protected boolean mShowing;
- //是否处于锁定状态
- protected boolean mIsLocked;
- //播放视图隐藏超时
- protected int mDefaultTimeout = 4000;
- //是否开启根据屏幕方向进入/退出全屏
- private boolean mEnableOrientation;
- //屏幕方向监听辅助类
- protected OrientationHelper mOrientationHelper;
- //用户设置是否适配刘海屏
- private boolean mAdaptCutout;
- //是否有刘海
- private Boolean mHasCutout;
- //刘海的高度
- private int mCutoutHeight;
- //是否开始刷新进度
- private boolean mIsStartProgress;
- //保存了所有的控制组件
- protected LinkedHashMap<IControlComponent, Boolean> mControlComponents = new LinkedHashMap<>();
- private Animation mShowAnim;
- private Animation mHideAnim;
- 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 VideoView} 和控制器绑定
- */
- @CallSuper
- public void setMediaPlayer(MediaPlayerControl mediaPlayer) {
- mControlWrapper = new ControlWrapper(mediaPlayer, this);
- //绑定ControlComponent和Controller
- for (Map.Entry<IControlComponent, Boolean> next : mControlComponents.entrySet()) {
- IControlComponent component = next.getKey();
- component.attach(mControlWrapper);
- }
- //开始监听设备方向
- mOrientationHelper.setOnOrientationChangeListener(this);
- }
- /**
- * 添加控制组件,最后面添加的在最下面,合理组织添加顺序,可让ControlComponent位于不同的层级
- */
- public void addControlComponent(IControlComponent... component) {
- for (IControlComponent item : component) {
- addControlComponent(item, false);
- }
- }
- /**
- * 添加控制组件,最后面添加的在最下面,合理组织添加顺序,可让ControlComponent位于不同的层级
- *
- * @param isPrivate 是否为独有的组件,如果是就不添加到控制器中
- */
- public void addControlComponent(IControlComponent 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(IControlComponent component) {
- removeView(component.getView());
- mControlComponents.remove(component);
- }
- public void removeAllControlComponent() {
- for (Map.Entry<IControlComponent, Boolean> next : mControlComponents.entrySet()) {
- removeView(next.getKey().getView());
- }
- mControlComponents.clear();
- }
- public void removeAllPrivateComponents() {
- Iterator<Map.Entry<IControlComponent, Boolean>> it = mControlComponents.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry<IControlComponent, Boolean> next = it.next();
- if (next.getValue()) {
- it.remove();
- }
- }
- }
- /**
- * {@link VideoView}调用此方法向控制器设置播放状态
- */
- @CallSuper
- public void setPlayState(int playState) {
- handlePlayStateChanged(playState);
- }
- /**
- * {@link VideoView}调用此方法向控制器设置播放器状态
- */
- @CallSuper
- public void setPlayerState(final int playerState) {
- handlePlayerStateChanged(playerState);
- }
- /**
- * 设置播放视图自动隐藏超时
- */
- public void setDismissTimeout(int timeout) {
- if (timeout > 0) {
- mDefaultTimeout = timeout;
- }
- }
- /**
- * 隐藏播放视图
- */
- @Override
- public void hide() {
- if (mShowing) {
- stopFadeOut();
- handleVisibilityChanged(false, mHideAnim);
- mShowing = false;
- }
- }
- /**
- * 显示播放视图
- */
- @Override
- public void show() {
- if (!mShowing) {
- handleVisibilityChanged(true, mShowAnim);
- startFadeOut();
- mShowing = true;
- }
- }
- @Override
- public boolean isShowing() {
- return mShowing;
- }
- /**
- * 开始计时
- */
- @Override
- public void startFadeOut() {
- //重新开始计时
- stopFadeOut();
- postDelayed(mFadeOut, mDefaultTimeout);
- }
- /**
- * 取消计时
- */
- @Override
- public void stopFadeOut() {
- removeCallbacks(mFadeOut);
- }
- /**
- * 隐藏播放视图Runnable
- */
- protected final Runnable mFadeOut = new Runnable() {
- @Override
- public void run() {
- hide();
- }
- };
- @Override
- public void setLocked(boolean locked) {
- mIsLocked = locked;
- handleLockStateChanged(locked);
- }
- @Override
- public boolean isLocked() {
- return mIsLocked;
- }
- /**
- * 开始刷新进度,注意:需在STATE_PLAYING时调用才会开始刷新进度
- */
- @Override
- public void startProgress() {
- if (mIsStartProgress) return;
- post(mShowProgress);
- mIsStartProgress = true;
- }
- /**
- * 停止刷新进度
- */
- @Override
- public void stopProgress() {
- if (!mIsStartProgress) return;
- removeCallbacks(mShowProgress);
- mIsStartProgress = false;
- }
- /**
- * 刷新进度Runnable
- */
- protected Runnable mShowProgress = new Runnable() {
- @Override
- public void run() {
- int pos = setProgress();
- if (mControlWrapper.isPlaying()) {
- postDelayed(this, (long) ((1000 - pos % 1000) / mControlWrapper.getSpeed()));
- } else {
- mIsStartProgress = false;
- }
- }
- };
- private int setProgress() {
- int position = (int) mControlWrapper.getCurrentPosition();
- int duration = (int) mControlWrapper.getDuration();
- handleSetProgress(duration, position);
- return position;
- }
- /**
- * 设置是否适配刘海屏
- */
- public void setAdaptCutout(boolean adaptCutout) {
- mAdaptCutout = adaptCutout;
- }
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- checkCutout();
- }
- /**
- * 检查是否需要适配刘海
- */
- private void checkCutout() {
- if (!mAdaptCutout) return;
- if (mActivity != null && mHasCutout == null) {
- mHasCutout = StatesCutoutUtils.allowDisplayToCutout(mActivity);
- if (mHasCutout) {
- //竖屏下的状态栏高度可认为是刘海的高度
- mCutoutHeight = (int) PlayerUtils.getStatusBarHeightPortrait(mActivity);
- }
- }
- VideoLogUtils.d("hasCutout: " + mHasCutout + " cutout height: " + mCutoutHeight);
- }
- /**
- * 是否有刘海屏
- */
- @Override
- public boolean hasCutout() {
- return mHasCutout != null && mHasCutout;
- }
- /**
- * 刘海的高度
- */
- @Override
- public int getCutoutHeight() {
- return mCutoutHeight;
- }
- /**
- * 显示移动网络播放提示
- *
- * @return 返回显示移动网络播放提示的条件,false:不显示, true显示
- * 此处默认根据手机网络类型来决定是否显示,开发者可以重写相关逻辑
- */
- public boolean showNetWarning() {
- return NetworkUtils.getNetworkType(getContext()) == NetworkUtils.NETWORK_MOBILE
- && !VideoViewManager.instance().playOnMobileNetwork();
- }
- /**
- * 播放和暂停
- */
- protected void togglePlay() {
- mControlWrapper.togglePlay();
- }
- /**
- * 横竖屏切换
- */
- protected void toggleFullScreen() {
- mControlWrapper.toggleFullScreen(mActivity);
- }
- /**
- * 子类中请使用此方法来进入全屏
- *
- * @return 是否成功进入全屏
- */
- protected boolean startFullScreen() {
- if (mActivity == null || mActivity.isFinishing()) return false;
- mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
- mControlWrapper.startFullScreen();
- return true;
- }
- /**
- * 子类中请使用此方法来退出全屏
- *
- * @return 是否成功退出全屏
- */
- protected boolean stopFullScreen() {
- if (mActivity == null || mActivity.isFinishing()) return false;
- mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- mControlWrapper.stopFullScreen();
- return true;
- }
- /**
- * 改变返回键逻辑,用于activity
- */
- public boolean onBackPressed() {
- return false;
- }
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
- if (mControlWrapper.isPlaying()
- && (mEnableOrientation || mControlWrapper.isFullScreen())) {
- if (hasWindowFocus) {
- postDelayed(new Runnable() {
- @Override
- public void run() {
- mOrientationHelper.enable();
- }
- }, 800);
- } else {
- mOrientationHelper.disable();
- }
- }
- }
- /**
- * 是否自动旋转, 默认不自动旋转
- */
- public void setEnableOrientation(boolean enableOrientation) {
- mEnableOrientation = enableOrientation;
- }
- private int mOrientation = 0;
- @CallSuper
- @Override
- public void onOrientationChanged(int orientation) {
- if (mActivity == null || mActivity.isFinishing()) return;
- //记录用户手机上一次放置的位置
- int lastOrientation = mOrientation;
- if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
- //手机平放时,检测不到有效的角度
- //重置为原始位置 -1
- mOrientation = -1;
- return;
- }
- if (orientation > 350 || orientation < 10) {
- int o = mActivity.getRequestedOrientation();
- //手动切换横竖屏
- if (o == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE && lastOrientation == 0) return;
- if (mOrientation == 0) return;
- //0度,用户竖直拿着手机
- mOrientation = 0;
- onOrientationPortrait(mActivity);
- } else if (orientation > 80 && orientation < 100) {
- int o = mActivity.getRequestedOrientation();
- //手动切换横竖屏
- if (o == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT && lastOrientation == 90) return;
- if (mOrientation == 90) return;
- //90度,用户右侧横屏拿着手机
- mOrientation = 90;
- onOrientationReverseLandscape(mActivity);
- } else if (orientation > 260 && orientation < 280) {
- int o = mActivity.getRequestedOrientation();
- //手动切换横竖屏
- if (o == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT && lastOrientation == 270) return;
- if (mOrientation == 270) return;
- //270度,用户左侧横屏拿着手机
- mOrientation = 270;
- onOrientationLandscape(mActivity);
- }
- }
- /**
- * 竖屏
- */
- protected void onOrientationPortrait(Activity activity) {
- //屏幕锁定的情况
- if (mIsLocked) return;
- //没有开启设备方向监听的情况
- if (!mEnableOrientation) return;
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- mControlWrapper.stopFullScreen();
- }
- /**
- * 横屏
- */
- protected void onOrientationLandscape(Activity activity) {
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
- if (mControlWrapper.isFullScreen()) {
- handlePlayerStateChanged(ConstantKeys.PlayMode.MODE_FULL_SCREEN);
- } else {
- mControlWrapper.startFullScreen();
- }
- }
- /**
- * 反向横屏
- */
- protected void onOrientationReverseLandscape(Activity activity) {
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
- if (mControlWrapper.isFullScreen()) {
- handlePlayerStateChanged(ConstantKeys.PlayMode.MODE_FULL_SCREEN);
- } else {
- mControlWrapper.startFullScreen();
- }
- }
- //------------------------ start handle event change ------------------------//
- private void handleVisibilityChanged(boolean isVisible, Animation anim) {
- if (!mIsLocked) { //没锁住时才向ControlComponent下发此事件
- for (Map.Entry<IControlComponent, Boolean> next
- : mControlComponents.entrySet()) {
- IControlComponent component = next.getKey();
- component.onVisibilityChanged(isVisible, anim);
- }
- }
- onVisibilityChanged(isVisible, anim);
- }
- /**
- * 子类重写此方法监听控制的显示和隐藏
- *
- * @param isVisible 是否可见
- * @param anim 显示/隐藏动画
- */
- protected void onVisibilityChanged(boolean isVisible, Animation anim) {
- }
- private void handlePlayStateChanged(int playState) {
- for (Map.Entry<IControlComponent, Boolean> next
- : mControlComponents.entrySet()) {
- IControlComponent component = next.getKey();
- component.onPlayStateChanged(playState);
- }
- onPlayStateChanged(playState);
- }
- /**
- * 子类重写此方法并在其中更新控制器在不同播放状态下的ui
- */
- @CallSuper
- protected void onPlayStateChanged(int playState) {
- switch (playState) {
- case VideoView.STATE_IDLE:
- mOrientationHelper.disable();
- mOrientation = 0;
- mIsLocked = false;
- mShowing = false;
- removeAllPrivateComponents();
- break;
- case VideoView.STATE_PLAYBACK_COMPLETED:
- mIsLocked = false;
- mShowing = false;
- break;
- case VideoView.STATE_ERROR:
- mShowing = false;
- break;
- }
- }
- private void handlePlayerStateChanged(int playerState) {
- for (Map.Entry<IControlComponent, Boolean> next : mControlComponents.entrySet()) {
- IControlComponent component = next.getKey();
- component.onPlayerStateChanged(playerState);
- }
- onPlayerStateChanged(playerState);
- }
- /**
- * 子类重写此方法并在其中更新控制器在不同播放器状态下的ui
- */
- @CallSuper
- protected void onPlayerStateChanged(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;
- }
- }
- private void handleSetProgress(int duration, int position) {
- for (Map.Entry<IControlComponent, Boolean> next
- : mControlComponents.entrySet()) {
- IControlComponent component = next.getKey();
- component.setProgress(duration, position);
- }
- setProgress(duration, position);
- }
- /**
- * 刷新进度回调,子类可在此方法监听进度刷新,然后更新ui
- *
- * @param duration 视频总时长
- * @param position 视频当前时长
- */
- protected void setProgress(int duration, int position) {
- }
- private void handleLockStateChanged(boolean isLocked) {
- for (Map.Entry<IControlComponent, Boolean> next
- : mControlComponents.entrySet()) {
- IControlComponent component = next.getKey();
- component.onLockStateChanged(isLocked);
- }
- onLockStateChanged(isLocked);
- }
- /**
- * 子类可重写此方法监听锁定状态发生改变,然后更新ui
- */
- protected void onLockStateChanged(boolean isLocked) {
- }
- //------------------------ end handle event change ------------------------//
- }
|