supervision 0.2.0-next.1 → 0.2.0-next.3
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 +557 -134
- 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 +8 -0
- package/dist/media/media-source.d.ts.map +1 -1
- package/dist/media/mediabunny-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 +7 -2
- package/dist/web-video-engine/decode-scheduler.d.ts +9 -3
- 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 +10 -0
- package/dist/web-video-engine/engine.js +131 -15
- package/dist/web-video-engine/engine.worker.js +513 -36
- 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 +6 -5
- package/dist/web-video-engine/video-engine.d.ts +16 -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
|
@@ -26172,6 +26172,19 @@
|
|
|
26172
26172
|
return queued.timestampS;
|
|
26173
26173
|
return this.reachable;
|
|
26174
26174
|
}
|
|
26175
|
+
async resizeOutput({ width, height }) {
|
|
26176
|
+
await this.exclusive(async () => {
|
|
26177
|
+
if (this.closed || this.outputWidth === undefined)
|
|
26178
|
+
return;
|
|
26179
|
+
if (this.outputWidth === width && this.outputHeight === height)
|
|
26180
|
+
return;
|
|
26181
|
+
this.quiesce();
|
|
26182
|
+
this.outputWidth = width;
|
|
26183
|
+
this.outputHeight = height;
|
|
26184
|
+
this.exhausted = true;
|
|
26185
|
+
this.epoch++;
|
|
26186
|
+
});
|
|
26187
|
+
}
|
|
26175
26188
|
/** The frame at or before `targetS`, or null when nothing precedes it. */
|
|
26176
26189
|
async frameAt(targetS) {
|
|
26177
26190
|
const landed = await this.exclusive(() => this.land(targetS));
|
|
@@ -26701,6 +26714,25 @@
|
|
|
26701
26714
|
function nativeResolution() {
|
|
26702
26715
|
return { kind: "native" };
|
|
26703
26716
|
}
|
|
26717
|
+
/**
|
|
26718
|
+
* Decode at the size the frame will actually occupy inside a box the consumer
|
|
26719
|
+
* describes: native aspect fitted into boxWidth x boxHeight, times the clamped
|
|
26720
|
+
* device pixel ratio. Letterboxing is why the box is two numbers and not one,
|
|
26721
|
+
* since a portrait source in a landscape box paints far narrower than the box.
|
|
26722
|
+
*
|
|
26723
|
+
* This is the strategy for presentation "frames". There the consumer owns the
|
|
26724
|
+
* compositor, so it is the only side that knows the box, and viewportResolution
|
|
26725
|
+
* has nothing to read.
|
|
26726
|
+
*/
|
|
26727
|
+
function displayBoxResolution(options) {
|
|
26728
|
+
return {
|
|
26729
|
+
kind: "displayBox",
|
|
26730
|
+
boxWidth: options.boxWidth,
|
|
26731
|
+
boxHeight: options.boxHeight,
|
|
26732
|
+
devicePixelRatio: options.devicePixelRatio,
|
|
26733
|
+
maxDevicePixelRatio: options.maxDevicePixelRatio ?? 2,
|
|
26734
|
+
};
|
|
26735
|
+
}
|
|
26704
26736
|
/** Runs the descriptor against a context to produce a target decode width. */
|
|
26705
26737
|
function strategyWidth(strategy, ctx) {
|
|
26706
26738
|
switch (strategy.kind) {
|
|
@@ -27120,6 +27152,9 @@
|
|
|
27120
27152
|
? sampleAtPresentationTime(sample, timeline.fromSourceTime(sample.timestamp))
|
|
27121
27153
|
: null;
|
|
27122
27154
|
return {
|
|
27155
|
+
...(source.resizeOutput
|
|
27156
|
+
? { resizeOutput: source.resizeOutput.bind(source) }
|
|
27157
|
+
: {}),
|
|
27123
27158
|
async frameAt(targetS) {
|
|
27124
27159
|
return wrap(await source.frameAt(timeline.toSourceTime(targetS)));
|
|
27125
27160
|
},
|
|
@@ -27172,6 +27207,7 @@
|
|
|
27172
27207
|
};
|
|
27173
27208
|
return {
|
|
27174
27209
|
decodePath: "sample",
|
|
27210
|
+
resizeOutput: async () => undefined,
|
|
27175
27211
|
// Pool-bound: every outstanding frame is a slot the sink cannot refill.
|
|
27176
27212
|
playReadAhead: 1,
|
|
27177
27213
|
track: handle.track,
|
|
@@ -27207,6 +27243,9 @@
|
|
|
27207
27243
|
function sessionProvider(handle) {
|
|
27208
27244
|
return {
|
|
27209
27245
|
decodePath: "session",
|
|
27246
|
+
...(handle.session.resizeOutput
|
|
27247
|
+
? { resizeOutput: handle.session.resizeOutput.bind(handle.session) }
|
|
27248
|
+
: {}),
|
|
27210
27249
|
playReadAhead: PLAYBACK.READ_AHEAD_CANVAS,
|
|
27211
27250
|
track: handle.track,
|
|
27212
27251
|
keyframeProbe: handle.keyframeProbe,
|
|
@@ -27630,6 +27669,8 @@
|
|
|
27630
27669
|
class CanvasSinkScrubCursor {
|
|
27631
27670
|
provider;
|
|
27632
27671
|
trackInfo;
|
|
27672
|
+
outputGeneration = 0;
|
|
27673
|
+
outputReads = new Set();
|
|
27633
27674
|
disposeSource;
|
|
27634
27675
|
/**
|
|
27635
27676
|
* Forward-playback iterator. Lives only between attachPlay and detachPlay so
|
|
@@ -27669,6 +27710,41 @@
|
|
|
27669
27710
|
this.pendingSeekTargetS === null &&
|
|
27670
27711
|
!this.seekDraining);
|
|
27671
27712
|
}
|
|
27713
|
+
async resizeOutput({ width, height }) {
|
|
27714
|
+
if (!this.provider.resizeOutput) {
|
|
27715
|
+
throw new WebVideoEngineError(WebVideoEngineErrorCode.PresentationMismatch, "this decode source cannot resize frame output in place");
|
|
27716
|
+
}
|
|
27717
|
+
if (this.closed ||
|
|
27718
|
+
(width === this.trackInfo.decodeWidth &&
|
|
27719
|
+
height === this.trackInfo.decodeHeight))
|
|
27720
|
+
return false;
|
|
27721
|
+
this.outputGeneration++;
|
|
27722
|
+
this.pendingSeekTargetS = null;
|
|
27723
|
+
const returning = this.iterator?.return();
|
|
27724
|
+
this.iterator = null;
|
|
27725
|
+
this.nextPending = 0;
|
|
27726
|
+
this.lastEmittedFrame = null;
|
|
27727
|
+
await Promise.allSettled([
|
|
27728
|
+
...this.outputReads,
|
|
27729
|
+
...(returning ? [returning] : []),
|
|
27730
|
+
]);
|
|
27731
|
+
if (this.closed)
|
|
27732
|
+
return false;
|
|
27733
|
+
await this.provider.resizeOutput({ width, height });
|
|
27734
|
+
if (this.closed)
|
|
27735
|
+
return false;
|
|
27736
|
+
this.trackInfo = {
|
|
27737
|
+
...this.trackInfo,
|
|
27738
|
+
decodeWidth: width,
|
|
27739
|
+
decodeHeight: height,
|
|
27740
|
+
};
|
|
27741
|
+
return true;
|
|
27742
|
+
}
|
|
27743
|
+
trackOutputRead(read) {
|
|
27744
|
+
this.outputReads.add(read);
|
|
27745
|
+
void read.then(() => this.outputReads.delete(read), () => this.outputReads.delete(read));
|
|
27746
|
+
return read;
|
|
27747
|
+
}
|
|
27672
27748
|
async open() {
|
|
27673
27749
|
// Seed the first paint via the same random-access path used for every
|
|
27674
27750
|
// seek. Cheaper than a one-shot iterator just for the seed. Seed at the
|
|
@@ -27694,12 +27770,20 @@
|
|
|
27694
27770
|
* lands on that frame and no other. Nothing here walks, compares or skips,
|
|
27695
27771
|
* so a step across a frame boundary needs no epsilon.
|
|
27696
27772
|
*/
|
|
27697
|
-
async seekToFrame(frame) {
|
|
27773
|
+
async seekToFrame(frame, isPresentationCurrent) {
|
|
27698
27774
|
if (this.closed)
|
|
27699
27775
|
return null;
|
|
27700
|
-
|
|
27701
|
-
|
|
27776
|
+
this.outputGeneration += 1;
|
|
27777
|
+
const outputGeneration = this.outputGeneration;
|
|
27778
|
+
const decoded = await this.trackOutputRead(this.provider.getFrame(this.trackInfo.timeline.timeAt(frame.index)));
|
|
27779
|
+
if (this.closed ||
|
|
27780
|
+
outputGeneration !== this.outputGeneration ||
|
|
27781
|
+
isPresentationCurrent?.() === false ||
|
|
27782
|
+
!decoded) {
|
|
27783
|
+
if (decoded?.kind === "sample")
|
|
27784
|
+
decoded.sample.close();
|
|
27702
27785
|
return null;
|
|
27786
|
+
}
|
|
27703
27787
|
this.emitFrame(decoded);
|
|
27704
27788
|
return this.lastEmittedFrame;
|
|
27705
27789
|
}
|
|
@@ -27727,11 +27811,13 @@
|
|
|
27727
27811
|
attachPlay(startS) {
|
|
27728
27812
|
if (this.closed)
|
|
27729
27813
|
return;
|
|
27730
|
-
|
|
27814
|
+
if (this.iterator)
|
|
27815
|
+
void this.trackOutputRead(this.iterator.return());
|
|
27731
27816
|
this.iterator = this.provider.frames(startS);
|
|
27732
27817
|
}
|
|
27733
27818
|
detachPlay() {
|
|
27734
|
-
|
|
27819
|
+
if (this.iterator)
|
|
27820
|
+
void this.trackOutputRead(this.iterator.return());
|
|
27735
27821
|
this.iterator = null;
|
|
27736
27822
|
}
|
|
27737
27823
|
next() {
|
|
@@ -27776,6 +27862,7 @@
|
|
|
27776
27862
|
}
|
|
27777
27863
|
async close() {
|
|
27778
27864
|
this.closed = true;
|
|
27865
|
+
this.outputReads.clear();
|
|
27779
27866
|
this.pendingSeekTargetS = null;
|
|
27780
27867
|
this.currentState = ScrubCursorState.Closed;
|
|
27781
27868
|
this.listeners.clear();
|
|
@@ -27809,11 +27896,14 @@
|
|
|
27809
27896
|
async runSeek(t, keyOnly) {
|
|
27810
27897
|
if (this.closed)
|
|
27811
27898
|
return;
|
|
27899
|
+
const outputGeneration = this.outputGeneration;
|
|
27812
27900
|
this.currentState = ScrubCursorState.Seeking;
|
|
27813
|
-
const frame = await this.provider.getFrame(t);
|
|
27814
|
-
if (this.closed)
|
|
27815
|
-
|
|
27816
|
-
|
|
27901
|
+
const frame = await this.trackOutputRead(this.provider.getFrame(t));
|
|
27902
|
+
if (this.closed || outputGeneration !== this.outputGeneration) {
|
|
27903
|
+
if (frame?.kind === "sample")
|
|
27904
|
+
frame.sample.close();
|
|
27905
|
+
}
|
|
27906
|
+
else if (frame)
|
|
27817
27907
|
this.emitFrame(frame, keyOnly);
|
|
27818
27908
|
if (!this.closed)
|
|
27819
27909
|
this.currentState = ScrubCursorState.Idle;
|
|
@@ -27826,7 +27916,7 @@
|
|
|
27826
27916
|
// otherwise we would race the iterator state.
|
|
27827
27917
|
if (this.seekDraining)
|
|
27828
27918
|
return;
|
|
27829
|
-
const next = (await iterator.next()).value ?? null;
|
|
27919
|
+
const next = (await this.trackOutputRead(iterator.next())).value ?? null;
|
|
27830
27920
|
if (this.closed || iterator !== this.iterator || !next) {
|
|
27831
27921
|
if (next?.kind === "sample")
|
|
27832
27922
|
next.sample.close();
|
|
@@ -27910,6 +28000,7 @@
|
|
|
27910
28000
|
FrameTier["Preview"] = "preview";
|
|
27911
28001
|
})(FrameTier || (FrameTier = {}));
|
|
27912
28002
|
class FrameCache {
|
|
28003
|
+
options;
|
|
27913
28004
|
exact;
|
|
27914
28005
|
preview;
|
|
27915
28006
|
exactHits = 0;
|
|
@@ -27918,6 +28009,7 @@
|
|
|
27918
28009
|
bucketMs;
|
|
27919
28010
|
exactBudgetBytes;
|
|
27920
28011
|
constructor(options) {
|
|
28012
|
+
this.options = options;
|
|
27921
28013
|
const exactWidth = Math.max(1, Math.round(options.exactWidth));
|
|
27922
28014
|
const exactHeight = Math.max(1, Math.round(options.exactHeight));
|
|
27923
28015
|
const aspect = exactWidth / exactHeight;
|
|
@@ -27937,6 +28029,13 @@
|
|
|
27937
28029
|
this.exact = new TierStore(exactCapacity, exactWidth, exactHeight, FRAME_IDENTITY_KEY_GRID_MS);
|
|
27938
28030
|
this.preview = new TierStore(Math.max(0, Math.floor(options.previewCapacity)), previewWidth, previewHeight, PREVIEW_KEY_GRID_MS);
|
|
27939
28031
|
}
|
|
28032
|
+
resized(width, height) {
|
|
28033
|
+
return new FrameCache({
|
|
28034
|
+
...this.options,
|
|
28035
|
+
exactWidth: width,
|
|
28036
|
+
exactHeight: height,
|
|
28037
|
+
});
|
|
28038
|
+
}
|
|
27940
28039
|
/**
|
|
27941
28040
|
* Store a crisp full-resolution frame for the exact (scrub) tier under
|
|
27942
28041
|
* `frame`, the timeline's name for it. `timestampMs` is the decoded
|
|
@@ -28663,6 +28762,9 @@
|
|
|
28663
28762
|
cache;
|
|
28664
28763
|
keyframeIndex;
|
|
28665
28764
|
trackInfo;
|
|
28765
|
+
outputGeneration = 0;
|
|
28766
|
+
resizingOutput = false;
|
|
28767
|
+
outputReads = new Set();
|
|
28666
28768
|
exactTolMs;
|
|
28667
28769
|
previewTolMs;
|
|
28668
28770
|
now;
|
|
@@ -28777,6 +28879,47 @@
|
|
|
28777
28879
|
this.pendingSeekTargetS === null &&
|
|
28778
28880
|
!this.seekDraining);
|
|
28779
28881
|
}
|
|
28882
|
+
async resizeOutput({ width, height }) {
|
|
28883
|
+
if (!this.provider.resizeOutput) {
|
|
28884
|
+
throw new WebVideoEngineError(WebVideoEngineErrorCode.PresentationMismatch, "this decode source cannot resize frame output in place");
|
|
28885
|
+
}
|
|
28886
|
+
if (this.closed ||
|
|
28887
|
+
(width === this.trackInfo.decodeWidth &&
|
|
28888
|
+
height === this.trackInfo.decodeHeight))
|
|
28889
|
+
return false;
|
|
28890
|
+
this.outputGeneration++;
|
|
28891
|
+
this.resizingOutput = true;
|
|
28892
|
+
this.pendingSeekTargetS = null;
|
|
28893
|
+
const returning = this.iterator?.return();
|
|
28894
|
+
this.iterator = null;
|
|
28895
|
+
this.nextPending = 0;
|
|
28896
|
+
this.abandonPrefetch();
|
|
28897
|
+
this.lastEmittedFrame = null;
|
|
28898
|
+
try {
|
|
28899
|
+
await this.prefetchTask?.catch(() => undefined);
|
|
28900
|
+
await Promise.allSettled([
|
|
28901
|
+
...this.outputReads,
|
|
28902
|
+
...(returning ? [returning] : []),
|
|
28903
|
+
]);
|
|
28904
|
+
if (this.closed)
|
|
28905
|
+
return false;
|
|
28906
|
+
await this.provider.resizeOutput({ width, height });
|
|
28907
|
+
if (this.closed)
|
|
28908
|
+
return false;
|
|
28909
|
+
this.trackInfo = {
|
|
28910
|
+
...this.trackInfo,
|
|
28911
|
+
decodeWidth: width,
|
|
28912
|
+
decodeHeight: height,
|
|
28913
|
+
};
|
|
28914
|
+
const oldCache = this.cache;
|
|
28915
|
+
this.cache = oldCache.resized(width, height);
|
|
28916
|
+
oldCache.clear();
|
|
28917
|
+
return true;
|
|
28918
|
+
}
|
|
28919
|
+
finally {
|
|
28920
|
+
this.resizingOutput = false;
|
|
28921
|
+
}
|
|
28922
|
+
}
|
|
28780
28923
|
async open() {
|
|
28781
28924
|
// Seed at the track's real first timestamp, not a hardcoded 0: a trimmed
|
|
28782
28925
|
// or offset clip's first frame is not at the origin, so getFrame(0) would
|
|
@@ -28996,7 +29139,9 @@
|
|
|
28996
29139
|
this.enterMode(AccessMode.Playing);
|
|
28997
29140
|
// Signal any running sweep to bail; the first pull awaits its teardown.
|
|
28998
29141
|
this.abandonPrefetch();
|
|
28999
|
-
|
|
29142
|
+
if (this.iterator) {
|
|
29143
|
+
void this.trackOutputRead(this.iterator.return());
|
|
29144
|
+
}
|
|
29000
29145
|
this.iterator = this.provider.frames(startS);
|
|
29001
29146
|
// Reset the in-flight latch for the new session. An old-iterator pull may
|
|
29002
29147
|
// still be settling; its finally is iterator-identity-aware (see next()),
|
|
@@ -29008,7 +29153,9 @@
|
|
|
29008
29153
|
this.nextInFlight = Promise.resolve();
|
|
29009
29154
|
}
|
|
29010
29155
|
detachPlay() {
|
|
29011
|
-
|
|
29156
|
+
if (this.iterator) {
|
|
29157
|
+
void this.trackOutputRead(this.iterator.return());
|
|
29158
|
+
}
|
|
29012
29159
|
this.iterator = null;
|
|
29013
29160
|
// A pull in flight at detach never decrements, since its finally only
|
|
29014
29161
|
// fires for the iterator it was issued against. Left stranded, the count
|
|
@@ -29051,13 +29198,18 @@
|
|
|
29051
29198
|
* reuses whatever decode position the session already holds, with no
|
|
29052
29199
|
* iterator opened per press.
|
|
29053
29200
|
*/
|
|
29054
|
-
async seekToFrame(frame) {
|
|
29201
|
+
async seekToFrame(frame, isPresentationCurrent) {
|
|
29055
29202
|
if (this.closed)
|
|
29056
29203
|
return null;
|
|
29204
|
+
this.outputGeneration += 1;
|
|
29057
29205
|
this.enterMode(AccessMode.Stepping);
|
|
29058
29206
|
this.abandonPrefetch();
|
|
29207
|
+
const outputGeneration = this.outputGeneration;
|
|
29059
29208
|
const decoded = await this.withDecodeWatchdog(this.provider.getFrame(this.trackInfo.timeline.timeAt(frame.index)));
|
|
29060
|
-
if (this.closed ||
|
|
29209
|
+
if (this.closed ||
|
|
29210
|
+
outputGeneration !== this.outputGeneration ||
|
|
29211
|
+
isPresentationCurrent?.() === false ||
|
|
29212
|
+
!decoded) {
|
|
29061
29213
|
if (decoded?.kind === "sample")
|
|
29062
29214
|
decoded.sample.close();
|
|
29063
29215
|
return null;
|
|
@@ -29090,6 +29242,7 @@
|
|
|
29090
29242
|
}
|
|
29091
29243
|
async close() {
|
|
29092
29244
|
this.closed = true;
|
|
29245
|
+
this.outputReads.clear();
|
|
29093
29246
|
this.abandonPrefetch();
|
|
29094
29247
|
this.pendingSeekTargetS = null;
|
|
29095
29248
|
this.currentState = ScrubCursorState.Closed;
|
|
@@ -29168,6 +29321,7 @@
|
|
|
29168
29321
|
}
|
|
29169
29322
|
}
|
|
29170
29323
|
async runExactSeek(t) {
|
|
29324
|
+
const outputGeneration = this.outputGeneration;
|
|
29171
29325
|
this.exactSeeks += 1;
|
|
29172
29326
|
const ms = t * 1000;
|
|
29173
29327
|
const cached = this.cache.getForFrame(this.frameAtTime(t), ms, this.previewTolMs);
|
|
@@ -29188,7 +29342,7 @@
|
|
|
29188
29342
|
const previewPaintedAt = painted && cached.tier === FrameTier.Preview ? this.now() : null;
|
|
29189
29343
|
const startedAt = this.now();
|
|
29190
29344
|
const frame = await this.withDecodeWatchdog(this.provider.getFrame(t));
|
|
29191
|
-
if (this.closed || !frame) {
|
|
29345
|
+
if (this.closed || outputGeneration !== this.outputGeneration || !frame) {
|
|
29192
29346
|
if (frame?.kind === "sample")
|
|
29193
29347
|
frame.sample.close();
|
|
29194
29348
|
return;
|
|
@@ -29255,13 +29409,14 @@
|
|
|
29255
29409
|
: null, targetMs);
|
|
29256
29410
|
}
|
|
29257
29411
|
async runKeySeek(t) {
|
|
29412
|
+
const outputGeneration = this.outputGeneration;
|
|
29258
29413
|
this.keySeeks += 1;
|
|
29259
29414
|
// getFrame already anchors on the enclosing keyframe, so re-anchoring the
|
|
29260
29415
|
// request collapses every target within a GOP onto that one frame. A
|
|
29261
29416
|
// source carrying a single keyframe answers its whole timeline with the
|
|
29262
29417
|
// first frame.
|
|
29263
29418
|
const frame = await this.withDecodeWatchdog(this.provider.getFrame(t));
|
|
29264
|
-
if (this.closed || !frame) {
|
|
29419
|
+
if (this.closed || outputGeneration !== this.outputGeneration || !frame) {
|
|
29265
29420
|
if (frame?.kind === "sample")
|
|
29266
29421
|
frame.sample.close();
|
|
29267
29422
|
return;
|
|
@@ -29319,7 +29474,12 @@
|
|
|
29319
29474
|
* decoder having a bad moment from one that will never answer again.
|
|
29320
29475
|
*/
|
|
29321
29476
|
withDecodeWatchdog(decode) {
|
|
29322
|
-
return this.watchdogged(decode, true);
|
|
29477
|
+
return this.watchdogged(this.trackOutputRead(decode), true);
|
|
29478
|
+
}
|
|
29479
|
+
trackOutputRead(read) {
|
|
29480
|
+
this.outputReads.add(read);
|
|
29481
|
+
void read.then(() => this.outputReads.delete(read), () => this.outputReads.delete(read));
|
|
29482
|
+
return read;
|
|
29323
29483
|
}
|
|
29324
29484
|
/**
|
|
29325
29485
|
* The same watchdog over a container metadata read. Deliberately unbooked:
|
|
@@ -29447,6 +29607,11 @@
|
|
|
29447
29607
|
return;
|
|
29448
29608
|
}
|
|
29449
29609
|
this.provider = openFrameProvider(handle);
|
|
29610
|
+
this.outputReads.clear();
|
|
29611
|
+
await this.provider.resizeOutput?.({
|
|
29612
|
+
width: this.trackInfo.decodeWidth,
|
|
29613
|
+
height: this.trackInfo.decodeHeight,
|
|
29614
|
+
});
|
|
29450
29615
|
// Disposing the old input invalidated the old probe, so the index
|
|
29451
29616
|
// has to be rebuilt against the new one. It starts empty, and
|
|
29452
29617
|
// nothing else re-walks it: the source's keyframes are exactly
|
|
@@ -29527,6 +29692,8 @@
|
|
|
29527
29692
|
* is never used twice at once, then runs under a captured generation token.
|
|
29528
29693
|
*/
|
|
29529
29694
|
schedulePrefetch(aroundS) {
|
|
29695
|
+
if (this.resizingOutput)
|
|
29696
|
+
return;
|
|
29530
29697
|
if (this.mode === AccessMode.Playing)
|
|
29531
29698
|
return;
|
|
29532
29699
|
this.abandonPrefetch();
|
|
@@ -29639,7 +29806,7 @@
|
|
|
29639
29806
|
}
|
|
29640
29807
|
}
|
|
29641
29808
|
finally {
|
|
29642
|
-
void iter.return();
|
|
29809
|
+
void this.trackOutputRead(iter.return());
|
|
29643
29810
|
}
|
|
29644
29811
|
}
|
|
29645
29812
|
/**
|
|
@@ -31373,6 +31540,17 @@ ${VERTEX_STAGE}
|
|
|
31373
31540
|
* crosses the duration.
|
|
31374
31541
|
*/
|
|
31375
31542
|
beginPlay(startS) {
|
|
31543
|
+
const { timeline } = this.deps.cursor.track;
|
|
31544
|
+
if (this.playSessionLive &&
|
|
31545
|
+
this.deps.clock.playing &&
|
|
31546
|
+
this.lastDeliveryS !== null &&
|
|
31547
|
+
!this.paintedSincePlay &&
|
|
31548
|
+
timeline.indexAtOrBefore(this.playAnchorS) ===
|
|
31549
|
+
timeline.indexAtOrBefore(startS)) {
|
|
31550
|
+
return;
|
|
31551
|
+
}
|
|
31552
|
+
if (this.lastDeliveryS !== null)
|
|
31553
|
+
this.endPlay();
|
|
31376
31554
|
this.endedFired = false;
|
|
31377
31555
|
this.playAnchorS = startS;
|
|
31378
31556
|
// The realtime needles are per-session rates, not lifetime totals: a
|
|
@@ -31401,6 +31579,7 @@ ${VERTEX_STAGE}
|
|
|
31401
31579
|
this.lastPresentTickS = null;
|
|
31402
31580
|
this.resetCadenceTracking();
|
|
31403
31581
|
this.clearPlayQueue();
|
|
31582
|
+
this.clearStash();
|
|
31404
31583
|
this.deps.cursor.attachPlay(startS);
|
|
31405
31584
|
this.deps.cursor.next();
|
|
31406
31585
|
}
|
|
@@ -31420,6 +31599,17 @@ ${VERTEX_STAGE}
|
|
|
31420
31599
|
this.resetCadenceTracking();
|
|
31421
31600
|
this.clearPlayQueue();
|
|
31422
31601
|
}
|
|
31602
|
+
holdOutput() {
|
|
31603
|
+
this.stop();
|
|
31604
|
+
this.clearStash();
|
|
31605
|
+
this.clearPlayQueue();
|
|
31606
|
+
this.deps.cursor.detachPlay();
|
|
31607
|
+
this.lastDeliveryS = null;
|
|
31608
|
+
}
|
|
31609
|
+
releaseOutput() {
|
|
31610
|
+
if (!this.disposed && this.sink)
|
|
31611
|
+
this.start();
|
|
31612
|
+
}
|
|
31423
31613
|
/**
|
|
31424
31614
|
* Cache lookup helper used by the engine on scrub. Asks the cursor for a
|
|
31425
31615
|
* cached frame near the target and, if it is far enough from what the canvas
|
|
@@ -31427,10 +31617,8 @@ ${VERTEX_STAGE}
|
|
|
31427
31617
|
* calls cursor.seekTo to land the full-res frame; the cursor's emit listener
|
|
31428
31618
|
* stashes it for the next tick.
|
|
31429
31619
|
*
|
|
31430
|
-
*
|
|
31431
|
-
*
|
|
31432
|
-
* consumers tune to control single-frame-step UX: a higher value forces a
|
|
31433
|
-
* fresh decode on tighter movements, a value of 0 always serves the cache.
|
|
31620
|
+
* Nearby approximate hits respect cacheSkipNearMs. An exact hit for the
|
|
31621
|
+
* requested frame can advance immediately, without waiting for a decode.
|
|
31434
31622
|
*/
|
|
31435
31623
|
tryPaintFromCache(timestampMs) {
|
|
31436
31624
|
if (!this.sink)
|
|
@@ -31440,7 +31628,12 @@ ${VERTEX_STAGE}
|
|
|
31440
31628
|
return false;
|
|
31441
31629
|
const hitMs = Math.round(hit.timestampS * 1000);
|
|
31442
31630
|
if (this.lastPaintedMs !== null) {
|
|
31443
|
-
|
|
31631
|
+
const { timeline } = this.deps.cursor.track;
|
|
31632
|
+
const exactTarget = hit.quality === "exact" &&
|
|
31633
|
+
timeline.indexOfDecoded(hit.timestampS) ===
|
|
31634
|
+
timeline.indexAtOrBefore(timestampMs / 1000);
|
|
31635
|
+
if (!exactTarget &&
|
|
31636
|
+
Math.abs(hitMs - this.lastPaintedMs) < this.deps.cacheSkipNearMs)
|
|
31444
31637
|
return false;
|
|
31445
31638
|
// And it has to be an improvement on what is showing, measured from
|
|
31446
31639
|
// where the user is pointing. The check above measures only distance
|
|
@@ -31456,10 +31649,30 @@ ${VERTEX_STAGE}
|
|
|
31456
31649
|
return false;
|
|
31457
31650
|
}
|
|
31458
31651
|
}
|
|
31652
|
+
return this.paintCached(hit);
|
|
31653
|
+
}
|
|
31654
|
+
/** A new commit needs its own presentation even when its exact pixels were
|
|
31655
|
+
* already shown by the preceding scrub. Approximate neighbors cannot answer it. */
|
|
31656
|
+
presentExactFromCache(timestampMs) {
|
|
31657
|
+
if (!this.sink)
|
|
31658
|
+
return false;
|
|
31659
|
+
const hit = this.deps.cursor.peekCached(timestampMs);
|
|
31660
|
+
const { timeline } = this.deps.cursor.track;
|
|
31661
|
+
if (!hit ||
|
|
31662
|
+
hit.quality !== "exact" ||
|
|
31663
|
+
timeline.indexOfDecoded(hit.timestampS) !==
|
|
31664
|
+
timeline.indexAtOrBefore(timestampMs / 1000)) {
|
|
31665
|
+
return false;
|
|
31666
|
+
}
|
|
31667
|
+
return this.paintCached(hit);
|
|
31668
|
+
}
|
|
31669
|
+
paintCached(hit) {
|
|
31670
|
+
if (!this.sink)
|
|
31671
|
+
return false;
|
|
31459
31672
|
// A cache hit is always a canvas blit, never a live sample, so there is
|
|
31460
31673
|
// no close obligation here.
|
|
31461
31674
|
const presented = this.sink.present(hit);
|
|
31462
|
-
this.lastPaintedMs =
|
|
31675
|
+
this.lastPaintedMs = Math.round(hit.timestampS * 1000);
|
|
31463
31676
|
this.lastPaintedQuality = hit.quality;
|
|
31464
31677
|
// This is a full frame on the display canvas and the user sees the
|
|
31465
31678
|
// picture change, so it is a paint. Reporting only decoded frames left
|
|
@@ -32340,6 +32553,45 @@ ${VERTEX_STAGE}
|
|
|
32340
32553
|
return residency;
|
|
32341
32554
|
}
|
|
32342
32555
|
|
|
32556
|
+
function createPausableDeadline(timeoutMs, onExpire) {
|
|
32557
|
+
let cancelled = false;
|
|
32558
|
+
let remainingMs = timeoutMs;
|
|
32559
|
+
let startedAtMs = performance.now();
|
|
32560
|
+
let timer = null;
|
|
32561
|
+
const expire = () => {
|
|
32562
|
+
if (cancelled)
|
|
32563
|
+
return;
|
|
32564
|
+
cancelled = true;
|
|
32565
|
+
timer = null;
|
|
32566
|
+
onExpire();
|
|
32567
|
+
};
|
|
32568
|
+
const arm = () => {
|
|
32569
|
+
if (cancelled || timer !== null)
|
|
32570
|
+
return;
|
|
32571
|
+
startedAtMs = performance.now();
|
|
32572
|
+
timer = setTimeout(expire, remainingMs);
|
|
32573
|
+
};
|
|
32574
|
+
arm();
|
|
32575
|
+
return {
|
|
32576
|
+
cancel() {
|
|
32577
|
+
if (cancelled)
|
|
32578
|
+
return;
|
|
32579
|
+
cancelled = true;
|
|
32580
|
+
if (timer !== null)
|
|
32581
|
+
clearTimeout(timer);
|
|
32582
|
+
timer = null;
|
|
32583
|
+
},
|
|
32584
|
+
pause() {
|
|
32585
|
+
if (cancelled || timer === null)
|
|
32586
|
+
return;
|
|
32587
|
+
remainingMs = Math.max(0, remainingMs - (performance.now() - startedAtMs));
|
|
32588
|
+
clearTimeout(timer);
|
|
32589
|
+
timer = null;
|
|
32590
|
+
},
|
|
32591
|
+
resume: arm,
|
|
32592
|
+
};
|
|
32593
|
+
}
|
|
32594
|
+
|
|
32343
32595
|
function serializeEngineError(error) {
|
|
32344
32596
|
return { code: error.code, message: error.message };
|
|
32345
32597
|
}
|
|
@@ -32359,6 +32611,8 @@ ${VERTEX_STAGE}
|
|
|
32359
32611
|
* the core never emits a competing time that would yank a settled playhead.
|
|
32360
32612
|
*/
|
|
32361
32613
|
class EngineCore {
|
|
32614
|
+
decodeStrategy = nativeResolution();
|
|
32615
|
+
displayResize = null;
|
|
32362
32616
|
emit;
|
|
32363
32617
|
emitDiagnostics;
|
|
32364
32618
|
emitPresentedFrame;
|
|
@@ -32404,6 +32658,7 @@ ${VERTEX_STAGE}
|
|
|
32404
32658
|
* flight and publishes itself as the playhead. */
|
|
32405
32659
|
awaitedSeek = null;
|
|
32406
32660
|
seekGeneration = 0;
|
|
32661
|
+
stepCommandGeneration = 0;
|
|
32407
32662
|
/** Wall stamp of a seek being served by re-anchoring the playback walk, or
|
|
32408
32663
|
* null when no such seek is waiting on its crisp frame. */
|
|
32409
32664
|
playSeekStartedAtMs = null;
|
|
@@ -32417,6 +32672,8 @@ ${VERTEX_STAGE}
|
|
|
32417
32672
|
lastHostFrame = null;
|
|
32418
32673
|
/** Rejects an older refresh that completes after a newer one in one epoch. */
|
|
32419
32674
|
lastHostPaintSeq = 0;
|
|
32675
|
+
/** Main-thread visibility pauses only the wait for host presentation. */
|
|
32676
|
+
presentationVisible = true;
|
|
32420
32677
|
/** Mirrors WebVideoEngine's interactive-seek latch: true when a drag paused a
|
|
32421
32678
|
* playing engine, so endInteractiveSeek knows to resume. */
|
|
32422
32679
|
resumeAfterInteractiveSeek = false;
|
|
@@ -32457,6 +32714,7 @@ ${VERTEX_STAGE}
|
|
|
32457
32714
|
this.abandonAwaitedSeek();
|
|
32458
32715
|
}
|
|
32459
32716
|
async load(config) {
|
|
32717
|
+
this.decodeStrategy = config.decodeStrategy ?? nativeResolution();
|
|
32460
32718
|
this.beginPresentationGeneration();
|
|
32461
32719
|
this.presentation = config.presentation ?? "canvas";
|
|
32462
32720
|
this.displayBox =
|
|
@@ -32541,11 +32799,164 @@ ${VERTEX_STAGE}
|
|
|
32541
32799
|
this.residency?.startWarming();
|
|
32542
32800
|
return this.metadata;
|
|
32543
32801
|
}
|
|
32802
|
+
setDisplay(display) {
|
|
32803
|
+
const cursor = this.cursor;
|
|
32804
|
+
if (this.presentation !== "frames" ||
|
|
32805
|
+
this.decodeStrategy.kind !== "displayBox" ||
|
|
32806
|
+
!cursor?.resizeOutput) {
|
|
32807
|
+
return Promise.reject(new WebVideoEngineError(WebVideoEngineErrorCode.PresentationMismatch, "display resizing requires frames presentation with a displayBox strategy"));
|
|
32808
|
+
}
|
|
32809
|
+
const resolved = resolveDecodeDimensions(displayBoxResolution(display), {
|
|
32810
|
+
nativeWidth: cursor.track.width,
|
|
32811
|
+
nativeHeight: cursor.track.height,
|
|
32812
|
+
displayWidth: null,
|
|
32813
|
+
devicePixelRatio: display.devicePixelRatio,
|
|
32814
|
+
});
|
|
32815
|
+
if (!this.displayResize &&
|
|
32816
|
+
resolved.width === cursor.track.decodeWidth &&
|
|
32817
|
+
resolved.height === cursor.track.decodeHeight) {
|
|
32818
|
+
this.displayBox = {
|
|
32819
|
+
cssWidth: display.boxWidth,
|
|
32820
|
+
cssHeight: display.boxHeight,
|
|
32821
|
+
devicePixelRatio: display.devicePixelRatio,
|
|
32822
|
+
};
|
|
32823
|
+
return Promise.resolve(false);
|
|
32824
|
+
}
|
|
32825
|
+
let state = this.displayResize;
|
|
32826
|
+
if (!state) {
|
|
32827
|
+
let cancel = () => { };
|
|
32828
|
+
const aborted = new Promise((resolve) => {
|
|
32829
|
+
cancel = resolve;
|
|
32830
|
+
});
|
|
32831
|
+
state = {
|
|
32832
|
+
tail: Promise.resolve(false),
|
|
32833
|
+
request: 0,
|
|
32834
|
+
navigation: 0,
|
|
32835
|
+
pendingNavigation: false,
|
|
32836
|
+
held: false,
|
|
32837
|
+
cancelled: false,
|
|
32838
|
+
cancel,
|
|
32839
|
+
aborted,
|
|
32840
|
+
};
|
|
32841
|
+
this.displayResize = state;
|
|
32842
|
+
}
|
|
32843
|
+
const active = state;
|
|
32844
|
+
const request = ++active.request;
|
|
32845
|
+
const previous = active.tail;
|
|
32846
|
+
if (active.held)
|
|
32847
|
+
this.abandonAwaitedSeek();
|
|
32848
|
+
const run = (async () => {
|
|
32849
|
+
await previous.catch((error) => {
|
|
32850
|
+
if (active.cancelled || !isResizeSuperseded(error))
|
|
32851
|
+
throw error;
|
|
32852
|
+
});
|
|
32853
|
+
if (active.cancelled)
|
|
32854
|
+
throw displayResizeAborted();
|
|
32855
|
+
if (request !== active.request || active.pendingNavigation)
|
|
32856
|
+
throw displayResizeSuperseded();
|
|
32857
|
+
const dimensions = resolved;
|
|
32858
|
+
this.displayBox = {
|
|
32859
|
+
cssWidth: display.boxWidth,
|
|
32860
|
+
cssHeight: display.boxHeight,
|
|
32861
|
+
devicePixelRatio: display.devicePixelRatio,
|
|
32862
|
+
};
|
|
32863
|
+
if (dimensions.width === cursor.track.decodeWidth &&
|
|
32864
|
+
dimensions.height === cursor.track.decodeHeight)
|
|
32865
|
+
return false;
|
|
32866
|
+
const target = this.lastHostFrame ??
|
|
32867
|
+
this.timeline().landingAt(this.timeline().indexAtOrBefore(this.clock.now()));
|
|
32868
|
+
this.clock.pause();
|
|
32869
|
+
this.clock.seek(target.mediaTimeS);
|
|
32870
|
+
this.controller?.holdOutput();
|
|
32871
|
+
active.held = true;
|
|
32872
|
+
this.abandonAwaitedSeek();
|
|
32873
|
+
this.beginPresentationGeneration();
|
|
32874
|
+
const untilDisposed = (work) => Promise.race([
|
|
32875
|
+
work,
|
|
32876
|
+
active.aborted.then(() => {
|
|
32877
|
+
throw displayResizeAborted();
|
|
32878
|
+
}),
|
|
32879
|
+
]);
|
|
32880
|
+
await untilDisposed(cursor.resizeOutput(dimensions));
|
|
32881
|
+
if (request !== active.request || active.pendingNavigation)
|
|
32882
|
+
throw displayResizeSuperseded();
|
|
32883
|
+
const wait = this.awaitSeekLanding(target.frame, false, true);
|
|
32884
|
+
void wait.presented.catch(() => undefined);
|
|
32885
|
+
const decoded = await untilDisposed(cursor.seekToFrame(target.frame));
|
|
32886
|
+
if (request !== active.request || active.pendingNavigation) {
|
|
32887
|
+
this.controller?.holdOutput();
|
|
32888
|
+
this.abandonAwaitedSeek();
|
|
32889
|
+
throw displayResizeSuperseded();
|
|
32890
|
+
}
|
|
32891
|
+
if (!decoded)
|
|
32892
|
+
throw new WebVideoEngineError(WebVideoEngineErrorCode.DecoderStalled, "resized frame could not be decoded");
|
|
32893
|
+
this.controller?.releaseOutput();
|
|
32894
|
+
await untilDisposed(wait.presented);
|
|
32895
|
+
if (request !== active.request || active.pendingNavigation)
|
|
32896
|
+
throw displayResizeSuperseded();
|
|
32897
|
+
return true;
|
|
32898
|
+
})();
|
|
32899
|
+
const settled = run
|
|
32900
|
+
.catch((error) => {
|
|
32901
|
+
if (active.held) {
|
|
32902
|
+
this.abandonAwaitedSeek();
|
|
32903
|
+
this.controller?.holdOutput();
|
|
32904
|
+
}
|
|
32905
|
+
if (!isResizeSuperseded(error)) {
|
|
32906
|
+
this.playing = false;
|
|
32907
|
+
if (!active.cancelled)
|
|
32908
|
+
this.emitStatus(PlaybackStatus.Paused);
|
|
32909
|
+
}
|
|
32910
|
+
throw error;
|
|
32911
|
+
})
|
|
32912
|
+
.finally(() => {
|
|
32913
|
+
if (active.tail !== settled)
|
|
32914
|
+
return;
|
|
32915
|
+
this.displayResize = null;
|
|
32916
|
+
this.controller?.releaseOutput();
|
|
32917
|
+
if ((active.held || !this.clock.playing) &&
|
|
32918
|
+
!active.cancelled &&
|
|
32919
|
+
!active.pendingNavigation &&
|
|
32920
|
+
this.playing) {
|
|
32921
|
+
this.clock.play();
|
|
32922
|
+
this.controller?.beginPlay(this.clock.now());
|
|
32923
|
+
this.emitStatus(PlaybackStatus.Playing);
|
|
32924
|
+
}
|
|
32925
|
+
});
|
|
32926
|
+
active.tail = settled;
|
|
32927
|
+
return settled;
|
|
32928
|
+
}
|
|
32929
|
+
async afterDisplayResize(run, superseded) {
|
|
32930
|
+
const state = this.displayResize;
|
|
32931
|
+
const navigation = ++state.navigation;
|
|
32932
|
+
state.pendingNavigation = true;
|
|
32933
|
+
state.held = true;
|
|
32934
|
+
this.controller?.holdOutput();
|
|
32935
|
+
this.abandonAwaitedSeek();
|
|
32936
|
+
this.beginPresentationGeneration();
|
|
32937
|
+
while (this.displayResize === state)
|
|
32938
|
+
await state.tail.catch((error) => {
|
|
32939
|
+
if (state.cancelled || !isResizeSuperseded(error))
|
|
32940
|
+
throw error;
|
|
32941
|
+
});
|
|
32942
|
+
if (state.cancelled)
|
|
32943
|
+
throw displayResizeAborted();
|
|
32944
|
+
if (navigation !== state.navigation)
|
|
32945
|
+
return superseded;
|
|
32946
|
+
if (this.playing)
|
|
32947
|
+
this.clock.play();
|
|
32948
|
+
return run();
|
|
32949
|
+
}
|
|
32544
32950
|
play() {
|
|
32545
32951
|
if (!this.cursor || !this.controller)
|
|
32546
32952
|
return;
|
|
32547
32953
|
if (this.decodeFailure)
|
|
32548
32954
|
return this.republishFailure();
|
|
32955
|
+
if (this.displayResize) {
|
|
32956
|
+
this.playing = true;
|
|
32957
|
+
return;
|
|
32958
|
+
}
|
|
32959
|
+
this.stepCommandGeneration += 1;
|
|
32549
32960
|
// Resume-from-end: a play after the clock crossed duration would tick
|
|
32550
32961
|
// straight to Ended. Snap back to 0 first.
|
|
32551
32962
|
const durS = this.durationMs / 1000;
|
|
@@ -32593,8 +33004,9 @@ ${VERTEX_STAGE}
|
|
|
32593
33004
|
});
|
|
32594
33005
|
}
|
|
32595
33006
|
}
|
|
33007
|
+
this.stepCommandGeneration += 1;
|
|
32596
33008
|
this.controller?.endPlay();
|
|
32597
|
-
if (wasPlaying)
|
|
33009
|
+
if (wasPlaying && !this.displayResize)
|
|
32598
33010
|
this.abandonAwaitedSeek();
|
|
32599
33011
|
if (this.decodeFailure)
|
|
32600
33012
|
return this.republishFailure();
|
|
@@ -32635,6 +33047,17 @@ ${VERTEX_STAGE}
|
|
|
32635
33047
|
* thread snapped the gesture and wrote it. Paints a cache preview, then
|
|
32636
33048
|
* walks the cursor. */
|
|
32637
33049
|
scrub(frameIndex, intent = "gesture") {
|
|
33050
|
+
if (this.displayResize) {
|
|
33051
|
+
void this.afterDisplayResize(() => this.scrub(frameIndex, intent), undefined).catch((error) => {
|
|
33052
|
+
if (error instanceof WebVideoEngineError &&
|
|
33053
|
+
error.code === WebVideoEngineErrorCode.Aborted)
|
|
33054
|
+
return;
|
|
33055
|
+
this.handleTerminalFailure(error instanceof WebVideoEngineError
|
|
33056
|
+
? error
|
|
33057
|
+
: new WebVideoEngineError(WebVideoEngineErrorCode.BackendCrashed, "display resize failed", error));
|
|
33058
|
+
});
|
|
33059
|
+
return;
|
|
33060
|
+
}
|
|
32638
33061
|
if (!this.cursor)
|
|
32639
33062
|
return;
|
|
32640
33063
|
const target = this.timeline().landingAt(frameIndex);
|
|
@@ -32667,7 +33090,6 @@ ${VERTEX_STAGE}
|
|
|
32667
33090
|
// crisp frame up, and then the wait is over before the walk restarts.
|
|
32668
33091
|
if (!this.awaitedSeek)
|
|
32669
33092
|
this.closePlaySeekWait();
|
|
32670
|
-
this.controller?.endPlay();
|
|
32671
33093
|
this.controller?.beginPlay(tSec);
|
|
32672
33094
|
}
|
|
32673
33095
|
/** Closes the wait of a seek served by re-anchoring, at the crisp paint that
|
|
@@ -32689,6 +33111,8 @@ ${VERTEX_STAGE}
|
|
|
32689
33111
|
this.playSeekMaxMs = waitMs;
|
|
32690
33112
|
}
|
|
32691
33113
|
async commit(frameIndex) {
|
|
33114
|
+
if (this.displayResize)
|
|
33115
|
+
return this.afterDisplayResize(() => this.commit(frameIndex), null);
|
|
32692
33116
|
if (!this.cursor)
|
|
32693
33117
|
return null;
|
|
32694
33118
|
if (this.decodeFailure)
|
|
@@ -32698,7 +33122,9 @@ ${VERTEX_STAGE}
|
|
|
32698
33122
|
this.lastStepLanded = null;
|
|
32699
33123
|
const wait = this.awaitSeekLanding(target.frame, false, true);
|
|
32700
33124
|
this.clock.seek(tSec);
|
|
32701
|
-
this.controller?.
|
|
33125
|
+
if (!this.controller?.presentExactFromCache(tSec * 1000)) {
|
|
33126
|
+
this.controller?.tryPaintFromCache(tSec * 1000);
|
|
33127
|
+
}
|
|
32702
33128
|
// While playing, moving the playhead means re-anchoring the playback
|
|
32703
33129
|
// walk, exactly as scrub does. Seeking the cursor instead leaves the walk
|
|
32704
33130
|
// where it was, so playback carries on from the old position and the seek
|
|
@@ -32746,6 +33172,8 @@ ${VERTEX_STAGE}
|
|
|
32746
33172
|
return landed;
|
|
32747
33173
|
}
|
|
32748
33174
|
async seekToKey(timeMs) {
|
|
33175
|
+
if (this.displayResize)
|
|
33176
|
+
return this.afterDisplayResize(() => this.seekToKey(timeMs), null);
|
|
32749
33177
|
if (!this.cursor)
|
|
32750
33178
|
return null;
|
|
32751
33179
|
if (this.decodeFailure)
|
|
@@ -32809,6 +33237,14 @@ ${VERTEX_STAGE}
|
|
|
32809
33237
|
* other.
|
|
32810
33238
|
*/
|
|
32811
33239
|
async step(direction) {
|
|
33240
|
+
if (this.displayResize) {
|
|
33241
|
+
return this.afterDisplayResize(async () => {
|
|
33242
|
+
const landed = await this.step(direction);
|
|
33243
|
+
if (this.playing)
|
|
33244
|
+
this.controller?.beginPlay(this.clock.now());
|
|
33245
|
+
return landed;
|
|
33246
|
+
}, null);
|
|
33247
|
+
}
|
|
32812
33248
|
if (!this.cursor || !this.controller)
|
|
32813
33249
|
return null;
|
|
32814
33250
|
const timeline = this.timeline();
|
|
@@ -32822,9 +33258,14 @@ ${VERTEX_STAGE}
|
|
|
32822
33258
|
const target = timeline.landingAt(base.index + direction);
|
|
32823
33259
|
if (target.frame.index === base.index)
|
|
32824
33260
|
return null;
|
|
32825
|
-
this.
|
|
32826
|
-
const
|
|
32827
|
-
|
|
33261
|
+
this.abandonAwaitedSeek();
|
|
33262
|
+
const commandGeneration = ++this.stepCommandGeneration;
|
|
33263
|
+
const generation = this.beginPresentationGeneration();
|
|
33264
|
+
const next = await this.cursor.seekToFrame(target.frame, () => this.isCurrentSeek(generation) &&
|
|
33265
|
+
commandGeneration === this.stepCommandGeneration);
|
|
33266
|
+
if (!next ||
|
|
33267
|
+
!this.isCurrentSeek(generation) ||
|
|
33268
|
+
commandGeneration !== this.stepCommandGeneration)
|
|
32828
33269
|
return null;
|
|
32829
33270
|
this.lastStepLanded = target.frame;
|
|
32830
33271
|
this.clock.seek(target.mediaTimeS);
|
|
@@ -32887,6 +33328,10 @@ ${VERTEX_STAGE}
|
|
|
32887
33328
|
this.play();
|
|
32888
33329
|
}
|
|
32889
33330
|
async dispose() {
|
|
33331
|
+
if (this.displayResize) {
|
|
33332
|
+
this.displayResize.cancelled = true;
|
|
33333
|
+
this.displayResize.cancel();
|
|
33334
|
+
}
|
|
32890
33335
|
this.playing = false;
|
|
32891
33336
|
this.diagnosticsStop();
|
|
32892
33337
|
// A capture still running is evidence someone asked for; keep it, marked
|
|
@@ -32957,7 +33402,7 @@ ${VERTEX_STAGE}
|
|
|
32957
33402
|
return { generation, presented: Promise.resolve(null) };
|
|
32958
33403
|
}
|
|
32959
33404
|
const presented = new Promise((resolve, reject) => {
|
|
32960
|
-
const
|
|
33405
|
+
const presentationDeadline = createPausableDeadline(HANG_RECOVERY.PRESENTATION_LATCH_TIMEOUT_MS, () => {
|
|
32961
33406
|
const awaited = this.awaitedSeek;
|
|
32962
33407
|
if (!awaited ||
|
|
32963
33408
|
awaited.generation !== generation ||
|
|
@@ -32970,20 +33415,33 @@ ${VERTEX_STAGE}
|
|
|
32970
33415
|
awaited.reject(error);
|
|
32971
33416
|
else
|
|
32972
33417
|
awaited.resolve(null);
|
|
32973
|
-
}
|
|
33418
|
+
});
|
|
32974
33419
|
const awaited = {
|
|
32975
33420
|
generation,
|
|
32976
33421
|
keyOnly,
|
|
32977
|
-
|
|
33422
|
+
presentationDeadline,
|
|
32978
33423
|
reject: (error) => reject(error),
|
|
32979
33424
|
rejectOnFailure,
|
|
32980
33425
|
resolve,
|
|
32981
33426
|
target,
|
|
32982
33427
|
};
|
|
32983
33428
|
this.awaitedSeek = awaited;
|
|
33429
|
+
if (!this.presentationVisible)
|
|
33430
|
+
presentationDeadline.pause();
|
|
32984
33431
|
});
|
|
32985
33432
|
return { generation, presented };
|
|
32986
33433
|
}
|
|
33434
|
+
/** Main-thread document visibility; decoding/watchdogs remain active. */
|
|
33435
|
+
setPresentationVisibility(visible) {
|
|
33436
|
+
this.presentationVisible = visible;
|
|
33437
|
+
const awaited = this.awaitedSeek;
|
|
33438
|
+
if (!awaited)
|
|
33439
|
+
return;
|
|
33440
|
+
if (visible)
|
|
33441
|
+
awaited.presentationDeadline.resume();
|
|
33442
|
+
else
|
|
33443
|
+
awaited.presentationDeadline.pause();
|
|
33444
|
+
}
|
|
32987
33445
|
isCurrentSeek(generation) {
|
|
32988
33446
|
return generation === this.seekGeneration;
|
|
32989
33447
|
}
|
|
@@ -32997,7 +33455,7 @@ ${VERTEX_STAGE}
|
|
|
32997
33455
|
if (!awaited)
|
|
32998
33456
|
return;
|
|
32999
33457
|
this.awaitedSeek = null;
|
|
33000
|
-
|
|
33458
|
+
awaited.presentationDeadline.cancel();
|
|
33001
33459
|
awaited.resolve(null);
|
|
33002
33460
|
}
|
|
33003
33461
|
/** Ends an impossible landing. Awaitable movement rejects with the engine's
|
|
@@ -33007,7 +33465,7 @@ ${VERTEX_STAGE}
|
|
|
33007
33465
|
if (!awaited)
|
|
33008
33466
|
return;
|
|
33009
33467
|
this.awaitedSeek = null;
|
|
33010
|
-
|
|
33468
|
+
awaited.presentationDeadline.cancel();
|
|
33011
33469
|
if (awaited.rejectOnFailure)
|
|
33012
33470
|
awaited.reject(error);
|
|
33013
33471
|
else
|
|
@@ -33104,7 +33562,7 @@ ${VERTEX_STAGE}
|
|
|
33104
33562
|
if (answeredSeek && this.awaitedSeek === answeredSeek) {
|
|
33105
33563
|
this.seekLanded = frameId;
|
|
33106
33564
|
this.awaitedSeek = null;
|
|
33107
|
-
|
|
33565
|
+
answeredSeek.presentationDeadline.cancel();
|
|
33108
33566
|
this.closePlaySeekWait();
|
|
33109
33567
|
// Paused paints normally never move the playhead, but the landing
|
|
33110
33568
|
// of an awaited seek is the one paint that says where the request
|
|
@@ -33486,6 +33944,17 @@ ${VERTEX_STAGE}
|
|
|
33486
33944
|
const wanted = Math.round((windowMs / 1000) * DIAGNOSTICS.BROADCAST_HZ);
|
|
33487
33945
|
return Math.min(DIAGNOSTICS.TRACE_SNAPSHOT_CAP, Math.max(1, wanted));
|
|
33488
33946
|
}
|
|
33947
|
+
function displayResizeAborted() {
|
|
33948
|
+
return new WebVideoEngineError(WebVideoEngineErrorCode.Aborted, "display resize aborted by disposal");
|
|
33949
|
+
}
|
|
33950
|
+
function displayResizeSuperseded() {
|
|
33951
|
+
return new WebVideoEngineError(WebVideoEngineErrorCode.Aborted, "display resize superseded");
|
|
33952
|
+
}
|
|
33953
|
+
function isResizeSuperseded(error) {
|
|
33954
|
+
return (error instanceof WebVideoEngineError &&
|
|
33955
|
+
error.code === WebVideoEngineErrorCode.Aborted &&
|
|
33956
|
+
error.message === "display resize superseded");
|
|
33957
|
+
}
|
|
33489
33958
|
|
|
33490
33959
|
function toSerializedError(error) {
|
|
33491
33960
|
if (error instanceof WebVideoEngineError)
|
|
@@ -33511,6 +33980,9 @@ ${VERTEX_STAGE}
|
|
|
33511
33980
|
case "unbindCanvas":
|
|
33512
33981
|
engine.setCanvas(null);
|
|
33513
33982
|
return;
|
|
33983
|
+
case "setPresentationVisibility":
|
|
33984
|
+
engine.setPresentationVisibility(command.visible);
|
|
33985
|
+
return;
|
|
33514
33986
|
case "scrub":
|
|
33515
33987
|
engine.scrub(command.frameIndex, command.intent);
|
|
33516
33988
|
return;
|
|
@@ -33552,6 +34024,11 @@ ${VERTEX_STAGE}
|
|
|
33552
34024
|
engine.play();
|
|
33553
34025
|
return { type: "ack", requestId: command.requestId };
|
|
33554
34026
|
});
|
|
34027
|
+
case "setDisplay":
|
|
34028
|
+
return settle(post, command.requestId, async () => {
|
|
34029
|
+
const outputChanged = await engine.setDisplay(command.display);
|
|
34030
|
+
return { type: "ack", requestId: command.requestId, outputChanged };
|
|
34031
|
+
});
|
|
33555
34032
|
case "commit":
|
|
33556
34033
|
return settle(post, command.requestId, async () => {
|
|
33557
34034
|
const landing = await engine.commit(command.frameIndex);
|