@bubo-squared/gyroview 0.1.0
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/CHANGELOG.md +32 -0
- package/LICENSE +21 -0
- package/README.md +216 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.js +4 -0
- package/dist/index.d.ts +959 -0
- package/dist/index.js +13 -0
- package/dist/packages/adapters/blob/src/BlobRandomAccessSource.js +37 -0
- package/dist/packages/adapters/blob/src/index.js +2 -0
- package/dist/packages/adapters/fetch/src/HttpRangeSource.js +194 -0
- package/dist/packages/adapters/fetch/src/HttpResourceLocator.js +35 -0
- package/dist/packages/adapters/fetch/src/httpRequest.js +126 -0
- package/dist/packages/adapters/fetch/src/index.js +3 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyAudioSegments.js +133 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyDemuxer.js +63 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyVideoTrackReader.js +84 -0
- package/dist/packages/adapters/mediabunny/src/SegmentChannel.js +84 -0
- package/dist/packages/adapters/mediabunny/src/bufferSources.js +12 -0
- package/dist/packages/adapters/mediabunny/src/index.js +2 -0
- package/dist/packages/adapters/mse-audio/src/MediaSourceAudioClock.js +139 -0
- package/dist/packages/adapters/mse-audio/src/SourceBufferFeeder.js +174 -0
- package/dist/packages/adapters/mse-audio/src/events.js +26 -0
- package/dist/packages/adapters/mse-audio/src/index.js +2 -0
- package/dist/packages/adapters/mse-audio/src/mediaSourceSupport.js +82 -0
- package/dist/packages/adapters/three/src/ThreeFrameRenderer.js +274 -0
- package/dist/packages/adapters/three/src/compileAndProve.js +14 -0
- package/dist/packages/adapters/three/src/fullscreenPass.js +52 -0
- package/dist/packages/adapters/three/src/index.js +2 -0
- package/dist/packages/adapters/three/src/lensTextures.js +25 -0
- package/dist/packages/adapters/three/src/pictureMaterials.js +44 -0
- package/dist/packages/adapters/three/src/renderInto.js +15 -0
- package/dist/packages/adapters/three/src/rendererUniforms.js +157 -0
- package/dist/packages/adapters/three/src/samplingStrategies.js +26 -0
- package/dist/packages/adapters/three/src/seamMeter/SeamMeterPass.js +79 -0
- package/dist/packages/adapters/three/src/seamMeter/rowMeans.js +38 -0
- package/dist/packages/adapters/three/src/shaderPrograms.js +66 -0
- package/dist/packages/adapters/three/src/shaders/analysis.frag.js +4 -0
- package/dist/packages/adapters/three/src/shaders/equirectangularRays.js +4 -0
- package/dist/packages/adapters/three/src/shaders/fixedJoin.js +4 -0
- package/dist/packages/adapters/three/src/shaders/fullscreen.vert.js +4 -0
- package/dist/packages/adapters/three/src/shaders/header.js +4 -0
- package/dist/packages/adapters/three/src/shaders/lensModels.js +4 -0
- package/dist/packages/adapters/three/src/shaders/lensSampling.js +4 -0
- package/dist/packages/adapters/three/src/shaders/lensTextures.js +4 -0
- package/dist/packages/adapters/three/src/shaders/rawLenses.frag.js +4 -0
- package/dist/packages/adapters/three/src/shaders/rectilinearRays.js +4 -0
- package/dist/packages/adapters/three/src/shaders/screenAreas.js +4 -0
- package/dist/packages/adapters/three/src/shaders/stitch.frag.js +4 -0
- package/dist/packages/adapters/three/src/webglRenderer.js +37 -0
- package/dist/packages/adapters/webcodecs/src/WebCodecsVideoDecoderPort.js +135 -0
- package/dist/packages/adapters/webcodecs/src/index.js +2 -0
- package/dist/packages/core/src/application/gainMatching/GainMatching.js +66 -0
- package/dist/packages/core/src/application/gainMatching/GainMatchingFrameSink.js +62 -0
- package/dist/packages/core/src/application/playback/ClockWatch.js +76 -0
- package/dist/packages/core/src/application/playback/DecodePipeline.js +188 -0
- package/dist/packages/core/src/application/playback/DecodeRun.js +93 -0
- package/dist/packages/core/src/application/playback/FramePairQueue.js +93 -0
- package/dist/packages/core/src/application/playback/FramePairer.js +60 -0
- package/dist/packages/core/src/application/playback/PlaybackSession.js +417 -0
- package/dist/packages/core/src/application/playback/SeekOrder.js +36 -0
- package/dist/packages/core/src/application/playback/SessionLifecycle.js +42 -0
- package/dist/packages/core/src/application/playback/StartGate.js +55 -0
- package/dist/packages/core/src/application/playback/TimeUpdates.js +31 -0
- package/dist/packages/core/src/application/playback/WallClock.js +46 -0
- package/dist/packages/core/src/application/playback/keyframeTimeAt.js +15 -0
- package/dist/packages/core/src/application/playback/probeDecoding.js +168 -0
- package/dist/packages/core/src/application/recording/frameTimesOf.js +22 -0
- package/dist/packages/core/src/application/recording/inspectLayout.js +21 -0
- package/dist/packages/core/src/application/recording/inspectRecording.js +124 -0
- package/dist/packages/core/src/application/recording/locateOtherLensFile.js +18 -0
- package/dist/packages/core/src/application/recording/motionOf.js +39 -0
- package/dist/packages/core/src/application/recording/readRecording.js +31 -0
- package/dist/packages/core/src/application/recording/timeRecording.js +57 -0
- package/dist/packages/core/src/application/stabilization/StabilizingFrameSink.js +31 -0
- package/dist/packages/core/src/domain/format/boxes/BoxLayout.js +7 -0
- package/dist/packages/core/src/domain/format/boxes/boxConstants.js +16 -0
- package/dist/packages/core/src/domain/format/boxes/scanBoxes.js +59 -0
- package/dist/packages/core/src/domain/format/calibration/CalibrationVersion.js +13 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/CalibrationStringLayout.js +34 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/LegacyCalibrationLayout.js +44 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/MeiCalibrationLayout.js +45 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/PolynomialCalibrationLayout.js +44 -0
- package/dist/packages/core/src/domain/format/calibration/offsetTokens.js +65 -0
- package/dist/packages/core/src/domain/format/calibration/parseOffsetString.js +52 -0
- package/dist/packages/core/src/domain/format/calibration/selectCalibration.js +46 -0
- package/dist/packages/core/src/domain/format/captureOrigin.js +20 -0
- package/dist/packages/core/src/domain/format/constants.js +33 -0
- package/dist/packages/core/src/domain/format/info/infoFields.js +110 -0
- package/dist/packages/core/src/domain/format/info/parseInfoRecord.js +90 -0
- package/dist/packages/core/src/domain/format/info/readInfoRecord.js +14 -0
- package/dist/packages/core/src/domain/format/layout/detectLensLayout.js +122 -0
- package/dist/packages/core/src/domain/format/naming/RecordingFileName.js +67 -0
- package/dist/packages/core/src/domain/format/records/TrailerRecords.js +83 -0
- package/dist/packages/core/src/domain/format/records/exposure/parseExposureRecord.js +48 -0
- package/dist/packages/core/src/domain/format/records/gyro/FloatGyroSampleLayout.js +39 -0
- package/dist/packages/core/src/domain/format/records/gyro/RawGyroSampleLayout.js +55 -0
- package/dist/packages/core/src/domain/format/records/gyro/SampleColumns.js +82 -0
- package/dist/packages/core/src/domain/format/records/gyro/gyroLayouts.js +12 -0
- package/dist/packages/core/src/domain/format/records/gyro/parseGyroRecord.js +129 -0
- package/dist/packages/core/src/domain/format/trailer/RecordHeader.js +23 -0
- package/dist/packages/core/src/domain/format/trailer/Trailer.js +23 -0
- package/dist/packages/core/src/domain/format/trailer/TrailerFooter.js +27 -0
- package/dist/packages/core/src/domain/format/trailer/locateRecords.js +80 -0
- package/dist/packages/core/src/domain/format/trailer/parseRecordIndex.js +28 -0
- package/dist/packages/core/src/domain/format/trailer/readTrailer.js +32 -0
- package/dist/packages/core/src/domain/motion/exposure/ExposureRecord.js +53 -0
- package/dist/packages/core/src/domain/motion/gyro/GyroTrack.js +60 -0
- package/dist/packages/core/src/domain/motion/gyro/medianStep.js +11 -0
- package/dist/packages/core/src/domain/motion/gyro/repairedTimeline.js +101 -0
- package/dist/packages/core/src/domain/motion/imu/ImuFrame.js +112 -0
- package/dist/packages/core/src/domain/motion/orientation/OrientationTrack.js +146 -0
- package/dist/packages/core/src/domain/motion/orientation/estimateGyroBias.js +64 -0
- package/dist/packages/core/src/domain/motion/orientation/gravity.js +97 -0
- package/dist/packages/core/src/domain/motion/stabilization/Stabilizer.js +13 -0
- package/dist/packages/core/src/domain/motion/stabilization/stabilizers.js +86 -0
- package/dist/packages/core/src/domain/motion/timing/CaptureClock.js +41 -0
- package/dist/packages/core/src/domain/motion/timing/FrameTimes.js +92 -0
- package/dist/packages/core/src/domain/motion/timing/frameTimeSources.js +61 -0
- package/dist/packages/core/src/domain/motion/timing/resolveFrameTimes.js +68 -0
- package/dist/packages/core/src/domain/optics/EquidistantModel.js +39 -0
- package/dist/packages/core/src/domain/optics/MeiModel.js +45 -0
- package/dist/packages/core/src/domain/optics/PolynomialModel.js +44 -0
- package/dist/packages/core/src/domain/optics/gainMatch.js +62 -0
- package/dist/packages/core/src/domain/optics/lensGeometry.js +18 -0
- package/dist/packages/core/src/domain/optics/lensPose.js +35 -0
- package/dist/packages/core/src/domain/optics/opticsConstants.js +5 -0
- package/dist/packages/core/src/domain/playback/PlayerState.js +83 -0
- package/dist/packages/core/src/domain/stitching/LensLayout.js +21 -0
- package/dist/packages/core/src/domain/stitching/StitchingSetup.js +78 -0
- package/dist/packages/core/src/domain/stitching/seamDisparity.js +4 -0
- package/dist/packages/core/src/domain/stitching/seamDisparityField.js +5 -0
- package/dist/packages/core/src/domain/stitching/seamJoin.js +16 -0
- package/dist/packages/core/src/domain/stitching/seamMismatch.js +1 -0
- package/dist/packages/core/src/domain/stitching/seamRing.js +4 -0
- package/dist/packages/core/src/domain/stitching/seamStrip.js +26 -0
- package/dist/packages/core/src/domain/view/Framing.js +31 -0
- package/dist/packages/core/src/domain/view/PictureQuality.js +26 -0
- package/dist/packages/core/src/domain/view/ViewMode.js +9 -0
- package/dist/packages/core/src/domain/view/ViewState.js +48 -0
- package/dist/packages/core/src/domain/view/lensTilesView.js +63 -0
- package/dist/packages/core/src/domain/view/magnification.js +103 -0
- package/dist/packages/core/src/domain/view/normalView.js +29 -0
- package/dist/packages/core/src/domain/view/panoramaView.js +99 -0
- package/dist/packages/core/src/domain/view/rectilinear.js +40 -0
- package/dist/packages/core/src/domain/view/screenLayout.js +113 -0
- package/dist/packages/core/src/domain/view/viewGestures.js +98 -0
- package/dist/packages/core/src/domain/view/viewModes.js +14 -0
- package/dist/packages/core/src/index.js +59 -0
- package/dist/packages/core/src/ports/FramePair.js +6 -0
- package/dist/packages/core/src/shared/async/Deferred.js +42 -0
- package/dist/packages/core/src/shared/async/RunStop.js +40 -0
- package/dist/packages/core/src/shared/binary/ByteRange.js +38 -0
- package/dist/packages/core/src/shared/binary/ByteReader.js +77 -0
- package/dist/packages/core/src/shared/errors/GyroViewError.js +81 -0
- package/dist/packages/core/src/shared/errors/reportLater.js +11 -0
- package/dist/packages/core/src/shared/events/Outbox.js +68 -0
- package/dist/packages/core/src/shared/events/TypedEmitter.js +55 -0
- package/dist/packages/core/src/shared/keysOf.js +10 -0
- package/dist/packages/core/src/shared/lazy.js +11 -0
- package/dist/packages/core/src/shared/math/Matrix3.js +93 -0
- package/dist/packages/core/src/shared/math/Quaternion.js +133 -0
- package/dist/packages/core/src/shared/math/Vector3.js +54 -0
- package/dist/packages/core/src/shared/math/clamp.js +9 -0
- package/dist/packages/core/src/shared/protobuf/ProtobufMessage.js +157 -0
- package/dist/packages/core/src/shared/text/urlPath.js +31 -0
- package/dist/packages/core/src/shared/text/utf8.js +12 -0
- package/dist/packages/core/src/shared/units/angle.js +22 -0
- package/dist/packages/core/src/shared/units/time.js +13 -0
- package/dist/packages/player/src/PlayerSource.js +15 -0
- package/dist/packages/player/src/browserPlayer.js +18 -0
- package/dist/packages/player/src/choices.js +25 -0
- package/dist/packages/player/src/composition/Disposables.js +31 -0
- package/dist/packages/player/src/composition/browserPorts.js +46 -0
- package/dist/packages/player/src/composition/buildPipeline.js +153 -0
- package/dist/packages/player/src/composition/ensureDecodable.js +63 -0
- package/dist/packages/player/src/composition/openInputs.js +179 -0
- package/dist/packages/player/src/composition/openRecording.js +42 -0
- package/dist/packages/player/src/controls/ChoiceMenu.js +113 -0
- package/dist/packages/player/src/controls/PictureMenus.js +47 -0
- package/dist/packages/player/src/controls/SeekBar.js +125 -0
- package/dist/packages/player/src/controls/SoundControls.js +41 -0
- package/dist/packages/player/src/controls/TransportButtons.js +31 -0
- package/dist/packages/player/src/controls/ViewGestures.js +136 -0
- package/dist/packages/player/src/controls/Wording.js +78 -0
- package/dist/packages/player/src/controls/controlParts.js +36 -0
- package/dist/packages/player/src/controls/controls.js +4 -0
- package/dist/packages/player/src/controls/controlsBar.js +23 -0
- package/dist/packages/player/src/controls/controlsMarkup.js +85 -0
- package/dist/packages/player/src/controls/customControls.js +59 -0
- package/dist/packages/player/src/controls/formatTime.js +19 -0
- package/dist/packages/player/src/controls/icons.js +40 -0
- package/dist/packages/player/src/controls/keyboard.js +118 -0
- package/dist/packages/player/src/controls/messages.js +94 -0
- package/dist/packages/player/src/controls/parseMarkup.js +31 -0
- package/dist/packages/player/src/controls/viewButtons.js +14 -0
- package/dist/packages/player/src/element/ElementLoads.js +119 -0
- package/dist/packages/player/src/element/FullscreenToggle.js +39 -0
- package/dist/packages/player/src/element/GyroViewElement.js +313 -0
- package/dist/packages/player/src/element/IdleWatcher.js +71 -0
- package/dist/packages/player/src/element/TypedEventElement.js +22 -0
- package/dist/packages/player/src/element/accessibleRegion.js +19 -0
- package/dist/packages/player/src/element/attributeNames.js +51 -0
- package/dist/packages/player/src/element/attributes.js +101 -0
- package/dist/packages/player/src/element/defineGyroView.js +21 -0
- package/dist/packages/player/src/element/earlyProperties.js +36 -0
- package/dist/packages/player/src/element/elementSource.js +23 -0
- package/dist/packages/player/src/element/liveSettings.js +138 -0
- package/dist/packages/player/src/element/playbackAttributes.js +53 -0
- package/dist/packages/player/src/element/playerEventMirror.js +33 -0
- package/dist/packages/player/src/element/reflectedProperties.js +78 -0
- package/dist/packages/player/src/element/styles.js +4 -0
- package/dist/packages/player/src/element/template.js +47 -0
- package/dist/packages/player/src/index.js +10 -0
- package/dist/packages/player/src/inspectRecording.js +22 -0
- package/dist/packages/player/src/player/DrawingBufferFit.js +77 -0
- package/dist/packages/player/src/player/FrameLoop.js +40 -0
- package/dist/packages/player/src/player/PictureSettings.js +60 -0
- package/dist/packages/player/src/player/Player.js +377 -0
- package/dist/packages/player/src/player/PlayerEvents.js +28 -0
- package/dist/packages/player/src/player/PlayerPhase.js +21 -0
- package/dist/packages/player/src/player/PlayerSound.js +61 -0
- package/dist/packages/player/src/player/PlayerView.js +113 -0
- package/dist/packages/player/src/player/SessionRelay.js +66 -0
- package/dist/packages/player/src/player/ensureFinite.js +26 -0
- package/dist/packages/player/src/player/loadRecording.js +65 -0
- package/dist/packages/player/src/player/transportEvents.js +29 -0
- package/dist/standalone.js +4919 -0
- package/package.json +73 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { GyroViewError, ensureInvariant } from "../../shared/errors/GyroViewError.js";
|
|
2
|
+
import { FramePairer } from "./FramePairer.js";
|
|
3
|
+
import { StartGate } from "./StartGate.js";
|
|
4
|
+
import { Deferred } from "../../shared/async/Deferred.js";
|
|
5
|
+
import { RunStop, STOPPED } from "../../shared/async/RunStop.js";
|
|
6
|
+
//#region ../../packages/core/src/application/playback/DecodePipeline.ts
|
|
7
|
+
/**
|
|
8
|
+
* Decodes the frame sources of a recording in lockstep from a chosen time: starts every decoder at
|
|
9
|
+
* the key packet at or before that time (the first one, for a time before it), feeds packets
|
|
10
|
+
* round-robin with bounded decoder queues, pairs the resulting frames and hands pairs to the output
|
|
11
|
+
* queue through a {@link StartGate}. One instance runs once; each `DecodeRun` creates its own.
|
|
12
|
+
*/
|
|
13
|
+
var DecodePipeline = class {
|
|
14
|
+
frameSources;
|
|
15
|
+
decoderPort;
|
|
16
|
+
options;
|
|
17
|
+
stop = new RunStop();
|
|
18
|
+
hasRun = false;
|
|
19
|
+
constructor(frameSources, decoderPort, options) {
|
|
20
|
+
this.frameSources = frameSources;
|
|
21
|
+
this.decoderPort = decoderPort;
|
|
22
|
+
this.options = options;
|
|
23
|
+
if (frameSources.length === 0) throw new GyroViewError("invariant-violation", "a decode pipeline needs at least one frame source");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Runs until the tracks end, the output queue is closed or {@link abort} is called. Resolves
|
|
27
|
+
* with a report; rejects when a decoder fails or a track cannot be read.
|
|
28
|
+
*/
|
|
29
|
+
async run(from, output) {
|
|
30
|
+
ensureInvariant(!this.hasRun, "a decode pipeline runs once; create a new one for each run");
|
|
31
|
+
this.hasRun = true;
|
|
32
|
+
const run = await this.openRun(from, output);
|
|
33
|
+
try {
|
|
34
|
+
const hasReachedEnd = await this.feed(run);
|
|
35
|
+
await settle(run, hasReachedEnd);
|
|
36
|
+
if (run.failure.isSettled) throw await run.failure.promise;
|
|
37
|
+
return reportOf(run, hasReachedEnd);
|
|
38
|
+
} finally {
|
|
39
|
+
closeRun(run);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Ends the run early; pending decodes are discarded. Safe before, during and after the run.
|
|
44
|
+
*/
|
|
45
|
+
abort() {
|
|
46
|
+
this.stop.stop();
|
|
47
|
+
}
|
|
48
|
+
async openRun(from, output) {
|
|
49
|
+
const { stop } = this;
|
|
50
|
+
const failure = new Deferred();
|
|
51
|
+
const tally = {
|
|
52
|
+
packets: 0,
|
|
53
|
+
pairs: 0
|
|
54
|
+
};
|
|
55
|
+
const gate = new StartGate(from, (pair) => {
|
|
56
|
+
tally.pairs += 1;
|
|
57
|
+
output.push(pair);
|
|
58
|
+
});
|
|
59
|
+
const pairer = new FramePairer(this.frameSources.length, this.options.pairTolerance, (pair) => {
|
|
60
|
+
gate.push(pair);
|
|
61
|
+
});
|
|
62
|
+
const iterators = stop.wasStopped ? [] : await this.packetIteratorsFrom(from);
|
|
63
|
+
const onError = (error) => {
|
|
64
|
+
failure.resolve(error);
|
|
65
|
+
stop.stop();
|
|
66
|
+
};
|
|
67
|
+
try {
|
|
68
|
+
return {
|
|
69
|
+
output,
|
|
70
|
+
stop,
|
|
71
|
+
failure,
|
|
72
|
+
tally,
|
|
73
|
+
gate,
|
|
74
|
+
pairer,
|
|
75
|
+
iterators,
|
|
76
|
+
decoders: stop.wasStopped ? [] : await this.createDecoders(pairer, onError)
|
|
77
|
+
};
|
|
78
|
+
} catch (error) {
|
|
79
|
+
closeIterators(iterators);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Opens one decoder per frame source; if any refuses, the ones already open are closed again.
|
|
85
|
+
*/
|
|
86
|
+
async createDecoders(pairer, onError) {
|
|
87
|
+
const results = await Promise.allSettled(this.frameSources.map(async (track, sourceIndex) => this.decoderPort.create(await track.decoderConfiguration(), {
|
|
88
|
+
onFrame: (frame) => {
|
|
89
|
+
pairer.push(sourceIndex, frame);
|
|
90
|
+
},
|
|
91
|
+
onError
|
|
92
|
+
})));
|
|
93
|
+
const opened = results.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
94
|
+
const refusal = results.find((result) => result.status === "rejected");
|
|
95
|
+
if (refusal) {
|
|
96
|
+
for (const decoder of opened) decoder.close();
|
|
97
|
+
throw toError(refusal.reason);
|
|
98
|
+
}
|
|
99
|
+
return opened;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Resolves to true when every track ran out of packets, false when aborted or the queue closed.
|
|
103
|
+
*/
|
|
104
|
+
async feed(run) {
|
|
105
|
+
while (!shouldStop(run)) {
|
|
106
|
+
await run.stop.race(() => run.output.waitForRoom());
|
|
107
|
+
if (shouldStop(run)) return false;
|
|
108
|
+
const round = await run.stop.race(() => nextRound(run.iterators));
|
|
109
|
+
if (round === STOPPED) return false;
|
|
110
|
+
if (!round) return true;
|
|
111
|
+
await this.decodeRound(run, round);
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
async decodeRound(run, packets) {
|
|
116
|
+
for (const [sourceIndex, decoder] of run.decoders.entries()) {
|
|
117
|
+
const packet = packets[sourceIndex];
|
|
118
|
+
if (!packet) continue;
|
|
119
|
+
await run.stop.race(() => decoder.waitForPendingBelow(this.options.maxPendingPackets));
|
|
120
|
+
if (shouldStop(run)) return;
|
|
121
|
+
decoder.decode(packet);
|
|
122
|
+
run.tally.packets += 1;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
packetIteratorsFrom(from) {
|
|
126
|
+
return Promise.all(this.frameSources.map(async (track) => {
|
|
127
|
+
const start = await track.keyPacketAt(from) ?? await track.firstKeyPacket();
|
|
128
|
+
if (!start) throw new GyroViewError("no-key-frame", `track ${track.description.trackIndex} has no key frame`);
|
|
129
|
+
return track.packetsFrom(start)[Symbol.asyncIterator]();
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
function shouldStop(run) {
|
|
134
|
+
return run.stop.wasStopped || run.output.isClosedForGood;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* After the feed: a finished run drains the decoders and, having reached the end, releases the
|
|
138
|
+
* pair the gate held back. An abort, before or while draining, leaves what is pending to the
|
|
139
|
+
* decoders' closing.
|
|
140
|
+
*/
|
|
141
|
+
async function settle(run, hasReachedEnd) {
|
|
142
|
+
if (await run.stop.race(() => drain(run.decoders, run.failure)) !== STOPPED && hasReachedEnd) run.gate.release();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* A decoder that fails while draining has already aborted the run and settled its failure.
|
|
146
|
+
*/
|
|
147
|
+
async function drain(decoders, failure) {
|
|
148
|
+
try {
|
|
149
|
+
await Promise.all(decoders.map((decoder) => decoder.flush()));
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (!failure.isSettled) throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function closeRun(run) {
|
|
155
|
+
for (const decoder of run.decoders) decoder.close();
|
|
156
|
+
run.pairer.discardAll();
|
|
157
|
+
run.gate.discard();
|
|
158
|
+
closeIterators(run.iterators);
|
|
159
|
+
}
|
|
160
|
+
function closeIterators(iterators) {
|
|
161
|
+
for (const iterator of iterators) iterator.return?.();
|
|
162
|
+
}
|
|
163
|
+
function reportOf(run, hasReachedEnd) {
|
|
164
|
+
return {
|
|
165
|
+
packetsDecoded: run.tally.packets,
|
|
166
|
+
pairsDelivered: run.tally.pairs,
|
|
167
|
+
unpairedFrames: run.pairer.unpaired,
|
|
168
|
+
pairsDroppedBeforeStart: run.gate.dropped,
|
|
169
|
+
hasReachedEnd
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function toError(reason) {
|
|
173
|
+
return reason instanceof Error ? reason : new GyroViewError("decode", "a decoder could not be created", { cause: reason });
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* One packet per frame source, or undefined as soon as any of them is exhausted.
|
|
177
|
+
*/
|
|
178
|
+
async function nextRound(iterators) {
|
|
179
|
+
const results = await Promise.all(iterators.map((iterator) => iterator.next()));
|
|
180
|
+
const packets = [];
|
|
181
|
+
for (const result of results) {
|
|
182
|
+
if (result.done === true) return void 0;
|
|
183
|
+
packets.push(result.value);
|
|
184
|
+
}
|
|
185
|
+
return packets;
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
188
|
+
export { DecodePipeline };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { seconds } from "../../shared/units/time.js";
|
|
2
|
+
import { FramePairQueue } from "./FramePairQueue.js";
|
|
3
|
+
import { DecodePipeline } from "./DecodePipeline.js";
|
|
4
|
+
//#region ../../packages/core/src/application/playback/DecodeRun.ts
|
|
5
|
+
/**
|
|
6
|
+
* Pairs queued before the clock (re)starts, so playback does not stall on its first frames.
|
|
7
|
+
*/
|
|
8
|
+
var PRIMING_PAIRS = 2;
|
|
9
|
+
/**
|
|
10
|
+
* A shown frame this far behind the clock, with nothing decoded to follow it, means the decoders
|
|
11
|
+
* have fallen behind: several frames at any frame rate, well beyond jitter.
|
|
12
|
+
*/
|
|
13
|
+
var STARVATION_LAG_SECONDS = .25;
|
|
14
|
+
/**
|
|
15
|
+
* One decode of every frame source from a time into a queue of its own: the pipeline, the pairs
|
|
16
|
+
* it delivered and whether it reached the end, which live and go together. Once aborted it drops
|
|
17
|
+
* what arrives late and reports nothing, so a run a seek replaced never speaks for its successor.
|
|
18
|
+
*/
|
|
19
|
+
var DecodeRun = class DecodeRun {
|
|
20
|
+
listener;
|
|
21
|
+
queue;
|
|
22
|
+
pipeline;
|
|
23
|
+
primingPairs;
|
|
24
|
+
isAborted = false;
|
|
25
|
+
hasReachedEnd = false;
|
|
26
|
+
hasHandedOutPair = false;
|
|
27
|
+
constructor(parts, listener) {
|
|
28
|
+
this.listener = listener;
|
|
29
|
+
this.primingPairs = Math.min(PRIMING_PAIRS, parts.queueCapacity);
|
|
30
|
+
this.queue = new FramePairQueue(parts.queueCapacity, () => {
|
|
31
|
+
if (!this.isAborted) listener.onProgress();
|
|
32
|
+
});
|
|
33
|
+
this.pipeline = new DecodePipeline(parts.frameSources, parts.decoderPort, parts.pipeline);
|
|
34
|
+
}
|
|
35
|
+
static start(parts, from, listener) {
|
|
36
|
+
const run = new DecodeRun(parts, listener);
|
|
37
|
+
run.follow(run.pipeline.run(from, run.queue));
|
|
38
|
+
return run;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Everything has been decoded and every pair handed out.
|
|
42
|
+
*/
|
|
43
|
+
get isDrained() {
|
|
44
|
+
return this.hasReachedEnd && this.queue.length === 0;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Enough frames are decoded to start moving at `now`: a couple queued, reaching the clock's time
|
|
48
|
+
* (pairs it has already passed would leave it starved at once), or the run is over and what is
|
|
49
|
+
* left is already there.
|
|
50
|
+
*/
|
|
51
|
+
isPrimedAt(now) {
|
|
52
|
+
const hasCaughtUp = (this.queue.newestTimestamp() ?? -Infinity) >= now;
|
|
53
|
+
return this.hasReachedEnd || hasCaughtUp && this.queue.length >= this.primingPairs;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Nothing is queued, the run is not over, and the frame on screen (shown for `shownAt`, if any)
|
|
57
|
+
* is well behind the clock: the decoders cannot keep up, even when each tick still finds a
|
|
58
|
+
* pair to show.
|
|
59
|
+
*/
|
|
60
|
+
isStarvedAt(now, shownAt) {
|
|
61
|
+
const isWaitingOnDecoders = this.queue.length === 0 && !this.hasReachedEnd;
|
|
62
|
+
const lag = now - (shownAt ?? -Infinity);
|
|
63
|
+
return isWaitingOnDecoders && lag > STARVATION_LAG_SECONDS;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The latest pair due at `time`. Until the run has handed one out, its first pair is due at
|
|
67
|
+
* once: on tracks whose first frame comes after `time`, that frame is the picture there.
|
|
68
|
+
*/
|
|
69
|
+
takePairAt(time) {
|
|
70
|
+
const head = this.queue.peekTimestamp();
|
|
71
|
+
const isFirst = !this.hasHandedOutPair && head !== void 0;
|
|
72
|
+
const pair = this.queue.takePairAt(isFirst ? seconds(Math.max(time, head)) : time);
|
|
73
|
+
if (pair) this.hasHandedOutPair = true;
|
|
74
|
+
return pair;
|
|
75
|
+
}
|
|
76
|
+
abort() {
|
|
77
|
+
this.isAborted = true;
|
|
78
|
+
this.pipeline.abort();
|
|
79
|
+
this.queue.close();
|
|
80
|
+
}
|
|
81
|
+
async follow(report) {
|
|
82
|
+
try {
|
|
83
|
+
const { hasReachedEnd } = await report;
|
|
84
|
+
if (this.isAborted) return;
|
|
85
|
+
this.hasReachedEnd = hasReachedEnd;
|
|
86
|
+
this.listener.onProgress();
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (!this.isAborted) this.listener.onFailure(error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
//#endregion
|
|
93
|
+
export { DecodeRun };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ensureInvariant } from "../../shared/errors/GyroViewError.js";
|
|
2
|
+
import { closeFramePair } from "../../ports/FramePair.js";
|
|
3
|
+
//#region ../../packages/core/src/application/playback/FramePairQueue.ts
|
|
4
|
+
/**
|
|
5
|
+
* Buffer of decoded pairs between the decode pipeline (producer) and the renderer (consumer).
|
|
6
|
+
* `capacity` is a soft limit: the producer waits for room before feeding more packets, but frames
|
|
7
|
+
* already in flight always land, so the queue may briefly hold `capacity` plus the decoders'
|
|
8
|
+
* pending limit. Pairs leave the queue when the consumer takes them; the consumer then owns them
|
|
9
|
+
* and closes them when they are superseded.
|
|
10
|
+
*/
|
|
11
|
+
var FramePairQueue = class {
|
|
12
|
+
capacity;
|
|
13
|
+
onPush;
|
|
14
|
+
pairs = [];
|
|
15
|
+
roomWaiters = [];
|
|
16
|
+
isClosed = false;
|
|
17
|
+
/**
|
|
18
|
+
* `onPush` hears about every pair kept, so a consumer waiting for frames need not poll.
|
|
19
|
+
*/
|
|
20
|
+
constructor(capacity, onPush = doNothing) {
|
|
21
|
+
this.capacity = capacity;
|
|
22
|
+
this.onPush = onPush;
|
|
23
|
+
ensureInvariant(Number.isSafeInteger(capacity) && capacity > 0, "queue capacity must be positive");
|
|
24
|
+
}
|
|
25
|
+
get length() {
|
|
26
|
+
return this.pairs.length;
|
|
27
|
+
}
|
|
28
|
+
get isFull() {
|
|
29
|
+
return this.pairs.length >= this.capacity;
|
|
30
|
+
}
|
|
31
|
+
get isClosedForGood() {
|
|
32
|
+
return this.isClosed;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolves once the queue is below capacity, or immediately once it was closed.
|
|
36
|
+
*/
|
|
37
|
+
waitForRoom() {
|
|
38
|
+
return !this.isFull || this.isClosed ? Promise.resolve() : new Promise((resolve) => {
|
|
39
|
+
this.roomWaiters.push(resolve);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Adds a pair; a closed queue closes the pair instead of keeping it.
|
|
44
|
+
*/
|
|
45
|
+
push(pair) {
|
|
46
|
+
if (this.isClosed) {
|
|
47
|
+
closeFramePair(pair);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this.pairs.push(pair);
|
|
51
|
+
this.onPush();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Removes and returns the latest pair at or before `time`, closing older pairs it skips over.
|
|
55
|
+
* Undefined when no queued pair is due yet; newer pairs stay queued.
|
|
56
|
+
*/
|
|
57
|
+
takePairAt(time) {
|
|
58
|
+
let taken;
|
|
59
|
+
while (this.pairs.length > 0 && (this.pairs[0]?.timestamp ?? Infinity) <= time) {
|
|
60
|
+
if (taken) closeFramePair(taken);
|
|
61
|
+
taken = this.pairs.shift();
|
|
62
|
+
}
|
|
63
|
+
if (taken) this.wakeWaiters();
|
|
64
|
+
return taken;
|
|
65
|
+
}
|
|
66
|
+
peekTimestamp() {
|
|
67
|
+
return this.pairs[0]?.timestamp;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The time of the pair queued last, the furthest decoding has come.
|
|
71
|
+
*/
|
|
72
|
+
newestTimestamp() {
|
|
73
|
+
return this.pairs.at(-1)?.timestamp;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Drops and closes everything held, and every pair pushed from now on.
|
|
77
|
+
*/
|
|
78
|
+
close() {
|
|
79
|
+
this.isClosed = true;
|
|
80
|
+
for (const pair of this.pairs) closeFramePair(pair);
|
|
81
|
+
this.pairs.length = 0;
|
|
82
|
+
this.wakeWaiters();
|
|
83
|
+
}
|
|
84
|
+
wakeWaiters() {
|
|
85
|
+
if (this.isFull && !this.isClosed) return;
|
|
86
|
+
const waiters = this.roomWaiters;
|
|
87
|
+
this.roomWaiters = [];
|
|
88
|
+
for (const resolve of waiters) resolve();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
function doNothing() {}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { FramePairQueue };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ensureIndexInRange } from "../../shared/errors/GyroViewError.js";
|
|
2
|
+
import { seconds } from "../../shared/units/time.js";
|
|
3
|
+
//#region ../../packages/core/src/application/playback/FramePairer.ts
|
|
4
|
+
/**
|
|
5
|
+
* Buffers decoded frames per frame source and emits a {@link FramePair} as soon as every source has
|
|
6
|
+
* a frame for the same instant. The tracks of one recording share the camera clock, so their
|
|
7
|
+
* timestamps agree exactly; the tolerance only absorbs floating-point conversion noise.
|
|
8
|
+
*/
|
|
9
|
+
var FramePairer = class {
|
|
10
|
+
tolerance;
|
|
11
|
+
onPair;
|
|
12
|
+
queues;
|
|
13
|
+
unpairedCount = 0;
|
|
14
|
+
constructor(sourceCount, tolerance, onPair) {
|
|
15
|
+
this.tolerance = tolerance;
|
|
16
|
+
this.onPair = onPair;
|
|
17
|
+
this.queues = Array.from({ length: sourceCount }, () => []);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Frames dropped because no other source ever produced a matching instant.
|
|
21
|
+
*/
|
|
22
|
+
get unpaired() {
|
|
23
|
+
return this.unpairedCount;
|
|
24
|
+
}
|
|
25
|
+
push(sourceIndex, frame) {
|
|
26
|
+
ensureIndexInRange(sourceIndex, this.queues.length, "frame source");
|
|
27
|
+
this.queues[sourceIndex]?.push(frame);
|
|
28
|
+
this.drain();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Closes everything still buffered, for example when stopping mid-stream.
|
|
32
|
+
*/
|
|
33
|
+
discardAll() {
|
|
34
|
+
for (const queue of this.queues) {
|
|
35
|
+
for (const frame of queue) frame.close();
|
|
36
|
+
queue.length = 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
drain() {
|
|
40
|
+
while (this.queues.every((queue) => queue.length > 0)) {
|
|
41
|
+
const heads = this.queues.map((queue) => queue[0]).filter((frame) => frame !== void 0);
|
|
42
|
+
const earliest = Math.min(...heads.map((frame) => frame.timestamp));
|
|
43
|
+
if (heads.every((frame) => Math.abs(frame.timestamp - earliest) <= this.tolerance)) this.emit(heads, seconds(earliest));
|
|
44
|
+
else this.dropHeadAt(earliest);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
emit(frames, timestamp) {
|
|
48
|
+
for (const queue of this.queues) queue.shift();
|
|
49
|
+
this.onPair({
|
|
50
|
+
timestamp,
|
|
51
|
+
frames
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
dropHeadAt(timestamp) {
|
|
55
|
+
this.queues.find((candidate) => candidate[0]?.timestamp === timestamp)?.shift()?.close();
|
|
56
|
+
this.unpairedCount += 1;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
//#endregion
|
|
60
|
+
export { FramePairer };
|