supervision 0.2.5 → 0.2.7

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 (197) hide show
  1. package/dist/depth-preview-probe-DG7McQEN.js +314 -0
  2. package/dist/depth-preview-probe-DG7McQEN.js.map +1 -0
  3. package/dist/depth-preview-track-BvgwNEE9.js +612 -0
  4. package/dist/depth-preview-track-BvgwNEE9.js.map +1 -0
  5. package/dist/detections/chunked-detection-frame-source.d.ts.map +1 -1
  6. package/dist/editing.d.ts +1 -1
  7. package/dist/editing.d.ts.map +1 -1
  8. package/dist/index.d.ts +4 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +9845 -4759
  11. package/dist/index.js.map +1 -1
  12. package/dist/mask-preparation.worker.js +788 -25
  13. package/dist/mask-preparation.worker.js.map +1 -1
  14. package/dist/media/decoder-deadline.d.ts +18 -0
  15. package/dist/media/decoder-deadline.d.ts.map +1 -0
  16. package/dist/media/depth-preview-probe.d.ts +65 -0
  17. package/dist/media/depth-preview-probe.d.ts.map +1 -0
  18. package/dist/media/depth-preview-track.d.ts +96 -0
  19. package/dist/media/depth-preview-track.d.ts.map +1 -0
  20. package/dist/media/media-normalization.d.ts.map +1 -1
  21. package/dist/media/media-source.d.ts +7 -0
  22. package/dist/media/media-source.d.ts.map +1 -1
  23. package/dist/media/mediabunny-frame-clock.d.ts +7 -0
  24. package/dist/media/mediabunny-frame-clock.d.ts.map +1 -0
  25. package/dist/media/mediabunny-media-source.d.ts +3 -0
  26. package/dist/media/mediabunny-media-source.d.ts.map +1 -1
  27. package/dist/media/track-frame-index.d.ts +14 -0
  28. package/dist/media/track-frame-index.d.ts.map +1 -0
  29. package/dist/media/video-engine-media-source.d.ts.map +1 -1
  30. package/dist/media/video-engine-media-source.js +1 -1
  31. package/dist/render-preparation/depth/clip-preview.d.ts +41 -0
  32. package/dist/render-preparation/depth/clip-preview.d.ts.map +1 -0
  33. package/dist/render-preparation/depth/clip-timing.d.ts +18 -0
  34. package/dist/render-preparation/depth/clip-timing.d.ts.map +1 -0
  35. package/dist/render-preparation/depth/clip.d.ts +14 -0
  36. package/dist/render-preparation/depth/clip.d.ts.map +1 -0
  37. package/dist/render-preparation/depth/exact-at-rest.d.ts +44 -0
  38. package/dist/render-preparation/depth/exact-at-rest.d.ts.map +1 -0
  39. package/dist/render-preparation/depth/exact-playback.d.ts +40 -0
  40. package/dist/render-preparation/depth/exact-playback.d.ts.map +1 -0
  41. package/dist/render-preparation/depth/files.d.ts +55 -0
  42. package/dist/render-preparation/depth/files.d.ts.map +1 -0
  43. package/dist/render-preparation/depth/frame-decode.d.ts +20 -0
  44. package/dist/render-preparation/depth/frame-decode.d.ts.map +1 -0
  45. package/dist/render-preparation/depth/frame-preparer.d.ts +51 -0
  46. package/dist/render-preparation/depth/frame-preparer.d.ts.map +1 -0
  47. package/dist/render-preparation/depth/frame-window.d.ts +146 -0
  48. package/dist/render-preparation/depth/frame-window.d.ts.map +1 -0
  49. package/dist/render-preparation/depth/options.d.ts +37 -0
  50. package/dist/render-preparation/depth/options.d.ts.map +1 -0
  51. package/dist/render-preparation/depth/png16.d.ts +40 -0
  52. package/dist/render-preparation/depth/png16.d.ts.map +1 -0
  53. package/dist/render-preparation/depth/preview-luma-copier.d.ts +12 -0
  54. package/dist/render-preparation/depth/preview-luma-copier.d.ts.map +1 -0
  55. package/dist/render-preparation/depth/preview-luma.d.ts +48 -0
  56. package/dist/render-preparation/depth/preview-luma.d.ts.map +1 -0
  57. package/dist/render-preparation/depth/source.d.ts +123 -0
  58. package/dist/render-preparation/depth/source.d.ts.map +1 -0
  59. package/dist/render-preparation/depth/worker-protocol.d.ts +76 -0
  60. package/dist/render-preparation/depth/worker-protocol.d.ts.map +1 -0
  61. package/dist/render-preparation/mask-frame-artifact.d.ts +10 -0
  62. package/dist/render-preparation/mask-frame-artifact.d.ts.map +1 -1
  63. package/dist/render-preparation/mask-frame-compositor.d.ts +2 -1
  64. package/dist/render-preparation/mask-frame-compositor.d.ts.map +1 -1
  65. package/dist/render-preparation/mask-frame-preparer.d.ts.map +1 -1
  66. package/dist/render-preparation/mask-outline-paths.d.ts +3 -0
  67. package/dist/render-preparation/mask-outline-paths.d.ts.map +1 -0
  68. package/dist/render-preparation/mask-preparation-worker-protocol.d.ts +2 -1
  69. package/dist/render-preparation/mask-preparation-worker-protocol.d.ts.map +1 -1
  70. package/dist/render-preparation/playhead-motion.d.ts +67 -0
  71. package/dist/render-preparation/playhead-motion.d.ts.map +1 -0
  72. package/dist/render-preparation/prepared-render-window.d.ts +12 -0
  73. package/dist/render-preparation/prepared-render-window.d.ts.map +1 -1
  74. package/dist/renderers/depth-textures.d.ts +79 -0
  75. package/dist/renderers/depth-textures.d.ts.map +1 -0
  76. package/dist/renderers/detection-render-order.d.ts +4 -0
  77. package/dist/renderers/detection-render-order.d.ts.map +1 -0
  78. package/dist/renderers/id-mask-stroke-coverage.d.ts +3 -0
  79. package/dist/renderers/id-mask-stroke-coverage.d.ts.map +1 -0
  80. package/dist/renderers/media-renderer-core.d.ts.map +1 -1
  81. package/dist/renderers/media-renderer-scene.d.ts +16 -2
  82. package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
  83. package/dist/renderers/pixi-batch-texture-bindings.d.ts +9 -0
  84. package/dist/renderers/pixi-batch-texture-bindings.d.ts.map +1 -0
  85. package/dist/renderers/pixi-box-layer.d.ts +1 -0
  86. package/dist/renderers/pixi-box-layer.d.ts.map +1 -1
  87. package/dist/renderers/pixi-depth-layer.d.ts +76 -0
  88. package/dist/renderers/pixi-depth-layer.d.ts.map +1 -0
  89. package/dist/renderers/pixi-depth-shader.d.ts +76 -0
  90. package/dist/renderers/pixi-depth-shader.d.ts.map +1 -0
  91. package/dist/renderers/pixi-focus-layer.d.ts +1 -0
  92. package/dist/renderers/pixi-focus-layer.d.ts.map +1 -1
  93. package/dist/renderers/pixi-frame-present.d.ts +1 -0
  94. package/dist/renderers/pixi-frame-present.d.ts.map +1 -1
  95. package/dist/renderers/pixi-id-mask-shader.d.ts +1 -0
  96. package/dist/renderers/pixi-id-mask-shader.d.ts.map +1 -1
  97. package/dist/renderers/pixi-interaction-layer.d.ts +1 -1
  98. package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
  99. package/dist/renderers/pixi-interaction-presentation-layer.d.ts +1 -0
  100. package/dist/renderers/pixi-interaction-presentation-layer.d.ts.map +1 -1
  101. package/dist/renderers/pixi-mask-brush-preview.d.ts.map +1 -1
  102. package/dist/renderers/pixi-mask-halo.d.ts +1 -0
  103. package/dist/renderers/pixi-mask-halo.d.ts.map +1 -1
  104. package/dist/renderers/pixi-mask-layer.d.ts +8 -2
  105. package/dist/renderers/pixi-mask-layer.d.ts.map +1 -1
  106. package/dist/renderers/pixi-mask-screen-stroke.d.ts +17 -0
  107. package/dist/renderers/pixi-mask-screen-stroke.d.ts.map +1 -0
  108. package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
  109. package/dist/renderers/pixi-region-coverage-mask.d.ts +1 -0
  110. package/dist/renderers/pixi-region-coverage-mask.d.ts.map +1 -1
  111. package/dist/renderers/pixi-region-layer.d.ts +1 -0
  112. package/dist/renderers/pixi-region-layer.d.ts.map +1 -1
  113. package/dist/renderers/pixi-scene-layer-slot.d.ts +1 -0
  114. package/dist/renderers/pixi-scene-layer-slot.d.ts.map +1 -1
  115. package/dist/renderers/pixi-vector-layer.d.ts +1 -0
  116. package/dist/renderers/pixi-vector-layer.d.ts.map +1 -1
  117. package/dist/renderers/prepared-annotation-window.d.ts +1 -0
  118. package/dist/renderers/prepared-annotation-window.d.ts.map +1 -1
  119. package/dist/renderers/render-preparation-report.d.ts +23 -0
  120. package/dist/renderers/render-preparation-report.d.ts.map +1 -0
  121. package/dist/sessions/media-session-defaults.d.ts.map +1 -1
  122. package/dist/sessions/media-session-state.d.ts.map +1 -1
  123. package/dist/sessions/media-session.d.ts.map +1 -1
  124. package/dist/tracking.worker.js +64 -0
  125. package/dist/tracking.worker.js.map +1 -1
  126. package/dist/types/media-depth.d.ts +55 -0
  127. package/dist/types/media-depth.d.ts.map +1 -0
  128. package/dist/types/media-renderer.d.ts +33 -0
  129. package/dist/types/media-renderer.d.ts.map +1 -1
  130. package/dist/types/media-session.d.ts +21 -0
  131. package/dist/types/media-session.d.ts.map +1 -1
  132. package/dist/types/render-preparation.d.ts +105 -3
  133. package/dist/types/render-preparation.d.ts.map +1 -1
  134. package/dist/{video-engine-media-source-DR-x1UfK.js → video-engine-media-source-DOFsuGS4.js} +35 -29
  135. package/dist/video-engine-media-source-DOFsuGS4.js.map +1 -0
  136. package/dist/web-video-engine/analysis.js +6 -43
  137. package/dist/web-video-engine/cache-budget.d.ts +4 -7
  138. package/dist/web-video-engine/constants.d.ts +3 -4
  139. package/dist/web-video-engine/create-scrub-cursor.d.ts +1 -0
  140. package/dist/web-video-engine/decode-resolution-fynpRIAG.js +109 -0
  141. package/dist/web-video-engine/decode-scheduler.d.ts +1 -0
  142. package/dist/web-video-engine/engine-core.d.ts +4 -2
  143. package/dist/web-video-engine/engine.js +8 -6
  144. package/dist/web-video-engine/engine.worker.js +164 -66
  145. package/dist/web-video-engine/frame-cache.d.ts +9 -0
  146. package/dist/web-video-engine/frame-index.d.ts +1 -0
  147. package/dist/web-video-engine/frame-index.js +97 -0
  148. package/dist/web-video-engine/{frame-timeline-DreEwsRY.js → frame-timeline-CYgzzmpA.js} +1 -109
  149. package/dist/web-video-engine/frame-timeline-reader.d.ts +21 -0
  150. package/dist/web-video-engine/video-engine.d.ts +9 -4
  151. package/node_modules/supervision-js-core/THIRD_PARTY_NOTICES.md +132 -0
  152. package/node_modules/supervision-js-core/dist/detections/buffered-detection-timeline.d.ts.map +1 -1
  153. package/node_modules/supervision-js-core/dist/index.d.ts +8 -3
  154. package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
  155. package/node_modules/supervision-js-core/dist/index.js +1172 -70
  156. package/node_modules/supervision-js-core/dist/index.js.map +1 -1
  157. package/node_modules/supervision-js-core/dist/interactions/annotation-editing-engine.d.ts +2 -2
  158. package/node_modules/supervision-js-core/dist/interactions/annotation-editing-engine.d.ts.map +1 -1
  159. package/node_modules/supervision-js-core/dist/interactions/annotation-handles.d.ts +2 -2
  160. package/node_modules/supervision-js-core/dist/interactions/annotation-handles.d.ts.map +1 -1
  161. package/node_modules/supervision-js-core/dist/interactions/detection-picker.d.ts.map +1 -1
  162. package/node_modules/supervision-js-core/dist/interactions/viewport-controller.d.ts.map +1 -1
  163. package/node_modules/supervision-js-core/dist/styles/annotation-renderer-presentation.d.ts.map +1 -1
  164. package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts.map +1 -1
  165. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts +50 -3
  166. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -1
  167. package/node_modules/supervision-js-core/dist/types/depth-map.d.ts +203 -0
  168. package/node_modules/supervision-js-core/dist/types/depth-map.d.ts.map +1 -0
  169. package/node_modules/supervision-js-core/dist/types/detections.d.ts +2 -1
  170. package/node_modules/supervision-js-core/dist/types/detections.d.ts.map +1 -1
  171. package/node_modules/supervision-js-core/dist/types/editing.d.ts +9 -0
  172. package/node_modules/supervision-js-core/dist/types/editing.d.ts.map +1 -1
  173. package/node_modules/supervision-js-core/dist/types/interaction.d.ts +4 -0
  174. package/node_modules/supervision-js-core/dist/types/interaction.d.ts.map +1 -1
  175. package/node_modules/supervision-js-core/dist/types/mask-style.d.ts +3 -0
  176. package/node_modules/supervision-js-core/dist/types/mask-style.d.ts.map +1 -1
  177. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts +3 -0
  178. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts.map +1 -1
  179. package/node_modules/supervision-js-core/dist/types/viewport.d.ts +3 -0
  180. package/node_modules/supervision-js-core/dist/types/viewport.d.ts.map +1 -1
  181. package/node_modules/supervision-js-core/dist/utils/depth-color-mapping.d.ts +91 -0
  182. package/node_modules/supervision-js-core/dist/utils/depth-color-mapping.d.ts.map +1 -0
  183. package/node_modules/supervision-js-core/dist/utils/depth-colormap-tables.d.ts +30 -0
  184. package/node_modules/supervision-js-core/dist/utils/depth-colormap-tables.d.ts.map +1 -0
  185. package/node_modules/supervision-js-core/dist/utils/depth-colormaps.d.ts +16 -0
  186. package/node_modules/supervision-js-core/dist/utils/depth-colormaps.d.ts.map +1 -0
  187. package/node_modules/supervision-js-core/dist/utils/depth-manifest.d.ts +23 -0
  188. package/node_modules/supervision-js-core/dist/utils/depth-manifest.d.ts.map +1 -0
  189. package/node_modules/supervision-js-core/dist/utils/depth-readout.d.ts +14 -0
  190. package/node_modules/supervision-js-core/dist/utils/depth-readout.d.ts.map +1 -0
  191. package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts +21 -0
  192. package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts.map +1 -1
  193. package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts +3 -7
  194. package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts.map +1 -1
  195. package/node_modules/supervision-js-core/package.json +2 -1
  196. package/package.json +6 -1
  197. package/dist/video-engine-media-source-DR-x1UfK.js.map +0 -1
