/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/PlaybackStateObserver.java

https://github.com/chromium/chromium · Java · 185 lines · 121 code · 29 blank · 35 comment · 20 complexity · 8bfb3aa14c0f0ec61c9cc4a01ce63024 MD5 · raw file

  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. package org.chromium.chrome.browser.video_tutorials;
  5. import android.os.SystemClock;
  6. import androidx.annotation.VisibleForTesting;
  7. import org.chromium.base.supplier.Supplier;
  8. import org.chromium.chrome.browser.video_tutorials.PlaybackStateObserver.WatchStateInfo.State;
  9. import org.chromium.content_public.browser.MediaSession;
  10. import org.chromium.content_public.browser.MediaSessionObserver;
  11. import org.chromium.services.media_session.MediaPosition;
  12. /**
  13. * Responsible for observing a media session and notifying the observer about play/pause/end media
  14. * events.
  15. */
  16. public class PlaybackStateObserver extends MediaSessionObserver {
  17. /**
  18. * A ratio used for collecting metrics used to determine whether a video was sufficiently
  19. * watched by the user.
  20. */
  21. private static final float WATCH_COMPLETION_RATIO_THRESHOLD = 0.5f;
  22. /** Contains playback info about currently playing media. */
  23. public static class WatchStateInfo {
  24. /** Contains various states during the media playback. */
  25. public enum State {
  26. INITIAL,
  27. PLAYING,
  28. PAUSED,
  29. ENDED,
  30. ERROR,
  31. }
  32. /** The current state. */
  33. public State state = State.INITIAL;
  34. /** The duration of the video. */
  35. public long videoLength;
  36. /** The current position of the video. */
  37. public long currentPosition;
  38. /**
  39. * Whether the video has been watched up to a certain point so that it can be considered as
  40. * completed.
  41. */
  42. public boolean videoWatched() {
  43. return currentPosition > videoLength * WATCH_COMPLETION_RATIO_THRESHOLD;
  44. }
  45. }
  46. /**
  47. * Interface to be notified of playback state updates.
  48. */
  49. public interface Observer {
  50. /** Called when the player has started playing or resumed. */
  51. void onPlay();
  52. /** Called when the player has been paused. */
  53. void onPause();
  54. /** Called when the player has completed playing the video. */
  55. void onEnded();
  56. /** Called when an error has occurred. */
  57. void onError();
  58. }
  59. private static Long sCurrentSystemTimeForTesting;
  60. private final Supplier<Observer> mObserver;
  61. private WatchStateInfo mWatchStateInfo = new WatchStateInfo();
  62. private MediaPosition mLastPosition;
  63. private boolean mIsControllable;
  64. private boolean mIsSuspended;
  65. /** Constructor. */
  66. public PlaybackStateObserver(MediaSession mediaSession, Supplier<Observer> observer) {
  67. super(mediaSession);
  68. mObserver = observer;
  69. }
  70. /**
  71. * Called to get the current media playback info, such as duration, current progress, playback
  72. * state etc.
  73. * @return The current watch state info.
  74. */
  75. public WatchStateInfo getWatchStateInfo() {
  76. return mWatchStateInfo;
  77. }
  78. /** Reset internal state. */
  79. public void reset() {
  80. mLastPosition = null;
  81. mIsControllable = false;
  82. mIsSuspended = false;
  83. mWatchStateInfo = new WatchStateInfo();
  84. }
  85. @Override
  86. public void mediaSessionPositionChanged(MediaPosition position) {
  87. if (position != null) {
  88. mWatchStateInfo.videoLength = position.getDuration();
  89. }
  90. updateState(position);
  91. mWatchStateInfo.currentPosition =
  92. computeCurrentPosition(position == null ? mLastPosition : position);
  93. mLastPosition = position;
  94. }
  95. @Override
  96. public void mediaSessionStateChanged(boolean isControllable, boolean isSuspended) {
  97. mIsControllable = isControllable;
  98. mIsSuspended = isSuspended;
  99. updateState(mLastPosition);
  100. }
  101. private void updateState(MediaPosition newPosition) {
  102. State nextState = mWatchStateInfo.state;
  103. if (mIsControllable) {
  104. nextState = mIsSuspended ? State.PAUSED : State.PLAYING;
  105. } else if (newPosition == null) {
  106. // TODO(shaktisahu): Determine error state.
  107. if (mLastPosition == null) {
  108. nextState = State.INITIAL;
  109. } else if (mLastPosition.getDuration() == computeCurrentPosition(mLastPosition)) {
  110. nextState = State.ENDED;
  111. }
  112. }
  113. updateObservers(nextState);
  114. }
  115. private void updateObservers(State nextState) {
  116. if (nextState == mWatchStateInfo.state) return;
  117. mWatchStateInfo.state = nextState;
  118. switch (nextState) {
  119. case INITIAL:
  120. break;
  121. case PLAYING:
  122. mObserver.get().onPlay();
  123. break;
  124. case PAUSED:
  125. mObserver.get().onPause();
  126. break;
  127. case ENDED:
  128. mObserver.get().onEnded();
  129. break;
  130. case ERROR:
  131. mObserver.get().onError();
  132. break;
  133. default:
  134. assert false : "Unknown media playback state";
  135. }
  136. }
  137. private static long computeCurrentPosition(MediaPosition mediaPosition) {
  138. if (mediaPosition == null) return 0;
  139. long elapsedTime = getCurrentSystemTime() - mediaPosition.getLastUpdatedTime();
  140. long updatedPosition = (long) (mediaPosition.getPosition()
  141. + (elapsedTime * mediaPosition.getPlaybackRate()));
  142. updatedPosition = Math.min(updatedPosition, mediaPosition.getDuration());
  143. if (mediaPosition.getDuration() - updatedPosition < 100) {
  144. updatedPosition = mediaPosition.getDuration();
  145. }
  146. return updatedPosition;
  147. }
  148. private static long getCurrentSystemTime() {
  149. if (sCurrentSystemTimeForTesting != null) return sCurrentSystemTimeForTesting;
  150. return SystemClock.elapsedRealtime();
  151. }
  152. @VisibleForTesting
  153. protected static void setCurrentSystemTimeForTesting(Long currentTimeForTesting) {
  154. sCurrentSystemTimeForTesting = currentTimeForTesting;
  155. }
  156. }