BaseVideoController.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. package org.yczbj.ycvideoplayerlib.controller;
  2. import android.app.Activity;
  3. import android.content.Context;
  4. import android.content.pm.ActivityInfo;
  5. import android.util.AttributeSet;
  6. import android.view.LayoutInflater;
  7. import android.view.OrientationEventListener;
  8. import android.view.View;
  9. import android.view.animation.AlphaAnimation;
  10. import android.view.animation.Animation;
  11. import android.widget.FrameLayout;
  12. import androidx.annotation.AttrRes;
  13. import androidx.annotation.CallSuper;
  14. import androidx.annotation.NonNull;
  15. import androidx.annotation.Nullable;
  16. import org.yczbj.ycvideoplayerlib.config.ConstantKeys;
  17. import org.yczbj.ycvideoplayerlib.player.manager.VideoViewManager;
  18. import org.yczbj.ycvideoplayerlib.player.video.VideoView;
  19. import org.yczbj.ycvideoplayerlib.tool.utils.StatesCutoutUtils;
  20. import org.yczbj.ycvideoplayerlib.tool.utils.NetworkUtils;
  21. import org.yczbj.ycvideoplayerlib.tool.utils.PlayerUtils;
  22. import org.yczbj.ycvideoplayerlib.tool.utils.VideoLogUtils;
  23. import java.util.Iterator;
  24. import java.util.LinkedHashMap;
  25. import java.util.Map;
  26. /**
  27. * 控制器基类
  28. * 此类集成各种事件的处理逻辑,包括
  29. * 1.播放器状态改变: {@link #handlePlayerStateChanged(int)}
  30. * 2.播放状态改变: {@link #handlePlayStateChanged(int)}
  31. * 3.控制视图的显示和隐藏: {@link #handleVisibilityChanged(boolean, Animation)}
  32. * 4.播放进度改变: {@link #handleSetProgress(int, int)}
  33. * 5.锁定状态改变: {@link #handleLockStateChanged(boolean)}
  34. * 6.设备方向监听: {@link #onOrientationChanged(int)}
  35. */
  36. public abstract class BaseVideoController extends FrameLayout implements IVideoController,
  37. OrientationHelper.OnOrientationChangeListener {
  38. //播放器包装类,集合了MediaPlayerControl的api和IVideoController的api
  39. protected ControlWrapper mControlWrapper;
  40. @Nullable
  41. protected Activity mActivity;
  42. //控制器是否处于显示状态
  43. protected boolean mShowing;
  44. //是否处于锁定状态
  45. protected boolean mIsLocked;
  46. //播放视图隐藏超时
  47. protected int mDefaultTimeout = 4000;
  48. //是否开启根据屏幕方向进入/退出全屏
  49. private boolean mEnableOrientation;
  50. //屏幕方向监听辅助类
  51. protected OrientationHelper mOrientationHelper;
  52. //用户设置是否适配刘海屏
  53. private boolean mAdaptCutout;
  54. //是否有刘海
  55. private Boolean mHasCutout;
  56. //刘海的高度
  57. private int mCutoutHeight;
  58. //是否开始刷新进度
  59. private boolean mIsStartProgress;
  60. //保存了所有的控制组件
  61. protected LinkedHashMap<IControlComponent, Boolean> mControlComponents = new LinkedHashMap<>();
  62. private Animation mShowAnim;
  63. private Animation mHideAnim;
  64. public BaseVideoController(@NonNull Context context) {
  65. this(context, null);
  66. }
  67. public BaseVideoController(@NonNull Context context, @Nullable AttributeSet attrs) {
  68. this(context, attrs, 0);
  69. }
  70. public BaseVideoController(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
  71. super(context, attrs, defStyleAttr);
  72. initView(context);
  73. }
  74. protected void initView(Context context) {
  75. if (getLayoutId() != 0) {
  76. LayoutInflater.from(getContext()).inflate(getLayoutId(), this, true);
  77. }
  78. mOrientationHelper = new OrientationHelper(context.getApplicationContext());
  79. mEnableOrientation = VideoViewManager.getConfig().mEnableOrientation;
  80. mAdaptCutout = VideoViewManager.getConfig().mAdaptCutout;
  81. mShowAnim = new AlphaAnimation(0f, 1f);
  82. mShowAnim.setDuration(300);
  83. mHideAnim = new AlphaAnimation(1f, 0f);
  84. mHideAnim.setDuration(300);
  85. mActivity = PlayerUtils.scanForActivity(context);
  86. }
  87. /**
  88. * 设置控制器布局文件,子类必须实现
  89. */
  90. protected abstract int getLayoutId();
  91. /**
  92. * 重要:此方法用于将{@link VideoView} 和控制器绑定
  93. */
  94. @CallSuper
  95. public void setMediaPlayer(MediaPlayerControl mediaPlayer) {
  96. mControlWrapper = new ControlWrapper(mediaPlayer, this);
  97. //绑定ControlComponent和Controller
  98. for (Map.Entry<IControlComponent, Boolean> next : mControlComponents.entrySet()) {
  99. IControlComponent component = next.getKey();
  100. component.attach(mControlWrapper);
  101. }
  102. //开始监听设备方向
  103. mOrientationHelper.setOnOrientationChangeListener(this);
  104. }
  105. /**
  106. * 添加控制组件,最后面添加的在最下面,合理组织添加顺序,可让ControlComponent位于不同的层级
  107. */
  108. public void addControlComponent(IControlComponent... component) {
  109. for (IControlComponent item : component) {
  110. addControlComponent(item, false);
  111. }
  112. }
  113. /**
  114. * 添加控制组件,最后面添加的在最下面,合理组织添加顺序,可让ControlComponent位于不同的层级
  115. *
  116. * @param isPrivate 是否为独有的组件,如果是就不添加到控制器中
  117. */
  118. public void addControlComponent(IControlComponent component, boolean isPrivate) {
  119. mControlComponents.put(component, isPrivate);
  120. if (mControlWrapper != null) {
  121. component.attach(mControlWrapper);
  122. }
  123. View view = component.getView();
  124. if (view != null && !isPrivate) {
  125. addView(view, 0);
  126. }
  127. }
  128. /**
  129. * 移除控制组件
  130. */
  131. public void removeControlComponent(IControlComponent component) {
  132. removeView(component.getView());
  133. mControlComponents.remove(component);
  134. }
  135. public void removeAllControlComponent() {
  136. for (Map.Entry<IControlComponent, Boolean> next : mControlComponents.entrySet()) {
  137. removeView(next.getKey().getView());
  138. }
  139. mControlComponents.clear();
  140. }
  141. public void removeAllPrivateComponents() {
  142. Iterator<Map.Entry<IControlComponent, Boolean>> it = mControlComponents.entrySet().iterator();
  143. while (it.hasNext()) {
  144. Map.Entry<IControlComponent, Boolean> next = it.next();
  145. if (next.getValue()) {
  146. it.remove();
  147. }
  148. }
  149. }
  150. /**
  151. * {@link VideoView}调用此方法向控制器设置播放状态
  152. */
  153. @CallSuper
  154. public void setPlayState(int playState) {
  155. handlePlayStateChanged(playState);
  156. }
  157. /**
  158. * {@link VideoView}调用此方法向控制器设置播放器状态
  159. */
  160. @CallSuper
  161. public void setPlayerState(final int playerState) {
  162. handlePlayerStateChanged(playerState);
  163. }
  164. /**
  165. * 设置播放视图自动隐藏超时
  166. */
  167. public void setDismissTimeout(int timeout) {
  168. if (timeout > 0) {
  169. mDefaultTimeout = timeout;
  170. }
  171. }
  172. /**
  173. * 隐藏播放视图
  174. */
  175. @Override
  176. public void hide() {
  177. if (mShowing) {
  178. stopFadeOut();
  179. handleVisibilityChanged(false, mHideAnim);
  180. mShowing = false;
  181. }
  182. }
  183. /**
  184. * 显示播放视图
  185. */
  186. @Override
  187. public void show() {
  188. if (!mShowing) {
  189. handleVisibilityChanged(true, mShowAnim);
  190. startFadeOut();
  191. mShowing = true;
  192. }
  193. }
  194. @Override
  195. public boolean isShowing() {
  196. return mShowing;
  197. }
  198. /**
  199. * 开始计时
  200. */
  201. @Override
  202. public void startFadeOut() {
  203. //重新开始计时
  204. stopFadeOut();
  205. postDelayed(mFadeOut, mDefaultTimeout);
  206. }
  207. /**
  208. * 取消计时
  209. */
  210. @Override
  211. public void stopFadeOut() {
  212. removeCallbacks(mFadeOut);
  213. }
  214. /**
  215. * 隐藏播放视图Runnable
  216. */
  217. protected final Runnable mFadeOut = new Runnable() {
  218. @Override
  219. public void run() {
  220. hide();
  221. }
  222. };
  223. @Override
  224. public void setLocked(boolean locked) {
  225. mIsLocked = locked;
  226. handleLockStateChanged(locked);
  227. }
  228. @Override
  229. public boolean isLocked() {
  230. return mIsLocked;
  231. }
  232. /**
  233. * 开始刷新进度,注意:需在STATE_PLAYING时调用才会开始刷新进度
  234. */
  235. @Override
  236. public void startProgress() {
  237. if (mIsStartProgress) return;
  238. post(mShowProgress);
  239. mIsStartProgress = true;
  240. }
  241. /**
  242. * 停止刷新进度
  243. */
  244. @Override
  245. public void stopProgress() {
  246. if (!mIsStartProgress) return;
  247. removeCallbacks(mShowProgress);
  248. mIsStartProgress = false;
  249. }
  250. /**
  251. * 刷新进度Runnable
  252. */
  253. protected Runnable mShowProgress = new Runnable() {
  254. @Override
  255. public void run() {
  256. int pos = setProgress();
  257. if (mControlWrapper.isPlaying()) {
  258. postDelayed(this, (long) ((1000 - pos % 1000) / mControlWrapper.getSpeed()));
  259. } else {
  260. mIsStartProgress = false;
  261. }
  262. }
  263. };
  264. private int setProgress() {
  265. int position = (int) mControlWrapper.getCurrentPosition();
  266. int duration = (int) mControlWrapper.getDuration();
  267. handleSetProgress(duration, position);
  268. return position;
  269. }
  270. /**
  271. * 设置是否适配刘海屏
  272. */
  273. public void setAdaptCutout(boolean adaptCutout) {
  274. mAdaptCutout = adaptCutout;
  275. }
  276. @Override
  277. protected void onAttachedToWindow() {
  278. super.onAttachedToWindow();
  279. checkCutout();
  280. }
  281. /**
  282. * 检查是否需要适配刘海
  283. */
  284. private void checkCutout() {
  285. if (!mAdaptCutout) return;
  286. if (mActivity != null && mHasCutout == null) {
  287. mHasCutout = StatesCutoutUtils.allowDisplayToCutout(mActivity);
  288. if (mHasCutout) {
  289. //竖屏下的状态栏高度可认为是刘海的高度
  290. mCutoutHeight = (int) PlayerUtils.getStatusBarHeightPortrait(mActivity);
  291. }
  292. }
  293. VideoLogUtils.d("hasCutout: " + mHasCutout + " cutout height: " + mCutoutHeight);
  294. }
  295. /**
  296. * 是否有刘海屏
  297. */
  298. @Override
  299. public boolean hasCutout() {
  300. return mHasCutout != null && mHasCutout;
  301. }
  302. /**
  303. * 刘海的高度
  304. */
  305. @Override
  306. public int getCutoutHeight() {
  307. return mCutoutHeight;
  308. }
  309. /**
  310. * 显示移动网络播放提示
  311. *
  312. * @return 返回显示移动网络播放提示的条件,false:不显示, true显示
  313. * 此处默认根据手机网络类型来决定是否显示,开发者可以重写相关逻辑
  314. */
  315. public boolean showNetWarning() {
  316. return NetworkUtils.getNetworkType(getContext()) == NetworkUtils.NETWORK_MOBILE
  317. && !VideoViewManager.instance().playOnMobileNetwork();
  318. }
  319. /**
  320. * 播放和暂停
  321. */
  322. protected void togglePlay() {
  323. mControlWrapper.togglePlay();
  324. }
  325. /**
  326. * 横竖屏切换
  327. */
  328. protected void toggleFullScreen() {
  329. mControlWrapper.toggleFullScreen(mActivity);
  330. }
  331. /**
  332. * 子类中请使用此方法来进入全屏
  333. *
  334. * @return 是否成功进入全屏
  335. */
  336. protected boolean startFullScreen() {
  337. if (mActivity == null || mActivity.isFinishing()) return false;
  338. mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
  339. mControlWrapper.startFullScreen();
  340. return true;
  341. }
  342. /**
  343. * 子类中请使用此方法来退出全屏
  344. *
  345. * @return 是否成功退出全屏
  346. */
  347. protected boolean stopFullScreen() {
  348. if (mActivity == null || mActivity.isFinishing()) return false;
  349. mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
  350. mControlWrapper.stopFullScreen();
  351. return true;
  352. }
  353. /**
  354. * 改变返回键逻辑,用于activity
  355. */
  356. public boolean onBackPressed() {
  357. return false;
  358. }
  359. @Override
  360. public void onWindowFocusChanged(boolean hasWindowFocus) {
  361. super.onWindowFocusChanged(hasWindowFocus);
  362. if (mControlWrapper.isPlaying()
  363. && (mEnableOrientation || mControlWrapper.isFullScreen())) {
  364. if (hasWindowFocus) {
  365. postDelayed(new Runnable() {
  366. @Override
  367. public void run() {
  368. mOrientationHelper.enable();
  369. }
  370. }, 800);
  371. } else {
  372. mOrientationHelper.disable();
  373. }
  374. }
  375. }
  376. /**
  377. * 是否自动旋转, 默认不自动旋转
  378. */
  379. public void setEnableOrientation(boolean enableOrientation) {
  380. mEnableOrientation = enableOrientation;
  381. }
  382. private int mOrientation = 0;
  383. @CallSuper
  384. @Override
  385. public void onOrientationChanged(int orientation) {
  386. if (mActivity == null || mActivity.isFinishing()) return;
  387. //记录用户手机上一次放置的位置
  388. int lastOrientation = mOrientation;
  389. if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
  390. //手机平放时,检测不到有效的角度
  391. //重置为原始位置 -1
  392. mOrientation = -1;
  393. return;
  394. }
  395. if (orientation > 350 || orientation < 10) {
  396. int o = mActivity.getRequestedOrientation();
  397. //手动切换横竖屏
  398. if (o == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE && lastOrientation == 0) return;
  399. if (mOrientation == 0) return;
  400. //0度,用户竖直拿着手机
  401. mOrientation = 0;
  402. onOrientationPortrait(mActivity);
  403. } else if (orientation > 80 && orientation < 100) {
  404. int o = mActivity.getRequestedOrientation();
  405. //手动切换横竖屏
  406. if (o == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT && lastOrientation == 90) return;
  407. if (mOrientation == 90) return;
  408. //90度,用户右侧横屏拿着手机
  409. mOrientation = 90;
  410. onOrientationReverseLandscape(mActivity);
  411. } else if (orientation > 260 && orientation < 280) {
  412. int o = mActivity.getRequestedOrientation();
  413. //手动切换横竖屏
  414. if (o == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT && lastOrientation == 270) return;
  415. if (mOrientation == 270) return;
  416. //270度,用户左侧横屏拿着手机
  417. mOrientation = 270;
  418. onOrientationLandscape(mActivity);
  419. }
  420. }
  421. /**
  422. * 竖屏
  423. */
  424. protected void onOrientationPortrait(Activity activity) {
  425. //屏幕锁定的情况
  426. if (mIsLocked) return;
  427. //没有开启设备方向监听的情况
  428. if (!mEnableOrientation) return;
  429. activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
  430. mControlWrapper.stopFullScreen();
  431. }
  432. /**
  433. * 横屏
  434. */
  435. protected void onOrientationLandscape(Activity activity) {
  436. activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
  437. if (mControlWrapper.isFullScreen()) {
  438. handlePlayerStateChanged(ConstantKeys.PlayMode.MODE_FULL_SCREEN);
  439. } else {
  440. mControlWrapper.startFullScreen();
  441. }
  442. }
  443. /**
  444. * 反向横屏
  445. */
  446. protected void onOrientationReverseLandscape(Activity activity) {
  447. activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
  448. if (mControlWrapper.isFullScreen()) {
  449. handlePlayerStateChanged(ConstantKeys.PlayMode.MODE_FULL_SCREEN);
  450. } else {
  451. mControlWrapper.startFullScreen();
  452. }
  453. }
  454. //------------------------ start handle event change ------------------------//
  455. private void handleVisibilityChanged(boolean isVisible, Animation anim) {
  456. if (!mIsLocked) { //没锁住时才向ControlComponent下发此事件
  457. for (Map.Entry<IControlComponent, Boolean> next
  458. : mControlComponents.entrySet()) {
  459. IControlComponent component = next.getKey();
  460. component.onVisibilityChanged(isVisible, anim);
  461. }
  462. }
  463. onVisibilityChanged(isVisible, anim);
  464. }
  465. /**
  466. * 子类重写此方法监听控制的显示和隐藏
  467. *
  468. * @param isVisible 是否可见
  469. * @param anim 显示/隐藏动画
  470. */
  471. protected void onVisibilityChanged(boolean isVisible, Animation anim) {
  472. }
  473. private void handlePlayStateChanged(int playState) {
  474. for (Map.Entry<IControlComponent, Boolean> next
  475. : mControlComponents.entrySet()) {
  476. IControlComponent component = next.getKey();
  477. component.onPlayStateChanged(playState);
  478. }
  479. onPlayStateChanged(playState);
  480. }
  481. /**
  482. * 子类重写此方法并在其中更新控制器在不同播放状态下的ui
  483. */
  484. @CallSuper
  485. protected void onPlayStateChanged(int playState) {
  486. switch (playState) {
  487. case VideoView.STATE_IDLE:
  488. mOrientationHelper.disable();
  489. mOrientation = 0;
  490. mIsLocked = false;
  491. mShowing = false;
  492. removeAllPrivateComponents();
  493. break;
  494. case VideoView.STATE_PLAYBACK_COMPLETED:
  495. mIsLocked = false;
  496. mShowing = false;
  497. break;
  498. case VideoView.STATE_ERROR:
  499. mShowing = false;
  500. break;
  501. }
  502. }
  503. private void handlePlayerStateChanged(int playerState) {
  504. for (Map.Entry<IControlComponent, Boolean> next : mControlComponents.entrySet()) {
  505. IControlComponent component = next.getKey();
  506. component.onPlayerStateChanged(playerState);
  507. }
  508. onPlayerStateChanged(playerState);
  509. }
  510. /**
  511. * 子类重写此方法并在其中更新控制器在不同播放器状态下的ui
  512. */
  513. @CallSuper
  514. protected void onPlayerStateChanged(int playerState) {
  515. switch (playerState) {
  516. case ConstantKeys.PlayMode.MODE_NORMAL:
  517. if (mEnableOrientation) {
  518. mOrientationHelper.enable();
  519. } else {
  520. mOrientationHelper.disable();
  521. }
  522. if (hasCutout()) {
  523. StatesCutoutUtils.adaptCutoutAboveAndroidP(getContext(), false);
  524. }
  525. break;
  526. case ConstantKeys.PlayMode.MODE_FULL_SCREEN:
  527. //在全屏时强制监听设备方向
  528. mOrientationHelper.enable();
  529. if (hasCutout()) {
  530. StatesCutoutUtils.adaptCutoutAboveAndroidP(getContext(), true);
  531. }
  532. break;
  533. case ConstantKeys.PlayMode.MODE_TINY_WINDOW:
  534. mOrientationHelper.disable();
  535. break;
  536. }
  537. }
  538. private void handleSetProgress(int duration, int position) {
  539. for (Map.Entry<IControlComponent, Boolean> next
  540. : mControlComponents.entrySet()) {
  541. IControlComponent component = next.getKey();
  542. component.setProgress(duration, position);
  543. }
  544. setProgress(duration, position);
  545. }
  546. /**
  547. * 刷新进度回调,子类可在此方法监听进度刷新,然后更新ui
  548. *
  549. * @param duration 视频总时长
  550. * @param position 视频当前时长
  551. */
  552. protected void setProgress(int duration, int position) {
  553. }
  554. private void handleLockStateChanged(boolean isLocked) {
  555. for (Map.Entry<IControlComponent, Boolean> next
  556. : mControlComponents.entrySet()) {
  557. IControlComponent component = next.getKey();
  558. component.onLockStateChanged(isLocked);
  559. }
  560. onLockStateChanged(isLocked);
  561. }
  562. /**
  563. * 子类可重写此方法监听锁定状态发生改变,然后更新ui
  564. */
  565. protected void onLockStateChanged(boolean isLocked) {
  566. }
  567. //------------------------ end handle event change ------------------------//
  568. }