@@ -217,7 +217,7 @@
217
217
  * session path that is a probe plus a walk from the enclosing sync sample,
218
218
  * measured at 49ms mean / 80ms worst on a 2840x2840 15fps source, flat in how
219
219
  * far the jump travelled. Coverage past the window is worth having, not worth
220
- * having crisp, so it lives in the preview tier at a 79th of the bytes.
220
+ * having crisp, so it lives in the smaller preview tier.
221
221
  *
222
222
  * resolveCacheBudgets turns these into a per-source budget from
223
223
  * navigator.deviceMemory and the decode frame size. A crisp slot costs
@@ -239,9 +239,8 @@
239
239
  * PREVIEW_SLOTS_MAX: the preview slot count is its byte budget over the
240
240
  * downscaled frame size, capped here. No floor pairs with it: raising a starved
241
241
  * count means overspending the byte budget, when the frame width is what a wide
242
- * preview has to give up. At a 79th of a crisp slot the tier carries the
243
- * timeline coverage an 8MP source cannot afford crisply: 163 slots against the
244
- * exact tier's 13, 11s of a 15fps clip.
242
+ * preview has to give up. A 320px square preview costs about a 79th of an
243
+ * 8MP crisp slot, so that source keeps coarse history beyond its exact window.
245
244
  *
