supervision 0.2.0-next.1 → 0.2.0-next.2
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/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +508 -127
- package/dist/index.js.map +1 -1
- package/dist/mask-preparation.worker.js.map +1 -1
- package/dist/media/media-frame-clock.d.ts +4 -0
- package/dist/media/media-frame-clock.d.ts.map +1 -0
- package/dist/media/media-source.d.ts +6 -0
- package/dist/media/media-source.d.ts.map +1 -1
- 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/prepared-window-timeline.d.ts +1 -0
- package/dist/render-preparation/prepared-window-timeline.d.ts.map +1 -1
- package/dist/renderers/media-frame-navigation.d.ts +17 -0
- package/dist/renderers/media-frame-navigation.d.ts.map +1 -0
- package/dist/renderers/media-renderer-core.d.ts.map +1 -1
- package/dist/renderers/media-renderer-scene.d.ts +1 -0
- package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
- package/dist/renderers/media-renderer-transport.d.ts +4 -1
- package/dist/renderers/media-renderer-transport.d.ts.map +1 -1
- package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
- package/dist/renderers/prepared-annotation-window.d.ts.map +1 -1
- package/dist/renderers/presented-frame-channel.d.ts +8 -6
- package/dist/renderers/presented-frame-channel.d.ts.map +1 -1
- package/dist/sessions/media-session-defaults.d.ts.map +1 -1
- package/dist/sessions/media-session-detections.d.ts.map +1 -1
- package/dist/sessions/media-session.d.ts.map +1 -1
- package/dist/tracking.worker.js.map +1 -1
- package/dist/types/media-frame-clock.d.ts +16 -0
- package/dist/types/media-frame-clock.d.ts.map +1 -0
- package/dist/types/media-frame-navigation.d.ts +28 -0
- package/dist/types/media-frame-navigation.d.ts.map +1 -0
- package/dist/types/media-renderer-display.d.ts +12 -0
- package/dist/types/media-renderer-display.d.ts.map +1 -0
- package/dist/types/media-renderer.d.ts +13 -0
- package/dist/types/media-renderer.d.ts.map +1 -1
- package/dist/types/media-session.d.ts +15 -5
- package/dist/types/media-session.d.ts.map +1 -1
- package/dist/types/render-preparation.d.ts +8 -5
- package/dist/types/render-preparation.d.ts.map +1 -1
- package/dist/{video-engine-media-source-CUXnOJOV.js → video-engine-media-source-DR-x1UfK.js} +35 -2
- package/dist/video-engine-media-source-DR-x1UfK.js.map +1 -0
- package/dist/web-video-engine/analysis.js +16 -0
- package/dist/web-video-engine/canvas-sink-scrub-cursor.d.ts +6 -1
- package/dist/web-video-engine/decode-scheduler.d.ts +8 -2
- package/dist/web-video-engine/decode-session.d.ts +5 -2
- package/dist/web-video-engine/decode-source.d.ts +2 -0
- package/dist/web-video-engine/engine-core.d.ts +9 -0
- package/dist/web-video-engine/engine.js +103 -11
- package/dist/web-video-engine/engine.worker.js +492 -31
- package/dist/web-video-engine/frame-cache.d.ts +2 -0
- package/dist/web-video-engine/pausable-deadline.d.ts +11 -0
- package/dist/web-video-engine/scrub-controller.d.ts +8 -4
- package/dist/web-video-engine/scrub-cursor.d.ts +3 -0
- package/dist/web-video-engine/video-engine.d.ts +12 -1
- package/dist/web-video-engine/worker-protocol.d.ts +9 -1
- package/node_modules/supervision-js-core/dist/detections/buffered-detection-timeline.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.js +77 -41
- package/node_modules/supervision-js-core/dist/index.js.map +1 -1
- package/node_modules/supervision-js-core/dist/types/detection-timeline.d.ts +8 -3
- package/node_modules/supervision-js-core/dist/types/detection-timeline.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/video-engine-media-source-CUXnOJOV.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { copySortedDetectionFrames, detectionFrameOverlapsRange, projectDetectionFrameForTracking, createSortTracker, createOCSortTracker, createCBIoUTracker, createByteTrackTracker, MediaErrorKind, includeDefined, MediaSourceStatus, MediaRendererPlaybackState, MediaRendererFit, createDefaultAnnotationPresentation, createProjectedDetectionFrameSource, createArrayDetectionFrameSource, createBufferedDetectionTimeline, resolveAnnotationRendererPresentation, PlaybackGateReach, createIdleDetectionBufferState, MediaInteractionMode, decodeCompressedRleMask, createIdMaskFrame, encodeBinaryMask, rasterizePolygonToMask, resolveIdMaskPaletteId, decodeCompressedRleCounts, canReuseMaskStyleArtifacts, getBufferedDetectionTimelineFrameSnapshot, MAX_ID_MASK_PALETTE_ENTRIES, StrokeAlignment, BaseBoxStyle, centerRectToTopLeftRect, BoxShape, BaseFocusStyle, extractDetectionMaskRectRuns, AnnotationGestureStateKind, AnnotationHandleKind, getDetectionRect, DetectionPickTarget, createDetectionPickKey, rebaseDetectionPickToFrame, pickDetectionAtPoint, followDetectionPickAcrossFrames, haveSameDetectionPickIdentities, pickAnnotationHandle, getAnnotationHandles, LabelPlacement, BasePolygonStyle, BasePolylineStyle, BaseKeypointStyle, ShapeInstructionKind, sampleEllipseArc, resolveEllipseSegmentCount, KeypointMarkerShape, MarkerSizeSpace, MarkerShape, resolveMarkerGeometry, BaseInteractionStyle, DetectionInteractionState, writeIdMaskPaletteEntry, resolveIdMaskStrokeTexels, MAX_ID_MASK_STROKE_WIDTH, DetectionBufferStatus, resolveMaskStyleOpacity, pickDetectionByMaskId, BoxStrokeAlignment, RegionRendererMediaEffectKind, RegionRendererSourceKind, RegionRendererCoverageKind, RegionRendererSizeSpace, resolveStyleValue, resolveAnnotationStyleState, lightenColor, resolveAnnotationRendererStyleFields, annotationRendererKinds, createViewportController, MediaSessionMode, DetectionFrameRetentionMode, createMemoryColdDetectionFrameStore, createWritableDetectionFrameSource, createCompositeDetectionFrameSource, MediaSessionActivityStatus, MediaSessionActivityKind, MediaSessionStatus, createSourceAwarePresentation, projectDetectionFrames, DetectionFrameSelectionMode } from 'supervision-js-core';
|
|
2
2
|
export { BaseBoxCornerStyle, BaseBoxStyle, BaseFocusStyle, BaseInteractionStyle, BaseKeypointStyle, BaseLabelStyle, BaseMarkerStyle, BaseMaskStyle, BasePolygonStyle, BasePolylineStyle, BoxShape, BoxStrokeAlignment, DEFAULT_DETECTION_CLASS_STYLES, DEFAULT_DETECTION_COLOR_SEQUENCE, DetectionBufferStatus, DetectionFrameRetentionMode, DetectionFrameSelectionMode, DetectionInteractionState, DetectionMaskEncoding, DetectionPickTarget, FocusTargetMode, KeypointMarkerShape, KeypointVisibility, LabelPlacement, LabelVisibilityMode, MarkerShape, MarkerSizeSpace, MaskRenderMode, MediaErrorKind, MediaInteractionMode, MediaRendererFit, MediaRendererPlaybackState, MediaSessionActivityKind, MediaSessionActivityStatus, MediaSessionMode, MediaSessionStatus, MediaSourceStatus, PlaybackGateReach, RegionRendererComposeMode, RegionRendererCoverageKind, RegionRendererMediaEffectKind, RegionRendererRegionKind, RegionRendererSizeSpace, RegionRendererSourceKind, SUPERVISION_ROBOFLOW_COLOR, TrackingGeometry, annotationRendererKinds, annotationRenderers, createArrayDetectionFrameSource, createBufferedDetectionTimeline, createByteTrackTracker, createCBIoUTracker, createColdDetectionFrameSource, createCompositeDetectionFrameSource, createDefaultAnnotationPresentation, createMemoryColdDetectionFrameStore, createOCSortTracker, createProjectedDetectionFrameSource, createSortTracker, createWritableDetectionFrameSource, detectionPostProcessors, normalizeDetectionClassName, pickDetectionAtPoint, projectDetectionFrame, projectDetectionFrameForTracking, projectDetectionFrames, resolveDetectionClassColorStyle } from 'supervision-js-core';
|
|
3
|
-
import { M as MediaSourceError, t as toMediaSourceError, r as resolveDisplayPixelRatio } from './video-engine-media-source-
|
|
4
|
-
export { c as createWebVideoEngineMediaRendererSource, g as getMediaErrorKind, i as isMediaSourceError, o as openWebVideoEngineMediaSource } from './video-engine-media-source-
|
|
3
|
+
import { M as MediaSourceError, t as toMediaSourceError, r as resolveDisplayPixelRatio } from './video-engine-media-source-DR-x1UfK.js';
|
|
4
|
+
export { c as createWebVideoEngineMediaRendererSource, g as getMediaErrorKind, i as isMediaSourceError, o as openWebVideoEngineMediaSource } from './video-engine-media-source-DR-x1UfK.js';
|
|
5
5
|
|
|
6
6
|
const DEFAULT_DATABASE_NAME = "supervision-js-detection-frames";
|
|
7
7
|
const DEFAULT_CHUNK_DURATION_SECONDS = 1;
|
|
@@ -2341,7 +2341,7 @@ function createMediaPlaybackController(options) {
|
|
|
2341
2341
|
((now - playbackOriginNow) / 1000) * playbackRate;
|
|
2342
2342
|
const playableEnd = options.duration === null
|
|
2343
2343
|
? null
|
|
2344
|
-
:
|
|
2344
|
+
: Math.max(options.firstTimestamp, options.duration);
|
|
2345
2345
|
let shouldPresentLoopStartSample = false;
|
|
2346
2346
|
if (playableEnd !== null && requestedMediaTime >= playableEnd) {
|
|
2347
2347
|
if (!options.loop) {
|
|
@@ -2959,6 +2959,178 @@ function createMediaRenderErrorSourcePatch(error) {
|
|
|
2959
2959
|
};
|
|
2960
2960
|
}
|
|
2961
2961
|
|
|
2962
|
+
function createMediaFrameNavigation(options) {
|
|
2963
|
+
let activeOperation = null;
|
|
2964
|
+
let displayed = null;
|
|
2965
|
+
let destroyed = false;
|
|
2966
|
+
let destroyError;
|
|
2967
|
+
const controller = {
|
|
2968
|
+
api: {
|
|
2969
|
+
moveToFrame(index) {
|
|
2970
|
+
return startMove(() => landingAt(index));
|
|
2971
|
+
},
|
|
2972
|
+
moveToTime(mediaTime) {
|
|
2973
|
+
return startMove(() => landingAt(options.clock.indexAtOrBefore(mediaTime)));
|
|
2974
|
+
},
|
|
2975
|
+
scrubToFrame(index) {
|
|
2976
|
+
const target = landingAt(index);
|
|
2977
|
+
return startScrub(target);
|
|
2978
|
+
},
|
|
2979
|
+
scrubToTime(mediaTime) {
|
|
2980
|
+
const target = landingAt(options.clock.indexAtOrBefore(mediaTime));
|
|
2981
|
+
return startScrub(target);
|
|
2982
|
+
},
|
|
2983
|
+
},
|
|
2984
|
+
presented(frame) {
|
|
2985
|
+
displayed = frame;
|
|
2986
|
+
const operation = activeOperation;
|
|
2987
|
+
if (!operation ||
|
|
2988
|
+
frame.index !== operation.target.index ||
|
|
2989
|
+
frame.mediaTime !== operation.target.mediaTime) {
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
if (operation.kind === "scrub") {
|
|
2993
|
+
activeOperation = null;
|
|
2994
|
+
operation.resolve({ frame: operation.target, status: "landed" });
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
operation.presented = true;
|
|
2998
|
+
},
|
|
2999
|
+
cancel() {
|
|
3000
|
+
supersedeActive(createAbortError("Media frame navigation was superseded."));
|
|
3001
|
+
},
|
|
3002
|
+
destroy(error) {
|
|
3003
|
+
if (destroyed) {
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
destroyed = true;
|
|
3007
|
+
destroyError =
|
|
3008
|
+
error ?? createAbortError("Media frame navigation was destroyed.");
|
|
3009
|
+
supersedeActive(destroyError);
|
|
3010
|
+
},
|
|
3011
|
+
};
|
|
3012
|
+
return controller;
|
|
3013
|
+
function landingAt(index) {
|
|
3014
|
+
return {
|
|
3015
|
+
duration: options.clock.durationAt(index),
|
|
3016
|
+
index,
|
|
3017
|
+
mediaTime: options.clock.timeAt(index),
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
function startMove(getTarget) {
|
|
3021
|
+
if (destroyed) {
|
|
3022
|
+
return own(Promise.reject(destroyError));
|
|
3023
|
+
}
|
|
3024
|
+
let target;
|
|
3025
|
+
try {
|
|
3026
|
+
target = getTarget();
|
|
3027
|
+
}
|
|
3028
|
+
catch (error) {
|
|
3029
|
+
return own(Promise.reject(error));
|
|
3030
|
+
}
|
|
3031
|
+
supersedeActive(createAbortError("Media frame navigation was superseded."));
|
|
3032
|
+
let resolve;
|
|
3033
|
+
let reject;
|
|
3034
|
+
const promise = new Promise((resolvePromise, rejectPromise) => {
|
|
3035
|
+
resolve = resolvePromise;
|
|
3036
|
+
reject = rejectPromise;
|
|
3037
|
+
});
|
|
3038
|
+
const operation = {
|
|
3039
|
+
kind: "move",
|
|
3040
|
+
presented: false,
|
|
3041
|
+
reject,
|
|
3042
|
+
resolve,
|
|
3043
|
+
target,
|
|
3044
|
+
};
|
|
3045
|
+
activeOperation = operation;
|
|
3046
|
+
let command;
|
|
3047
|
+
try {
|
|
3048
|
+
command = options.seek(target.mediaTime);
|
|
3049
|
+
}
|
|
3050
|
+
catch (error) {
|
|
3051
|
+
failMove(operation, error);
|
|
3052
|
+
return own(promise);
|
|
3053
|
+
}
|
|
3054
|
+
void Promise.resolve(command).then(() => {
|
|
3055
|
+
if (activeOperation !== operation) {
|
|
3056
|
+
return;
|
|
3057
|
+
}
|
|
3058
|
+
if (!operation.presented && !isDisplayed(operation.target)) {
|
|
3059
|
+
failMove(operation, new Error("Media seek completed without presenting the requested exact frame."));
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
activeOperation = null;
|
|
3063
|
+
operation.resolve(operation.target);
|
|
3064
|
+
}, (error) => {
|
|
3065
|
+
failMove(operation, error);
|
|
3066
|
+
});
|
|
3067
|
+
return own(promise);
|
|
3068
|
+
}
|
|
3069
|
+
function startScrub(target) {
|
|
3070
|
+
if (destroyed) {
|
|
3071
|
+
throw destroyError;
|
|
3072
|
+
}
|
|
3073
|
+
supersedeActive(createAbortError("Media frame navigation was superseded."));
|
|
3074
|
+
let resolve;
|
|
3075
|
+
const settled = new Promise((resolvePromise) => {
|
|
3076
|
+
resolve = resolvePromise;
|
|
3077
|
+
});
|
|
3078
|
+
const operation = {
|
|
3079
|
+
kind: "scrub",
|
|
3080
|
+
resolve,
|
|
3081
|
+
target,
|
|
3082
|
+
};
|
|
3083
|
+
activeOperation = operation;
|
|
3084
|
+
own(settled);
|
|
3085
|
+
try {
|
|
3086
|
+
options.scrub(target.mediaTime);
|
|
3087
|
+
if (activeOperation === operation && isDisplayed(target)) {
|
|
3088
|
+
activeOperation = null;
|
|
3089
|
+
operation.resolve({ frame: target, status: "landed" });
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
catch (error) {
|
|
3093
|
+
if (activeOperation === operation) {
|
|
3094
|
+
activeOperation = null;
|
|
3095
|
+
operation.resolve({ status: "superseded" });
|
|
3096
|
+
}
|
|
3097
|
+
throw error;
|
|
3098
|
+
}
|
|
3099
|
+
return { settled, target };
|
|
3100
|
+
}
|
|
3101
|
+
function isDisplayed(target) {
|
|
3102
|
+
return (displayed?.index === target.index &&
|
|
3103
|
+
displayed.mediaTime === target.mediaTime);
|
|
3104
|
+
}
|
|
3105
|
+
function failMove(operation, error) {
|
|
3106
|
+
if (activeOperation !== operation) {
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
activeOperation = null;
|
|
3110
|
+
operation.reject(error);
|
|
3111
|
+
}
|
|
3112
|
+
function supersedeActive(error) {
|
|
3113
|
+
const operation = activeOperation;
|
|
3114
|
+
if (!operation) {
|
|
3115
|
+
return;
|
|
3116
|
+
}
|
|
3117
|
+
activeOperation = null;
|
|
3118
|
+
if (operation.kind === "move") {
|
|
3119
|
+
operation.reject(error);
|
|
3120
|
+
}
|
|
3121
|
+
else {
|
|
3122
|
+
operation.resolve({ status: "superseded" });
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
function own(promise) {
|
|
3127
|
+
void promise.catch(() => undefined);
|
|
3128
|
+
return promise;
|
|
3129
|
+
}
|
|
3130
|
+
function createAbortError(message) {
|
|
3131
|
+
return new DOMException(message, "AbortError");
|
|
3132
|
+
}
|
|
3133
|
+
|
|
2962
3134
|
const MILLISECONDS_PER_SECOND$2 = 1000;
|
|
2963
3135
|
/**
|
|
2964
3136
|
* The renderer's playback surface over a producer that owns the playhead.
|
|
@@ -2985,6 +3157,7 @@ function createMediaRendererTransport(options) {
|
|
|
2985
3157
|
let readinessFreezeHeld = false;
|
|
2986
3158
|
let activeReadinessWait;
|
|
2987
3159
|
let presentationWait = 0;
|
|
3160
|
+
let displayResize = 0;
|
|
2988
3161
|
let presentationHold = null;
|
|
2989
3162
|
const isHoldingForReadiness = () => readinessHoldIntent === playbackIntent ||
|
|
2990
3163
|
presentationHold?.intent === playbackIntent;
|
|
@@ -3143,6 +3316,10 @@ function createMediaRendererTransport(options) {
|
|
|
3143
3316
|
await options.waitForReadiness(channel.getPlayhead().mediaTimeS, readinessWait.signal);
|
|
3144
3317
|
readinessLanded = true;
|
|
3145
3318
|
}
|
|
3319
|
+
catch (error) {
|
|
3320
|
+
if (intent === playbackIntent)
|
|
3321
|
+
throw error;
|
|
3322
|
+
}
|
|
3146
3323
|
finally {
|
|
3147
3324
|
if (activeReadinessWait === readinessWait) {
|
|
3148
3325
|
activeReadinessWait = undefined;
|
|
@@ -3176,6 +3353,11 @@ function createMediaRendererTransport(options) {
|
|
|
3176
3353
|
// A pause ends the producer's mechanical hold, so it lands ahead of the
|
|
3177
3354
|
// releases a readiness hold or an open gesture still owe.
|
|
3178
3355
|
channel.pause();
|
|
3356
|
+
// A gate may already have mechanically paused the producer, so its
|
|
3357
|
+
// no-op pause emits no state signal to retire Buffering.
|
|
3358
|
+
if (channel.getStatus() === "PAUSED") {
|
|
3359
|
+
publishPlaybackState();
|
|
3360
|
+
}
|
|
3179
3361
|
void releaseReadinessFreeze();
|
|
3180
3362
|
void releaseGesture();
|
|
3181
3363
|
},
|
|
@@ -3211,7 +3393,7 @@ function createMediaRendererTransport(options) {
|
|
|
3211
3393
|
options.invalidatePresentedFrame?.();
|
|
3212
3394
|
const navigationPresentation = ++presentationWait;
|
|
3213
3395
|
presentationHold = null;
|
|
3214
|
-
beginPlaybackIntent();
|
|
3396
|
+
const intent = beginPlaybackIntent();
|
|
3215
3397
|
// Releasing first is what keeps a drag from freezing the picture on
|
|
3216
3398
|
// release: the producer resumes on its own terms, and the landing decode
|
|
3217
3399
|
// for a cold region no longer sits between the pointer coming up and
|
|
@@ -3229,16 +3411,51 @@ function createMediaRendererTransport(options) {
|
|
|
3229
3411
|
landingRelease = null;
|
|
3230
3412
|
}
|
|
3231
3413
|
}
|
|
3414
|
+
if (intent !== playbackIntent)
|
|
3415
|
+
return;
|
|
3232
3416
|
const presentation = options.beginPresentedFrameNavigation?.();
|
|
3233
3417
|
try {
|
|
3418
|
+
const before = channel.getPlayhead().frame;
|
|
3234
3419
|
await channel.commit(mediaTime * MILLISECONDS_PER_SECOND$2);
|
|
3235
|
-
|
|
3420
|
+
if (intent !== playbackIntent) {
|
|
3421
|
+
presentation?.cancel();
|
|
3422
|
+
return;
|
|
3423
|
+
}
|
|
3424
|
+
const after = channel.getPlayhead().frame;
|
|
3425
|
+
await presentation?.waitFor(after, before.index === after.index && before.ticks === after.ticks);
|
|
3236
3426
|
}
|
|
3237
3427
|
catch (error) {
|
|
3238
3428
|
presentation?.cancel();
|
|
3239
3429
|
throw error;
|
|
3240
3430
|
}
|
|
3241
3431
|
},
|
|
3432
|
+
async resizeOutput(resize) {
|
|
3433
|
+
const intent = playbackIntent;
|
|
3434
|
+
const request = ++displayResize;
|
|
3435
|
+
const assertCurrent = () => {
|
|
3436
|
+
if (intent !== playbackIntent || request !== displayResize)
|
|
3437
|
+
throw new DOMException("Display resize was superseded.", "AbortError");
|
|
3438
|
+
};
|
|
3439
|
+
// Geometry does not change the viewer's play/pause or annotation gate.
|
|
3440
|
+
// A replacement enters through the same guard as every other frame.
|
|
3441
|
+
const presentation = options.beginPresentedFrameNavigation?.(true);
|
|
3442
|
+
try {
|
|
3443
|
+
const changed = await resize();
|
|
3444
|
+
assertCurrent();
|
|
3445
|
+
if (changed) {
|
|
3446
|
+
await presentation?.waitFor(channel.getPlayhead().frame);
|
|
3447
|
+
assertCurrent();
|
|
3448
|
+
}
|
|
3449
|
+
else {
|
|
3450
|
+
presentation?.cancel();
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
catch (error) {
|
|
3454
|
+
presentation?.cancel();
|
|
3455
|
+
assertCurrent();
|
|
3456
|
+
throw error;
|
|
3457
|
+
}
|
|
3458
|
+
},
|
|
3242
3459
|
async step(direction) {
|
|
3243
3460
|
options.invalidatePresentedFrame?.();
|
|
3244
3461
|
const navigationPresentation = ++presentationWait;
|
|
@@ -3277,15 +3494,17 @@ function createMediaRendererTransport(options) {
|
|
|
3277
3494
|
const guarded = options.waitForPresentationReadiness(mediaTime, signal);
|
|
3278
3495
|
if (!guarded) {
|
|
3279
3496
|
if (presentationHold) {
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3497
|
+
presentationHold = {
|
|
3498
|
+
intent: playbackIntent,
|
|
3499
|
+
ready: true,
|
|
3500
|
+
wait: ++presentationWait,
|
|
3501
|
+
};
|
|
3283
3502
|
}
|
|
3284
3503
|
return null;
|
|
3285
3504
|
}
|
|
3286
3505
|
const wait = ++presentationWait;
|
|
3287
3506
|
const intent = playbackIntent;
|
|
3288
|
-
presentationHold = { intent, wait };
|
|
3507
|
+
presentationHold = { intent, ready: false, wait };
|
|
3289
3508
|
readinessFreezeHeld = true;
|
|
3290
3509
|
channel.beginInteractiveSeek();
|
|
3291
3510
|
publishPlaybackState();
|
|
@@ -3298,18 +3517,22 @@ function createMediaRendererTransport(options) {
|
|
|
3298
3517
|
// provider's diagnostics own the error while the frame stays visible.
|
|
3299
3518
|
}
|
|
3300
3519
|
finally {
|
|
3301
|
-
if (presentationHold?.wait === wait) {
|
|
3302
|
-
presentationHold = null;
|
|
3303
|
-
}
|
|
3304
|
-
// An aborted frame hands the existing freeze to a newer frame. A
|
|
3305
|
-
// navigation increments presentationWait and releases explicitly.
|
|
3306
3520
|
if (wait === presentationWait && !signal.aborted) {
|
|
3307
|
-
|
|
3308
|
-
publishPlaybackState();
|
|
3521
|
+
presentationHold = { intent, ready: true, wait };
|
|
3309
3522
|
}
|
|
3310
3523
|
}
|
|
3311
3524
|
})();
|
|
3312
3525
|
},
|
|
3526
|
+
didPresentFrame() {
|
|
3527
|
+
const hold = presentationHold;
|
|
3528
|
+
if (!hold?.ready ||
|
|
3529
|
+
hold.intent !== playbackIntent ||
|
|
3530
|
+
hold.wait !== presentationWait) {
|
|
3531
|
+
return;
|
|
3532
|
+
}
|
|
3533
|
+
presentationHold = null;
|
|
3534
|
+
return releaseReadinessFreeze().then(publishPlaybackState);
|
|
3535
|
+
},
|
|
3313
3536
|
destroy() {
|
|
3314
3537
|
presentationWait += 1;
|
|
3315
3538
|
presentationHold = null;
|
|
@@ -3363,13 +3586,14 @@ function isSettling(status, seeking) {
|
|
|
3363
3586
|
* VideoFrame, so stale readiness can never authorize presentation after a new
|
|
3364
3587
|
* seek. The downstream handler owns a frame only after the guard succeeds.
|
|
3365
3588
|
*/
|
|
3366
|
-
function createProtectedPresentedFrameSource(upstream, onPresentationError = () => undefined) {
|
|
3589
|
+
function createProtectedPresentedFrameSource(upstream, onPresentationError = () => undefined, onAccepted, onPresented) {
|
|
3367
3590
|
let downstream = null;
|
|
3368
3591
|
let protect = null;
|
|
3369
3592
|
let pending = null;
|
|
3370
3593
|
let active = null;
|
|
3371
|
-
|
|
3594
|
+
const navigations = new Set();
|
|
3372
3595
|
let generation = 0;
|
|
3596
|
+
let lastAcceptedKey = null;
|
|
3373
3597
|
let destroyed = false;
|
|
3374
3598
|
let firstPresented = false;
|
|
3375
3599
|
let resolveFirstPresentation;
|
|
@@ -3392,27 +3616,27 @@ function createProtectedPresentedFrameSource(upstream, onPresentationError = ()
|
|
|
3392
3616
|
if (ticket.settled)
|
|
3393
3617
|
return;
|
|
3394
3618
|
ticket.settled = true;
|
|
3395
|
-
|
|
3396
|
-
navigation = null;
|
|
3619
|
+
navigations.delete(ticket);
|
|
3397
3620
|
if (error === undefined)
|
|
3398
3621
|
ticket.resolve();
|
|
3399
3622
|
else
|
|
3400
3623
|
ticket.reject(error);
|
|
3401
3624
|
};
|
|
3402
|
-
const acceptNavigationFrame = (frameId) => {
|
|
3403
|
-
const ticket = navigation;
|
|
3404
|
-
if (!ticket || ticket.settled)
|
|
3405
|
-
return;
|
|
3625
|
+
const acceptNavigationFrame = (frameId, frameGeneration) => {
|
|
3406
3626
|
const accepted = frameKey(frameId);
|
|
3407
|
-
ticket
|
|
3408
|
-
|
|
3409
|
-
|
|
3627
|
+
for (const ticket of navigations) {
|
|
3628
|
+
if (frameGeneration <= ticket.afterGeneration)
|
|
3629
|
+
continue;
|
|
3630
|
+
ticket.accepted.add(accepted);
|
|
3631
|
+
if (ticket.target === accepted)
|
|
3632
|
+
settleNavigation(ticket);
|
|
3633
|
+
}
|
|
3410
3634
|
};
|
|
3411
3635
|
const failPresentation = (error) => {
|
|
3412
3636
|
if (!firstPresented)
|
|
3413
3637
|
rejectFirstPresentation(error);
|
|
3414
|
-
|
|
3415
|
-
settleNavigation(
|
|
3638
|
+
for (const ticket of navigations)
|
|
3639
|
+
settleNavigation(ticket, error);
|
|
3416
3640
|
if (firstPresented)
|
|
3417
3641
|
onPresentationError(error);
|
|
3418
3642
|
};
|
|
@@ -3450,29 +3674,64 @@ function createProtectedPresentedFrameSource(upstream, onPresentationError = ()
|
|
|
3450
3674
|
run.handedOff = true;
|
|
3451
3675
|
try {
|
|
3452
3676
|
const acknowledge = frame.acknowledgePresentation;
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3677
|
+
let acknowledged = false;
|
|
3678
|
+
downstream({
|
|
3679
|
+
...frame,
|
|
3680
|
+
acknowledgePresentation: () => {
|
|
3681
|
+
if (acknowledged ||
|
|
3682
|
+
destroyed ||
|
|
3683
|
+
controller.signal.aborted ||
|
|
3684
|
+
run.generation !== generation) {
|
|
3685
|
+
return;
|
|
3686
|
+
}
|
|
3687
|
+
acknowledged = true;
|
|
3688
|
+
try {
|
|
3689
|
+
acknowledge?.();
|
|
3690
|
+
}
|
|
3691
|
+
catch (error) {
|
|
3692
|
+
failPresentation(error);
|
|
3693
|
+
return;
|
|
3694
|
+
}
|
|
3695
|
+
if (destroyed ||
|
|
3696
|
+
controller.signal.aborted ||
|
|
3697
|
+
run.generation !== generation) {
|
|
3698
|
+
return;
|
|
3699
|
+
}
|
|
3700
|
+
try {
|
|
3701
|
+
const release = onPresented?.();
|
|
3702
|
+
if (release) {
|
|
3703
|
+
void release.catch((error) => {
|
|
3704
|
+
if (!destroyed &&
|
|
3705
|
+
!controller.signal.aborted &&
|
|
3706
|
+
run.generation === generation) {
|
|
3707
|
+
failPresentation(error);
|
|
3708
|
+
}
|
|
3709
|
+
});
|
|
3461
3710
|
}
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3711
|
+
}
|
|
3712
|
+
catch (error) {
|
|
3713
|
+
failPresentation(error);
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
if (destroyed ||
|
|
3717
|
+
controller.signal.aborted ||
|
|
3718
|
+
run.generation !== generation) {
|
|
3719
|
+
return;
|
|
3720
|
+
}
|
|
3721
|
+
lastAcceptedKey = frameKey(frame.frameId);
|
|
3722
|
+
onAccepted?.(frame.frameId, frame.mediaTimeS);
|
|
3723
|
+
acceptNavigationFrame(frame.frameId, run.generation);
|
|
3724
|
+
if (!firstPresented) {
|
|
3725
|
+
firstPresented = true;
|
|
3726
|
+
resolveFirstPresentation();
|
|
3727
|
+
}
|
|
3728
|
+
},
|
|
3729
|
+
});
|
|
3466
3730
|
}
|
|
3467
3731
|
catch (error) {
|
|
3468
3732
|
failPresentation(error);
|
|
3469
3733
|
return;
|
|
3470
3734
|
}
|
|
3471
|
-
acceptNavigationFrame(frame.frameId);
|
|
3472
|
-
if (!firstPresented) {
|
|
3473
|
-
firstPresented = true;
|
|
3474
|
-
resolveFirstPresentation();
|
|
3475
|
-
}
|
|
3476
3735
|
};
|
|
3477
3736
|
let guarded;
|
|
3478
3737
|
try {
|
|
@@ -3532,16 +3791,18 @@ function createProtectedPresentedFrameSource(upstream, onPresentationError = ()
|
|
|
3532
3791
|
cancelActive();
|
|
3533
3792
|
pending?.frame.close();
|
|
3534
3793
|
pending = null;
|
|
3535
|
-
|
|
3536
|
-
settleNavigation(
|
|
3537
|
-
},
|
|
3538
|
-
beginNavigation() {
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3794
|
+
for (const ticket of navigations)
|
|
3795
|
+
settleNavigation(ticket);
|
|
3796
|
+
},
|
|
3797
|
+
beginNavigation(preserveCurrent = false) {
|
|
3798
|
+
if (!preserveCurrent) {
|
|
3799
|
+
generation += 1;
|
|
3800
|
+
cancelActive();
|
|
3801
|
+
pending?.frame.close();
|
|
3802
|
+
pending = null;
|
|
3803
|
+
for (const ticket of navigations)
|
|
3804
|
+
settleNavigation(ticket);
|
|
3805
|
+
}
|
|
3545
3806
|
let resolve;
|
|
3546
3807
|
let reject;
|
|
3547
3808
|
const promise = new Promise((onResolve, onReject) => {
|
|
@@ -3553,20 +3814,28 @@ function createProtectedPresentedFrameSource(upstream, onPresentationError = ()
|
|
|
3553
3814
|
void promise.catch(() => undefined);
|
|
3554
3815
|
const ticket = {
|
|
3555
3816
|
accepted: new Set(),
|
|
3817
|
+
afterGeneration: generation,
|
|
3556
3818
|
promise,
|
|
3557
3819
|
reject,
|
|
3558
3820
|
resolve,
|
|
3559
3821
|
settled: false,
|
|
3560
3822
|
target: null,
|
|
3561
3823
|
};
|
|
3562
|
-
|
|
3824
|
+
navigations.add(ticket);
|
|
3563
3825
|
return {
|
|
3564
|
-
waitFor(frameId) {
|
|
3826
|
+
waitFor(frameId, allowCurrent = false) {
|
|
3565
3827
|
if (ticket.settled)
|
|
3566
3828
|
return ticket.promise;
|
|
3567
3829
|
ticket.target = frameKey(frameId);
|
|
3568
3830
|
if (ticket.accepted.has(ticket.target))
|
|
3569
3831
|
settleNavigation(ticket);
|
|
3832
|
+
if (allowCurrent &&
|
|
3833
|
+
lastAcceptedKey === ticket.target &&
|
|
3834
|
+
generation === ticket.afterGeneration &&
|
|
3835
|
+
pending === null &&
|
|
3836
|
+
active === null) {
|
|
3837
|
+
settleNavigation(ticket);
|
|
3838
|
+
}
|
|
3570
3839
|
return ticket.promise;
|
|
3571
3840
|
},
|
|
3572
3841
|
cancel() {
|
|
@@ -3583,8 +3852,8 @@ function createProtectedPresentedFrameSource(upstream, onPresentationError = ()
|
|
|
3583
3852
|
cancelActive();
|
|
3584
3853
|
pending?.frame.close();
|
|
3585
3854
|
pending = null;
|
|
3586
|
-
|
|
3587
|
-
settleNavigation(
|
|
3855
|
+
for (const ticket of navigations)
|
|
3856
|
+
settleNavigation(ticket);
|
|
3588
3857
|
upstream.onPresentedFrame((presented) => presented.frame.close());
|
|
3589
3858
|
if (!firstPresented)
|
|
3590
3859
|
resolveFirstPresentation();
|
|
@@ -3723,6 +3992,8 @@ async function createMediaRendererCore(options, providers) {
|
|
|
3723
3992
|
options.onState?.(state);
|
|
3724
3993
|
},
|
|
3725
3994
|
});
|
|
3995
|
+
let frameClock = null;
|
|
3996
|
+
let frameNavigation;
|
|
3726
3997
|
let activeSampleIterator;
|
|
3727
3998
|
let mediaInput;
|
|
3728
3999
|
let playbackController;
|
|
@@ -3789,6 +4060,7 @@ async function createMediaRendererCore(options, providers) {
|
|
|
3789
4060
|
runtimeState.setLoading();
|
|
3790
4061
|
break;
|
|
3791
4062
|
case MediaRendererPlaybackState.Error:
|
|
4063
|
+
frameNavigation?.cancel();
|
|
3792
4064
|
runtimeState.setRenderError(new Error("Media playback failed."));
|
|
3793
4065
|
break;
|
|
3794
4066
|
}
|
|
@@ -4104,8 +4376,53 @@ async function createMediaRendererCore(options, providers) {
|
|
|
4104
4376
|
});
|
|
4105
4377
|
});
|
|
4106
4378
|
};
|
|
4379
|
+
const seekMediaTime = async (mediaTime) => {
|
|
4380
|
+
if (runtimeState.isDestroyed()) {
|
|
4381
|
+
throw new Error("Media renderer has been destroyed.");
|
|
4382
|
+
}
|
|
4383
|
+
if (runtimeState.isError()) {
|
|
4384
|
+
throw new Error(runtimeState.errorMessage() ?? "Media renderer is in error state.");
|
|
4385
|
+
}
|
|
4386
|
+
const targetTime = clampSeekTime({
|
|
4387
|
+
duration: runtimeState.duration(),
|
|
4388
|
+
firstTimestamp,
|
|
4389
|
+
mediaTime,
|
|
4390
|
+
});
|
|
4391
|
+
endSeekGesture();
|
|
4392
|
+
if (transport) {
|
|
4393
|
+
await transport.commit(targetTime);
|
|
4394
|
+
return;
|
|
4395
|
+
}
|
|
4396
|
+
await seekPullSample(targetTime);
|
|
4397
|
+
};
|
|
4398
|
+
const scrubMediaTime = (mediaTime) => {
|
|
4399
|
+
if (runtimeState.isDestroyed() || runtimeState.isError())
|
|
4400
|
+
return;
|
|
4401
|
+
const targetTime = clampSeekTime({
|
|
4402
|
+
duration: runtimeState.duration(),
|
|
4403
|
+
firstTimestamp,
|
|
4404
|
+
mediaTime,
|
|
4405
|
+
});
|
|
4406
|
+
isSeekGestureInFlight = true;
|
|
4407
|
+
publishPlaybackActivity();
|
|
4408
|
+
if (transport) {
|
|
4409
|
+
transport.scrub(targetTime);
|
|
4410
|
+
return;
|
|
4411
|
+
}
|
|
4412
|
+
pendingPullScrubTime = targetTime;
|
|
4413
|
+
if (pullScrubReadInFlight)
|
|
4414
|
+
navigationVersion += 1;
|
|
4415
|
+
schedulePullScrubPreview();
|
|
4416
|
+
};
|
|
4107
4417
|
const renderer = {
|
|
4418
|
+
get frameClock() {
|
|
4419
|
+
return frameClock;
|
|
4420
|
+
},
|
|
4421
|
+
get frameNavigation() {
|
|
4422
|
+
return frameNavigation?.api ?? null;
|
|
4423
|
+
},
|
|
4108
4424
|
async play() {
|
|
4425
|
+
frameNavigation?.cancel();
|
|
4109
4426
|
if (runtimeState.isDestroyed()) {
|
|
4110
4427
|
throw new Error("Media renderer has been destroyed.");
|
|
4111
4428
|
}
|
|
@@ -4134,6 +4451,7 @@ async function createMediaRendererCore(options, providers) {
|
|
|
4134
4451
|
playbackController.play();
|
|
4135
4452
|
},
|
|
4136
4453
|
pause() {
|
|
4454
|
+
frameNavigation?.cancel();
|
|
4137
4455
|
if (runtimeState.isDestroyed()) {
|
|
4138
4456
|
return;
|
|
4139
4457
|
}
|
|
@@ -4149,6 +4467,7 @@ async function createMediaRendererCore(options, providers) {
|
|
|
4149
4467
|
playbackController?.pause();
|
|
4150
4468
|
},
|
|
4151
4469
|
async togglePlayback() {
|
|
4470
|
+
frameNavigation?.cancel();
|
|
4152
4471
|
if (runtimeState.isDestroyed()) {
|
|
4153
4472
|
return;
|
|
4154
4473
|
}
|
|
@@ -4164,49 +4483,19 @@ async function createMediaRendererCore(options, providers) {
|
|
|
4164
4483
|
await renderer.play();
|
|
4165
4484
|
},
|
|
4166
4485
|
async seek(mediaTime) {
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
}
|
|
4170
|
-
if (runtimeState.isError()) {
|
|
4171
|
-
throw new Error(runtimeState.errorMessage() ?? "Media renderer is in error state.");
|
|
4172
|
-
}
|
|
4173
|
-
const targetTime = clampSeekTime({
|
|
4174
|
-
duration: runtimeState.duration(),
|
|
4175
|
-
firstTimestamp,
|
|
4176
|
-
mediaTime,
|
|
4177
|
-
});
|
|
4178
|
-
endSeekGesture();
|
|
4179
|
-
if (transport) {
|
|
4180
|
-
await transport.commit(targetTime);
|
|
4181
|
-
return;
|
|
4182
|
-
}
|
|
4183
|
-
await seekPullSample(targetTime);
|
|
4486
|
+
frameNavigation?.cancel();
|
|
4487
|
+
await seekMediaTime(mediaTime);
|
|
4184
4488
|
},
|
|
4185
4489
|
scrub(mediaTime) {
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
}
|
|
4189
|
-
const targetTime = clampSeekTime({
|
|
4190
|
-
duration: runtimeState.duration(),
|
|
4191
|
-
firstTimestamp,
|
|
4192
|
-
mediaTime,
|
|
4193
|
-
});
|
|
4194
|
-
isSeekGestureInFlight = true;
|
|
4195
|
-
publishPlaybackActivity();
|
|
4196
|
-
if (transport) {
|
|
4197
|
-
transport.scrub(targetTime);
|
|
4198
|
-
return;
|
|
4199
|
-
}
|
|
4200
|
-
pendingPullScrubTime = targetTime;
|
|
4201
|
-
if (pullScrubReadInFlight) {
|
|
4202
|
-
navigationVersion += 1;
|
|
4203
|
-
}
|
|
4204
|
-
schedulePullScrubPreview();
|
|
4490
|
+
frameNavigation?.cancel();
|
|
4491
|
+
scrubMediaTime(mediaTime);
|
|
4205
4492
|
},
|
|
4206
4493
|
async stepForward() {
|
|
4494
|
+
frameNavigation?.cancel();
|
|
4207
4495
|
await stepToAdjacentSample("forward");
|
|
4208
4496
|
},
|
|
4209
4497
|
async stepBackward() {
|
|
4498
|
+
frameNavigation?.cancel();
|
|
4210
4499
|
await stepToAdjacentSample("backward");
|
|
4211
4500
|
},
|
|
4212
4501
|
setPlaybackRate(playbackRate) {
|
|
@@ -4246,7 +4535,7 @@ async function createMediaRendererCore(options, providers) {
|
|
|
4246
4535
|
if (requestVersion !== navigationVersion || runtimeState.isDestroyed()) {
|
|
4247
4536
|
return;
|
|
4248
4537
|
}
|
|
4249
|
-
const presentedSample = mediaScene.setPresentation(currentPresentation,
|
|
4538
|
+
const presentedSample = mediaScene.setPresentation(currentPresentation, runtimeState.presentedTime() ?? runtimeState.currentTime());
|
|
4250
4539
|
if (presentedSample) {
|
|
4251
4540
|
runtimeState.recordPresentationUpdate(presentedSample);
|
|
4252
4541
|
}
|
|
@@ -4337,6 +4626,7 @@ async function createMediaRendererCore(options, providers) {
|
|
|
4337
4626
|
return;
|
|
4338
4627
|
}
|
|
4339
4628
|
cancelPullScrubPreview();
|
|
4629
|
+
frameNavigation?.destroy();
|
|
4340
4630
|
runtimeState.markDestroyed();
|
|
4341
4631
|
protectedPresentedFrames?.destroy();
|
|
4342
4632
|
transport?.destroy();
|
|
@@ -4355,6 +4645,7 @@ async function createMediaRendererCore(options, providers) {
|
|
|
4355
4645
|
throw new Error("Provide either detectionFrames or detectionSource, not both.");
|
|
4356
4646
|
}
|
|
4357
4647
|
const mediaSource = await openRendererMediaSource(options, providers);
|
|
4648
|
+
frameClock = mediaSource.frameClock ?? null;
|
|
4358
4649
|
mediaInput = mediaSource.input;
|
|
4359
4650
|
sampleSink = mediaSource.sampleSink;
|
|
4360
4651
|
if (runtimeState.isDestroyed()) {
|
|
@@ -4388,11 +4679,12 @@ async function createMediaRendererCore(options, providers) {
|
|
|
4388
4679
|
// the producer keeps running. Cut off future frames and state
|
|
4389
4680
|
// signals before publishing the rendering failure.
|
|
4390
4681
|
protectedPresentedFrames?.destroy();
|
|
4682
|
+
frameNavigation?.destroy(error);
|
|
4391
4683
|
transport?.destroy();
|
|
4392
4684
|
presentedFrameChannel.pause();
|
|
4393
4685
|
if (!runtimeState.isDestroyed())
|
|
4394
4686
|
runtimeState.setRenderError(error);
|
|
4395
|
-
})
|
|
4687
|
+
}, (id, mediaTime) => frameNavigation?.presented({ index: id.index, mediaTime }), () => transport?.didPresentFrame())
|
|
4396
4688
|
: undefined;
|
|
4397
4689
|
const mediaDimensions = runtimeState.recordMediaMetadata(metadata);
|
|
4398
4690
|
mediaScene = await providers.createScene({
|
|
@@ -4437,14 +4729,13 @@ async function createMediaRendererCore(options, providers) {
|
|
|
4437
4729
|
});
|
|
4438
4730
|
publishPlaybackActivity();
|
|
4439
4731
|
runtimeState.setRendererBackend(mediaScene.rendererBackend);
|
|
4440
|
-
|
|
4441
|
-
duration: metadata.duration,
|
|
4442
|
-
loop: options.loop !== false,
|
|
4443
|
-
});
|
|
4444
|
-
mediaScene.setTimelineContext?.({
|
|
4732
|
+
const timelineContext = {
|
|
4445
4733
|
duration: metadata.duration,
|
|
4734
|
+
firstTimestamp: metadata.firstTimestamp,
|
|
4446
4735
|
loop: options.loop !== false,
|
|
4447
|
-
}
|
|
4736
|
+
};
|
|
4737
|
+
detectionTimeline.setTimelineContext?.(timelineContext);
|
|
4738
|
+
mediaScene.setTimelineContext?.(timelineContext);
|
|
4448
4739
|
mediaScene.initializeMedia(mediaDimensions);
|
|
4449
4740
|
runtimeState.setSourceReady(metadata);
|
|
4450
4741
|
if (presentedFrameChannel) {
|
|
@@ -4474,9 +4765,35 @@ async function createMediaRendererCore(options, providers) {
|
|
|
4474
4765
|
? holdForPresentationReadiness
|
|
4475
4766
|
: undefined,
|
|
4476
4767
|
invalidatePresentedFrame: () => protectedPresentedFrames?.invalidate(),
|
|
4477
|
-
beginPresentedFrameNavigation: () => protectedPresentedFrames.beginNavigation(),
|
|
4768
|
+
beginPresentedFrameNavigation: (preserveCurrent) => protectedPresentedFrames.beginNavigation(preserveCurrent),
|
|
4478
4769
|
});
|
|
4479
|
-
|
|
4770
|
+
const setDisplay = mediaSource.setDisplay;
|
|
4771
|
+
if (setDisplay) {
|
|
4772
|
+
renderer.setDisplay = async (display) => {
|
|
4773
|
+
if (runtimeState.isDestroyed())
|
|
4774
|
+
throw new Error("Media renderer has been destroyed.");
|
|
4775
|
+
await transport.resizeOutput(() => setDisplay(display));
|
|
4776
|
+
};
|
|
4777
|
+
}
|
|
4778
|
+
if (frameClock) {
|
|
4779
|
+
frameNavigation = createMediaFrameNavigation({
|
|
4780
|
+
clock: frameClock,
|
|
4781
|
+
seek: seekMediaTime,
|
|
4782
|
+
scrub(mediaTime) {
|
|
4783
|
+
if (runtimeState.isDestroyed()) {
|
|
4784
|
+
throw new Error("Media renderer has been destroyed.");
|
|
4785
|
+
}
|
|
4786
|
+
if (runtimeState.isError()) {
|
|
4787
|
+
throw new Error(runtimeState.errorMessage() ??
|
|
4788
|
+
"Media renderer is in error state.");
|
|
4789
|
+
}
|
|
4790
|
+
scrubMediaTime(mediaTime);
|
|
4791
|
+
},
|
|
4792
|
+
});
|
|
4793
|
+
}
|
|
4794
|
+
protectedPresentedFrames?.activate((presented, signal) => pushPresentationReady
|
|
4795
|
+
? transport.protectPresentation(presented.mediaTimeS, signal)
|
|
4796
|
+
: null);
|
|
4480
4797
|
if (initialPlaybackRate !== 1) {
|
|
4481
4798
|
transport.setPlaybackRate(initialPlaybackRate);
|
|
4482
4799
|
}
|
|
@@ -4498,7 +4815,11 @@ async function createMediaRendererCore(options, providers) {
|
|
|
4498
4815
|
pushPresentationReady = true;
|
|
4499
4816
|
runtimeState.setReady();
|
|
4500
4817
|
if (options.autoPlay ?? true) {
|
|
4501
|
-
|
|
4818
|
+
// Opening exposes the writer even when autoplay awaits future detections.
|
|
4819
|
+
void renderer.play().catch((error) => {
|
|
4820
|
+
if (!runtimeState.isDestroyed())
|
|
4821
|
+
runtimeState.setRenderError(error);
|
|
4822
|
+
});
|
|
4502
4823
|
}
|
|
4503
4824
|
return renderer;
|
|
4504
4825
|
}
|
|
@@ -4662,7 +4983,7 @@ function clampSeekTime(options) {
|
|
|
4662
4983
|
const startTime = options.firstTimestamp;
|
|
4663
4984
|
const endTime = options.duration === null
|
|
4664
4985
|
? null
|
|
4665
|
-
:
|
|
4986
|
+
: Math.max(options.firstTimestamp, options.duration);
|
|
4666
4987
|
return Math.min(Math.max(mediaTime, startTime), endTime ?? mediaTime);
|
|
4667
4988
|
}
|
|
4668
4989
|
|
|
@@ -5559,9 +5880,10 @@ function createPreparedWindowTimeline() {
|
|
|
5559
5880
|
},
|
|
5560
5881
|
};
|
|
5561
5882
|
function isLoopingTimeline() {
|
|
5883
|
+
const firstTimestamp = timelineContext.firstTimestamp ?? 0;
|
|
5562
5884
|
return (timelineContext.loop &&
|
|
5563
5885
|
timelineContext.duration !== null &&
|
|
5564
|
-
timelineContext.duration >
|
|
5886
|
+
timelineContext.duration > firstTimestamp);
|
|
5565
5887
|
}
|
|
5566
5888
|
/**
|
|
5567
5889
|
* Travel forward from `mediaTime` to `frameTime`, wrapping at the media end,
|
|
@@ -5571,11 +5893,12 @@ function createPreparedWindowTimeline() {
|
|
|
5571
5893
|
if (!isLoopingTimeline() || timelineContext.duration === null) {
|
|
5572
5894
|
return Math.max(0, frameTime - mediaTime);
|
|
5573
5895
|
}
|
|
5574
|
-
const
|
|
5575
|
-
const
|
|
5576
|
-
const
|
|
5896
|
+
const firstTimestamp = timelineContext.firstTimestamp ?? 0;
|
|
5897
|
+
const loopSpan = timelineContext.duration - firstTimestamp;
|
|
5898
|
+
const normalizedFrameTime = firstTimestamp + modulo(frameTime - firstTimestamp, loopSpan);
|
|
5899
|
+
const normalizedMediaTime = firstTimestamp + modulo(mediaTime - firstTimestamp, loopSpan);
|
|
5577
5900
|
const rawDistance = normalizedFrameTime - normalizedMediaTime;
|
|
5578
|
-
return rawDistance >= 0 ? rawDistance : rawDistance +
|
|
5901
|
+
return rawDistance >= 0 ? rawDistance : rawDistance + loopSpan;
|
|
5579
5902
|
}
|
|
5580
5903
|
/**
|
|
5581
5904
|
* The buffer's end time is the far edge of what the playhead reaches next, and
|
|
@@ -6642,17 +6965,23 @@ function createPreparedAnnotationWindow(options) {
|
|
|
6642
6965
|
const spanFrameCount = resolvePreparedWindowFrameCount({
|
|
6643
6966
|
renderPreparation: options.renderPreparation,
|
|
6644
6967
|
});
|
|
6968
|
+
const frameRevisions = new WeakMap();
|
|
6969
|
+
let nextFrameRevision = 0;
|
|
6645
6970
|
const getPreparedFrame = (mediaTime) => options.detectionTimeline.selectFrame(mediaTime) ?? null;
|
|
6646
6971
|
const isFrameArtifactPrepared = (mediaTime) => options.getLayers().every((layer) => layer.isArtifactPrepared(mediaTime));
|
|
6647
6972
|
return {
|
|
6648
6973
|
getPreparedFrame,
|
|
6649
6974
|
getReadinessToken(mediaTime) {
|
|
6650
6975
|
const detectionFrame = options.detectionTimeline.selectFrame(mediaTime);
|
|
6976
|
+
if (detectionFrame && !frameRevisions.has(detectionFrame)) {
|
|
6977
|
+
frameRevisions.set(detectionFrame, ++nextFrameRevision);
|
|
6978
|
+
}
|
|
6979
|
+
const revision = detectionFrame ? frameRevisions.get(detectionFrame) : 0;
|
|
6651
6980
|
const cooks = options
|
|
6652
6981
|
.getLayers()
|
|
6653
6982
|
.map((layer) => (layer.isArtifactPrepared(mediaTime) ? "1" : "0"))
|
|
6654
6983
|
.join("");
|
|
6655
|
-
return `${detectionFrame?.frameIndex ?? "time"}:${detectionFrame?.mediaTime ?? "none"}:${cooks}`;
|
|
6984
|
+
return `${detectionFrame?.frameIndex ?? "time"}:${detectionFrame?.mediaTime ?? "none"}:${revision}:${cooks}`;
|
|
6656
6985
|
},
|
|
6657
6986
|
getSnapshot() {
|
|
6658
6987
|
const playheadMediaTime = options.getPlayheadMediaTime();
|
|
@@ -13143,13 +13472,14 @@ async function createPixiMediaScene(options) {
|
|
|
13143
13472
|
});
|
|
13144
13473
|
},
|
|
13145
13474
|
};
|
|
13146
|
-
//
|
|
13147
|
-
//
|
|
13148
|
-
// that clock did not drive would move them out of turn.
|
|
13475
|
+
// A content redraw updates editing geometry at the same media time without
|
|
13476
|
+
// advancing the focus animation.
|
|
13149
13477
|
const annotationDrawLayers = {
|
|
13150
13478
|
...presentLayers,
|
|
13151
13479
|
advanceFocus: () => undefined,
|
|
13152
|
-
drawAnnotationOverlay:
|
|
13480
|
+
drawAnnotationOverlay: frameChannel
|
|
13481
|
+
? presentLayers.drawAnnotationOverlay
|
|
13482
|
+
: () => undefined,
|
|
13153
13483
|
};
|
|
13154
13484
|
// A viewport-style redraw covers the layers whose geometry is a function of
|
|
13155
13485
|
// the viewport scale. Neither the mask raster nor the interaction hit map
|
|
@@ -13593,6 +13923,8 @@ async function createPixiMediaScene(options) {
|
|
|
13593
13923
|
return pick;
|
|
13594
13924
|
},
|
|
13595
13925
|
destroy() {
|
|
13926
|
+
if (isDestroyed)
|
|
13927
|
+
return;
|
|
13596
13928
|
isDestroyed = true;
|
|
13597
13929
|
if (displayFrameHandle !== null) {
|
|
13598
13930
|
cancelDisplayFrame(displayFrameHandle);
|
|
@@ -13634,6 +13966,7 @@ async function createPixiMediaScene(options) {
|
|
|
13634
13966
|
texture: true,
|
|
13635
13967
|
textureSource: true,
|
|
13636
13968
|
});
|
|
13969
|
+
backdrop.remove();
|
|
13637
13970
|
},
|
|
13638
13971
|
};
|
|
13639
13972
|
/**
|
|
@@ -14648,10 +14981,11 @@ const STREAM_DETECTION_BUFFER_DEFAULTS = {
|
|
|
14648
14981
|
refreshIntervalSeconds: 0.25,
|
|
14649
14982
|
};
|
|
14650
14983
|
/**
|
|
14651
|
-
*
|
|
14652
|
-
* a frame and the marks that belong to it together
|
|
14653
|
-
*
|
|
14654
|
-
* own `enabled`, turns
|
|
14984
|
+
* Once a session is open, playback waits for annotations unless a host says
|
|
14985
|
+
* otherwise, so a viewer sees a frame and the marks that belong to it together.
|
|
14986
|
+
* Opening itself may show a bare initial frame while future annotations arrive.
|
|
14987
|
+
* `playbackGate: false` on the session, or either gate's own `enabled`, turns
|
|
14988
|
+
* it off.
|
|
14655
14989
|
*/
|
|
14656
14990
|
const DETECTION_PLAYBACK_GATE_DEFAULTS = {
|
|
14657
14991
|
enabled: true,
|
|
@@ -14844,10 +15178,16 @@ async function prepareSessionDetections(options) {
|
|
|
14844
15178
|
sourcePresentations: [],
|
|
14845
15179
|
};
|
|
14846
15180
|
}
|
|
15181
|
+
const writableSource = isWritableSource(detections.source)
|
|
15182
|
+
? detections.source
|
|
15183
|
+
: undefined;
|
|
14847
15184
|
return {
|
|
14848
|
-
|
|
15185
|
+
appendableSource: writableSource,
|
|
15186
|
+
appendableSources: new Map(writableSource ? [[writableSource.datasetId, writableSource]] : []),
|
|
14849
15187
|
detectionFrames: detections.frames,
|
|
14850
|
-
detectionSource: detections.source
|
|
15188
|
+
detectionSource: detections.source
|
|
15189
|
+
? borrowDetectionSource(detections.source)
|
|
15190
|
+
: undefined,
|
|
14851
15191
|
sourcePresentations: [],
|
|
14852
15192
|
};
|
|
14853
15193
|
}
|
|
@@ -14866,6 +15206,7 @@ async function prepareMultiSourceSessionDetections(options) {
|
|
|
14866
15206
|
throw new Error("detections.sources must include at least one source.");
|
|
14867
15207
|
}
|
|
14868
15208
|
const appendableSources = new Map();
|
|
15209
|
+
const ownedSources = [];
|
|
14869
15210
|
const compositeEntries = [];
|
|
14870
15211
|
validateMediaSessionDetectionSources(detections.sources);
|
|
14871
15212
|
try {
|
|
@@ -14875,7 +15216,12 @@ async function prepareMultiSourceSessionDetections(options) {
|
|
|
14875
15216
|
appendable: sourceOptions.appendable,
|
|
14876
15217
|
mode: options.mode,
|
|
14877
15218
|
})
|
|
14878
|
-
:
|
|
15219
|
+
: isWritableSource(sourceOptions.source)
|
|
15220
|
+
? sourceOptions.source
|
|
15221
|
+
: undefined;
|
|
15222
|
+
if (sourceOptions.appendable && appendableSource) {
|
|
15223
|
+
ownedSources.push(appendableSource);
|
|
15224
|
+
}
|
|
14879
15225
|
if (appendableSource) {
|
|
14880
15226
|
appendableSources.set(sourceOptions.id, appendableSource);
|
|
14881
15227
|
}
|
|
@@ -14884,13 +15230,15 @@ async function prepareMultiSourceSessionDetections(options) {
|
|
|
14884
15230
|
id: sourceOptions.id,
|
|
14885
15231
|
order: sourceOptions.order,
|
|
14886
15232
|
requiredForCoverage: sourceOptions.requiredForCoverage,
|
|
14887
|
-
source: sourceOptions.source
|
|
15233
|
+
source: sourceOptions.source
|
|
15234
|
+
? borrowDetectionSource(sourceOptions.source)
|
|
15235
|
+
: appendableSource,
|
|
14888
15236
|
sync: sourceOptions.sync,
|
|
14889
15237
|
});
|
|
14890
15238
|
}
|
|
14891
15239
|
}
|
|
14892
15240
|
catch (error) {
|
|
14893
|
-
for (const source of
|
|
15241
|
+
for (const source of ownedSources) {
|
|
14894
15242
|
source.destroy?.();
|
|
14895
15243
|
}
|
|
14896
15244
|
throw error;
|
|
@@ -14907,6 +15255,36 @@ async function prepareMultiSourceSessionDetections(options) {
|
|
|
14907
15255
|
})),
|
|
14908
15256
|
};
|
|
14909
15257
|
}
|
|
15258
|
+
function isWritableSource(source) {
|
|
15259
|
+
const candidate = source;
|
|
15260
|
+
return (typeof candidate?.datasetId === "string" &&
|
|
15261
|
+
[
|
|
15262
|
+
"appendFrames",
|
|
15263
|
+
"replaceFrames",
|
|
15264
|
+
"clear",
|
|
15265
|
+
"getVersion",
|
|
15266
|
+
"getSummary",
|
|
15267
|
+
"waitForRange",
|
|
15268
|
+
"getAvailableRanges",
|
|
15269
|
+
].every((method) => typeof candidate[method] === "function"));
|
|
15270
|
+
}
|
|
15271
|
+
function borrowDetectionSource(source) {
|
|
15272
|
+
return {
|
|
15273
|
+
loadFrames: source.loadFrames.bind(source),
|
|
15274
|
+
...(source.getVersion
|
|
15275
|
+
? { getVersion: source.getVersion.bind(source) }
|
|
15276
|
+
: {}),
|
|
15277
|
+
...(source.getChangesSince
|
|
15278
|
+
? { getChangesSince: source.getChangesSince.bind(source) }
|
|
15279
|
+
: {}),
|
|
15280
|
+
...(source.getAvailableRanges
|
|
15281
|
+
? { getAvailableRanges: source.getAvailableRanges.bind(source) }
|
|
15282
|
+
: {}),
|
|
15283
|
+
...(source.waitForRange
|
|
15284
|
+
? { waitForRange: source.waitForRange.bind(source) }
|
|
15285
|
+
: {}),
|
|
15286
|
+
};
|
|
15287
|
+
}
|
|
14910
15288
|
function validateMediaSessionDetectionSources(sources) {
|
|
14911
15289
|
const sourceIds = new Set();
|
|
14912
15290
|
for (const source of sources) {
|
|
@@ -15529,6 +15907,9 @@ async function createMediaSession(options) {
|
|
|
15529
15907
|
};
|
|
15530
15908
|
};
|
|
15531
15909
|
return {
|
|
15910
|
+
frameClock: renderer.frameClock ?? null,
|
|
15911
|
+
frameNavigation: renderer.frameNavigation ?? null,
|
|
15912
|
+
setDisplay: renderer.setDisplay?.bind(renderer),
|
|
15532
15913
|
detectionSource: sessionDetections.detectionSource,
|
|
15533
15914
|
media: sessionMedia.state,
|
|
15534
15915
|
renderer,
|