MediaStreamTrack-audio-stats.https.html (8936B)
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({ audio: 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 && stats.totalFramesDuration > firstStats.totalFramesDuration); 32 }, `totalFrames and totalFramesDuration increase over time`); 33 34 promise_test(async t => { 35 const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); 36 const [track] = stream.getTracks(); 37 t.add_cleanup(() => track.stop()); 38 39 // Wait one second for stats 40 const stats = 41 await getFrameStatsUntil(track, stats => stats.totalFramesDuration > 1000); 42 assert_less_than_equal(stats.deliveredFrames, stats.totalFrames); 43 assert_less_than_equal(stats.deliveredFramesDuration, stats.totalFramesDuration); 44 assert_greater_than_equal(stats.deliveredFrames, 0); 45 assert_greater_than_equal(stats.deliveredFramesDuration, 0); 46 }, `deliveredFrames and deliveredFramesDuration are at most as large as totalFrames and totalFramesDuration`); 47 48 promise_test(async t => { 49 function assertLatencyStatsInBounds(stats) { 50 assert_greater_than_equal(stats.maximumLatency, stats.latency); 51 assert_greater_than_equal(stats.maximumLatency, stats.averageLatency); 52 assert_less_than_equal(stats.minimumLatency, stats.latency); 53 assert_less_than_equal(stats.minimumLatency, stats.averageLatency); 54 }; 55 56 const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); 57 const [track] = stream.getTracks(); 58 t.add_cleanup(() => track.stop()); 59 const firstStats = track.stats.toJSON(); 60 assertLatencyStatsInBounds(firstStats); 61 62 // Wait one second for a second stats object. 63 const secondStats = 64 await getFrameStatsUntil(track, stats => stats.totalFramesDuration - firstStats.totalFramesDuration >= 1000); 65 assertLatencyStatsInBounds(secondStats); 66 67 // Reset the latency stats and wait one second for a third stats object. 68 track.stats.resetLatency(); 69 const thirdStats = 70 await getFrameStatsUntil(track, stats => stats.totalFramesDuration - secondStats.totalFramesDuration >= 1000); 71 assertLatencyStatsInBounds(thirdStats); 72 }, `Latency and averageLatency is within the bounds of minimumLatency and maximumLatency`); 73 74 promise_test(async t => { 75 // This behaviour is defined in 76 // https://w3c.github.io/mediacapture-extensions/#dom-mediastreamtrackaudiostats-resetlatency 77 const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); 78 const [track] = stream.getTracks(); 79 t.add_cleanup(() => track.stop()); 80 81 // Wait one second for stats 82 const stats = 83 await getFrameStatsUntil(track, stats => stats.totalFramesDuration > 1000); 84 track.stats.resetLatency(); 85 assert_equals(track.stats.latency, stats.latency); 86 assert_equals(track.stats.averageLatency, stats.latency); 87 assert_equals(track.stats.minimumLatency, stats.latency); 88 assert_equals(track.stats.maximumLatency, stats.latency); 89 }, `Immediately after resetLatency(), latency, averageLatency, minimumLatency and maximumLatency are equal to the most recent latency.`); 90 91 promise_test(async t => { 92 // This behaviour is defined in 93 // https://w3c.github.io/mediacapture-extensions/#dfn-expose-audio-frame-counters-steps 94 const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); 95 const [track] = stream.getTracks(); 96 t.add_cleanup(() => track.stop()); 97 98 // Wait until we have meaningful data 99 await getFrameStatsUntil(track, stats => stats.totalFrames > 0); 100 const firstStats = track.stats.toJSON(); 101 102 // Synchronously wait 500 ms. 103 const start = performance.now(); 104 while(performance.now() - start < 500); 105 106 // The stats should still be the same, despite the time difference. 107 const secondStats = track.stats.toJSON(); 108 assert_equals(JSON.stringify(firstStats), JSON.stringify(secondStats)); 109 }, `Stats do not change within the same task execution cycle.`); 110 111 promise_test(async t => { 112 const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); 113 const [track] = stream.getTracks(); 114 t.add_cleanup(() => track.stop()); 115 116 // Wait for media to flow before disabling the `track`. 117 const initialStats = await getFrameStatsUntil(track, stats => stats.totalFrames > 0); 118 track.enabled = false; 119 // Upon disabling, the counters are not reset. 120 const disabledSnapshot = track.stats.toJSON(); 121 assert_greater_than_equal(disabledSnapshot.totalFramesDuration, 122 initialStats.totalFramesDuration); 123 124 await new Promise(r => t.step_timeout(r, 4000)); 125 126 // Frame metrics should be frozen, but because `enabled = false` does not 127 // return a promise, we allow some lee-way in case som buffers were still in flight 128 // during the disabling. 129 assert_approx_equals( 130 track.stats.totalFramesDuration, disabledSnapshot.totalFramesDuration, 1000); 131 }, `Stats are frozen while disabled`); 132 133 promise_test(async t => { 134 const stream = await navigator.mediaDevices.getUserMedia({audio:true}); 135 const [track] = stream.getTracks(); 136 t.add_cleanup(() => track.stop()); 137 138 const a = track.stats; 139 await getFrameStatsUntil(track, stats => stats.totalFrames > 0); 140 const b = track.stats; 141 // The counters may have changed, but `a` and `b` are still the same object. 142 assert_equals(a, b); 143 }, `SameObject policy applies`); 144 145 promise_test(async t => { 146 const stream = await navigator.mediaDevices.getUserMedia({audio:true}); 147 const [track] = stream.getTracks(); 148 t.add_cleanup(() => track.stop()); 149 150 // Hold a reference directly to the [SameObject] stats, bypassing the 151 // `track.stats` getter in the subsequent getting of `totalFrames`. 152 const stats = track.stats; 153 const firstTotalFrames = stats.totalFrames; 154 while (stats.totalFrames == firstTotalFrames) { 155 await Promise.resolve(); 156 } 157 assert_greater_than(stats.totalFrames, firstTotalFrames); 158 }, `Counters increase even if we don't call the track.stats getter`); 159 160 promise_test(async t => { 161 const stream = await navigator.mediaDevices.getUserMedia({audio:true}); 162 const [track] = stream.getTracks(); 163 t.add_cleanup(() => track.stop()); 164 165 // Wait for 500 ms of audio to flow before disabling the `track`. 166 const initialStats = await getFrameStatsUntil(track, stats => 167 stats.totalFramesDuration > 500); 168 track.enabled = false; 169 170 // Re-enable the track. The stats counters should be greater than or equal to 171 // what they were previously. 172 track.enabled = true; 173 assert_greater_than_equal(track.stats.totalFrames, 174 initialStats.totalFrames); 175 assert_greater_than_equal(track.stats.totalFramesDuration, 176 initialStats.totalFramesDuration); 177 // This should be true even in the next task execution cycle. 178 await Promise.resolve(); 179 assert_greater_than_equal(track.stats.totalFrames, 180 initialStats.totalFrames); 181 assert_greater_than_equal(track.stats.totalFramesDuration, 182 initialStats.totalFramesDuration); 183 }, `Disabling and re-enabling does not reset the counters`); 184 185 promise_test(async t => { 186 const stream = await navigator.mediaDevices.getUserMedia({audio:true}); 187 const [originalTrack] = stream.getTracks(); 188 t.add_cleanup(() => originalTrack.stop()); 189 190 // Wait for 500 ms of audio to flow. 191 await getFrameStatsUntil(originalTrack, stats => 192 stats.totalFramesDuration > 500); 193 194 // Clone the track. While its counters should initially be zero, it would be 195 // racy to assert that they are exactly zero because media is flowing. 196 const clonedTrack = originalTrack.clone(); 197 t.add_cleanup(() => clonedTrack.stop()); 198 199 // Ensure that as media continues to flow, the cloned track will necessarily 200 // have less frames than the original track on all accounts since its counters 201 // will have started from zero. 202 const clonedTrackStats = await getFrameStatsUntil(clonedTrack, stats => 203 stats.totalFramesDuration > 0); 204 assert_less_than(clonedTrackStats.totalFrames, 205 originalTrack.stats.totalFrames); 206 assert_less_than(clonedTrackStats.totalFramesDuration, 207 originalTrack.stats.totalFramesDuration); 208 }, `New stats baselines when a track is cloned from an enabled track`); 209 </script>