tor-browser

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

RTCPeerConnection-insertable-streams-audio.https.html (8239B)


      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4 <title>RTCPeerConnection Insertable Streams Audio</title>
      5 <script src="/resources/testharness.js"></script>
      6 <script src="/resources/testharnessreport.js"></script>
      7 <script src=/resources/testdriver.js></script>
      8 <script src=/resources/testdriver-vendor.js></script>
      9 <script src='../../mediacapture-streams/permission-helper.js'></script>
     10 <script src="../../webrtc/RTCPeerConnection-helper.js"></script>
     11 <script src="./RTCPeerConnection-insertable-streams.js"></script>
     12 </head>
     13 <body>
     14 <script>
     15 async function testAudioFlow(t, negotiationFunction, setConstructorParam, perFrameCallback = () => {}) {
     16  const caller = new RTCPeerConnection(setConstructorParam ? {encodedInsertableStreams:true} : {});
     17  t.add_cleanup(() => caller.close());
     18  const callee = new RTCPeerConnection(setConstructorParam ? {encodedInsertableStreams:true} : {});
     19  t.add_cleanup(() => callee.close());
     20 
     21  await setMediaPermission("granted", ["microphone"]);
     22  const stream = await navigator.mediaDevices.getUserMedia({audio:true});
     23  const audioTrack = stream.getAudioTracks()[0];
     24  t.add_cleanup(() => audioTrack.stop());
     25 
     26  const audioSender = caller.addTrack(audioTrack)
     27  const senderStreams = audioSender.createEncodedStreams();
     28  const senderReader = senderStreams.readable.getReader();
     29  const senderWriter = senderStreams.writable.getWriter();
     30 
     31  const frameInfos = [];
     32  const numFramesPassthrough = 5;
     33  const numFramesReplaceData = 5;
     34  const numFramesModifyData = 5;
     35  const numFramesToSend = numFramesPassthrough + numFramesReplaceData + numFramesModifyData;
     36 
     37  let streamsCreatedAtNegotiation;
     38 
     39  const ontrackPromise = new Promise(resolve => {
     40    callee.ontrack = t.step_func(() => {
     41      const audioReceiver = callee.getReceivers().find(r => r.track.kind === 'audio');
     42      assert_not_equals(audioReceiver, undefined);
     43 
     44      let receiverReader;
     45      let receiverWriter;
     46      if (streamsCreatedAtNegotiation) {
     47        const audioStreams = streamsCreatedAtNegotiation.find(r => r.kind === 'audio');
     48        assert_true(!!audioStreams);
     49        receiverReader = audioStreams.streams.readable.getReader();
     50        receiverWriter = audioStreams.streams.writable.getWriter();
     51      } else {
     52        const receiverStreams =
     53          audioReceiver.createEncodedStreams();
     54        receiverReader = receiverStreams.readable.getReader();
     55        receiverWriter = receiverStreams.writable.getWriter();
     56      }
     57 
     58      const maxFramesToReceive = numFramesToSend;
     59      let numVerifiedFrames = 0;
     60      for (let i = 0; i < maxFramesToReceive; i++) {
     61        receiverReader.read().then(t.step_func(result => {
     62          if (frameInfos[numVerifiedFrames] &&
     63              areFrameInfosEqual(result.value, frameInfos[numVerifiedFrames])) {
     64            numVerifiedFrames++;
     65          } else {
     66            // Receiving unexpected frames is an indication that
     67            // frames are not passed correctly between sender and receiver.
     68            assert_unreached("Incorrect frame received");
     69          }
     70          assert_not_equals(result.value.getMetadata().sequenceNumber, undefined);
     71 
     72          if (numVerifiedFrames == numFramesToSend)
     73            resolve();
     74        }));
     75      }
     76    });
     77  });
     78 
     79  exchangeIceCandidates(caller, callee);
     80  await negotiationFunction(caller, callee, (streams) => {streamsCreatedAtNegotiation = streams;});
     81 
     82  // Pass frames as they come from the encoder.
     83  for (let i = 0; i < numFramesPassthrough; i++) {
     84    const result = await senderReader.read();
     85    const frame = result.value;
     86    perFrameCallback(frame);
     87    frameInfos.push({
     88      data: frame.data,
     89      timestamp: frame.timestamp,
     90      type: frame.type,
     91      metadata: frame.getMetadata(),
     92      getMetadata() { return this.metadata; }
     93    });
     94    senderWriter.write(frame);
     95  }
     96 
     97  // Replace frame data with arbitrary buffers.
     98  for (let i = 0; i < numFramesReplaceData; i++) {
     99    const result = await senderReader.read()
    100    const frame = result.value;
    101 
    102    const buffer = new ArrayBuffer(100);
    103    const int8View = new Int8Array(buffer);
    104    int8View.fill(i);
    105 
    106    frame.data = buffer;
    107    perFrameCallback(frame);
    108    frameInfos.push({
    109      data: frame.data,
    110      timestamp: frame.timestamp,
    111      type: frame.type,
    112      metadata: frame.getMetadata(),
    113      getMetadata() { return this.metadata; }
    114    });
    115    senderWriter.write(frame);
    116  }
    117 
    118  // Modify frame data.
    119  for (let i = 0; i < numFramesReplaceData; i++) {
    120    const result = await senderReader.read()
    121    const frame = result.value;
    122    const int8View = new Int8Array(frame.data);
    123    int8View.fill(i);
    124 
    125    perFrameCallback(frame);
    126    frameInfos.push({
    127      data: frame.data,
    128      timestamp: frame.timestamp,
    129      type: frame.type,
    130      metadata: frame.getMetadata(),
    131      getMetadata() { return this.metadata; }
    132    });
    133    senderWriter.write(frame);
    134  }
    135 
    136  return ontrackPromise;
    137 }
    138 
    139 for (const setConstructorParam of [false, true]) {
    140  promise_test(async t => {
    141    return testAudioFlow(t, exchangeOfferAnswer, setConstructorParam);
    142  }, 'Frames flow correctly using insertable streams' + (setConstructorParam ? ' with param' : ''));
    143 
    144  promise_test(async t => {
    145    return testAudioFlow(t, exchangeOfferAnswerReverse, setConstructorParam);
    146  }, 'Frames flow correctly using insertable streams when receiver starts negotiation' + (setConstructorParam ? ' with param' : ''));
    147 }
    148 
    149 promise_test(async t => {
    150  const caller = new RTCPeerConnection({encodedInsertableStreams:true});
    151  t.add_cleanup(() => caller.close());
    152  const callee = new RTCPeerConnection();
    153  t.add_cleanup(() => callee.close());
    154 
    155  const stream = await navigator.mediaDevices.getUserMedia({audio:true});
    156  const track = stream.getTracks()[0];
    157  t.add_cleanup(() => track.stop());
    158 
    159  const sender = caller.addTrack(track)
    160  const streams = sender.createEncodedStreams();
    161  const transformer = new TransformStream({
    162    transform(frame, controller) {
    163      // Inserting the same frame twice will result in failure since the frame
    164      // will be neutered after the first insertion is processed.
    165      controller.enqueue(frame);
    166      controller.enqueue(frame);
    167    }
    168  });
    169 
    170  exchangeIceCandidates(caller, callee);
    171  await exchangeOfferAnswer(caller, callee);
    172 
    173  await promise_rejects_dom(
    174      t, 'OperationError',
    175      streams.readable.pipeThrough(transformer).pipeTo(streams.writable));
    176 }, 'Enqueuing the same frame twice fails');
    177 
    178 promise_test(async t => {
    179  const caller = new RTCPeerConnection({encodedInsertableStreams:true});
    180  t.add_cleanup(() => caller.close());
    181  const stream = await navigator.mediaDevices.getUserMedia({audio:true});
    182  const track = stream.getTracks()[0];
    183  t.add_cleanup(() => track.stop());
    184  const sender = caller.addTrack(track)
    185  sender.createEncodedStreams();
    186  assert_throws_dom("InvalidStateError", () => sender.createEncodedStreams());
    187 }, 'Creating streams twice throws');
    188 
    189 promise_test(async t => {
    190  let clonedFrames = [];
    191  function verifyFramesSerializeAndDeserialize(frame) {
    192    // Clone encoded frames using structedClone (ie serialize + deserialize) and
    193    // keep a reference.
    194    const clone = structuredClone(frame);
    195    clonedFrames.push(clone);
    196  };
    197 
    198  await testAudioFlow(
    199    t, exchangeOfferAnswer, /*setConstructorParam=*/false, verifyFramesSerializeAndDeserialize);
    200 
    201  // Ensure all of our cloned frames are still alive and well, despite the
    202  // originals having been sent through the PeerConnection.
    203  clonedFrames.forEach((clonedFrame) => {
    204    assert_not_equals(clonedFrame.data.size, 0);
    205    assert_not_equals(clonedFrame.timestamp, 0);
    206  });
    207 }, 'Encoded frames serialize and deserialize into a deep clone');
    208 
    209 promise_test(async t => {
    210  let clonedFrames = [];
    211  function rewriteFrameTimestamps(frame) {
    212    // Add 1 to the rtp timestamp of the frame.
    213    const metadata = frame.getMetadata();
    214    metadata.rtpTimestamp += 1;
    215    frame.setMetadata(metadata);
    216 
    217    assert_equals(frame.getMetadata().rtpTimestamp, metadata.rtpTimestamp)
    218  };
    219 
    220  // Run audio flows which will assert that the frames received have the
    221  // rtp timestamp set by our modification.
    222  await testAudioFlow(
    223    t, exchangeOfferAnswer, /*setConstructorParam=*/false, rewriteFrameTimestamps);
    224 }, 'Modifying rtp timestamp');
    225 
    226 </script>
    227 </body>
    228 </html>