246
245
  * SKIP_NEAR_MS: cache lookups that resolve to a frame within this many
247
246
  * milliseconds of what the visible canvas already shows are rejected so the
@@ -356,22 +355,15 @@
356
355
  * scrub prefetch window, so its slot floor survives any frame size and the RAM
357
356
  * band adds speculative slots on top only while a slot stays cheap. An 8MP
358
357
  * source lands on the floor and keeps nothing speculative, which is what holds
359
- * its resident footprint down; coverage past the window comes from the preview
360
- * tier at a 79th of the cost per slot. That tier has no working set to protect,
361
- * so its count is purely its byte budget over the downscaled frame size, which
362
- * gives a square 8MP source enough coarse history without over-allocating for a
363
- * small 16:9 one.
358
+ * its resident footprint down. The preview tier's byte ceiling depends on
359
+ * device memory; FrameCache derives its slot count from the actual raster size.
364
360
  */
365
- function resolveCacheBudgets(decodeWidth, decodeHeight, previewWidth) {
361
+ function resolveCacheBudgets(decodeWidth, decodeHeight) {
366
362
  const gb = deviceMemoryGb();
367
363
  const crispFrameBytes = Math.max(1, decodeWidth * decodeHeight * 4);
368
364
  const exactBudgetBytes = Math.max(clamp$1(Math.round(gb * FRAME_CACHE.EXACT_BUDGET_BYTES_PER_GB), FRAME_CACHE.EXACT_BUDGET_BYTES_MIN, FRAME_CACHE.EXACT_BUDGET_BYTES_MAX), FRAME_CACHE.MIN_EXACT_SLOTS * crispFrameBytes);
369
- const aspect = decodeWidth > 0 && decodeHeight > 0 ? decodeWidth / decodeHeight : 1;
370
- const previewHeight = Math.max(1, Math.round(previewWidth / aspect));
371
- const previewFrameBytes = Math.max(1, previewWidth * previewHeight * 4);
372
365
  const previewBudgetBytes = clamp$1(Math.round(gb * FRAME_CACHE.PREVIEW_BUDGET_BYTES_PER_GB), FRAME_CACHE.PREVIEW_BUDGET_BYTES_MIN, FRAME_CACHE.PREVIEW_BUDGET_BYTES_MAX);
373
- const previewCapacity = Math.min(Math.floor(previewBudgetBytes / previewFrameBytes), FRAME_CACHE.PREVIEW_SLOTS_MAX);
374
- return { exactBudgetBytes, previewCapacity };
366
+ return { exactBudgetBytes, previewBudgetBytes };
375
367
  }
376
368
 
377
369
  /*!
@@ -26973,6 +26965,99 @@
26973
26965
  }
26974
26966
  }
26975
26967
 
26968
+ /**
26969
+ * Walks the track's packets metadata-only and records each one's timestamp in
26970
+ * the container's own integer grain, which is the grain every timestamp of the
26971
+ * track is a whole multiple of.
26972
+ *
26973
+ * Decode order is not presentation order on a B-frame source, so the table is
26974
+ * sorted before it is indexed, and the trailing frame's duration comes from the
26975
+ * packet that ends up last in that order. A WebM block need not state a
26976
+ * duration, which the demuxer reads as zero, so a last frame without one is
26977
+ * given a duration from elsewhere.
26978
+ */
26979
+ async function readFrameTimeline(videoTrack) {
26980
+ return (await readFrameIndex(videoTrack)).timeline;
26981
+ }
26982
+ /**
26983
+ * The same walk, also naming the key frames: the presentation index of every
26984
+ * frame a decode can start at, ascending.
26985
+ */
26986
+ async function readFrameIndex(videoTrack) {
26987
+ const track = videoTrack;
26988
+ const tickRate = typeof track.getTimeResolution === "function"
26989
+ ? await track.getTimeResolution()
26990
+ : (track.timeResolution ?? FRAME_TIMELINE.FALLBACK_TICK_RATE);
26991
+ const sink = new EncodedPacketSink(videoTrack);
26992
+ const ticks = [];
26993
+ const keyTicks = new Set();
26994
+ let lastTicks = -Infinity;
26995
+ let lastDurationTicks = 0;
26996
+ for await (const packet of sink.packets(undefined, undefined, {
26997
+ metadataOnly: true,
26998
+ })) {
26999
+ const at = Math.round(packet.timestamp * tickRate);
27000
+ if (ticks.length >= FRAME_TIMELINE.MAX_FRAMES) {
27001
+ throw new WebVideoEngineError(WebVideoEngineErrorCode.DecodeUnsupported, `openInput: source video track carries more than ${FRAME_TIMELINE.MAX_FRAMES} frames`);
27002
+ }
27003
+ ticks.push(at);
27004
+ if (packet.type === "key")
27005
+ keyTicks.add(at);
27006
+ if (at >= lastTicks) {
27007
+ lastTicks = at;
27008
+ lastDurationTicks = Math.round(packet.duration * tickRate);
27009
+ }
27010
+ }
27011
+ if (ticks.length === 0) {
27012
+ throw new WebVideoEngineError(WebVideoEngineErrorCode.DecodeUnsupported, "openInput: source video track has no frames");
27013
+ }
27014
+ ticks.sort((a, b) => a - b);
27015
+ if (lastDurationTicks === 0) {
27016
+ lastDurationTicks = await unstatedLastDurationTicks(track, ticks, tickRate);
27017
+ }
27018
+ const timeline = FrameTimeline.from({
27019
+ lastDurationTicks,
27020
+ tickRate,
27021
+ ticks: Float64Array.from(ticks),
27022
+ });
27023
+ const { ticks: presented, sourceTicks = presented } = timeline.toData();
27024
+ const keyIndices = [];
27025
+ for (let index = 0; index < sourceTicks.length; index += 1) {
27026
+ if (keyTicks.has(sourceTicks[index]))
27027
+ keyIndices.push(index);
27028
+ }
27029
+ return { keyIndices: Int32Array.from(keyIndices), timeline };
27030
+ }
27031
+ /**
27032
+ * How long the last frame lasts when its packet does not say: until the end
27033
+ * the container states for the track, else as long as the frame before it. A
27034
+ * lone frame with neither keeps a span of no time.
27035
+ */
27036
+ async function unstatedLastDurationTicks(track, ticks, tickRate) {
27037
+ const lastTicks = ticks[ticks.length - 1];
27038
+ const statedEndS = await readStatedEndS(track);
27039
+ if (statedEndS !== null) {
27040
+ const toStatedEnd = Math.round(statedEndS * tickRate) - lastTicks;
27041
+ if (toStatedEnd > 0)
27042
+ return toStatedEnd;
27043
+ }
27044
+ let before = ticks.length - 2;
27045
+ while (before >= 0 && ticks[before] === lastTicks)
27046
+ before -= 1;
27047
+ return before >= 0 ? lastTicks - ticks[before] : 0;
27048
+ }
27049
+ async function readStatedEndS(track) {
27050
+ if (typeof track.getDurationFromMetadata !== "function")
27051
+ return null;
27052
+ try {
27053
+ const endS = await track.getDurationFromMetadata();
27054
+ return typeof endS === "number" && Number.isFinite(endS) ? endS : null;
27055
+ }
27056
+ catch {
27057
+ return null;
27058
+ }
27059
+ }
27060
+
26976
27061
  var ScrubCursorState;
26977
27062
  (function (ScrubCursorState) {
26978
27063
  ScrubCursorState["Idle"] = "idle";
@@ -27414,8 +27499,10 @@
27414
27499
  }
27415
27500
  const displayWidth = videoTrack.displayWidth;
27416
27501
  const displayHeight = videoTrack.displayHeight;
27417
- const durationS = asSec(await readTrackDurationS(videoTrack));
27418
27502
  const timeline = await readFrameTimeline(videoTrack);
27503
+ // The demuxer measures a track to the end of its last packet, which falls
27504
+ // short when that packet states no duration and the timeline found one.
27505
+ const durationS = asSec(Math.max(await readTrackDurationS(videoTrack), timeline.endTicksAt(timeline.frameCount - 1) / timeline.tickRate));
27419
27506
  // Mediabunny ships a measured native fps via computePacketStats:
27420
27507
  // averagePacketRate "for video tracks, equals the average frame rate". We
27421
27508
  // bound the packet sample to a small slice so the load promise doesn't stall
@@ -27470,47 +27557,6 @@
27470
27557
  ? new WebVideoEngineError(WebVideoEngineErrorCode.VideoTrackUnreadable, "openInput: the container opened and the demuxer parsed no track out of it")
27471
27558
  : new WebVideoEngineError(WebVideoEngineErrorCode.NoVideoTrack, "openInput: the container's tracks read and none of them carries video");
27472
27559
  }
27473
- /**
27474
- * Walks the track's packets metadata-only and records each one's timestamp in
27475
- * the container's own integer grain, which is the grain every timestamp of the
27476
- * track is a whole multiple of.
27477
- *
27478
- * Decode order is not presentation order on a B-frame source, so the table is
27479
- * sorted before it is indexed, and the trailing frame's duration comes from the
27480
- * packet that ends up last in that order.
27481
- */
27482
- async function readFrameTimeline(videoTrack) {
27483
- const track = videoTrack;
27484
- const tickRate = typeof track.getTimeResolution === "function"
27485
- ? await track.getTimeResolution()
27486
- : (track.timeResolution ?? FRAME_TIMELINE.FALLBACK_TICK_RATE);
27487
- const sink = new EncodedPacketSink(videoTrack);
27488
- const ticks = [];
27489
- let lastTicks = -Infinity;
27490
- let lastDurationTicks = 0;
27491
- for await (const packet of sink.packets(undefined, undefined, {
27492
- metadataOnly: true,
27493
- })) {
27494
- const at = Math.round(packet.timestamp * tickRate);
27495
- if (ticks.length >= FRAME_TIMELINE.MAX_FRAMES) {
27496
- throw new WebVideoEngineError(WebVideoEngineErrorCode.DecodeUnsupported, `openInput: source video track carries more than ${FRAME_TIMELINE.MAX_FRAMES} frames`);
27497
- }
27498
- ticks.push(at);
27499
- if (at >= lastTicks) {
27500
- lastTicks = at;
27501
- lastDurationTicks = Math.round(packet.duration * tickRate);
27502
- }
27503
- }
27504
- if (ticks.length === 0) {
27505
- throw new WebVideoEngineError(WebVideoEngineErrorCode.DecodeUnsupported, "openInput: source video track has no frames");
27506
- }
27507
- ticks.sort((a, b) => a - b);
27508
- return FrameTimeline.from({
27509
- lastDurationTicks,
27510
- tickRate,
27511
- ticks: Float64Array.from(ticks),
27512
- });
27513
- }
27514
27560
  /**
27515
27561
  * The gate. True only when every condition holds: the 2D path is not pinned, the
27516
27562
  * strategy decodes at native (no downscale), and the realm offers WebGPU's
@@ -28013,7 +28059,10 @@
28013
28059
  const exactWidth = Math.max(1, Math.round(options.exactWidth));
28014
28060
  const exactHeight = Math.max(1, Math.round(options.exactHeight));
28015
28061
  const aspect = exactWidth / exactHeight;
28016
- const previewWidth = Math.max(1, Math.round(options.previewWidth));
28062
+ const configuredPreviewWidth = Math.max(1, Math.round(options.previewWidth));
28063
+ const previewWidth = options.fitPreviewToOutput
28064
+ ? Math.min(configuredPreviewWidth, exactWidth)
28065
+ : configuredPreviewWidth;
28017
28066
  const previewHeight = Math.max(1, Math.round(previewWidth / aspect));
28018
28067
  const frameBytes = exactWidth * exactHeight * BYTES_PER_PIXEL;
28019
28068
  const floor = Math.max(1, Math.floor(options.minExactSlots ?? 1));
@@ -28027,7 +28076,10 @@
28027
28076
  // pretending the floor's frames fit a smaller budget.
28028
28077
  this.exactBudgetBytes = Math.max(Math.max(0, options.exactBudgetBytes), exactCapacity * frameBytes);
28029
28078
  this.exact = new TierStore(exactCapacity, exactWidth, exactHeight, FRAME_IDENTITY_KEY_GRID_MS);
28030
- this.preview = new TierStore(Math.max(0, Math.floor(options.previewCapacity)), previewWidth, previewHeight, PREVIEW_KEY_GRID_MS);
28079
+ this.preview = new TierStore(Math.min(Math.max(0, Math.floor(options.previewCapacity)), options.previewBudgetBytes === undefined
28080
+ ? Infinity
28081
+ : Math.max(0, Math.floor(options.previewBudgetBytes /
28082
+ (previewWidth * previewHeight * BYTES_PER_PIXEL)))), previewWidth, previewHeight, PREVIEW_KEY_GRID_MS);
28031
28083
  }
28032
28084
  resized(width, height) {
28033
28085
  return new FrameCache({
@@ -28076,6 +28128,11 @@
28076
28128
  const coarse = this.preview.get(timestampMs, previewTolMs);
28077
28129
  return coarse ? { ...coarse, tier: FrameTier.Preview } : null;
28078
28130
  }
28131
+ /** Preview-only lookup without hit accounting or LRU promotion. */
28132
+ peekPreview(timestampMs, toleranceMs) {
28133
+ const coarse = this.preview.peek(timestampMs, toleranceMs);
28134
+ return coarse ? { ...coarse, tier: FrameTier.Preview } : null;
28135
+ }
28079
28136
  /**
28080
28137
  * Best cached frame for `timestampMs`, consulting the exact tier then the
28081
28138
  * preview tier: a crisp hit within `exactTolMs`, else a coarse hit within
@@ -28148,6 +28205,10 @@
28148
28205
  bumpExactFrame(frame) {
28149
28206
  this.exact.touchKey(frame.ticks);
28150
28207
  }
28208
+ /** Protects a resident preview during a sweep that fills other targets. */
28209
+ bumpPreview(timestampMs) {
28210
+ this.preview.touch(timestampMs);
28211
+ }
28151
28212
  clear() {
28152
28213
  this.exact.clear();
28153
28214
  this.preview.clear();
@@ -28284,6 +28345,15 @@
28284
28345
  this.bump(best.key);
28285
28346
  return { canvas: entry.canvas, timestampMs: entry.timestampMs };
28286
28347
  }
28348
+ peek(timestampMs, tolMs) {
28349
+ const best = this.nearest(timestampMs, false);
28350
+ if (!best || best.delta > tolMs)
28351
+ return null;
28352
+ const entry = this.entries.get(best.key);
28353
+ return entry
28354
+ ? { canvas: entry.canvas, timestampMs: entry.timestampMs }
28355
+ : null;
28356
+ }
28287
28357
  /** Retrieves exactly one owner-assigned key and promotes it to MRU. */
28288
28358
  getByKey(key) {
28289
28359
  const entry = this.entries.get(key);
@@ -28692,6 +28762,8 @@
28692
28762
  /** Keyframes one idle sweep decodes; a short-GOP source offers far more inside
28693
28763
  * the span than a background walk should hold the decoder for. */
28694
28764
  const PREVIEW_SWEEP_MAX_FRAMES = 8;
28765
+ /** A provider may round or truncate timestamps onto WebCodecs' microseconds. */
28766
+ const DECODE_TIMESTAMP_ROUNDING_MS = 0.001;
28695
28767
  /** Spends the sweep budget nearest the playhead, then returns to ascending
28696
28768
  * order: the decoder flushes and re-walks the GOP on a backward step, so the
28697
28769
  * batch it receives has to climb even though the budget was spent outward. */
@@ -29082,7 +29154,7 @@
29082
29154
  return null;
29083
29155
  const pos = this.currentPositionS();
29084
29156
  const targetsS = this.mode === AccessMode.Idle
29085
- ? selectPreviewTargets(this.keyframeIndex.known.filter((k) => Math.abs(k - pos) <= PREVIEW_SWEEP_SPAN_S), pos)
29157
+ ? selectPreviewTargets(this.keyframeIndex.known.filter((k) => Math.abs(k - pos) <= PREVIEW_SWEEP_SPAN_S), pos).filter((targetS) => !this.residentPreviewAt(targetS))
29086
29158
  : this.windowTimestamps(pos);
29087
29159
  if (!targetsS.length)
29088
29160
  return null;
@@ -29765,7 +29837,24 @@
29765
29837
  gen !== this.prefetchGen ||
29766
29838
  this.closed)
29767
29839
  return;
29768
- await this.decodeInto(selectPreviewTargets(keyframes, aroundS), gen, true);
29840
+ const targets = selectPreviewTargets(keyframes, aroundS).filter((targetS) => {
29841
+ const resident = this.residentPreviewAt(targetS);
29842
+ if (!resident)
29843
+ return true;
29844
+ this.cache.bumpPreview(resident.timestampMs);
29845
+ return false;
29846
+ });
29847
+ await this.decodeInto(targets, gen, true);
29848
+ }
29849
+ residentPreviewAt(timestampS) {
29850
+ const timestampMs = timestampS * 1000;
29851
+ const preview = this.cache.peekPreview(timestampMs, DECODE_TIMESTAMP_ROUNDING_MS +
29852
+ Number.EPSILON * Math.max(1, Math.abs(timestampMs)));
29853
+ return preview &&
29854
+ this.frameIdOf(preview.timestampMs / 1000).ticks ===
29855
+ this.frameAtTime(timestampS).ticks
29856
+ ? preview
29857
+ : null;
29769
29858
  }
