<!DOCTYPE html>
<title>Test RTCEncodedVideoFrame.setMetadata in a loopback RTCPeerConnection</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/RTCPeerConnection-helper.js"></script>
<script>
"use strict";
async function setupLoopbackWithCodedAndGetStreams(t, codec) {
const caller = new RTCPeerConnection({encodedInsertableStreams:true});
t.add_cleanup(() => caller.close());
const callee = new RTCPeerConnection({encodedInsertableStreams:true});
t.add_cleanup(() => callee.close());
const stream = await navigator.mediaDevices.getUserMedia({video:true});
const videoTrack = stream.getVideoTracks()[0];
t.add_cleanup(() => videoTrack.stop());
const transceiver = caller.addTransceiver(videoTrack);
const codecCapability =
RTCRtpSender.getCapabilities('video').codecs.find(capability => {
return capability.mimeType.includes(codec);
});
assert_not_equals(codecCapability, undefined);
transceiver.setCodecPreferences([codecCapability]);
const senderStreams = transceiver.sender.createEncodedStreams();
exchangeIceCandidates(caller, callee);
const ontrackPromise = addEventListenerPromise(t, callee, 'track');
await exchangeOfferAnswer(caller, callee);
const trackEvent = await ontrackPromise;
const receiverTransceiver = trackEvent.transceiver;
const receiverStreams = receiverTransceiver.receiver.createEncodedStreams();
return {senderStreams, receiverStreams};
}
async function setupLoopbackWithCodecAndGetReader(t, codec) {
const {senderStreams, receiverStreams} = await setupLoopbackWithCodedAndGetStreams(t, codec);
receiverStreams.readable.pipeTo(receiverStreams.writable);
return senderStreams.readable.getReader();
}
promise_test(async t => {
const senderReader = await setupLoopbackWithCodecAndGetReader(t, 'VP8');
const result = await senderReader.read();
const metadata = result.value.getMetadata();
const clone = structuredClone(result.value);
metadata.frameId = 2;
metadata.dependencies = [1];
clone.setMetadata(metadata);
}, "setMetadata() does allow changes to frameId and dependencies without a flag");
promise_test(async t => {
const senderReader = await setupLoopbackWithCodecAndGetReader(t, 'VP8');
const result = await senderReader.read();
const field_values = {
'height':600,
'payloadType':17,
'spatialIndex':4,
'synchronizationSource':17,
'temporalIndex':5,
'contributingSources' : [13],
'width':800
}
for (const field in field_values) {
const metadata = result.value.getMetadata();
const clone = structuredClone(result.value);
metadata[field] = field_values[field];
assert_throws_dom('InvalidModificationError', () => clone.setMetadata(metadata), "For field \"" + field + "\",");
}
}, "setMetadata() does not allow changes to fields without a flag");
promise_test(async t => {
const senderReader = await setupLoopbackWithCodecAndGetReader(t, 'VP8');
const result = await senderReader.read();
const field_values = {
'contributingSources':[13],
'dependencies':[1],
'frameId':2,
'height':600,
'spatialIndex':4,
'synchronizationSource':1234567,
'temporalIndex':5,
'width':800
}
const metadata = result.value.getMetadata();
const clone = structuredClone(result.value);
for (const field in field_values) {
metadata[field] = field_values[field];
}
clone.setMetadata(metadata);
const cloneMetadata = clone.getMetadata();
for (const field in field_values) {
if(Array.isArray(field_values[field])) {
assert_array_equals(cloneMetadata[field], field_values[field], field);
} else {
assert_equals(cloneMetadata[field], field_values[field], field);
}
}
}, "setMetadata() allows changes when AllowRTCEncodedVideoFrameSetMetadataAllFields is enabled");
promise_test(async t => {
const senderReader = await setupLoopbackWithCodecAndGetReader(t, 'VP8');
const result = await senderReader.read();
const originalFrame = result.value;
const metadata = originalFrame.getMetadata();
const cloneFrame = structuredClone(originalFrame);
metadata.frameId = undefined;
metadata.dependencies = [1];
assert_throws_dom('InvalidModificationError', () => cloneFrame.setMetadata(metadata), "frameID missing, but has dependencies in RTCEncodedVideoFrameMetadata.");
}, "setMetadata() does not allow metadata with dependencies but no frameId");
promise_test(async t => {
const senderReader = await setupLoopbackWithCodecAndGetReader(t, 'VP8');
const result = await senderReader.read();
const originalFrame = result.value;
const metadata = originalFrame.getMetadata();
const cloneFrame = structuredClone(originalFrame);
metadata.frameId = 2;
metadata.dependencies = undefined;
cloneFrame.setMetadata(metadata);
}, "setMetadata() does allow metadata with no dependencies but a frameId");
</script>