tor-browser

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

RTCRtpSender-replaceTrack.https.html (12851B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <meta name="timeout" content="long">
      4 <title>RTCRtpSender.prototype.replaceTrack</title>
      5 <script src="/resources/testharness.js"></script>
      6 <script src="/resources/testharnessreport.js"></script>
      7 <script src="RTCPeerConnection-helper.js"></script>
      8 <script>
      9  'use strict';
     10 
     11  // Test is based on the following editor draft:
     12  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
     13 
     14  /*
     15    5.2.  RTCRtpSender Interface
     16      interface RTCRtpSender {
     17        readonly attribute MediaStreamTrack? track;
     18        Promise<void>           replaceTrack(MediaStreamTrack? withTrack);
     19        ...
     20      };
     21 
     22      replaceTrack
     23        Attempts to replace the track being sent with another track provided
     24        (or with a null track), without renegotiation.
     25   */
     26 
     27  /*
     28    5.2.  replaceTrack
     29      4.  If connection's [[isClosed]] slot is true, return a promise rejected
     30          with a newly created InvalidStateError and abort these steps.
     31   */
     32  promise_test(async t => {
     33    const pc = new RTCPeerConnection();
     34    t.add_cleanup(() => pc.close());
     35    const stream = await getNoiseStream({audio: true});
     36    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     37    const [track] = stream.getTracks();
     38 
     39    const transceiver = pc.addTransceiver('audio');
     40    const { sender } = transceiver;
     41    pc.close();
     42 
     43    return promise_rejects_dom(t, 'InvalidStateError',
     44      sender.replaceTrack(track));
     45  }, 'Calling replaceTrack on closed connection should reject with InvalidStateError');
     46 
     47  /*
     48    5.2.  replaceTrack
     49      7.  If withTrack is non-null and withTrack.kind differs from the
     50          transceiver kind of transceiver, return a promise rejected with a
     51          newly created TypeError.
     52   */
     53  promise_test(async t => {
     54    const pc = new RTCPeerConnection();
     55    t.add_cleanup(() => pc.close());
     56    const stream = await getNoiseStream({video: true});
     57    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     58    const [track] = stream.getTracks();
     59 
     60    const transceiver = pc.addTransceiver('audio');
     61    const { sender } = transceiver;
     62 
     63    return promise_rejects_js(t, TypeError,
     64      sender.replaceTrack(track));
     65  }, 'Calling replaceTrack with track of different kind should reject with TypeError');
     66 
     67  /*
     68    5.2.  replaceTrack
     69      5.  If transceiver.stopped is true, return a promise rejected with a newly
     70          created InvalidStateError.
     71   */
     72  promise_test(async t => {
     73    const pc = new RTCPeerConnection();
     74    t.add_cleanup(() => pc.close());
     75    const stream = await getNoiseStream({audio: true});
     76    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     77    const [track] = stream.getTracks();
     78 
     79    const transceiver = pc.addTransceiver('audio');
     80    const { sender } = transceiver;
     81    transceiver.stop();
     82    return promise_rejects_dom(t, 'InvalidStateError',
     83      sender.replaceTrack(track));
     84  }, 'Calling replaceTrack on stopped sender should reject with InvalidStateError');
     85 
     86  /*
     87    5.2.  replaceTrack
     88      8.  If transceiver is not yet associated with a media description [JSEP]
     89          (section 3.4.1.), then set sender's track attribute to withTrack, and
     90          return a promise resolved with undefined.
     91   */
     92  promise_test(async t => {
     93    const pc = new RTCPeerConnection();
     94    t.add_cleanup(() => pc.close());
     95    const stream = await getNoiseStream({audio: true});
     96    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     97    const [track] = stream.getTracks();
     98 
     99    const transceiver = pc.addTransceiver('audio');
    100    const { sender } = transceiver;
    101    assert_equals(sender.track, null);
    102 
    103    return sender.replaceTrack(track)
    104    .then(() => {
    105      assert_equals(sender.track, track);
    106    });
    107  }, 'Calling replaceTrack on sender with null track and not set to session description should resolve with sender.track set to given track');
    108 
    109  promise_test(async t => {
    110    const pc = new RTCPeerConnection();
    111    t.add_cleanup(() => pc.close());
    112    const stream1 = await getNoiseStream({audio: true});
    113    t.add_cleanup(() => stream1.getTracks().forEach(track => track.stop()));
    114    const [track1] = stream1.getTracks();
    115    const stream2 = await getNoiseStream({audio: true});
    116    t.add_cleanup(() => stream2.getTracks().forEach(track => track.stop()));
    117    const [track2] = stream2.getTracks();
    118 
    119    const transceiver = pc.addTransceiver(track1);
    120    const { sender } = transceiver;
    121 
    122    assert_equals(sender.track, track1);
    123 
    124    return sender.replaceTrack(track2)
    125    .then(() => {
    126      assert_equals(sender.track, track2);
    127    });
    128  }, 'Calling replaceTrack on sender not set to session description should resolve with sender.track set to given track');
    129 
    130  promise_test(async t => {
    131    const pc = new RTCPeerConnection();
    132    t.add_cleanup(() => pc.close());
    133    const stream = await getNoiseStream({audio: true});
    134    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    135    const [track] = stream.getTracks();
    136 
    137    const transceiver = pc.addTransceiver(track);
    138    const { sender } = transceiver;
    139 
    140    assert_equals(sender.track, track);
    141 
    142    return sender.replaceTrack(null)
    143    .then(() => {
    144      assert_equals(sender.track, null);
    145    });
    146  }, 'Calling replaceTrack(null) on sender not set to session description should resolve with sender.track set to null');
    147 
    148  /*
    149    5.2.  replaceTrack
    150      10. Run the following steps in parallel:
    151          1.  Determine if negotiation is needed to transmit withTrack in place
    152              of the sender's existing track.
    153 
    154              Negotiation is not needed if withTrack is null.
    155 
    156          3.  Queue a task that runs the following steps:
    157              2.  Set sender's track attribute to withTrack.
    158   */
    159  promise_test(async t => {
    160    const pc = new RTCPeerConnection();
    161    t.add_cleanup(() => pc.close());
    162    const stream = await getNoiseStream({audio: true});
    163    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    164    const [track] = stream.getTracks();
    165 
    166    const transceiver = pc.addTransceiver(track);
    167    const { sender } = transceiver;
    168 
    169    assert_equals(sender.track, track);
    170 
    171    return pc.createOffer()
    172    .then(offer => pc.setLocalDescription(offer))
    173    .then(() => sender.replaceTrack(null))
    174    .then(() => {
    175      assert_equals(sender.track, null);
    176    });
    177  }, 'Calling replaceTrack(null) on sender set to session description should resolve with sender.track set to null');
    178 
    179  /*
    180    5.2.  replaceTrack
    181      10. Run the following steps in parallel:
    182          1.  Determine if negotiation is needed to transmit withTrack in place
    183              of the sender's existing track.
    184 
    185              Negotiation is not needed if the sender's existing track is
    186              ended (which appears as though the track was muted).
    187 
    188          3.  Queue a task that runs the following steps:
    189              2.  Set sender's track attribute to withTrack.
    190   */
    191  promise_test(async t => {
    192    const pc = new RTCPeerConnection();
    193    t.add_cleanup(() => pc.close());
    194    const stream1 = await getNoiseStream({audio: true});
    195    t.add_cleanup(() => stream1.getTracks().forEach(track => track.stop()));
    196    const [track1] = stream1.getTracks();
    197    const stream2 = await getNoiseStream({audio: true});
    198    t.add_cleanup(() => stream2.getTracks().forEach(track => track.stop()));
    199    const [track2] = stream1.getTracks();
    200 
    201    const transceiver = pc.addTransceiver(track1);
    202    const { sender } = transceiver;
    203    assert_equals(sender.track, track1);
    204 
    205    track1.stop();
    206 
    207    return pc.createOffer()
    208    .then(offer => pc.setLocalDescription(offer))
    209    .then(() => sender.replaceTrack(track2))
    210    .then(() => {
    211      assert_equals(sender.track, track2);
    212    });
    213  }, 'Calling replaceTrack on sender with stopped track and and set to session description should resolve with sender.track set to given track');
    214 
    215  /*
    216    5.2.  replaceTrack
    217      10. Run the following steps in parallel:
    218          1.  Determine if negotiation is needed to transmit withTrack in place
    219              of the sender's existing track.
    220 
    221              (tracks generated with default parameters *should* be similar
    222              enough to not require re-negotiation)
    223 
    224          3.  Queue a task that runs the following steps:
    225              2.  Set sender's track attribute to withTrack.
    226   */
    227  promise_test(async t => {
    228    const pc = new RTCPeerConnection();
    229    t.add_cleanup(() => pc.close());
    230    const stream1 = await getNoiseStream({audio: true});
    231    t.add_cleanup(() => stream1.getTracks().forEach(track => track.stop()));
    232    const [track1] = stream1.getTracks();
    233    const stream2 = await getNoiseStream({audio: true});
    234    t.add_cleanup(() => stream2.getTracks().forEach(track => track.stop()));
    235    const [track2] = stream1.getTracks();
    236 
    237    const transceiver = pc.addTransceiver(track1);
    238    const { sender } = transceiver;
    239    assert_equals(sender.track, track1);
    240 
    241    return pc.createOffer()
    242    .then(offer => pc.setLocalDescription(offer))
    243    .then(() => sender.replaceTrack(track2))
    244    .then(() => {
    245      assert_equals(sender.track, track2);
    246    });
    247  }, 'Calling replaceTrack on sender with similar track and and set to session description should resolve with sender.track set to new track');
    248 
    249  /*
    250    TODO
    251      5.2.  replaceTrack
    252        To avoid track identifiers changing on the remote receiving end when
    253        a track is replaced, the sender must retain the original track
    254        identifier and stream associations and use these in subsequent
    255        negotiations.
    256 
    257    Non-Testable
    258      5.2.  replaceTrack
    259        10. Run the following steps in parallel:
    260            1.  Determine if negotiation is needed to transmit withTrack in place
    261                of the sender's existing track.
    262 
    263                Ignore which MediaStream the track resides in and the id attribute
    264                of the track in this determination.
    265 
    266                If negotiation is needed, then reject p with a newly created
    267                InvalidModificationError and abort these steps.
    268 
    269            2.  If withTrack is null, have the sender stop sending, without
    270                negotiating. Otherwise, have the sender switch seamlessly to
    271                transmitting withTrack instead of the sender's existing track,
    272                without negotiating.
    273            3.  Queue a task that runs the following steps:
    274              1.  If connection's [[isClosed]] slot is true, abort these steps.
    275  */
    276 
    277  promise_test(async t => {
    278    const v = document.createElement('video');
    279    v.autoplay = true;
    280    const pc1 = new RTCPeerConnection();
    281    t.add_cleanup(() => pc1.close());
    282    const pc2 = new RTCPeerConnection();
    283    t.add_cleanup(() => pc2.close());
    284    const stream1 = await getNoiseStream({video: {signal: 20}});
    285    t.add_cleanup(() => stream1.getTracks().forEach(track => track.stop()));
    286    const [track1] = stream1.getTracks();
    287    const stream2 = await getNoiseStream({video: {signal: 250}});
    288    t.add_cleanup(() => stream2.getTracks().forEach(track => track.stop()));
    289    const [track2] = stream2.getTracks();
    290    const sender = pc1.addTrack(track1);
    291    pc2.ontrack = (e) => {
    292      v.srcObject = new MediaStream([e.track]);
    293    };
    294    const metadataToBeLoaded = new Promise((resolve) => {
    295      v.addEventListener('loadedmetadata', () => {
    296        resolve();
    297      });
    298    });
    299    exchangeIceCandidates(pc1, pc2);
    300    exchangeOfferAnswer(pc1, pc2);
    301    await metadataToBeLoaded;
    302    await detectSignal(t, v, 20);
    303    await sender.replaceTrack(track2);
    304    await detectSignal(t, v, 250);
    305  }, 'ReplaceTrack transmits the new track not the old track');
    306 
    307  promise_test(async t => {
    308    const v = document.createElement('video');
    309    v.autoplay = true;
    310    const pc1 = new RTCPeerConnection();
    311    t.add_cleanup(() => pc1.close());
    312    const pc2 = new RTCPeerConnection();
    313    t.add_cleanup(() => pc2.close());
    314    const stream1 = await getNoiseStream({video: {signal: 20}});
    315    t.add_cleanup(() => stream1.getTracks().forEach(track => track.stop()));
    316    const [track1] = stream1.getTracks();
    317    const stream2 = await getNoiseStream({video: {signal: 250}});
    318    t.add_cleanup(() => stream2.getTracks().forEach(track => track.stop()));
    319    const [track2] = stream2.getTracks();
    320    const sender = pc1.addTrack(track1);
    321    pc2.ontrack = (e) => {
    322      v.srcObject = new MediaStream([e.track]);
    323    };
    324    const metadataToBeLoaded = new Promise((resolve) => {
    325      v.addEventListener('loadedmetadata', () => {
    326        resolve();
    327      });
    328    });
    329    exchangeIceCandidates(pc1, pc2);
    330    exchangeOfferAnswer(pc1, pc2);
    331    await metadataToBeLoaded;
    332    await detectSignal(t, v, 20);
    333    await sender.replaceTrack(null);
    334    await sender.replaceTrack(track2);
    335    await detectSignal(t, v, 250);
    336  }, 'ReplaceTrack null -> new track transmits the new track');
    337 </script>