Bug 1274919 - part2 : implement resume/suspend mechanism in MediaDecoder.
Create the new class "BackgroundVideoDecodingPermissionObserver" to handle the
suspended request sent from the front end side.
We would do registration on startup and deregistration on shutdown of MediaDecoder.
MozReview-Commit-ID: 1UwHA7YuVN3
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -117,16 +117,114 @@ public:
DecodersArray& decoders = Decoders();
decoders.RemoveElement(aDecoder);
if (decoders.IsEmpty()) {
sUniqueInstance = nullptr;
}
}
};
+class MediaDecoder::BackgroundVideoDecodingPermissionObserver final :
+ public nsIObserver
+{
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit BackgroundVideoDecodingPermissionObserver(MediaDecoder* aDecoder)
+ : mDecoder(aDecoder)
+ , mIsRegisteredForEvent(false)
+ {
+ MOZ_ASSERT(mDecoder);
+ }
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override
+ {
+ if (!MediaPrefs::ResumeVideoDecodingOnTabHover()) {
+ return NS_OK;
+ }
+
+ if (!IsValidEventSender(aSubject)) {
+ return NS_OK;
+ }
+
+ if (strcmp(aTopic, "unselected-tab-hover") == 0) {
+ mDecoder->mIsBackgroundVideoDecodingAllowed = !NS_strcmp(aData, u"true");
+ mDecoder->UpdateVideoDecodeMode();
+ }
+ return NS_OK;
+ }
+
+ void RegisterEvent() {
+ MOZ_ASSERT(!mIsRegisteredForEvent);
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, "unselected-tab-hover", false);
+ mIsRegisteredForEvent = true;
+ }
+ }
+
+ void UnregisterEvent() {
+ MOZ_ASSERT(mIsRegisteredForEvent);
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, "unselected-tab-hover");
+ mIsRegisteredForEvent = false;
+ mDecoder->mIsBackgroundVideoDecodingAllowed = false;
+ mDecoder->UpdateVideoDecodeMode();
+ }
+ }
+ private:
+ ~BackgroundVideoDecodingPermissionObserver() {
+ MOZ_ASSERT(!mIsRegisteredForEvent);
+ }
+
+ bool IsValidEventSender(nsISupports* aSubject) const
+ {
+ nsCOMPtr<nsPIDOMWindowInner> senderInner(do_QueryInterface(aSubject));
+ if (!senderInner) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> senderOuter = senderInner->GetOuterWindow();
+ if (!senderOuter) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> senderTop = senderOuter->GetTop();
+ if (!senderTop) {
+ return false;
+ }
+
+ nsIDocument* doc = mDecoder->GetOwner()->GetDocument();
+ if (!doc) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> ownerInner = doc->GetInnerWindow();
+ if (!ownerInner) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> ownerOuter = ownerInner->GetOuterWindow();
+ if (!ownerOuter) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> ownerTop = ownerOuter->GetTop();
+ return ownerTop == senderTop;
+ }
+ // The life cycle of observer would always be shorter than decoder, so we
+ // use raw pointer here.
+ MediaDecoder* mDecoder;
+ bool mIsRegisteredForEvent;
+};
+
+NS_IMPL_ISUPPORTS(MediaDecoder::BackgroundVideoDecodingPermissionObserver, nsIObserver)
+
StaticRefPtr<MediaMemoryTracker> MediaMemoryTracker::sUniqueInstance;
LazyLogModule gMediaTimerLog("MediaTimer");
constexpr TimeUnit MediaDecoder::DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED;
void
MediaDecoder::InitStatics()
@@ -248,16 +346,18 @@ MediaDecoder::MediaDecoder(MediaDecoderI
, INIT_CANONICAL(mPlayState, PLAY_STATE_LOADING)
, INIT_CANONICAL(mNextState, PLAY_STATE_PAUSED)
, INIT_CANONICAL(mLogicallySeeking, false)
, INIT_CANONICAL(mSameOriginMedia, false)
, INIT_CANONICAL(mMediaPrincipalHandle, PRINCIPAL_HANDLE_NONE)
, INIT_CANONICAL(mPlaybackBytesPerSecond, 0.0)
, INIT_CANONICAL(mPlaybackRateReliable, true)
, INIT_CANONICAL(mDecoderPosition, 0)
+ , mVideoDecodingOberver(new BackgroundVideoDecodingPermissionObserver(this))
+ , mIsBackgroundVideoDecodingAllowed(false)
, mTelemetryReported(false)
, mContainerType(aInit.mContainerType)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mAbstractMainThread);
MediaMemoryTracker::AddMediaDecoder(this);
//
@@ -281,16 +381,17 @@ MediaDecoder::MediaDecoder(MediaDecoderI
// mIgnoreProgressData
mWatchManager.Watch(mLogicallySeeking, &MediaDecoder::SeekingChanged);
mWatchManager.Watch(mIsAudioDataAudible,
&MediaDecoder::NotifyAudibleStateChanged);
MediaShutdownManager::InitStatics();
+ mVideoDecodingOberver->RegisterEvent();
}
#undef INIT_MIRROR
#undef INIT_CANONICAL
void
MediaDecoder::Shutdown()
{
@@ -340,16 +441,18 @@ MediaDecoder::Shutdown()
if (mResource) {
mResource->Close();
}
// Ask the owner to remove its audio/video tracks.
GetOwner()->RemoveMediaTracks();
ChangeState(PLAY_STATE_SHUTDOWN);
+ mVideoDecodingOberver->UnregisterEvent();
+ mVideoDecodingOberver = nullptr;
mOwner = nullptr;
}
void
MediaDecoder::NotifyXPCOMShutdown()
{
MOZ_ASSERT(NS_IsMainThread());
if (auto owner = GetOwner()) {
@@ -1138,16 +1241,22 @@ MediaDecoder::UpdateVideoDecodeMode()
}
// If mForcedHidden is set, suspend the video decoder anyway.
if (mForcedHidden) {
mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Suspend);
return;
}
+ // Resume decoding in the advance, even the element is in the background.
+ if (mIsBackgroundVideoDecodingAllowed) {
+ mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Normal);
+ return;
+ }
+
// Otherwise, depends on the owner's visibility state.
// A element is visible only if its document is visible and the element
// itself is visible.
if (mIsDocumentVisible &&
mElementVisibility == Visibility::APPROXIMATELY_VISIBLE) {
mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Normal);
} else {
mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Suspend);
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -764,16 +764,26 @@ protected:
Canonical<bool> mPlaybackRateReliable;
// Current decoding position in the stream. This is where the decoder
// is up to consuming the stream. This is not adjusted during decoder
// seek operations, but it's updated at the end when we start playing
// back again.
Canonical<int64_t> mDecoderPosition;
+ // We can allow video decoding in background when we match some special
+ // conditions, eg. when the cursor is hovering over the tab. This observer is
+ // used to listen the related events.
+ class BackgroundVideoDecodingPermissionObserver;
+ RefPtr<BackgroundVideoDecodingPermissionObserver> mVideoDecodingOberver;
+
+ // True if we want to resume video decoding even the media element is in the
+ // background.
+ bool mIsBackgroundVideoDecodingAllowed;
+
public:
AbstractCanonical<double>* CanonicalVolume() { return &mVolume; }
AbstractCanonical<bool>* CanonicalPreservesPitch()
{
return &mPreservesPitch;
}
AbstractCanonical<bool>* CanonicalLooping()
{
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -203,16 +203,18 @@ private:
DECL_MEDIA_PREF("media.mp4.enabled", MP4Enabled, bool, false);
// Error/warning handling, Decoder Doctor
DECL_MEDIA_PREF("media.playback.warnings-as-errors", MediaWarningsAsErrors, bool, false);
DECL_MEDIA_PREF("media.playback.warnings-as-errors.stagefright-vs-rust",
MediaWarningsAsErrorsStageFrightVsRust, bool, false);
+ // resume background video decoding when the cursor is hovering over the tab.
+ DECL_MEDIA_PREF("media.resume-bkgnd-video-on-tabhover", ResumeVideoDecodingOnTabHover, bool, false);
public:
// Manage the singleton:
static MediaPrefs& GetSingleton();
static bool SingletonExists();
private:
template<class T> friend class StaticAutoPtr;
static StaticAutoPtr<MediaPrefs> sInstance;
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -443,16 +443,19 @@ pref("media.decoder-doctor.wmf-disabled-
// URL to report decode issues
pref("media.decoder-doctor.new-issue-endpoint", "https://webcompat.com/issues/new");
// Whether to suspend decoding of videos in background tabs.
pref("media.suspend-bkgnd-video.enabled", true);
// Delay, in ms, from time window goes to background to suspending
// video decoders. Defaults to 10 seconds.
pref("media.suspend-bkgnd-video.delay-ms", 10000);
+// Resume video decoding when the cursor is hovering on a background tab to
+// reduce the resume latency and improve the user experience.
+pref("media.resume-bkgnd-video-on-tabhover", true);;
#ifdef MOZ_WEBRTC
pref("media.navigator.enabled", true);
pref("media.navigator.video.enabled", true);
pref("media.navigator.load_adapt", true);
pref("media.navigator.load_adapt.encoder_only", true);
pref("media.navigator.load_adapt.measure_interval",1000);
pref("media.navigator.load_adapt.avg_seconds",3);