diff options
Diffstat (limited to 'packages/meshbay-hub/tests/harness')
| -rw-r--r-- | packages/meshbay-hub/tests/harness/mse_harness.mjs | 56 |
1 files changed, 46 insertions, 10 deletions
diff --git a/packages/meshbay-hub/tests/harness/mse_harness.mjs b/packages/meshbay-hub/tests/harness/mse_harness.mjs index 91018ef..f0fd04c 100644 --- a/packages/meshbay-hub/tests/harness/mse_harness.mjs +++ b/packages/meshbay-hub/tests/harness/mse_harness.mjs @@ -35,7 +35,8 @@ const useCallback = (fn) => fn; // be written in. Extracting into an order of our own would quietly repair a // hook declared before its own dependency — a real fault, which reached // production once, and which the harness is otherwise well placed to catch. -const src = ['currentRange', 'bufferedAhead', 'evictBehind', 'flushQueue', 'pump'] +const src = ['currentRange', 'bufferedAhead', 'aheadLimit', 'evictBehind', + 'flushQueue', 'pump'] .sort((a, b) => app.indexOf(`const ${a} = useCallback(`) - app.indexOf(`const ${b} = useCallback(`)) .map(grab).join('\n'); @@ -45,14 +46,21 @@ const CAP = capMB * 1048576; const BITRATE = fileMB * 1048576 / durationS; // Read from app.js too, so a change to the constants is a change to the test. +// Arithmetic is allowed because some of them are written as one (`48 * 1024 * +// 1024`), and refusing anything that is not arithmetic keeps this a reader +// rather than an evaluator of whatever happens to be on the line. const constOf = (name) => { - const m = app.match(new RegExp(`const ${name} = (\\d+)`)); + const m = app.match(new RegExp(`const ${name} = ([^;]+);`)); if (!m) throw new Error(`${name} not found`); - return Number(m[1]); + if (!/^[\d\s.*/+-]+$/.test(m[1])) throw new Error(`${name} is not a number`); + return Number(new Function(`return (${m[1]});`)()); }; const BUFFER_BEHIND_S = constOf('BUFFER_BEHIND_S'); const STREAM_WINDOW = constOf('STREAM_WINDOW'); const BUFFER_AHEAD_S = constOf('BUFFER_AHEAD_S'); +const BUFFER_AHEAD_STEP_BYTES = constOf('BUFFER_AHEAD_STEP_BYTES'); +const BUFFER_AHEAD_MAX_BYTES = constOf('BUFFER_AHEAD_MAX_BYTES'); +const BUFFER_AHEAD_MAX_S = constOf('BUFFER_AHEAD_MAX_S'); const QUEUE_HIGH_WATER = constOf('QUEUE_HIGH_WATER'); const CREDIT_KEEPALIVE_MS = constOf('CREDIT_KEEPALIVE_MS'); @@ -110,7 +118,11 @@ const sb = { }; let pendingRemoveEvents = 0; -const video = { currentTime: 0 }; +// `paused` is part of the element the player reads, not decoration: the +// read-ahead budget only grows while the film is running, and a fake video +// that never reports being paused would exercise the growing branch in a run +// whose whole point is that nobody pressed play. +const video = { currentTime: 0, paused: !playing }; const sbRef = { current: sb }, videoRef = { current: video }; const msRef = { current: { readyState: 'open', endOfStream() {} } }; const queueRef = { current: [] }; @@ -118,6 +130,13 @@ const appendingRef = { current: false }, endedRef = { current: false }; const outstandingRef = { current: 0 }, lastPokeRef = { current: 0 }; const quotaRef = { current: 0 }, stalledRef = { current: false }; const awaitingInitRef = { current: false }; +// The film's own average bitrate, as `stream_init` gives the player, and the +// budget state the read-ahead walks up from. Both start where the component +// starts them. +const bitrateRef = { current: BITRATE }; +const aheadBytesRef = { current: 0 }; +const aheadCapRef = { current: BUFFER_AHEAD_MAX_BYTES }; +const quotaHoldRef = { current: false }; const transportRef = { current: { connected: true, @@ -132,12 +151,16 @@ const fns = new Function( 'sbRef,videoRef,msRef,queueRef,appendingRef,endedRef,outstandingRef,' + 'lastPokeRef,transportRef,BUFFER_BEHIND_S,BUFFER_AHEAD_S,QUEUE_HIGH_WATER,' + 'CREDIT_KEEPALIVE_MS,STREAM_WINDOW,quotaRef,stalledRef,awaitingInitRef,' + - 'console,useCallback', - src + '\n return {currentRange, bufferedAhead, evictBehind, flushQueue, pump};' + 'bitrateRef,aheadBytesRef,aheadCapRef,quotaHoldRef,' + + 'BUFFER_AHEAD_STEP_BYTES,' + + 'BUFFER_AHEAD_MAX_BYTES,BUFFER_AHEAD_MAX_S,console,useCallback', + src + '\n return {currentRange, bufferedAhead, aheadLimit, evictBehind,' + + ' flushQueue, pump};' )(sbRef, videoRef, msRef, queueRef, appendingRef, endedRef, outstandingRef, lastPokeRef, transportRef, BUFFER_BEHIND_S, BUFFER_AHEAD_S, QUEUE_HIGH_WATER, CREDIT_KEEPALIVE_MS, STREAM_WINDOW, quotaRef, stalledRef, awaitingInitRef, - console, useCallback); + bitrateRef, aheadBytesRef, aheadCapRef, quotaHoldRef, BUFFER_AHEAD_STEP_BYTES, + BUFFER_AHEAD_MAX_BYTES, BUFFER_AHEAD_MAX_S, console, useCallback); // The player's own `updateend` listener, transcribed — the one part of the // component that is a listener rather than a callback, and the place the @@ -151,6 +174,15 @@ credit = STREAM_WINDOW; outstandingRef.current = STREAM_WINDOW; let wall = 0; const TICK = 0.05; +// Bytes the link has carried but not yet spent on a whole segment. This used +// to be re-created inside the loop and thrown away at the end of every tick, +// so a link slower than one segment per tick — 5 MB/s at this resolution, +// which is every mobile network there is — delivered *nothing at all* and the +// run reported a player that had simply never been fed. Anything below about +// forty megabits was unmeasurable here, which is most of the cases worth +// measuring. Carried over instead, and capped at one window's worth, because +// a link cannot bank a burst larger than what the node may have in flight. +let netBudget = 0; while (wall < wallS) { wall += TICK; if (playing) { @@ -160,9 +192,9 @@ while (wall < wallS) { fns.pump(); // the 1 s timer and timeupdate while (pendingRemoveEvents > 0) { pendingRemoveEvents--; updateend(); } - let budget = netMBs * 1048576 * TICK; - while (credit > 0 && budget >= SEG) { - credit--; budget -= SEG; sent++; + netBudget = Math.min(netBudget + netMBs * 1048576 * TICK, SEG * STREAM_WINDOW); + while (credit > 0 && netBudget >= SEG) { + credit--; netBudget -= SEG; sent++; outstandingRef.current = Math.max(0, outstandingRef.current - 1); queueRef.current.push({ byteLength: SEG }); fns.flushQueue(); @@ -176,6 +208,10 @@ console.log(JSON.stringify({ heldInBufferMB: +(bytes / 1048576).toFixed(1), queueDepth: queueRef.current.length, bufferedAheadS: +fns.bufferedAhead().toFixed(1), + aheadLimitS: +fns.aheadLimit().toFixed(1), + budgetMB: +(aheadBytesRef.current / 1048576).toFixed(1), + budgetCapMB: +(aheadCapRef.current / 1048576).toFixed(1), + bitrateMbits: +(BITRATE * 8 / 1e6).toFixed(2), watchedS: +video.currentTime.toFixed(1), grants: granted, keepalives, |