diff options
Diffstat (limited to 'testing/web-platform/tests/video-rvfc')
10 files changed, 698 insertions, 0 deletions
diff --git a/testing/web-platform/tests/video-rvfc/META.yml b/testing/web-platform/tests/video-rvfc/META.yml new file mode 100644 index 0000000000..5e581f4234 --- /dev/null +++ b/testing/web-platform/tests/video-rvfc/META.yml @@ -0,0 +1,3 @@ +spec: https://wicg.github.io/video-rvfc/ +suggested_reviewers: + - tguilbert diff --git a/testing/web-platform/tests/video-rvfc/README.md b/testing/web-platform/tests/video-rvfc/README.md new file mode 100644 index 0000000000..ab7a353051 --- /dev/null +++ b/testing/web-platform/tests/video-rvfc/README.md @@ -0,0 +1,7 @@ +# HTMLVideoElement.requestVideoFrameCallback specification Tests + +The HTMLVideoElement.requestVideoFrameCallback specification is available here: https://wicg.github.io/video-rvfc + +GitHub repository: https://github.com/WICG/video-rvfc + +File an issue: https://github.com/wicg/video-rvfc/issues/new diff --git a/testing/web-platform/tests/video-rvfc/idlharness.window.js b/testing/web-platform/tests/video-rvfc/idlharness.window.js new file mode 100644 index 0000000000..fa9e4e0988 --- /dev/null +++ b/testing/web-platform/tests/video-rvfc/idlharness.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: timeout=long + +'use strict'; + +idl_test( + ['video-rvfc'], + ['html', 'dom'], + idl_array => { + idl_array.add_objects({ + HTMLVideoElement: ['video'], + }); + self.video = document.createElement('video'); + } +); + diff --git a/testing/web-platform/tests/video-rvfc/request-video-frame-callback-before-xr-session.https.html b/testing/web-platform/tests/video-rvfc/request-video-frame-callback-before-xr-session.https.html new file mode 100644 index 0000000000..5277fbba92 --- /dev/null +++ b/testing/web-platform/tests/video-rvfc/request-video-frame-callback-before-xr-session.https.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html> +<title>Test that video.rVFC callbacks started before an XRSession work.</title> +<body> +</body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/media.js"></script> +<script src="../webxr/resources/webxr_util.js"></script> +<script src="../webxr/resources/webxr_test_constants.js"></script> +<script> + +// Start the video.rVFC callbacks before starting the XR Session. +let video = document.createElement('video'); +video.src = getVideoURI('/media/movie_5'); + +var numberVFCs = 0; +let videoCallback = () => { + numberVFCs++; + video.requestVideoFrameCallback(videoCallback); +} + +video.requestVideoFrameCallback(videoCallback); +video.play(); + +let testFunction = async function(session, fakeDeviceController, t) { + let watcherDone = new Event("watcherdone"); + let eventWatcher = new EventWatcher(t, session, ["end", "watcherdone"]); + let eventPromise = eventWatcher.wait_for(["end", "watcherdone"]); + + numberVFCs = 0; + + function onXRFrame(time, frame) { + if(numberVFCs >= 2) { + // Make sure video.rVFCs are still coming through before ending the + // session. + session.end(); + } + + session.requestAnimationFrame(onXRFrame); + } + + function onSessionEnd(event) { + // Make sure we are still getting rVFC callbacks after the session end. + numberVFCs = 0; + t.step_wait_func(() => numberVFCs >= 2, + () => session.dispatchEvent(watcherDone), + "Time out waiting for VFC callbacks"); + } + + session.addEventListener("end", onSessionEnd, false); + session.requestAnimationFrame(onXRFrame); + + return eventPromise; +} + +xr_session_promise_test('Make sure video.rVFC works during a non-immersive session', + testFunction, TRACKED_IMMERSIVE_DEVICE, 'inline'); + +video.currentTime = 0; + +xr_session_promise_test('Make sure video.rVFC works during an immersive session', + testFunction, TRACKED_IMMERSIVE_DEVICE, 'immersive-vr'); + +</script> +</html> diff --git a/testing/web-platform/tests/video-rvfc/request-video-frame-callback-dom.html b/testing/web-platform/tests/video-rvfc/request-video-frame-callback-dom.html new file mode 100644 index 0000000000..c1804b4edd --- /dev/null +++ b/testing/web-platform/tests/video-rvfc/request-video-frame-callback-dom.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<html> +<title>Test the video.requestVideoFrameCallback() API for non visible video elements.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/media.js"></script> +<body> +</body> +<script> +var testVideo = { + url: getVideoURI('/media/movie_5'), + height: 240, + width: 320, +} + +promise_test(async function(t) { + let done; + const promise = new Promise(resolve => done = resolve); + + let video = document.createElement('video'); + + video.requestVideoFrameCallback(done); + video.src = testVideo.url; + await video.play(); + + return promise; +}, 'Test a video outside of the DOM can still use video.rVFC.'); + +function rvfcStyleTest(applyStyle, description) { + promise_test(async function(t) { + let done; + const promise = new Promise(resolve => done = resolve); + + let video = document.createElement('video'); + document.body.appendChild(video); + applyStyle(video); + + video.requestVideoFrameCallback( + t.step_func( _ => { + // Make sure we can receive more than one callback. + video.requestVideoFrameCallback(done); + }) + ); + + video.src = testVideo.url; + await video.play(); + + return promise; + }, description); +} + +rvfcStyleTest((video) => { video.style.display = "none"}, + 'Test video.rVFC works with "display:none".'); + +rvfcStyleTest((video) => { video.style.visibility = "hidden"}, + 'Test video.rVFC works with "visibility:hidden".'); + +</script> +</html> diff --git a/testing/web-platform/tests/video-rvfc/request-video-frame-callback-during-xr-session.https.html b/testing/web-platform/tests/video-rvfc/request-video-frame-callback-during-xr-session.https.html new file mode 100644 index 0000000000..34561ee5fd --- /dev/null +++ b/testing/web-platform/tests/video-rvfc/request-video-frame-callback-during-xr-session.https.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<html> +<title>Test that video.rVFC callbacks started during an XRSession work.</title> +<body> + <canvas/> +</body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/media.js"></script> +<script src="../webxr/resources/webxr_util.js"></script> +<script src="../webxr/resources/webxr_test_constants.js"></script> +<script> + +let testFunction = async function(session, fakeDeviceController, t) { + let watcherDone = new Event("watcherdone"); + let eventWatcher = new EventWatcher(t, session, ["end", "watcherdone"]); + let eventPromise = eventWatcher.wait_for(["end", "watcherdone"]); + + // Start the video.rVFC callbacks while we are in the the XR Session. + let video = document.createElement('video'); + video.src = getVideoURI('/media/movie_5'); + + var numberVFCs = 0; + let videoCallback = () => { + numberVFCs++; + video.requestVideoFrameCallback(videoCallback); + } + + video.requestVideoFrameCallback(videoCallback); + video.play(); + + function onXRFrame(time, frame) { + if(numberVFCs >= 2) { + // Make sure video.rVFCs are coming through before ending the + // session. + session.end(); + } + + session.requestAnimationFrame(onXRFrame); + } + + function onSessionEnd(event) { + // Make sure we are still getting rVFC callbacks after the session end. + numberVFCs = 0; + t.step_wait_func(() => numberVFCs >= 2, + () => session.dispatchEvent(watcherDone), + "Time out waiting for VFC callbacks"); + } + + session.addEventListener("end", onSessionEnd, false); + session.requestAnimationFrame(onXRFrame); + + return eventPromise; +} + +xr_session_promise_test('Make sure video.rVFC callbacks started during an immersive session continue after it ends', + testFunction, TRACKED_IMMERSIVE_DEVICE, 'immersive-vr'); + +</script> +</html> diff --git a/testing/web-platform/tests/video-rvfc/request-video-frame-callback-parallel.html b/testing/web-platform/tests/video-rvfc/request-video-frame-callback-parallel.html new file mode 100644 index 0000000000..682fd0ac8f --- /dev/null +++ b/testing/web-platform/tests/video-rvfc/request-video-frame-callback-parallel.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<html> +<title>Test having multiple video.rVFC callbacks in flight for a single element.</title> +<body></body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/media.js"></script> +<script> + +promise_test(async function(t) { + let done; + const promise = new Promise(resolve => done = resolve); + + let video = document.createElement('video'); + document.body.appendChild(video); + + let firstTime; + let firstMetadata; + + video.requestVideoFrameCallback(t.step_func((time, metadata) => { + firstTime = time; + firstMetadata = metadata; + })); + + video.requestVideoFrameCallback(t.step_func((time, metadata) => { + assert_equals(firstTime, time); + assert_object_equals(firstMetadata, metadata); + done(); + })); + + video.src = getVideoURI('/media/movie_5'); + video.play(); + + return promise; +}, 'Test callbacks get the same information.'); + +promise_test(async function(t) { + let done; + const promise = new Promise(resolve => done = resolve); + + let video = document.createElement('video'); + document.body.appendChild(video); + + let secondCallbackId; + + video.requestVideoFrameCallback( + t.step_func(_ => { video.cancelVideoFrameCallback(secondCallbackId); }) + ); + + secondCallbackId = video.requestVideoFrameCallback( + t.step_func(_ => { + assert_unreached("Cancelled callbacks shouldn't be executed") + }) + ); + + // NOTE: This callback should be executed last. + video.requestVideoFrameCallback(done); + + video.src = getVideoURI('/media/movie_5'); + video.play(); + + return promise; +}, 'Test we can cancel callbacks from callbacks.'); +</script> +</html> diff --git a/testing/web-platform/tests/video-rvfc/request-video-frame-callback-repeating.html b/testing/web-platform/tests/video-rvfc/request-video-frame-callback-repeating.html new file mode 100644 index 0000000000..38e4abafd4 --- /dev/null +++ b/testing/web-platform/tests/video-rvfc/request-video-frame-callback-repeating.html @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<html> +<title>Test repeatedly chaining video.rVFC() callbacks.</title> +<body></body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/media.js"></script> +<script> + +promise_test(async function(t) { + let done; + const promise = new Promise(resolve => done = resolve); + + let video = document.createElement('video'); + document.body.appendChild(video); + + let firstTime; + video.requestVideoFrameCallback(t.step_func((time) => { + firstTime = time; + + // Queue up a callback and make sure it's not immediately executed. + let secondTime; + video.requestVideoFrameCallback(t.step_func((time) => { + secondTime = time; + assert_greater_than(secondTime, firstTime, "Callbacks should be executed on the next frame"); + })) + + // Queue up a second callback, and make sure it's called at the same time + // as the one we just queued up. + video.requestVideoFrameCallback(t.step_func((time) => { + assert_equals(time, secondTime, "Callbacks queued together should be called at the same time"); + done(); + })) + + })); + + video.src = getVideoURI('/media/movie_5'); + await video.play(); + + return promise; +}, 'Test new callbacks are only called on the next frame.'); + +promise_test(async function(t) { + let done; + const promise = new Promise(resolve => done = resolve); + + let video = document.createElement('video'); + document.body.appendChild(video); + + let maxNumberOfCalls = 10; + let currentCallNumber = 0; + let lastMetadata; + + function verifyMetadata(last, current) { + assert_greater_than(current.presentedFrames, last.presentedFrames, "presentedFrames should be monotonically increasing"); + assert_greater_than(current.presentationTime, last.presentationTime, "presentationTime should be monotonically increasing"); + assert_greater_than(current.expectedDisplayTime, last.expectedDisplayTime, "expectedDisplayTime should be monotonically increasing"); + + // We aren't seeking through the file, so this should be increasing from frame to frame. + assert_greater_than(current.mediaTime, last.mediaTime, "mediaTime should be increasing"); + + // The test video's size doesn't change. + assert_equals(current.width, last.width, "width should remain constant"); + assert_equals(current.height, last.height, "height should remain constant"); + } + + function repeatingCallback(time, metadata) { + // Skip the first call to verifyMetadata. + if (currentCallNumber) + verifyMetadata(lastMetadata, metadata) + + lastMetadata = metadata; + + if (++currentCallNumber > maxNumberOfCalls) { + done() + } else { + video.requestVideoFrameCallback(t.step_func(repeatingCallback)); + } + } + + video.requestVideoFrameCallback(t.step_func(repeatingCallback)); + + video.src = getVideoURI('/media/movie_5'); + await video.play(); + + return promise; +}, 'Test chaining calls to video.rVFC, and verify the required parameters.'); +</script> +</html> diff --git a/testing/web-platform/tests/video-rvfc/request-video-frame-callback-webrtc.https.html b/testing/web-platform/tests/video-rvfc/request-video-frame-callback-webrtc.https.html new file mode 100644 index 0000000000..dcf97e4ca9 --- /dev/null +++ b/testing/web-platform/tests/video-rvfc/request-video-frame-callback-webrtc.https.html @@ -0,0 +1,165 @@ +<!doctype html> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <title>WebRTC video.requestVideoFrameCallback() test</title> + <script src="/webrtc/RTCPeerConnection-helper.js"></script> +</head> +<body> + <div id="log"></div> + <div> + <video id="local-view" muted autoplay="autoplay"></video> + <video id="remote-view" muted autoplay="autoplay"/> + </video> + </div> + + <!-- These files are in place when executing on W3C. --> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script type="text/javascript"> + var test = async_test('Test video.requestVideoFrameCallback() parameters for WebRTC applications.'); + + // + // This test is based on /webrtc/simplecall.https.html, but it calls to + // video.requestVideoFrameCallback() before ending, to verify WebRTC required + // and optional parameters. + // + + var gFirstConnection = null; + var gSecondConnection = null; + var gCallbackCounter = 0; + var verify_params = (now, metadata) => { + gCallbackCounter = gCallbackCounter + 1; + assert_greater_than(now, 0); + + // Verify all required fields + assert_greater_than(metadata.presentationTime, 0); + assert_greater_than(metadata.expectedDisplayTime, 0); + assert_greater_than(metadata.presentedFrames, 0); + assert_greater_than(metadata.width, 0); + assert_greater_than(metadata.height, 0); + assert_true("mediaTime" in metadata, "mediaTime should be present"); + + // Verify WebRTC only fields. + assert_true("rtpTimestamp" in metadata, "rtpTimestamp should be present"); + assert_true("receiveTime" in metadata, "receiveTime should be present"); + // captureTime is not available until roundtrip time estimation is done. + if (gCallbackCounter > 60 || "captureTime" in metadata) { + assert_true("captureTime" in metadata, "captureTime should be present"); + test.done(); + } + else { + // Keep requesting callbacks. + document.getElementById('remote-view').requestVideoFrameCallback(test.step_func(verify_params)); + } + } + + var verify_local_metadata = (now, metadata) => { + assert_greater_than(metadata.expectedDisplayTime, 0); + assert_greater_than(metadata.presentedFrames, 0); + assert_greater_than(metadata.width, 0); + assert_greater_than(metadata.height, 0); + assert_true("captureTime" in metadata, "captureTime should always be present for local sources."); + assert_greater_than(metadata.captureTime, 0); + } + + // If the remote video gets video data that implies the negotiation + // as well as the ICE and DTLS connection are up. + document.getElementById('remote-view') + .addEventListener('loadedmetadata', function() { + document.getElementById('remote-view').requestVideoFrameCallback(test.step_func(verify_params)); + }); + + document.getElementById('local-view') + .addEventListener('loadmetadata', function() { + document.getElementById('local-view').requestVideoFrameCallback(test.step_func_done(verify_local_metadata)); + }); + + + function getNoiseStreamOkCallback(localStream) { + gFirstConnection = new RTCPeerConnection(null); + test.add_cleanup(() => gFirstConnection.close()); + gFirstConnection.onicecandidate = onIceCandidateToFirst; + + gSecondConnection = new RTCPeerConnection(null); + test.add_cleanup(() => gSecondConnection.close()); + gSecondConnection.onicecandidate = onIceCandidateToSecond; + gSecondConnection.ontrack = onRemoteTrack; + + localStream.getTracks().forEach(function(track) { + // Bidirectional streams are needed in order for captureTime to be + // populated. Use the same source in both directions. + gFirstConnection.addTrack(track, localStream); + gSecondConnection.addTrack(track, localStream); + }); + + gFirstConnection.createOffer().then(onOfferCreated, failed('createOffer')); + + var videoTag = document.getElementById('local-view'); + videoTag.srcObject = localStream; + }; + + var onOfferCreated = test.step_func(function(offer) { + gFirstConnection.setLocalDescription(offer); + + // This would normally go across the application's signaling solution. + // In our case, the "signaling" is to call this function. + receiveCall(offer.sdp); + }); + + function receiveCall(offerSdp) { + var parsedOffer = new RTCSessionDescription({ type: 'offer', + sdp: offerSdp }); + gSecondConnection.setRemoteDescription(parsedOffer); + + gSecondConnection.createAnswer().then(onAnswerCreated, + failed('createAnswer')); + }; + + var onAnswerCreated = test.step_func(function(answer) { + gSecondConnection.setLocalDescription(answer); + + // Similarly, this would go over the application's signaling solution. + handleAnswer(answer.sdp); + }); + + function handleAnswer(answerSdp) { + var parsedAnswer = new RTCSessionDescription({ type: 'answer', + sdp: answerSdp }); + gFirstConnection.setRemoteDescription(parsedAnswer); + }; + + var onIceCandidateToFirst = test.step_func(function(event) { + // If event.candidate is null = no more candidates. + if (event.candidate) { + gSecondConnection.addIceCandidate(event.candidate); + } + }); + + var onIceCandidateToSecond = test.step_func(function(event) { + if (event.candidate) { + gFirstConnection.addIceCandidate(event.candidate); + } + }); + + var onRemoteTrack = test.step_func(function(event) { + var videoTag = document.getElementById('remote-view'); + if (!videoTag.srcObject) { + videoTag.srcObject = event.streams[0]; + } + }); + + // Returns a suitable error callback. + function failed(function_name) { + return test.unreached_func('WebRTC called error callback for ' + function_name); + } + + // This function starts the test. + test.step(function() { + getNoiseStream({ video: true, audio: true }) + .then(test.step_func(getNoiseStreamOkCallback), failed('getNoiseStream')); + }); +</script> + +</body> +</html> diff --git a/testing/web-platform/tests/video-rvfc/request-video-frame-callback.html b/testing/web-platform/tests/video-rvfc/request-video-frame-callback.html new file mode 100644 index 0000000000..256216e8fc --- /dev/null +++ b/testing/web-platform/tests/video-rvfc/request-video-frame-callback.html @@ -0,0 +1,167 @@ +<!DOCTYPE html> +<html> +<title>Test the basics of the video.requestVideoFrameCallback() API.</title> +<body></body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/media.js"></script> +<script> +var testVideo = { + url: getVideoURI('/media/movie_5'), + height: 240, + width: 320, +} + +var altTestVideo = { + url: getVideoURI('/media/counting'), + height: 240, + width: 320, +} + +promise_test(async function(t) { + let done; + const promise = new Promise(resolve => done = resolve); + + let video = document.createElement('video'); + document.body.appendChild(video); + + let id = video.requestVideoFrameCallback( + t.step_func((time, metadata) => { + assert_true(time > 0); + assert_equals(metadata.height, testVideo.height); + assert_equals(metadata.width, testVideo.width); + done(); + }) + ); + + assert_true(id > 0); + + video.src = testVideo.url; + await video.play(); + + return promise; +}, 'Test we can register a video.rVFC callback.'); + +promise_test(async function(t) { + let done; + const promise = new Promise(resolve => done = resolve); + + let video = document.createElement('video'); + document.body.appendChild(video); + + video.requestVideoFrameCallback( + t.step_func(video_now => { + // Queue a call to window.rAF, and make sure it is executed within the + // same turn of the event loop (with the same 'time' parameter). + window.requestAnimationFrame( t.step_func( window_now => { + assert_equals(video_now, window_now); + done(); + })); + }) + ); + + video.src = testVideo.url; + await video.play(); + + return promise; +}, 'Test video.rVFC callbacks run before window.rAF callbacks.'); + + +promise_test(async function(t) { + let done; + const promise = new Promise(resolve => done = resolve); + + let video = document.createElement('video'); + document.body.appendChild(video); + + let id = video.requestVideoFrameCallback( + t.step_func(_ => { + assert_unreached("Cancelled callbacks shouldn't be executed") + }) + ); + + video.cancelVideoFrameCallback(id); + + video.requestVideoFrameCallback( + t.step_func(_ => { + // At this point, the other callback shouldn't have fired, but + // give it some more time and really make sure it doesn't, by going + // throught the event loop once more. + t.step_timeout(() => { done(); }, 0); + }) + ); + + video.src = testVideo.url; + await video.play(); + + return promise; +}, 'Test we can cancel a video.rVFC request.'); + +test(function(t) { + let video = document.createElement('video'); + document.body.appendChild(video); + + // requestVideoFrameCallback() expects 1 function as a parameter. + assert_throws_js(TypeError, _ => { video.requestVideoFrameCallback() } ); + assert_throws_js(TypeError, _ => { video.requestVideoFrameCallback(0) }); + assert_throws_js(TypeError, _ => { video.requestVideoFrameCallback("foo") }); + + // cancelVideoFrameCallback() expects 1 number as a parameter + assert_throws_js(TypeError, _ => { video.cancelVideoFrameCallback() } ); + + // Invalid calls are just no-ops + video.cancelVideoFrameCallback(_ => {}); + video.cancelVideoFrameCallback(NaN); + video.cancelVideoFrameCallback("foo"); + video.cancelVideoFrameCallback(12345); + video.cancelVideoFrameCallback(-1); + +}, 'Test invalid calls to the video.rVFC API.'); + +promise_test(async function(t) { + let video = document.createElement('video'); + video.autoplay = true; + document.body.appendChild(video); + + let first_width = 0; + let first_height = 0; + + video.src = testVideo.url; + + await video.play(); + + // Switch to and from a second video, and make sure we get rVFC calls at + // each step. + return new Promise((resolve, reject) => { + let onReturnToOriginalVideo = t.step_func((now, metadata) => { + assert_equals(first_width, metadata.width); + assert_equals(first_height, metadata.height); + + resolve(); + }); + + let onAltVideoFirstFrame = t.step_func((now, metadata) => { + // The videos have different sizes, and shouldn't match. + assert_not_equals(first_width, metadata.width); + assert_not_equals(first_height, metadata.height); + + // Swith back to the original video. + video.requestVideoFrameCallback(onReturnToOriginalVideo); + video.src = testVideo.url; + }); + + let onFirstFrame = t.step_func((now, metadata) => { + first_width = metadata.width; + first_height = metadata.height; + + // Callbacks should persist after changing the source to the alt video. + video.requestVideoFrameCallback(onAltVideoFirstFrame); + video.src = altTestVideo.url; + }) + + video.requestVideoFrameCallback(onFirstFrame); + }); +}, 'Test video.rVFC does not stop when switching sources.'); + +</script> +</html> |