diff options
Diffstat (limited to 'dom/media/test/chrome')
-rw-r--r-- | dom/media/test/chrome/chrome.toml | 12 | ||||
-rw-r--r-- | dom/media/test/chrome/test_accumulated_play_time.html | 694 | ||||
-rw-r--r-- | dom/media/test/chrome/test_telemetry_source_buffer_type.html | 105 |
3 files changed, 811 insertions, 0 deletions
diff --git a/dom/media/test/chrome/chrome.toml b/dom/media/test/chrome/chrome.toml new file mode 100644 index 0000000000..57d1843841 --- /dev/null +++ b/dom/media/test/chrome/chrome.toml @@ -0,0 +1,12 @@ +[DEFAULT] +subsuite = "media" +support-files = [ + "../gizmo.mp4", + "../gizmo-noaudio.mp4", + "../TestPatternHDR.mp4", + "../tone2s-silence4s-tone2s.opus", +] + +["test_accumulated_play_time.html"] + +["test_telemetry_source_buffer_type.html"] diff --git a/dom/media/test/chrome/test_accumulated_play_time.html b/dom/media/test/chrome/test_accumulated_play_time.html new file mode 100644 index 0000000000..e59c0377b2 --- /dev/null +++ b/dom/media/test/chrome/test_accumulated_play_time.html @@ -0,0 +1,694 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Test Video Play Time Related Permanent Telemetry Probes</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<script type="application/javascript"> + +/** + * This test is used to ensure that we accumulate time for video playback + * correctly, and the results would be used in Telemetry probes. + * Currently this test covers following probes + * - VIDEO_PLAY_TIME_MS + * - VIDEO_HDR_PLAY_TIME_MS + * - VIDEO_HIDDEN_PLAY_TIME_MS + * - VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE + * - VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE + * - VIDEO_VISIBLE_PLAY_TIME_MS + * - MEDIA_PLAY_TIME_MS + * - MUTED_PLAY_TIME_PERCENT + * - AUDIBLE_PLAY_TIME_PERCENT + */ +const videoHistNames = [ + "VIDEO_PLAY_TIME_MS", + "VIDEO_HIDDEN_PLAY_TIME_MS" +]; +const videoHDRHistNames = [ + "VIDEO_HDR_PLAY_TIME_MS" +]; +const videoKeyedHistNames = [ + "VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE", + "VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE", + "VIDEO_VISIBLE_PLAY_TIME_MS" +]; +const audioKeyedHistNames = [ + "MUTED_PLAY_TIME_PERCENT", + "AUDIBLE_PLAY_TIME_PERCENT" +]; + +add_task(async function setTestPref() { + await SpecialPowers.pushPrefEnv({ + set: [["media.testing-only-events", true], + ["media.test.video-suspend", true], + ["media.suspend-background-video.enabled", true], + ["media.suspend-background-video.delay-ms", 0], + ["dom.media.silence_duration_for_audibility", 0.1] + ]}); +}); + +add_task(async function testTotalPlayTime() { + 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, "totalVideoPlayTime", 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, "totalVideoPlayTime"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime"); + + info(`should not accumulate time for paused video`); + video.pause(); + await once(video, "moztotalplaytimepaused"); + assertValueKeptUnchanged(videoChrome, "totalVideoPlayTime"); + assertValueEqualTo(videoChrome, "totalVideoPlayTime", 0); + + info(`should start accumulating time again`); + let rv = await Promise.all([ + onceWithTrueReturn(video, "moztotalplaytimestarted"), + video.play().then(_ => true, _ => false), + ]); + ok(returnTrueWhenAllValuesAreTrue(rv), "video started again"); + await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); + await cleanUpMediaAndCheckTelemetry(video); +}); + +// The testHDRPlayTime task will only pass on platforms that accurately report +// color depth in their VideoInfo structures. Presently, that is only true for +// macOS. +const {AppConstants} = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const reportsColorDepthFromVideoData = (AppConstants.platform == "macosx"); +if (reportsColorDepthFromVideoData) { + add_task(async function testHDRPlayTime() { + // This task is different from the others because the HTMLMediaElement does + // not expose a chrome property for video hdr play time. But we do capture + // telemety for VIDEO_HDR_PLAY_TIME_MS. To ensure that this telemetry is + // generated, this task follows the same structure as the other tasks, but + // doesn't actually check the properties of the video player, other than to + // confirm that video has played for at least some time. + const video = document.createElement('video'); + video.src = "TestPatternHDR.mp4"; // This is an HDR video with no audio. + document.body.appendChild(video); + + info(`load the HDR video`); + const videoChrome = SpecialPowers.wrap(video); + await new Promise(r => video.onloadeddata = r); + + info(`start accumulating play time after media starts`); + video.autoplay = true; + await Promise.all([ + once(video, "playing"), + once(video, "moztotalplaytimestarted"), + ]); + // Check that we have at least some video play time, because the + // HDR play time telemetry is emitted by the same process. + await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); + await cleanUpMediaAndCheckTelemetry(video, {hasVideo: true, hasAudio: false, hasVideoHDR: true}); + }); +} + +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, "totalVideoPlayTime", 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, "totalVideoPlayTime"); + await assertValueConstantlyIncreases(videoChrome, "visiblePlayTime"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + + info(`make video invisible`); + video.style.display = "none"; + await once(video, "mozinvisibleplaytimestarted"); + await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); + await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); + assertValueKeptUnchanged(videoChrome, "visiblePlayTime"); + await cleanUpMediaAndCheckTelemetry(video); +}); + +add_task(async function testAudibleAudioPlayTime() { + const audio = document.createElement('audio'); + audio.src = "tone2s-silence4s-tone2s.opus"; + audio.controls = true; + audio.loop = true; + document.body.appendChild(audio); + + info(`all accumulated time should be zero`); + const audioChrome = SpecialPowers.wrap(audio); + await new Promise(r => audio.onloadeddata = r); + assertValueEqualTo(audioChrome, "totalVideoPlayTime", 0); + assertValueEqualTo(audioChrome, "totalAudioPlayTime", 0); + assertValueEqualTo(audioChrome, "mutedPlayTime", 0); + assertValueEqualTo(audioChrome, "audiblePlayTime", 0); + + info(`start accumulating play time after media starts`); + await Promise.all([ + audio.play(), + once(audio, "moztotalplaytimestarted"), + ]); + await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); + assertValueKeptUnchanged(audioChrome, "mutedPlayTime"); + assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); + + info(`audio becomes inaudible for 4s`); + await once(audio, "mozinaudibleaudioplaytimestarted"); + + await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); + assertValueKeptUnchanged(audioChrome, "audiblePlayTime"); + assertValueKeptUnchanged(audioChrome, "mutedPlayTime"); + assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); + + info(`audio becomes audible after 4s`); + await once(audio, "mozinaudibleaudioplaytimepaused"); + + await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); + assertValueKeptUnchanged(audioChrome, "mutedPlayTime"); + assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); + + await cleanUpMediaAndCheckTelemetry(audio, {hasVideo: false}); +}); + +add_task(async function testHiddenPlayTime() { + const invisibleReasons = ["notInTree", "notInConnectedTree", "invisibleInDisplay"]; + for (let reason of invisibleReasons) { + const video = document.createElement('video'); + video.src = "gizmo.mp4"; + video.loop = true; + info(`invisible video due to '${reason}'`); + + if (reason == "notInConnectedTree") { + let disconnected = document.createElement("div") + disconnected.appendChild(video); + } else if (reason == "invisibleInDisplay") { + document.body.appendChild(video); + video.style.display = "none"; + } else if (reason == "notInTree") { + // video is already created in the `notInTree` situation. + } else { + ok(false, "undefined reason"); + } + + info(`start invisible video should start accumulating timers`); + const videoChrome = SpecialPowers.wrap(video); + let rv = await Promise.all([ + onceWithTrueReturn(video, "mozinvisibleplaytimestarted"), + video.play().then(_ => true, _ => false), + ]); + ok(returnTrueWhenAllValuesAreTrue(rv), "video started playing"); + await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); + + info(`should not accumulate time for paused video`); + video.pause(); + await once(video, "mozinvisibleplaytimepaused"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + + info(`should start accumulating time again`); + rv = await Promise.all([ + onceWithTrueReturn(video, "mozinvisibleplaytimestarted"), + video.play().then(_ => true, _ => false), + ]); + ok(returnTrueWhenAllValuesAreTrue(rv), "video started again"); + await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); + + info(`make video visible should stop accumulating invisible related time`); + if (reason == "notInTree" || reason == "notInConnectedTree") { + document.body.appendChild(video); + } else if (reason == "invisibleInDisplay") { + video.style.display = "block"; + } else { + ok(false, "undefined reason"); + } + await once(video, "mozinvisibleplaytimepaused"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + await cleanUpMediaAndCheckTelemetry(video); + } +}); + +add_task(async function testAudioProbesWithoutAudio() { + const video = document.createElement('video'); + video.src = "gizmo-noaudio.mp4"; + video.loop = true; + 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, "totalVideoPlayTime", 0); + assertValueEqualTo(videoChrome, "totalAudioPlayTime", 0); + assertValueEqualTo(videoChrome, "mutedPlayTime", 0); + assertValueEqualTo(videoChrome, "audiblePlayTime", 0); + + info(`start accumulating play time after media starts`); + await Promise.all([ + video.play(), + once(video, "moztotalplaytimestarted"), + ]); + + async function checkInvariants() { + await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); + assertValueKeptUnchanged(videoChrome, "audiblePlayTime"); + assertValueKeptUnchanged(videoChrome, "mutedPlayTime"); + assertValueKeptUnchanged(videoChrome, "totalAudioPlayTime"); + } + + checkInvariants(); + + video.muted = true; + + checkInvariants(); + + video.currentTime = 0.0; + await once(video, "seeked"); + + checkInvariants(); + + video.muted = false; + + checkInvariants(); + + video.volume = 0.0; + + checkInvariants(); + + video.volume = 1.0; + + checkInvariants(); + + video.muted = true; + + checkInvariants(); + + video.currentTime = 0.0; + + checkInvariants(); + + await cleanUpMediaAndCheckTelemetry(video, {hasAudio: false}); +}); + +add_task(async function testMutedAudioPlayTime() { + const audio = document.createElement('audio'); + audio.src = "gizmo.mp4"; + audio.controls = true; + audio.loop = true; + document.body.appendChild(audio); + + info(`all accumulated time should be zero`); + const audioChrome = SpecialPowers.wrap(audio); + await new Promise(r => audio.onloadeddata = r); + assertValueEqualTo(audioChrome, "totalVideoPlayTime", 0); + assertValueEqualTo(audioChrome, "totalAudioPlayTime", 0); + assertValueEqualTo(audioChrome, "mutedPlayTime", 0); + assertValueEqualTo(audioChrome, "audiblePlayTime", 0); + + info(`start accumulating play time after media starts`); + await Promise.all([ + audio.play(), + once(audio, "moztotalplaytimestarted"), + ]); + await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); + assertValueKeptUnchanged(audioChrome, "mutedPlayTime"); + assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); + + audio.muted = true; + await once(audio, "mozmutedaudioplaytimestarted"); + + await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); + assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); + + audio.currentTime = 0.0; + await once(audio, "seeked"); + + await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); + assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); + + audio.muted = false; + await once(audio, "mozmutedeaudioplaytimepaused"); + + await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); + assertValueKeptUnchanged(audioChrome, "mutedPlayTime"); + assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); + + audio.volume = 0.0; + await once(audio, "mozmutedaudioplaytimestarted"); + + await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); + assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); + + audio.volume = 1.0; + await once(audio, "mozmutedeaudioplaytimepaused"); + + await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); + assertValueKeptUnchanged(audioChrome, "mutedPlayTime"); + assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); + + audio.muted = true; + await once(audio, "mozmutedaudioplaytimestarted"); + + await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); + assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); + + audio.currentTime = 0.0; + + await assertValueConstantlyIncreases(audioChrome, "totalAudioPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "mutedPlayTime"); + await assertValueConstantlyIncreases(audioChrome, "audiblePlayTime"); + assertValueKeptUnchanged(audioChrome, "totalVideoPlayTime"); + + // The media has a video track, but it's being played back in an + // HTMLAudioElement, without video frame location. + await cleanUpMediaAndCheckTelemetry(audio, {hasVideo: false}); +}); + +// Note that video suspended time is not always align with the invisible play +// time even if `media.suspend-background-video.delay-ms` is `0`, because not all +// invisible videos would be suspended under current strategy. +add_task(async function testDecodeSuspendedTime() { + const video = document.createElement('video'); + video.src = "gizmo.mp4"; + video.loop = true; + document.body.appendChild(video); + + info(`start video should start accumulating timers`); + const videoChrome = SpecialPowers.wrap(video); + let rv = await Promise.all([ + onceWithTrueReturn(video, "moztotalplaytimestarted"), + video.play().then(_ => true, _ => false), + ]); + ok(returnTrueWhenAllValuesAreTrue(rv), "video started playing"); + await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime"); + + info(`make it invisible and force to suspend decoding`); + video.setVisible(false); + await once(video, "mozvideodecodesuspendedstarted"); + await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); + await assertValueConstantlyIncreases(videoChrome, "invisiblePlayTime"); + await assertValueConstantlyIncreases(videoChrome, "videoDecodeSuspendedTime"); + + info(`make it visible and resume decoding`); + video.setVisible(true); + await Promise.all([ + once(video, "mozinvisibleplaytimepaused"), + once(video, "mozvideodecodesuspendedpaused"), + ]); + await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime"); + await cleanUpMediaAndCheckTelemetry(video); +}); + +add_task(async function reuseSameElementForPlayback() { + const video = document.createElement('video'); + video.src = "gizmo.mp4"; + document.body.appendChild(video); + + info(`start accumulating play time after media starts`); + const videoChrome = SpecialPowers.wrap(video); + let rv = await Promise.all([ + onceWithTrueReturn(video, "moztotalplaytimestarted"), + video.play().then(_ => true, _ => false), + ]); + ok(returnTrueWhenAllValuesAreTrue(rv), "video started again"); + await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); + + 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 + // the error. If the following step tries to set the new resource and `play()` + // , then they should be done after receving the `error` from that failed load + // first. + await Promise.all([ + once(video, "error"), + cleanUpMediaAndCheckTelemetry(video), + ]); + // video doesn't have a decoder, so the return value would be -1 (error). + assertValueEqualTo(videoChrome, "totalVideoPlayTime", -1); + assertValueEqualTo(videoChrome, "invisiblePlayTime", -1); + + info(`resue same element, make it visible and start playback again`); + video.src = "gizmo.mp4"; + rv = await Promise.all([ + onceWithTrueReturn(video, "moztotalplaytimestarted"), + video.play().then(_ => true, _ => false), + ]); + ok(returnTrueWhenAllValuesAreTrue(rv), "video started"); + await assertValueConstantlyIncreases(videoChrome, "totalVideoPlayTime"); + await cleanUpMediaAndCheckTelemetry(video); +}); + +add_task(async function testNoReportedTelemetryResult() { + info(`No result for empty video`); + const video = document.createElement('video'); + assertAllProbeRelatedAttributesKeptUnchanged(video); + await assertNoReportedTelemetryResult(video); + + info(`No result for video which hasn't started playing`); + video.src = "gizmo.mp4"; + document.body.appendChild(video); + ok(await once(video, "loadeddata").then(_ => true), "video loaded data"); + assertAllProbeRelatedAttributesKeptUnchanged(video); + await assertNoReportedTelemetryResult(video); + + info(`No result for video with error`); + video.src = "filedoesnotexist.mp4"; + ok(await video.play().then(_ => false, _ => true), "video failed to play"); + ok(video.error != undefined, "video got error"); + assertAllProbeRelatedAttributesKeptUnchanged(video); + await assertNoReportedTelemetryResult(video); +}); + +/** + * Following are helper functions + */ +async function cleanUpMediaAndCheckTelemetry(media, { reportExpected = true, hasVideo = true, hasAudio = true, hasVideoHDR = false } = {}) { + media.src = ""; + await checkReportedTelemetry(media, reportExpected, hasVideo, hasAudio, hasVideoHDR); +} + +async function assertNoReportedTelemetryResult(media) { + await checkReportedTelemetry(media, false, true, true); +} + +async function checkReportedTelemetry(media, reportExpected, hasVideo, hasAudio, hasVideoHDR) { + const reportResultPromise = once(media, "mozreportedtelemetry"); + info(`check telemetry result, reportExpected=${reportExpected}`); + if (reportExpected) { + await reportResultPromise; + } + for (const name of videoHistNames) { + try { + const hist = SpecialPowers.Services.telemetry.getHistogramById(name); + /** + * Histogram's snapshot looks like that + * { + * "bucket_count": X, + * "histogram_type": Y, + * "sum": Z, + * "range": [min, max], + * "values": { "value1" : "num1", "value2" : "num2", ...} + * } + */ + const entriesNums = Object.entries(hist.snapshot().values).length; + if (reportExpected && hasVideo) { + ok(entriesNums > 0, `Reported result for ${name}`); + } else { + ok(entriesNums == 0, `Reported nothing for ${name}`); + } + hist.clear(); + } catch (e) { + ok(false , `histogram '${name}' doesn't exist`); + } + } + // videoHDRHistNames are checked for total time, not for number of samples. + for (const name of videoHDRHistNames) { + try { + const hist = SpecialPowers.Services.telemetry.getHistogramById(name); + const totalTimeMS = hist.snapshot().sum; + if (reportExpected && hasVideoHDR) { + ok(totalTimeMS > 0, `Reported some time for ${name}`); + } else { + ok(totalTimeMS == 0, `Reported no time for ${name}`); + } + hist.clear(); + } catch (e) { + ok(false , `histogram '${name}' doesn't exist`); + } + } + for (const name of videoKeyedHistNames) { + try { + const hist = SpecialPowers.Services.telemetry.getKeyedHistogramById(name); + /** + * Keyed Histogram's snapshot looks like that + * { + * "Key1" : { + * "bucket_count": X, + * "histogram_type": Y, + * "sum": Z, + * "range": [min, max], + * "values": { "value1" : "num1", "value2" : "num2", ...} + * }, + * "Key2" : {...}, + * } + */ + const items = Object.entries(hist.snapshot()); + if (items.length) { + for (const [key, value] of items) { + const entriesNums = Object.entries(value.values).length; + ok(reportExpected && entriesNums > 0, `Reported ${key} for ${name}`); + } + } else if (reportExpected) { + ok(!hasVideo, `No video telemetry reported but no video track in the media`); + } else { + ok(true, `No video telemetry expected, none reported`); + } + // Avoid to pollute next test task. + hist.clear(); + } catch (e) { + ok(false , `keyed histogram '${name}' doesn't exist`); + } + } + + // In any case, the combined probe MEDIA_PLAY_TIME_MS should be reported, if + // expected + { + const hist = + SpecialPowers.Services.telemetry.getKeyedHistogramById("MEDIA_PLAY_TIME_MS"); + const items = Object.entries(hist.snapshot()); + if (items.length) { + for (const item of items) { + ok(item[0].includes("V") != -1 || !hasVideo, "Video time is reported if video was present"); + } + hist.clear(); + } else { + ok(!reportExpected, "MEDIA_PLAY_TIME_MS should always be reported if a report is expected"); + } + } + + for (const name of audioKeyedHistNames) { + try { + const hist = SpecialPowers.Services.telemetry.getKeyedHistogramById(name); + const items = Object.entries(hist.snapshot()); + if (items.length) { + for (const [key, value] of items) { + const entriesNums = Object.entries(value.values).length; + ok(reportExpected && entriesNums > 0, `Reported ${key} for ${name}`); + } + } else { + ok(!reportExpected || !hasAudio, `No audio telemetry expected, none reported`); + } + // Avoid to pollute next test task. + hist.clear(); + } catch (e) { + ok(false , `keyed histogram '${name}' doesn't exist`); + } + } +} + +function once(target, name) { + return new Promise(r => target.addEventListener(name, r, { once: true })); +} + +function onceWithTrueReturn(target, name) { + return once(target, name).then(_ => true); +} + +function returnTrueWhenAllValuesAreTrue(arr) { + for (let val of arr) { + if (!val) { + return false; + } + } + return true; +} + +// Block the main thread for a number of milliseconds +function blockMainThread(durationMS) { + const start = Date.now(); + while (Date.now() - start < durationMS) { /* spin */ } +} + +// Allows comparing two values from the system clocks that are not gathered +// atomically. Allow up to 1ms of fuzzing when lhs and rhs are seconds. +function timeFuzzyEquals(lhs, rhs, str) { + ok(Math.abs(lhs - rhs) < 1e-3, str); +} + +function assertAttributeDefined(mediaChrome, checkType) { + ok(mediaChrome[checkType] != undefined, `${checkType} exists`); +} + +function assertValueEqualTo(mediaChrome, checkType, expectedValue) { + assertAttributeDefined(mediaChrome, checkType); + is(mediaChrome[checkType], expectedValue, `${checkType} equals to ${expectedValue}`); +} + +async function assertValueConstantlyIncreases(mediaChrome, checkType) { + assertAttributeDefined(mediaChrome, checkType); + const valueSnapshot = mediaChrome[checkType]; + // 30ms is long enough to have a low-resolution system clock tick, but short + // enough to not slow the test down. + blockMainThread(30); + const current = mediaChrome[checkType]; + ok(current > valueSnapshot, `${checkType} keeps increasing (${current} > ${valueSnapshot})`); +} + +function assertValueKeptUnchanged(mediaChrome, checkType) { + assertAttributeDefined(mediaChrome, checkType); + const valueSnapshot = mediaChrome[checkType]; + // 30ms is long enough to have a low-resolution system clock tick, but short + // enough to not slow the test down. + blockMainThread(30); + const newValue = mediaChrome[checkType]; + timeFuzzyEquals(newValue, valueSnapshot, `${checkType} keeps unchanged (${newValue} vs. ${valueSnapshot})`); +} + +function assertAllProbeRelatedAttributesKeptUnchanged(video) { + const videoChrome = SpecialPowers.wrap(video); + assertValueKeptUnchanged(videoChrome, "totalVideoPlayTime"); + assertValueKeptUnchanged(videoChrome, "invisiblePlayTime"); + assertValueKeptUnchanged(videoChrome, "videoDecodeSuspendedTime"); +} + +</script> +</head> +<body> +</body> +</html> diff --git a/dom/media/test/chrome/test_telemetry_source_buffer_type.html b/dom/media/test/chrome/test_telemetry_source_buffer_type.html new file mode 100644 index 0000000000..85ccfd1000 --- /dev/null +++ b/dom/media/test/chrome/test_telemetry_source_buffer_type.html @@ -0,0 +1,105 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Telemetry : test permanent probe MSE_SOURCE_BUFFER_TYPE</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<script type="application/javascript"> + +/** + * This test is used for the permanent Telemetry probe MSE_SOURCE_BUFFER_TYPE. + * which is used to measure the usage of mime type used in MSE. + */ +const VIDEO_HEVC = "video/hevc"; +const VIDEO_WEBM = "video/webm"; +const AUDIO_WEBM = "audio/webm"; +const VIDEO_MP4 = "video/mp4"; +const AUDIO_MP4 = "audio/mp4"; +const VIDEO_MP2T = "video/mp2t"; +const AUDIO_MP2T = "audio/mp2t"; +const AUDIO_MP3 = "audio/mpeg"; +const AUDIO_AAC = "audio/aac"; + +// The order follows `MSE_SOURCE_BUFFER_TYPE` in `Histogram.json`. +const gLabelNames = [VIDEO_HEVC, VIDEO_WEBM, AUDIO_WEBM, VIDEO_MP4, AUDIO_MP4, + VIDEO_MP2T, AUDIO_MP2T, AUDIO_MP3, AUDIO_AAC]; + +add_task(async function setTestPref() { + await SpecialPowers.pushPrefEnv({ + set: [["media.testing-only-events", true]]}); +}); + +add_task(async function testSourceBufferTypeProbe() { + for (let label of gLabelNames) { + // HEVC will be tested in another task. + if (label == VIDEO_HEVC) { + continue; + } + info(`calling 'isTypeSupported()' should increase the probe value`); + MediaSource.isTypeSupported(label); + assertLabelValueEqualTo(label, 1); + + info(`calling 'addSourceBuffer()' should increase the probe value`); + try { + const source = new MediaSource(); + source.addSourceBuffer(label); + } catch (e) { + info(`ignore error for unsupported type ${label}`); + } + assertLabelValueEqualTo(label, 2); + } +}); + +add_task(async function testSourceBufferHEVCTypeProbe() { + // These are all possible mime types for HEVC + const mimeTypes = [ + "video/mp4; codecs=\"hvc1\"", + "video/mp4; codecs=\"hev1\"", + "video/mp4; codecs=\"hvc1.1.6.L93.B0\"", + "video/mp4; codecs=\"hev1.1.6.L93.B0\"", + ]; + for (let idx = 0; idx < mimeTypes.length; idx++) { + const mimeType = mimeTypes[idx]; + info(`calling 'isTypeSupported()' should increase the probe value`); + MediaSource.isTypeSupported(mimeType); + assertLabelValueEqualTo(VIDEO_HEVC, idx * 2 + 1); + + info(`calling 'addSourceBuffer()' should increase the probe value`); + try { + const source = new MediaSource(); + source.addSourceBuffer(mimeType); + } catch (e) { + info(`ignore error for unsupported type ${mimeType}`); + } + assertLabelValueEqualTo(VIDEO_HEVC, idx * 2 + 2); + } +}); + +/** + * Following are helper functions + */ +async function assertLabelValueEqualTo(labelName, value) { + const hist = SpecialPowers.Services.telemetry.getHistogramById("MSE_SOURCE_BUFFER_TYPE"); + /** + * Histogram's snapshot looks like that + * { + * "bucket_count": X, + * "histogram_type": Y, + * "sum": Z, + * "range": [min, max], + * "values": { "value1" : "num1", "value2" : "num2", ...} + * } + */ + if (!gLabelNames.includes(labelName)) { + ok(false, `undefined label name=${labelName}`); + return; + } + const labelIdx = gLabelNames.indexOf(labelName); + is(hist.snapshot().values[labelIdx], value, `${labelName} equal to ${value}`); +} + +</script> +</head> +<body> +</body> +</html> |