tor-browser

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

RTCRtpEncodingParameters-scaleResolutionDownTo.https.html (14065B)


      1 <!DOCTYPE html>
      2 <meta charset="utf-8">
      3 <title>RTCRtpEncodingParameters scaleResolutionDownTo attribute</title>
      4 <meta name="timeout" content="long">
      5 <script src="/resources/testharness.js"></script>
      6 <script src="/resources/testharnessreport.js"></script>
      7 <script src="../webrtc/simulcast/simulcast.js"></script>
      8 <script src="../webrtc/third_party/sdp/sdp.js"></script>
      9 <script>
     10 'use strict';
     11 
     12 // Creates a track that can be resized with `track.resize(w,h)`.
     13 function createResizableTrack(width, height) {
     14  // Draw to a canvas with a 30 fps interval.
     15  const canvas = Object.assign(
     16      document.createElement('canvas'), {width, height});
     17  const ctx = canvas.getContext('2d');
     18  ctx.fillStyle = 'rgb(255,0,0)';
     19  const interval = setInterval(() => {
     20    ctx.fillRect(0, 0, canvas.width, canvas.height);
     21  }, 1000 / 30);
     22  // Capture the canvas and add/modify reize() and stop() methods.
     23  const stream = canvas.captureStream();
     24  const [track] = stream.getTracks();
     25  track.resize = (w, h) => {
     26    canvas.width = w;
     27    canvas.height = h;
     28  };
     29  const nativeStop = track.stop;
     30  track.stop = () => {
     31    clearInterval(interval);
     32    nativeStop.apply(track);
     33  };
     34  return track;
     35 }
     36 
     37 async function getLastEncodedResolution(pc, rid = null) {
     38  const report = await pc.getStats();
     39  for (const stats of report.values()) {
     40    if (stats.type != 'outbound-rtp') {
     41      continue;
     42    }
     43    if (rid && stats.rid != rid) {
     44      continue;
     45    }
     46    if (!stats.frameWidth || !stats.frameWidth) {
     47      // The resolution is missing until the first frame has been encoded.
     48      break;
     49    }
     50    return { width: stats.frameWidth, height: stats.frameHeight };
     51  }
     52  return { width: null, height: null };
     53 }
     54 
     55 async function waitForFrameWithResolution(t, pc, width, height, rid = null) {
     56  let resolution;
     57  do {
     58    resolution = await getLastEncodedResolution(pc, rid);
     59    await new Promise(r => t.step_timeout(r, 50));
     60  } while (resolution.width != width || resolution.height != height);
     61 }
     62 
     63 promise_test(async t => {
     64  const pc = new RTCPeerConnection();
     65  t.add_cleanup(() => pc.close());
     66 
     67  const track = createResizableTrack(120, 60);
     68  t.add_cleanup(() => track.stop());
     69  assert_throws_dom('OperationError', () => {
     70    pc.addTransceiver(track, {
     71          sendEncodings:[{
     72              scaleResolutionDownTo: { maxWidth: 0, maxHeight: 0 }
     73          }]
     74        });
     75  });
     76 }, `addTransceiver: Invalid scaling throws`);
     77 
     78 promise_test(async t => {
     79  const pc = new RTCPeerConnection();
     80  t.add_cleanup(() => pc.close());
     81 
     82  const track = createResizableTrack(120, 60);
     83  t.add_cleanup(() => track.stop());
     84  const {sender} = pc.addTransceiver(track, {sendEncodings:[{}]});
     85 
     86  const params = sender.getParameters();
     87  params.encodings[0].scaleResolutionDownTo = { maxWidth: 0, maxHeight: 0 };
     88  const p = sender.setParameters(params);
     89 
     90  promise_rejects_dom(t, 'InvalidModificationError', p);
     91 }, `setParameters: Invalid scaling throws`);
     92 
     93 promise_test(async t => {
     94  const pc = new RTCPeerConnection();
     95  t.add_cleanup(() => pc.close());
     96 
     97  const track = createResizableTrack(120, 60);
     98  t.add_cleanup(() => track.stop());
     99  assert_throws_dom('OperationError', () => {
    100    pc.addTransceiver(track, {
    101          sendEncodings:[{
    102              scaleResolutionDownTo: undefined,
    103          }, {
    104              scaleResolutionDownTo: { maxWidth: 120, maxHeight: 60 }
    105          }]
    106        });
    107  });
    108 }, `addTransceiver: Specifying scaling on a subset of active encodings throws`);
    109 
    110 promise_test(async t => {
    111  const pc = new RTCPeerConnection();
    112  t.add_cleanup(() => pc.close());
    113 
    114  const track = createResizableTrack(120, 60);
    115  t.add_cleanup(() => track.stop());
    116  pc.addTransceiver(track, {
    117        sendEncodings:[{
    118            active: true,
    119            scaleResolutionDownTo: { maxWidth: 120, maxHeight: 60 },
    120        }, {
    121            active: false,
    122            scaleResolutionDownTo: undefined
    123        }]
    124      });
    125 }, `addTransceiver: Specifying scaling on inactive encodings is optional`);
    126 
    127 promise_test(async t => {
    128  const pc = new RTCPeerConnection();
    129  t.add_cleanup(() => pc.close());
    130 
    131  const track = createResizableTrack(120, 60);
    132  t.add_cleanup(() => track.stop());
    133  const {sender} = pc.addTransceiver(track, {sendEncodings:[{},{}]});
    134 
    135  const params = sender.getParameters();
    136  params.encodings[0].scaleResolutionDownTo = undefined;
    137  params.encodings[1].scaleResolutionDownTo = { maxWidth: 120, maxHeight: 60 };
    138  const p = sender.setParameters(params);
    139 
    140  promise_rejects_dom(t, 'InvalidModificationError', p);
    141 }, `setParameters: Specifying scaling on a subset of active encodings throws`);
    142 
    143 promise_test(async t => {
    144  const pc = new RTCPeerConnection();
    145  t.add_cleanup(() => pc.close());
    146 
    147  const track = createResizableTrack(120, 60);
    148  t.add_cleanup(() => track.stop());
    149  const {sender} = pc.addTransceiver(track, {sendEncodings:[{},{}]});
    150 
    151  const params = sender.getParameters();
    152  params.encodings[0].active = true;
    153  params.encodings[0].scaleResolutionDownTo = { maxWidth: 120, maxHeight: 60 };
    154  params.encodings[1].active = false;
    155  params.encodings[1].scaleResolutionDownTo = undefined;
    156  await sender.setParameters(params);
    157 }, `setParameters: Specifying scaling on inactive encodings is optional`);
    158 
    159 promise_test(async t => {
    160  const pc1 = new RTCPeerConnection();
    161  t.add_cleanup(() => pc1.close());
    162  const pc2 = new RTCPeerConnection();
    163  t.add_cleanup(() => pc2.close());
    164  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    165  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    166 
    167  const track = createResizableTrack(120, 60);
    168  t.add_cleanup(() => track.stop());
    169 
    170  let {sender} = pc1.addTransceiver(track, {
    171        sendEncodings:[{
    172            scaleResolutionDownTo: { maxWidth: 120, maxHeight: 60 }
    173        }]
    174      });
    175 
    176  // Get the initial value set.
    177  let params = sender.getParameters();
    178  assert_equals(params.encodings[0].scaleResolutionDownTo?.maxWidth, 120);
    179  assert_equals(params.encodings[0].scaleResolutionDownTo?.maxHeight, 60);
    180  // Set parameters and get the result.
    181  params.encodings[0].scaleResolutionDownTo = { maxWidth: 60, maxHeight: 30 };
    182  await sender.setParameters(params);
    183  params = sender.getParameters();
    184  assert_equals(params.encodings[0].scaleResolutionDownTo?.maxWidth, 60);
    185  assert_equals(params.encodings[0].scaleResolutionDownTo?.maxHeight, 30);
    186 }, `getParameters reflect the current scaleResolutionDownTo`);
    187 
    188 promise_test(async t => {
    189  const pc1 = new RTCPeerConnection();
    190  t.add_cleanup(() => pc1.close());
    191  const pc2 = new RTCPeerConnection();
    192  t.add_cleanup(() => pc2.close());
    193  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    194  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    195 
    196  const track = createResizableTrack(120, 60);
    197  t.add_cleanup(() => track.stop());
    198 
    199  pc1.addTransceiver(track, {
    200        sendEncodings:[{
    201            scaleResolutionDownBy: 2.0,
    202            scaleResolutionDownTo: { maxWidth: 120, maxHeight: 60 }
    203        }]
    204      });
    205 
    206  await pc1.setLocalDescription();
    207  await pc2.setRemoteDescription(pc1.localDescription);
    208  await pc2.setLocalDescription();
    209  await pc1.setRemoteDescription(pc2.localDescription);
    210 
    211  await waitForFrameWithResolution(t, pc1, 120, 60);
    212 }, `addTransceiver: scaleResolutionDownBy is ignored when ` +
    213   `scaleResolutionDownTo is specified`);
    214 
    215 promise_test(async t => {
    216  const pc1 = new RTCPeerConnection();
    217  t.add_cleanup(() => pc1.close());
    218  const pc2 = new RTCPeerConnection();
    219  t.add_cleanup(() => pc2.close());
    220  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    221  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    222 
    223  const track = createResizableTrack(120, 60);
    224  t.add_cleanup(() => track.stop());
    225  const {sender} = pc1.addTransceiver(track);
    226 
    227  const params = sender.getParameters();
    228  params.encodings[0].scaleResolutionDownBy = 2.0;
    229  params.encodings[0].scaleResolutionDownTo = { maxWidth: 120, maxHeight: 60 };
    230  const p = sender.setParameters(params);
    231 
    232  await pc1.setLocalDescription();
    233  await pc2.setRemoteDescription(pc1.localDescription);
    234  await pc2.setLocalDescription();
    235  await pc1.setRemoteDescription(pc2.localDescription);
    236 
    237  await waitForFrameWithResolution(t, pc1, 120, 60);
    238 }, `setParameters: scaleResolutionDownBy is ignored when ` +
    239   `scaleResolutionDownTo is specified`);
    240 
    241 promise_test(async t => {
    242  const pc1 = new RTCPeerConnection();
    243  t.add_cleanup(() => pc1.close());
    244  const pc2 = new RTCPeerConnection();
    245  t.add_cleanup(() => pc2.close());
    246  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    247  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    248 
    249  const track = createResizableTrack(120, 60);
    250  t.add_cleanup(() => track.stop());
    251  const {sender} = pc1.addTransceiver(track, {
    252      sendEncodings: [{
    253          scaleResolutionDownTo: { maxWidth: 60, maxHeight: 30 }
    254      }]
    255  });
    256 
    257  await pc1.setLocalDescription();
    258  await pc2.setRemoteDescription(pc1.localDescription);
    259  await pc2.setLocalDescription();
    260  await pc1.setRemoteDescription(pc2.localDescription);
    261 
    262  await waitForFrameWithResolution(t, pc1, 60, 30);
    263 }, `addTransceiver: scaleResolutionDownTo with half resolution`);
    264 
    265 promise_test(async t => {
    266  const pc1 = new RTCPeerConnection();
    267  t.add_cleanup(() => pc1.close());
    268  const pc2 = new RTCPeerConnection();
    269  t.add_cleanup(() => pc2.close());
    270  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    271  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    272 
    273  const track = createResizableTrack(120, 60);
    274  t.add_cleanup(() => track.stop());
    275  const {sender} = pc1.addTransceiver(track);
    276 
    277  await pc1.setLocalDescription();
    278  await pc2.setRemoteDescription(pc1.localDescription);
    279  await pc2.setLocalDescription();
    280  await pc1.setRemoteDescription(pc2.localDescription);
    281 
    282  // Request full resolution.
    283  let params = sender.getParameters();
    284  params.encodings[0].scaleResolutionDownTo = { maxWidth: 120, maxHeight: 60 };
    285  await sender.setParameters(params);
    286  await waitForFrameWithResolution(t, pc1, 120, 60);
    287 
    288  // Request half resolution.
    289  params = sender.getParameters();
    290  params.encodings[0].scaleResolutionDownTo = { maxWidth: 60, maxHeight: 30 };
    291  await sender.setParameters(params);
    292  await waitForFrameWithResolution(t, pc1, 60, 30);
    293 
    294  // Request full resolution again.
    295  params = sender.getParameters();
    296  params.encodings[0].scaleResolutionDownTo = { maxWidth: 120, maxHeight: 60 };
    297  await sender.setParameters(params);
    298  await waitForFrameWithResolution(t, pc1, 120, 60);
    299 }, `setParameters: Modify scaleResolutionDownTo while sending`);
    300 
    301 promise_test(async t => {
    302  const pc1 = new RTCPeerConnection();
    303  t.add_cleanup(() => pc1.close());
    304  const pc2 = new RTCPeerConnection();
    305  t.add_cleanup(() => pc2.close());
    306  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    307  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    308 
    309  const track = createResizableTrack(80, 40);
    310  t.add_cleanup(() => track.stop());
    311  const {sender} = pc1.addTransceiver(track);
    312 
    313  await pc1.setLocalDescription();
    314  await pc2.setRemoteDescription(pc1.localDescription);
    315  await pc2.setLocalDescription();
    316  await pc1.setRemoteDescription(pc2.localDescription);
    317 
    318  // scaleTo is portrait, track is landscape, but no scaling should happen due
    319  // to orientation agnosticism.
    320  let params = sender.getParameters();
    321  params.encodings[0].scaleResolutionDownTo = { maxWidth: 40, maxHeight: 80 };
    322  await sender.setParameters(params);
    323  await waitForFrameWithResolution(t, pc1, 80, 40);
    324 
    325  // Change orientation of the track: still no downscale, but encoder begins to
    326  // produce the new orientation.
    327  track.resize(40, 80);
    328  await waitForFrameWithResolution(t, pc1, 40, 80);
    329 
    330  // scaleTo in landscape, reducing to half size. Verify track, which is
    331  // portrait, is scaled down by 2.
    332  params = sender.getParameters();
    333  params.encodings[0].scaleResolutionDownTo = { maxWidth: 40, maxHeight: 20 };
    334  await sender.setParameters(params);
    335  await waitForFrameWithResolution(t, pc1, 20, 40);
    336 }, `scaleResolutionDownTo is orientation agnostic`);
    337 
    338 promise_test(async t => {
    339  const pc1 = new RTCPeerConnection();
    340  t.add_cleanup(() => pc1.close());
    341  const pc2 = new RTCPeerConnection();
    342  t.add_cleanup(() => pc2.close());
    343  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    344  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    345 
    346  const track = createResizableTrack(120, 60);
    347  t.add_cleanup(() => track.stop());
    348  const {sender} = pc1.addTransceiver(track);
    349 
    350  await pc1.setLocalDescription();
    351  await pc2.setRemoteDescription(pc1.localDescription);
    352  await pc2.setLocalDescription();
    353  await pc1.setRemoteDescription(pc2.localDescription);
    354 
    355  // Restrict to 60x60. This results in 60x30 due to maintaining aspect ratio.
    356  let params = sender.getParameters();
    357  params.encodings[0].scaleResolutionDownTo = { maxWidth: 60, maxHeight: 60 };
    358  await sender.setParameters(params);
    359  await waitForFrameWithResolution(t, pc1, 60, 30);
    360 }, `scaleResolutionDownTo does not change aspect ratio`);
    361 
    362 promise_test(async t => {
    363  const pc1 = new RTCPeerConnection();
    364  t.add_cleanup(() => pc1.close());
    365  const pc2 = new RTCPeerConnection();
    366  t.add_cleanup(() => pc2.close());
    367  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    368  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    369 
    370  const track = createResizableTrack(160, 80);
    371  t.add_cleanup(() => track.stop());
    372  const {sender} = pc1.addTransceiver(track, {
    373      sendEncodings: [{
    374          rid: '0',
    375          scaleResolutionDownTo: { maxWidth: 40, maxHeight: 20 }
    376      }, {
    377          rid: '1',
    378          scaleResolutionDownTo: { maxWidth: 80, maxHeight: 40 }
    379      }, {
    380          rid: '2',
    381          scaleResolutionDownTo: { maxWidth: 160, maxHeight: 80 }
    382      }]
    383  });
    384 
    385  // Negotiate with simulcast tweaks.
    386  await doOfferToSendSimulcastAndAnswer(pc1, pc2, ['0', '1', '2']);
    387 
    388  await waitForFrameWithResolution(t, pc1, 40, 20, '0');
    389  await waitForFrameWithResolution(t, pc1, 80, 40, '1');
    390  await waitForFrameWithResolution(t, pc1, 160, 80, '2');
    391 }, `scaleResolutionDownTo with simulcast`);
    392 </script>