RTCPeerConnection-insertable-streams-video.https.html (6825B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>RTCPeerConnection Insertable Streams - Video</title> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src=/resources/testdriver.js></script> 8 <script src=/resources/testdriver-vendor.js></script> 9 <script src='../../mediacapture-streams/permission-helper.js'></script> 10 <script src="../../webrtc/RTCPeerConnection-helper.js"></script> 11 <script src="./RTCPeerConnection-insertable-streams.js"></script> 12 </head> 13 <body> 14 <script> 15 async function testVideoFlow(t, negotiationFunction, setConstructorParam, frameCallback = () => {}) { 16 const caller = new RTCPeerConnection(setConstructorParam ? {encodedInsertableStreams:true} : {}); 17 t.add_cleanup(() => caller.close()); 18 const callee = new RTCPeerConnection(setConstructorParam ? {encodedInsertableStreams:true} : {}); 19 t.add_cleanup(() => callee.close()); 20 21 await setMediaPermission("granted", ["camera"]); 22 const stream = await navigator.mediaDevices.getUserMedia({video:true}); 23 const videoTrack = stream.getVideoTracks()[0]; 24 t.add_cleanup(() => videoTrack.stop()); 25 26 const videoSender = caller.addTrack(videoTrack) 27 const senderStreams = videoSender.createEncodedStreams(); 28 const senderReader = senderStreams.readable.getReader(); 29 const senderWriter = senderStreams.writable.getWriter(); 30 31 const frameInfos = []; 32 const numFramesPassthrough = 5; 33 const numFramesReplaceData = 5; 34 const numFramesModifyData = 5; 35 const numFramesToSend = numFramesPassthrough + numFramesReplaceData + numFramesModifyData; 36 37 let streamsCreatedAtNegotiation; 38 const ontrackPromise = new Promise(resolve => { 39 callee.ontrack = t.step_func(() => { 40 const videoReceiver = callee.getReceivers().find(r => r.track.kind === 'video'); 41 assert_not_equals(videoReceiver, undefined); 42 43 let receiverReader; 44 let receiverWriter; 45 if (streamsCreatedAtNegotiation) { 46 const videoStreams = streamsCreatedAtNegotiation.find(r => r.kind === 'video'); 47 assert_true(!!videoStreams); 48 receiverReader = videoStreams.streams.readable.getReader(); 49 receiverWriter = videoStreams.streams.writable.getWriter(); 50 } else { 51 const receiverStreams = 52 videoReceiver.createEncodedStreams(); 53 receiverReader = receiverStreams.readable.getReader(); 54 receiverWriter = receiverStreams.writable.getWriter(); 55 } 56 57 const maxFramesToReceive = numFramesToSend; 58 let numVerifiedFrames = 0; 59 for (let i = 0; i < maxFramesToReceive; i++) { 60 receiverReader.read().then(t.step_func(result => { 61 verifyNonstandardAdditionalDataIfPresent(result.value); 62 if (frameInfos[numVerifiedFrames] && 63 areFrameInfosEqual(result.value, frameInfos[numVerifiedFrames])) { 64 numVerifiedFrames++; 65 } else { 66 // Receiving unexpected frames is an indication that 67 // frames are not passed correctly between sender and receiver. 68 assert_unreached("Incorrect frame received"); 69 } 70 71 if (numVerifiedFrames == numFramesToSend) 72 resolve(); 73 })); 74 } 75 }); 76 }); 77 78 exchangeIceCandidates(caller, callee); 79 await negotiationFunction(caller, callee, (streams) => {streamsCreatedAtNegotiation = streams;}); 80 81 // Pass frames as they come from the encoder. 82 for (let i = 0; i < numFramesPassthrough; i++) { 83 const result = await senderReader.read(); 84 const frame = result.value; 85 const metadata = frame.getMetadata(); 86 assert_true(containsVideoMetadata(metadata)); 87 verifyNonstandardAdditionalDataIfPresent(frame); 88 frameInfos.push({ 89 timestamp: frame.timestamp, 90 type: frame.type, 91 data: frame.data, 92 metadata: metadata, 93 getMetadata() { return this.metadata; } 94 }); 95 frameCallback(frame); 96 senderWriter.write(frame); 97 } 98 99 // Replace frame data with arbitrary buffers. 100 for (let i = 0; i < numFramesReplaceData; i++) { 101 const result = await senderReader.read(); 102 const metadata = result.value.getMetadata(); 103 assert_true(containsVideoMetadata(metadata)); 104 const buffer = new ArrayBuffer(100); 105 const int8View = new Int8Array(buffer); 106 int8View.fill(i); 107 108 result.value.data = buffer; 109 frameInfos.push({ 110 timestamp: result.value.timestamp, 111 type: result.value.type, 112 data: result.value.data, 113 metadata: metadata, 114 getMetadata() { return this.metadata; } 115 }); 116 senderWriter.write(result.value); 117 } 118 119 // Modify frame data. 120 for (let i = 0; i < numFramesReplaceData; i++) { 121 const result = await senderReader.read(); 122 const metadata = result.value.getMetadata(); 123 assert_true(containsVideoMetadata(metadata)); 124 const int8View = new Int8Array(result.value.data); 125 int8View.fill(i); 126 127 frameInfos.push({ 128 timestamp: result.value.timestamp, 129 type: result.value.type, 130 data: result.value.data, 131 metadata: metadata, 132 getMetadata() { return this.metadata; } 133 }); 134 senderWriter.write(result.value); 135 } 136 137 return ontrackPromise; 138 } 139 140 for (const setConstructorParam of [false, true]) { 141 promise_test(async t => { 142 return testVideoFlow(t, exchangeOfferAnswer, setConstructorParam); 143 }, 'Frames flow correctly using insertable streams' + (setConstructorParam ? ' with param' : '')); 144 145 promise_test(async t => { 146 return testVideoFlow(t, exchangeOfferAnswerReverse, setConstructorParam); 147 }, 'Frames flow correctly using insertable streams when receiver starts negotiation' + (setConstructorParam ? ' with param' : '')); 148 } 149 150 promise_test(async t => { 151 const caller = new RTCPeerConnection(); 152 t.add_cleanup(() => caller.close()); 153 const stream = await navigator.mediaDevices.getUserMedia({video:true}); 154 const track = stream.getTracks()[0]; 155 t.add_cleanup(() => track.stop()); 156 const sender = caller.addTrack(track) 157 sender.createEncodedStreams(); 158 assert_throws_dom("InvalidStateError", () => sender.createEncodedStreams()); 159 }, 'Creating streams twice throws'); 160 161 promise_test(async t => { 162 let clonedFrames = []; 163 function verifyFramesSerializeAndDeserialize(frame) { 164 // Clone encoded frames using structedClone (ie serialize + deserialize) and 165 // keep a reference. 166 const clone = structuredClone(frame); 167 clonedFrames.push(clone); 168 }; 169 170 await testVideoFlow(t, exchangeOfferAnswer, /*setConstructorParam=*/false, verifyFramesSerializeAndDeserialize); 171 172 // Ensure all of our cloned frames are still alive and well, despite the 173 // originals having been sent through the PeerConnection. 174 clonedFrames.forEach((clonedFrame) => { 175 assert_not_equals(clonedFrame.data.size, 0); 176 assert_not_equals(clonedFrame.type, "empty") 177 }); 178 }, 'Encoded frames serialize and deserialize into a deep clone'); 179 180 </script> 181 </body> 182 </html>