supervision 0.2.5 → 0.2.6
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.
- package/dist/depth-preview-probe-DG7McQEN.js +314 -0
- package/dist/depth-preview-probe-DG7McQEN.js.map +1 -0
- package/dist/depth-preview-track-BvgwNEE9.js +612 -0
- package/dist/depth-preview-track-BvgwNEE9.js.map +1 -0
- package/dist/detections/chunked-detection-frame-source.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9504 -4719
- package/dist/index.js.map +1 -1
- package/dist/mask-preparation.worker.js +658 -21
- package/dist/mask-preparation.worker.js.map +1 -1
- package/dist/media/decoder-deadline.d.ts +18 -0
- package/dist/media/decoder-deadline.d.ts.map +1 -0
- package/dist/media/depth-preview-probe.d.ts +65 -0
- package/dist/media/depth-preview-probe.d.ts.map +1 -0
- package/dist/media/depth-preview-track.d.ts +96 -0
- package/dist/media/depth-preview-track.d.ts.map +1 -0
- package/dist/media/media-normalization.d.ts.map +1 -1
- package/dist/media/media-source.d.ts +7 -0
- package/dist/media/media-source.d.ts.map +1 -1
- package/dist/media/mediabunny-frame-clock.d.ts +7 -0
- package/dist/media/mediabunny-frame-clock.d.ts.map +1 -0
- package/dist/media/mediabunny-media-source.d.ts +3 -0
- package/dist/media/mediabunny-media-source.d.ts.map +1 -1
- package/dist/media/track-frame-index.d.ts +14 -0
- package/dist/media/track-frame-index.d.ts.map +1 -0
- package/dist/media/video-engine-media-source.d.ts.map +1 -1
- package/dist/media/video-engine-media-source.js +1 -1
- package/dist/render-preparation/depth/clip-preview.d.ts +41 -0
- package/dist/render-preparation/depth/clip-preview.d.ts.map +1 -0
- package/dist/render-preparation/depth/clip-timing.d.ts +18 -0
- package/dist/render-preparation/depth/clip-timing.d.ts.map +1 -0
- package/dist/render-preparation/depth/clip.d.ts +14 -0
- package/dist/render-preparation/depth/clip.d.ts.map +1 -0
- package/dist/render-preparation/depth/exact-at-rest.d.ts +44 -0
- package/dist/render-preparation/depth/exact-at-rest.d.ts.map +1 -0
- package/dist/render-preparation/depth/exact-playback.d.ts +40 -0
- package/dist/render-preparation/depth/exact-playback.d.ts.map +1 -0
- package/dist/render-preparation/depth/files.d.ts +55 -0
- package/dist/render-preparation/depth/files.d.ts.map +1 -0
- package/dist/render-preparation/depth/frame-decode.d.ts +20 -0
- package/dist/render-preparation/depth/frame-decode.d.ts.map +1 -0
- package/dist/render-preparation/depth/frame-preparer.d.ts +51 -0
- package/dist/render-preparation/depth/frame-preparer.d.ts.map +1 -0
- package/dist/render-preparation/depth/frame-window.d.ts +146 -0
- package/dist/render-preparation/depth/frame-window.d.ts.map +1 -0
- package/dist/render-preparation/depth/options.d.ts +37 -0
- package/dist/render-preparation/depth/options.d.ts.map +1 -0
- package/dist/render-preparation/depth/png16.d.ts +40 -0
- package/dist/render-preparation/depth/png16.d.ts.map +1 -0
- package/dist/render-preparation/depth/preview-luma-copier.d.ts +12 -0
- package/dist/render-preparation/depth/preview-luma-copier.d.ts.map +1 -0
- package/dist/render-preparation/depth/preview-luma.d.ts +48 -0
- package/dist/render-preparation/depth/preview-luma.d.ts.map +1 -0
- package/dist/render-preparation/depth/source.d.ts +123 -0
- package/dist/render-preparation/depth/source.d.ts.map +1 -0
- package/dist/render-preparation/depth/worker-protocol.d.ts +76 -0
- package/dist/render-preparation/depth/worker-protocol.d.ts.map +1 -0
- package/dist/render-preparation/playhead-motion.d.ts +67 -0
- package/dist/render-preparation/playhead-motion.d.ts.map +1 -0
- package/dist/render-preparation/prepared-render-window.d.ts +12 -0
- package/dist/render-preparation/prepared-render-window.d.ts.map +1 -1
- package/dist/renderers/depth-textures.d.ts +79 -0
- package/dist/renderers/depth-textures.d.ts.map +1 -0
- package/dist/renderers/id-mask-stroke-coverage.d.ts +3 -0
- package/dist/renderers/id-mask-stroke-coverage.d.ts.map +1 -0
- package/dist/renderers/media-renderer-core.d.ts.map +1 -1
- package/dist/renderers/media-renderer-scene.d.ts +14 -1
- package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
- package/dist/renderers/pixi-batch-texture-bindings.d.ts +9 -0
- package/dist/renderers/pixi-batch-texture-bindings.d.ts.map +1 -0
- package/dist/renderers/pixi-depth-layer.d.ts +76 -0
- package/dist/renderers/pixi-depth-layer.d.ts.map +1 -0
- package/dist/renderers/pixi-depth-shader.d.ts +76 -0
- package/dist/renderers/pixi-depth-shader.d.ts.map +1 -0
- package/dist/renderers/pixi-focus-layer.d.ts +1 -0
- package/dist/renderers/pixi-focus-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-frame-present.d.ts +1 -0
- package/dist/renderers/pixi-frame-present.d.ts.map +1 -1
- package/dist/renderers/pixi-id-mask-shader.d.ts +1 -0
- package/dist/renderers/pixi-id-mask-shader.d.ts.map +1 -1
- package/dist/renderers/pixi-interaction-layer.d.ts +1 -1
- package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-interaction-presentation-layer.d.ts +1 -0
- package/dist/renderers/pixi-interaction-presentation-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-mask-halo.d.ts +1 -0
- package/dist/renderers/pixi-mask-halo.d.ts.map +1 -1
- package/dist/renderers/pixi-mask-layer.d.ts +4 -0
- package/dist/renderers/pixi-mask-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
- package/dist/renderers/pixi-region-coverage-mask.d.ts +1 -0
- package/dist/renderers/pixi-region-coverage-mask.d.ts.map +1 -1
- package/dist/renderers/pixi-region-layer.d.ts +1 -0
- package/dist/renderers/pixi-region-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-scene-layer-slot.d.ts +1 -0
- package/dist/renderers/pixi-scene-layer-slot.d.ts.map +1 -1
- package/dist/renderers/prepared-annotation-window.d.ts +1 -0
- package/dist/renderers/prepared-annotation-window.d.ts.map +1 -1
- package/dist/renderers/render-preparation-report.d.ts +23 -0
- package/dist/renderers/render-preparation-report.d.ts.map +1 -0
- package/dist/sessions/media-session-defaults.d.ts.map +1 -1
- package/dist/sessions/media-session-state.d.ts.map +1 -1
- package/dist/sessions/media-session.d.ts.map +1 -1
- package/dist/tracking.worker.js +64 -0
- package/dist/tracking.worker.js.map +1 -1
- package/dist/types/media-depth.d.ts +55 -0
- package/dist/types/media-depth.d.ts.map +1 -0
- package/dist/types/media-renderer.d.ts +19 -0
- package/dist/types/media-renderer.d.ts.map +1 -1
- package/dist/types/media-session.d.ts +19 -0
- package/dist/types/media-session.d.ts.map +1 -1
- package/dist/types/render-preparation.d.ts +105 -3
- package/dist/types/render-preparation.d.ts.map +1 -1
- package/dist/{video-engine-media-source-DR-x1UfK.js → video-engine-media-source-DOFsuGS4.js} +35 -29
- package/dist/video-engine-media-source-DOFsuGS4.js.map +1 -0
- package/dist/web-video-engine/analysis.js +6 -43
- package/dist/web-video-engine/cache-budget.d.ts +4 -7
- package/dist/web-video-engine/constants.d.ts +3 -4
- package/dist/web-video-engine/create-scrub-cursor.d.ts +1 -0
- package/dist/web-video-engine/decode-resolution-fynpRIAG.js +109 -0
- package/dist/web-video-engine/decode-scheduler.d.ts +1 -0
- package/dist/web-video-engine/engine-core.d.ts +4 -2
- package/dist/web-video-engine/engine.js +8 -6
- package/dist/web-video-engine/engine.worker.js +164 -66
- package/dist/web-video-engine/frame-cache.d.ts +9 -0
- package/dist/web-video-engine/frame-index.d.ts +1 -0
- package/dist/web-video-engine/frame-index.js +97 -0
- package/dist/web-video-engine/{frame-timeline-DreEwsRY.js → frame-timeline-CYgzzmpA.js} +1 -109
- package/dist/web-video-engine/frame-timeline-reader.d.ts +21 -0
- package/dist/web-video-engine/video-engine.d.ts +9 -4
- package/node_modules/supervision-js-core/THIRD_PARTY_NOTICES.md +132 -0
- package/node_modules/supervision-js-core/dist/detections/buffered-detection-timeline.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.d.ts +7 -2
- package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.js +1128 -59
- package/node_modules/supervision-js-core/dist/index.js.map +1 -1
- package/node_modules/supervision-js-core/dist/interactions/annotation-editing-engine.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/interactions/annotation-handles.d.ts +2 -2
- package/node_modules/supervision-js-core/dist/interactions/annotation-handles.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-presentation.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts +50 -3
- package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/depth-map.d.ts +203 -0
- package/node_modules/supervision-js-core/dist/types/depth-map.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/editing.d.ts +2 -0
- package/node_modules/supervision-js-core/dist/types/editing.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts +3 -0
- package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/utils/depth-color-mapping.d.ts +91 -0
- package/node_modules/supervision-js-core/dist/utils/depth-color-mapping.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/utils/depth-colormap-tables.d.ts +30 -0
- package/node_modules/supervision-js-core/dist/utils/depth-colormap-tables.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/utils/depth-colormaps.d.ts +16 -0
- package/node_modules/supervision-js-core/dist/utils/depth-colormaps.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/utils/depth-manifest.d.ts +23 -0
- package/node_modules/supervision-js-core/dist/utils/depth-manifest.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/utils/depth-readout.d.ts +14 -0
- package/node_modules/supervision-js-core/dist/utils/depth-readout.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts +21 -0
- package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts +3 -7
- package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts.map +1 -1
- package/node_modules/supervision-js-core/package.json +2 -1
- package/package.json +6 -1
- 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
|
|
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.
|
|
243
|
-
*
|
|
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
|
|
360
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
|
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)),
|
|
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
|
-
|
|
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
|
|
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 ??
|
|
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
|
|
33486
|
-
*
|
|
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
|
|
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,
|
|
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
|
+
}>;
|