| <!doctype html> |
| <meta charset=utf-8> |
| <meta name="timeout" content="long"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script> |
| 'use strict'; |
| |
| async function getFrameStatsUntil(track, condition) { |
| while (true) { |
| const stats = await track.getFrameStats(); |
| if (condition(stats)) { |
| return stats; |
| } |
| } |
| } |
| |
| promise_test(async t => { |
| const stream = await navigator.mediaDevices.getUserMedia({video:true}); |
| const [track] = stream.getTracks(); |
| t.add_cleanup(() => track.stop()); |
| |
| const firstStats = |
| await getFrameStatsUntil(track, stats => stats.totalFrames > 0); |
| await getFrameStatsUntil(track, |
| stats => stats.totalFrames > firstStats.totalFrames); |
| }, `totalFrames increases over time`); |
| |
| promise_test(async t => { |
| const stream = await navigator.mediaDevices.getUserMedia({video:true}); |
| const [track] = stream.getTracks(); |
| t.add_cleanup(() => track.stop()); |
| |
| // `deliveredFrames` increments for each deliverable frame, even if the |
| // `track` does not have any sink. |
| const firstStats = await getFrameStatsUntil( |
| track, stats => stats.deliveredFrames > 0); |
| await getFrameStatsUntil( |
| track, stats => stats.deliveredFrames > firstStats.deliveredFrames); |
| }, `deliveredFrames increases, even without sinks`); |
| |
| promise_test(async t => { |
| const stream = await navigator.mediaDevices.getUserMedia({ |
| video:{frameRate:{ideal:20}} |
| }); |
| const [track] = stream.getTracks(); |
| t.add_cleanup(() => track.stop()); |
| |
| // Assert test prerequisite is met: frames will be discarded if the track is |
| // opened with a higher frame rate than we apply after it is opened. |
| assert_greater_than(track.getSettings().frameRate, 10); |
| await track.applyConstraints({frameRate:{ideal:10}}); |
| |
| await getFrameStatsUntil(track, stats => stats.discardedFrames > 0); |
| }, `discardedFrames increases when frameRate decimation is happening`); |
| |
| promise_test(async t => { |
| const stream = await navigator.mediaDevices.getUserMedia({ |
| video:{frameRate:{ideal:20}} |
| }); |
| const [track] = stream.getTracks(); |
| t.add_cleanup(() => track.stop()); |
| |
| // Assert test prerequisite is met: frames will be discarded if the track is |
| // opened with a higher frame rate than we apply after it is opened. |
| assert_greater_than(track.getSettings().frameRate, 10); |
| await track.applyConstraints({frameRate:{ideal:10}}); |
| |
| // Wait until we have both delivered and discarded frames. |
| const stats = await getFrameStatsUntil(track, stats => |
| stats.deliveredFrames > 0 && stats.discardedFrames > 0); |
| |
| // This test assumes that no frames are dropped, otherwise `totalFrames` can |
| // be greater than the sum of `deliveredFrames` and `discardedFrames`. |
| assert_equals(stats.totalFrames, |
| stats.deliveredFrames + stats.discardedFrames); |
| }, `totalFrames is the sum of deliveredFrames and discardedFrames`); |
| |
| promise_test(async t => { |
| const stream = await navigator.mediaDevices.getUserMedia({ |
| video:{frameRate:{ideal:20}} |
| }); |
| const [track] = stream.getTracks(); |
| t.add_cleanup(() => track.stop()); |
| |
| // Assert test prerequisite is met: frames will be discarded if the track is |
| // opened with a higher frame rate than we apply after it is opened. |
| assert_greater_than(track.getSettings().frameRate, 10); |
| await track.applyConstraints({frameRate:{ideal:10}}); |
| |
| // Wait for media to flow before disabling the `track`. |
| const initialStats = await getFrameStatsUntil(track, stats => |
| stats.deliveredFrames > 0 && stats.discardedFrames > 0 && |
| stats.totalFrames > 10); |
| track.enabled = false; |
| // Upon disabling, the counters are not reset. |
| const disabledSnapshot = await track.getFrameStats(); |
| assert_greater_than_equal(disabledSnapshot.deliveredFrames, |
| initialStats.deliveredFrames); |
| assert_greater_than_equal(disabledSnapshot.discardedFrames, |
| initialStats.discardedFrames); |
| assert_greater_than_equal(disabledSnapshot.totalFrames, |
| initialStats.totalFrames); |
| |
| // Wait enough time that frames should have been produced. |
| await new Promise(r => t.step_timeout(r, 500)); |
| |
| // Frame metrics should be frozen, but because `enabled = false` does not |
| // return a promise, we allow some lee-way in case a frame was still in flight |
| // during the disabling. |
| const stats = await track.getFrameStats(); |
| assert_approx_equals( |
| stats.deliveredFrames, disabledSnapshot.deliveredFrames, 1); |
| assert_approx_equals( |
| stats.discardedFrames, disabledSnapshot.discardedFrames, 1); |
| assert_approx_equals(stats.totalFrames, disabledSnapshot.totalFrames, 1); |
| }, `Stats are frozen while disabled`); |
| |
| promise_test(async t => { |
| const stream = await navigator.mediaDevices.getUserMedia({ |
| video:{frameRate:{ideal:20}} |
| }); |
| const [track] = stream.getTracks(); |
| t.add_cleanup(() => track.stop()); |
| |
| // Assert test prerequisite is met: frames will be discarded if the track is |
| // opened with a higher frame rate than we apply after it is opened. |
| assert_greater_than(track.getSettings().frameRate, 10); |
| await track.applyConstraints({frameRate:{ideal:10}}); |
| |
| // Wait for media to flow before disabling the `track`. |
| const initialStats = await getFrameStatsUntil(track, stats => |
| stats.deliveredFrames > 10 && stats.discardedFrames > 10); |
| track.enabled = false; |
| |
| // Re-enable the track. The stats counters should be greater than or equal to |
| // what they were previously. |
| track.enabled = true; |
| const stats = await track.getFrameStats(); |
| assert_greater_than_equal(stats.deliveredFrames, |
| initialStats.deliveredFrames); |
| assert_greater_than_equal(stats.discardedFrames, |
| initialStats.discardedFrames); |
| assert_greater_than_equal(stats.totalFrames, |
| initialStats.totalFrames); |
| }, `Disabling and re-enabling does not reset the counters`); |
| |
| promise_test(async t => { |
| const stream = await navigator.mediaDevices.getUserMedia({ |
| video:{frameRate:{ideal:20}} |
| }); |
| const [originalTrack] = stream.getTracks(); |
| t.add_cleanup(() => originalTrack.stop()); |
| |
| // Assert test prerequisite is met: frames will be discarded if the track is |
| // opened with a higher frame rate than we apply after it is opened. |
| assert_greater_than(originalTrack.getSettings().frameRate, 10); |
| await originalTrack.applyConstraints({frameRate:{ideal:10}}); |
| |
| // Wait for media to flow before disabling the `track`. |
| await getFrameStatsUntil(originalTrack, stats => |
| stats.deliveredFrames > 0 && stats.discardedFrames > 0); |
| originalTrack.enabled = false; |
| const originalTrackInitialStats = await originalTrack.getFrameStats(); |
| |
| // Clone the track, its counters should be zero initially. |
| // This is not racy because the cloned track is also disabled. |
| const clonedTrack = originalTrack.clone(); |
| t.add_cleanup(() => clonedTrack.stop()); |
| const clonedTrackStats = await clonedTrack.getFrameStats(); |
| assert_equals(clonedTrackStats.deliveredFrames, 0); |
| assert_equals(clonedTrackStats.discardedFrames, 0); |
| assert_equals(clonedTrackStats.totalFrames, 0); |
| |
| // Enabled the cloned track and wait for media to flow. |
| clonedTrack.enabled = true; |
| await getFrameStatsUntil(clonedTrack, stats => |
| stats.deliveredFrames > 0 && stats.discardedFrames > 0); |
| |
| // This does not affect the original track's stats, which are still frozen due |
| // to the original track being disabled. |
| const originalTrackSecondStats = await originalTrack.getFrameStats(); |
| assert_equals(originalTrackSecondStats.deliveredFrames, |
| originalTrackInitialStats.deliveredFrames); |
| assert_equals(originalTrackSecondStats.discardedFrames, |
| originalTrackInitialStats.discardedFrames); |
| assert_equals(originalTrackSecondStats.totalFrames, |
| originalTrackInitialStats.totalFrames); |
| }, `New stats baselines when a track is cloned from a disabled track`); |
| |
| promise_test(async t => { |
| const stream = await navigator.mediaDevices.getUserMedia({ |
| video:{frameRate:{ideal:20}} |
| }); |
| const [originalTrack] = stream.getTracks(); |
| t.add_cleanup(() => originalTrack.stop()); |
| |
| // Assert test prerequisite is met: frames will be discarded if the track is |
| // opened with a higher frame rate than we apply after it is opened. |
| assert_greater_than(originalTrack.getSettings().frameRate, 10); |
| await originalTrack.applyConstraints({frameRate:{ideal:10}}); |
| |
| // Wait for media to flow. |
| await getFrameStatsUntil(originalTrack, stats => |
| stats.deliveredFrames > 0 && stats.discardedFrames > 0); |
| |
| // Clone the track. While its counters should initially be zero, it would be |
| // racy to assert that they are exactly zero because media is flowing. |
| const clonedTrack = originalTrack.clone(); |
| t.add_cleanup(() => clonedTrack.stop()); |
| |
| // Ensure that as media continues to flow, the cloned track will necessarily |
| // have less frames than the original track on all accounts since its counters |
| // will have started from zero. |
| const clonedTrackStats = await getFrameStatsUntil(clonedTrack, stats => |
| stats.deliveredFrames > 0 && stats.discardedFrames > 0); |
| const originalTrackStats = await originalTrack.getFrameStats(); |
| assert_less_than(clonedTrackStats.deliveredFrames, |
| originalTrackStats.deliveredFrames); |
| assert_less_than(clonedTrackStats.discardedFrames, |
| originalTrackStats.discardedFrames); |
| assert_less_than(clonedTrackStats.totalFrames, |
| originalTrackStats.totalFrames); |
| }, `New stats baselines when a track is cloned from an enabled track`); |
| |
| promise_test(async t => { |
| const stream = await navigator.mediaDevices.getUserMedia({ |
| video:{frameRate:{ideal:20}} |
| }); |
| const [originalTrack] = stream.getTracks(); |
| t.add_cleanup(() => originalTrack.stop()); |
| |
| // Assert test prerequisite is met: frames will be discarded if the track is |
| // opened with a higher frame rate than we apply after it is opened. |
| assert_greater_than(originalTrack.getSettings().frameRate, 10); |
| await originalTrack.applyConstraints({frameRate:{ideal:10}}); |
| |
| // Wait for media to flow. |
| await getFrameStatsUntil(originalTrack, stats => |
| stats.deliveredFrames > 0 && stats.discardedFrames > 0); |
| |
| // Clone and wait for media to flow. |
| const cloneA = originalTrack.clone(); |
| t.add_cleanup(() => cloneA.stop()); |
| await getFrameStatsUntil(cloneA, stats => |
| stats.deliveredFrames > 0 && stats.discardedFrames > 0); |
| |
| // Clone the clone and wait for media to flow. |
| const cloneB = cloneA.clone(); |
| t.add_cleanup(() => cloneB.stop()); |
| await getFrameStatsUntil(cloneB, stats => |
| stats.deliveredFrames > 0 && stats.discardedFrames > 0); |
| |
| // Because every clone reset its counters and every waits for media before |
| // cloning, this must be true: originalStats > cloneAStats > cloneBStats. |
| const originalStats = await originalTrack.getFrameStats(); |
| const cloneAStats = await cloneA.getFrameStats(); |
| const cloneBStats = await cloneB.getFrameStats(); |
| assert_greater_than(originalStats.totalFrames, cloneAStats.totalFrames); |
| assert_greater_than(cloneAStats.totalFrames, cloneBStats.totalFrames); |
| }, `New stats baselines for the clone of a clone`); |
| |
| promise_test(async t => { |
| const stream = await navigator.mediaDevices.getUserMedia({audio:true}); |
| const [track] = stream.getTracks(); |
| t.add_cleanup(() => track.stop()); |
| |
| try { |
| await track.getFrameStats(); |
| assert_not_reached(); |
| } catch (e) { |
| assert_equals(e.name, 'NotSupportedError'); |
| } |
| }, `getFrameStats throws on audio tracks`); |
| </script> |