RTCPeerConnection-addTransceiver.https.html (17284B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>RTCPeerConnection.prototype.addTransceiver</title> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <script src="RTCPeerConnection-helper.js"></script> 7 <script> 8 'use strict'; 9 10 // Test is based on the following editor draft: 11 // https://rawgit.com/w3c/webrtc-pc/cc8d80f455b86c8041d63bceb8b457f45c72aa89/webrtc.html 12 13 /* 14 5.1. RTCPeerConnection Interface Extensions 15 16 partial interface RTCPeerConnection { 17 sequence<RTCRtpSender> getSenders(); 18 sequence<RTCRtpReceiver> getReceivers(); 19 sequence<RTCRtpTransceiver> getTransceivers(); 20 RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind, 21 optional RTCRtpTransceiverInit init); 22 ... 23 }; 24 25 dictionary RTCRtpTransceiverInit { 26 RTCRtpTransceiverDirection direction = "sendrecv"; 27 sequence<MediaStream> streams; 28 sequence<RTCRtpEncodingParameters> sendEncodings; 29 }; 30 31 enum RTCRtpTransceiverDirection { 32 "sendrecv", 33 "sendonly", 34 "recvonly", 35 "inactive" 36 }; 37 38 5.2. RTCRtpSender Interface 39 40 interface RTCRtpSender { 41 readonly attribute MediaStreamTrack? track; 42 ... 43 }; 44 45 5.3. RTCRtpReceiver Interface 46 47 interface RTCRtpReceiver { 48 readonly attribute MediaStreamTrack track; 49 ... 50 }; 51 52 5.4. RTCRtpTransceiver Interface 53 54 interface RTCRtpTransceiver { 55 readonly attribute DOMString? mid; 56 [SameObject] 57 readonly attribute RTCRtpSender sender; 58 [SameObject] 59 readonly attribute RTCRtpReceiver receiver; 60 readonly attribute boolean stopped; 61 readonly attribute RTCRtpTransceiverDirection direction; 62 readonly attribute RTCRtpTransceiverDirection? currentDirection; 63 ... 64 }; 65 66 Note 67 While addTrack checks if the MediaStreamTrack given as an argument is 68 already being sent to avoid sending the same MediaStreamTrack twice, 69 the other ways do not, allowing the same MediaStreamTrack to be sent 70 several times simultaneously. 71 */ 72 73 /* 74 5.1. addTransceiver 75 3. If the first argument is a string, let it be kind and run the following steps: 76 1. If kind is not a legal MediaStreamTrack kind, throw a TypeError. 77 */ 78 test(t => { 79 const pc = new RTCPeerConnection(); 80 t.add_cleanup(() => pc.close()); 81 82 assert_idl_attribute(pc, 'addTransceiver'); 83 assert_throws_js(TypeError, () => pc.addTransceiver('invalid')); 84 }, 'addTransceiver() with string argument as invalid kind should throw TypeError'); 85 86 /* 87 5.1. addTransceiver 88 The initial value of mid is null. 89 90 3. If the dictionary argument is present, let direction be the value of the 91 direction member. Otherwise let direction be sendrecv. 92 4. If the first argument is a string, let it be kind and run the following steps: 93 2. Let track be null. 94 8. Create an RTCRtpSender with track, streams and sendEncodings and let 95 sender be the result. 96 9. Create an RTCRtpReceiver with kind and let receiver be the result. 97 10. Create an RTCRtpTransceiver with sender, receiver and direction, and let 98 transceiver be the result. 99 11. Add transceiver to connection's set of transceivers. 100 101 5.2. RTCRtpSender Interface 102 Create an RTCRtpSender 103 2. Set sender.track to track. 104 105 5.3. RTCRtpReceiver Interface 106 Create an RTCRtpReceiver 107 2. Let track be a new MediaStreamTrack object [GETUSERMEDIA]. The source of 108 track is a remote source provided by receiver. 109 3. Initialize track.kind to kind. 110 5. Initialize track.label to the result of concatenating the string "remote " 111 with kind. 112 6. Initialize track.readyState to live. 113 7. Initialize track.muted to true. 114 8. Set receiver.track to track. 115 116 5.4. RTCRtpTransceiver Interface 117 Create an RTCRtpTransceiver 118 2. Set transceiver.sender to sender. 119 3. Set transceiver.receiver to receiver. 120 4. Let transceiver have a [[Direction]] internal slot, initialized to direction. 121 5. Let transceiver have a [[CurrentDirection]] internal slot, initialized 122 to null. 123 6. Set transceiver.stopped to false. 124 */ 125 test(t => { 126 const pc = new RTCPeerConnection(); 127 t.add_cleanup(() => pc.close()); 128 129 assert_idl_attribute(pc, 'addTransceiver'); 130 131 const transceiver = pc.addTransceiver('audio'); 132 assert_true(transceiver instanceof RTCRtpTransceiver, 133 'Expect transceiver to be instance of RTCRtpTransceiver'); 134 135 assert_equals(transceiver.mid, null); 136 assert_equals(transceiver.stopped, false); 137 assert_equals(transceiver.direction, 'sendrecv'); 138 assert_equals(transceiver.currentDirection, null); 139 140 assert_array_equals([transceiver], pc.getTransceivers(), 141 `Expect added transceiver to be the only element in connection's list of transceivers`); 142 143 const sender = transceiver.sender; 144 145 assert_true(sender instanceof RTCRtpSender, 146 'Expect sender to be instance of RTCRtpSender'); 147 148 assert_equals(sender.track, null); 149 150 assert_array_equals([sender], pc.getSenders(), 151 `Expect added sender to be the only element in connection's list of senders`); 152 153 const receiver = transceiver.receiver; 154 assert_true(receiver instanceof RTCRtpReceiver, 155 'Expect receiver to be instance of RTCRtpReceiver'); 156 157 const track = receiver.track; 158 assert_true(track instanceof MediaStreamTrack, 159 'Expect receiver.track to be instance of MediaStreamTrack'); 160 161 assert_equals(track.kind, 'audio'); 162 assert_equals(track.readyState, 'live'); 163 assert_equals(track.muted, true); 164 165 assert_array_equals([receiver], pc.getReceivers(), 166 `Expect added receiver to be the only element in connection's list of receivers`); 167 168 }, `addTransceiver('audio') should return an audio transceiver`); 169 170 test(t => { 171 const pc = new RTCPeerConnection(); 172 t.add_cleanup(() => pc.close()); 173 174 assert_idl_attribute(pc, 'addTransceiver'); 175 176 const transceiver = pc.addTransceiver('video'); 177 assert_true(transceiver instanceof RTCRtpTransceiver, 178 'Expect transceiver to be instance of RTCRtpTransceiver'); 179 180 assert_equals(transceiver.mid, null); 181 assert_equals(transceiver.stopped, false); 182 assert_equals(transceiver.direction, 'sendrecv'); 183 184 assert_array_equals([transceiver], pc.getTransceivers(), 185 `Expect added transceiver to be the only element in connection's list of transceivers`); 186 187 const sender = transceiver.sender; 188 189 assert_true(sender instanceof RTCRtpSender, 190 'Expect sender to be instance of RTCRtpSender'); 191 192 assert_equals(sender.track, null); 193 194 assert_array_equals([sender], pc.getSenders(), 195 `Expect added sender to be the only element in connection's list of senders`); 196 197 const receiver = transceiver.receiver; 198 assert_true(receiver instanceof RTCRtpReceiver, 199 'Expect receiver to be instance of RTCRtpReceiver'); 200 201 const track = receiver.track; 202 assert_true(track instanceof MediaStreamTrack, 203 'Expect receiver.track to be instance of MediaStreamTrack'); 204 205 assert_equals(track.kind, 'video'); 206 assert_equals(track.readyState, 'live'); 207 assert_equals(track.muted, true); 208 209 assert_array_equals([receiver], pc.getReceivers(), 210 `Expect added receiver to be the only element in connection's list of receivers`); 211 212 }, `addTransceiver('video') should return a video transceiver`); 213 214 test(t => { 215 const pc = new RTCPeerConnection(); 216 t.add_cleanup(() => pc.close()); 217 218 const transceiver = pc.addTransceiver('audio', { direction: 'sendonly' }); 219 assert_equals(transceiver.direction, 'sendonly'); 220 }, `addTransceiver() with direction sendonly should have result transceiver.direction be the same`); 221 222 test(t => { 223 const pc = new RTCPeerConnection(); 224 t.add_cleanup(() => pc.close()); 225 226 const transceiver = pc.addTransceiver('audio', { direction: 'inactive' }); 227 assert_equals(transceiver.direction, 'inactive'); 228 }, `addTransceiver() with direction inactive should have result transceiver.direction be the same`); 229 230 test(t => { 231 const pc = new RTCPeerConnection(); 232 t.add_cleanup(() => pc.close()); 233 234 assert_idl_attribute(pc, 'addTransceiver'); 235 assert_throws_js(TypeError, () => 236 pc.addTransceiver('audio', { direction: 'invalid' })); 237 }, `addTransceiver() with invalid direction should throw TypeError`); 238 239 /* 240 5.1. addTransceiver 241 5. If the first argument is a MediaStreamTrack , let it be track and let 242 kind be track.kind. 243 */ 244 promise_test(async t => { 245 const pc = new RTCPeerConnection(); 246 t.add_cleanup(() => pc.close()); 247 248 const stream = await getNoiseStream({audio: true}); 249 t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); 250 const [track] = stream.getTracks(); 251 const transceiver = pc.addTransceiver(track); 252 const { sender, receiver } = transceiver; 253 254 assert_true(sender instanceof RTCRtpSender, 255 'Expect sender to be instance of RTCRtpSender'); 256 257 assert_true(receiver instanceof RTCRtpReceiver, 258 'Expect receiver to be instance of RTCRtpReceiver'); 259 260 assert_equals(sender.track, track, 261 'Expect sender.track should be the track that is added'); 262 263 const receiverTrack = receiver.track; 264 assert_true(receiverTrack instanceof MediaStreamTrack, 265 'Expect receiver.track to be instance of MediaStreamTrack'); 266 267 assert_equals(receiverTrack.kind, 'audio', 268 `receiver.track should have the same kind as added track's kind`); 269 270 assert_equals(receiverTrack.readyState, 'live'); 271 assert_equals(receiverTrack.muted, true); 272 273 assert_array_equals([transceiver], pc.getTransceivers(), 274 `Expect added transceiver to be the only element in connection's list of transceivers`); 275 276 assert_array_equals([sender], pc.getSenders(), 277 `Expect added sender to be the only element in connection's list of senders`); 278 279 assert_array_equals([receiver], pc.getReceivers(), 280 `Expect added receiver to be the only element in connection's list of receivers`); 281 282 }, 'addTransceiver(track) should have result with sender.track be given track'); 283 284 promise_test(async t => { 285 const pc = new RTCPeerConnection(); 286 t.add_cleanup(() => pc.close()); 287 288 const stream = await getNoiseStream({audio: true}); 289 t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); 290 const [track] = stream.getTracks(); 291 const transceiver1 = pc.addTransceiver(track); 292 const transceiver2 = pc.addTransceiver(track); 293 294 assert_not_equals(transceiver1, transceiver2); 295 296 const sender1 = transceiver1.sender; 297 const sender2 = transceiver2.sender; 298 299 assert_not_equals(sender1, sender2); 300 assert_equals(transceiver1.sender.track, track); 301 assert_equals(transceiver2.sender.track, track); 302 303 const transceivers = pc.getTransceivers(); 304 assert_equals(transceivers.length, 2); 305 assert_true(transceivers.includes(transceiver1)); 306 assert_true(transceivers.includes(transceiver2)); 307 308 const senders = pc.getSenders(); 309 assert_equals(senders.length, 2); 310 assert_true(senders.includes(sender1)); 311 assert_true(senders.includes(sender2)); 312 313 }, 'addTransceiver(track) multiple times should create multiple transceivers'); 314 315 /* 316 5.1. addTransceiver 317 6. Verify that each rid value in sendEncodings is composed only of 318 case-sensitive alphanumeric characters (a-z, A-Z, 0-9) up to a maximum 319 of 16 characters. If one of the RIDs does not meet these requirements, 320 throw a TypeError. 321 */ 322 323 ["video", "audio"].forEach(kind => { 324 325 test(t => { 326 const pc = new RTCPeerConnection(); 327 t.add_cleanup(() => pc.close()); 328 assert_idl_attribute(pc, 'addTransceiver'); 329 330 assert_throws_js(TypeError, () => 331 pc.addTransceiver(kind, { 332 sendEncodings: [{ 333 rid: '@Invalid!' 334 }] 335 })); 336 }, `addTransceiver("${kind}") with rid containing invalid non-alphanumeric characters should throw TypeError`); 337 338 test(t => { 339 const pc = new RTCPeerConnection(); 340 t.add_cleanup(() => pc.close()); 341 assert_idl_attribute(pc, 'addTransceiver'); 342 343 assert_throws_js(TypeError, () => 344 pc.addTransceiver(kind, { 345 sendEncodings: [{ 346 rid: 'a'.repeat(17) 347 }] 348 })); 349 }, `addTransceiver("${kind}") with rid longer than 16 characters should throw TypeError`); 350 351 test(t => { 352 const pc = new RTCPeerConnection(); 353 t.add_cleanup(() => pc.close()); 354 assert_idl_attribute(pc, 'addTransceiver'); 355 356 assert_throws_js(TypeError, () => 357 pc.addTransceiver(kind, { 358 sendEncodings: [{rid: 'a'}, {rid: 'a'}] 359 })); 360 }, `addTransceiver("${kind}") with duplicate rids should throw TypeError`); 361 362 test(t => { 363 const pc = new RTCPeerConnection(); 364 t.add_cleanup(() => pc.close()); 365 pc.addTransceiver(kind, { 366 sendEncodings: [{ 367 rid: 'foo' 368 }] 369 }); 370 }, `addTransceiver("${kind}") with valid rid value should succeed`); 371 372 test(t => { 373 const pc = new RTCPeerConnection(); 374 t.add_cleanup(() => pc.close()); 375 pc.addTransceiver('video', { 376 sendEncodings: [{rid: 'a'}, {rid: 'b'}] 377 }); 378 }, `addTransceiver("${kind}") with multiple rid values should succeed`); 379 380 test(t => { 381 const pc = new RTCPeerConnection(); 382 t.add_cleanup(() => pc.close()); 383 384 pc.addTransceiver('video', { 385 sendEncodings: [{ 386 dtx: 'enabled', 387 active: false, 388 ptime: 5, 389 maxBitrate: 8, 390 maxFramerate: 25, 391 rid: 'foo' 392 }] 393 }); 394 }, `addTransceiver("${kind}") with valid sendEncodings should succeed`); 395 }); 396 397 /* 398 TODO 399 5.1. addTransceiver 400 - Adding a transceiver will cause future calls to createOffer to add a media 401 description for the corresponding transceiver, as defined in [JSEP] 402 (section 5.2.2.). 403 404 - Setting a new RTCSessionDescription may change mid to a non-null value, 405 as defined in [JSEP] (section 5.5. and section 5.6.). 406 407 1. If the dictionary argument is present, and it has a streams member, let 408 streams be that list of MediaStream objects. 409 410 5.2. RTCRtpSender Interface 411 Create an RTCRtpSender 412 3. Let sender have an [[associated MediaStreams]] internal slot, representing 413 a list of MediaStream objects that the MediaStreamTrack object of this 414 sender is associated with. 415 416 4. Set sender's [[associated MediaStreams]] slot to streams. 417 418 5. Let sender have a [[send encodings]] internal slot, representing a list 419 of RTCRtpEncodingParameters dictionaries. 420 421 6. If sendEncodings is given as input to this algorithm, and is non-empty, 422 set the [[send encodings]] slot to sendEncodings. Otherwise, set it to a 423 list containing a single RTCRtpEncodingParameters with active set to true. 424 425 5.3. RTCRtpReceiver Interface 426 Create an RTCRtpReceiver 427 4. If an id string, id, was given as input to this algorithm, initialize 428 track.id to id. (Otherwise the value generated when track was created 429 will be used.) 430 431 Tested in RTCPeerConnection-onnegotiationneeded.html 432 5.1. addTransceiver 433 12. Update the negotiation-needed flag for connection. 434 435 Out of Scope 436 5.1. addTransceiver 437 8. If sendEncodings is set, then subsequent calls to createOffer will be 438 configured to send multiple RTP encodings as defined in [JSEP] 439 (section 5.2.2. and section 5.2.1.). 440 441 When setRemoteDescription is called with a corresponding remote 442 description that is able to receive multiple RTP encodings as defined 443 in [JSEP] (section 3.7.), the RTCRtpSender may send multiple RTP 444 encodings and the parameters retrieved via the transceiver's 445 sender.getParameters() will reflect the encodings negotiated. 446 447 9. This specification does not define how to configure createOffer to 448 receive multiple RTP encodings. However when setRemoteDescription is 449 called with a corresponding remote description that is able to send 450 multiple RTP encodings as defined in [JSEP], the RTCRtpReceiver may 451 receive multiple RTP encodings and the parameters retrieved via the 452 transceiver's receiver.getParameters() will reflect the encodings 453 negotiated. 454 455 Coverage Report 456 Tested Not-Tested Non-Testable Total 457 addTransceiver 14 1 3 18 458 Create Sender 3 4 0 7 459 Create Receiver 8 1 0 9 460 Create Transceiver 7 0 0 7 461 462 Total 32 6 3 41 463 */ 464 </script>