@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,31 @@
|
|
|
1
|
+
//#region ../../packages/core/src/shared/text/urlPath.ts
|
|
2
|
+
var QUERY_OR_FRAGMENT = /[#?]/u;
|
|
3
|
+
/**
|
|
4
|
+
* A URL cut into its path and the query and fragment after it, read as text: relative URLs and
|
|
5
|
+
* ones a URL parser would refuse split too.
|
|
6
|
+
*/
|
|
7
|
+
function splitUrl(url) {
|
|
8
|
+
const suffixStart = url.search(QUERY_OR_FRAGMENT);
|
|
9
|
+
return suffixStart === -1 ? {
|
|
10
|
+
path: url,
|
|
11
|
+
suffix: ""
|
|
12
|
+
} : {
|
|
13
|
+
path: url.slice(0, suffixStart),
|
|
14
|
+
suffix: url.slice(suffixStart)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The file name a URL names: its last path segment, percent-decoded (kept as written when the
|
|
19
|
+
* encoding is broken); the whole path when there is no slash.
|
|
20
|
+
*/
|
|
21
|
+
function fileNameOfUrl(url) {
|
|
22
|
+
const { path } = splitUrl(url);
|
|
23
|
+
const encoded = path.slice(path.lastIndexOf("/") + 1);
|
|
24
|
+
try {
|
|
25
|
+
return decodeURIComponent(encoded);
|
|
26
|
+
} catch {
|
|
27
|
+
return encoded;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { fileNameOfUrl, splitUrl };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { lazy } from "../lazy.js";
|
|
2
|
+
//#region ../../packages/core/src/shared/text/utf8.ts
|
|
3
|
+
var decoder = lazy(() => new globalThis.TextDecoder("utf8"));
|
|
4
|
+
/**
|
|
5
|
+
* Text the camera wrote as UTF-8; malformed sequences, an encoded surrogate among them, become
|
|
6
|
+
* U+FFFD, as the platform decodes them.
|
|
7
|
+
*/
|
|
8
|
+
function decodeUtf8(bytes) {
|
|
9
|
+
return decoder().decode(bytes);
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { decodeUtf8 };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region ../../packages/core/src/shared/units/angle.ts
|
|
2
|
+
var degrees = (value) => value;
|
|
3
|
+
var radians = (value) => value;
|
|
4
|
+
var DEGREES_PER_QUARTER_TURN = 90;
|
|
5
|
+
var DEGREES_PER_HALF_TURN = 180;
|
|
6
|
+
var DEGREES_PER_TURN = 360;
|
|
7
|
+
var QUARTER_TURN = degrees(DEGREES_PER_QUARTER_TURN);
|
|
8
|
+
var HALF_TURN = degrees(DEGREES_PER_HALF_TURN);
|
|
9
|
+
var FULL_TURN = degrees(DEGREES_PER_TURN);
|
|
10
|
+
var degreesToRadians = (value) => radians(value * Math.PI / DEGREES_PER_HALF_TURN);
|
|
11
|
+
var radiansToDegrees = (value) => degrees(value * DEGREES_PER_HALF_TURN / Math.PI);
|
|
12
|
+
/**
|
|
13
|
+
* The same direction as an angle in (-180, 180] degrees.
|
|
14
|
+
*/
|
|
15
|
+
function wrapHalfTurn(angle) {
|
|
16
|
+
let wrapped = angle % FULL_TURN;
|
|
17
|
+
if (wrapped > HALF_TURN) wrapped -= FULL_TURN;
|
|
18
|
+
if (wrapped <= -HALF_TURN) wrapped += FULL_TURN;
|
|
19
|
+
return degrees(wrapped);
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { FULL_TURN, HALF_TURN, QUARTER_TURN, degrees, degreesToRadians, radians, radiansToDegrees, wrapHalfTurn };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region ../../packages/core/src/shared/units/time.ts
|
|
2
|
+
var MICROSECONDS_PER_SECOND = 1e6;
|
|
3
|
+
var MILLISECONDS_PER_SECOND = 1e3;
|
|
4
|
+
var MICROSECONDS_PER_MILLISECOND = 1e3;
|
|
5
|
+
var microseconds = (value) => value;
|
|
6
|
+
var milliseconds = (value) => value;
|
|
7
|
+
var seconds = (value) => value;
|
|
8
|
+
var microsecondsToSeconds = (value) => seconds(value / MICROSECONDS_PER_SECOND);
|
|
9
|
+
var millisecondsToMicroseconds = (value) => microseconds(value * MICROSECONDS_PER_MILLISECOND);
|
|
10
|
+
var millisecondsToSeconds = (value) => seconds(value / MILLISECONDS_PER_SECOND);
|
|
11
|
+
var secondsToMicroseconds = (value) => microseconds(value * MICROSECONDS_PER_SECOND);
|
|
12
|
+
//#endregion
|
|
13
|
+
export { microseconds, microsecondsToSeconds, milliseconds, millisecondsToMicroseconds, millisecondsToSeconds, seconds, secondsToMicroseconds };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { fileNameOfUrl } from "../../core/src/shared/text/urlPath.js";
|
|
2
|
+
import "../../core/src/index.js";
|
|
3
|
+
//#region ../../packages/player/src/PlayerSource.ts
|
|
4
|
+
function isUrlInput(input) {
|
|
5
|
+
return "url" in input;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The file name the input goes by: the URL's last path segment or the blob's name. A hint for
|
|
9
|
+
* layout detection and messages, never the truth about the file.
|
|
10
|
+
*/
|
|
11
|
+
function inputName(input) {
|
|
12
|
+
return isUrlInput(input) ? fileNameOfUrl(input.url) : input.name;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { inputName, isUrlInput };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { browserPorts } from "./composition/browserPorts.js";
|
|
2
|
+
import { buildPipeline } from "./composition/buildPipeline.js";
|
|
3
|
+
import { Player } from "./player/Player.js";
|
|
4
|
+
//#region ../../packages/player/src/browserPlayer.ts
|
|
5
|
+
/**
|
|
6
|
+
* A player composed for a browser: HTTP, blob and WebCodecs ports and the GPU pipeline, drawing
|
|
7
|
+
* on the host's canvas and sounding through its audio element. What `<gyro-view>` plays with,
|
|
8
|
+
* and where a page that wants the player without the element starts.
|
|
9
|
+
*/
|
|
10
|
+
function createBrowserPlayer(host, options) {
|
|
11
|
+
return new Player({
|
|
12
|
+
host,
|
|
13
|
+
ports: browserPorts(options),
|
|
14
|
+
pipelines: buildPipeline
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { createBrowserPlayer };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { STABILIZATION_MODES } from "../../core/src/domain/motion/stabilization/Stabilizer.js";
|
|
2
|
+
import { PICTURE_QUALITIES } from "../../core/src/domain/view/PictureQuality.js";
|
|
3
|
+
import { VIEW_MODES } from "../../core/src/domain/view/ViewMode.js";
|
|
4
|
+
import "../../core/src/index.js";
|
|
5
|
+
//#region ../../packages/player/src/choices.ts
|
|
6
|
+
/**
|
|
7
|
+
* The choice `value` names, matched without regard to case or surrounding space; undefined when
|
|
8
|
+
* absent or unknown. Attributes, menus and property setters read their choices through it.
|
|
9
|
+
*/
|
|
10
|
+
function choiceOf(value, choices) {
|
|
11
|
+
if (value === null) return void 0;
|
|
12
|
+
const wanted = value.trim().toLowerCase();
|
|
13
|
+
return choices.find((choice) => choice === wanted);
|
|
14
|
+
}
|
|
15
|
+
function stabilizationModeOf(value) {
|
|
16
|
+
return choiceOf(value, STABILIZATION_MODES);
|
|
17
|
+
}
|
|
18
|
+
function viewModeOf(value) {
|
|
19
|
+
return choiceOf(value, VIEW_MODES);
|
|
20
|
+
}
|
|
21
|
+
function pictureQualityOf(value) {
|
|
22
|
+
return choiceOf(value, PICTURE_QUALITIES);
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { choiceOf, pictureQualityOf, stabilizationModeOf, viewModeOf };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region ../../packages/player/src/composition/Disposables.ts
|
|
2
|
+
/**
|
|
3
|
+
* Collects the resources one operation opens so a failure part way can release them in reverse
|
|
4
|
+
* order, and so success can hand them over as a single disposer.
|
|
5
|
+
*/
|
|
6
|
+
var Disposables = class {
|
|
7
|
+
actions = [];
|
|
8
|
+
isDisposed = false;
|
|
9
|
+
add(dispose) {
|
|
10
|
+
if (this.isDisposed) {
|
|
11
|
+
dispose();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
this.actions.push(dispose);
|
|
15
|
+
}
|
|
16
|
+
disposeAll() {
|
|
17
|
+
if (this.isDisposed) return;
|
|
18
|
+
this.isDisposed = true;
|
|
19
|
+
for (const dispose of this.actions.splice(0).toReversed()) dispose();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The collected resources as one idempotent disposer, for the object that will own them.
|
|
23
|
+
*/
|
|
24
|
+
toDisposer() {
|
|
25
|
+
return () => {
|
|
26
|
+
this.disposeAll();
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { Disposables };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Deferred } from "../../../core/src/shared/async/Deferred.js";
|
|
2
|
+
import "../../../core/src/index.js";
|
|
3
|
+
import { BlobRandomAccessSource } from "../../../adapters/blob/src/BlobRandomAccessSource.js";
|
|
4
|
+
import "../../../adapters/blob/src/index.js";
|
|
5
|
+
import { HttpRangeSource } from "../../../adapters/fetch/src/HttpRangeSource.js";
|
|
6
|
+
import { HttpResourceLocator } from "../../../adapters/fetch/src/HttpResourceLocator.js";
|
|
7
|
+
import "../../../adapters/fetch/src/index.js";
|
|
8
|
+
import { MediabunnyDemuxer } from "../../../adapters/mediabunny/src/MediabunnyDemuxer.js";
|
|
9
|
+
import "../../../adapters/mediabunny/src/index.js";
|
|
10
|
+
import { WebCodecsVideoDecoderPort } from "../../../adapters/webcodecs/src/WebCodecsVideoDecoderPort.js";
|
|
11
|
+
import "../../../adapters/webcodecs/src/index.js";
|
|
12
|
+
import { isUrlInput } from "../PlayerSource.js";
|
|
13
|
+
//#region ../../packages/player/src/composition/browserPorts.ts
|
|
14
|
+
var DEFAULT_PROBE_TIMEOUT_MS = 15e3;
|
|
15
|
+
/**
|
|
16
|
+
* The real adapters behind the player in a browser.
|
|
17
|
+
*/
|
|
18
|
+
function browserPorts(options = {}) {
|
|
19
|
+
const http = options.http ?? {};
|
|
20
|
+
const probeTimeoutMs = options.probeTimeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
21
|
+
return {
|
|
22
|
+
sources: browserSources(http),
|
|
23
|
+
demuxer: new MediabunnyDemuxer(),
|
|
24
|
+
decoderPort: new WebCodecsVideoDecoderPort(),
|
|
25
|
+
locator: new HttpResourceLocator(http),
|
|
26
|
+
probeDeadline: () => deadlineIn(probeTimeoutMs)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* URLs are read with HTTP ranges, blobs by slicing.
|
|
31
|
+
*/
|
|
32
|
+
function browserSources(http) {
|
|
33
|
+
return { open: (input, signal) => isUrlInput(input) ? new HttpRangeSource(input.url, http, signal) : new BlobRandomAccessSource(input.blob) };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A deadline that passes after `ms`: the core has no timers, so the host supplies deadlines.
|
|
37
|
+
*/
|
|
38
|
+
function deadlineIn(ms) {
|
|
39
|
+
const deadline = new Deferred();
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
deadline.resolve();
|
|
42
|
+
}, ms);
|
|
43
|
+
return deadline;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { browserPorts, browserSources };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { messageOf } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
import { milliseconds, seconds } from "../../../core/src/shared/units/time.js";
|
|
3
|
+
import { stabilizerFor } from "../../../core/src/domain/motion/stabilization/stabilizers.js";
|
|
4
|
+
import { buildStitchingSetup } from "../../../core/src/domain/stitching/StitchingSetup.js";
|
|
5
|
+
import { StabilizingFrameSink } from "../../../core/src/application/stabilization/StabilizingFrameSink.js";
|
|
6
|
+
import { GainMatchingFrameSink } from "../../../core/src/application/gainMatching/GainMatchingFrameSink.js";
|
|
7
|
+
import { WallClock } from "../../../core/src/application/playback/WallClock.js";
|
|
8
|
+
import { PlaybackSession } from "../../../core/src/application/playback/PlaybackSession.js";
|
|
9
|
+
import "../../../core/src/index.js";
|
|
10
|
+
import { MediaSourceAudioClock } from "../../../adapters/mse-audio/src/MediaSourceAudioClock.js";
|
|
11
|
+
import "../../../adapters/mse-audio/src/index.js";
|
|
12
|
+
import { ThreeFrameRenderer } from "../../../adapters/three/src/ThreeFrameRenderer.js";
|
|
13
|
+
import "../../../adapters/three/src/index.js";
|
|
14
|
+
import { Disposables } from "./Disposables.js";
|
|
15
|
+
var DECODE_PIPELINE_OPTIONS = {
|
|
16
|
+
maxPendingPackets: 4,
|
|
17
|
+
pairTolerance: seconds(5e-4)
|
|
18
|
+
};
|
|
19
|
+
var NO_AUDIO_WARNING = "the recording has no audio track; playback follows a silent clock";
|
|
20
|
+
var AUDIO_UNSUPPORTED_WARNING = "this browser cannot play the audio track through Media Source Extensions; playback follows a silent clock";
|
|
21
|
+
var AUDIO_FAILED_WARNING = "the audio track could not be prepared for playback; playback follows a silent clock";
|
|
22
|
+
/**
|
|
23
|
+
* Assembles the playing parts for an opened recording: the clock the picture follows, the GPU
|
|
24
|
+
* stitcher, the stabilizing and gain-matching sinks in front of it and the session driving them.
|
|
25
|
+
* Nothing here decides anything about the file; every choice was made while opening it.
|
|
26
|
+
*/
|
|
27
|
+
async function buildPipeline(parts) {
|
|
28
|
+
const disposables = new Disposables();
|
|
29
|
+
try {
|
|
30
|
+
const clock = await clockFor(parts);
|
|
31
|
+
disposables.add(() => {
|
|
32
|
+
clock.clock.dispose();
|
|
33
|
+
});
|
|
34
|
+
parts.signal.throwIfAborted();
|
|
35
|
+
const drawing = drawingFor(parts, disposables);
|
|
36
|
+
const session = sessionFor(parts, clock.clock, drawing.gainMatching);
|
|
37
|
+
disposables.add(() => {
|
|
38
|
+
session.dispose();
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
session,
|
|
42
|
+
renderer: drawing.renderer,
|
|
43
|
+
...pictureSettingsOf(drawing, session),
|
|
44
|
+
soundWarnings: clock.warnings,
|
|
45
|
+
dispose: disposables.toDisposer()
|
|
46
|
+
};
|
|
47
|
+
} catch (error) {
|
|
48
|
+
disposables.disposeAll();
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The GPU stitcher on the host canvas, behind the stabilizing sink when there is a gyro, behind
|
|
54
|
+
* gain matching, which measures what the stitcher drew.
|
|
55
|
+
*/
|
|
56
|
+
function drawingFor(parts, disposables) {
|
|
57
|
+
const renderer = ThreeFrameRenderer.create(parts.host.canvas, stitchingSetupOf(parts.opened));
|
|
58
|
+
disposables.add(() => {
|
|
59
|
+
renderer.dispose();
|
|
60
|
+
});
|
|
61
|
+
const { sink, stabilizing } = sinkOver(renderer, parts.opened);
|
|
62
|
+
const gainMatching = new GainMatchingFrameSink(sink, renderer);
|
|
63
|
+
disposables.add(() => {
|
|
64
|
+
gainMatching.dispose();
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
renderer,
|
|
68
|
+
stabilizing,
|
|
69
|
+
gainMatching
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Each setting goes to the sink it concerns, and the frame standing on screen is drawn again, so
|
|
74
|
+
* the change shows while paused.
|
|
75
|
+
*/
|
|
76
|
+
function pictureSettingsOf(drawing, session) {
|
|
77
|
+
return {
|
|
78
|
+
setStabilization: (mode) => {
|
|
79
|
+
drawing.stabilizing?.setStabilizer(stabilizerFor(mode));
|
|
80
|
+
session.redraw();
|
|
81
|
+
},
|
|
82
|
+
setQuality: (quality) => {
|
|
83
|
+
drawing.renderer.setQuality(quality);
|
|
84
|
+
},
|
|
85
|
+
setGainMatching: (isEnabled) => {
|
|
86
|
+
if (isEnabled) drawing.gainMatching.enable();
|
|
87
|
+
else drawing.gainMatching.disable();
|
|
88
|
+
session.redraw();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The recording's own audio when there is a track this browser can feed to the element;
|
|
94
|
+
* otherwise a wall clock, with a warning saying why there is no sound.
|
|
95
|
+
*/
|
|
96
|
+
async function clockFor(parts) {
|
|
97
|
+
const { audioTrack } = parts.opened;
|
|
98
|
+
if (!audioTrack) return wallClock(NO_AUDIO_WARNING);
|
|
99
|
+
try {
|
|
100
|
+
const segments = await audioTrack.openSegments();
|
|
101
|
+
if (!MediaSourceAudioClock.isSupported(segments)) return wallClock(AUDIO_UNSUPPORTED_WARNING);
|
|
102
|
+
parts.signal.throwIfAborted();
|
|
103
|
+
return {
|
|
104
|
+
clock: await MediaSourceAudioClock.open(parts.host.audio, segments),
|
|
105
|
+
warnings: []
|
|
106
|
+
};
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return wallClock(`${AUDIO_FAILED_WARNING} (${messageOf(error)})`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A clock over the monotonic page time, which a change of the system clock does not move.
|
|
113
|
+
*/
|
|
114
|
+
function wallClock(warning) {
|
|
115
|
+
return {
|
|
116
|
+
clock: new WallClock(() => milliseconds(performance.now())),
|
|
117
|
+
warnings: [warning]
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function stitchingSetupOf(opened) {
|
|
121
|
+
return buildStitchingSetup({
|
|
122
|
+
calibration: opened.calibration,
|
|
123
|
+
layout: opened.layout
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function sinkOver(renderer, opened) {
|
|
127
|
+
if (!opened.motion) return {
|
|
128
|
+
sink: renderer,
|
|
129
|
+
stabilizing: void 0
|
|
130
|
+
};
|
|
131
|
+
const stabilizing = new StabilizingFrameSink({
|
|
132
|
+
sink: renderer,
|
|
133
|
+
orientations: opened.motion.orientations,
|
|
134
|
+
frameTimes: opened.frameTimes
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
sink: stabilizing,
|
|
138
|
+
stabilizing
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function sessionFor(parts, clock, sink) {
|
|
142
|
+
return new PlaybackSession({
|
|
143
|
+
frameSources: parts.opened.frameSources,
|
|
144
|
+
decoderPort: parts.decoderPort,
|
|
145
|
+
clock,
|
|
146
|
+
sink,
|
|
147
|
+
duration: parts.opened.duration,
|
|
148
|
+
pipeline: DECODE_PIPELINE_OPTIONS,
|
|
149
|
+
queueCapacity: 4
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
export { DECODE_PIPELINE_OPTIONS, buildPipeline };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { GyroViewError } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
import { probeDecoding } from "../../../core/src/application/playback/probeDecoding.js";
|
|
3
|
+
import "../../../core/src/index.js";
|
|
4
|
+
//#region ../../packages/player/src/composition/ensureDecodable.ts
|
|
5
|
+
/**
|
|
6
|
+
* Probes that this platform decodes every frame source, and refuses the recording with the
|
|
7
|
+
* cause when it does not: the browser's, the file's or the network's.
|
|
8
|
+
*/
|
|
9
|
+
async function ensureDecodable(frameSources, attempt) {
|
|
10
|
+
const { ports, signal } = attempt;
|
|
11
|
+
const report = await probeDecoding(frameSources, ports.decoderPort, probeDeadlineOf(attempt));
|
|
12
|
+
signal.throwIfAborted();
|
|
13
|
+
if (!report.canDecode) throw probeFailure(report);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The failures that are not the browser's, by the verdict every failed track shares: a file
|
|
17
|
+
* without a key frame to start from, or a network too slow to bring one before the deadline.
|
|
18
|
+
*/
|
|
19
|
+
var FAILURES_NOT_OF_THE_BROWSER = {
|
|
20
|
+
"no-key-frame": {
|
|
21
|
+
code: "no-key-frame",
|
|
22
|
+
message: "the recording has no key frame to start decoding from"
|
|
23
|
+
},
|
|
24
|
+
"key-frame-late": {
|
|
25
|
+
code: "source-unreadable",
|
|
26
|
+
message: "the recording's first frames did not arrive in time"
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var BROWSER_CANNOT_DECODE = {
|
|
30
|
+
code: "codec-unsupported",
|
|
31
|
+
message: "this browser cannot decode the recording"
|
|
32
|
+
};
|
|
33
|
+
function probeFailure(report) {
|
|
34
|
+
const cause = sharedCause(report);
|
|
35
|
+
const { code, message } = (cause === void 0 ? void 0 : FAILURES_NOT_OF_THE_BROWSER[cause]) ?? BROWSER_CANNOT_DECODE;
|
|
36
|
+
return new GyroViewError(code, `${message}: ${describeProbe(report)}`);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The verdict every failed track shares, if one does. A decoder still busy at the deadline beside
|
|
40
|
+
* a key frame that never came is taken for the same slow link's doing.
|
|
41
|
+
*/
|
|
42
|
+
function sharedCause(report) {
|
|
43
|
+
const verdicts = new Set(report.sources.filter((source) => source.verdict !== "decodes").map((source) => source.verdict));
|
|
44
|
+
if (verdicts.has("key-frame-late")) verdicts.delete("timed-out");
|
|
45
|
+
const [shared] = verdicts;
|
|
46
|
+
return verdicts.size === 1 ? shared : void 0;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The probe's deadline, brought forward by an abort: a superseded load lets its probe decoders
|
|
50
|
+
* go at once, which matters where decoders are few (iOS Safari).
|
|
51
|
+
*/
|
|
52
|
+
function probeDeadlineOf(attempt) {
|
|
53
|
+
const deadline = attempt.ports.probeDeadline();
|
|
54
|
+
attempt.signal.addEventListener("abort", () => {
|
|
55
|
+
deadline.resolve();
|
|
56
|
+
}, { once: true });
|
|
57
|
+
return deadline;
|
|
58
|
+
}
|
|
59
|
+
function describeProbe(report) {
|
|
60
|
+
return report.sources.filter((lens) => lens.verdict !== "decodes").map((lens) => `track ${lens.track.trackIndex} ${lens.verdict}${lens.detail ? ` (${lens.detail})` : ""}`).join("; ");
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
export { ensureDecodable };
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { GyroViewError, ensureInvariant, hasErrorCode } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
import { seconds } from "../../../core/src/shared/units/time.js";
|
|
3
|
+
import { readRecording } from "../../../core/src/application/recording/readRecording.js";
|
|
4
|
+
import { timeRecording } from "../../../core/src/application/recording/timeRecording.js";
|
|
5
|
+
import { detectLensLayout } from "../../../core/src/domain/format/layout/detectLensLayout.js";
|
|
6
|
+
import { lensFrameOrder } from "../../../core/src/domain/stitching/StitchingSetup.js";
|
|
7
|
+
import "../../../core/src/index.js";
|
|
8
|
+
import { inputName } from "../PlayerSource.js";
|
|
9
|
+
import { Disposables } from "./Disposables.js";
|
|
10
|
+
import { ensureDecodable } from "./ensureDecodable.js";
|
|
11
|
+
//#region ../../packages/player/src/composition/openInputs.ts
|
|
12
|
+
/**
|
|
13
|
+
* Opens the given inputs as one recording, its metadata from the input that carries the
|
|
14
|
+
* trailer. Everything opened is released again when any step fails or the attempt is aborted.
|
|
15
|
+
*/
|
|
16
|
+
async function openInputs(inputs, attempt) {
|
|
17
|
+
const disposables = new Disposables();
|
|
18
|
+
try {
|
|
19
|
+
const demuxed = await demuxInputs(inputs, attempt, disposables);
|
|
20
|
+
const layout = detectLensLayout(demuxed.inputs, demuxed.recording.info);
|
|
21
|
+
const frameSources = lensFrameOrder(layout).map((key) => trackAt(demuxed.inputs, key));
|
|
22
|
+
const calibration = calibrationOf(demuxed.recording);
|
|
23
|
+
await ensureDecodable(frameSources, attempt);
|
|
24
|
+
const timing = await timeRecording(demuxed.recording, frameSources[0]);
|
|
25
|
+
attempt.signal.throwIfAborted();
|
|
26
|
+
return assemble({
|
|
27
|
+
demuxed,
|
|
28
|
+
layout,
|
|
29
|
+
frameSources,
|
|
30
|
+
calibration,
|
|
31
|
+
timing
|
|
32
|
+
}, disposables);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
disposables.disposeAll();
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function demuxInputs(inputs, attempt, disposables) {
|
|
39
|
+
const { ports, signal } = attempt;
|
|
40
|
+
const given = inputs.map((input) => ({
|
|
41
|
+
input,
|
|
42
|
+
source: ports.sources.open(input, signal)
|
|
43
|
+
}));
|
|
44
|
+
const recording = await readRecordingOf(given, ports);
|
|
45
|
+
signal.throwIfAborted();
|
|
46
|
+
const openings = [...given, ...await declaredSecond(given, recording, attempt)];
|
|
47
|
+
const settled = await Promise.allSettled(openings.map(({ input, source }) => ports.demuxer.open(source, inputName(input))));
|
|
48
|
+
const opened = settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
49
|
+
for (const input of opened) disposables.add(() => {
|
|
50
|
+
input.dispose();
|
|
51
|
+
});
|
|
52
|
+
const failure = settled.find((result) => result.status === "rejected");
|
|
53
|
+
if (failure) throw failure.reason;
|
|
54
|
+
signal.throwIfAborted();
|
|
55
|
+
return {
|
|
56
|
+
recording,
|
|
57
|
+
inputs: opened
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The other lens file a lone file's info record says the recording is split into, when the
|
|
62
|
+
* server has it: found before the lone file's tracks are read, which finding it only once they
|
|
63
|
+
* fell short would read twice. A file that does not say so still gets its sibling looked for
|
|
64
|
+
* once its tracks fall short (see openRecording).
|
|
65
|
+
*/
|
|
66
|
+
async function declaredSecond(given, recording, attempt) {
|
|
67
|
+
if (!(given.length === 1 && recording.info.fileLayout === "split-files") || !attempt.findSecondFile) return [];
|
|
68
|
+
const url = await attempt.findSecondFile();
|
|
69
|
+
attempt.signal.throwIfAborted();
|
|
70
|
+
if (url === void 0) return [];
|
|
71
|
+
const input = { url };
|
|
72
|
+
return [{
|
|
73
|
+
input,
|
|
74
|
+
source: attempt.ports.sources.open(input, attempt.signal)
|
|
75
|
+
}];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The layout hints of a file without an info record: the tracks alone decide.
|
|
79
|
+
*/
|
|
80
|
+
var NO_HINTS = {
|
|
81
|
+
fileLayout: void 0,
|
|
82
|
+
trackOrder: void 0
|
|
83
|
+
};
|
|
84
|
+
async function readRecordingOf(openings, ports) {
|
|
85
|
+
try {
|
|
86
|
+
return await readTrailerCarrier(openings.map(({ source }) => source));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
const [only] = openings;
|
|
89
|
+
if (openings.length === 1 && hasErrorCode(error, "invalid-trailer") && only) await refuseAsLoneHalf(only, ports);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A file without a trailer may be the _10_ half of an older camera's pair, which writes the
|
|
95
|
+
* trailer to _00_ alone: its one square track tells, and the refusal asks for the other file
|
|
96
|
+
* instead of calling it damaged. Anything else leaves the trailer's own refusal standing.
|
|
97
|
+
*/
|
|
98
|
+
async function refuseAsLoneHalf(opening, ports) {
|
|
99
|
+
let input;
|
|
100
|
+
try {
|
|
101
|
+
input = await ports.demuxer.open(opening.source, inputName(opening.input));
|
|
102
|
+
} catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
detectLensLayout([input], NO_HINTS);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (hasErrorCode(error, "missing-second-file")) throw error;
|
|
109
|
+
} finally {
|
|
110
|
+
input.dispose();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The recording read from the input that carries the trailer: the first, or the second when the
|
|
115
|
+
* first has none, as the _10_ file of an older camera's pair, which writes it to _00_ alone,
|
|
116
|
+
* when the pair is given the other way round.
|
|
117
|
+
*/
|
|
118
|
+
async function readTrailerCarrier(sources) {
|
|
119
|
+
const [first, second] = sources;
|
|
120
|
+
ensureInvariant(first !== void 0, "a recording needs at least one input");
|
|
121
|
+
try {
|
|
122
|
+
return await readRecording(first);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (second === void 0 || !hasErrorCode(error, "invalid-trailer")) throw error;
|
|
125
|
+
return readRecording(second);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function trackAt(inputs, key) {
|
|
129
|
+
const track = inputs[key.inputIndex]?.videoTracks[key.trackIndex];
|
|
130
|
+
ensureInvariant(track !== void 0, `the layout points at track ${key.trackIndex} of input ${key.inputIndex}, which does not exist`);
|
|
131
|
+
return track;
|
|
132
|
+
}
|
|
133
|
+
function calibrationOf(recording) {
|
|
134
|
+
const { calibration, warnings } = recording.calibration;
|
|
135
|
+
if (calibration) return calibration;
|
|
136
|
+
const detail = warnings.length > 0 ? ` (${warnings.join("; ")})` : "";
|
|
137
|
+
throw new GyroViewError("no-calibration", `the recording carries no usable lens calibration, so it cannot be stitched${detail}`);
|
|
138
|
+
}
|
|
139
|
+
function assemble(parts, disposables) {
|
|
140
|
+
const { demuxed, layout, frameSources, calibration, timing } = parts;
|
|
141
|
+
const duration = seconds(Math.min(...demuxed.inputs.map((input) => input.duration)));
|
|
142
|
+
const [audioTrack] = demuxed.inputs.flatMap((input) => input.audioTracks);
|
|
143
|
+
return {
|
|
144
|
+
recording: demuxed.recording,
|
|
145
|
+
layout,
|
|
146
|
+
frameSources,
|
|
147
|
+
calibration,
|
|
148
|
+
duration,
|
|
149
|
+
frameTimes: timing.frameTimes,
|
|
150
|
+
motion: timing.motion,
|
|
151
|
+
audioTrack,
|
|
152
|
+
metadata: metadataOf(parts, duration, audioTrack !== void 0),
|
|
153
|
+
warnings: timing.warnings,
|
|
154
|
+
dispose: disposables.toDisposer()
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function metadataOf(parts, duration, hasAudio) {
|
|
158
|
+
const { info } = parts.demuxed.recording;
|
|
159
|
+
const { motion } = parts.timing;
|
|
160
|
+
return {
|
|
161
|
+
model: info.model,
|
|
162
|
+
firmware: info.firmware,
|
|
163
|
+
captureMode: info.captureMode,
|
|
164
|
+
layout: parts.layout.kind,
|
|
165
|
+
layoutEvidence: parts.layout.evidence,
|
|
166
|
+
tracks: parts.frameSources.map((track) => track.description),
|
|
167
|
+
calibrationVersion: parts.calibration.version,
|
|
168
|
+
frameTimeSource: parts.timing.frameTimeSource,
|
|
169
|
+
hasGyro: motion !== void 0,
|
|
170
|
+
imuFrame: motion && {
|
|
171
|
+
name: motion.imuFrame.name,
|
|
172
|
+
isVerified: motion.imuFrame.isVerified
|
|
173
|
+
},
|
|
174
|
+
hasAudio,
|
|
175
|
+
duration
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
export { openInputs };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { hasErrorCode } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
import { lazy } from "../../../core/src/shared/lazy.js";
|
|
3
|
+
import { locateOtherLensFile } from "../../../core/src/application/recording/locateOtherLensFile.js";
|
|
4
|
+
import "../../../core/src/index.js";
|
|
5
|
+
import { isUrlInput } from "../PlayerSource.js";
|
|
6
|
+
import { openInputs } from "./openInputs.js";
|
|
7
|
+
//#region ../../packages/player/src/composition/openRecording.ts
|
|
8
|
+
/**
|
|
9
|
+
* Use case at the composition root: opens what a source names, following the data. A lone file
|
|
10
|
+
* of a split-file pair fetches its sibling when the server has it. Only the recording itself
|
|
11
|
+
* ever plays (ADR 0017). Aborting releases everything opened so far.
|
|
12
|
+
*/
|
|
13
|
+
async function openRecording(source, ports, signal) {
|
|
14
|
+
const attempt = attemptFor(source, ports, signal);
|
|
15
|
+
const inputs = source.second ? [source.main, source.second] : [source.main];
|
|
16
|
+
try {
|
|
17
|
+
return await openInputs(inputs, attempt);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
const { findSecondFile } = attempt;
|
|
20
|
+
if (!findSecondFile || !hasErrorCode(error, "missing-second-file")) throw error;
|
|
21
|
+
const second = await findSecondFile();
|
|
22
|
+
signal.throwIfAborted();
|
|
23
|
+
if (!second) throw error;
|
|
24
|
+
return openInputs([source.main, { url: second }], attempt);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* How the source is opened: a lone file named by URL may have its other lens file beside it.
|
|
29
|
+
*/
|
|
30
|
+
function attemptFor(source, ports, signal) {
|
|
31
|
+
const { main } = source;
|
|
32
|
+
return source.second === void 0 && isUrlInput(main) ? {
|
|
33
|
+
ports,
|
|
34
|
+
signal,
|
|
35
|
+
findSecondFile: lazy(() => locateOtherLensFile(main.url, ports.locator))
|
|
36
|
+
} : {
|
|
37
|
+
ports,
|
|
38
|
+
signal
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { openRecording };
|