tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

RTCPeerConnection-addTrack.https.html (16097B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title>RTCPeerConnection.prototype.addTrack</title>
      4 <script src="/resources/testharness.js"></script>
      5 <script src="/resources/testharnessreport.js"></script>
      6 <script src=/resources/testdriver.js></script>
      7 <script src=/resources/testdriver-vendor.js></script>
      8 <script src='../mediacapture-streams/permission-helper.js'></script>
      9 <script src="RTCPeerConnection-helper.js"></script>
     10 <script>
     11  'use strict';
     12 
     13  // Test is based on the following editor draft:
     14  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
     15 
     16  // The following helper functions are called from RTCPeerConnection-helper.js:
     17  //   getNoiseStream()
     18 
     19  /*
     20    5.1.  RTCPeerConnection Interface Extensions
     21      partial interface RTCPeerConnection {
     22        ...
     23        sequence<RTCRtpSender>      getSenders();
     24        sequence<RTCRtpReceiver>    getReceivers();
     25        sequence<RTCRtpTransceiver> getTransceivers();
     26        RTCRtpSender                addTrack(MediaStreamTrack track,
     27                                             MediaStream... streams);
     28        RTCRtpTransceiver           addTransceiver((MediaStreamTrack or DOMString) trackOrKind,
     29                                                   optional RTCRtpTransceiverInit init);
     30      };
     31 
     32      Note
     33        While addTrack checks if the MediaStreamTrack given as an argument is
     34        already being sent to avoid sending the same MediaStreamTrack twice,
     35        the other ways do not, allowing the same MediaStreamTrack to be sent
     36        several times simultaneously.
     37   */
     38 
     39  /*
     40    5.1.  addTrack
     41      4.  If connection's [[isClosed]] slot is true, throw an InvalidStateError.
     42   */
     43  promise_test(async t => {
     44    const pc = new RTCPeerConnection();
     45    t.add_cleanup(() => pc.close());
     46 
     47    const stream = await getNoiseStream({ audio: true });
     48    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     49    const [track] = stream.getTracks();
     50 
     51    pc.close();
     52    assert_throws_dom('InvalidStateError', () => pc.addTrack(track, stream))
     53  }, 'addTrack when pc is closed should throw InvalidStateError');
     54 
     55  /*
     56    5.1.  addTrack
     57      8.  If sender is null, run the following steps:
     58          1.  Create an RTCRtpSender with track and streams and let sender be
     59              the result.
     60          2.  Create an RTCRtpReceiver with track.kind as kind and let receiver
     61              be the result.
     62          3.  Create an RTCRtpTransceiver with sender and receiver and let
     63              transceiver be the result.
     64          4.  Add transceiver to connection's set of transceivers.
     65   */
     66  promise_test(async t => {
     67    const pc = new RTCPeerConnection();
     68    t.add_cleanup(() => pc.close());
     69 
     70    const stream = await getNoiseStream({ audio: true });
     71    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     72    const [track] = stream.getTracks();
     73 
     74    const sender = pc.addTrack(track);
     75 
     76    assert_true(sender instanceof RTCRtpSender,
     77      'Expect sender to be instance of RTCRtpSender');
     78 
     79    assert_equals(sender.track, track,
     80      `Expect sender's track to be the added track`);
     81 
     82    const transceivers = pc.getTransceivers();
     83    assert_equals(transceivers.length, 1,
     84      'Expect only one transceiver with sender added');
     85 
     86    const [transceiver] = transceivers;
     87    assert_equals(transceiver.sender, sender);
     88 
     89    assert_array_equals([sender], pc.getSenders(),
     90      'Expect only one sender with given track added');
     91 
     92    const { receiver } = transceiver;
     93    assert_equals(receiver.track.kind, 'audio');
     94    assert_array_equals([transceiver.receiver], pc.getReceivers(),
     95      'Expect only one receiver associated with transceiver added');
     96  }, 'addTrack with single track argument and no stream should succeed');
     97 
     98  promise_test(async t => {
     99    const pc = new RTCPeerConnection();
    100    t.add_cleanup(() => pc.close());
    101 
    102    const stream = await getNoiseStream({ audio: true });
    103    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    104    const [track] = stream.getTracks();
    105 
    106    const sender = pc.addTrack(track, stream);
    107 
    108    assert_true(sender instanceof RTCRtpSender,
    109      'Expect sender to be instance of RTCRtpSender');
    110 
    111    assert_equals(sender.track, track,
    112      `Expect sender's track to be the added track`);
    113  }, 'addTrack with single track argument and single stream should succeed');
    114 
    115  promise_test(async t => {
    116    const pc = new RTCPeerConnection();
    117    t.add_cleanup(() => pc.close());
    118 
    119    const stream = await getNoiseStream({ audio: true });
    120    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    121    const [track] = stream.getTracks();
    122 
    123    const stream2 = new MediaStream([track]);
    124    const sender = pc.addTrack(track, stream, stream2);
    125 
    126    assert_true(sender instanceof RTCRtpSender,
    127      'Expect sender to be instance of RTCRtpSender');
    128 
    129    assert_equals(sender.track, track,
    130      `Expect sender's track to be the added track`);
    131  }, 'addTrack with single track argument and multiple streams should succeed');
    132 
    133  /*
    134    5.1.  addTrack
    135      5.  Let senders be the result of executing the CollectSenders algorithm.
    136          If an RTCRtpSender for track already exists in senders, throw an
    137          InvalidAccessError.
    138   */
    139  promise_test(async t => {
    140    const pc = new RTCPeerConnection();
    141    t.add_cleanup(() => pc.close());
    142 
    143    const stream = await getNoiseStream({ audio: true });
    144    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    145    const [track] = stream.getTracks();
    146 
    147    pc.addTrack(track, stream);
    148    assert_throws_dom('InvalidAccessError', () => pc.addTrack(track, stream));
    149  }, 'Adding the same track multiple times should throw InvalidAccessError');
    150 
    151  /*
    152    5.1.  addTrack
    153      6.  The steps below describe how to determine if an existing sender can
    154          be reused.
    155 
    156          If any RTCRtpSender object in senders matches all the following
    157          criteria, let sender be that object, or null otherwise:
    158            - The sender's track is null.
    159            - The transceiver kind of the RTCRtpTransceiver, associated with
    160              the sender, matches track's kind.
    161            - The sender has never been used to send. More precisely, the
    162              RTCRtpTransceiver associated with the sender has never had a
    163              currentDirection of sendrecv or sendonly.
    164      7.  If sender is not null, run the following steps to use that sender:
    165          1.  Set sender.track to track.
    166          3.  Enable sending direction on the RTCRtpTransceiver associated
    167              with sender.
    168   */
    169  promise_test(async t => {
    170    const pc = new RTCPeerConnection();
    171    t.add_cleanup(() => pc.close());
    172 
    173    const transceiver = pc.addTransceiver('audio', { direction: 'recvonly' });
    174    assert_equals(transceiver.sender.track, null);
    175    assert_equals(transceiver.direction, 'recvonly');
    176 
    177    await setMediaPermission("granted", ["microphone"]);
    178    const stream = await navigator.mediaDevices.getUserMedia({audio: true});
    179    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    180    const [track] = stream.getTracks();
    181    const sender = pc.addTrack(track);
    182 
    183    assert_equals(sender, transceiver.sender);
    184    assert_equals(sender.track, track);
    185    assert_equals(transceiver.direction, 'sendrecv');
    186    assert_array_equals([sender], pc.getSenders());
    187  }, 'addTrack with existing sender with null track, same kind, and recvonly direction should reuse sender');
    188 
    189  promise_test(async t => {
    190    const pc = new RTCPeerConnection();
    191    t.add_cleanup(() => pc.close());
    192 
    193    const transceiver = pc.addTransceiver('audio');
    194    assert_equals(transceiver.sender.track, null);
    195    assert_equals(transceiver.direction, 'sendrecv');
    196 
    197    const stream = await getNoiseStream({audio: true});
    198    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    199    const [track] = stream.getTracks();
    200    const sender = pc.addTrack(track);
    201 
    202    assert_equals(sender.track, track);
    203    assert_equals(sender, transceiver.sender);
    204  }, 'addTrack with existing sender that has not been used to send should reuse the sender');
    205 
    206  promise_test(async t => {
    207    const caller = new RTCPeerConnection();
    208    t.add_cleanup(() => caller.close());
    209    const callee = new RTCPeerConnection();
    210    t.add_cleanup(() => callee.close());
    211 
    212    const stream = await getNoiseStream({audio: true});
    213    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    214    const [track] = stream.getTracks();
    215    const transceiver = caller.addTransceiver(track);
    216    {
    217      const offer = await caller.createOffer();
    218      await caller.setLocalDescription(offer);
    219      await callee.setRemoteDescription(offer);
    220      const answer = await callee.createAnswer();
    221      await callee.setLocalDescription(answer);
    222      await caller.setRemoteDescription(answer);
    223    }
    224    assert_equals(transceiver.currentDirection, 'sendonly');
    225 
    226    caller.removeTrack(transceiver.sender);
    227    {
    228      const offer = await caller.createOffer();
    229      await caller.setLocalDescription(offer);
    230      await callee.setRemoteDescription(offer);
    231      const answer = await callee.createAnswer();
    232      await callee.setLocalDescription(answer);
    233      await caller.setRemoteDescription(answer);
    234    }
    235    assert_equals(transceiver.direction, 'recvonly');
    236    assert_equals(transceiver.currentDirection, 'inactive');
    237 
    238    // |transceiver.sender| is currently not used for sending, but it should not
    239    // be reused because it has been used for sending before.
    240    const sender = caller.addTrack(track);
    241    assert_true(sender != null);
    242    assert_not_equals(sender, transceiver.sender);
    243  }, 'addTrack with existing sender that has been used to send should create new sender');
    244 
    245  promise_test(async t => {
    246    const pc = new RTCPeerConnection();
    247    t.add_cleanup(() => pc.close());
    248 
    249    const transceiver = pc.addTransceiver('video', { direction: 'recvonly' });
    250    assert_equals(transceiver.sender.track, null);
    251    assert_equals(transceiver.direction, 'recvonly');
    252 
    253    const stream = await getNoiseStream({audio: true});
    254    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    255    const [track] = stream.getTracks();
    256    const sender = pc.addTrack(track);
    257 
    258    assert_equals(sender.track, track);
    259    assert_not_equals(sender, transceiver.sender);
    260 
    261    const senders = pc.getSenders();
    262    assert_equals(senders.length, 2,
    263      'Expect 2 senders added to connection');
    264 
    265    assert_true(senders.includes(sender),
    266      'Expect senders list to include sender');
    267 
    268    assert_true(senders.includes(transceiver.sender),
    269      `Expect senders list to include first transceiver's sender`);
    270  }, 'addTrack with existing sender with null track, different kind, and recvonly direction should create new sender');
    271 
    272  /*
    273    TODO
    274      5.1.  addTrack
    275        3.  Let streams be a list of MediaStream objects constructed from the
    276            method's remaining arguments, or an empty list if the method was
    277            called with a single argument.
    278        6.  The steps below describe how to determine if an existing sender can
    279            be reused. Doing so will cause future calls to createOffer and
    280            createAnswer to mark the corresponding media description as sendrecv
    281            or sendonly and add the MSID of the track added, as defined in [JSEP]
    282            (section 5.2.2. and section 5.3.2.).
    283 
    284    Non-Testable
    285      5.1.  addTrack
    286        7.  If sender is not null, run the following steps to use that sender:
    287          2.  Set sender's [[associated MediaStreams]] to streams.
    288 
    289    Tested in RTCPeerConnection-onnegotiationneeded.html:
    290      5.1. addTrack
    291        10. Update the negotiation-needed flag for connection.
    292 
    293  */
    294 
    295  promise_test(async t => {
    296    const caller = new RTCPeerConnection();
    297    t.add_cleanup(() => caller.close());
    298    const callee = new RTCPeerConnection();
    299    t.add_cleanup(() => callee.close());
    300 
    301    const stream = await getNoiseStream({audio: true});
    302    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    303    const [track] = stream.getTracks();
    304    const transceiver = caller.addTransceiver(track);
    305    // Note that this test doesn't process canididates.
    306    {
    307      const offer = await caller.createOffer();
    308      await caller.setLocalDescription(offer);
    309      await callee.setRemoteDescription(offer);
    310      const answer = await callee.createAnswer();
    311      await callee.setLocalDescription(answer);
    312      await caller.setRemoteDescription(answer);
    313    }
    314    assert_equals(transceiver.currentDirection, 'sendonly');
    315    await waitForIceGatheringState(caller, ['complete']);
    316    await waitForIceGatheringState(callee, ['complete']);
    317 
    318    const second_stream = await getNoiseStream({audio: true});
    319    t.add_cleanup(() => second_stream.getTracks().forEach(track => track.stop()));
    320    // There may be callee candidates in flight. It seems that waiting
    321    // for a createOffer() is enough time to let them complete processing.
    322    // TODO(https://crbug.com/webrtc/13095): Fix bug and remove.
    323    await caller.createOffer();
    324 
    325    const [second_track] = second_stream.getTracks();
    326    caller.onicecandidate = t.unreached_func(
    327      'No caller candidates should be generated.');
    328    callee.onicecandidate = t.unreached_func(
    329      'No callee candidates should be generated.');
    330    caller.addTrack(second_track);
    331    {
    332      const offer = await caller.createOffer();
    333      await caller.setLocalDescription(offer);
    334      await callee.setRemoteDescription(offer);
    335      const answer = await callee.createAnswer();
    336      await callee.setLocalDescription(answer);
    337      await caller.setRemoteDescription(answer);
    338    }
    339    // Check that we're bundled.
    340    const [first_transceiver, second_transceiver] = caller.getTransceivers();
    341    assert_equals(first_transceiver.transport, second_transceiver.transport);
    342 
    343  }, 'Adding more tracks does not generate more candidates if bundled');
    344 
    345  promise_test(async t => {
    346    const pc1 = new RTCPeerConnection();
    347    t.add_cleanup(() => pc1.close());
    348    const pc2 = new RTCPeerConnection();
    349    t.add_cleanup(() => pc2.close());
    350 
    351    const stream = await getNoiseStream({ audio: true });
    352    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    353    const [track] = stream.getTracks();
    354 
    355    pc1.addTrack(track);
    356    const offer = await pc1.createOffer();
    357    // We do not await here; we want to ensure that the transceiver this creates
    358    // is untouched by addTrack, and that addTrack creates _another_ transceiver
    359    const srdPromise = pc2.setRemoteDescription(offer);
    360 
    361    const sender = pc2.addTrack(track);
    362 
    363    await srdPromise;
    364 
    365    assert_equals(pc2.getTransceivers().length, 1, "Should have 1 transceiver");
    366    assert_equals(pc2.getTransceivers()[0].sender, sender, "The transceiver should be the one added by addTrack");
    367  }, 'Calling addTrack while sRD(offer) is pending should allow the new remote transceiver to be the same one that addTrack creates');
    368 
    369  promise_test(async t => {
    370    const pc1 = new RTCPeerConnection();
    371    t.add_cleanup(() => pc1.close());
    372    const pc2 = new RTCPeerConnection();
    373    t.add_cleanup(() => pc2.close());
    374    pc1.addTransceiver('video');
    375 
    376    const stream = await getNoiseStream({ audio: true });
    377    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    378    const [track] = stream.getTracks();
    379 
    380    const offer = await pc1.createOffer();
    381    const srdPromise = pc2.setRemoteDescription(offer);
    382    assert_equals(pc2.getTransceivers().length, 0);
    383    pc2.addTrack(track);
    384    assert_equals(pc2.getTransceivers().length, 1);
    385    const transceiver0 = pc2.getTransceivers()[0];
    386    assert_equals(transceiver0.mid, null);
    387    await srdPromise;
    388    assert_equals(pc2.getTransceivers().length, 2);
    389    const transceiver1 = pc2.getTransceivers()[1];
    390    assert_equals(transceiver0.mid, null);
    391    assert_not_equals(transceiver1.mid, null);
    392  }, 'When addTrack is called while sRD is in progress, and both addTrack and sRD add a transceiver of different media types, the addTrack transceiver should come first, and then the sRD transceiver.');
    393 
    394 </script>