| <!doctype html> | |
| <meta charset=utf-8> | |
| <title>RTCPeerConnection.prototype.addTrack</title> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <script src="RTCPeerConnection-helper.js"></script> | |
| <script> | |
| 'use strict'; | |
| // Test is based on the following editor draft: | |
| // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html | |
| // The following helper functions are called from RTCPeerConnection-helper.js: | |
| // generateMediaStreamTrack | |
| /* | |
| 5.1. RTCPeerConnection Interface Extensions | |
| partial interface RTCPeerConnection { | |
| ... | |
| sequence<RTCRtpSender> getSenders(); | |
| sequence<RTCRtpReceiver> getReceivers(); | |
| sequence<RTCRtpTransceiver> getTransceivers(); | |
| RTCRtpSender addTrack(MediaStreamTrack track, | |
| MediaStream... streams); | |
| RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind, | |
| optional RTCRtpTransceiverInit init); | |
| }; | |
| Note | |
| While addTrack checks if the MediaStreamTrack given as an argument is | |
| already being sent to avoid sending the same MediaStreamTrack twice, | |
| the other ways do not, allowing the same MediaStreamTrack to be sent | |
| several times simultaneously. | |
| */ | |
| /* | |
| 5.1. addTrack | |
| 4. If connection's [[isClosed]] slot is true, throw an InvalidStateError. | |
| */ | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| return navigator.mediaDevices.getUserMedia({ audio: true }) | |
| .then(mediaStream => { | |
| const tracks = mediaStream.getTracks(); | |
| assert_greater_than(tracks.length, 0, | |
| 'Expect getUserMedia to return at least one audio track'); | |
| const track = tracks[0]; | |
| pc.close(); | |
| assert_throws('InvalidStateError', () => pc.addTrack(track, mediaStream)) | |
| }); | |
| }, 'addTrack when pc is closed should throw InvalidStateError'); | |
| /* | |
| 5.1. addTrack | |
| 8. If sender is null, run the following steps: | |
| 1. Create an RTCRtpSender with track and streams and let sender be | |
| the result. | |
| 2. Create an RTCRtpReceiver with track.kind as kind and let receiver | |
| be the result. | |
| 3. Create an RTCRtpTransceiver with sender and receiver and let | |
| transceiver be the result. | |
| 4. Add transceiver to connection's set of transceivers. | |
| */ | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| return navigator.mediaDevices.getUserMedia({ audio: true }) | |
| .then(mediaStream => { | |
| const tracks = mediaStream.getTracks(); | |
| assert_greater_than(tracks.length, 0, | |
| 'Expect getUserMedia to return at least one audio track'); | |
| const track = tracks[0]; | |
| const sender = pc.addTrack(track); | |
| assert_true(sender instanceof RTCRtpSender, | |
| 'Expect sender to be instance of RTCRtpSender'); | |
| assert_equals(sender.track, track, | |
| `Expect sender's track to be the added track`); | |
| const transceivers = pc.getTransceivers(); | |
| assert_equals(transceivers.length, 1, | |
| 'Expect only one transceiver with sender added'); | |
| const [transceiver] = transceivers; | |
| assert_equals(transceiver.sender, sender); | |
| assert_array_equals([sender], pc.getSenders(), | |
| 'Expect only one sender with given track added'); | |
| const { receiver } = transceiver; | |
| assert_equals(receiver.track.kind, 'audio'); | |
| assert_array_equals([transceiver.receiver], pc.getReceivers(), | |
| 'Expect only one receiver associated with transceiver added'); | |
| }); | |
| }, 'addTrack with single track argument and no mediaStream should succeed'); | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| return navigator.mediaDevices.getUserMedia({ audio: true }) | |
| .then(mediaStream => { | |
| const tracks = mediaStream.getTracks(); | |
| assert_greater_than(tracks.length, 0, | |
| 'Expect getUserMedia to return at least one audio track'); | |
| const track = tracks[0]; | |
| const sender = pc.addTrack(track, mediaStream); | |
| assert_true(sender instanceof RTCRtpSender, | |
| 'Expect sender to be instance of RTCRtpSender'); | |
| assert_equals(sender.track, track, | |
| `Expect sender's track to be the added track`); | |
| }); | |
| }, 'addTrack with single track argument and single mediaStream should succeed'); | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| return navigator.mediaDevices.getUserMedia({ audio: true }) | |
| .then(mediaStream => { | |
| const tracks = mediaStream.getTracks(); | |
| assert_greater_than(tracks.length, 0, | |
| 'Expect getUserMedia to return at least one audio track'); | |
| const track = tracks[0]; | |
| const mediaStream2 = new MediaStream([track]); | |
| const sender = pc.addTrack(track, mediaStream, mediaStream2); | |
| assert_true(sender instanceof RTCRtpSender, | |
| 'Expect sender to be instance of RTCRtpSender'); | |
| assert_equals(sender.track, track, | |
| `Expect sender's track to be the added track`); | |
| }); | |
| }, 'addTrack with single track argument and multiple mediaStreams should succeed'); | |
| /* | |
| 5.1. addTrack | |
| 5. Let senders be the result of executing the CollectSenders algorithm. | |
| If an RTCRtpSender for track already exists in senders, throw an | |
| InvalidAccessError. | |
| */ | |
| promise_test(t => { | |
| const pc = new RTCPeerConnection(); | |
| return navigator.mediaDevices.getUserMedia({ audio: true }) | |
| .then(mediaStream => { | |
| const tracks = mediaStream.getTracks(); | |
| assert_greater_than(tracks.length, 0, | |
| 'Expect getUserMedia to return at least one audio track'); | |
| const track = tracks[0]; | |
| pc.addTrack(track, mediaStream); | |
| assert_throws('InvalidAccessError', () => pc.addTrack(track, mediaStream)); | |
| }); | |
| }, 'Adding the same track multiple times should throw InvalidAccessError'); | |
| /* | |
| 5.1. addTrack | |
| 6. The steps below describe how to determine if an existing sender can | |
| be reused. | |
| If any RTCRtpSender object in senders matches all the following | |
| criteria, let sender be that object, or null otherwise: | |
| - The sender's track is null. | |
| - The transceiver kind of the RTCRtpTransceiver, associated with | |
| the sender, matches track's kind. | |
| - The sender has never been used to send. More precisely, the | |
| RTCRtpTransceiver associated with the sender has never had a | |
| currentDirection of sendrecv or sendonly. | |
| 7. If sender is not null, run the following steps to use that sender: | |
| 1. Set sender.track to track. | |
| 3. Enable sending direction on the RTCRtpTransceiver associated | |
| with sender. | |
| */ | |
| test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const transceiver = pc.addTransceiver('audio', { direction: 'recvonly' }); | |
| assert_equals(transceiver.sender.track, null); | |
| assert_equals(transceiver.direction, 'recvonly'); | |
| const track = generateMediaStreamTrack('audio'); | |
| const sender = pc.addTrack(track); | |
| assert_equals(sender, transceiver.sender); | |
| assert_equals(sender.track, track); | |
| assert_equals(transceiver.direction, 'sendrecv'); | |
| assert_array_equals([sender], pc.getSenders()); | |
| }, 'addTrack with existing sender with null track, same kind, and recvonly direction should reuse sender'); | |
| test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const transceiver = pc.addTransceiver('audio'); | |
| assert_equals(transceiver.sender.track, null); | |
| assert_equals(transceiver.direction, 'sendrecv'); | |
| const track = generateMediaStreamTrack('audio'); | |
| const sender = pc.addTrack(track); | |
| assert_equals(sender.track, track); | |
| assert_not_equals(sender, transceiver.sender); | |
| const senders = pc.getSenders(); | |
| assert_equals(senders.length, 2, | |
| 'Expect 2 senders added to connection'); | |
| assert_true(senders.includes(sender), | |
| 'Expect senders list to include sender'); | |
| assert_true(senders.includes(transceiver.sender), | |
| `Expect senders list to include first transceiver's sender`); | |
| }, 'addTrack with existing sender with null track, same kind, and sendrecv direction should create new sender'); | |
| test(t => { | |
| const pc = new RTCPeerConnection(); | |
| const transceiver = pc.addTransceiver('video', { direction: 'recvonly' }); | |
| assert_equals(transceiver.sender.track, null); | |
| assert_equals(transceiver.direction, 'recvonly'); | |
| const track = generateMediaStreamTrack('audio'); | |
| const sender = pc.addTrack(track); | |
| assert_equals(sender.track, track); | |
| assert_not_equals(sender, transceiver.sender); | |
| const senders = pc.getSenders(); | |
| assert_equals(senders.length, 2, | |
| 'Expect 2 senders added to connection'); | |
| assert_true(senders.includes(sender), | |
| 'Expect senders list to include sender'); | |
| assert_true(senders.includes(transceiver.sender), | |
| `Expect senders list to include first transceiver's sender`); | |
| }, 'addTrack with existing sender with null track, different kind, and recvonly direction should create new sender'); | |
| /* | |
| TODO | |
| 5.1. addTrack | |
| 3. Let streams be a list of MediaStream objects constructed from the | |
| method's remaining arguments, or an empty list if the method was | |
| called with a single argument. | |
| 6. The steps below describe how to determine if an existing sender can | |
| be reused. Doing so will cause future calls to createOffer and | |
| createAnswer to mark the corresponding media description as sendrecv | |
| or sendonly and add the MSID of the track added, as defined in [JSEP] | |
| (section 5.2.2. and section 5.3.2.). | |
| 9. A track could have contents that are inaccessible to the application. | |
| This can be due to being marked with a peerIdentity option or anything | |
| that would make a track CORS cross-origin. These tracks can be supplied | |
| to the addTrack method, and have an RTCRtpSender created for them, but | |
| content must not be transmitted, unless they are also marked with | |
| peerIdentity and they meet the requirements for sending (see isolated | |
| streams and RTCPeerConnection). | |
| All other tracks that are not accessible to the application must not be | |
| sent to the peer, with silence (audio), black frames (video) or | |
| equivalently absent content being sent in place of track content. | |
| Note that this property can change over time. | |
| Non-Testable | |
| 5.1. addTrack | |
| 7. If sender is not null, run the following steps to use that sender: | |
| 2. Set sender's [[associated MediaStreams]] to streams. | |
| Tested in RTCPeerConnection-onnegotiationneeded.html: | |
| 5.1. addTrack | |
| 10. Update the negotiation-needed flag for connection. | |
| */ | |
| </script> |