Bug 1382151 - Pause Exoplayer when mediaelement is paused. r=kikuo
authorJames Cheng <jacheng@mozilla.com>
Thu, 20 Jul 2017 17:47:54 +0800
changeset 419529 9679bdd8b0a39b0f37cd754324a436e7ff76d9f2
parent 419528 0203a69a9e6decb8112f738622056118fd7488fd
child 419530 b82ea19a479f5bee3d18e8d121f9746a74f27fd1
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskikuo
bugs1382151
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1382151 - Pause Exoplayer when mediaelement is paused. r=kikuo MozReview-Commit-ID: 5MDBBP5vfpa
dom/media/hls/HLSDecoder.cpp
dom/media/hls/HLSDecoder.h
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/BaseHlsPlayer.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHLSResourceWrapper.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsPlayer.java
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
--- a/dom/media/hls/HLSDecoder.cpp
+++ b/dom/media/hls/HLSDecoder.cpp
@@ -88,9 +88,31 @@ HLSDecoder::Load(nsIChannel* aChannel,
 
 nsresult
 HLSDecoder::Load(MediaResource*)
 {
   MOZ_CRASH("Clone is not supported");
   return NS_ERROR_FAILURE;
 }
 
+nsresult
+HLSDecoder::Play()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  HLS_DEBUG("HLSDecoder", "MediaElement called Play");
+  auto resourceWrapper =
+        static_cast<HLSResource*>(GetResource())->GetResourceWrapper();
+  resourceWrapper->Play();
+  return MediaDecoder::Play();
+}
+
+void
+HLSDecoder::Pause()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  HLS_DEBUG("HLSDecoder", "MediaElement called Pause");
+  auto resourceWrapper =
+      static_cast<HLSResource*>(GetResource())->GetResourceWrapper();
+  resourceWrapper->Pause();
+  return MediaDecoder::Pause();
+}
+
 } // namespace mozilla
--- a/dom/media/hls/HLSDecoder.h
+++ b/dom/media/hls/HLSDecoder.h
@@ -32,13 +32,17 @@ public:
   // with the a platform decoder backend.
   // If provided, codecs are checked for support.
   static bool IsSupportedType(const MediaContainerType& aContainerType);
 
   nsresult Load(nsIChannel* aChannel,
                 bool aIsPrivateBrowsing,
                 nsIStreamListener**) override;
   nsresult Load(MediaResource*) override;
+
+  nsresult Play() override;
+
+  void Pause() override;
 };
 
 } // namespace mozilla
 
 #endif /* HLSDecoder_h_ */
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/BaseHlsPlayer.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/BaseHlsPlayer.java
@@ -83,10 +83,14 @@ public interface BaseHlsPlayer {
     public boolean seek(long positionUs);
 
     public long getNextKeyFrameTime();
 
     public void suspend();
 
     public void resume();
 
+    public void play();
+
+    public void pause();
+
     public void release();
 }
