tor-browser

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

MediaStreamTrack-video-stats.https.html (14666B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <meta name="timeout" content="long">
      4 <button id="button">User gesture</button>
      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>
     10 'use strict';
     11 
     12 async function getFrameStatsUntil(track, condition) {
     13  while (true) {
     14    const stats = track.stats.toJSON();
     15    if (condition(stats)) {
     16      return stats;
     17    }
     18    // Repeat in the next task execution cycle.
     19    await Promise.resolve();
     20  }
     21 }
     22 
     23 promise_test(async t => {
     24  const stream = await navigator.mediaDevices.getUserMedia({video:true});
     25  const [track] = stream.getTracks();
     26  t.add_cleanup(() => track.stop());
     27 
     28  const firstStats =
     29      await getFrameStatsUntil(track, stats => stats.totalFrames > 0);
     30  await getFrameStatsUntil(track,
     31                           stats => stats.totalFrames > firstStats.totalFrames);
     32 }, `totalFrames increases over time`);
     33 
     34 promise_test(async t => {
     35  const stream = await navigator.mediaDevices.getUserMedia({video:true});
     36  const [track] = stream.getTracks();
     37  t.add_cleanup(() => track.stop());
     38 
     39  // `deliveredFrames` increments for each deliverable frame, even if the
     40  // `track` does not have any sink.
     41  const firstStats = await getFrameStatsUntil(
     42      track, stats => stats.deliveredFrames > 0);
     43  await getFrameStatsUntil(
     44      track, stats => stats.deliveredFrames > firstStats.deliveredFrames);
     45 }, `deliveredFrames increases, even without sinks`);
     46 
     47 promise_test(async t => {
     48  const stream = await navigator.mediaDevices.getUserMedia({
     49          video:{frameRate:{ideal:20}}
     50      });
     51  const [track] = stream.getTracks();
     52  t.add_cleanup(() => track.stop());
     53 
     54  // Assert test prerequisite is met: frames will be discarded if the track is
     55  // opened with a higher frame rate than we apply after it is opened.
     56  assert_greater_than(track.getSettings().frameRate, 10);
     57  await track.applyConstraints({frameRate:{ideal:10}});
     58 
     59  await getFrameStatsUntil(track, stats => stats.discardedFrames > 0);
     60 }, `discardedFrames increases when frameRate decimation is happening`);
     61 
     62 promise_test(async t => {
     63  const stream = await navigator.mediaDevices.getUserMedia({video:true});
     64  const [track] = stream.getTracks();
     65  t.add_cleanup(() => track.stop());
     66 
     67  // Hold a reference directly to the [SameObject] stats, bypassing the
     68  // `track.stats` getter in the subsequent getting of `totalFrames`.
     69  const stats = track.stats;
     70  const firstTotalFrames = stats.totalFrames;
     71  while (stats.totalFrames == firstTotalFrames) {
     72    await Promise.resolve();
     73  }
     74  assert_greater_than(stats.totalFrames, firstTotalFrames);
     75 }, `Counters increase even if we don't call the track.stats getter`);
     76 
     77 promise_test(async t => {
     78  const stream = await navigator.mediaDevices.getUserMedia({video:true});
     79  const [track] = stream.getTracks();
     80  t.add_cleanup(() => track.stop());
     81 
     82  const firstTotalFrames = track.stats.totalFrames;
     83  // Busy-loop for 100 ms, all within the same task execution cycle.
     84  const firstTimeMs = performance.now();
     85  while (performance.now() - firstTimeMs < 100) {}
     86  // The frame counter should not have changed.
     87  assert_equals(track.stats.totalFrames, firstTotalFrames);
     88 }, `Counters do not increase in the same task execution cycle`);
     89 
     90 promise_test(async t => {
     91  const stream = await navigator.mediaDevices.getUserMedia({
     92          video:{frameRate:{ideal:20}}
     93      });
     94  const [track] = stream.getTracks();
     95  t.add_cleanup(() => track.stop());
     96 
     97  // Assert test prerequisite is met: frames will be discarded if the track is
     98  // opened with a higher frame rate than we apply after it is opened.
     99  assert_greater_than(track.getSettings().frameRate, 10);
    100  await track.applyConstraints({frameRate:{ideal:10}});
    101 
    102  // Wait until we have both delivered and discarded frames.
    103  const stats = await getFrameStatsUntil(track, stats =>
    104      stats.deliveredFrames > 0 && stats.discardedFrames > 0);
    105 
    106  // This test assumes that no frames are dropped, otherwise `totalFrames` can
    107  // be greater than the sum of `deliveredFrames` and `discardedFrames`.
    108  assert_equals(stats.totalFrames,
    109                stats.deliveredFrames + stats.discardedFrames);
    110 }, `totalFrames is the sum of deliveredFrames and discardedFrames`);
    111 
    112 promise_test(async t => {
    113  const stream = await navigator.mediaDevices.getUserMedia({video:true});
    114  const [track] = stream.getTracks();
    115  t.add_cleanup(() => track.stop());
    116 
    117  const a = track.stats;
    118  await getFrameStatsUntil(track, stats => stats.totalFrames > 0);
    119  const b = track.stats;
    120  // The counters may have changed, but `a` and `b` are still the same object.
    121  assert_equals(a, b);
    122 }, `SameObject policy applies`);
    123 
    124 promise_test(async t => {
    125  const stream = await navigator.mediaDevices.getUserMedia({
    126          video:{frameRate:{ideal:20}}
    127      });
    128  const [track] = stream.getTracks();
    129  t.add_cleanup(() => track.stop());
    130 
    131  // Assert test prerequisite is met: frames will be discarded if the track is
    132  // opened with a higher frame rate than we apply after it is opened.
    133  assert_greater_than(track.getSettings().frameRate, 10);
    134  await track.applyConstraints({frameRate:{ideal:10}});
    135 
    136  // Wait for media to flow before disabling the `track`.
    137  const initialStats = await getFrameStatsUntil(track, stats =>
    138      stats.deliveredFrames > 0 && stats.discardedFrames > 0 &&
    139      stats.totalFrames > 10);
    140  track.enabled = false;
    141  // Upon disabling, the counters are not reset.
    142  const disabledSnapshot = track.stats.toJSON();
    143  assert_greater_than_equal(disabledSnapshot.deliveredFrames,
    144                            initialStats.deliveredFrames);
    145  assert_greater_than_equal(disabledSnapshot.discardedFrames,
    146                            initialStats.discardedFrames);
    147  assert_greater_than_equal(disabledSnapshot.totalFrames,
    148                            initialStats.totalFrames);
    149 
    150  // Wait enough time that frames should have been produced.
    151  await new Promise(r => t.step_timeout(r, 500));
    152 
    153  // Frame metrics should be frozen, but because `enabled = false` does not
    154  // return a promise, we allow some lee-way in case a frame was still in flight
    155  // during the disabling.
    156  assert_approx_equals(
    157      track.stats.deliveredFrames, disabledSnapshot.deliveredFrames, 1);
    158  assert_approx_equals(
    159      track.stats.discardedFrames, disabledSnapshot.discardedFrames, 1);
    160  assert_approx_equals(
    161      track.stats.totalFrames, disabledSnapshot.totalFrames, 1);
    162 }, `Stats are frozen while disabled`);
    163 
    164 promise_test(async t => {
    165  const stream = await navigator.mediaDevices.getUserMedia({
    166          video:{frameRate:{ideal:20}}
    167      });
    168  const [track] = stream.getTracks();
    169  t.add_cleanup(() => track.stop());
    170 
    171  // Assert test prerequisite is met: frames will be discarded if the track is
    172  // opened with a higher frame rate than we apply after it is opened.
    173  assert_greater_than(track.getSettings().frameRate, 10);
    174  await track.applyConstraints({frameRate:{ideal:10}});
    175 
    176  // Wait for media to flow before disabling the `track`.
    177  const initialStats = await getFrameStatsUntil(track, stats =>
    178      stats.deliveredFrames > 10 && stats.discardedFrames > 10);
    179  track.enabled = false;
    180 
    181  // Re-enable the track. The stats counters should be greater than or equal to
    182  // what they were previously.
    183  track.enabled = true;
    184  assert_greater_than_equal(track.stats.deliveredFrames,
    185                            initialStats.deliveredFrames);
    186  assert_greater_than_equal(track.stats.discardedFrames,
    187                            initialStats.discardedFrames);
    188  assert_greater_than_equal(track.stats.totalFrames,
    189                            initialStats.totalFrames);
    190 }, `Disabling and re-enabling does not reset the counters`);
    191 
    192 promise_test(async t => {
    193  const stream = await navigator.mediaDevices.getUserMedia({
    194          video:{frameRate:{ideal:20}}
    195      });
    196  const [originalTrack] = stream.getTracks();
    197  t.add_cleanup(() => originalTrack.stop());
    198 
    199  // Assert test prerequisite is met: frames will be discarded if the track is
    200  // opened with a higher frame rate than we apply after it is opened.
    201  assert_greater_than(originalTrack.getSettings().frameRate, 10);
    202  await originalTrack.applyConstraints({frameRate:{ideal:10}});
    203 
    204  // Wait for media to flow before disabling the `track`.
    205  await getFrameStatsUntil(originalTrack, stats =>
    206      stats.deliveredFrames > 0 && stats.discardedFrames > 0);
    207  originalTrack.enabled = false;
    208  const originalTrackInitialStats = originalTrack.stats.toJSON();
    209 
    210  // Clone the track, its counters should be zero initially.
    211  // This is not racy because the cloned track is also disabled.
    212  const clonedTrack = originalTrack.clone();
    213  t.add_cleanup(() => clonedTrack.stop());
    214  const clonedTrackStats = clonedTrack.stats.toJSON();
    215  assert_equals(clonedTrackStats.deliveredFrames, 0);
    216  assert_equals(clonedTrackStats.discardedFrames, 0);
    217  assert_equals(clonedTrackStats.totalFrames, 0);
    218 
    219  // Enabled the cloned track and wait for media to flow.
    220  clonedTrack.enabled = true;
    221  await getFrameStatsUntil(clonedTrack, stats =>
    222      stats.deliveredFrames > 0 && stats.discardedFrames > 0);
    223 
    224  // This does not affect the original track's stats, which are still frozen due
    225  // to the original track being disabled.
    226  assert_equals(originalTrack.stats.deliveredFrames,
    227                originalTrackInitialStats.deliveredFrames);
    228  assert_equals(originalTrack.stats.discardedFrames,
    229                originalTrackInitialStats.discardedFrames);
    230  assert_equals(originalTrack.stats.totalFrames,
    231                originalTrackInitialStats.totalFrames);
    232 }, `New stats baselines when a track is cloned from a disabled track`);
    233 
    234 promise_test(async t => {
    235  const stream = await navigator.mediaDevices.getUserMedia({
    236          video:{frameRate:{ideal:20}}
    237      });
    238  const [originalTrack] = stream.getTracks();
    239  t.add_cleanup(() => originalTrack.stop());
    240 
    241  // Assert test prerequisite is met: frames will be discarded if the track is
    242  // opened with a higher frame rate than we apply after it is opened.
    243  assert_greater_than(originalTrack.getSettings().frameRate, 10);
    244  await originalTrack.applyConstraints({frameRate:{ideal:10}});
    245 
    246  // Wait for media to flow.
    247  await getFrameStatsUntil(originalTrack, stats =>
    248      stats.deliveredFrames > 0 && stats.discardedFrames > 0);
    249 
    250  // Clone the track. While its counters should initially be zero, it would be
    251  // racy to assert that they are exactly zero because media is flowing.
    252  const clonedTrack = originalTrack.clone();
    253  t.add_cleanup(() => clonedTrack.stop());
    254 
    255  // Ensure that as media continues to flow, the cloned track will necessarily
    256  // have less frames than the original track on all accounts since its counters
    257  // will have started from zero.
    258  const clonedTrackStats = await getFrameStatsUntil(clonedTrack, stats =>
    259      stats.deliveredFrames > 0 && stats.discardedFrames > 0);
    260  assert_less_than(clonedTrackStats.deliveredFrames,
    261                   originalTrack.stats.deliveredFrames);
    262  assert_less_than(clonedTrackStats.discardedFrames,
    263                   originalTrack.stats.discardedFrames);
    264  assert_less_than(clonedTrackStats.totalFrames,
    265                   originalTrack.stats.totalFrames);
    266 }, `New stats baselines when a track is cloned from an enabled track`);
    267 
    268 promise_test(async t => {
    269  const stream = await navigator.mediaDevices.getUserMedia({
    270          video:{frameRate:{ideal:20}}
    271      });
    272  const [originalTrack] = stream.getTracks();
    273  t.add_cleanup(() => originalTrack.stop());
    274 
    275  // Assert test prerequisite is met: frames will be discarded if the track is
    276  // opened with a higher frame rate than we apply after it is opened.
    277  assert_greater_than(originalTrack.getSettings().frameRate, 10);
    278  await originalTrack.applyConstraints({frameRate:{ideal:10}});
    279 
    280  // Wait for media to flow.
    281  await getFrameStatsUntil(originalTrack, stats =>
    282      stats.deliveredFrames > 0 && stats.discardedFrames > 0);
    283 
    284  // Clone and wait for media to flow.
    285  const cloneA = originalTrack.clone();
    286  t.add_cleanup(() => cloneA.stop());
    287  await getFrameStatsUntil(cloneA, stats =>
    288      stats.deliveredFrames > 0 && stats.discardedFrames > 0);
    289 
    290  // Clone the clone and wait for media to flow.
    291  const cloneB = cloneA.clone();
    292  t.add_cleanup(() => cloneB.stop());
    293  await getFrameStatsUntil(cloneB, stats =>
    294      stats.deliveredFrames > 0 && stats.discardedFrames > 0);
    295 
    296  // Because every clone reset its counters and every waits for media before
    297  // cloning, this must be true: originalStats > cloneAStats > cloneBStats.
    298  const originalStats = originalTrack.stats.toJSON();
    299  const cloneAStats = cloneA.stats.toJSON();
    300  const cloneBStats = cloneB.stats.toJSON();
    301  assert_greater_than(originalStats.totalFrames, cloneAStats.totalFrames);
    302  assert_greater_than(cloneAStats.totalFrames, cloneBStats.totalFrames);
    303 }, `New stats baselines for the clone of a clone`);
    304 
    305 promise_test(async t => {
    306  const stream = await navigator.mediaDevices.getUserMedia({video:true});
    307  const [originalTrack] = stream.getTracks();
    308  t.add_cleanup(() => originalTrack.stop());
    309 
    310  // Wait for some frames and assert that no frames are discarded.
    311  const firstStats = await getFrameStatsUntil(originalTrack, stats =>
    312      stats.deliveredFrames > 20);
    313  assert_equals(firstStats.discardedFrames, 0);
    314  // Make a clone that discards almost all frames. This should not affect the
    315  // discarded frames counter of the original track.
    316  const clonedTrack = originalTrack.clone();
    317  await clonedTrack.applyConstraints({frameRate:{ideal:1}});
    318  // Wait for some more frames. There should still be no frames discarded.
    319  const secondStats = await getFrameStatsUntil(originalTrack, stats =>
    320      stats.deliveredFrames > 40);
    321  assert_equals(secondStats.discardedFrames, 0);
    322 }, `A low FPS clone does not affect the original track's discardedFrames`);
    323 
    324 promise_test(async t => {
    325  const canvas = document.createElement('canvas');
    326  const stream = canvas.captureStream(10);
    327  const [track] = stream.getTracks();
    328  t.add_cleanup(() => track.stop());
    329 
    330  assert_equals(track.stats, null);
    331 }, `track.stats is null on non-device tracks, such as canvas`);
    332 
    333 promise_test(async t => {
    334  // getDisplayMedia() requires inducing a user gesture.
    335  const p = new Promise(r => button.onclick = r);
    336  await test_driver.click(button);
    337  await p;
    338 
    339  const stream = await navigator.mediaDevices.getDisplayMedia({video:true});
    340  const [track] = stream.getTracks();
    341  t.add_cleanup(() => track.stop());
    342 
    343  await getFrameStatsUntil(track, stats => stats.totalFrames > 0)
    344 }, `track.stats is supported on getDisplayMedia tracks`);
    345 </script>