29770
29859
  async decodeInto(timestamps, gen, preview) {
29771
29860
  if (!timestamps.length || gen !== this.prefetchGen || this.closed)
@@ -30094,13 +30183,17 @@
30094
30183
  const bucketMs = fps && fps > 0
30095
30184
  ? Math.max(1, Math.round(1000 / fps))
30096
30185
  : FRAME_CACHE.DEFAULT_BUCKET_MS;
30097
- const budgets = resolveCacheBudgets(track.decodeWidth, track.decodeHeight, config.previewWidth);
30186
+ const budgets = resolveCacheBudgets(track.decodeWidth, track.decodeHeight);
30098
30187
  return new FrameCache({
30099
30188
  exactWidth: track.decodeWidth,
30100
30189
  exactHeight: track.decodeHeight,
30101
30190
  previewWidth: config.previewWidth,
30191
+ fitPreviewToOutput: config.fitPreviewToOutput,
30102
30192
  exactBudgetBytes: config.exactBudgetBytes ?? budgets.exactBudgetBytes,
30103
- previewCapacity: config.previewCapacity ?? budgets.previewCapacity,
30193
+ previewCapacity: config.previewCapacity ?? FRAME_CACHE.PREVIEW_SLOTS_MAX,
30194
+ previewBudgetBytes: config.previewCapacity === undefined
30195
+ ? budgets.previewBudgetBytes
30196
+ : undefined,
30104
30197
  bucketMs,
30105
30198
  // Floor the exact tier to one full prefetch window so a huge-frame source
30106
30199
  // does not evict the very neighbors a settle just decoded.
@@ -32752,6 +32845,8 @@ ${VERTEX_STAGE}
32752
32845
  ? null
32753
32846
  : {
32754
32847
  previewWidth: config.previewWidth ?? FRAME_CACHE.PREVIEW_WIDTH_PX,
32848
+ fitPreviewToOutput: config.previewWidth === undefined &&
32849
+ this.decodeStrategy.kind === "displayBox",
32755
32850
  // Budgets omitted: resolveCacheBudgets sizes both tiers
32756
32851
  // from device memory and frame size. previewCapacity is
32757
32852
  // still honored as an explicit caller override.
@@ -33482,8 +33577,10 @@ ${VERTEX_STAGE}
33482
33577
  *
33483
33578
  * An exact walk answers at or before the frame it aimed at, and no further
33484
33579
  * back than the sample next to it, so the target falls inside the answering
33485
- * frame's own span. That span is the whole test: a frame decoded for some
33486
- * earlier position sits outside it however recently it was painted.
33580
+ * frame's own span. That span is the test: a frame decoded for some earlier
33581
+ * position sits outside it however recently it was painted. The aimed-at
33582
+ * frame answers without it, because a track's last frame has an empty span
33583
+ * when its file states no duration for it.
33487
33584
  */
33488
33585
  answersAwaitedSeek(painted, isKeyFrame) {
33489
33586
  const awaited = this.awaitedSeek;
@@ -33493,7 +33590,8 @@ ${VERTEX_STAGE}
33493
33590
  return false;
33494
33591
  if (awaited.keyOnly)
33495
33592
  return isKeyFrame;
33496
- return awaited.target.ticks < this.timeline().endTicksAt(painted.index);
33593
+ return (painted.ticks === awaited.target.ticks ||
33594
+ awaited.target.ticks < this.timeline().endTicksAt(painted.index));
33497
33595
  }
33498
33596
  /** The frame table of the loaded source. Reached only from paths that have
33499
33597
  * already established a cursor, which is what carries it. */
@@ -75,10 +75,14 @@ export interface FrameCacheOptions {
75
75
  readonly exactHeight: number;
76
76
  /** Coarse-tier width; height derives from the exact aspect ratio. */
77
77
  readonly previewWidth: number;
78
+ /** Limit the configured coarse width to the current decode width. */
79
+ readonly fitPreviewToOutput?: boolean;
78
80
  /** Crisp scrub-tier RAM ceiling in bytes; slot count derives from frame size. */
79
81
  readonly exactBudgetBytes: number;
80
82
  /** Coarse-tier slot count. */
81
83
  readonly previewCapacity: number;
84
+ /** Coarse-tier byte ceiling, additionally limiting its slot count. */
85
+ readonly previewBudgetBytes?: number;
82
86
  /** Source frame interval in ms, reported to diagnostics as the timeline mark
83
87
  * width. It keys nothing. */
84
88
  readonly bucketMs: number;
@@ -116,6 +120,8 @@ export declare class FrameCache {
116
120
  getForFrame(frame: FrameId, timestampMs: number, previewTolMs: number): CachedFrame | null;
117
121
  /** Non-accounting form of {@link getForFrame}. */
118
122
  peekForFrame(frame: FrameId, timestampMs: number, previewTolMs: number): CachedFrame | null;
123
+ /** Preview-only lookup without hit accounting or LRU promotion. */
124
+ peekPreview(timestampMs: number, toleranceMs: number): CachedFrame | null;
119
125
  /**
120
126
  * Best cached frame for `timestampMs`, consulting the exact tier then the
121
127
  * preview tier: a crisp hit within `exactTolMs`, else a coarse hit within
@@ -145,6 +151,8 @@ export declare class FrameCache {
145
151
  bumpExact(timestampMs: number): void;
146
152
  /** Promotes one exact frame by identity. */
147
153
  bumpExactFrame(frame: FrameId): void;
154
+ /** Protects a resident preview during a sweep that fills other targets. */
155
+ bumpPreview(timestampMs: number): void;
148
156
  clear(): void;
149
157
  get stats(): FrameCacheStats;
150
158
  }
@@ -201,6 +209,7 @@ export declare class TierStore {
201
209
  * alternate.
202
210
  */
203
211
  get(timestampMs: number, tolMs: number, atOrBefore?: boolean): TierHit | null;
212
+ peek(timestampMs: number, tolMs: number): TierHit | null;
204
213
  /** Retrieves exactly one owner-assigned key and promotes it to MRU. */
205
214
  getByKey(key: number): TierHit | null;
206
215
  /** Promotes the entry nearest `timestampMs` to most-recently-used. Scans
@@ -0,0 +1 @@
1
+ export { readFrameIndex, readFrameTimeline } from "./frame-timeline-reader";
@@ -0,0 +1,97 @@
1
+ import { EncodedPacketSink } from 'mediabunny';
2
+ import { h as FRAME_TIMELINE, W as WebVideoEngineError, b as WebVideoEngineErrorCode, F as FrameTimeline } from './frame-timeline-CYgzzmpA.js';
3
+
4
+ /**
5
+ * Walks the track's packets metadata-only and records each one's timestamp in
6
+ * the container's own integer grain, which is the grain every timestamp of the
7
+ * track is a whole multiple of.
8
+ *
9
+ * Decode order is not presentation order on a B-frame source, so the table is
10
+ * sorted before it is indexed, and the trailing frame's duration comes from the
11
+ * packet that ends up last in that order. A WebM block need not state a
12
+ * duration, which the demuxer reads as zero, so a last frame without one is
13
+ * given a duration from elsewhere.
14
+ */
15
+ async function readFrameTimeline(videoTrack) {
16
+ return (await readFrameIndex(videoTrack)).timeline;
17
+ }
18
+ /**
19
+ * The same walk, also naming the key frames: the presentation index of every
20
+ * frame a decode can start at, ascending.
21
+ */
22
+ async function readFrameIndex(videoTrack) {
23
+ const track = videoTrack;
24
+ const tickRate = typeof track.getTimeResolution === "function"
25
+ ? await track.getTimeResolution()
26
+ : (track.timeResolution ?? FRAME_TIMELINE.FALLBACK_TICK_RATE);
27
+ const sink = new EncodedPacketSink(videoTrack);
28
+ const ticks = [];
29
+ const keyTicks = new Set();
30
+ let lastTicks = -Infinity;
31
+ let lastDurationTicks = 0;
32
+ for await (const packet of sink.packets(undefined, undefined, {
33
+ metadataOnly: true,
34
+ })) {
35
+ const at = Math.round(packet.timestamp * tickRate);
36
+ if (ticks.length >= FRAME_TIMELINE.MAX_FRAMES) {
37
+ throw new WebVideoEngineError(WebVideoEngineErrorCode.DecodeUnsupported, `openInput: source video track carries more than ${FRAME_TIMELINE.MAX_FRAMES} frames`);
38
+ }
39
+ ticks.push(at);
40
+ if (packet.type === "key")
41
+ keyTicks.add(at);
42
+ if (at >= lastTicks) {
43
+ lastTicks = at;
44
+ lastDurationTicks = Math.round(packet.duration * tickRate);
45
+ }
46
+ }
47
+ if (ticks.length === 0) {
48
+ throw new WebVideoEngineError(WebVideoEngineErrorCode.DecodeUnsupported, "openInput: source video track has no frames");
49
+ }
50
+ ticks.sort((a, b) => a - b);
51
+ if (lastDurationTicks === 0) {
52
+ lastDurationTicks = await unstatedLastDurationTicks(track, ticks, tickRate);
53
+ }
54
+ const timeline = FrameTimeline.from({
55
+ lastDurationTicks,
56
+ tickRate,
57
+ ticks: Float64Array.from(ticks),
58
+ });
59
+ const { ticks: presented, sourceTicks = presented } = timeline.toData();
60
+ const keyIndices = [];
61
+ for (let index = 0; index < sourceTicks.length; index += 1) {
62
+ if (keyTicks.has(sourceTicks[index]))
63
+ keyIndices.push(index);
64
+ }
65
+ return { keyIndices: Int32Array.from(keyIndices), timeline };
66
+ }
67
+ /**
68
+ * How long the last frame lasts when its packet does not say: until the end
69
+ * the container states for the track, else as long as the frame before it. A
70
+ * lone frame with neither keeps a span of no time.
71
+ */
72
+ async function unstatedLastDurationTicks(track, ticks, tickRate) {
73
+ const lastTicks = ticks[ticks.length - 1];
74
+ const statedEndS = await readStatedEndS(track);
75
+ if (statedEndS !== null) {
76
+ const toStatedEnd = Math.round(statedEndS * tickRate) - lastTicks;
77
+ if (toStatedEnd > 0)
78
+ return toStatedEnd;
79
+ }
80
+ let before = ticks.length - 2;
81
+ while (before >= 0 && ticks[before] === lastTicks)
82
+ before -= 1;
83
+ return before >= 0 ? lastTicks - ticks[before] : 0;
84
+ }
85
+ async function readStatedEndS(track) {
86
+ if (typeof track.getDurationFromMetadata !== "function")
87
+ return null;
88
+ try {
89
+ const endS = await track.getDurationFromMetadata();
90
+ return typeof endS === "number" && Number.isFinite(endS) ? endS : null;
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
96
+
97
+ export { readFrameIndex, readFrameTimeline };
@@ -310,114 +310,6 @@ function resolvePlaybackRate(rate) {
310
310
  return rate;
311
311
  }
312
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
313
  /**
422
314
  * One entry per presentation instant.
423
315
  *
@@ -613,4 +505,4 @@ class FrameTimeline {
613
505
  }
614
506
  }
615
507
 
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 };
508
+ export { DIAGNOSTICS as D, FrameTimeline as F, HANG_RECOVERY as H, PlaybackStatus as P, SourceKind as S, TRACE_RING_BOUNDS as T, WebVideoEngineError as W, asPaintSeq as a, WebVideoEngineErrorCode as b, canvasBindingRefused as c, PLAYBACK_RATE as d, asFps as e, asSec as f, SCRUB as g, FRAME_TIMELINE as h, resolvePlaybackRate as r };
@@ -0,0 +1,21 @@
1
+ import { FrameTimeline } from "./frame-timeline";
2
+ /**
3
+ * Walks the track's packets metadata-only and records each one's timestamp in
4
+ * the container's own integer grain, which is the grain every timestamp of the
5
+ * track is a whole multiple of.
6
+ *
7
+ * Decode order is not presentation order on a B-frame source, so the table is
8
+ * sorted before it is indexed, and the trailing frame's duration comes from the
9
+ * packet that ends up last in that order. A WebM block need not state a
10
+ * duration, which the demuxer reads as zero, so a last frame without one is
11
+ * given a duration from elsewhere.
12
+ */
13
+ export declare function readFrameTimeline(videoTrack: unknown): Promise<FrameTimeline>;
14
+ /**
15
+ * The same walk, also naming the key frames: the presentation index of every
16
+ * frame a decode can start at, ascending.
17
+ */
18
+ export declare function readFrameIndex(videoTrack: unknown): Promise<{
19
+ readonly timeline: FrameTimeline;
20
+ readonly keyIndices: Int32Array;
21
+ }>;