tor-browser

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

MediaStream-MediaElement-srcObject.https.html (18571B)


      1 <!doctype html>
      2 <html>
      3 <head>
      4 <title>Assigning mediastream to a video element</title>
      5 <link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
      6 <link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#navigatorusermedia">
      7 </head>
      8 <body>
      9 <p class="instructions">When prompted, accept to share your video stream.</p>
     10 <h1 class="instructions">Description</h1>
     11 <p class="instructions">This test checks that the MediaStream object returned by
     12 the success callback in getUserMedia can be properly assigned to a video element
     13 via the <code>srcObject</code> attribute.</p>
     14 
     15 <audio id="aud"></audio>
     16 <video id="vid"></video>
     17 
     18 <div id='log'></div>
     19 <script src=/resources/testharness.js></script>
     20 <script src=/resources/testharnessreport.js></script>
     21 <script src=/resources/testdriver.js></script>
     22 <script src=/resources/testdriver-vendor.js></script>
     23 <script src=permission-helper.js></script>
     24 <script>
     25 'use strict';
     26 const vid = document.getElementById("vid");
     27 
     28 function queueTask(f) {
     29  window.onmessage = f;
     30  window.postMessage("hi");
     31 }
     32 
     33 promise_test(async t => {
     34  await setMediaPermission();
     35  const stream = await navigator.mediaDevices.getUserMedia({video: true});
     36  t.add_cleanup(() => {
     37    vid.srcObject = null;
     38    stream.getTracks().forEach(track => track.stop());
     39  });
     40  vid.srcObject = stream;
     41 }, "Tests that a MediaStream can be assigned to a video element with srcObject");
     42 
     43 promise_test(async t => {
     44  const stream = await navigator.mediaDevices.getUserMedia({video: true});
     45  t.add_cleanup(() => {
     46    vid.srcObject = null;
     47    stream.getTracks().forEach(track => track.stop());
     48  });
     49  vid.srcObject = stream;
     50 
     51  assert_true(!vid.seeking, "A MediaStream is not seekable");
     52  assert_equals(vid.seekable.length, 0, "A MediaStream is not seekable");
     53 }, "Tests that a MediaStream assigned to a video element is not seekable");
     54 
     55 promise_test(async t => {
     56  const stream = await navigator.mediaDevices.getUserMedia({video: true});
     57  t.add_cleanup(() => {
     58    vid.srcObject = null;
     59    stream.getTracks().forEach(track => track.stop());
     60  });
     61  vid.srcObject = stream;
     62 
     63  assert_equals(vid.readyState, vid.HAVE_NOTHING,
     64    "readyState is HAVE_NOTHING initially");
     65  await new Promise(r => vid.onloadeddata = r);
     66  assert_equals(vid.readyState, vid.HAVE_ENOUGH_DATA,
     67    "Upon having loaded a media stream, the UA sets readyState to HAVE_ENOUGH_DATA");
     68 }, "Tests that a MediaStream assigned to a video element is in readyState HAVE_NOTHING initially");
     69 
     70 promise_test(async t => {
     71  const stream = await navigator.mediaDevices.getUserMedia({video: true});
     72  t.add_cleanup(() => {
     73    vid.srcObject = null;
     74    stream.getTracks().forEach(track => track.stop());
     75  });
     76  vid.srcObject = stream;
     77 
     78  assert_equals(vid.duration, NaN,
     79    "A MediaStream does not have any duration initially.");
     80  await new Promise(r => vid.ondurationchange = r);
     81  assert_equals(vid.duration, Infinity,
     82    "A loaded MediaStream does not have a pre-defined duration.");
     83 
     84  vid.play();
     85  await new Promise(r => vid.ontimeupdate = r);
     86  for (const t of stream.getTracks()) {
     87    t.stop();
     88  }
     89 
     90  await new Promise(r => vid.ondurationchange = r);
     91  assert_equals(vid.duration, vid.currentTime,
     92    "After ending playback, duration gets set to currentTime");
     93 }, "Tests that a MediaStream assigned to a video element has expected duration");
     94 
     95 promise_test(async t => {
     96  const stream = await navigator.mediaDevices.getUserMedia({video: true});
     97  t.add_cleanup(() => {
     98    vid.srcObject = null;
     99    stream.getTracks().forEach(track => track.stop());
    100  });
    101 
    102  vid.preload = "metadata";
    103  vid.srcObject = stream;
    104 
    105  assert_equals(vid.buffered.length, 0,
    106    "A MediaStream cannot be preloaded. Therefore, there are no buffered timeranges");
    107  assert_equals(vid.preload, "none", "preload must always be none");
    108  vid.preload = "auto";
    109  assert_equals(vid.preload, "none", "Setting preload must be ignored");
    110 
    111  await new Promise(r => vid.onloadeddata = r);
    112  assert_equals(vid.buffered.length, 0,
    113    "A MediaStream cannot be preloaded. Therefore, there are no buffered timeranges");
    114 
    115  vid.srcObject = null;
    116 
    117  assert_equals(vid.preload, "metadata",
    118    "The preload attribute returns the value it had before using a MediaStream");
    119 }, "Tests that a video element with a MediaStream assigned is not preloaded");
    120 
    121 promise_test(async t => {
    122  const stream = await navigator.mediaDevices.getUserMedia({video: true});
    123  t.add_cleanup(() => {
    124    vid.srcObject = null;
    125    stream.getTracks().forEach(track => track.stop());
    126  });
    127 
    128  vid.defaultPlaybackRate = 0.3;
    129  vid.playbackRate = 0.3;
    130  vid.onratechange = t.unreached_func("ratechange event must not be fired");
    131  vid.srcObject = stream;
    132 
    133  assert_equals(vid.defaultPlaybackRate, 1, "playback rate is always 1");
    134  vid.defaultPlaybackRate = 0.5;
    135  assert_equals(vid.defaultPlaybackRate, 1,
    136    "Setting defaultPlaybackRate must be ignored");
    137 
    138  assert_equals(vid.playbackRate, 1, "playback rate is always 1");
    139  vid.playbackRate = 0.5;
    140  assert_equals(vid.playbackRate, 1, "Setting playbackRate must be ignored");
    141 
    142  vid.srcObject = null;
    143  assert_equals(vid.defaultPlaybackRate, 0.3,
    144    "The defaultPlaybackRate attribute returns the value it had before using a MediaStream");
    145  assert_equals(vid.playbackRate, 0.3,
    146    "The playbackRate attribute is set to the value of the defaultPlaybackRate attribute when unsetting srcObject");
    147 
    148  // Check that there's no ratechange event
    149  await new Promise(r => t.step_timeout(r, 100));
    150 }, "Tests that a video element with a MediaStream assigned ignores playbackRate attributes (defaultPlaybackRate is identical)");
    151 
    152 promise_test(async t => {
    153  const stream = await navigator.mediaDevices.getUserMedia({video: true});
    154  t.add_cleanup(() => {
    155    vid.srcObject = null;
    156    stream.getTracks().forEach(track => track.stop());
    157  });
    158 
    159  vid.defaultPlaybackRate = 0.3;
    160  vid.playbackRate = 0.4;
    161  vid.onratechange = t.unreached_func("ratechange event must not be fired");
    162  vid.srcObject = stream;
    163 
    164  assert_equals(vid.defaultPlaybackRate, 1, "playback rate is always 1");
    165  vid.defaultPlaybackRate = 0.5;
    166  assert_equals(vid.defaultPlaybackRate, 1,
    167    "Setting defaultPlaybackRate must be ignored");
    168 
    169  assert_equals(vid.playbackRate, 1, "playback rate is always 1");
    170  vid.playbackRate = 0.5;
    171  assert_equals(vid.playbackRate, 1, "Setting playbackRate must be ignored");
    172 
    173  vid.srcObject = null;
    174  assert_equals(vid.defaultPlaybackRate, 0.3,
    175    "The defaultPlaybackRate attribute returns the value it had before using a MediaStream");
    176  assert_equals(vid.playbackRate, 0.3,
    177    "The playbackRate attribute is set to the value of the defaultPlaybackRate attribute when unsetting srcObject (and fires ratechange)");
    178  await new Promise(r => vid.onratechange = r);
    179 }, "Tests that a video element with a MediaStream assigned ignores playbackRate attributes (defaultPlaybackRate is different)");
    180 
    181 promise_test(async t => {
    182  const stream = await navigator.mediaDevices.getUserMedia({video: true});
    183  t.add_cleanup(() => {
    184    vid.srcObject = null;
    185    stream.getTracks().forEach(track => track.stop());
    186  });
    187  vid.srcObject = stream;
    188  await new Promise(r => vid.oncanplay = r);
    189  vid.play();
    190  await new Promise(r => vid.ontimeupdate = r);
    191  assert_greater_than(vid.currentTime, 0,
    192    "currentTime is greater than 0 after first timeupdate");
    193 
    194  assert_equals(vid.played.length, 1,
    195    "A MediaStream's timeline always consists of a single range");
    196  assert_equals(vid.played.start(0), 0,
    197    "A MediaStream's timeline always starts at zero");
    198  assert_equals(vid.played.end(0), vid.currentTime,
    199    "A MediaStream's end MUST return the last known currentTime");
    200 
    201  const time = vid.currentTime;
    202  vid.currentTime = 0;
    203  assert_equals(vid.currentTime, time,
    204    "The UA MUST ignore attempts to set the currentTime attribute");
    205 }, "Tests that a media element with an assigned MediaStream reports the played attribute as expected");
    206 
    207 promise_test(async t => {
    208  const stream = await navigator.mediaDevices.getUserMedia({video: true});
    209  t.add_cleanup(() => {
    210    vid.srcObject = null;
    211    stream.getTracks().forEach(track => track.stop());
    212  });
    213  vid.srcObject = stream;
    214 
    215  assert_equals(vid.currentTime, 0, "The initial value is 0");
    216  vid.currentTime = 42;
    217  assert_equals(vid.currentTime, 0,
    218    "The UA MUST ignore attempts to set the currentTime attribute (default playback start position)");
    219 
    220  await new Promise(r => vid.onloadeddata = r);
    221  assert_equals(vid.currentTime, 0, "The initial value is 0");
    222  vid.currentTime = 42;
    223  assert_equals(vid.currentTime, 0,
    224    "The UA MUST ignore attempts to set the currentTime attribute (official playback position)");
    225 
    226  vid.play();
    227  await new Promise(r => vid.ontimeupdate = r);
    228  assert_greater_than(vid.currentTime, 0,
    229    "currentTime is greater than 0 after first timeupdate");
    230 
    231  const lastTime = vid.currentTime;
    232  vid.currentTime = 0;
    233  assert_equals(vid.currentTime, lastTime,
    234    "The UA MUST ignore attempts to set the currentTime attribute (restart)");
    235 
    236  for(const t of stream.getTracks()) {
    237    t.stop();
    238  }
    239  await new Promise(r => vid.onended = r);
    240  assert_greater_than_equal(vid.currentTime, lastTime,
    241    "currentTime advanced after stopping");
    242 }, "Tests that a media element with an assigned MediaStream reports the currentTime attribute as expected");
    243 
    244 promise_test(async t => {
    245  const stream = await navigator.mediaDevices.getUserMedia({video: true});
    246  t.add_cleanup(() => {
    247    vid.srcObject = null;
    248    stream.getTracks().forEach(track => track.stop());
    249  });
    250  vid.srcObject = stream;
    251 
    252  await new Promise(r => t.step_timeout(r, 500));
    253 
    254  vid.play();
    255  await new Promise(r => vid.ontimeupdate = r);
    256  assert_between_exclusive(vid.currentTime, 0, 0.5,
    257    "currentTime starts at 0 and has progressed at first timeupdate");
    258 }, "Tests that a media element with an assigned MediaStream starts its timeline at 0 regardless of when the MediaStream was created");
    259 
    260 promise_test(async t => {
    261  const stream = await navigator.mediaDevices.getUserMedia({video: true});
    262  t.add_cleanup(() => {
    263    vid.srcObject = null;
    264    stream.getTracks().forEach(track => track.stop());
    265  });
    266  vid.srcObject = stream;
    267 
    268  vid.play();
    269  await new Promise(r => vid.ontimeupdate = r);
    270 
    271  vid.pause();
    272  const pauseCurrentTime = vid.currentTime;
    273 
    274  await new Promise(r => vid.onpause = r);
    275  vid.ontimeupdate = () => assert_unreached("No timeupdate while paused");
    276 
    277  await new Promise(r => t.step_timeout(r, 500));
    278  assert_equals(vid.currentTime, pauseCurrentTime,
    279    "currentTime does not change while paused");
    280 
    281  vid.play();
    282 
    283  await new Promise(r => vid.ontimeupdate = r);
    284  assert_between_exclusive(vid.currentTime - pauseCurrentTime, 0, 0.5,
    285    "currentTime does not skip ahead after pause");
    286 }, "Tests that a media element with an assigned MediaStream does not advance currentTime while paused");
    287 
    288 promise_test(async t => {
    289  const canvas = document.createElement("canvas");
    290  const ctx = canvas.getContext("2d");
    291  const stream = canvas.captureStream();
    292  t.add_cleanup(() => {
    293    vid.srcObject = null;
    294    stream.getTracks().forEach(track => track.stop());
    295  });
    296  vid.srcObject = stream;
    297 
    298  vid.ontimeupdate = () =>
    299    assert_unreached("No timeupdate until potentially playing");
    300 
    301  vid.play();
    302 
    303  await new Promise(r => t.step_timeout(r, 1000));
    304  assert_equals(vid.readyState, vid.HAVE_NOTHING,
    305    "Video dimensions not known yet");
    306 
    307  const start = performance.now();
    308  ctx.fillStyle = "green";
    309  ctx.fillRect(0, 0, canvas.width, canvas.height);
    310 
    311  // Wait for, and check, potentially playing
    312  await new Promise(r => vid.oncanplay = r);
    313  const canplayDuration = (performance.now() - start) / 1000;
    314  // "canplay" was just dispatched from a task queued when the element became
    315  // potentially playing. currentTime may not have progressed more than the time
    316  // it took from becoming potentially playing to starting the
    317  // canplay-dispatching task. Though the media clock and the js clock may be
    318  // different, so we take double this duration, or 100ms, whichever is greater,
    319  // as a safety margin.
    320  const margin = Math.max(0.1, canplayDuration * 2);
    321  assert_between_inclusive(vid.currentTime, 0, margin,
    322    "currentTime has not advanced more than twice it took to dispatch canplay");
    323  assert_false(vid.paused, "Media element is not paused");
    324  assert_false(vid.ended, "Media element is not ended");
    325  assert_equals(vid.error, null,
    326    "Media element playback has not stopped due to errors");
    327  assert_greater_than(vid.readyState, vid.HAVE_CURRENT_DATA,
    328    "Media element playback is not blocked");
    329  // Unclear how to check for "paused for user interaction" and "paused for
    330  // in-band content".
    331 
    332  await new Promise(r => vid.ontimeupdate = r);
    333  assert_between_exclusive(vid.currentTime, 0, 1,
    334    "currentTime advances while potentially playing");
    335 }, "Tests that a media element with an assigned MediaStream does not start advancing currentTime until potentially playing");
    336 
    337 promise_test(async t => {
    338  const stream = await navigator.mediaDevices.getUserMedia({video: true});
    339  t.add_cleanup(() => {
    340    vid.srcObject = null;
    341    stream.getTracks().forEach(track => track.stop());
    342  });
    343  assert_equals(vid.loop, false, "loop is false by default");
    344  vid.srcObject = stream;
    345 
    346  vid.loop = true;
    347  assert_equals(vid.loop, true,
    348    "loop can be changed when assigned a MediaStream");
    349 
    350  await new Promise(r => vid.onloadeddata = r);
    351  vid.loop = false;
    352  assert_equals(vid.loop, false,
    353    "loop can be changed when having loaded a MediaStream");
    354 
    355  vid.play();
    356  await new Promise(r => vid.ontimeupdate = r);
    357  vid.loop = true;
    358  assert_equals(vid.loop, true,
    359    "loop can be changed when playing a MediaStream");
    360 
    361  for(const t of stream.getTracks()) {
    362    t.stop();
    363  }
    364  // If loop is ignored, we get "ended",
    365  // otherwise the media element sets currentTime to 0 without ending.
    366  await new Promise(r => vid.onended = r);
    367 }, "Tests that the loop attribute has no effect on a media element with an assigned MediaStream");
    368 
    369 promise_test(async t => {
    370  const stream = await navigator.mediaDevices.getUserMedia({video: true});
    371  t.add_cleanup(() => { vid.srcObject = null; });
    372  vid.srcObject = stream;
    373 
    374  await vid.play();
    375 
    376  for (const track of stream.getTracks()) {
    377    track.stop();
    378  }
    379 
    380  assert_false(stream.active, "MediaStream becomes inactive with only ended tracks");
    381  assert_false(vid.ended, "HTMLMediaElement reports ended the next time the event loop reaches step 1 (sync)");
    382 
    383  await Promise.resolve();
    384  assert_false(vid.ended, "HTMLMediaElement reports ended the next time the event loop reaches step 1 (microtask)");
    385 
    386  let ended = false;
    387  vid.onended = () => ended = true;
    388  await new Promise(r => queueTask(r));
    389 
    390  assert_true(vid.ended, "HTMLMediaElement becomes ended asynchronously when its MediaStream provider becomes inactive");
    391  assert_true(ended, "HTMLMediaElement fires the ended event asynchronously when its MediaStream provider becomes inactive");
    392 }, "Tests that a media element with an assigned MediaStream ends when the MediaStream becomes inactive through tracks ending");
    393 
    394 promise_test(async t => {
    395  const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
    396  t.add_cleanup(() => {
    397    aud.srcObject = null;
    398    stream.getTracks().forEach(track => track.stop());
    399  });
    400  aud.srcObject = stream;
    401 
    402  await aud.play();
    403 
    404  for (const track of stream.getAudioTracks()) {
    405    track.stop();
    406  }
    407 
    408  assert_true(stream.active, "MediaStream is still active with a live video track");
    409  assert_false(aud.ended, "HTMLMediaElement reports ended the next time the event loop reaches step 1 (sync)");
    410 
    411  await Promise.resolve();
    412  assert_false(aud.ended, "HTMLMediaElement reports ended the next time the event loop reaches step 1 (microtask)");
    413 
    414  let ended = false;
    415  aud.onended = () => ended = true;
    416  await new Promise(r => queueTask(r));
    417 
    418  assert_true(aud.ended, "HTMLAudioElement becomes ended asynchronously when its MediaStream provider becomes inaudible");
    419  assert_true(ended, "HTMLAudioElement fires the ended event asynchronously when its MediaStream provider becomes inaudible");
    420 }, "Tests that an audio element with an assigned MediaStream ends when the MediaStream becomes inaudible through audio tracks ending");
    421 
    422 promise_test(async t => {
    423  const stream = await navigator.mediaDevices.getUserMedia({video: true});
    424  t.add_cleanup(() => { vid.srcObject = null; });
    425  vid.srcObject = stream;
    426 
    427  await vid.play();
    428 
    429  for (const track of stream.getTracks()) {
    430    stream.removeTrack(track);
    431  }
    432 
    433  assert_false(stream.active, "MediaStream becomes inactive with no tracks");
    434  assert_false(vid.ended, "HTMLMediaElement reports ended the next time the event loop reaches step 1 (sync)");
    435 
    436  await Promise.resolve();
    437  assert_false(vid.ended, "HTMLMediaElement reports ended the next time the event loop reaches step 1 (microtask)");
    438 
    439  let ended = false;
    440  vid.onended = () => ended = true;
    441  await new Promise(r => queueTask(r));
    442 
    443  assert_true(vid.ended, "HTMLMediaElement becomes ended asynchronously when its MediaStream provider becomes inactive");
    444  assert_true(ended, "HTMLMediaElement fires the ended event asynchronously when its MediaStream provider becomes inactive");
    445 }, "Tests that a media element with an assigned MediaStream ends when the MediaStream becomes inactive through track removal");
    446 
    447 promise_test(async t => {
    448  const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
    449  t.add_cleanup(() => {
    450    aud.srcObject = null;
    451    stream.getTracks().forEach(track => track.stop());
    452  });
    453  aud.srcObject = stream;
    454 
    455  await aud.play();
    456 
    457  for (const track of stream.getAudioTracks()) {
    458    stream.removeTrack(track);
    459  }
    460 
    461  assert_true(stream.active, "MediaStream is still active with a live video track");
    462  assert_false(aud.ended, "HTMLMediaElement reports ended the next time the event loop reaches step 1 (sync)");
    463 
    464  await Promise.resolve();
    465  assert_false(aud.ended, "HTMLMediaElement reports ended the next time the event loop reaches step 1 (microtask)");
    466 
    467  let ended = false;
    468  aud.onended = () => ended = true;
    469  await new Promise(r => queueTask(r));
    470 
    471  assert_true(aud.ended, "HTMLAudioElement becomes ended asynchronously when its MediaStream provider becomes inaudible");
    472  assert_true(ended, "HTMLAudioElement fires the ended event asynchronously when its MediaStream provider becomes inaudible");
    473 }, "Tests that an audio element with an assigned MediaStream ends when the MediaStream becomes inaudible through track removal");
    474 </script>
    475 </body>
    476 </html>