supervision 0.1.7 → 0.2.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/dist/constants/media-renderer.d.ts +3 -0
  2. package/dist/constants/media-renderer.d.ts.map +1 -1
  3. package/dist/detections/chunked-detection-frame-source.d.ts.map +1 -1
  4. package/dist/index.d.ts +8 -4
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +9281 -5038
  7. package/dist/index.js.map +1 -1
  8. package/dist/mask-preparation.worker.js +409 -185
  9. package/dist/mask-preparation.worker.js.map +1 -1
  10. package/dist/media/display-pixel-ratio.d.ts +15 -0
  11. package/dist/media/display-pixel-ratio.d.ts.map +1 -0
  12. package/dist/media/engine-import-failure.d.ts +16 -0
  13. package/dist/media/engine-import-failure.d.ts.map +1 -0
  14. package/dist/media/media-errors.d.ts +6 -0
  15. package/dist/media/media-errors.d.ts.map +1 -1
  16. package/dist/media/media-normalization.d.ts.map +1 -1
  17. package/dist/media/media-probe.d.ts.map +1 -1
  18. package/dist/media/media-source-state.d.ts.map +1 -1
  19. package/dist/media/media-source.d.ts +17 -0
  20. package/dist/media/media-source.d.ts.map +1 -1
  21. package/dist/media/mediabunny-media-source.d.ts.map +1 -1
  22. package/dist/media/video-engine-media-source.d.ts +54 -0
  23. package/dist/media/video-engine-media-source.d.ts.map +1 -0
  24. package/dist/media/video-engine-media-source.js +3 -0
  25. package/dist/media/video-engine-media-source.js.map +1 -0
  26. package/dist/playback/media-playback-controller.d.ts +7 -1
  27. package/dist/playback/media-playback-controller.d.ts.map +1 -1
  28. package/dist/render-preparation/mask-frame-artifact.d.ts +48 -12
  29. package/dist/render-preparation/mask-frame-artifact.d.ts.map +1 -1
  30. package/dist/render-preparation/mask-frame-compositor.d.ts +12 -5
  31. package/dist/render-preparation/mask-frame-compositor.d.ts.map +1 -1
  32. package/dist/render-preparation/mask-frame-preparer.d.ts.map +1 -1
  33. package/dist/render-preparation/mask-preparation-worker-count.d.ts +4 -4
  34. package/dist/render-preparation/mask-preparation-worker-protocol.d.ts +13 -3
  35. package/dist/render-preparation/mask-preparation-worker-protocol.d.ts.map +1 -1
  36. package/dist/render-preparation/prepared-render-window.d.ts +44 -2
  37. package/dist/render-preparation/prepared-render-window.d.ts.map +1 -1
  38. package/dist/render-preparation/prepared-window-timeline.d.ts +2 -6
  39. package/dist/render-preparation/prepared-window-timeline.d.ts.map +1 -1
  40. package/dist/renderers/injected-pixi.d.ts +38 -0
  41. package/dist/renderers/injected-pixi.d.ts.map +1 -0
  42. package/dist/renderers/mask-palette.d.ts +14 -0
  43. package/dist/renderers/mask-palette.d.ts.map +1 -0
  44. package/dist/renderers/mask-vertex.d.ts +14 -0
  45. package/dist/renderers/mask-vertex.d.ts.map +1 -0
  46. package/dist/renderers/media-renderer-core.d.ts +9 -0
  47. package/dist/renderers/media-renderer-core.d.ts.map +1 -1
  48. package/dist/renderers/media-renderer-scene.d.ts +50 -2
  49. package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
  50. package/dist/renderers/media-renderer-state.d.ts +20 -1
  51. package/dist/renderers/media-renderer-state.d.ts.map +1 -1
  52. package/dist/renderers/media-renderer-transport.d.ts +72 -0
  53. package/dist/renderers/media-renderer-transport.d.ts.map +1 -0
  54. package/dist/renderers/pixi-box-layer.d.ts +5 -0
  55. package/dist/renderers/pixi-box-layer.d.ts.map +1 -1
  56. package/dist/renderers/pixi-focus-layer.d.ts +19 -2
  57. package/dist/renderers/pixi-focus-layer.d.ts.map +1 -1
  58. package/dist/renderers/pixi-frame-present.d.ts +77 -0
  59. package/dist/renderers/pixi-frame-present.d.ts.map +1 -0
  60. package/dist/renderers/pixi-id-mask-shader.d.ts +6 -25
  61. package/dist/renderers/pixi-id-mask-shader.d.ts.map +1 -1
  62. package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
  63. package/dist/renderers/pixi-interaction-presentation-layer.d.ts +12 -2
  64. package/dist/renderers/pixi-interaction-presentation-layer.d.ts.map +1 -1
  65. package/dist/renderers/pixi-mask-halo.d.ts +18 -23
  66. package/dist/renderers/pixi-mask-halo.d.ts.map +1 -1
  67. package/dist/renderers/pixi-mask-layer.d.ts +94 -32
  68. package/dist/renderers/pixi-mask-layer.d.ts.map +1 -1
  69. package/dist/renderers/pixi-media-scene.d.ts +67 -0
  70. package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
  71. package/dist/renderers/pixi-polygon-layer.d.ts +9 -2
  72. package/dist/renderers/pixi-polygon-layer.d.ts.map +1 -1
  73. package/dist/renderers/pixi-region-coverage-mask.d.ts +62 -0
  74. package/dist/renderers/pixi-region-coverage-mask.d.ts.map +1 -0
  75. package/dist/renderers/pixi-region-effect.d.ts +42 -0
  76. package/dist/renderers/pixi-region-effect.d.ts.map +1 -0
  77. package/dist/renderers/pixi-region-layer.d.ts +43 -2
  78. package/dist/renderers/pixi-region-layer.d.ts.map +1 -1
  79. package/dist/renderers/pixi-shader-lifecycle.d.ts +14 -0
  80. package/dist/renderers/pixi-shader-lifecycle.d.ts.map +1 -0
  81. package/dist/renderers/pixi-vector-layer.d.ts.map +1 -1
  82. package/dist/renderers/prepared-annotation-window.d.ts +47 -0
  83. package/dist/renderers/prepared-annotation-window.d.ts.map +1 -0
  84. package/dist/renderers/presented-frame-channel.d.ts +138 -0
  85. package/dist/renderers/presented-frame-channel.d.ts.map +1 -0
  86. package/dist/renderers/scene-render-scheduler.d.ts +20 -0
  87. package/dist/renderers/scene-render-scheduler.d.ts.map +1 -0
  88. package/dist/sessions/media-session-defaults.d.ts +10 -6
  89. package/dist/sessions/media-session-defaults.d.ts.map +1 -1
  90. package/dist/sessions/media-session-media.d.ts.map +1 -1
  91. package/dist/sessions/media-session-state.d.ts.map +1 -1
  92. package/dist/sessions/media-session.d.ts.map +1 -1
  93. package/dist/tracking.worker.js +73 -4
  94. package/dist/tracking.worker.js.map +1 -1
  95. package/dist/types/media-normalization.d.ts +34 -0
  96. package/dist/types/media-normalization.d.ts.map +1 -1
  97. package/dist/types/media-renderer.d.ts +31 -1
  98. package/dist/types/media-renderer.d.ts.map +1 -1
  99. package/dist/types/media-session.d.ts +89 -15
  100. package/dist/types/media-session.d.ts.map +1 -1
  101. package/dist/types/render-preparation.d.ts +149 -5
  102. package/dist/types/render-preparation.d.ts.map +1 -1
  103. package/dist/video-engine-media-source-CUXnOJOV.js +450 -0
  104. package/dist/video-engine-media-source-CUXnOJOV.js.map +1 -0
  105. package/dist/web-video-engine/analysis-session.d.ts +75 -0
  106. package/dist/web-video-engine/analysis.d.ts +6 -0
  107. package/dist/web-video-engine/analysis.js +1850 -0
  108. package/dist/web-video-engine/cache-budget.d.ts +18 -0
  109. package/dist/web-video-engine/canvas-sink-scrub-cursor.d.ts +89 -0
  110. package/dist/web-video-engine/clock.d.ts +82 -0
  111. package/dist/web-video-engine/constants.d.ts +261 -0
  112. package/dist/web-video-engine/create-scrub-cursor.d.ts +65 -0
  113. package/dist/web-video-engine/decode-resolution.d.ts +114 -0
  114. package/dist/web-video-engine/decode-scheduler.d.ts +371 -0
  115. package/dist/web-video-engine/decode-session.d.ts +283 -0
  116. package/dist/web-video-engine/decode-source.d.ts +258 -0
  117. package/dist/web-video-engine/diagnostics-store.d.ts +17 -0
  118. package/dist/web-video-engine/diagnostics.d.ts +316 -0
  119. package/dist/web-video-engine/embedded-engine-worker.d.ts +1 -0
  120. package/dist/web-video-engine/engine-core.d.ts +269 -0
  121. package/dist/web-video-engine/engine.d.ts +25 -0
  122. package/dist/web-video-engine/engine.js +912 -0
  123. package/dist/web-video-engine/engine.worker.d.ts +1 -0
  124. package/dist/web-video-engine/engine.worker.js +33624 -0
  125. package/dist/web-video-engine/frame-cache.d.ts +236 -0
  126. package/dist/web-video-engine/frame-extractor.d.ts +27 -0
  127. package/dist/web-video-engine/frame-timeline-DreEwsRY.js +616 -0
  128. package/dist/web-video-engine/frame-timeline.d.ts +80 -0
  129. package/dist/web-video-engine/frame-walker.d.ts +73 -0
  130. package/dist/web-video-engine/index.d.ts +13 -0
  131. package/dist/web-video-engine/index.d.ts.map +1 -0
  132. package/dist/web-video-engine/index.js +3 -0
  133. package/dist/web-video-engine/index.js.map +1 -0
  134. package/dist/web-video-engine/key-packet.d.ts +64 -0
  135. package/dist/web-video-engine/keyframe-index.d.ts +90 -0
  136. package/dist/web-video-engine/mirror-store.d.ts +55 -0
  137. package/dist/web-video-engine/renderer.d.ts +93 -0
  138. package/dist/web-video-engine/rotation.d.ts +34 -0
  139. package/dist/web-video-engine/scrub-controller.d.ts +375 -0
  140. package/dist/web-video-engine/scrub-cursor.d.ts +347 -0
  141. package/dist/web-video-engine/scrub-trajectory.d.ts +29 -0
  142. package/dist/web-video-engine/source-residency.d.ts +91 -0
  143. package/dist/web-video-engine/trace-recorder.d.ts +163 -0
  144. package/dist/web-video-engine/types.d.ts +200 -0
  145. package/dist/web-video-engine/video-engine.d.ts +437 -0
  146. package/dist/web-video-engine/webgpu-renderer.d.ts +61 -0
  147. package/dist/web-video-engine/worker-bridge.d.ts +11 -0
  148. package/dist/web-video-engine/worker-dispatch.d.ts +16 -0
  149. package/dist/web-video-engine/worker-protocol.d.ts +282 -0
  150. package/node_modules/supervision-js-core/dist/detections/buffered-detection-timeline.d.ts.map +1 -1
  151. package/node_modules/supervision-js-core/dist/detections/composite-detection-frame-source.d.ts.map +1 -1
  152. package/node_modules/supervision-js-core/dist/index.d.ts +5 -4
  153. package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
  154. package/node_modules/supervision-js-core/dist/index.js +1167 -280
  155. package/node_modules/supervision-js-core/dist/index.js.map +1 -1
  156. package/node_modules/supervision-js-core/dist/styles/default-annotation-presentation.d.ts.map +1 -1
  157. package/node_modules/supervision-js-core/dist/styles/interaction-style.d.ts +4 -36
  158. package/node_modules/supervision-js-core/dist/styles/interaction-style.d.ts.map +1 -1
  159. package/node_modules/supervision-js-core/dist/styles/polyline-style.d.ts +5 -0
  160. package/node_modules/supervision-js-core/dist/styles/polyline-style.d.ts.map +1 -1
  161. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts +92 -7
  162. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -1
  163. package/node_modules/supervision-js-core/dist/types/detection-timeline.d.ts +191 -17
  164. package/node_modules/supervision-js-core/dist/types/detection-timeline.d.ts.map +1 -1
  165. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts +83 -5
  166. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts.map +1 -1
  167. package/node_modules/supervision-js-core/dist/types/polyline-style.d.ts +2 -0
  168. package/node_modules/supervision-js-core/dist/types/polyline-style.d.ts.map +1 -1
  169. package/node_modules/supervision-js-core/dist/types/session-lifecycle.d.ts +32 -0
  170. package/node_modules/supervision-js-core/dist/types/session-lifecycle.d.ts.map +1 -1
  171. package/node_modules/supervision-js-core/dist/utils/detection-conversions.d.ts.map +1 -1
  172. package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts +5 -0
  173. package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts.map +1 -1
  174. package/node_modules/supervision-js-core/dist/utils/detection-masks.d.ts +9 -0
  175. package/node_modules/supervision-js-core/dist/utils/detection-masks.d.ts.map +1 -1
  176. package/node_modules/supervision-js-core/dist/utils/detection-ranges.d.ts +18 -0
  177. package/node_modules/supervision-js-core/dist/utils/detection-ranges.d.ts.map +1 -0
  178. package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts +34 -2
  179. package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts.map +1 -1
  180. package/node_modules/supervision-js-core/dist/utils/wait-bound.d.ts +7 -0
  181. package/node_modules/supervision-js-core/dist/utils/wait-bound.d.ts.map +1 -0
  182. package/package.json +24 -3
