diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index 3a74ac3b477f..3dd22c325cc2 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -2085,6 +2085,10 @@ double HTMLMediaElement::TotalPlayTime() const { return mDecoder ? mDecoder->GetTotalPlayTimeInSeconds() : -1.0; } +double HTMLMediaElement::VisiblePlayTime() const { + return mDecoder ? mDecoder->GetVisibleVideoPlayTimeInSeconds() : -1.0; +} + double HTMLMediaElement::InvisiblePlayTime() const { return mDecoder ? mDecoder->GetInvisibleVideoPlayTimeInSeconds() : -1.0; } diff --git a/dom/html/HTMLMediaElement.h b/dom/html/HTMLMediaElement.h index 159135549381..fd80f4e202d4 100644 --- a/dom/html/HTMLMediaElement.h +++ b/dom/html/HTMLMediaElement.h @@ -656,6 +656,7 @@ class HTMLMediaElement : public nsGenericHTMLElement, // These functions return accumulated time, which are used for the telemetry // usage. Return -1 for error. double TotalPlayTime() const; + double VisiblePlayTime() const; double InvisiblePlayTime() const; double VideoDecodeSuspendedTime() const; diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index 9c93a76be1f7..89aa03a0d803 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -1400,6 +1400,10 @@ double MediaDecoder::GetTotalPlayTimeInSeconds() const { return mTelemetryProbesReporter->GetTotalPlayTimeInSeconds(); } +double MediaDecoder::GetVisibleVideoPlayTimeInSeconds() const { + return mTelemetryProbesReporter->GetVisibleVideoPlayTimeInSeconds(); +} + double MediaDecoder::GetInvisibleVideoPlayTimeInSeconds() const { return mTelemetryProbesReporter->GetInvisibleVideoPlayTimeInSeconds(); } diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h index 321e7a9cade9..24b02818ab87 100644 --- a/dom/media/MediaDecoder.h +++ b/dom/media/MediaDecoder.h @@ -720,6 +720,7 @@ class MediaDecoder : public DecoderDoctorLifeLogger { // They are used for reporting telemetry related results. double GetTotalPlayTimeInSeconds() const; + double GetVisibleVideoPlayTimeInSeconds() const; double GetInvisibleVideoPlayTimeInSeconds() const; double GetVideoDecodeSuspendedTimeInSeconds() const; diff --git a/dom/media/test/chrome/test_accumulated_play_time.html b/dom/media/test/chrome/test_accumulated_play_time.html index 509ebd5a07dc..70c14e502e23 100644 --- a/dom/media/test/chrome/test_accumulated_play_time.html +++ b/dom/media/test/chrome/test_accumulated_play_time.html @@ -14,9 +14,10 @@ * - VIDEO_HIDDEN_PLAY_TIME_MS * - VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE * - VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE + * - VIDEO_VISIBLE_PLAY_TIME_MS */ const histNames = ["VIDEO_PLAY_TIME_MS", "VIDEO_HIDDEN_PLAY_TIME_MS"]; -const keyedHistNames = ["VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE", "VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE"]; +const keyedHistNames = ["VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE", "VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE", "VIDEO_VISIBLE_PLAY_TIME_MS"]; add_task(async function setTestPref() { await SpecialPowers.pushPrefEnv({ @@ -36,7 +37,6 @@ add_task(async function testTotalPlayTime() { await new Promise(r => video.onloadeddata = r); assertValueEqualTo(videoChrome, "totalPlayTime", 0); assertValueEqualTo(videoChrome, "invisiblePlayTime", 0); - assertValueEqualTo(videoChrome, "videoDecodeSuspendedTime", 0); info(`start accumulating play time after media starts`); video.autoplay = true; @@ -44,7 +44,7 @@ add_task(async function testTotalPlayTime() { once(video, "playing"), once(video, "moztotalplaytimestarted"), ]); - assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + await assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime"); @@ -52,6 +52,7 @@ add_task(async function testTotalPlayTime() { video.pause(); await once(video, "moztotalplaytimepaused"); assertValueKeptUnchanged(videoChrome, "totalPlayTime"); + assertValueEqualTo(videoChrome, "totalPlayTime", 0); info(`should start accumulating time again`); let rv = await Promise.all([ @@ -59,7 +60,37 @@ add_task(async function testTotalPlayTime() { video.play().then(_ => true, _ => false), ]); ok(returnTrueWhenAllValuesAreTrue(rv), "video started again"); - assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + await assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + await cleanUpMediaAndCheckTelemetry(video); +}); + +add_task(async function testVisiblePlayTime() { + const video = document.createElement('video'); + video.src = "gizmo.mp4"; + document.body.appendChild(video); + + info(`all accumulated time should be zero`); + const videoChrome = SpecialPowers.wrap(video); + await new Promise(r => video.onloadeddata = r); + assertValueEqualTo(videoChrome, "totalPlayTime", 0); + assertValueEqualTo(videoChrome, "visiblePlayTime", 0); + assertValueEqualTo(videoChrome, "invisiblePlayTime", 0); + + info(`start accumulating play time after media starts`); + video.autoplay = true; + await Promise.all([ + once(video, "playing"), + once(video, "moztotalplaytimestarted"), + ]); + await assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + await assertValueConstantlyIncreases(videoChrome, "visiblePlayTime"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + + info(`make video invisible`); + document.body.removeChild(video); + await assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); + assertValueKeptUnchanged(videoChrome, "visiblePlayTime"); await cleanUpMediaAndCheckTelemetry(video); }); @@ -90,7 +121,7 @@ add_task(async function testHiddenPlayTime() { video.play().then(_ => true, _ => false), ]); ok(returnTrueWhenAllValuesAreTrue(rv), "video started playing"); - assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); + await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); info(`should not accumulate time for paused video`); video.pause(); @@ -103,7 +134,7 @@ add_task(async function testHiddenPlayTime() { video.play().then(_ => true, _ => false), ]); ok(returnTrueWhenAllValuesAreTrue(rv), "video started again"); - assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); + await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); info(`make video visible should stop accumulating invisible related time`); if (reason == "notInTree" || reason == "notInConnectedTree") { @@ -138,16 +169,16 @@ add_task(async function testDecodeSuspendedTime() { video.play().then(_ => true, _ => false), ]); ok(returnTrueWhenAllValuesAreTrue(rv), "video started playing"); - assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + await assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime"); info(`make it invisible and force to suspend decoding`); video.setVisible(false); await once(video, "mozvideodecodesuspendedstarted"); - assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); - assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); - assertValueConstantlyIncreases(videoChrome, "videoDecodeSuspendedTime"); + await assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); + await assertValueConstantlyIncreases(videoChrome, "videoDecodeSuspendedTime"); info(`make it visible and resume decoding`); video.setVisible(true); @@ -155,7 +186,7 @@ add_task(async function testDecodeSuspendedTime() { once(video, "mozinvisibleplaytimepaused"), once(video, "mozvideodecodesuspendedpaused"), ]); - assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + await assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime"); await cleanUpMediaAndCheckTelemetry(video); @@ -173,7 +204,7 @@ add_task(async function reuseSameElementForPlayback() { video.play().then(_ => true, _ => false), ]); ok(returnTrueWhenAllValuesAreTrue(rv), "video started again"); - assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + await assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); info(`reset its src and all accumulated value should be reset after then`); // After setting its src to nothing, that would trigger a failed load and set @@ -195,7 +226,7 @@ add_task(async function reuseSameElementForPlayback() { video.play().then(_ => true, _ => false), ]); ok(returnTrueWhenAllValuesAreTrue(rv), "video started"); - assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); + await assertValueConstantlyIncreases(videoChrome, "totalPlayTime"); await cleanUpMediaAndCheckTelemetry(video); }); @@ -329,16 +360,19 @@ function assertValueEqualTo(mediaChrome, checkType, expectedValue) { is(mediaChrome[checkType], expectedValue, `${checkType} equals to ${expectedValue}`); } -function assertValueConstantlyIncreases(mediaChrome, checkType) { +async function assertValueConstantlyIncreases(mediaChrome, checkType) { assertAttributeDefined(mediaChrome, checkType); const valueSnapshot = mediaChrome[checkType]; - ok(mediaChrome[checkType] > valueSnapshot, `${checkType} keeps increasing`); + // Simply wait for a short while. + await new Promise(r => r()); + const current = mediaChrome[checkType]; + ok(current > valueSnapshot, `${checkType} keeps increasing (${current} > ${valueSnapshot})`); } function assertValueKeptUnchanged(mediaChrome, checkType) { assertAttributeDefined(mediaChrome, checkType); const valueSnapshot = mediaChrome[checkType]; - ok(mediaChrome[checkType] == valueSnapshot, `${checkType} keeps unchanged`); + is(mediaChrome[checkType], valueSnapshot, `${checkType} keeps unchanged (${valueSnapshot})`); } function assertAllProbeRelatedAttributesKeptUnchanged(video) { diff --git a/dom/media/utils/TelemetryProbesReporter.cpp b/dom/media/utils/TelemetryProbesReporter.cpp index 6f3e71eede99..99373b309575 100644 --- a/dom/media/utils/TelemetryProbesReporter.cpp +++ b/dom/media/utils/TelemetryProbesReporter.cpp @@ -310,6 +310,10 @@ double TelemetryProbesReporter::GetTotalPlayTimeInSeconds() const { return mTotalPlayTime.PeekTotal(); } +double TelemetryProbesReporter::GetVisibleVideoPlayTimeInSeconds() const { + return GetTotalPlayTimeInSeconds() - GetInvisibleVideoPlayTimeInSeconds(); +} + double TelemetryProbesReporter::GetInvisibleVideoPlayTimeInSeconds() const { return mInvisibleVideoPlayTime.PeekTotal(); } diff --git a/dom/media/utils/TelemetryProbesReporter.h b/dom/media/utils/TelemetryProbesReporter.h index 4c1451e6fb93..a50daaca6d39 100644 --- a/dom/media/utils/TelemetryProbesReporter.h +++ b/dom/media/utils/TelemetryProbesReporter.h @@ -45,6 +45,7 @@ class TelemetryProbesReporter final { void OnShutdown(); double GetTotalPlayTimeInSeconds() const; + double GetVisibleVideoPlayTimeInSeconds() const; double GetInvisibleVideoPlayTimeInSeconds() const; double GetVideoDecodeSuspendedTimeInSeconds() const; diff --git a/dom/webidl/HTMLMediaElement.webidl b/dom/webidl/HTMLMediaElement.webidl index d7219537b381..4c9f4f138fc9 100644 --- a/dom/webidl/HTMLMediaElement.webidl +++ b/dom/webidl/HTMLMediaElement.webidl @@ -231,6 +231,9 @@ partial interface HTMLMediaElement { [ChromeOnly] readonly attribute double totalPlayTime; + [ChromeOnly] + readonly attribute double visiblePlayTime; + [ChromeOnly] readonly attribute double invisiblePlayTime;