test_peerConnection_setParameters.html (18663B)
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <script type="application/javascript" src="pc.js"></script> 5 </head> 6 <body> 7 <pre id="test"> 8 <script type="application/javascript"> 9 createHTML({ 10 bug: "1230184", 11 title: "Set parameters on sender", 12 visible: true 13 }); 14 15 const simulcastOffer = `v=0 16 o=- 3840232462471583827 0 IN IP4 127.0.0.1 17 s=- 18 t=0 0 19 a=group:BUNDLE 0 20 a=msid-semantic: WMS 21 m=video 9 UDP/TLS/RTP/SAVPF 96 22 c=IN IP4 0.0.0.0 23 a=rtcp:9 IN IP4 0.0.0.0 24 a=ice-ufrag:Li6+ 25 a=ice-pwd:3C05CTZBRQVmGCAq7hVasHlT 26 a=ice-options:trickle 27 a=fingerprint:sha-256 5B:D3:8E:66:0E:7D:D3:F3:8E:E6:80:28:19:FC:55:AD:58:5D:B9:3D:A8:DE:45:4A:E7:87:02:F8:3C:0B:3B:B3 28 a=setup:actpass 29 a=mid:0 30 a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id 31 a=recvonly 32 a=rtcp-mux 33 a=rtpmap:96 VP8/90000 34 a=rtcp-fb:96 goog-remb 35 a=rtcp-fb:96 transport-cc 36 a=rtcp-fb:96 ccm fir 37 a=rid:foo recv 38 a=rid:bar recv 39 a=simulcast:recv foo;bar 40 `; 41 42 function buildMaximumSendEncodings() { 43 const sendEncodings = []; 44 while (true) { 45 // isDeeply does not see identical string primitives and String objects 46 // as the same, so we make this a string primitive. 47 sendEncodings.push({rid: `${sendEncodings.length}`}); 48 const pc = new RTCPeerConnection(); 49 const {sender} = pc.addTransceiver('video', {sendEncodings}); 50 const {encodings} = sender.getParameters(); 51 if (encodings.length < sendEncodings.length) { 52 sendEncodings.pop(); 53 return sendEncodings; 54 } 55 } 56 } 57 58 async function queueAWebrtcTask() { 59 const pc = new RTCPeerConnection(); 60 pc.addTransceiver('audio'); 61 await new Promise(r => pc.onnegotiationneeded = r); 62 pc.close(); 63 } 64 65 // setParameters is mostly tested in wpt, but we test a few 66 // implementation-specific things here. Other mochitests check whether the 67 // set parameters actually have the desired effect on the media streams. 68 const tests = [ 69 70 // wpt currently does not assume support for 3 encodings, which limits the 71 // effectiveness of its powers-of-2 test (since it can test only for 1 and 2) 72 async function checkScaleResolutionDownByAutoFillPowersOf2() { 73 const pc = new RTCPeerConnection(); 74 const {sender} = pc.addTransceiver('video', { 75 sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}] 76 }); 77 const {encodings} = sender.getParameters(); 78 const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy); 79 isDeeply(scaleValues, [4, 2, 1]); 80 }, 81 82 // wpt currently does not assume support for 3 encodings, which limits the 83 // effectiveness of its fill-with-1 test 84 async function checkScaleResolutionDownByAutoFillWith1() { 85 const pc = new RTCPeerConnection(); 86 const {sender} = pc.addTransceiver('video', { 87 sendEncodings: [ 88 {rid: "0"},{rid: "1", scaleResolutionDownBy: 3},{rid: "2"} 89 ] 90 }); 91 const {encodings} = sender.getParameters(); 92 const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy); 93 isDeeply(scaleValues, [1, 3, 1]); 94 }, 95 96 async function checkVideoEncodingLimit() { 97 const pc = new RTCPeerConnection(); 98 const maxSendEncodings = buildMaximumSendEncodings(); 99 const sendEncodings = maxSendEncodings.concat({rid: "a"}); 100 const {sender} = pc.addTransceiver('video', {sendEncodings}); 101 const {encodings} = sender.getParameters(); 102 103 const rids = encodings.map(({rid}) => rid); 104 const expectedRids = maxSendEncodings.map(({rid}) => rid); 105 isDeeply(rids, expectedRids); 106 107 const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy); 108 const expectedScaleValues = []; 109 let scale = 1; 110 while (expectedScaleValues.length < maxSendEncodings.length) { 111 expectedScaleValues.push(scale); 112 scale *= 2; 113 } 114 isDeeply(scaleValues, expectedScaleValues.reverse()); 115 }, 116 117 async function checkScaleDownByInTrimmedEncoding() { 118 const pc = new RTCPeerConnection(); 119 const maxSendEncodings = buildMaximumSendEncodings(); 120 const sendEncodings = maxSendEncodings.concat({rid: "a", scaleResolutionDownBy: 3}); 121 const {sender} = pc.addTransceiver('video', {sendEncodings}); 122 const {encodings} = sender.getParameters(); 123 const rids = encodings.map(({rid}) => rid); 124 const expectedRids = maxSendEncodings.map(({rid}) => rid); 125 isDeeply(rids, expectedRids); 126 const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy); 127 const expectedScaleValues = maxSendEncodings.map(() => 1); 128 isDeeply(scaleValues, expectedScaleValues); 129 }, 130 131 async function checkLibwebrtcRidLengthLimit() { 132 const pc = new RTCPeerConnection(); 133 try { 134 pc.addTransceiver('video', { 135 sendEncodings: [{rid: "wibblywobblyjeremybearimy"}]} 136 ); 137 ok(false, "Rid should be too long for libwebrtc!"); 138 } catch (e) { 139 is(e.name, "TypeError", 140 "Rid that is too long for libwebrtc should result in a TypeError"); 141 } 142 }, 143 144 async function checkErrorsInTrimmedEncodings() { 145 const pc = new RTCPeerConnection(); 146 const maxSendEncodings = buildMaximumSendEncodings(); 147 try { 148 const sendEncodings = maxSendEncodings.concat({rid: "foo-bar"}); 149 pc.addTransceiver('video', { sendEncodings }); 150 ok(false, "Should throw due to invalid rid characters"); 151 } catch (e) { 152 is(e.name, "TypeError") 153 } 154 try { 155 const sendEncodings = maxSendEncodings.concat({rid: "wibblywobblyjeremybearimy"}); 156 pc.addTransceiver('video', { sendEncodings }); 157 ok(false, "Should throw because rid too long"); 158 } catch (e) { 159 is(e.name, "TypeError") 160 } 161 try { 162 const sendEncodings = maxSendEncodings.concat({scaleResolutionDownBy: 2}); 163 pc.addTransceiver('video', { sendEncodings }); 164 ok(false, "Should throw due to missing rid"); 165 } catch (e) { 166 is(e.name, "TypeError") 167 } 168 try { 169 const sendEncodings = maxSendEncodings.concat(maxSendEncodings[0]); 170 pc.addTransceiver('video', { sendEncodings }); 171 ok(false, "Should throw due to duplicate rid"); 172 } catch (e) { 173 is(e.name, "TypeError") 174 } 175 try { 176 const sendEncodings = maxSendEncodings.concat({rid: maxSendEncodings.length, scaleResolutionDownBy: 0}); 177 pc.addTransceiver('video', { sendEncodings }); 178 ok(false, "Should throw due to invalid scaleResolutionDownBy"); 179 } catch (e) { 180 is(e.name, "RangeError") 181 } 182 try { 183 const sendEncodings = maxSendEncodings.concat({rid: maxSendEncodings.length, maxFramerate: -1}); 184 pc.addTransceiver('video', { sendEncodings }); 185 ok(false, "Should throw due to invalid maxFramerate"); 186 } catch (e) { 187 is(e.name, "RangeError") 188 } 189 }, 190 191 async function checkCompatModeUnicastSetParametersAllowsSimulcastOffer() { 192 await pushPrefs( 193 ["media.peerconnection.allow_old_setParameters", true]); 194 const pc1 = new RTCPeerConnection(); 195 const stream = await navigator.mediaDevices.getUserMedia({video: true}); 196 const sender = pc1.addTrack(stream.getTracks()[0]); 197 const parameters = sender.getParameters(); 198 parameters.encodings[0].scaleResolutionDownBy = 3.0; 199 await sender.setParameters(parameters); 200 201 await pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer}); 202 203 const {encodings} = sender.getParameters(); 204 const rids = encodings.map(({rid}) => rid); 205 isDeeply(rids, ["foo", "bar"]); 206 is(encodings[0].scaleResolutionDownBy, 2.0); 207 is(encodings[1].scaleResolutionDownBy, 1.0); 208 }, 209 210 async function checkCompatModeUnicastSetParametersInterruptAllowsSimulcastOffer() { 211 await pushPrefs( 212 ["media.peerconnection.allow_old_setParameters", true]); 213 const pc1 = new RTCPeerConnection(); 214 const stream = await navigator.mediaDevices.getUserMedia({video: true}); 215 const sender = pc1.addTrack(stream.getTracks()[0]); 216 const parameters = sender.getParameters(); 217 parameters.encodings[0].scaleResolutionDownBy = 3.0; 218 219 const offerDone = pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer}); 220 await sender.setParameters(parameters); 221 await offerDone; 222 223 const {encodings} = sender.getParameters(); 224 const rids = encodings.map(({rid}) => rid); 225 isDeeply(rids, ["foo", "bar"]); 226 is(encodings[0].scaleResolutionDownBy, 2.0); 227 is(encodings[1].scaleResolutionDownBy, 1.0); 228 }, 229 230 async function checkCompatModeSimulcastSetParametersSetsSimulcastEnvelope() { 231 await pushPrefs( 232 ["media.peerconnection.allow_old_setParameters", true]); 233 const pc1 = new RTCPeerConnection(); 234 const stream = await navigator.mediaDevices.getUserMedia({video: true}); 235 const sender = pc1.addTrack(stream.getTracks()[0]); 236 const parameters = sender.getParameters(); 237 parameters.encodings[0].rid = "1"; 238 parameters.encodings.push({rid: "2"}); 239 await sender.setParameters(parameters); 240 241 await pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer}); 242 243 const {encodings} = sender.getParameters(); 244 const rids = encodings.map(({rid}) => rid); 245 // No overlap in rids -> unicast 246 isDeeply(rids, [undefined]); 247 }, 248 249 async function checkCompatModeSimulcastSetParametersRacesLocalUnicastOffer() { 250 await pushPrefs( 251 ["media.peerconnection.allow_old_setParameters", true]); 252 const pc1 = new RTCPeerConnection(); 253 const pc2 = new RTCPeerConnection(); 254 const stream = await navigator.mediaDevices.getUserMedia({video: true}); 255 const sender = pc1.addTrack(stream.getTracks()[0]); 256 // unicast offer 257 const offer = await pc1.createOffer(); 258 const aTask = queueAWebrtcTask(); 259 const sldPromise = pc1.setLocalDescription(offer); 260 261 // Right now, we have aTask queued. The success task for sLD is not queued 262 // yet, because Firefox performs the initial steps on the microtask queue, 263 // which we have not allowed to run yet. Awaiting aTask will first clear 264 // the microtask queue, then run the task queue until aTask is finished. 265 // That _should_ result in the success task for sLD(offer) being queued. 266 await aTask; 267 268 const parameters = sender.getParameters(); 269 parameters.encodings[0].rid = "foo"; 270 parameters.encodings.push({rid: "bar"}); 271 // simulcast setParameters; the task to update [[SendEncodings]] should be 272 // queued after the success task for sLD(offer) 273 await sender.setParameters(parameters); 274 await sldPromise; 275 276 const {encodings} = sender.getParameters(); 277 const rids = encodings.map(({rid}) => rid); 278 // Compat mode lets this slide, but won't try to negotiate it since we've 279 // already applied a unicast local offer. 280 isDeeply(rids, ["foo", "bar"]); 281 282 // Let negotiation finish, so we can generate a new offer 283 await pc2.setRemoteDescription(pc1.localDescription); 284 await pc2.setLocalDescription(); 285 await pc1.setRemoteDescription(pc2.localDescription); 286 287 const reoffer = await pc1.createOffer(); 288 ok(!reoffer.sdp.includes("a=simulcast"), "reoffer should be unicast"); 289 }, 290 291 async function checkCompatModeSimulcastSetParametersRacesRemoteOffer() { 292 await pushPrefs( 293 ["media.peerconnection.allow_old_setParameters", true]); 294 const pc1 = new RTCPeerConnection(); 295 const stream = await navigator.mediaDevices.getUserMedia({video: true}); 296 const sender = pc1.addTrack(stream.getTracks()[0]); 297 const parameters = sender.getParameters(); 298 parameters.encodings[0].rid = "foo"; 299 parameters.encodings.push({rid: "bar"}); 300 const p = sender.setParameters(parameters); 301 await pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer}); 302 await p; 303 const answer = await pc1.createAnswer(); 304 305 const {encodings} = sender.getParameters(); 306 const rids = encodings.map(({rid}) => rid); 307 isDeeply(rids, ["foo", "bar"]); 308 ok(answer.sdp.includes("a=simulcast:send foo;bar"), "answer should be simulcast"); 309 }, 310 311 async function checkCompatModeSimulcastSetParametersRacesLocalAnswer() { 312 await pushPrefs( 313 ["media.peerconnection.allow_old_setParameters", true]); 314 // We do an initial negotiation, and while the local answer is pending, 315 // perform a setParameters on a not-yet-negotiated video sender. The intent 316 // here is to have the success task for sLD(answer) run while the 317 // setParameters is pending. 318 const pc1 = new RTCPeerConnection(); 319 const pc2 = new RTCPeerConnection(); 320 321 const audioStream = await navigator.mediaDevices.getUserMedia({audio: true}); 322 // We use this later on, but set it up now so we don't inadvertently 323 // crank the event loop more than we intend below. 324 const videoStream = await navigator.mediaDevices.getUserMedia({video: true}); 325 pc2.addTrack(audioStream.getTracks()[0]); 326 await pc2.setLocalDescription(); 327 await pc1.setRemoteDescription(pc2.localDescription); 328 const answer = await pc1.createAnswer(); 329 const aTask = queueAWebrtcTask(); 330 const sldPromise = pc1.setLocalDescription(answer); 331 332 // Right now, we have aTask queued. The success task for sLD is not queued 333 // yet, because Firefox performs the initial steps on the microtask queue, 334 // which we have not allowed to run yet. Awaiting aTask will first clear 335 // the microtask queue, then run the task queue until aTask is finished. 336 // That _should_ result in the success task for sLD(answer) being queued. 337 await aTask; 338 339 // The success task for sLD(answer) should be queued now. Don't relinquish 340 // the event loop! 341 342 // New sender that has nothing to do with the negotiation in progress. 343 const sender = pc1.addTrack(videoStream.getTracks()[0]); 344 const parameters = sender.getParameters(); 345 parameters.encodings[0].rid = "foo"; 346 parameters.encodings.push({rid: "bar"}); 347 348 // We have not relinquished the event loop, so the sLD(answer) should still 349 // be queued. The task that updates [[SendEncodings]] (from setParameters) 350 // should be queued behind it. Let them both run. 351 await sender.setParameters(parameters); 352 await sldPromise; 353 354 const offer = await pc1.createOffer(); 355 356 const {encodings} = sender.getParameters(); 357 const rids = encodings.map(({rid}) => rid); 358 isDeeply(rids, ["foo", "bar"]); 359 ok(offer.sdp.includes("a=simulcast:send foo;bar"), "offer should be simulcast"); 360 }, 361 362 async function checkCompatModeSimulcastSetParametersRacesRemoteAnswer() { 363 await pushPrefs( 364 ["media.peerconnection.allow_old_setParameters", true]); 365 // We do an initial negotiation, and while the remote answer is pending, 366 // perform a setParameters on a not-yet-negotiated video sender. The intent 367 // here is to have the success task for sRD(answer) run while the 368 // setParameters is pending. 369 const pc1 = new RTCPeerConnection(); 370 const pc2 = new RTCPeerConnection(); 371 372 const audioStream = await navigator.mediaDevices.getUserMedia({audio: true}); 373 // We use this later on, but set it up now so we don't inadvertently 374 // crank the event loop more than we intend below. 375 const videoStream = await navigator.mediaDevices.getUserMedia({video: true}); 376 pc1.addTrack(audioStream.getTracks()[0]); 377 await pc1.setLocalDescription(); 378 await pc2.setRemoteDescription(pc1.localDescription); 379 await pc2.setLocalDescription(); 380 const aTask = queueAWebrtcTask(); 381 const srdPromise = pc1.setRemoteDescription(pc2.localDescription); 382 383 // Right now, we have aTask queued. The success task for sRD is not queued 384 // yet, because Firefox performs the initial steps on the microtask queue, 385 // which we have not allowed to run yet. Awaiting aTask will first clear 386 // the microtask queue, then run the task queue until aTask is finished. 387 // That _should_ result in the success task for sRD(answer) being queued. 388 await aTask; 389 390 // The success task for sRD(answer) should be queued now. Don't relinquish 391 // the event loop! 392 393 const sender = pc1.addTrack(videoStream.getTracks()[0]); 394 const parameters = sender.getParameters(); 395 parameters.encodings[0].rid = "foo"; 396 parameters.encodings.push({rid: "bar"}); 397 398 // We have not relinquished the event loop, so the sRD(answer) should still 399 // be queued. The task that updates [[SendEncodings]] (from setParameters) 400 // should be queued behind it. Let them both run. 401 await sender.setParameters(parameters); 402 await srdPromise; 403 404 const offer = await pc1.createOffer(); 405 406 const {encodings} = sender.getParameters(); 407 const rids = encodings.map(({rid}) => rid); 408 isDeeply(rids, ["foo", "bar"]); 409 ok(offer.sdp.includes("a=simulcast:send foo;bar"), "offer should be simulcast"); 410 }, 411 412 async function checkCompatModeSimulcastRidlessSetParametersRacesLocalOffer() { 413 await pushPrefs( 414 ["media.peerconnection.allow_old_setParameters", true]); 415 const pc1 = new RTCPeerConnection(); 416 const pc2 = new RTCPeerConnection(); 417 const stream = await navigator.mediaDevices.getUserMedia({video: true}); 418 const sender = pc1.addTrack(stream.getTracks()[0]); 419 // unicast offer 420 const aTask = queueAWebrtcTask(); 421 const sldPromise = pc1.setLocalDescription(); 422 423 // Right now, we have aTask queued. The success task for sLD is not queued 424 // yet, because Firefox performs the initial steps on the microtask queue, 425 // which we have not allowed to run yet. Awaiting aTask will first clear 426 // the microtask queue, then run the task queue until aTask is finished. 427 // That _should_ result in the success task for sLD(offer) being queued. 428 await aTask; 429 430 // simulcast setParameters; the task to update [[SendEncodings]] should be 431 // queued after the success task for sLD(offer) 432 try { 433 await sender.setParameters({"encodings": [{}, {}]}); 434 ok(false, "setParameters with two ridless encodings should fail"); 435 } catch (e) { 436 ok(true, "setParameters with two ridless encodings should fail"); 437 } 438 await sldPromise; 439 440 const {encodings} = sender.getParameters(); 441 const rids = encodings.map(({rid}) => rid); 442 // Compat mode lets this slide, but won't try to negotiate it since we've 443 // already applied a unicast local offer. 444 isDeeply(rids, [undefined]); 445 446 // Let negotiation finish, so we can generate a new offer 447 await pc2.setRemoteDescription(pc1.localDescription); 448 await pc2.setLocalDescription(); 449 await pc1.setRemoteDescription(pc2.localDescription); 450 451 const reoffer = await pc1.createOffer(); 452 ok(!reoffer.sdp.includes("a=simulcast"), "reoffer should be unicast"); 453 }, 454 455 ]; 456 457 runNetworkTest(async () => { 458 await pushPrefs( 459 ["media.peerconnection.allow_old_setParameters", false]); 460 for (const test of tests) { 461 info(`Running test: ${test.name}`); 462 await test(); 463 info(`Done running test: ${test.name}`); 464 } 465 }); 466 467 </script> 468 </pre> 469 </body> 470 </html>