@@ -0,0 +1,616 @@
1
+ /**
2
+ * READ_AHEAD_CANVAS is the playback decode-ahead depth: how many decoded frames
3
+ * the controller keeps queued ahead of the playhead so a decode that overruns a
4
+ * frame interval does not starve the next tick (the cause of stutter even on
5
+ * small, well-encoded clips, where playback otherwise pulls one frame per paint
6
+ * with no cushion). Applies to canvas blits, which are independent and cheap to
7
+ * hold; sample (zero-copy) frames pin a decoder slot each, so the controller
8
+ * keeps those at a depth of one to spare the WebCodecs pool.
9
+ *
10
+ * REANCHOR_STALL_MS is the wall time past which a picture standing still stops
11
+ * being a hitch to work through and becomes a stretch to re-anchor across. Two
12
+ * gaps are measured against it, and they settle in opposite directions.
13
+ *
14
+ * A gap between playing render ticks is a loop nothing was driving, which a
15
+ * hidden tab produces. Nobody was watching, so the clock is the truth and the
16
+ * walk seeks forward onto the playhead.
17
+ *
18
+ * A gap between the walk's deliveries is a pipeline with nothing to show while
19
+ * the loop ran, which a seek onto unbuffered ground produces. The viewer sat
20
+ * through it waiting on a position they asked for, so the frame is the truth
21
+ * and the clock is pulled back onto it. Left to catch up instead, the walk
22
+ * replays the whole wait at decode speed on its way to the playhead: measured
23
+ * against the deployed demo over six seeks, a mean 2.4x for a second, peaking
24
+ * at 24x, with 44 source frames flashed past.
25
+ *
26
+ * Wall time is the reading in both, so a high rate legitimately moving the
27
+ * media clock several frames per gap is not read as an absence.
28
+ *
29
+ * Half a second is where the tick-gap costs cross at 1x. A backlog costs one
30
+ * decode per source frame in it, measured at 240 frames a second on the 30fps
31
+ * demo clip (4.2ms each), against a keyframe-anchored seek at p95 47ms on the
32
+ * same clip: half a second of 30fps backlog is 15 frames, so 63ms of decode
33
+ * against that 47ms, and every higher rate puts more frames in the same gap. It
34
+ * clears the worst legitimate gap of either kind by the same order: a foreground
35
+ * loop leaves a long task or a GC pause to the frame-by-frame path, and forty
36
+ * seconds of network-fed playback of the demo clip held its picture still for at
37
+ * most 51ms.
38
+ *
39
+ * The present cadence is how often playback puts a frame on screen: the rate's
40
+ * whole demand cut to the share of it the machine has been paying for, and never
41
+ * under the source's own frame rate. At a full share the pump declines nothing,
42
+ * which is where a machine with headroom stays at every rate.
43
+ * A step moves the share by PRESENT_CADENCE_STEP, taking a quarter off it when
44
+ * the pipeline has fallen behind the playhead and putting that same quarter back
45
+ * when it has room to spare. The share bottoms out at one over
46
+ * PLAYBACK_RATE.MAX, which is the deepest cut that means anything: below it even
47
+ * the fastest rate asks for less than the source rate the cadence floors at.
48
+ *
49
+ * What takes the share down is a running bill, in frames, between what the clock
50
+ * asked the pipeline for and what the play walk delivered. The bill accumulates
51
+ * rather than being read per tick, which is what tells a shortfall from noise:
52
+ * counting whole frames against a fractional demand leaves every tick out by up
53
+ * to one either way and those cancel, while a machine that cannot sustain the
54
+ * rate falls short every tick and adds up. Measured over 100ms windows on an M3
55
+ * Max at rates 2x to 8x, a pipeline with headroom stayed inside 1.5 frames of
56
+ * its demand either way, so a bill of PRESENT_CADENCE_EVIDENCE_FRAMES frames is
57
+ * one it cannot run up.
58
+ *
59
+ * What puts the share back is the walk holding a frame the clock has not run
60
+ * past, unbroken for the wall time the source takes to produce that many frames.
61
+ * That is the only evidence a pipeline can offer that it has room to spare: a
62
+ * machine at equilibrium delivers exactly what the clock asks for however much
63
+ * headroom it has, so waiting for a surplus in the bill would leave every
64
+ * machine at whatever its worst stretch measured. It is a live reading and the
65
+ * bill is a cumulative one, which is why each takes one direction: ground lost
66
+ * still reads lost once the walk is keeping up again, and catch-up pulls are
67
+ * what carry the walk back onto the playhead.
68
+ *
69
+ * Neither reading is the depth of the decode-ahead queue, which belongs to the
70
+ * decode path and not to the machine: on the zero-copy path, which pins a
71
+ * decoder slot per queued frame and so buffers one, a single frame in hand is
72
+ * full depth and one tick from empty alike.
73
+ *
74
+ * How far behind the machine is therefore sets the pace rather than the size of
75
+ * the step: the bigger the shortfall the sooner the bill reaches that figure and
76
+ * the sooner the next step lands. Zeroing on each step makes the next one gather
77
+ * its own evidence, which is what stops a share sitting at the machine's limit
78
+ * from pulsing.
79
+ *
80
+ * Frames the clock passed, frames the walk delivered and wall time on the
81
+ * playhead: nothing either reading is made of knows what the panel refreshes at,
82
+ * so one machine lands on one cadence on any panel. A faster panel offers more
83
+ * slots and a machine with the headroom fills them; what it cannot do is hold
84
+ * the pump above what the machine pays for, because the share comes down until
85
+ * the pipeline is back on the playhead.
86
+ *
87
+ * PRESENT_CADENCE_SLACK is how far under the interval still counts as a full
88
+ * one. Presents land on display ticks, so a panel refreshing at the ceiling
89
+ * measures each gap as one interval give or take rAF jitter, and an exact
90
+ * comparison would decline every second frame there. A quarter leaves 4.2ms of
91
+ * jitter room at 60Hz.
92
+ *
93
+ * PRESENT_CADENCE_HZ is the demand above which frames the pump culled or
94
+ * declined dominate the dropped-frame ledger, so the discarded-frames rule in
95
+ * evaluateWarnings stands down rather than reading a count it cannot attribute.
96
+ */
97
+ const PLAYBACK = {
98
+ READ_AHEAD_CANVAS: 3};
99
+ /**
100
+ * Forward playback-rate range the engine accepts. Reverse playback needs a
101
+ * backwards decode strategy the runtime does not have, so zero and negative
102
+ * rates are refused rather than approximated.
103
+ *
104
+ * MAX is an API bound, not a promise that every source plays smoothly there.
105
+ * Sustaining rate r means decoding r x nativeFps frames a second, and the
106
+ * decode-ahead cushion is fixed at PLAYBACK.READ_AHEAD_CANVAS frames (raising it
107
+ * would outrun the CanvasSink pool and tear), so how high a given source really
108
+ * goes is a property of that source's frame size and encode. The engine measures
109
+ * it instead of guessing: DiagnosticsSnapshot carries the commanded rate beside
110
+ * the presented one, and PLAYBACK_RATE_NOT_SUSTAINED fires when they diverge.
111
+ * SUSTAINED_SHORTFALL is the share of the commanded rate the presented one has
112
+ * to hold to count as sustained.
113
+ */
114
+ const PLAYBACK_RATE = {
115
+ MIN: 0.25,
116
+ MAX: 8,
117
+ SUSTAINED_SHORTFALL: 0.8,
118
+ };
119
+ /**
120
+ * CanvasSink frame-pool size: how many decoded canvases the sink recycles
121
+ * round-robin. The pool has no backpressure (a new decode overwrites the oldest
122
+ * slot rather than blocking), and the playback queue hands the raw pool canvas
123
+ * straight to the painter, so the pool must outnumber every canvas outstanding at
124
+ * once: the full play read-ahead, the one in flight, and the one being painted.
125
+ * Sized below that, a fresh decode stomps the pixels of a still-queued frame
126
+ * before it paints (silent tearing). The cache is unaffected; it copies into its
127
+ * own OffscreenCanvas. Derived from the read-ahead so the two cannot drift.
128
+ */
129
+ const SCRUB = {
130
+ DEFAULT_POOL_SIZE: PLAYBACK.READ_AHEAD_CANVAS + 2,
131
+ };
132
+ /**
133
+ * Bounds on building a track's frame timeline, the presentation-order table of
134
+ * every frame's timestamp in the container's own integer grain.
135
+ *
136
+ * MAX_FRAMES caps the metadata walk that builds it. A 70-second 30fps source
137
+ * walks 2113 packets in a measured 5.7ms; the cap is four hundred times that,
138
+ * which no ordinary file reaches and a fragmented or endless source does. Past
139
+ * it the load fails, so nothing silently runs without frame identity.
140
+ *
141
+ * FALLBACK_TICK_RATE is for a track backing that cannot state its grain. A
142
+ * microsecond is the finest timestamp WebCodecs carries, so no real timestamp is
143
+ * lost at that rate; what is lost is the guarantee that every timestamp is a
144
+ * whole tick.
145
+ */
146
+ const FRAME_TIMELINE = {
147
+ MAX_FRAMES: 1_000_000,
148
+ FALLBACK_TICK_RATE: 1e6,
149
+ };
150
+ /**
151
+ * Diagnostics instrument tunables. Inert unless a consumer opts in: the worker
152
+ * broadcasts a snapshot at BROADCAST_HZ only while subscribed, the trace rings
153
+ * allocate only while armed, and SCRUB_LATENCY_RING bounds the percentile sample.
154
+ */
155
+ const DIAGNOSTICS = {
156
+ BROADCAST_HZ: 10,
157
+ TRACE_EVENT_CAP: 2000,
158
+ TRACE_SNAPSHOT_CAP: 600,
159
+ SCRUB_LATENCY_RING: 64,
160
+ KEYFRAME_TIMESTAMPS_CAP: 512,
161
+ };
162
+ /**
163
+ * What an armed capture actually keeps. The snapshot ring is fed at the fixed
164
+ * broadcast rate, so its capacity converts to a wall-clock window; the event
165
+ * ring is fed by paints and gestures at no fixed rate, so its bound is a count
166
+ * and nothing more. A readout that states one number for "the capture" is
167
+ * describing neither ring.
168
+ */
169
+ const TRACE_RING_BOUNDS = {
170
+ snapshotWindowMs: (DIAGNOSTICS.TRACE_SNAPSHOT_CAP / DIAGNOSTICS.BROADCAST_HZ) * 1000,
171
+ snapshotCap: DIAGNOSTICS.TRACE_SNAPSHOT_CAP,
172
+ eventCap: DIAGNOSTICS.TRACE_EVENT_CAP,
173
+ };
174
+ /**
175
+ * Hang-recovery bounds for the worker video runtime. mediabunny's getCanvas/
176
+ * getSample take no AbortSignal, so a decode that never settles cannot be
177
+ * cancelled. These two timeouts bound the damage: a decode that outruns its
178
+ * ceiling is abandoned and its provider rebuilt, and a main-thread caller
179
+ * blocked on a wedged worker surfaces an error.
180
+ *
181
+ * DECODE_HANG_TIMEOUT_MS bounds a random-access decode operation, not a single
182
+ * frame. A seek or a play-start lands mid-GOP and decodes the whole
183
+ * keyframe-to-target prefix, so an 8-second GOP of 8MP frames legitimately runs
184
+ * tens of seconds. A ceiling sized for one frame fires on healthy progress and
185
+ * kills the decode it exists to protect.
186
+ *
187
+ * SEED_HANG_TIMEOUT_MS bounds the first-frame seed alone, which is why it is a
188
+ * fraction of the figure above: the seed anchors on the track's own first
189
+ * sample, so it walks no GOP prefix, and the one cost it carries that a later
190
+ * decode does not is the decoder's cold hardware configuration. A re-anchored
191
+ * decode of a 2840x2840 source measured 49ms mean / 80ms worst, so this leaves
192
+ * two orders of magnitude of headroom over the decode plus its cold start, and
193
+ * SEED_DECODE_ATTEMPTS of it still finish inside WORKER_COMMAND_TIMEOUT_MS with
194
+ * the rebuilds between them. That is what lets a decoder which never starts
195
+ * reach the caller as an error rather than as the facade giving up.
196
+ *
197
+ * That budget is the mediabunny sink paths', which carry no ceiling of their
198
+ * own. The long-lived decode session bounds its own wait for output more
199
+ * tightly and can tell a decoder that never started from one that went quiet
200
+ * after working, so on that path the seed ends on the session's word and these
201
+ * attempts go unspent.
202
+ *
203
+ * WORKER_COMMAND_TIMEOUT_MS is the main-thread backstop for an awaitable command
204
+ * (load, commit, step). It derives from the decode ceiling so the two cannot
205
+ * drift: the worker has to be able to reach its own hang path, recover, and
206
+ * reply before the facade gives up on it.
207
+ */
208
+ const DECODE_HANG_TIMEOUT_MS = 30_000;
209
+ const WORKER_COMMAND_TIMEOUT_MS = DECODE_HANG_TIMEOUT_MS + 15_000;
210
+ const HANG_RECOVERY = {
211
+ WORKER_COMMAND_TIMEOUT_MS,
212
+ };
213
+
214
+ const asSec = (n) => n;
215
+ const asFps = (n) => n;
216
+ const asPaintSeq = (n) => n;
217
+ var SourceKind;
218
+ (function (SourceKind) {
219
+ SourceKind["Url"] = "url";
220
+ SourceKind["Blob"] = "blob";
221
+ SourceKind["Stream"] = "stream";
222
+ })(SourceKind || (SourceKind = {}));
223
+ /**
224
+ * Coarse-grained engine status. Updates rarely (load, play, pause, end,
225
+ * error). Sits on its own emit channel so subscribers do not wake up on
226
+ * 60Hz time ticks.
227
+ */
228
+ var PlaybackStatus;
229
+ (function (PlaybackStatus) {
230
+ PlaybackStatus["Idle"] = "IDLE";
231
+ PlaybackStatus["Loading"] = "LOADING";
232
+ PlaybackStatus["Ready"] = "READY";
233
+ PlaybackStatus["Playing"] = "PLAYING";
234
+ PlaybackStatus["Paused"] = "PAUSED";
235
+ PlaybackStatus["Seeking"] = "SEEKING";
236
+ PlaybackStatus["Ended"] = "ENDED";
237
+ PlaybackStatus["Errored"] = "ERRORED";
238
+ })(PlaybackStatus || (PlaybackStatus = {}));
239
+ var WebVideoEngineErrorCode;
240
+ (function (WebVideoEngineErrorCode) {
241
+ WebVideoEngineErrorCode["DecodeUnsupported"] = "DECODE_UNSUPPORTED";
242
+ WebVideoEngineErrorCode["SourceUnreadable"] = "SOURCE_UNREADABLE";
243
+ /**
244
+ * The demuxer refused the file outright: its container is not one this build
245
+ * reads, so no track was ever listed and no decoder was ever asked.
246
+ */
247
+ WebVideoEngineErrorCode["ContainerUnreadable"] = "CONTAINER_UNREADABLE";
248
+ /**
249
+ * The container opened and the demuxer parsed no track at all out of it. The
250
+ * file's streams are in formats it does not carry, so their video cannot be
251
+ * reached even though it is there.
252
+ */
253
+ WebVideoEngineErrorCode["VideoTrackUnreadable"] = "VIDEO_TRACK_UNREADABLE";
254
+ /**
255
+ * The container opened, its tracks listed, and none of them is video. This
256
+ * is the only case where the file itself is what lacks video.
257
+ */
258
+ WebVideoEngineErrorCode["NoVideoTrack"] = "NO_VIDEO_TRACK";
259
+ WebVideoEngineErrorCode["BackendCrashed"] = "BACKEND_CRASHED";
260
+ WebVideoEngineErrorCode["Aborted"] = "ABORTED";
261
+ /** A canvas was offered to an engine loaded in "frames" presentation mode,
262
+ * where the host owns the only canvas. */
263
+ WebVideoEngineErrorCode["PresentationMismatch"] = "PRESENTATION_MISMATCH";
264
+ /**
265
+ * The decoder cannot decode this source at all: it refused to configure, it
266
+ * errored, or it acknowledged decode requests and never produced a frame.
267
+ * Distinct from BackendCrashed; this one survives every rebuild, so the
268
+ * runtime stops rebuilding and says so. The usual cause is outside the page:
269
+ * another tab holding every hardware decoder session the machine has.
270
+ */
271
+ WebVideoEngineErrorCode["DecoderStalled"] = "DECODER_STALLED";
272
+ /** A playback rate outside the forward range the engine supports. */
273
+ WebVideoEngineErrorCode["RateUnsupported"] = "RATE_UNSUPPORTED";
274
+ })(WebVideoEngineErrorCode || (WebVideoEngineErrorCode = {}));
275
+ /**
276
+ * Thrown by createScrubCursor / WebVideoEngine.load when decode is unsupported
277
+ * or the source is unreadable. Branch on `error.code` (WebVideoEngineErrorCode)
278
+ * to differentiate decode failures from network failures.
279
+ */
280
+ class WebVideoEngineError extends Error {
281
+ code;
282
+ cause;
283
+ constructor(code, message, cause) {
284
+ super(message);
285
+ this.code = code;
286
+ this.cause = cause;
287
+ }
288
+ }
289
+ /** Shared by the facade and the core so a host gets the same refusal wherever
290
+ * its canvas is caught. */
291
+ function canvasBindingRefused() {
292
+ return new WebVideoEngineError(WebVideoEngineErrorCode.PresentationMismatch, 'presentation "frames" leaves the canvas to the host: this engine paints nothing and hands out VideoFrames instead');
293
+ }
294
+ /**
295
+ * Validates a requested playback rate and returns it. The facade and the core
296
+ * both call it, so a rate is refused at whichever boundary it arrives at and
297
+ * with the same message: the facade posts fire-and-forget, so a refusal raised
298
+ * only in the worker would reach nobody.
299
+ *
300
+ * Reverse is rejected, not clamped: backwards playback is a different decode
301
+ * problem, and a -1 quietly serviced as +0.25 plays the wrong direction while
302
+ * reporting success.
303
+ */
304
+ function resolvePlaybackRate(rate) {
305
+ if (!Number.isFinite(rate) ||
306
+ rate < PLAYBACK_RATE.MIN ||
307
+ rate > PLAYBACK_RATE.MAX) {
308
+ throw new WebVideoEngineError(WebVideoEngineErrorCode.RateUnsupported, `playback rate ${rate} is outside the supported forward range ${PLAYBACK_RATE.MIN}-${PLAYBACK_RATE.MAX}; reverse playback is not supported`);
309
+ }
310
+ return rate;
311
+ }
312
+
313
+ /**
314
+ * Decode-resolution strategies. A strategy decides the pixel width each video
315
+ * frame is decoded to, independent of the source's native resolution. Height
316
+ * follows from native aspect, so a strategy only ever returns a width.
317
+ *
318
+ * Decoding below native trades preview sharpness for paint cost and frame-cache
319
+ * memory. It does NOT cut decode cost: the codec decodes the full coded frame
320
+ * regardless of target width, and CanvasSink resizes the result afterward. The
321
+ * win scales with the gap between source resolution and the on-screen box: a 4K
322
+ * source shown in a 640px surface still pays full 4K decode, but the smaller
323
+ * surface cuts the per-frame paint work and the cached blit size.
324
+ *
325
+ * A strategy applies wherever frames decode through this seam: the live scrub
326
+ * surface and the analysis / extraction path alike. AnalysisSession decodes at
327
+ * whatever strategy it is given (native when omitted), so a thumbnail pass can
328
+ * cache a small blit instead of a native-size one.
329
+ */
330
+ /** Decode at native resolution. The conservative default. */
331
+ function nativeResolution() {
332
+ return { kind: "native" };
333
+ }
334
+ /**
335
+ * Decode at the on-screen size: canvas CSS width times devicePixelRatio. Best
336
+ * for fixed-size surfaces fed by a much larger source (frame sampler, inline
337
+ * players). Falls back to native while the box is unmeasured.
338
+ *
339
+ * The box it reads is the one bindCanvas measures, so this strategy resolves to
340
+ * native under presentation "frames", where the engine holds no canvas and
341
+ * nothing ever measures one. A frames-mode consumer passes displayBoxResolution
342
+ * instead.
343
+ */
344
+ function viewportResolution(options = {}) {
345
+ return {
346
+ kind: "viewport",
347
+ maxDevicePixelRatio: options.maxDevicePixelRatio ?? 2,
348
+ };
349
+ }
350
+ /**
351
+ * Decode at no more than maxWidth regardless of display size. For consumers
352
+ * with a hard throughput or memory ceiling (batch surfaces, thumbnail farms).
353
+ */
354
+ function cappedResolution(maxWidth) {
355
+ return { kind: "capped", maxWidth };
356
+ }
357
+ /**
358
+ * Decode at the size the frame will actually occupy inside a box the consumer
359
+ * describes: native aspect fitted into boxWidth x boxHeight, times the clamped
360
+ * device pixel ratio. Letterboxing is why the box is two numbers and not one,
361
+ * since a portrait source in a landscape box paints far narrower than the box.
362
+ *
363
+ * This is the strategy for presentation "frames". There the consumer owns the
364
+ * compositor, so it is the only side that knows the box, and viewportResolution
365
+ * has nothing to read.
366
+ */
367
+ function displayBoxResolution(options) {
368
+ return {
369
+ kind: "displayBox",
370
+ boxWidth: options.boxWidth,
371
+ boxHeight: options.boxHeight,
372
+ devicePixelRatio: options.devicePixelRatio,
373
+ maxDevicePixelRatio: options.maxDevicePixelRatio ?? 2,
374
+ };
375
+ }
376
+ /** Runs the descriptor against a context to produce a target decode width. */
377
+ function strategyWidth(strategy, ctx) {
378
+ switch (strategy.kind) {
379
+ case "native":
380
+ return ctx.nativeWidth;
381
+ case "viewport": {
382
+ if (ctx.displayWidth === null || ctx.displayWidth <= 0)
383
+ return ctx.nativeWidth;
384
+ const dpr = clampDpr(ctx.devicePixelRatio, strategy.maxDevicePixelRatio);
385
+ return Math.ceil(ctx.displayWidth * dpr);
386
+ }
387
+ case "capped":
388
+ return Math.min(strategy.maxWidth, ctx.nativeWidth);
389
+ case "displayBox": {
390
+ const { boxWidth, boxHeight } = strategy;
391
+ if (!(boxWidth > 0) || !(boxHeight > 0))
392
+ return ctx.nativeWidth;
393
+ const fit = Math.min(boxWidth / ctx.nativeWidth, boxHeight / ctx.nativeHeight);
394
+ const dpr = clampDpr(strategy.devicePixelRatio, strategy.maxDevicePixelRatio);
395
+ return Math.ceil(ctx.nativeWidth * fit * dpr);
396
+ }
397
+ }
398
+ }
399
+ /**
400
+ * Runs a strategy and returns native-bounded, aspect-preserving integer
401
+ * dimensions safe to hand to CanvasSink and to size a canvas backing store.
402
+ * A strategy that returns garbage (non-finite, non-positive) falls back to
403
+ * native rather than producing a broken surface.
404
+ */
405
+ function resolveDecodeDimensions(strategy, ctx) {
406
+ const nativeWidth = Math.max(1, Math.round(ctx.nativeWidth));
407
+ const nativeHeight = Math.max(1, Math.round(ctx.nativeHeight));
408
+ const raw = strategyWidth(strategy, ctx);
409
+ const width = Number.isFinite(raw) && raw > 0
410
+ ? Math.min(Math.round(raw), nativeWidth)
411
+ : nativeWidth;
412
+ const height = Math.max(1, Math.round((width * nativeHeight) / nativeWidth));
413
+ return { width, height };
414
+ }
415
+ function clampDpr(dpr, maxDpr) {
416
+ if (!Number.isFinite(dpr) || dpr <= 0)
417
+ return 1;
418
+ return Math.min(dpr, maxDpr);
419
+ }
420
+
421
+ /**
422
+ * One entry per presentation instant.
423
+ *
424
+ * A container may carry several coded pictures on one timestamp, and they
425
+ * occupy no time between them: a decode for that instant answers with a single
426
+ * picture, so the rest are frames no reader can reach or name. Counting them
427
+ * would leave a step across the group moving the picture nowhere, and a search
428
+ * for a time inside it answering with the group's last entry whatever it was
429
+ * asked.
430
+ */
431
+ function oneEntryPerInstant(data) {
432
+ const { ticks } = data;
433
+ let at = 1;
434
+ while (at < ticks.length && ticks[at] !== ticks[at - 1])
435
+ at += 1;
436
+ if (at === ticks.length)
437
+ return data;
438
+ const distinct = new Float64Array(ticks.length);
439
+ const distinctSource = data.sourceTicks
440
+ ? new Float64Array(ticks.length)
441
+ : undefined;
442
+ distinct.set(ticks.subarray(0, at));
443
+ if (distinctSource)
444
+ distinctSource.set(data.sourceTicks.subarray(0, at));
445
+ let size = at;
446
+ for (let i = at + 1; i < ticks.length; i += 1) {
447
+ if (ticks[i] === distinct[size - 1])
448
+ continue;
449
+ distinct[size] = ticks[i];
450
+ if (distinctSource)
451
+ distinctSource[size] = data.sourceTicks[i];
452
+ size += 1;
453
+ }
454
+ return {
455
+ ...data,
456
+ ticks: distinct.slice(0, size),
457
+ ...(distinctSource ? { sourceTicks: distinctSource.slice(0, size) } : {}),
458
+ };
459
+ }
460
+ function normalizePresentationOrigin(data) {
461
+ if (data.sourceTicks !== undefined || !(data.ticks[0] < 0))
462
+ return data;
463
+ let firstVisible = 0;
464
+ while (firstVisible < data.ticks.length) {
465
+ const end = firstVisible + 1 < data.ticks.length
466
+ ? data.ticks[firstVisible + 1]
467
+ : data.ticks[firstVisible] + data.lastDurationTicks;
468
+ if (end > 0)
469
+ break;
470
+ firstVisible += 1;
471
+ }
472
+ if (firstVisible === data.ticks.length) {
473
+ throw new RangeError("FrameTimeline: a track containing only pre-roll has no presentation timeline");
474
+ }
475
+ const sourceTicks = data.ticks.slice(firstVisible);
476
+ const ticks = sourceTicks.slice();
477
+ if (ticks[0] < 0)
478
+ ticks[0] = 0;
479
+ const lastDurationTicks = ticks.length === 1 && sourceTicks[0] < 0
480
+ ? data.lastDurationTicks + sourceTicks[0]
481
+ : data.lastDurationTicks;
482
+ return {
483
+ ...data,
484
+ lastDurationTicks,
485
+ sourceTicks,
486
+ ticks,
487
+ };
488
+ }
489
+ /**
490
+ * Every real frame of one track, in presentation order, by its container tick
491
+ * timestamp.
492
+ *
493
+ * A container states its timestamps as integer multiples of 1/tickRate, so a
494
+ * frame's tick count is exact and comparisons between two readers of the same
495
+ * container are integer comparisons. `timeAt` is the same division of the same
496
+ * two integers the demuxer itself performs, which is what lets a consumer
497
+ * holding only seconds match a producer's frame with `===`.
498
+ */
499
+ class FrameTimeline {
500
+ data;
501
+ ticksArray;
502
+ constructor(data) {
503
+ this.data = data;
504
+ this.ticksArray = data.ticks;
505
+ }
506
+ static from(data) {
507
+ if (data.ticks.length === 0) {
508
+ throw new RangeError("FrameTimeline: a track with no frames has no timeline");
509
+ }
510
+ if (!(data.tickRate > 0)) {
511
+ throw new RangeError(`FrameTimeline: tick rate ${data.tickRate} is not positive`);
512
+ }
513
+ return new FrameTimeline(oneEntryPerInstant(normalizePresentationOrigin(data)));
514
+ }
515
+ /** A synthetic constant-rate table. Tests and fakes only. The default tick
516
+ * rate is a whole multiple of `fps`, so no test table needs rounding. */
517
+ static uniform(fps, frameCount, tickRate = fps * 1000) {
518
+ const step = tickRate / fps;
519
+ const ticks = Float64Array.from({ length: frameCount }, (_, index) => index * step);
520
+ return FrameTimeline.from({ lastDurationTicks: step, tickRate, ticks });
521
+ }
522
+ get tickRate() {
523
+ return this.data.tickRate;
524
+ }
525
+ get frameCount() {
526
+ return this.ticksArray.length;
527
+ }
528
+ toData() {
529
+ return this.data;
530
+ }
531
+ ticksAt(index) {
532
+ return this.ticksArray[this.clampIndex(index)];
533
+ }
534
+ timeAt(index) {
535
+ return asSec(this.ticksAt(index) / this.data.tickRate);
536
+ }
537
+ /** Timestamp passed to the decoder for the named presentation frame. */
538
+ sourceTimeAt(index) {
539
+ const at = this.clampIndex(index);
540
+ return asSec((this.data.sourceTicks?.[at] ?? this.ticksArray[at]) / this.data.tickRate);
541
+ }
542
+ /** Maps a public presentation time onto the container's source clock. */
543
+ toSourceTime(timeS) {
544
+ if (this.data.sourceTicks && timeS <= this.timeAt(0)) {
545
+ return this.sourceTimeAt(0);
546
+ }
547
+ return asSec(timeS);
548
+ }
549
+ /** Maps a decoded/container timestamp onto the public presentation clock. */
550
+ fromSourceTime(timeS) {
551
+ return asSec(this.data.sourceTicks ? Math.max(0, timeS) : timeS);
552
+ }
553
+ idAt(index) {
554
+ const at = this.clampIndex(index);
555
+ return { index: at, ticks: this.ticksArray[at] };
556
+ }
557
+ landingAt(index) {
558
+ const at = this.clampIndex(index);
559
+ return { frame: this.idAt(at), mediaTimeS: this.timeAt(at) };
560
+ }
561
+ /** Ticks the frame at `index` ends at, which is where the next one starts. */
562
+ endTicksAt(index) {
563
+ const at = this.clampIndex(index);
564
+ return at + 1 < this.ticksArray.length
565
+ ? this.ticksArray[at + 1]
566
+ : this.ticksArray[at] + this.data.lastDurationTicks;
567
+ }
568
+ /**
569
+ * The frame covering `timeS`, which is what a decode for it returns.
570
+ *
571
+ * The comparison divides, as `timeAt` does, so a frame's own published
572
+ * second answers with that frame.
573
+ */
574
+ indexAtOrBefore(timeS) {
575
+ let low = 0;
576
+ let high = this.ticksArray.length - 1;
577
+ let found = 0;
578
+ while (low <= high) {
579
+ const mid = (low + high) >> 1;
580
+ if (this.ticksArray[mid] / this.data.tickRate <= timeS) {
581
+ found = mid;
582
+ low = mid + 1;
583
+ }
584
+ else {
585
+ high = mid - 1;
586
+ }
587
+ }
588
+ return found;
589
+ }
590
+ /**
591
+ * The frame a decoded sample is.
592
+ *
593
+ * A decoded timestamp has been through the WebCodecs microsecond plane, so
594
+ * it can miss its own tick by up to half a microsecond. Snapping to the
595
+ * nearer neighbour absorbs that by four orders of magnitude, since the
596
+ * narrowest real frame gap measured across the fixtures is 31667 us.
597
+ */
598
+ indexOfDecoded(timeS) {
599
+ const wanted = Math.round(timeS * this.data.tickRate);
600
+ const at = this.indexAtOrBefore(wanted / this.data.tickRate);
601
+ const next = at + 1;
602
+ if (next >= this.ticksArray.length)
603
+ return at;
604
+ return wanted - this.ticksArray[at] <= this.ticksArray[next] - wanted
605
+ ? at
606
+ : next;
607
+ }
608
+ clampIndex(index) {
609
+ if (index < 0)
610
+ return 0;
611
+ const last = this.ticksArray.length - 1;
612
+ return index > last ? last : index;
613
+ }
614
+ }
615
+
616
+ export { DIAGNOSTICS as D, FRAME_TIMELINE as F, HANG_RECOVERY as H, PlaybackStatus as P, SCRUB as S, TRACE_RING_BOUNDS as T, WebVideoEngineError as W, WebVideoEngineErrorCode as a, asSec as b, SourceKind as c, FrameTimeline as d, asPaintSeq as e, canvasBindingRefused as f, resolvePlaybackRate as g, PLAYBACK_RATE as h, asFps as i, cappedResolution as j, displayBoxResolution as k, nativeResolution as n, resolveDecodeDimensions as r, viewportResolution as v };