diff --git a/dom/media/hls/HLSDecoder.cpp b/dom/media/hls/HLSDecoder.cpp index 906a7700632d..e5522b6d9628 100644 --- a/dom/media/hls/HLSDecoder.cpp +++ b/dom/media/hls/HLSDecoder.cpp @@ -93,4 +93,26 @@ HLSDecoder::Load(MediaResource*) return NS_ERROR_FAILURE; } +nsresult +HLSDecoder::Play() +{ + MOZ_ASSERT(NS_IsMainThread()); + HLS_DEBUG("HLSDecoder", "MediaElement called Play"); + auto resourceWrapper = + static_cast(GetResource())->GetResourceWrapper(); + resourceWrapper->Play(); + return MediaDecoder::Play(); +} + +void +HLSDecoder::Pause() +{ + MOZ_ASSERT(NS_IsMainThread()); + HLS_DEBUG("HLSDecoder", "MediaElement called Pause"); + auto resourceWrapper = + static_cast(GetResource())->GetResourceWrapper(); + resourceWrapper->Pause(); + return MediaDecoder::Pause(); +} + } // namespace mozilla diff --git a/dom/media/hls/HLSDecoder.h b/dom/media/hls/HLSDecoder.h index 5a60da9abbd5..fd846efca814 100644 --- a/dom/media/hls/HLSDecoder.h +++ b/dom/media/hls/HLSDecoder.h @@ -37,6 +37,10 @@ public: bool aIsPrivateBrowsing, nsIStreamListener**) override; nsresult Load(MediaResource*) override; + + nsresult Play() override; + + void Pause() override; }; } // namespace mozilla diff --git 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 index f736408c13da..29d472a2c276 100644 --- 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 @@ -88,5 +88,9 @@ public interface BaseHlsPlayer { public void resume(); + public void play(); + + public void pause(); + public void release(); } \ No newline at end of file diff --git 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 index 52cd8aa54ca4..b4bcfd85de72 100644 --- 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 @@ -79,6 +79,22 @@ public class GeckoHLSResourceWrapper { } } + @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"); diff --git 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 index 7e0d7c7cf854..b215c8ccea86 100644 --- 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 @@ -60,7 +60,8 @@ public class GeckoHlsPlayer implements BaseHlsPlayer, ExoPlayer.EventListener { * 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; @@ -312,6 +313,9 @@ public class GeckoHlsPlayer implements BaseHlsPlayer, ExoPlayer.EventListener { 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); } @@ -321,8 +325,8 @@ public class GeckoHlsPlayer implements BaseHlsPlayer, ExoPlayer.EventListener { @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(); } } @@ -679,6 +683,12 @@ public class GeckoHlsPlayer implements BaseHlsPlayer, ExoPlayer.EventListener { // 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 @@ -712,7 +722,7 @@ public class GeckoHlsPlayer implements BaseHlsPlayer, ExoPlayer.EventListener { // Called on HLSDemuxer's TaskQueue @Override - public long getNextKeyFrameTime() { + public synchronized long getNextKeyFrameTime() { long nextKeyFrameTime = mVRenderer != null ? mVRenderer.getNextKeyFrameTime() : Long.MAX_VALUE; @@ -722,29 +732,68 @@ public class GeckoHlsPlayer implements BaseHlsPlayer, ExoPlayer.EventListener { // 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 (mPlayer != null) { - mPlayer.setPlayWhenReady(true); + 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() { diff --git a/widget/android/GeneratedJNIWrappers.cpp b/widget/android/GeneratedJNIWrappers.cpp index b72ca9e2325d..71b90763954d 100644 --- a/widget/android/GeneratedJNIWrappers.cpp +++ b/widget/android/GeneratedJNIWrappers.cpp @@ -2050,6 +2050,22 @@ auto GeckoHLSResourceWrapper::GetPlayerId() const -> int32_t return mozilla::jni::Method::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::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::Call(GeckoHLSResourceWrapper::mCtx, nullptr); +} + constexpr char GeckoHLSResourceWrapper::Resume_t::name[]; constexpr char GeckoHLSResourceWrapper::Resume_t::signature[]; diff --git a/widget/android/GeneratedJNIWrappers.h b/widget/android/GeneratedJNIWrappers.h index ccbd0df32283..6b5a07c0a625 100644 --- a/widget/android/GeneratedJNIWrappers.h +++ b/widget/android/GeneratedJNIWrappers.h @@ -5879,6 +5879,44 @@ public: 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;