tor-browser

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

RTCPeerConnection-ontrack.https.html (11512B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title>RTCPeerConnection.prototype.ontrack</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://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
     12 
     13  // The following helper functions are called from RTCPeerConnection-helper.js:
     14  //   getTrackFromUserMedia
     15 
     16  /*
     17    4.3.1.6.  Set the RTCSessionSessionDescription
     18      2.2.8.  If description is set as a remote description, then run the following
     19              steps for each media description in description:
     20        3.  Set transceiver's mid value to the mid of the corresponding media
     21            description. If the media description has no MID, and transceiver's
     22            mid is unset, generate a random value as described in [JSEP] (section 5.9.).
     23        4.  If the direction of the media description is sendrecv or sendonly, and
     24            transceiver.receiver.track has not yet been fired in a track event,
     25            process the remote track for the media description, given transceiver.
     26 
     27    5.1.1. Processing Remote MediaStreamTracks
     28      To process the remote track for an incoming media description [JSEP]
     29      (section 5.9.) given RTCRtpTransceiver transceiver, the user agent MUST
     30      run the following steps:
     31 
     32      1.  Let connection be the RTCPeerConnection object associated with transceiver.
     33      2.  Let streams be a list of MediaStream objects that the media description
     34          indicates the MediaStreamTrack belongs to.
     35      3.  Add track to all MediaStream objects in streams.
     36      4.  Queue a task to fire an event named track with transceiver, track, and
     37          streams at the connection object.
     38 
     39    5.7.  RTCTrackEvent
     40      [Constructor(DOMString type, RTCTrackEventInit eventInitDict)]
     41      interface RTCTrackEvent : Event {
     42        readonly attribute RTCRtpReceiver           receiver;
     43        readonly attribute MediaStreamTrack         track;
     44        [SameObject]
     45        readonly attribute FrozenArray<MediaStream> streams;
     46        readonly attribute RTCRtpTransceiver        transceiver;
     47      };
     48 
     49    [mediacapture-main]
     50    4.2.  MediaStream
     51      interface MediaStream : EventTarget {
     52        readonly attribute DOMString    id;
     53        sequence<MediaStreamTrack> getTracks();
     54        ...
     55      };
     56 
     57    [mediacapture-main]
     58    4.3.  MediaStreamTrack
     59      interface MediaStreamTrack : EventTarget {
     60        readonly attribute DOMString             kind;
     61        readonly attribute DOMString             id;
     62        ...
     63      };
     64   */
     65 
     66  function validateTrackEvent(trackEvent) {
     67    const { receiver, track, streams, transceiver } = trackEvent;
     68 
     69    assert_true(track instanceof MediaStreamTrack,
     70      'Expect track to be instance of MediaStreamTrack');
     71 
     72    assert_true(Array.isArray(streams),
     73      'Expect streams to be an array');
     74 
     75    for(const mediaStream of streams) {
     76      assert_true(mediaStream instanceof MediaStream,
     77        'Expect elements in streams to be instance of MediaStream');
     78 
     79      assert_true(mediaStream.getTracks().includes(track),
     80        'Expect each mediaStream to have track as one of their tracks');
     81    }
     82 
     83    assert_true(receiver instanceof RTCRtpReceiver,
     84      'Expect trackEvent.receiver to be defined and is instance of RTCRtpReceiver');
     85 
     86    assert_equals(receiver.track, track,
     87      'Expect trackEvent.receiver.track to be the same as trackEvent.track');
     88 
     89    assert_true(transceiver instanceof RTCRtpTransceiver,
     90      'Expect trackEvent.transceiver to be defined and is instance of RTCRtpTransceiver');
     91 
     92    assert_equals(transceiver.receiver, receiver,
     93      'Expect trackEvent.transceiver.receiver to be the same as trackEvent.receiver');
     94  }
     95 
     96  // tests that ontrack is called and parses the msid information from the SDP and creates
     97  // the streams with matching identifiers.
     98  promise_test(async t => {
     99    const pc = new RTCPeerConnection();
    100 
    101    t.add_cleanup(() => pc.close());
    102 
    103    // Fail the test if the ontrack event handler is not implemented
    104    assert_idl_attribute(pc, 'ontrack', 'Expect pc to have ontrack event handler attribute');
    105 
    106    const sdp = `v=0
    107 o=- 166855176514521964 2 IN IP4 127.0.0.1
    108 s=-
    109 t=0 0
    110 a=msid-semantic:WMS *
    111 m=audio 9 UDP/TLS/RTP/SAVPF 111
    112 c=IN IP4 0.0.0.0
    113 a=rtcp:9 IN IP4 0.0.0.0
    114 a=ice-ufrag:someufrag
    115 a=ice-pwd:somelongpwdwithenoughrandomness
    116 a=fingerprint:sha-256 8C:71:B3:8D:A5:38:FD:8F:A4:2E:A2:65:6C:86:52:BC:E0:6E:94:F2:9F:7C:4D:B5:DF:AF:AA:6F:44:90:8D:F4
    117 a=setup:actpass
    118 a=rtcp-mux
    119 a=mid:mid1
    120 a=sendonly
    121 a=rtpmap:111 opus/48000/2
    122 a=msid:stream1 track1
    123 a=ssrc:1001 cname:some
    124 `;
    125 
    126    const trackEventPromise = addEventListenerPromise(t, pc, 'track');
    127    await pc.setRemoteDescription({ type: 'offer', sdp });
    128    const trackEvent = await trackEventPromise;
    129    const { streams, track, transceiver } = trackEvent;
    130 
    131    assert_equals(streams.length, 1,
    132      'the track belongs to one MediaStream');
    133 
    134    const [stream] = streams;
    135    assert_equals(stream.id, 'stream1',
    136      'Expect stream.id to be the same as specified in the a=msid line');
    137 
    138    assert_equals(track.kind, 'audio',
    139      'Expect track.kind to be audio');
    140 
    141    validateTrackEvent(trackEvent);
    142 
    143    assert_equals(transceiver.direction, 'recvonly',
    144      'Expect transceiver.direction to be reverse of sendonly (recvonly)');
    145  }, 'setRemoteDescription should trigger ontrack event when the MSID of the stream is is parsed.');
    146 
    147  promise_test(async t => {
    148    const pc = new RTCPeerConnection();
    149 
    150    t.add_cleanup(() => pc.close());
    151 
    152    assert_idl_attribute(pc, 'ontrack', 'Expect pc to have ontrack event handler attribute');
    153 
    154    const sdp = `v=0
    155 o=- 166855176514521964 2 IN IP4 127.0.0.1
    156 s=-
    157 t=0 0
    158 a=msid-semantic:WMS *
    159 m=audio 9 UDP/TLS/RTP/SAVPF 111
    160 c=IN IP4 0.0.0.0
    161 a=rtcp:9 IN IP4 0.0.0.0
    162 a=ice-ufrag:someufrag
    163 a=ice-pwd:somelongpwdwithenoughrandomness
    164 a=fingerprint:sha-256 8C:71:B3:8D:A5:38:FD:8F:A4:2E:A2:65:6C:86:52:BC:E0:6E:94:F2:9F:7C:4D:B5:DF:AF:AA:6F:44:90:8D:F4
    165 a=setup:actpass
    166 a=rtcp-mux
    167 a=mid:mid1
    168 a=recvonly
    169 a=rtpmap:111 opus/48000/2
    170 a=msid:stream1 track1
    171 a=ssrc:1001 cname:some
    172 `;
    173 
    174    pc.ontrack = t.unreached_func('ontrack event should not fire for track with recvonly direction');
    175 
    176    await pc.setRemoteDescription({ type: 'offer', sdp });
    177    await new Promise(resolve => t.step_timeout(resolve, 100));
    178  }, 'setRemoteDescription() with m= line of recvonly direction should not trigger track event');
    179 
    180  promise_test(async t => {
    181    const pc1 = new RTCPeerConnection();
    182    t.add_cleanup(() => pc1.close());
    183    const pc2 = new RTCPeerConnection();
    184 
    185    t.add_cleanup(() => pc2.close());
    186 
    187    const [track, mediaStream] = await getTrackFromUserMedia('audio');
    188    pc1.addTrack(track, mediaStream);
    189    const trackEventPromise = addEventListenerPromise(t, pc2, 'track');
    190    await pc2.setRemoteDescription(await pc1.createOffer());
    191    const trackEvent = await trackEventPromise;
    192 
    193    assert_equals(trackEvent.track.kind, 'audio',
    194      'Expect track.kind to be audio');
    195 
    196    validateTrackEvent(trackEvent);
    197  }, 'addTrack() should cause remote connection to fire ontrack when setRemoteDescription()');
    198 
    199  promise_test(async t => {
    200    const pc1 = new RTCPeerConnection();
    201    t.add_cleanup(() => pc1.close());
    202    const pc2 = new RTCPeerConnection();
    203 
    204    t.add_cleanup(() => pc2.close());
    205 
    206    pc1.addTransceiver('video');
    207 
    208    const trackEventPromise = addEventListenerPromise(t, pc2, 'track');
    209    await pc2.setRemoteDescription(await pc1.createOffer());
    210    const trackEvent = await trackEventPromise;
    211    const { track } = trackEvent;
    212 
    213    assert_equals(track.kind, 'video',
    214      'Expect track.kind to be video');
    215 
    216    validateTrackEvent(trackEvent);
    217  }, `addTransceiver('video') should cause remote connection to fire ontrack when setRemoteDescription()`);
    218 
    219  promise_test(async t => {
    220    const pc1 = new RTCPeerConnection();
    221    t.add_cleanup(() => pc1.close());
    222    const pc2 = new RTCPeerConnection();
    223 
    224    t.add_cleanup(() => pc2.close());
    225 
    226    pc1.addTransceiver('audio', { direction: 'inactive' });
    227    pc2.ontrack = t.unreached_func('ontrack event should not fire for track with inactive direction');
    228 
    229    await pc2.setRemoteDescription(await pc1.createOffer());
    230    await new Promise(resolve => t.step_timeout(resolve, 100));
    231  }, `addTransceiver() with inactive direction should not cause remote connection to fire ontrack when setRemoteDescription()`);
    232 
    233  ["audio", "video"].forEach(type => promise_test(async t => {
    234    const pc1 = new RTCPeerConnection();
    235    t.add_cleanup(() => pc1.close());
    236    const pc2 = new RTCPeerConnection();
    237    t.add_cleanup(() => pc2.close());
    238 
    239    const checkNoUnexpectedTrack = ({track}) => {
    240      assert_equals(track.kind, type, `ontrack event should not fire for ${track.kind}`);
    241    };
    242 
    243    pc2.ontrack = t.step_func(checkNoUnexpectedTrack);
    244    pc1.ontrack = t.step_func(checkNoUnexpectedTrack);
    245 
    246    await pc1.setLocalDescription(await pc1.createOffer(
    247      { offerToReceiveVideo: true, offerToReceiveAudio: true }));
    248 
    249    pc2.addTrack(...await getTrackFromUserMedia(type));
    250 
    251    await pc2.setRemoteDescription(pc1.localDescription);
    252    await pc2.setLocalDescription(await pc2.createAnswer());
    253    await pc1.setRemoteDescription(pc2.localDescription);
    254 
    255    await new Promise(resolve => t.step_timeout(resolve, 100));
    256  }, `Using offerToReceiveAudio and offerToReceiveVideo should only cause a ${type} track event to fire, if ${type} was the only type negotiated`));
    257 
    258  promise_test(async t => {
    259    const pc1 = new RTCPeerConnection();
    260    t.add_cleanup(() => pc1.close());
    261    const pc2 = new RTCPeerConnection();
    262    t.add_cleanup(() => pc2.close());
    263 
    264    const stream = new MediaStream();
    265    const stream2 = new MediaStream();
    266    // audio-then-video.
    267    pc1.addTransceiver('audio', {streams: [stream]});
    268    pc1.addTransceiver('video', {streams: [stream]});
    269    // video-then-audio
    270    pc1.addTransceiver('video', {streams: [stream2]});
    271    pc1.addTransceiver('audio', {streams: [stream2]});
    272    const tracks = [];
    273    pc2.ontrack = (e) => tracks.push(e.track)
    274 
    275    await pc2.setRemoteDescription(await pc1.createOffer());
    276    assert_equals(tracks.length, 4, 'ontrack should have fired twice');
    277    assert_equals(tracks[0].kind, 'audio', 'first ontrack fires with an audio track');
    278    assert_equals(tracks[1].kind, 'video', 'second ontrack fires with a video track');
    279    assert_equals(tracks[2].kind, 'video', 'third ontrack fires with a video track');
    280    assert_equals(tracks[3].kind, 'audio', 'fourth ontrack fires with an audio track');
    281  }, `addTransceiver order of kinds is retained in ontrack at the receiver`);
    282 
    283  promise_test(async t => {
    284    const pc = new RTCPeerConnection();
    285    t.add_cleanup(() => pc.close());
    286 
    287    const sdp = `v=0
    288 o=- 166855176514521964 2 IN IP4 127.0.0.1
    289 s=-
    290 t=0 0
    291 a=msid-semantic:WMS *
    292 m=audio 0 UDP/TLS/RTP/SAVPF 111
    293 c=IN IP4 0.0.0.0
    294 a=rtcp:9 IN IP4 0.0.0.0
    295 a=ice-ufrag:someufrag
    296 a=ice-pwd:somelongpwdwithenoughrandomness
    297 a=fingerprint:sha-256 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
    298 a=setup:actpass
    299 a=mid:0
    300 a=sendonly
    301 a=rtcp-mux
    302 a=rtpmap:111 opus/48000/2
    303 a=ssrc:1001 cname:some
    304 `;
    305    pc.ontrack = t.unreached_func('ontrack event should not fire for track in a rejected media section');
    306 
    307    await pc.setRemoteDescription({type: 'offer', sdp});
    308  }, `ontrack should not fire for rejected media sections`);
    309 </script>