\ No newline at end of file
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHLSResourceWrapper.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHLSResourceWrapper.java
@@ -74,16 +74,32 @@ public class GeckoHLSResourceWrapper {
     @WrapForJNI(calledFrom = "gecko")
     public void resume() {
         if (DEBUG) Log.d(LOGTAG, "GeckoHLSResourceWrapper resume");
         if (mPlayer != null) {
             mPlayer.resume();
         }
     }
 
+    @WrapForJNI(calledFrom = "gecko")
+    public void play() {
+        if (DEBUG) Log.d(LOGTAG, "GeckoHLSResourceWrapper mediaelement played");
+        if (mPlayer != null) {
+            mPlayer.play();
+        }
+    }
+
+    @WrapForJNI(calledFrom = "gecko")
+    public void pause() {
+        if (DEBUG) Log.d(LOGTAG, "GeckoHLSResourceWrapper mediaelement paused");
+        if (mPlayer != null) {
+            mPlayer.pause();
+        }
+    }
+
     private static void assertTrue(boolean condition) {
         if (DEBUG && !condition) {
             throw new AssertionError("Expected condition to be true");
         }
     }
 
     @WrapForJNI // Called when native object is mDestroy.
     private void destroy() {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsPlayer.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsPlayer.java
@@ -55,17 +55,18 @@ public class GeckoHlsPlayer implements B
      *  Because we treat GeckoHlsPlayer as a source data provider.
      *  It will be created and initialized with a URL by HLSResource in
      *  Gecko media pipleine (in cpp). Once HLSDemuxer is created later, we
      *  need to bridge this HLSResource to the created demuxer. And they share
      *  the same GeckoHlsPlayer.
      *  mPlayerId is a token used for Gecko media pipeline to obtain corresponding player.
      */
     private final int mPlayerId;
-    private volatile boolean mSuspended = false;
+    private boolean mExoplayerSuspended = false;
+    private boolean mMediaElementSuspended = false;
     private DataSource.Factory mMediaDataSourceFactory;
 
     private Handler mMainHandler;
     private HandlerThread mThread;
     private ExoPlayer mPlayer;
     private GeckoHlsRendererBase[] mRenderers;
     private DefaultTrackSelector mTrackSelector;
     private MediaSource mMediaSource;
@@ -307,27 +308,30 @@ public class GeckoHlsPlayer implements B
         mDemuxerCallbacks = callback;
     }
 
     // Called on GeckoHlsPlayerThread from ExoPlayer
     @Override
     public synchronized void onLoadingChanged(boolean isLoading) {
         if (DEBUG) { Log.d(LOGTAG, "loading [" + isLoading + "]"); }
         if (!isLoading) {
+            if (mMediaElementSuspended) {
+                suspendExoplayer();
+            }
             // To update buffered position.
             mComponentEventDispatcher.onDataArrived(C.TRACK_TYPE_DEFAULT);
         }
     }
 
     // Called on GeckoHlsPlayerThread from ExoPlayer
     @Override
     public synchronized void onPlayerStateChanged(boolean playWhenReady, int state) {
         if (DEBUG) { Log.d(LOGTAG, "state [" + playWhenReady + ", " + getStateString(state) + "]"); }
-        if (state == ExoPlayer.STATE_READY && !mSuspended) {
-            mPlayer.setPlayWhenReady(true);
+        if (state == ExoPlayer.STATE_READY && !mExoplayerSuspended && !mMediaElementSuspended) {
+            resumeExoplayer();
         }
     }
 
     // Called on GeckoHlsPlayerThread from ExoPlayer
     @Override
     public void onPositionDiscontinuity() {
         if (DEBUG) { Log.d(LOGTAG, "positionDiscontinuity"); }
     }
@@ -674,16 +678,22 @@ public class GeckoHlsPlayer implements B
                                                   16, 0, getDuration(),
                                                   fmt.sampleMimeType, csd);
         return aInfo;
     }
 
     // Called on HLSDemuxer's TaskQueue
     @Override
     public synchronized boolean seek(long positionUs) {
+        // Need to temporarily resume Exoplayer to download the chunks for getting the demuxed
+        // keyframe sample when HTMLMediaElement is paused. Suspend Exoplayer when collecting enough
+        // samples in onLoadingChanged.
+        if (mExoplayerSuspended) {
+            resumeExoplayer();
+        }
         // positionUs : microseconds.
         // NOTE : 1) It's not possible to seek media by tracktype via ExoPlayer Interface.
         //        2) positionUs is samples PTS from MFR, we need to re-adjust it
         //           for ExoPlayer by subtracting sample start time.
         //        3) Time unit for ExoPlayer.seek() is milliseconds.
         try {
             // TODO : Gather Timeline Period / Window information to develop
             //        complete timeline, and seekTime should be inside the duration.
@@ -707,49 +717,88 @@ public class GeckoHlsPlayer implements B
             }
             return false;
         }
         return true;
     }
 
     // Called on HLSDemuxer's TaskQueue
     @Override
