@bvdmitri/gyroview 0.0.0-stage → 0.9.0-cutaway.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +402 -0
- package/LICENSE +21 -0
- package/README.md +313 -2
- package/dist/define.d.ts +3 -0
- package/dist/define.js +4 -0
- package/dist/index.d.ts +1204 -0
- package/dist/index.js +12 -0
- package/dist/packages/adapters/blob/src/BlobRandomAccessSource.js +36 -0
- package/dist/packages/adapters/fetch/src/HttpByteStream.js +173 -0
- package/dist/packages/adapters/fetch/src/HttpRangeSource.js +37 -0
- package/dist/packages/adapters/fetch/src/HttpResource.js +227 -0
- package/dist/packages/adapters/fetch/src/HttpResourceLocator.js +35 -0
- package/dist/packages/adapters/fetch/src/contentRange.js +15 -0
- package/dist/packages/adapters/fetch/src/httpRequest.js +136 -0
- package/dist/packages/adapters/fetch/src/passingFailures.js +23 -0
- package/dist/packages/adapters/fetch/src/rangeFailures.js +16 -0
- package/dist/packages/adapters/fetch/src/recordingVersion.js +37 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyAudioPackager.js +162 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyCodecReader.js +93 -0
- package/dist/packages/adapters/mediabunny/src/SegmentChannel.js +135 -0
- package/dist/packages/adapters/mediabunny/src/bufferSources.js +12 -0
- package/dist/packages/adapters/mediabunny/src/decoderConfigurations.js +43 -0
- package/dist/packages/adapters/mediabunny/src/trackColour.js +20 -0
- package/dist/packages/adapters/mse-audio/src/MediaSourceAudioClock.js +138 -0
- package/dist/packages/adapters/mse-audio/src/SourceBufferFeeder.js +263 -0
- package/dist/packages/adapters/mse-audio/src/events.js +26 -0
- package/dist/packages/adapters/mse-audio/src/mediaSourceSupport.js +81 -0
- package/dist/packages/adapters/three/src/ThreeFrameRenderer.js +215 -0
- package/dist/packages/adapters/three/src/compileAndProve.js +14 -0
- package/dist/packages/adapters/three/src/conversionUniforms.js +60 -0
- package/dist/packages/adapters/three/src/drawSchedules.js +13 -0
- package/dist/packages/adapters/three/src/fullscreenPass.js +52 -0
- package/dist/packages/adapters/three/src/glslLiterals.js +10 -0
- package/dist/packages/adapters/three/src/lensTextures.js +34 -0
- package/dist/packages/adapters/three/src/matrixCorrections.js +34 -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/rendererParts.js +108 -0
- package/dist/packages/adapters/three/src/rendererUniforms.js +176 -0
- package/dist/packages/adapters/three/src/samplingStrategies.js +26 -0
- package/dist/packages/adapters/three/src/scissorBox.js +20 -0
- package/dist/packages/adapters/three/src/seamMeter/SeamMeterPass.js +85 -0
- package/dist/packages/adapters/three/src/seamMeter/rowMeans.js +42 -0
- package/dist/packages/adapters/three/src/shaderPrograms.js +68 -0
- package/dist/packages/adapters/three/src/shaders/analysis.frag.js +4 -0
- package/dist/packages/adapters/three/src/shaders/displayConversion.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/threeMatrix.js +10 -0
- package/dist/packages/adapters/three/src/webglRenderer.js +37 -0
- package/dist/packages/adapters/webcodecs/src/WebCodecsVideoDecoderPort.js +152 -0
- package/dist/packages/adapters/webcodecs/src/colorSpaceOf.js +32 -0
- package/dist/packages/core/src/application/download/BlockStore.js +97 -0
- package/dist/packages/core/src/application/download/DownloadedAudioSamples.js +50 -0
- package/dist/packages/core/src/application/download/DownloadedVideoTrack.js +122 -0
- package/dist/packages/core/src/application/download/FileDownload.js +208 -0
- package/dist/packages/core/src/application/download/RecordingBuffer.js +22 -0
- package/dist/packages/core/src/application/download/SampleCursor.js +89 -0
- package/dist/packages/core/src/application/download/SourceByteStream.js +46 -0
- package/dist/packages/core/src/application/download/Transfers.js +82 -0
- package/dist/packages/core/src/application/download/startFileDownload.js +37 -0
- package/dist/packages/core/src/application/gainMatching/GainMatching.js +76 -0
- package/dist/packages/core/src/application/gainMatching/GainMatchingFrameSink.js +80 -0
- package/dist/packages/core/src/application/gainMatching/referenceLensOf.js +18 -0
- package/dist/packages/core/src/application/playback/Buffering.js +54 -0
- package/dist/packages/core/src/application/playback/ClockStart.js +55 -0
- package/dist/packages/core/src/application/playback/ClockWatch.js +76 -0
- package/dist/packages/core/src/application/playback/DecodePipeline.js +234 -0
- package/dist/packages/core/src/application/playback/DecodeRun.js +108 -0
- package/dist/packages/core/src/application/playback/FramePairQueue.js +93 -0
- package/dist/packages/core/src/application/playback/FramePairer.js +77 -0
- package/dist/packages/core/src/application/playback/PictureOnScreen.js +56 -0
- package/dist/packages/core/src/application/playback/PlaybackSession.js +410 -0
- package/dist/packages/core/src/application/playback/SeekOrder.js +35 -0
- package/dist/packages/core/src/application/playback/SessionLifecycle.js +42 -0
- package/dist/packages/core/src/application/playback/StartGate.js +69 -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 +182 -0
- package/dist/packages/core/src/application/recording/frameTimesOf.js +21 -0
- package/dist/packages/core/src/application/recording/inspectLayout.js +21 -0
- package/dist/packages/core/src/application/recording/inspectRecording.js +125 -0
- package/dist/packages/core/src/application/recording/locateOtherLensFile.js +18 -0
- package/dist/packages/core/src/application/recording/motionOf.js +43 -0
- package/dist/packages/core/src/application/recording/readRecording.js +31 -0
- package/dist/packages/core/src/application/recording/readRecordingFiles.js +130 -0
- package/dist/packages/core/src/application/recording/readSampleTable.js +27 -0
- package/dist/packages/core/src/application/recording/timeRecording.js +57 -0
- package/dist/packages/core/src/application/stabilization/StabilizingFrameSink.js +33 -0
- package/dist/packages/core/src/domain/colour/DisplayConversion.js +137 -0
- package/dist/packages/core/src/domain/colour/TrackColour.js +38 -0
- package/dist/packages/core/src/domain/colour/gainMatch.js +64 -0
- package/dist/packages/core/src/domain/colour/matrixCorrection.js +76 -0
- package/dist/packages/core/src/domain/container/KeyframeRule.js +7 -0
- package/dist/packages/core/src/domain/container/SampleTable.js +29 -0
- package/dist/packages/core/src/domain/container/TrackSampleTable.js +96 -0
- package/dist/packages/core/src/domain/container/presentationOrder.js +10 -0
- package/dist/packages/core/src/domain/download/DownloadPolicy.js +84 -0
- package/dist/packages/core/src/domain/download/isReadyToResume.js +27 -0
- package/dist/packages/core/src/domain/download/planDownloads.js +175 -0
- package/dist/packages/core/src/domain/download/trackNeeds.js +32 -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/boxHeader.js +44 -0
- package/dist/packages/core/src/domain/format/boxes/scanBoxes.js +32 -0
- package/dist/packages/core/src/domain/format/calibration/CalibrationVersion.js +16 -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 +46 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/MeiCalibrationLayout.js +121 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/PolynomialCalibrationLayout.js +46 -0
- package/dist/packages/core/src/domain/format/calibration/offsetTokens.js +94 -0
- package/dist/packages/core/src/domain/format/calibration/parseOffsetString.js +77 -0
- package/dist/packages/core/src/domain/format/calibration/selectCalibration.js +59 -0
- package/dist/packages/core/src/domain/format/calibration/v6TermReading.js +20 -0
- package/dist/packages/core/src/domain/format/captureOrigin.js +34 -0
- package/dist/packages/core/src/domain/format/constants.js +35 -0
- package/dist/packages/core/src/domain/format/info/calibrationSources.js +27 -0
- package/dist/packages/core/src/domain/format/info/infoFields.js +116 -0
- package/dist/packages/core/src/domain/format/info/parseInfoRecord.js +107 -0
- package/dist/packages/core/src/domain/format/info/readInfoRecord.js +14 -0
- package/dist/packages/core/src/domain/format/layout/detectLensLayout.js +133 -0
- package/dist/packages/core/src/domain/format/mp4/hevcCodecString.js +161 -0
- package/dist/packages/core/src/domain/format/mp4/keyframeRules.js +81 -0
- package/dist/packages/core/src/domain/format/mp4/movieBoxes.js +52 -0
- package/dist/packages/core/src/domain/format/mp4/mp4BoxTypes.js +46 -0
- package/dist/packages/core/src/domain/format/mp4/mp4Layouts.js +183 -0
- package/dist/packages/core/src/domain/format/mp4/parseMovie.js +72 -0
- package/dist/packages/core/src/domain/format/mp4/readTable.js +31 -0
- package/dist/packages/core/src/domain/format/mp4/sampleLocations.js +76 -0
- package/dist/packages/core/src/domain/format/mp4/sampleTiming.js +120 -0
- package/dist/packages/core/src/domain/format/mp4/trackHeaders.js +88 -0
- package/dist/packages/core/src/domain/format/naming/RecordingFileName.js +70 -0
- package/dist/packages/core/src/domain/format/records/TrailerRecords.js +100 -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 +38 -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 +89 -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 +138 -0
- package/dist/packages/core/src/domain/format/records/trim/parseTrimRecord.js +24 -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 +60 -0
- package/dist/packages/core/src/domain/motion/exposure/ExposureRecord.js +48 -0
- package/dist/packages/core/src/domain/motion/gyro/GyroTrack.js +70 -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 +178 -0
- package/dist/packages/core/src/domain/motion/mounting/Mounting.js +131 -0
- package/dist/packages/core/src/domain/motion/orientation/OrientationTrack.js +142 -0
- package/dist/packages/core/src/domain/motion/orientation/estimateGyroBias.js +72 -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 +85 -0
- package/dist/packages/core/src/domain/motion/timing/frameTimeSources.js +58 -0
- package/dist/packages/core/src/domain/motion/timing/resolveFrameTimes.js +68 -0
- package/dist/packages/core/src/domain/optics/EquidistantModel.js +34 -0
- package/dist/packages/core/src/domain/optics/MeiDistortion.js +27 -0
- package/dist/packages/core/src/domain/optics/MeiModel.js +27 -0
- package/dist/packages/core/src/domain/optics/PolynomialModel.js +39 -0
- package/dist/packages/core/src/domain/optics/lensPose.js +49 -0
- package/dist/packages/core/src/domain/optics/opticsConstants.js +5 -0
- package/dist/packages/core/src/domain/optics/scaledProjection.js +30 -0
- package/dist/packages/core/src/domain/playback/PlayerState.js +83 -0
- package/dist/packages/core/src/domain/rendition/RenditionSpan.js +70 -0
- package/dist/packages/core/src/domain/stitching/LensLayout.js +21 -0
- package/dist/packages/core/src/domain/stitching/StitchingSetup.js +87 -0
- package/dist/packages/core/src/domain/stitching/seamRing.js +4 -0
- package/dist/packages/core/src/domain/view/Framing.js +37 -0
- package/dist/packages/core/src/domain/view/Picture.js +12 -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 +54 -0
- package/dist/packages/core/src/domain/view/lensTilesView.js +64 -0
- package/dist/packages/core/src/domain/view/magnification.js +103 -0
- package/dist/packages/core/src/domain/view/motionLookView.js +100 -0
- package/dist/packages/core/src/domain/view/normalView.js +37 -0
- package/dist/packages/core/src/domain/view/panoramaView.js +95 -0
- package/dist/packages/core/src/domain/view/rectilinear.js +53 -0
- package/dist/packages/core/src/domain/view/screenLayout.js +129 -0
- package/dist/packages/core/src/domain/view/screenLook.js +74 -0
- package/dist/packages/core/src/domain/view/viewGestures.js +104 -0
- package/dist/packages/core/src/domain/view/viewModes.js +31 -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/async/iteration.js +24 -0
- package/dist/packages/core/src/shared/binary/ByteRange.js +50 -0
- package/dist/packages/core/src/shared/binary/ByteRangeSet.js +85 -0
- package/dist/packages/core/src/shared/binary/ByteReader.js +104 -0
- package/dist/packages/core/src/shared/binary/concatenated.js +15 -0
- package/dist/packages/core/src/shared/errors/GyroViewError.js +140 -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/mapRecord.js +11 -0
- package/dist/packages/core/src/shared/math/Matrix3.js +121 -0
- package/dist/packages/core/src/shared/math/Quaternion.js +133 -0
- package/dist/packages/core/src/shared/math/Vector3.js +58 -0
- package/dist/packages/core/src/shared/math/clamp.js +9 -0
- package/dist/packages/core/src/shared/math/countAtOrBelow.js +31 -0
- package/dist/packages/core/src/shared/math/minimum.js +17 -0
- package/dist/packages/core/src/shared/protobuf/ProtobufMessage.js +143 -0
- package/dist/packages/core/src/shared/protobuf/wireFormat.js +16 -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 +14 -0
- package/dist/packages/player/src/browserPlayer.js +20 -0
- package/dist/packages/player/src/choices.js +24 -0
- package/dist/packages/player/src/composition/AnimationFrameDraws.js +38 -0
- package/dist/packages/player/src/composition/BrowserAttitudeSensor.js +191 -0
- package/dist/packages/player/src/composition/Disposables.js +31 -0
- package/dist/packages/player/src/composition/DrawingBufferFit.js +119 -0
- package/dist/packages/player/src/composition/animationFrames.js +9 -0
- package/dist/packages/player/src/composition/attitudeSensor.js +18 -0
- package/dist/packages/player/src/composition/browserPorts.js +80 -0
- package/dist/packages/player/src/composition/buildPipeline.js +186 -0
- package/dist/packages/player/src/composition/ensureDecodable.js +62 -0
- package/dist/packages/player/src/composition/openInputs.js +120 -0
- package/dist/packages/player/src/composition/openRecording.js +41 -0
- package/dist/packages/player/src/composition/readInputs.js +25 -0
- package/dist/packages/player/src/composition/renditionFrames.js +160 -0
- package/dist/packages/player/src/controls/ChoiceMenu.js +125 -0
- package/dist/packages/player/src/controls/FullscreenButton.js +29 -0
- package/dist/packages/player/src/controls/MotionLookButton.js +34 -0
- package/dist/packages/player/src/controls/PictureMenus.js +60 -0
- package/dist/packages/player/src/controls/SeekBar.js +126 -0
- package/dist/packages/player/src/controls/SoundControls.js +43 -0
- package/dist/packages/player/src/controls/TransportButtons.js +28 -0
- package/dist/packages/player/src/controls/ViewGestures.js +135 -0
- package/dist/packages/player/src/controls/Wording.js +79 -0
- package/dist/packages/player/src/controls/controlParts.js +38 -0
- package/dist/packages/player/src/controls/controls.js +4 -0
- package/dist/packages/player/src/controls/controlsBar.js +33 -0
- package/dist/packages/player/src/controls/controlsMarkup.js +100 -0
- package/dist/packages/player/src/controls/customControls.js +43 -0
- package/dist/packages/player/src/controls/formatTime.js +19 -0
- package/dist/packages/player/src/controls/icons.js +66 -0
- package/dist/packages/player/src/controls/keyboard.js +120 -0
- package/dist/packages/player/src/controls/messages.js +130 -0
- package/dist/packages/player/src/controls/nativeFullscreen.js +27 -0
- package/dist/packages/player/src/controls/parseMarkup.js +30 -0
- package/dist/packages/player/src/controls/resetViewButton.js +11 -0
- package/dist/packages/player/src/controls/sliderFill.js +15 -0
- package/dist/packages/player/src/element/ElementLoads.js +130 -0
- package/dist/packages/player/src/element/FullscreenToggle.js +93 -0
- package/dist/packages/player/src/element/GyroViewElement.js +358 -0
- package/dist/packages/player/src/element/IdleWatcher.js +71 -0
- package/dist/packages/player/src/element/PlayerCanvas.js +72 -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 +62 -0
- package/dist/packages/player/src/element/attributes.js +123 -0
- package/dist/packages/player/src/element/defineGyroView.js +20 -0
- package/dist/packages/player/src/element/earlyProperties.js +35 -0
- package/dist/packages/player/src/element/elementProperties.js +34 -0
- package/dist/packages/player/src/element/elementSource.js +39 -0
- package/dist/packages/player/src/element/fetchProperty.js +48 -0
- package/dist/packages/player/src/element/liveSettings.js +137 -0
- package/dist/packages/player/src/element/playbackAttributes.js +52 -0
- package/dist/packages/player/src/element/playerEventMirror.js +33 -0
- package/dist/packages/player/src/element/reflectedProperties.js +96 -0
- package/dist/packages/player/src/element/styles.js +4 -0
- package/dist/packages/player/src/element/template.js +46 -0
- package/dist/packages/player/src/inspectRecording.js +21 -0
- package/dist/packages/player/src/player/FrameLoop.js +35 -0
- package/dist/packages/player/src/player/MotionLook.js +150 -0
- package/dist/packages/player/src/player/PictureSettings.js +59 -0
- package/dist/packages/player/src/player/PlaybackStarts.js +38 -0
- package/dist/packages/player/src/player/Player.js +379 -0
- package/dist/packages/player/src/player/PlayerEvents.js +28 -0
- package/dist/packages/player/src/player/PlayerPhase.js +38 -0
- package/dist/packages/player/src/player/PlayerSound.js +60 -0
- package/dist/packages/player/src/player/PlayerView.js +143 -0
- package/dist/packages/player/src/player/SessionRelay.js +74 -0
- package/dist/packages/player/src/player/StatusAnnouncer.js +29 -0
- package/dist/packages/player/src/player/ensureFinite.js +26 -0
- package/dist/packages/player/src/player/loadRecording.js +47 -0
- package/dist/packages/player/src/player/transportEvents.js +30 -0
- package/dist/packages/player/src/player/viewAngles.js +17 -0
- package/dist/packages/player/src/rendition.js +52 -0
- package/dist/standalone.js +5633 -0
- package/package.json +70 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { GYRO_VIEW_ERROR_CATEGORIES, GYRO_VIEW_ERROR_CODES, GyroViewError, hasErrorCode, isGyroViewErrorCode } from "./packages/core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
import { STABILIZATION_MODES } from "./packages/core/src/domain/motion/stabilization/Stabilizer.js";
|
|
3
|
+
import { PICTURE_QUALITIES } from "./packages/core/src/domain/view/PictureQuality.js";
|
|
4
|
+
import { VIEW_MODES } from "./packages/core/src/domain/view/ViewMode.js";
|
|
5
|
+
import { attachKeyboard, attachViewGestures } from "./packages/player/src/controls/customControls.js";
|
|
6
|
+
import { Player } from "./packages/player/src/player/Player.js";
|
|
7
|
+
import { createBrowserPlayer } from "./packages/player/src/browserPlayer.js";
|
|
8
|
+
import { inspectRecording } from "./packages/player/src/inspectRecording.js";
|
|
9
|
+
import { openRendition } from "./packages/player/src/rendition.js";
|
|
10
|
+
import { GyroViewElement } from "./packages/player/src/element/GyroViewElement.js";
|
|
11
|
+
import { GYRO_VIEW_TAG, defineGyroView } from "./packages/player/src/element/defineGyroView.js";
|
|
12
|
+
export { GYRO_VIEW_ERROR_CATEGORIES, GYRO_VIEW_ERROR_CODES, GYRO_VIEW_TAG, GyroViewElement, GyroViewError, PICTURE_QUALITIES, Player, STABILIZATION_MODES, VIEW_MODES, attachKeyboard, attachViewGestures, createBrowserPlayer, defineGyroView, hasErrorCode, inspectRecording, isGyroViewErrorCode, openRendition };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { GyroViewError } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
//#region ../../packages/adapters/blob/src/BlobRandomAccessSource.ts
|
|
3
|
+
/**
|
|
4
|
+
* RandomAccessSource over a `Blob` (a `File` from a picker or a drop). Each read slices the
|
|
5
|
+
* blob, which browsers serve from disk without loading the whole file, so multi-gigabyte
|
|
6
|
+
* recordings play from local storage the same way they play from a URL.
|
|
7
|
+
*/
|
|
8
|
+
var BlobRandomAccessSource = class {
|
|
9
|
+
blob;
|
|
10
|
+
constructor(blob) {
|
|
11
|
+
this.blob = blob;
|
|
12
|
+
}
|
|
13
|
+
size() {
|
|
14
|
+
return Promise.resolve(this.blob.size);
|
|
15
|
+
}
|
|
16
|
+
async read(range) {
|
|
17
|
+
range.ensureWithin(this.blob.size, "blob");
|
|
18
|
+
if (range.length === 0) return /* @__PURE__ */ new Uint8Array();
|
|
19
|
+
const bytes = await this.bytesOf(this.blob.slice(range.offset, range.end));
|
|
20
|
+
if (bytes.byteLength !== range.length) throw new GyroViewError("source-truncated", `the blob yielded ${bytes.byteLength} bytes for a ${range.length}-byte range at ${range.offset}; the file may have changed on disk`);
|
|
21
|
+
return bytes;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Reading is what fails, with a `NotReadableError`, when a picked file has changed or gone on
|
|
25
|
+
* disk; slicing only clamps its arguments.
|
|
26
|
+
*/
|
|
27
|
+
async bytesOf(slice) {
|
|
28
|
+
try {
|
|
29
|
+
return new Uint8Array(await slice.arrayBuffer());
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw new GyroViewError("source-unreadable", "the blob could not be read", { cause: error });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { BlobRandomAccessSource };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { GyroViewError, isAbortError } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
import { ByteRange } from "../../../core/src/shared/binary/ByteRange.js";
|
|
3
|
+
import { Ending, ITERATION_END } from "../../../core/src/shared/async/iteration.js";
|
|
4
|
+
import { contentRangeOf } from "./contentRange.js";
|
|
5
|
+
import { passing } from "./passingFailures.js";
|
|
6
|
+
import { brokenOff, truncated } from "./rangeFailures.js";
|
|
7
|
+
//#region ../../packages/adapters/fetch/src/HttpByteStream.ts
|
|
8
|
+
/**
|
|
9
|
+
* Long enough for a slow mobile network to deliver its next packet, short enough that playback
|
|
10
|
+
* waiting on a dead connection recovers well before the viewer gives up.
|
|
11
|
+
*/
|
|
12
|
+
var DEFAULT_STALL_TIMEOUT_MS = 1e4;
|
|
13
|
+
/**
|
|
14
|
+
* ByteStream over HTTP: a range is one request, its body handed on a chunk at a time as it
|
|
15
|
+
* comes, and given up by aborting the request. A body that breaks off or stalls is asked for
|
|
16
|
+
* again from its next byte, as the resource's retry rules allow.
|
|
17
|
+
*/
|
|
18
|
+
var HttpByteStream = class {
|
|
19
|
+
resource;
|
|
20
|
+
stallTimeoutMs;
|
|
21
|
+
constructor(resource, options = {}) {
|
|
22
|
+
this.resource = resource;
|
|
23
|
+
this.stallTimeoutMs = options.stallTimeoutMs ?? DEFAULT_STALL_TIMEOUT_MS;
|
|
24
|
+
}
|
|
25
|
+
stream(range) {
|
|
26
|
+
return { [Symbol.asyncIterator]: () => new RangeReading({
|
|
27
|
+
resource: this.resource,
|
|
28
|
+
range,
|
|
29
|
+
stallTimeoutMs: this.stallTimeoutMs
|
|
30
|
+
}) };
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* One range's requests and bodies. The answer must be the bytes asked for, every one and no
|
|
35
|
+
* more; returning the iteration aborts the request at once, a chunk still awaited coming as the
|
|
36
|
+
* end.
|
|
37
|
+
*/
|
|
38
|
+
var RangeReading = class {
|
|
39
|
+
parts;
|
|
40
|
+
attempt;
|
|
41
|
+
received = 0;
|
|
42
|
+
ending = new Ending();
|
|
43
|
+
constructor(parts) {
|
|
44
|
+
this.parts = parts;
|
|
45
|
+
}
|
|
46
|
+
async next() {
|
|
47
|
+
try {
|
|
48
|
+
const chunk = await this.parts.resource.askingAgain(() => this.readChunk());
|
|
49
|
+
if (chunk) return {
|
|
50
|
+
done: false,
|
|
51
|
+
value: chunk
|
|
52
|
+
};
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (!this.ending.hasEnded()) {
|
|
55
|
+
this.end();
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
this.end();
|
|
60
|
+
return ITERATION_END;
|
|
61
|
+
}
|
|
62
|
+
return() {
|
|
63
|
+
this.end();
|
|
64
|
+
return Promise.resolve(ITERATION_END);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The next chunk; none at the range's end. An attempt that fails is dropped, so the next
|
|
68
|
+
* asks anew for the rest of the range.
|
|
69
|
+
*/
|
|
70
|
+
async readChunk() {
|
|
71
|
+
const { resource, range } = this.parts;
|
|
72
|
+
await resource.ensureFits(range);
|
|
73
|
+
if (this.ending.hasEnded() || range.length === 0) return void 0;
|
|
74
|
+
this.attempt ??= this.attemptAtTheRest();
|
|
75
|
+
const { attempt } = this;
|
|
76
|
+
try {
|
|
77
|
+
const read = await this.withinStallTimeout(this.readFrom(attempt));
|
|
78
|
+
return this.taken(read);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
this.drop(attempt);
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
attemptAtTheRest() {
|
|
85
|
+
const { range } = this.parts;
|
|
86
|
+
const rest = ByteRange.of(range.offset + this.received, range.length - this.received);
|
|
87
|
+
const request = new AbortController();
|
|
88
|
+
return {
|
|
89
|
+
request,
|
|
90
|
+
body: this.bodyOf(rest, request.signal)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async bodyOf(rest, signal) {
|
|
94
|
+
const response = await this.parts.resource.rangeAnswer(rest, signal);
|
|
95
|
+
this.ensureAnswers(response, rest);
|
|
96
|
+
return response.body?.getReader() ?? new ReadableStream().getReader();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* A server may answer other bytes than asked (a cache serving a range it holds); the answer
|
|
100
|
+
* says which in its Content-Range, where the page may read it.
|
|
101
|
+
*/
|
|
102
|
+
ensureAnswers(response, rest) {
|
|
103
|
+
const answered = contentRangeOf(response);
|
|
104
|
+
if (!answered) return;
|
|
105
|
+
const asked = `${rest.offset}-${rest.end - 1}`;
|
|
106
|
+
const given = `${answered.first}-${answered.last}`;
|
|
107
|
+
if (given === asked) return;
|
|
108
|
+
throw new GyroViewError("source-unreadable", `${this.parts.resource.url} answered bytes ${given} to a request for bytes ${asked}`);
|
|
109
|
+
}
|
|
110
|
+
async readFrom(attempt) {
|
|
111
|
+
const body = await attempt.body;
|
|
112
|
+
try {
|
|
113
|
+
return await body.read();
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (isAbortError(error)) throw error;
|
|
116
|
+
throw brokenOff(this.parts.resource.url, this.parts.range, error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A request that brings no byte for the stall timeout, answer or body, is taken for stalled:
|
|
121
|
+
* a connection a network change left dead never fails by itself.
|
|
122
|
+
*/
|
|
123
|
+
async withinStallTimeout(pending) {
|
|
124
|
+
let timer;
|
|
125
|
+
const stalled = new Promise((_, reject) => {
|
|
126
|
+
timer = setTimeout(() => {
|
|
127
|
+
reject(this.stalled());
|
|
128
|
+
}, this.parts.stallTimeoutMs);
|
|
129
|
+
});
|
|
130
|
+
try {
|
|
131
|
+
return await Promise.race([pending, stalled]);
|
|
132
|
+
} finally {
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
stalled() {
|
|
137
|
+
const { resource, range, stallTimeoutMs } = this.parts;
|
|
138
|
+
const message = `${resource.url} sent nothing of the ${range.length}-byte range at ${range.offset} for ${stallTimeoutMs} ms`;
|
|
139
|
+
return passing(new GyroViewError("source-unreadable", message));
|
|
140
|
+
}
|
|
141
|
+
taken(read) {
|
|
142
|
+
if (read.done) {
|
|
143
|
+
this.ensureWhole();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.count(read.value);
|
|
147
|
+
return read.value;
|
|
148
|
+
}
|
|
149
|
+
count(chunk) {
|
|
150
|
+
const { resource, range } = this.parts;
|
|
151
|
+
this.received += chunk.byteLength;
|
|
152
|
+
if (this.received <= range.length) return;
|
|
153
|
+
throw new GyroViewError("source-unreadable", `${resource.url} sent more than the ${range.length}-byte range at ${range.offset}`);
|
|
154
|
+
}
|
|
155
|
+
ensureWhole() {
|
|
156
|
+
const { resource, range } = this.parts;
|
|
157
|
+
if (this.received !== range.length) throw truncated(resource.url, this.received, range);
|
|
158
|
+
}
|
|
159
|
+
drop(attempt) {
|
|
160
|
+
attempt.request.abort();
|
|
161
|
+
if (this.attempt === attempt) this.attempt = void 0;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Aborting the request once it is over, however it ended, also lets go of the listeners it
|
|
165
|
+
* put on the resource's own signal.
|
|
166
|
+
*/
|
|
167
|
+
end() {
|
|
168
|
+
this.ending.end();
|
|
169
|
+
if (this.attempt) this.drop(this.attempt);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
//#endregion
|
|
173
|
+
export { HttpByteStream };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isAbortError } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
import { brokenOff, truncated } from "./rangeFailures.js";
|
|
3
|
+
//#region ../../packages/adapters/fetch/src/HttpRangeSource.ts
|
|
4
|
+
/**
|
|
5
|
+
* RandomAccessSource over HTTP: each range read whole, in one request, asked for again when it
|
|
6
|
+
* fails on the way. It shares its resource with the recording's byte stream, so both know one
|
|
7
|
+
* size, one proof of CORS and one version.
|
|
8
|
+
*/
|
|
9
|
+
var HttpRangeSource = class {
|
|
10
|
+
resource;
|
|
11
|
+
constructor(resource) {
|
|
12
|
+
this.resource = resource;
|
|
13
|
+
}
|
|
14
|
+
size() {
|
|
15
|
+
return this.resource.size();
|
|
16
|
+
}
|
|
17
|
+
async read(range) {
|
|
18
|
+
await this.resource.ensureFits(range);
|
|
19
|
+
return range.length === 0 ? /* @__PURE__ */ new Uint8Array() : this.resource.askingAgain(() => this.readOnce(range));
|
|
20
|
+
}
|
|
21
|
+
async readOnce(range) {
|
|
22
|
+
const response = await this.resource.rangeAnswer(range);
|
|
23
|
+
const bytes = await this.bodyOf(response, range);
|
|
24
|
+
if (bytes.byteLength !== range.length) throw truncated(this.resource.url, bytes.byteLength, range);
|
|
25
|
+
return bytes;
|
|
26
|
+
}
|
|
27
|
+
async bodyOf(response, range) {
|
|
28
|
+
try {
|
|
29
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (isAbortError(error)) throw error;
|
|
32
|
+
throw brokenOff(this.resource.url, range, error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { HttpRangeSource };
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { GyroViewError, isAbortError } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
import { milliseconds, millisecondsToSeconds } from "../../../core/src/shared/units/time.js";
|
|
3
|
+
import { contentRangeOf } from "./contentRange.js";
|
|
4
|
+
import { isPassing, passing, passingIfUnreachable } from "./passingFailures.js";
|
|
5
|
+
import { EXPOSED_HEADERS_ADVICE, FIRST_BYTE_RANGE, discardBody, httpRequest, plainHttpRequest, withAbortSignal } from "./httpRequest.js";
|
|
6
|
+
import { isSameVersion, versionOf } from "./recordingVersion.js";
|
|
7
|
+
//#region ../../packages/adapters/fetch/src/HttpResource.ts
|
|
8
|
+
var HTTP_OK = 200;
|
|
9
|
+
var HTTP_PARTIAL_CONTENT = 206;
|
|
10
|
+
var HTTP_TOO_MANY_REQUESTS = 429;
|
|
11
|
+
var HTTP_SERVER_ERROR = 500;
|
|
12
|
+
var DEFAULT_RETRY_DELAYS_MS = [250, 1e3];
|
|
13
|
+
/**
|
|
14
|
+
* One recording at a URL, as every reader of it sees it: its size, whether its server has shown
|
|
15
|
+
* it lets this page read it (CORS), the version its first answer told of (ADR 0029), and how a
|
|
16
|
+
* range that failed on the way is asked for again.
|
|
17
|
+
* The server must answer `Range` requests with 206 and, for cross-origin use, send CORS headers
|
|
18
|
+
* that expose `Content-Range`; both are hard requirements of playing a remote recording and are
|
|
19
|
+
* reported with distinct error codes.
|
|
20
|
+
*/
|
|
21
|
+
var HttpResource = class {
|
|
22
|
+
url;
|
|
23
|
+
options;
|
|
24
|
+
retryDelaysMs;
|
|
25
|
+
sizePromise;
|
|
26
|
+
/**
|
|
27
|
+
* A range has come through: CORS is proven for this resource, so a request that fails from
|
|
28
|
+
* here on failed on the way, whatever the diagnosis says of an error page without CORS headers.
|
|
29
|
+
*/
|
|
30
|
+
hasReadRange = false;
|
|
31
|
+
version;
|
|
32
|
+
now;
|
|
33
|
+
shortestAnswerMs;
|
|
34
|
+
/**
|
|
35
|
+
* `signal` ends every request made for the resource, with the host's own `requestInit` signal.
|
|
36
|
+
*/
|
|
37
|
+
constructor(url, options = {}, signal) {
|
|
38
|
+
this.url = url;
|
|
39
|
+
this.options = signal ? withAbortSignal(options, signal) : options;
|
|
40
|
+
this.retryDelaysMs = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
|
|
41
|
+
this.now = options.now ?? (() => performance.now());
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* How long its server took to answer a byte range, from asking to the answer's headers: the
|
|
45
|
+
* shortest wait of the ranges answered so far, so a first one slowed by a new connection, a
|
|
46
|
+
* CORS preflight or a cold cache counts for nothing (ADR 0044). A HEAD, which a server may
|
|
47
|
+
* answer from what it knows of the file without reading it, and a refusal tell nothing.
|
|
48
|
+
* Undefined until a range was answered.
|
|
49
|
+
*/
|
|
50
|
+
answerWait() {
|
|
51
|
+
const shortest = this.shortestAnswerMs;
|
|
52
|
+
return shortest === void 0 ? void 0 : millisecondsToSeconds(milliseconds(shortest));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* One HEAD request (or a one-byte range where HEAD is refused or gives no length), cached for
|
|
56
|
+
* the lifetime of the resource once it succeeded; a failed lookup is retried on the next call.
|
|
57
|
+
*/
|
|
58
|
+
size() {
|
|
59
|
+
this.sizePromise ??= this.rememberSize();
|
|
60
|
+
return this.sizePromise;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Servers clamp out-of-range requests instead of failing them; the port contracts want a
|
|
64
|
+
* typed error, so the range is checked against the (cached) size first.
|
|
65
|
+
*/
|
|
66
|
+
async ensureFits(range) {
|
|
67
|
+
range.ensureWithin(await this.size(), `resource ${this.url}`);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Runs `attempt`, and after a short wait again, a few times, while it fails on the way: one
|
|
71
|
+
* dropped connection must not end a long playback, nor a server's 5xx or its 429 asking the
|
|
72
|
+
* player to slow down. What asking again cannot change (an abort, any other refusal, a server
|
|
73
|
+
* ignoring ranges, CORS) fails at once.
|
|
74
|
+
*/
|
|
75
|
+
async askingAgain(attempt) {
|
|
76
|
+
for (const delayMs of this.retryDelaysMs) {
|
|
77
|
+
try {
|
|
78
|
+
return await attempt();
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (!isPassing(error)) throw error;
|
|
81
|
+
}
|
|
82
|
+
await wait(delayMs, this.options.requestInit?.signal);
|
|
83
|
+
}
|
|
84
|
+
return attempt();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The server's answer to a byte range, its body still to be read: a 206, or the refusal it
|
|
88
|
+
* amounts to. `signal` ends this request alone.
|
|
89
|
+
*/
|
|
90
|
+
async rangeAnswer(range, signal) {
|
|
91
|
+
const options = signal ? withAbortSignal(this.options, signal) : this.options;
|
|
92
|
+
const response = await this.requestRange(range, options);
|
|
93
|
+
if (response.status !== HTTP_PARTIAL_CONTENT) {
|
|
94
|
+
discardBody(response);
|
|
95
|
+
throw this.refusalOf(response.status);
|
|
96
|
+
}
|
|
97
|
+
this.hasReadRange = true;
|
|
98
|
+
this.ensureUnchanged(response);
|
|
99
|
+
return response;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Every answer is of the version the first one told of: bytes of a recording replaced while it
|
|
103
|
+
* plays must not be put together with the old ones'.
|
|
104
|
+
*/
|
|
105
|
+
ensureUnchanged(response) {
|
|
106
|
+
const seen = versionOf(response);
|
|
107
|
+
this.version ??= seen;
|
|
108
|
+
if (isSameVersion(this.version, seen)) return;
|
|
109
|
+
discardBody(response);
|
|
110
|
+
throw new GyroViewError("source-changed", `${this.url} was replaced while it played; load it again`);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A request that did not get through for want of a network is worth another try; one CORS
|
|
114
|
+
* refused before any range came through is not.
|
|
115
|
+
*/
|
|
116
|
+
async requestRange(range, options) {
|
|
117
|
+
const headers = { Range: `bytes=${range.offset}-${range.end - 1}` };
|
|
118
|
+
try {
|
|
119
|
+
return await this.timed(() => this.hasReadRange ? plainHttpRequest(this.url, {
|
|
120
|
+
method: "GET",
|
|
121
|
+
headers
|
|
122
|
+
}, options) : httpRequest(this.url, {
|
|
123
|
+
method: "GET",
|
|
124
|
+
headers
|
|
125
|
+
}, options));
|
|
126
|
+
} catch (error) {
|
|
127
|
+
throw this.hasReadRange ? this.passingOnceAnswered(error) : passingIfUnreachable(error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
passingOnceAnswered(error) {
|
|
131
|
+
const message = `${this.url} could not be reached for a byte range`;
|
|
132
|
+
return isAbortError(error) ? error : passing(new GyroViewError("source-unreadable", message, { cause: error }));
|
|
133
|
+
}
|
|
134
|
+
refusalOf(status) {
|
|
135
|
+
if (status === HTTP_OK) return new GyroViewError("range-unsupported", `${this.url} ignores Range requests (answered 200 to a byte range)`);
|
|
136
|
+
const message = `${this.url} answered ${status} to a byte range`;
|
|
137
|
+
return isPassingStatus(status) ? passing(new GyroViewError("source-unreadable", message)) : new GyroViewError("source-unreadable", message);
|
|
138
|
+
}
|
|
139
|
+
async rememberSize() {
|
|
140
|
+
try {
|
|
141
|
+
return await this.fetchSize();
|
|
142
|
+
} catch (error) {
|
|
143
|
+
this.sizePromise = void 0;
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async fetchSize() {
|
|
148
|
+
const response = await this.request("HEAD");
|
|
149
|
+
discardBody(response);
|
|
150
|
+
if (!response.ok) return this.sizeFromContentRange();
|
|
151
|
+
this.ensureUnchanged(response);
|
|
152
|
+
const contentLength = Number(response.headers.get("content-length"));
|
|
153
|
+
return Number.isSafeInteger(contentLength) && contentLength > 0 ? contentLength : this.sizeFromContentRange();
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Some servers omit Content-Length on HEAD or refuse HEAD; a one-byte range reveals the total.
|
|
157
|
+
*/
|
|
158
|
+
async sizeFromContentRange() {
|
|
159
|
+
const response = await this.request("GET", { Range: FIRST_BYTE_RANGE });
|
|
160
|
+
discardBody(response);
|
|
161
|
+
if (response.status !== HTTP_PARTIAL_CONTENT) throw new GyroViewError("source-unreadable", `${this.url} answered ${response.status} to a byte range; cannot determine the file size`);
|
|
162
|
+
this.hasReadRange = true;
|
|
163
|
+
this.ensureUnchanged(response);
|
|
164
|
+
return this.totalOf(response);
|
|
165
|
+
}
|
|
166
|
+
totalOf(response) {
|
|
167
|
+
if (!response.headers.has("content-range") && response.type === "cors") throw this.hiddenContentRange();
|
|
168
|
+
const total = contentRangeOf(response)?.total;
|
|
169
|
+
if (total === void 0) throw new GyroViewError("source-unreadable", `${this.url} reports neither Content-Length nor Content-Range; cannot determine the file size`);
|
|
170
|
+
return total;
|
|
171
|
+
}
|
|
172
|
+
hiddenContentRange() {
|
|
173
|
+
return new GyroViewError("cors", `${this.url} answered a byte range but hides its Content-Range from this origin; add ${EXPOSED_HEADERS_ADVICE}`);
|
|
174
|
+
}
|
|
175
|
+
request(method, headers) {
|
|
176
|
+
return this.timed(() => httpRequest(this.url, {
|
|
177
|
+
method,
|
|
178
|
+
...headers && { headers }
|
|
179
|
+
}, this.options));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The request, its wait noted when it brought a range's bytes (206); one that failed, was
|
|
183
|
+
* given up or was refused tells nothing.
|
|
184
|
+
*/
|
|
185
|
+
async timed(send) {
|
|
186
|
+
const askedAt = this.now();
|
|
187
|
+
const response = await send();
|
|
188
|
+
if (response.status !== HTTP_PARTIAL_CONTENT) return response;
|
|
189
|
+
const waitMs = this.now() - askedAt;
|
|
190
|
+
this.shortestAnswerMs = Math.min(waitMs, this.shortestAnswerMs ?? waitMs);
|
|
191
|
+
return response;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* A server error, or a server asking the player to slow down (429, as rate-limited APIs answer):
|
|
196
|
+
* both may pass by the time the range is asked for again. Any other refusal stands.
|
|
197
|
+
*/
|
|
198
|
+
function isPassingStatus(status) {
|
|
199
|
+
return status >= HTTP_SERVER_ERROR || status === HTTP_TOO_MANY_REQUESTS;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* A pause before asking again, which an abort of the resource's requests ends at once.
|
|
203
|
+
*/
|
|
204
|
+
function wait(ms, signal) {
|
|
205
|
+
return new Promise((resolve, reject) => {
|
|
206
|
+
signal?.throwIfAborted();
|
|
207
|
+
const settled = new AbortController();
|
|
208
|
+
const timer = setTimeout(() => {
|
|
209
|
+
settled.abort();
|
|
210
|
+
resolve();
|
|
211
|
+
}, ms);
|
|
212
|
+
const onAbort = () => {
|
|
213
|
+
clearTimeout(timer);
|
|
214
|
+
reject(abortErrorOf(signal));
|
|
215
|
+
};
|
|
216
|
+
signal?.addEventListener("abort", onAbort, {
|
|
217
|
+
once: true,
|
|
218
|
+
signal: settled.signal
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function abortErrorOf(signal) {
|
|
223
|
+
const reason = signal?.reason;
|
|
224
|
+
return reason instanceof Error ? reason : new DOMException("the read was aborted", "AbortError");
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
227
|
+
export { HttpResource };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FIRST_BYTE_RANGE, discardBody, plainHttpRequest } from "./httpRequest.js";
|
|
2
|
+
//#region ../../packages/adapters/fetch/src/HttpResourceLocator.ts
|
|
3
|
+
/**
|
|
4
|
+
* How a server refuses HEAD: a 405, or a 403 from a URL signed for GET alone. The first byte
|
|
5
|
+
* then answers what HEAD would.
|
|
6
|
+
*/
|
|
7
|
+
var REFUSED_HEAD = /* @__PURE__ */ new Set([405, 403]);
|
|
8
|
+
/**
|
|
9
|
+
* ResourceLocator over HTTP: one HEAD request, or a one-byte GET when the server refuses HEAD.
|
|
10
|
+
* Every failure, including a missing CORS header, means "not available"; the file looked for is
|
|
11
|
+
* optional, so nothing here throws, and no request is spent explaining a failure.
|
|
12
|
+
*/
|
|
13
|
+
var HttpResourceLocator = class {
|
|
14
|
+
options;
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
async exists(url) {
|
|
19
|
+
try {
|
|
20
|
+
const head = await plainHttpRequest(url, { method: "HEAD" }, this.options);
|
|
21
|
+
discardBody(head);
|
|
22
|
+
if (!REFUSED_HEAD.has(head.status)) return head.ok;
|
|
23
|
+
const firstByte = await plainHttpRequest(url, {
|
|
24
|
+
method: "GET",
|
|
25
|
+
headers: { Range: FIRST_BYTE_RANGE }
|
|
26
|
+
}, this.options);
|
|
27
|
+
discardBody(firstByte);
|
|
28
|
+
return firstByte.ok;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
//#endregion
|
|
35
|
+
export { HttpResourceLocator };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region ../../packages/adapters/fetch/src/contentRange.ts
|
|
2
|
+
var CONTENT_RANGE = /^bytes (\d+)-(\d+)\/(\d+|\*)$/u;
|
|
3
|
+
var UNKNOWN_TOTAL = "*";
|
|
4
|
+
function contentRangeOf(response) {
|
|
5
|
+
const match = CONTENT_RANGE.exec(response.headers.get("content-range") ?? "");
|
|
6
|
+
if (!match) return void 0;
|
|
7
|
+
const [, first, last, total] = match;
|
|
8
|
+
return {
|
|
9
|
+
first: Number(first),
|
|
10
|
+
last: Number(last),
|
|
11
|
+
total: total === UNKNOWN_TOTAL ? void 0 : Number(total)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { contentRangeOf };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { GyroViewError, isAbortError } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
//#region ../../packages/adapters/fetch/src/httpRequest.ts
|
|
3
|
+
/**
|
|
4
|
+
* The Range header value that asks for the first byte only: enough to learn whether a resource
|
|
5
|
+
* is there, or its total size from Content-Range.
|
|
6
|
+
*/
|
|
7
|
+
var FIRST_BYTE_RANGE = "bytes=0-0";
|
|
8
|
+
/**
|
|
9
|
+
* The response headers a cross-origin page must be let read, as the fix a CORS failure names;
|
|
10
|
+
* `ETag` is optional, and tells a recording replaced while it plays more surely than
|
|
11
|
+
* `Last-Modified`, which a page may always read.
|
|
12
|
+
*/
|
|
13
|
+
var EXPOSED_HEADERS_ADVICE = "Access-Control-Expose-Headers: Content-Range, Content-Length, Accept-Ranges, ETag";
|
|
14
|
+
/**
|
|
15
|
+
* What a server must add for a page to read it across origins. A request that carries the
|
|
16
|
+
* visitor's cookies is held to more: the Fetch standard refuses a wildcard origin for it.
|
|
17
|
+
*/
|
|
18
|
+
var ANONYMOUS_CORS_ADVICE = `Access-Control-Allow-Origin, ${EXPOSED_HEADERS_ADVICE}`;
|
|
19
|
+
var CREDENTIALED_CORS_ADVICE = `Access-Control-Allow-Origin naming this page's origin, not *; Access-Control-Allow-Credentials: true; ${EXPOSED_HEADERS_ADVICE}`;
|
|
20
|
+
/**
|
|
21
|
+
* Browsers keep byte ranges of one URL as a sparse cache entry and revalidate them with
|
|
22
|
+
* conditional requests. A 304 to such a request makes the browser answer from that entry, and
|
|
23
|
+
* Chrome has been seen to hand back an empty body for a range it believed it held (ADR 0013).
|
|
24
|
+
* The download holds what it needs itself, so nothing is lost by leaving the cache out.
|
|
25
|
+
*/
|
|
26
|
+
var CACHE_MODE = "no-store";
|
|
27
|
+
/**
|
|
28
|
+
* One request with the shared settings merged in, failures explained: a request that fails
|
|
29
|
+
* outright is told apart from one the browser blocked for want of CORS headers (the latter is
|
|
30
|
+
* `cors`, the former `source-unreadable`). A caller's abort is passed on as it is.
|
|
31
|
+
*/
|
|
32
|
+
async function httpRequest(url, request, options) {
|
|
33
|
+
try {
|
|
34
|
+
return await plainHttpRequest(url, request, options);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (isAbortError(error)) throw error;
|
|
37
|
+
throw await diagnoseFailure(url, options, error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* One request with the shared settings merged in and its failure left as the platform reported
|
|
42
|
+
* it, for callers to whom any failure means the same.
|
|
43
|
+
*/
|
|
44
|
+
function plainHttpRequest(url, request, options) {
|
|
45
|
+
return (options.fetch ?? fetch)(url, {
|
|
46
|
+
cache: CACHE_MODE,
|
|
47
|
+
...options.requestInit,
|
|
48
|
+
method: request.method,
|
|
49
|
+
headers: {
|
|
50
|
+
...headersOf(options.requestInit),
|
|
51
|
+
...request.headers
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The options with `signal` ending their requests too, beside any signal the host gave: made once
|
|
57
|
+
* per source, so the listeners it adds do not pile up per request.
|
|
58
|
+
*/
|
|
59
|
+
function withAbortSignal(options, signal) {
|
|
60
|
+
const hostSignal = options.requestInit?.signal ?? void 0;
|
|
61
|
+
const either = hostSignal ? eitherSignal(hostSignal, signal) : signal;
|
|
62
|
+
return {
|
|
63
|
+
...options,
|
|
64
|
+
requestInit: {
|
|
65
|
+
...options.requestInit,
|
|
66
|
+
signal: either
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Fires when either does. `AbortSignal.any` would do, but iOS Safari has it only from 17.4.
|
|
72
|
+
* Whichever fires takes both listeners along, so a long-lived host signal keeps none.
|
|
73
|
+
*/
|
|
74
|
+
function eitherSignal(first, second) {
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
for (const signal of [first, second]) {
|
|
77
|
+
if (signal.aborted) controller.abort(signal.reason);
|
|
78
|
+
signal.addEventListener("abort", () => {
|
|
79
|
+
controller.abort(signal.reason);
|
|
80
|
+
}, {
|
|
81
|
+
once: true,
|
|
82
|
+
signal: controller.signal
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return controller.signal;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Browsers report a CORS refusal and a network failure alike, as a rejected fetch. A request
|
|
89
|
+
* that asks for no CORS answer at all (an opaque response) still succeeds when the server is
|
|
90
|
+
* reachable, so its outcome tells the two apart.
|
|
91
|
+
*/
|
|
92
|
+
async function diagnoseFailure(url, options, cause) {
|
|
93
|
+
return await isReachableWithoutCors(url, options) ? new GyroViewError("cors", `${url} answered, but its server does not allow this page's origin to read it; add CORS headers (${corsAdviceFor(options)})`, { cause }) : new GyroViewError("source-unreadable", `${url} could not be fetched; check the URL and the network`, { cause });
|
|
94
|
+
}
|
|
95
|
+
function corsAdviceFor(options) {
|
|
96
|
+
return options.requestInit?.credentials === "include" ? CREDENTIALED_CORS_ADVICE : ANONYMOUS_CORS_ADVICE;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The probe ends with the request it diagnoses: an abort of the load ends it too.
|
|
100
|
+
*/
|
|
101
|
+
async function isReachableWithoutCors(url, options) {
|
|
102
|
+
const doFetch = options.fetch ?? fetch;
|
|
103
|
+
const signal = options.requestInit?.signal ?? null;
|
|
104
|
+
try {
|
|
105
|
+
discardBody(await doFetch(url, {
|
|
106
|
+
method: "HEAD",
|
|
107
|
+
mode: "no-cors",
|
|
108
|
+
signal
|
|
109
|
+
}));
|
|
110
|
+
return true;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (isAbortError(error)) throw error;
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Lets the browser stop streaming a body that will not be read, for example a whole file
|
|
118
|
+
* answered to a byte range.
|
|
119
|
+
*/
|
|
120
|
+
function discardBody(response) {
|
|
121
|
+
cancelBody(response);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A body the request's abort already ended refuses the cancel with that abort: there is nothing
|
|
125
|
+
* left to stop, and the abort reaches the caller through the request itself.
|
|
126
|
+
*/
|
|
127
|
+
async function cancelBody(response) {
|
|
128
|
+
try {
|
|
129
|
+
await response.body?.cancel();
|
|
130
|
+
} catch {}
|
|
131
|
+
}
|
|
132
|
+
function headersOf(init) {
|
|
133
|
+
return Object.fromEntries(new Headers(init?.headers).entries());
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
136
|
+
export { EXPOSED_HEADERS_ADVICE, FIRST_BYTE_RANGE, discardBody, httpRequest, plainHttpRequest, withAbortSignal };
|