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
|
@@ -89,6 +89,7 @@ export interface FrameCacheOptions {
|
|
|
89
89
|
readonly minExactSlots?: number;
|
|
90
90
|
}
|
|
91
91
|
export declare class FrameCache {
|
|
92
|
+
private readonly options;
|
|
92
93
|
private readonly exact;
|
|
93
94
|
private readonly preview;
|
|
94
95
|
private exactHits;
|
|
@@ -97,6 +98,7 @@ export declare class FrameCache {
|
|
|
97
98
|
private readonly bucketMs;
|
|
98
99
|
private readonly exactBudgetBytes;
|
|
99
100
|
constructor(options: FrameCacheOptions);
|
|
101
|
+
resized(width: number, height: number): FrameCache;
|
|
100
102
|
/**
|
|
101
103
|
* Store a crisp full-resolution frame for the exact (scrub) tier under
|
|
102
104
|
* `frame`, the timeline's name for it. `timestampMs` is the decoded
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A timeout whose budget advances only while its owner is visible. The owner
|
|
3
|
+
* still decides which work is eligible: this helper never pauses the work
|
|
4
|
+
* itself, nor any unrelated watchdog.
|
|
5
|
+
*/
|
|
6
|
+
export interface PausableDeadline {
|
|
7
|
+
cancel(): void;
|
|
8
|
+
pause(): void;
|
|
9
|
+
resume(): void;
|
|
10
|
+
}
|
|
11
|
+
export declare function createPausableDeadline(timeoutMs: number, onExpire: () => void): PausableDeadline;
|
|
@@ -349,6 +349,8 @@ export declare class ScrubController {
|
|
|
349
349
|
* walk. Detaches the iterator and drops the read-ahead so paused ticks
|
|
350
350
|
* never paint a stale play frame. */
|
|
351
351
|
endPlay(): void;
|
|
352
|
+
holdOutput(): void;
|
|
353
|
+
releaseOutput(): void;
|
|
352
354
|
/**
|
|
353
355
|
* Cache lookup helper used by the engine on scrub. Asks the cursor for a
|
|
354
356
|
* cached frame near the target and, if it is far enough from what the canvas
|
|
@@ -356,12 +358,14 @@ export declare class ScrubController {
|
|
|
356
358
|
* calls cursor.seekTo to land the full-res frame; the cursor's emit listener
|
|
357
359
|
* stashes it for the next tick.
|
|
358
360
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
* consumers tune to control single-frame-step UX: a higher value forces a
|
|
362
|
-
* fresh decode on tighter movements, a value of 0 always serves the cache.
|
|
361
|
+
* Nearby approximate hits respect cacheSkipNearMs. An exact hit for the
|
|
362
|
+
* requested frame can advance immediately, without waiting for a decode.
|
|
363
363
|
*/
|
|
364
364
|
tryPaintFromCache(timestampMs: number): boolean;
|
|
365
|
+
/** A new commit needs its own presentation even when its exact pixels were
|
|
366
|
+
* already shown by the preceding scrub. Approximate neighbors cannot answer it. */
|
|
367
|
+
presentExactFromCache(timestampMs: number): boolean;
|
|
368
|
+
private paintCached;
|
|
365
369
|
private start;
|
|
366
370
|
private runTick;
|
|
367
371
|
private paint;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CreateScrubCursorOptions } from "./create-scrub-cursor";
|
|
2
|
+
import type { DecodeDimensions } from "./decode-resolution";
|
|
2
3
|
import type { FrameCacheStats } from "./frame-cache";
|
|
3
4
|
import type { FrameId, FrameTimeline } from "./frame-timeline";
|
|
4
5
|
import type { GopStats } from "./keyframe-index";
|
|
@@ -283,6 +284,8 @@ export interface ScrubTrackInfo {
|
|
|
283
284
|
* - close is final; reusing a closed cursor is a programming error.
|
|
284
285
|
*/
|
|
285
286
|
export interface ScrubCursor {
|
|
287
|
+
/** Drains old output and changes frame/cache size without reopening decode. */
|
|
288
|
+
resizeOutput?(dimensions: DecodeDimensions): Promise<boolean>;
|
|
286
289
|
readonly state: ScrubCursorState;
|
|
287
290
|
readonly track: ScrubTrackInfo;
|
|
288
291
|
readonly isIdle: boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DecodeResolutionStrategy } from "./decode-resolution";
|
|
1
|
+
import type { DecodeResolutionStrategy, DisplayBoxResolutionOptions } from "./decode-resolution";
|
|
2
2
|
import type { DiagnosticsSnapshot, EngineDiagnostics } from "./diagnostics";
|
|
3
3
|
import { type FrameId, type FrameLanding } from "./frame-timeline";
|
|
4
4
|
import type { SeekIntent } from "./scrub-cursor";
|
|
@@ -123,6 +123,9 @@ export declare class WebVideoEngine {
|
|
|
123
123
|
private lastTrace;
|
|
124
124
|
private traceArmed;
|
|
125
125
|
private readonly pending;
|
|
126
|
+
private presentationVisible;
|
|
127
|
+
private listeningForPresentationVisibility;
|
|
128
|
+
private readonly onPresentationVisibilityChange;
|
|
126
129
|
private nextRequestId;
|
|
127
130
|
private metadata;
|
|
128
131
|
/** The loaded source's frame table, held here so a gesture is resolved to a
|
|
@@ -156,6 +159,10 @@ export declare class WebVideoEngine {
|
|
|
156
159
|
*/
|
|
157
160
|
bindCanvas: (el: HTMLCanvasElement | null) => void;
|
|
158
161
|
play: () => Promise<void>;
|
|
162
|
+
/** Changes frames-mode display output without reopening the video. Resolves
|
|
163
|
+
* with true after worker delivery, or false for unchanged output dimensions.
|
|
164
|
+
* The host still acknowledges its actual presentation. */
|
|
165
|
+
setDisplay: (display: DisplayBoxResolutionOptions) => Promise<boolean>;
|
|
159
166
|
pause: () => void;
|
|
160
167
|
togglePlayback: () => void;
|
|
161
168
|
/**
|
|
@@ -334,6 +341,9 @@ export declare class WebVideoEngine {
|
|
|
334
341
|
private request;
|
|
335
342
|
private post;
|
|
336
343
|
private rejectAllPending;
|
|
344
|
+
private startPresentationVisibilityTracking;
|
|
345
|
+
private stopPresentationVisibilityTracking;
|
|
346
|
+
private syncPresentationVisibility;
|
|
337
347
|
/**
|
|
338
348
|
* The frame a target names.
|
|
339
349
|
*
|
|
@@ -363,6 +373,7 @@ export type PresentedFrameHandler = (presented: PresentedFrame) => void;
|
|
|
363
373
|
* getPlaybackState.
|
|
364
374
|
*/
|
|
365
375
|
export interface WebVideoEngineHandle {
|
|
376
|
+
setDisplay(display: DisplayBoxResolutionOptions): Promise<boolean>;
|
|
366
377
|
play(): Promise<void>;
|
|
367
378
|
pause(): void;
|
|
368
379
|
togglePlayback(): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FrameId, FrameLanding } from "./frame-timeline";
|
|
2
2
|
import type { FrameQuality, SeekIntent } from "./scrub-cursor";
|
|
3
|
-
import type { DecodeResolutionStrategy } from "./decode-resolution";
|
|
3
|
+
import type { DecodeResolutionStrategy, DisplayBoxResolutionOptions } from "./decode-resolution";
|
|
4
4
|
import type { DiagnosticsSnapshot, EngineDiagnostics } from "./diagnostics";
|
|
5
5
|
import type { Rotation } from "./rotation";
|
|
6
6
|
import type { EngineTrace } from "./trace-recorder";
|
|
@@ -79,6 +79,9 @@ export interface BindCanvasCommand {
|
|
|
79
79
|
}
|
|
80
80
|
export type FireAndForgetCommand = BindCanvasCommand | {
|
|
81
81
|
readonly type: "unbindCanvas";
|
|
82
|
+
} | {
|
|
83
|
+
readonly type: "setPresentationVisibility";
|
|
84
|
+
readonly visible: boolean;
|
|
82
85
|
} | {
|
|
83
86
|
readonly type: "scrub";
|
|
84
87
|
readonly frameIndex: number;
|
|
@@ -109,6 +112,10 @@ export type FireAndForgetCommand = BindCanvasCommand | {
|
|
|
109
112
|
readonly type: "traceDisarm";
|
|
110
113
|
};
|
|
111
114
|
export type AwaitableCommand = {
|
|
115
|
+
readonly type: "setDisplay";
|
|
116
|
+
readonly requestId: RequestId;
|
|
117
|
+
readonly display: DisplayBoxResolutionOptions;
|
|
118
|
+
} | {
|
|
112
119
|
readonly type: "load";
|
|
113
120
|
readonly requestId: RequestId;
|
|
114
121
|
readonly config: EngineLoadConfig;
|
|
@@ -200,6 +207,7 @@ export type ResponseEvent = {
|
|
|
200
207
|
readonly type: "ack";
|
|
201
208
|
readonly requestId: RequestId;
|
|
202
209
|
readonly landing?: FrameLanding;
|
|
210
|
+
readonly outputChanged?: boolean;
|
|
203
211
|
} | {
|
|
204
212
|
readonly type: "stats";
|
|
205
213
|
readonly requestId: RequestId;
|
package/node_modules/supervision-js-core/dist/detections/buffered-detection-timeline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buffered-detection-timeline.d.ts","sourceRoot":"","sources":["../../src/detections/buffered-detection-timeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAE3B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EAG1B,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAuDxD;;;;;GAKG;AACH,wBAAgB,yCAAyC,CACvD,QAAQ,EAAE,yBAAyB,GAClC,SAAS,cAAc,EAAE,CAI3B;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE;IACP,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;CACvC,GAAG,sBAAsB,GACzB,yBAAyB,
|
|
1
|
+
{"version":3,"file":"buffered-detection-timeline.d.ts","sourceRoot":"","sources":["../../src/detections/buffered-detection-timeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAE3B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EAG1B,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAuDxD;;;;;GAKG;AACH,wBAAgB,yCAAyC,CACvD,QAAQ,EAAE,yBAAyB,GAClC,SAAS,cAAc,EAAE,CAI3B;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE;IACP,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;CACvC,GAAG,sBAAsB,GACzB,yBAAyB,CA07B3B;AAED,wBAAgB,8BAA8B,IAAI,oBAAoB,CAWrE"}
|
|
@@ -615,6 +615,7 @@ function createBufferedDetectionTimeline(options) {
|
|
|
615
615
|
duration: null,
|
|
616
616
|
loop: false,
|
|
617
617
|
};
|
|
618
|
+
let timelineFirstTimestamp = 0;
|
|
618
619
|
let inFlight;
|
|
619
620
|
let incrementalRefresh;
|
|
620
621
|
/** Source version the gate last gave up on, or null while it still waits. */
|
|
@@ -754,14 +755,15 @@ function createBufferedDetectionTimeline(options) {
|
|
|
754
755
|
state.bufferEndTime === null) {
|
|
755
756
|
return;
|
|
756
757
|
}
|
|
757
|
-
const
|
|
758
|
+
const loopSpan = duration - timelineFirstTimestamp;
|
|
759
|
+
const laps = Math.round((getComparableMediaTime(mediaTime) - mediaTime) / loopSpan);
|
|
758
760
|
if (laps === 0) {
|
|
759
761
|
return;
|
|
760
762
|
}
|
|
761
763
|
state = {
|
|
762
764
|
...state,
|
|
763
|
-
bufferEndTime: state.bufferEndTime - laps *
|
|
764
|
-
bufferStartTime: state.bufferStartTime - laps *
|
|
765
|
+
bufferEndTime: state.bufferEndTime - laps * loopSpan,
|
|
766
|
+
bufferStartTime: state.bufferStartTime - laps * loopSpan,
|
|
765
767
|
};
|
|
766
768
|
notifyBufferChanged();
|
|
767
769
|
};
|
|
@@ -864,21 +866,27 @@ function createBufferedDetectionTimeline(options) {
|
|
|
864
866
|
if (destroyed || !isLoadingEnabled()) {
|
|
865
867
|
return;
|
|
866
868
|
}
|
|
869
|
+
adoptFirstTimestamp(prepareOptions?.firstTimestamp);
|
|
870
|
+
const navigationMediaTime = getNavigationMediaTime(mediaTime);
|
|
867
871
|
if (shouldWaitForPlaybackGate(prepareOptions)) {
|
|
868
|
-
await waitForPlaybackGate(
|
|
872
|
+
await waitForPlaybackGate(navigationMediaTime, prepareOptions);
|
|
869
873
|
if (destroyed) {
|
|
870
874
|
return;
|
|
871
875
|
}
|
|
872
876
|
}
|
|
873
|
-
await refreshBuffer(
|
|
874
|
-
anchorWindowToPlayhead(
|
|
877
|
+
await refreshBuffer(navigationMediaTime);
|
|
878
|
+
anchorWindowToPlayhead(navigationMediaTime);
|
|
875
879
|
},
|
|
876
880
|
needsBufferPrepare(mediaTime) {
|
|
881
|
+
const navigationMediaTime = getNavigationMediaTime(mediaTime);
|
|
877
882
|
return (!destroyed &&
|
|
878
883
|
isLoadingEnabled() &&
|
|
879
|
-
(!
|
|
884
|
+
(!isInsideBufferedRange(navigationMediaTime) ||
|
|
885
|
+
!inFlightCovers(navigationMediaTime)));
|
|
880
886
|
},
|
|
881
887
|
needsPlaybackGateWait(mediaTime, prepareOptions) {
|
|
888
|
+
adoptFirstTimestamp(prepareOptions?.firstTimestamp);
|
|
889
|
+
const navigationMediaTime = getNavigationMediaTime(mediaTime);
|
|
882
890
|
if (destroyed ||
|
|
883
891
|
!isLoadingEnabled() ||
|
|
884
892
|
!playbackGate?.enabled ||
|
|
@@ -888,28 +896,31 @@ function createBufferedDetectionTimeline(options) {
|
|
|
888
896
|
return false;
|
|
889
897
|
}
|
|
890
898
|
const availableRanges = options.source.getAvailableRanges();
|
|
891
|
-
return !createPlaybackGateCoveragePlan(
|
|
899
|
+
return !createPlaybackGateCoveragePlan(navigationMediaTime, prepareOptions).sourceRanges.every((range) => isRangeCovered(range, availableRanges));
|
|
892
900
|
},
|
|
893
901
|
prefetch(mediaTime) {
|
|
894
902
|
if (destroyed || !isLoadingEnabled()) {
|
|
895
903
|
return;
|
|
896
904
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
905
|
+
const navigationMediaTime = getNavigationMediaTime(mediaTime);
|
|
906
|
+
anchorWindowToPlayhead(navigationMediaTime);
|
|
907
|
+
supersedeLoadOutside(navigationMediaTime);
|
|
908
|
+
if (!shouldPrefetch(navigationMediaTime)) {
|
|
900
909
|
return;
|
|
901
910
|
}
|
|
902
|
-
pendingPrefetch = { loadId, mediaTime };
|
|
911
|
+
pendingPrefetch = { loadId, mediaTime: navigationMediaTime };
|
|
903
912
|
pumpPrefetchQueue();
|
|
904
913
|
},
|
|
905
914
|
selectFrame(mediaTime) {
|
|
906
|
-
|
|
915
|
+
const navigationMediaTime = getNavigationMediaTime(mediaTime);
|
|
916
|
+
if (!isInsideBufferedRange(navigationMediaTime)) {
|
|
907
917
|
return undefined;
|
|
908
918
|
}
|
|
909
|
-
return selectDetectionFrame(buffer, getSourceMediaTime(
|
|
919
|
+
return selectDetectionFrame(buffer, getSourceMediaTime(navigationMediaTime), options);
|
|
910
920
|
},
|
|
911
921
|
setTimelineContext(context) {
|
|
912
922
|
timelineContext = context;
|
|
923
|
+
adoptFirstTimestamp(context.firstTimestamp);
|
|
913
924
|
bufferedSourceVersion = null;
|
|
914
925
|
bufferedVersionRange = null;
|
|
915
926
|
},
|
|
@@ -1045,12 +1056,12 @@ function createBufferedDetectionTimeline(options) {
|
|
|
1045
1056
|
}
|
|
1046
1057
|
function createPlaybackGateCoveragePlan(mediaTime, prepareOptions) {
|
|
1047
1058
|
const requiredAheadSeconds = Math.max(0, playbackGate?.requiredAheadSeconds ?? 0);
|
|
1048
|
-
const comparableMediaTime = getComparableMediaTime(mediaTime);
|
|
1059
|
+
const comparableMediaTime = Math.max(timelineFirstTimestamp, getComparableMediaTime(mediaTime));
|
|
1049
1060
|
const endTime = getRequiredCoverageEndTime({
|
|
1050
1061
|
// A looping window counts past the end of media and wraps into the
|
|
1051
1062
|
// replay, so clamping it to duration would ask for less than it needs.
|
|
1052
1063
|
duration: isLoopingTimeline() ? null : prepareOptions?.duration,
|
|
1053
|
-
firstTimestamp:
|
|
1064
|
+
firstTimestamp: timelineFirstTimestamp,
|
|
1054
1065
|
mediaTime: comparableMediaTime,
|
|
1055
1066
|
requiredAheadSeconds,
|
|
1056
1067
|
});
|
|
@@ -1137,21 +1148,23 @@ function createBufferedDetectionTimeline(options) {
|
|
|
1137
1148
|
* a looping timeline. A plan is stated on the first lap and the window on the
|
|
1138
1149
|
* playhead's, so across the loop point the two state the same edge a whole lap
|
|
1139
1150
|
* apart; measured straight, that reads as a window a full lap stale and
|
|
1140
|
-
* rebuilds it on every frame until the plan stops reaching behind
|
|
1151
|
+
* rebuilds it on every frame until the plan stops reaching behind the first
|
|
1152
|
+
* playable timestamp.
|
|
1141
1153
|
*/
|
|
1142
1154
|
function getWindowDrift(planTime, bufferTime) {
|
|
1143
1155
|
const drift = Math.abs(planTime - bufferTime);
|
|
1144
1156
|
if (!isLoopingTimeline() || timelineContext.duration === null) {
|
|
1145
1157
|
return drift;
|
|
1146
1158
|
}
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1159
|
+
const loopSpan = timelineContext.duration - timelineFirstTimestamp;
|
|
1160
|
+
const wrappedDrift = modulo(drift, loopSpan);
|
|
1161
|
+
return Math.min(wrappedDrift, loopSpan - wrappedDrift);
|
|
1149
1162
|
}
|
|
1150
1163
|
function createLoadPlan(requestedStartTime, requestedEndTime) {
|
|
1151
1164
|
const startTime = Math.min(requestedStartTime, requestedEndTime);
|
|
1152
1165
|
const endTime = Math.max(startTime, requestedEndTime);
|
|
1153
1166
|
if (!isLoopingTimeline()) {
|
|
1154
|
-
const clampedStartTime = Math.max(
|
|
1167
|
+
const clampedStartTime = Math.max(timelineFirstTimestamp, startTime);
|
|
1155
1168
|
const clampedEndTime = Math.max(clampedStartTime, endTime);
|
|
1156
1169
|
return {
|
|
1157
1170
|
endTime: clampedEndTime,
|
|
@@ -1165,22 +1178,25 @@ function createBufferedDetectionTimeline(options) {
|
|
|
1165
1178
|
};
|
|
1166
1179
|
}
|
|
1167
1180
|
const duration = timelineContext.duration ?? 0;
|
|
1168
|
-
|
|
1181
|
+
const loopSpan = duration - timelineFirstTimestamp;
|
|
1182
|
+
if (endTime - startTime >= loopSpan) {
|
|
1169
1183
|
return {
|
|
1170
1184
|
endTime: duration,
|
|
1171
|
-
sourceRanges: [
|
|
1172
|
-
|
|
1185
|
+
sourceRanges: [
|
|
1186
|
+
{ endTime: duration, startTime: timelineFirstTimestamp },
|
|
1187
|
+
],
|
|
1188
|
+
startTime: timelineFirstTimestamp,
|
|
1173
1189
|
};
|
|
1174
1190
|
}
|
|
1175
1191
|
// Both ends move by the same whole number of laps, so the window keeps its
|
|
1176
1192
|
// span and its source ranges while its start reads on the media clock: a
|
|
1177
1193
|
// window counted in the laps playback accumulated is one no host can hold
|
|
1178
1194
|
// against a current time.
|
|
1179
|
-
const laps = Math.floor(startTime /
|
|
1195
|
+
const laps = Math.floor((startTime - timelineFirstTimestamp) / loopSpan);
|
|
1180
1196
|
return {
|
|
1181
|
-
endTime: endTime - laps *
|
|
1182
|
-
sourceRanges: getLoopingSourceRanges(startTime, endTime, duration),
|
|
1183
|
-
startTime: startTime - laps *
|
|
1197
|
+
endTime: endTime - laps * loopSpan,
|
|
1198
|
+
sourceRanges: getLoopingSourceRanges(startTime, endTime, duration, timelineFirstTimestamp),
|
|
1199
|
+
startTime: startTime - laps * loopSpan,
|
|
1184
1200
|
};
|
|
1185
1201
|
}
|
|
1186
1202
|
function getBufferedSourceRanges() {
|
|
@@ -1192,7 +1208,7 @@ function createBufferedDetectionTimeline(options) {
|
|
|
1192
1208
|
function isLoopingTimeline() {
|
|
1193
1209
|
return (timelineContext.loop &&
|
|
1194
1210
|
timelineContext.duration !== null &&
|
|
1195
|
-
timelineContext.duration >
|
|
1211
|
+
timelineContext.duration > timelineFirstTimestamp);
|
|
1196
1212
|
}
|
|
1197
1213
|
function getComparableMediaTime(mediaTime) {
|
|
1198
1214
|
if (!isLoopingTimeline() ||
|
|
@@ -1201,23 +1217,42 @@ function createBufferedDetectionTimeline(options) {
|
|
|
1201
1217
|
timelineContext.duration === null) {
|
|
1202
1218
|
return mediaTime;
|
|
1203
1219
|
}
|
|
1204
|
-
const
|
|
1205
|
-
// The representative of mediaTime (mod
|
|
1220
|
+
const loopSpan = timelineContext.duration - timelineFirstTimestamp;
|
|
1221
|
+
// The representative of mediaTime (mod the loop span) anchored at the window
|
|
1206
1222
|
// start. The mapping must depend only on where the window sits, never on
|
|
1207
1223
|
// how many laps playback has accumulated, or membership drifts away from
|
|
1208
1224
|
// what the buffer actually holds. A time less than one lap past the
|
|
1209
1225
|
// anchor never wraps, so ordinary forward playback cannot ratchet.
|
|
1210
1226
|
return (state.bufferStartTime +
|
|
1211
|
-
modulo(mediaTime - state.bufferStartTime,
|
|
1227
|
+
modulo(mediaTime - state.bufferStartTime, loopSpan));
|
|
1212
1228
|
}
|
|
1213
1229
|
function getSourceMediaTime(mediaTime) {
|
|
1214
1230
|
if (!isLoopingTimeline() || timelineContext.duration === null) {
|
|
1215
1231
|
return mediaTime;
|
|
1216
1232
|
}
|
|
1217
|
-
if (mediaTime >=
|
|
1233
|
+
if (mediaTime >= timelineFirstTimestamp &&
|
|
1234
|
+
mediaTime <= timelineContext.duration) {
|
|
1218
1235
|
return mediaTime;
|
|
1219
1236
|
}
|
|
1220
|
-
|
|
1237
|
+
const loopSpan = timelineContext.duration - timelineFirstTimestamp;
|
|
1238
|
+
return (timelineFirstTimestamp +
|
|
1239
|
+
modulo(mediaTime - timelineFirstTimestamp, loopSpan));
|
|
1240
|
+
}
|
|
1241
|
+
function getNavigationMediaTime(mediaTime) {
|
|
1242
|
+
return Math.max(timelineFirstTimestamp, mediaTime);
|
|
1243
|
+
}
|
|
1244
|
+
function adoptFirstTimestamp(firstTimestamp) {
|
|
1245
|
+
if (firstTimestamp === undefined ||
|
|
1246
|
+
!Number.isFinite(firstTimestamp) ||
|
|
1247
|
+
firstTimestamp === timelineFirstTimestamp) {
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
timelineFirstTimestamp = firstTimestamp;
|
|
1251
|
+
loadId += 1;
|
|
1252
|
+
inFlight = undefined;
|
|
1253
|
+
pendingPrefetch = undefined;
|
|
1254
|
+
bufferedSourceVersion = null;
|
|
1255
|
+
bufferedVersionRange = null;
|
|
1221
1256
|
}
|
|
1222
1257
|
}
|
|
1223
1258
|
function createIdleDetectionBufferState() {
|
|
@@ -1248,13 +1283,14 @@ function getRequiredCoverageEndTime(options) {
|
|
|
1248
1283
|
if (options.duration === null || options.duration === undefined) {
|
|
1249
1284
|
return requestedEndTime;
|
|
1250
1285
|
}
|
|
1251
|
-
return Math.min(requestedEndTime, (options.firstTimestamp ?? 0
|
|
1286
|
+
return Math.min(requestedEndTime, Math.max(options.firstTimestamp ?? 0, options.duration));
|
|
1252
1287
|
}
|
|
1253
|
-
function getLoopingSourceRanges(startTime, endTime, duration) {
|
|
1254
|
-
const
|
|
1255
|
-
const
|
|
1256
|
-
const
|
|
1257
|
-
const
|
|
1288
|
+
function getLoopingSourceRanges(startTime, endTime, duration, firstTimestamp = 0) {
|
|
1289
|
+
const loopSpan = duration - firstTimestamp;
|
|
1290
|
+
const normalizedStartTime = firstTimestamp + modulo(startTime - firstTimestamp, loopSpan);
|
|
1291
|
+
const normalizedEndTime = firstTimestamp + modulo(endTime - firstTimestamp, loopSpan);
|
|
1292
|
+
const startCycle = Math.floor((startTime - firstTimestamp) / loopSpan);
|
|
1293
|
+
const endCycle = Math.floor((endTime - firstTimestamp) / loopSpan);
|
|
1258
1294
|
if (startCycle === endCycle) {
|
|
1259
1295
|
return [{ endTime: normalizedEndTime, startTime: normalizedStartTime }];
|
|
1260
1296
|
}
|
|
@@ -1262,8 +1298,8 @@ function getLoopingSourceRanges(startTime, endTime, duration) {
|
|
|
1262
1298
|
if (normalizedStartTime < duration) {
|
|
1263
1299
|
ranges.push({ endTime: duration, startTime: normalizedStartTime });
|
|
1264
1300
|
}
|
|
1265
|
-
if (normalizedEndTime >
|
|
1266
|
-
ranges.push({ endTime: normalizedEndTime, startTime:
|
|
1301
|
+
if (normalizedEndTime > firstTimestamp) {
|
|
1302
|
+
ranges.push({ endTime: normalizedEndTime, startTime: firstTimestamp });
|
|
1267
1303
|
}
|
|
1268
1304
|
return ranges;
|
|
1269
1305
|
}
|