-    public long getNextKeyFrameTime() {
+    public synchronized long getNextKeyFrameTime() {
         long nextKeyFrameTime = mVRenderer != null
             ? mVRenderer.getNextKeyFrameTime()
             : Long.MAX_VALUE;
         return nextKeyFrameTime;
     }
 
     // Called on Gecko's main thread.
     @Override
     public synchronized void suspend() {
-        if (mSuspended) {
+        if (mExoplayerSuspended) {
             return;
         }
-        if (DEBUG) { Log.d(LOGTAG, "suspend player id : " + mPlayerId); }
-        mSuspended = true;
-        if (mPlayer != null) {
-            mPlayer.setPlayWhenReady(false);
+        if (mMediaElementSuspended) {
+            if (DEBUG) {
+                Log.d(LOGTAG, "suspend player id : " + mPlayerId);
+            }
+            suspendExoplayer();
         }
     }
 
     // Called on Gecko's main thread.
     @Override
     public synchronized void resume() {
-        if (!mSuspended) {
+        if (!mExoplayerSuspended) {
           return;
         }
-        if (DEBUG) { Log.d(LOGTAG, "resume player id : " + mPlayerId); }
-        mSuspended = false;
+        if (!mMediaElementSuspended) {
+            if (DEBUG) {
+                Log.d(LOGTAG, "resume player id : " + mPlayerId);
+            }
+            resumeExoplayer();
+        }
+    }
+
+    // Called on Gecko's main thread.
+    @Override
+    public synchronized void play() {
+        if (!mMediaElementSuspended) {
+            return;
+        }
+        if (DEBUG) { Log.d(LOGTAG, "mediaElement played."); }
+        mMediaElementSuspended = false;
+        resumeExoplayer();
+    }
+
+    // Called on Gecko's main thread.
+    @Override
+    public synchronized void pause() {
+        if (mMediaElementSuspended) {
+            return;
+        }
+        if (DEBUG) { Log.d(LOGTAG, "mediaElement paused."); }
+        mMediaElementSuspended = true;
+        suspendExoplayer();
+    }
+
+    private synchronized void suspendExoplayer() {
         if (mPlayer != null) {
+            mExoplayerSuspended = true;
+            if (DEBUG) { Log.d(LOGTAG, "suspend Exoplayer"); }
+            mPlayer.setPlayWhenReady(false);
+        }
+    }
+
+    private synchronized void resumeExoplayer() {
+        if (mPlayer != null) {
+            mExoplayerSuspended = false;
+            if (DEBUG) { Log.d(LOGTAG, "resume Exoplayer"); }
             mPlayer.setPlayWhenReady(true);
         }
     }
-
     // Called on Gecko's main thread, when HLSDemuxer or HLSResource destructs.
     @Override
     public synchronized void release() {
         if (DEBUG) { Log.d(LOGTAG, "releasing  ... id : " + mPlayerId); }
         if (mPlayer != null) {
             mPlayer.removeListener(this);
             mPlayer.stop();
             mPlayer.release();
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -2045,16 +2045,32 @@ auto GeckoHLSResourceWrapper::Destroy() 
 constexpr char GeckoHLSResourceWrapper::GetPlayerId_t::name[];
 constexpr char GeckoHLSResourceWrapper::GetPlayerId_t::signature[];
 
 auto GeckoHLSResourceWrapper::GetPlayerId() const -> int32_t
 {
     return mozilla::jni::Method<GetPlayerId_t>::Call(GeckoHLSResourceWrapper::mCtx, nullptr);
 }
 
+constexpr char GeckoHLSResourceWrapper::Pause_t::name[];
+constexpr char GeckoHLSResourceWrapper::Pause_t::signature[];
+
+auto GeckoHLSResourceWrapper::Pause() const -> void
+{
+    return mozilla::jni::Method<Pause_t>::Call(GeckoHLSResourceWrapper::mCtx, nullptr);
+}
+
+constexpr char GeckoHLSResourceWrapper::Play_t::name[];
+constexpr char GeckoHLSResourceWrapper::Play_t::signature[];
+
+auto GeckoHLSResourceWrapper::Play() const -> void
+{
+    return mozilla::jni::Method<Play_t>::Call(GeckoHLSResourceWrapper::mCtx, nullptr);
+}
+
 constexpr char GeckoHLSResourceWrapper::Resume_t::name[];
 constexpr char GeckoHLSResourceWrapper::Resume_t::signature[];
 
 auto GeckoHLSResourceWrapper::Resume() const -> void
 {
     return mozilla::jni::Method<Resume_t>::Call(GeckoHLSResourceWrapper::mCtx, nullptr);
 }
 
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -5874,16 +5874,54 @@ public:
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::GECKO;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     auto GetPlayerId() const -> int32_t;
 
+    struct Pause_t {
+        typedef GeckoHLSResourceWrapper Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "pause";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::GECKO;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    auto Pause() const -> void;
+
+    struct Play_t {
+        typedef GeckoHLSResourceWrapper Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "play";
+        static constexpr char signature[] =
+                "()V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::GECKO;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    auto Play() const -> void;
+
     struct Resume_t {
         typedef GeckoHLSResourceWrapper Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<> Args;
         static constexpr char name[] = "resume";
         static constexpr char signature[] =
                 "()V";