tor-browser

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

peerconnection.js (5190B)


      1 /**
      2 * @fileoverview Utility functions for tests utilizing PeerConnections
      3 */
      4 
      5 /**
      6 * Exchanges offers and answers between two peer connections.
      7 *
      8 * pc1's offer is set as local description in pc1 and
      9 * remote description in pc2. After that, pc2's answer
     10 * is set as it's local description and remote description in pc1.
     11 *
     12 * @param {!RTCPeerConnection} pc1 The first peer connection.
     13 * @param {!RTCPeerConnection} pc2 The second peer connection.
     14 */
     15 async function exchangeOfferAnswer(pc1, pc2) {
     16  await pc1.setLocalDescription();
     17  await pc2.setRemoteDescription(pc1.localDescription);
     18  await pc2.setLocalDescription();
     19  await pc1.setRemoteDescription(pc2.localDescription);
     20 }
     21 
     22 /**
     23 * Sets the specified codec preference if it's included in the transceiver's
     24 * list of supported codecs.
     25 * @param {!RTCRtpTransceiver} transceiver The RTP transceiver.
     26 * @param {string} codecPreference The codec preference.
     27 */
     28 function setTransceiverCodecPreference(transceiver, codecPreference) {
     29  for (const codec of RTCRtpSender.getCapabilities('video').codecs) {
     30    if (codec.mimeType.includes(codecPreference)) {
     31      transceiver.setCodecPreferences([codec]);
     32      return;
     33    }
     34  }
     35 }
     36 
     37 /**
     38 * Starts a connection between two peer connections, using a audio and/or video
     39 * stream.
     40 * @param {*} t Test instance.
     41 * @param {boolean} audio True if audio should be used.
     42 * @param {boolean} video True if video should be used.
     43 * @param {string} [videoCodecPreference] String containing the codec preference.
     44 * @returns an array with the two connected peer connections, the remote stream,
     45 * and an object containing transceivers by kind.
     46 */
     47 async function startConnection(t, audio, video, videoCodecPreference) {
     48  const scope = [];
     49  if (audio) scope.push("microphone");
     50  if (video) scope.push("camera");
     51  await setMediaPermission("granted", scope);
     52  const stream = await navigator.mediaDevices.getUserMedia({audio, video});
     53  t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     54  const pc1 = new RTCPeerConnection();
     55  t.add_cleanup(() => pc1.close());
     56  const pc2 = new RTCPeerConnection();
     57  t.add_cleanup(() => pc2.close());
     58  const transceivers = {};
     59  for (const track of stream.getTracks()) {
     60    const transceiver = pc1.addTransceiver(track, {streams: [stream]});
     61    transceivers[track.kind] = transceiver;
     62    if (videoCodecPreference && track.kind == 'video') {
     63      setTransceiverCodecPreference(transceiver, videoCodecPreference);
     64    }
     65  }
     66  for (const [local, remote] of [[pc1, pc2], [pc2, pc1]]) {
     67    local.addEventListener('icecandidate', ({candidate}) => {
     68      if (!candidate || remote.signalingState == 'closed') return;
     69      remote.addIceCandidate(candidate);
     70    });
     71  }
     72  const haveTrackEvent = new Promise(r => pc2.ontrack = r);
     73  await exchangeOfferAnswer(pc1, pc2);
     74  const {streams} = await haveTrackEvent;
     75  return [pc1, pc2, streams[0], transceivers];
     76 }
     77 
     78 /**
     79 * Given a peer connection, return after at least numFramesOrPackets
     80 * frames (video) or packets (audio) have been received.
     81 * @param {*} t Test instance.
     82 * @param {!RTCPeerConnection} pc The peer connection.
     83 * @param {boolean} lookForAudio True if audio packets should be waited for.
     84 * @param {boolean} lookForVideo True if video packets should be waited for.
     85 * @param {int} numFramesOrPackets Number of frames (video) and packets (audio)
     86 * to wait for.
     87 */
     88 async function waitForReceivedFramesOrPackets(
     89    t, pc, lookForAudio, lookForVideo, numFramesOrPackets) {
     90  let initialAudioPackets = 0;
     91  let initialVideoFrames = 0;
     92  while (lookForAudio || lookForVideo) {
     93    const report = await pc.getStats();
     94    for (const stats of report.values()) {
     95      if (stats.type == 'inbound-rtp') {
     96        if (lookForAudio && stats.kind == 'audio') {
     97          if (!initialAudioPackets) {
     98            initialAudioPackets = stats.packetsReceived;
     99          } else if (stats.packetsReceived > initialAudioPackets +
    100                     numFramesOrPackets) {
    101            lookForAudio = false;
    102          }
    103        }
    104        if (lookForVideo && stats.kind == 'video') {
    105          if (!initialVideoFrames) {
    106            initialVideoFrames = stats.framesDecoded;
    107          } else if (stats.framesDecoded > initialVideoFrames +
    108                     numFramesOrPackets) {
    109            lookForVideo = false;
    110          }
    111        }
    112      }
    113    }
    114    await new Promise(r => t.step_timeout(r, 100));
    115  }
    116 }
    117 
    118 /**
    119 * Given a peer connection, return after one of its inbound RTP connections
    120 * includes use of the specified codec.
    121 * @param {*} t Test instance.
    122 * @param {!RTCPeerConnection} pc The peer connection.
    123 * @param {string} codecToLookFor The waited-for codec.
    124 */
    125 async function waitForReceivedCodec(t, pc, codecToLookFor) {
    126  let currentCodecId;
    127  for (;;) {
    128    const report = await pc.getStats();
    129    for (const stats of report.values()) {
    130      if (stats.type == 'inbound-rtp' && stats.kind == 'video') {
    131        if (stats.codecId) {
    132          if (report.get(stats.codecId).mimeType.toLowerCase()
    133              .includes(codecToLookFor.toLowerCase())) {
    134            return;
    135          }
    136        }
    137      }
    138    }
    139    await new Promise(r => t.step_timeout(r, 100));
    140  }
    141 }