Bug 1382151 - Pause Exoplayer when mediaelement is paused. draft
authorJames Cheng <jacheng@mozilla.com>
Thu, 20 Jul 2017 17:47:54 +0800
changeset 615035 e1c81f03d42faf21bd5ab04f262a506b9b158abf
parent 611714 eb1d92b2b6a4161492561250f51bae5bafeda68a
child 639062 6df22d1ca5bfd677d6ae2f9b269703cf598e0f40
push id70225
push userbmo:jacheng@mozilla.com
push dateTue, 25 Jul 2017 10:23:19 +0000
bugs1382151
milestone56.0a1
Bug 1382151 - Pause Exoplayer when mediaelement is paused. 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
@@ -87,9 +87,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";