--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -43,19 +43,20 @@
#include "nsIScriptSecurityManager.h"
#include "nsIXPConnect.h"
#include "jsapi.h"
#include "nsITimer.h"
#include "MediaError.h"
#include "MediaDecoder.h"
-#include "nsICategoryManager.h"
+#include "MediaPrefs.h"
#include "MediaResource.h"
+#include "nsICategoryManager.h"
#include "nsIContentPolicy.h"
#include "nsContentPolicyUtils.h"
#include "nsCycleCollectionParticipant.h"
#include "nsICachingChannel.h"
#include "nsLayoutUtils.h"
#include "nsVideoFrame.h"
#include "Layers.h"
#include <limits>
@@ -2557,16 +2558,20 @@ HTMLMediaElement::~HTMLMediaElement()
}
UnregisterActivityObserver();
if (mDecoder) {
ShutdownDecoder();
}
if (mProgressTimer) {
StopProgress();
}
+ if (mVideoDecodeSuspendTimer) {
+ mVideoDecodeSuspendTimer->Cancel();
+ mVideoDecodeSuspendTimer = nullptr;
+ }
if (mSrcStream) {
EndSrcMediaStreamPlayback();
}
if (mCaptureStreamPort) {
mCaptureStreamPort->Destroy();
mCaptureStreamPort = nullptr;
}
@@ -3031,16 +3036,52 @@ nsresult HTMLMediaElement::BindToTree(ns
// When the MediaElement is binding to tree, the dormant status is
// aligned to document's hidden status.
mDecoder->NotifyOwnerActivityChanged(!IsHidden());
}
return rv;
}
+/* static */
+void HTMLMediaElement::VideoDecodeSuspendTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ auto element = static_cast<HTMLMediaElement*>(aClosure);
+ element->mVideoDecodeSuspendTime.Start();
+ element->mVideoDecodeSuspendTimer = nullptr;
+}
+
+void HTMLMediaElement::HiddenVideoStart()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mHiddenPlayTime.Start();
+ if (mVideoDecodeSuspendTimer) {
+ // Already started, just keep it running.
+ return;
+ }
+ mVideoDecodeSuspendTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mVideoDecodeSuspendTimer->InitWithNamedFuncCallback(
+ VideoDecodeSuspendTimerCallback, this,
+ MediaPrefs::MDSMSuspendBackgroundVideoDelay(), nsITimer::TYPE_ONE_SHOT,
+ "HTMLMediaElement::VideoDecodeSuspendTimerCallback");
+}
+
+void HTMLMediaElement::HiddenVideoStop()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mHiddenPlayTime.Pause();
+ mVideoDecodeSuspendTime.Pause();
+ if (!mVideoDecodeSuspendTimer) {
+ return;
+ }
+ mVideoDecodeSuspendTimer->Cancel();
+ mVideoDecodeSuspendTimer = nullptr;
+}
+
#ifdef MOZ_EME
void
HTMLMediaElement::ReportEMETelemetry()
{
// Report telemetry for EME videos when a page is unloaded.
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (mIsEncrypted && Preferences::GetBool("media.eme.enabled")) {
Telemetry::Accumulate(Telemetry::VIDEO_EME_PLAY_SUCCESS, mLoadedDataFired);
@@ -3117,26 +3158,26 @@ HTMLMediaElement::ReportTelemetry()
}
}
if (mMediaInfo.HasVideo() &&
mMediaInfo.mVideo.mImage.height > 0) {
// We have a valid video.
double playTime = mPlayTime.Total();
double hiddenPlayTime = mHiddenPlayTime.Total();
+ double videoDecodeSuspendTime = mVideoDecodeSuspendTime.Total();
Telemetry::Accumulate(Telemetry::VIDEO_PLAY_TIME_MS, SECONDS_TO_MS(playTime));
LOG(LogLevel::Debug, ("%p VIDEO_PLAY_TIME_MS = %f", this, playTime));
Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_MS, SECONDS_TO_MS(hiddenPlayTime));
LOG(LogLevel::Debug, ("%p VIDEO_HIDDEN_PLAY_TIME_MS = %f", this, hiddenPlayTime));
if (playTime > 0.0) {
- // We have actually played something -> Report hidden/total ratio.
- uint32_t hiddenPercentage = uint32_t(hiddenPlayTime / playTime * 100.0 + 0.5);
+ // We have actually played something -> Report some valid-video telemetry.
// Keyed by audio+video or video alone, and by a resolution range.
nsCString key(mMediaInfo.HasAudio() ? "AV," : "V,");
static const struct { int32_t mH; const char* mRes; } sResolutions[] = {
{ 240, "0<h<=240" },
{ 480, "240<h<=480" },
{ 576, "480<h<=576" },
{ 720, "576<h<=720" },
@@ -3148,26 +3189,38 @@ HTMLMediaElement::ReportTelemetry()
for (const auto& res : sResolutions) {
if (height <= res.mH) {
resolution = res.mRes;
break;
}
}
key.AppendASCII(resolution);
+ uint32_t hiddenPercentage = uint32_t(hiddenPlayTime / playTime * 100.0 + 0.5);
Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE,
key,
hiddenPercentage);
// Also accumulate all percentages in an "All" key.
Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE,
NS_LITERAL_CSTRING("All"),
hiddenPercentage);
LOG(LogLevel::Debug, ("%p VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE = %u, keys: '%s' and 'All'",
this, hiddenPercentage, key.get()));
+ uint32_t videoDecodeSuspendPercentage =
+ uint32_t(videoDecodeSuspendTime / playTime * 100.0 + 0.5);
+ Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE,
+ key,
+ videoDecodeSuspendPercentage);
+ Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE,
+ NS_LITERAL_CSTRING("All"),
+ videoDecodeSuspendPercentage);
+ LOG(LogLevel::Debug, ("%p VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE = %u, keys: '%s' and 'All'",
+ this, videoDecodeSuspendPercentage, key.get()));
+
if (data.mInterKeyframeCount != 0) {
uint32_t average_ms =
uint32_t(std::min<uint64_t>(double(data.mInterKeyframeSum_us)
/ double(data.mInterKeyframeCount)
/ 1000.0
+ 0.5,
UINT32_MAX));
Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS,
@@ -4706,24 +4759,24 @@ nsresult HTMLMediaElement::DispatchAsync
}
nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this);
NS_DispatchToMainThread(event);
if ((aName.EqualsLiteral("play") || aName.EqualsLiteral("playing"))) {
mPlayTime.Start();
if (IsHidden()) {
- mHiddenPlayTime.Start();
+ HiddenVideoStart();
}
} else if (aName.EqualsLiteral("waiting")) {
mPlayTime.Pause();
- mHiddenPlayTime.Pause();
+ HiddenVideoStop();
} else if (aName.EqualsLiteral("pause")) {
mPlayTime.Pause();
- mHiddenPlayTime.Pause();
+ HiddenVideoStop();
}
return NS_OK;
}
nsresult HTMLMediaElement::DispatchPendingMediaEvents()
{
NS_ASSERTION(!mEventDeliveryPaused,
@@ -4911,20 +4964,20 @@ void HTMLMediaElement::NotifyOwnerDocume
}
bool
HTMLMediaElement::NotifyOwnerDocumentActivityChangedInternal()
{
bool visible = !IsHidden();
if (visible) {
// Visible -> Just pause hidden play time (no-op if already paused).
- mHiddenPlayTime.Pause();
+ HiddenVideoStop();
} else if (mPlayTime.IsStarted()) {
// Not visible, play time is running -> Start hidden play time if needed.
- mHiddenPlayTime.Start();
+ HiddenVideoStart();
}
if (mDecoder && !IsBeingDestroyed()) {
mDecoder->NotifyOwnerActivityChanged(visible);
}
bool pauseElement = !IsActive();
SuspendOrResumeElement(pauseElement, !IsActive());
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1120,16 +1120,33 @@ protected:
// Return true if decoding should be paused
virtual bool GetPaused() final override
{
bool isPaused = false;
GetPaused(&isPaused);
return isPaused;
}
+ /**
+ * Video has been playing while hidden and, if feature was enabled, would
+ * trigger suspending decoder.
+ * Used to track hidden-video-decode-suspend telemetry.
+ */
+ static void VideoDecodeSuspendTimerCallback(nsITimer* aTimer, void* aClosure);
+ /**
+ * Video is now both: playing and hidden.
+ * Used to track hidden-video telemetry.
+ */
+ void HiddenVideoStart();
+ /**
+ * Video is not playing anymore and/or has become visible.
+ * Used to track hidden-video telemetry.
+ */
+ void HiddenVideoStop();
+
#ifdef MOZ_EME
void ReportEMETelemetry();
#endif
void ReportTelemetry();
// Check the permissions for audiochannel.
bool CheckAudioChannelPermissions(const nsAString& aType);
@@ -1370,19 +1387,22 @@ protected:
// Reference to the source element last returned by GetNextSource().
// This is the child source element which we're trying to load from.
nsCOMPtr<nsIContent> mSourceLoadCandidate;
// Range of time played.
RefPtr<TimeRanges> mPlayed;
- // Timer used for updating progress events
+ // Timer used for updating progress events.
nsCOMPtr<nsITimer> mProgressTimer;
+ // Timer used to simulate video-suspend.
+ nsCOMPtr<nsITimer> mVideoDecodeSuspendTimer;
+
#ifdef MOZ_EME
// Encrypted Media Extension media keys.
RefPtr<MediaKeys> mMediaKeys;
#endif
// Stores the time at the start of the current 'played' range.
double mCurrentPlayRangeStart;
@@ -1631,16 +1651,19 @@ public:
};
private:
// Total time a video has spent playing.
TimeDurationAccumulator mPlayTime;
// Total time a video has spent playing while hidden.
TimeDurationAccumulator mHiddenPlayTime;
+ // Total time a video has (or would have) spent in video-decode-suspend mode.
+ TimeDurationAccumulator mVideoDecodeSuspendTime;
+
// Indicates if user has interacted with the element.
// Used to block autoplay when disabled.
bool mHasUserInteraction;
// True if the first frame has been successfully loaded.
bool mFirstFrameLoaded;
// Media elements also have a default playback start position, which must