@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.
Files changed (228) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +21 -0
  3. package/README.md +216 -0
  4. package/dist/define.d.ts +3 -0
  5. package/dist/define.js +4 -0
  6. package/dist/index.d.ts +959 -0
  7. package/dist/index.js +13 -0
  8. package/dist/packages/adapters/blob/src/BlobRandomAccessSource.js +37 -0
  9. package/dist/packages/adapters/blob/src/index.js +2 -0
  10. package/dist/packages/adapters/fetch/src/HttpRangeSource.js +194 -0
  11. package/dist/packages/adapters/fetch/src/HttpResourceLocator.js +35 -0
  12. package/dist/packages/adapters/fetch/src/httpRequest.js +126 -0
  13. package/dist/packages/adapters/fetch/src/index.js +3 -0
  14. package/dist/packages/adapters/mediabunny/src/MediabunnyAudioSegments.js +133 -0
  15. package/dist/packages/adapters/mediabunny/src/MediabunnyDemuxer.js +63 -0
  16. package/dist/packages/adapters/mediabunny/src/MediabunnyVideoTrackReader.js +84 -0
  17. package/dist/packages/adapters/mediabunny/src/SegmentChannel.js +84 -0
  18. package/dist/packages/adapters/mediabunny/src/bufferSources.js +12 -0
  19. package/dist/packages/adapters/mediabunny/src/index.js +2 -0
  20. package/dist/packages/adapters/mse-audio/src/MediaSourceAudioClock.js +139 -0
  21. package/dist/packages/adapters/mse-audio/src/SourceBufferFeeder.js +174 -0
  22. package/dist/packages/adapters/mse-audio/src/events.js +26 -0
  23. package/dist/packages/adapters/mse-audio/src/index.js +2 -0
  24. package/dist/packages/adapters/mse-audio/src/mediaSourceSupport.js +82 -0
  25. package/dist/packages/adapters/three/src/ThreeFrameRenderer.js +274 -0
  26. package/dist/packages/adapters/three/src/compileAndProve.js +14 -0
  27. package/dist/packages/adapters/three/src/fullscreenPass.js +52 -0
  28. package/dist/packages/adapters/three/src/index.js +2 -0
  29. package/dist/packages/adapters/three/src/lensTextures.js +25 -0
  30. package/dist/packages/adapters/three/src/pictureMaterials.js +44 -0
  31. package/dist/packages/adapters/three/src/renderInto.js +15 -0
  32. package/dist/packages/adapters/three/src/rendererUniforms.js +157 -0
  33. package/dist/packages/adapters/three/src/samplingStrategies.js +26 -0
  34. package/dist/packages/adapters/three/src/seamMeter/SeamMeterPass.js +79 -0
  35. package/dist/packages/adapters/three/src/seamMeter/rowMeans.js +38 -0
  36. package/dist/packages/adapters/three/src/shaderPrograms.js +66 -0
  37. package/dist/packages/adapters/three/src/shaders/analysis.frag.js +4 -0
  38. package/dist/packages/adapters/three/src/shaders/equirectangularRays.js +4 -0
  39. package/dist/packages/adapters/three/src/shaders/fixedJoin.js +4 -0
  40. package/dist/packages/adapters/three/src/shaders/fullscreen.vert.js +4 -0
  41. package/dist/packages/adapters/three/src/shaders/header.js +4 -0
  42. package/dist/packages/adapters/three/src/shaders/lensModels.js +4 -0
  43. package/dist/packages/adapters/three/src/shaders/lensSampling.js +4 -0
  44. package/dist/packages/adapters/three/src/shaders/lensTextures.js +4 -0
  45. package/dist/packages/adapters/three/src/shaders/rawLenses.frag.js +4 -0
  46. package/dist/packages/adapters/three/src/shaders/rectilinearRays.js +4 -0
  47. package/dist/packages/adapters/three/src/shaders/screenAreas.js +4 -0
  48. package/dist/packages/adapters/three/src/shaders/stitch.frag.js +4 -0
  49. package/dist/packages/adapters/three/src/webglRenderer.js +37 -0
  50. package/dist/packages/adapters/webcodecs/src/WebCodecsVideoDecoderPort.js +135 -0
  51. package/dist/packages/adapters/webcodecs/src/index.js +2 -0
  52. package/dist/packages/core/src/application/gainMatching/GainMatching.js +66 -0
  53. package/dist/packages/core/src/application/gainMatching/GainMatchingFrameSink.js +62 -0
  54. package/dist/packages/core/src/application/playback/ClockWatch.js +76 -0
  55. package/dist/packages/core/src/application/playback/DecodePipeline.js +188 -0
  56. package/dist/packages/core/src/application/playback/DecodeRun.js +93 -0
  57. package/dist/packages/core/src/application/playback/FramePairQueue.js +93 -0
  58. package/dist/packages/core/src/application/playback/FramePairer.js +60 -0
  59. package/dist/packages/core/src/application/playback/PlaybackSession.js +417 -0
  60. package/dist/packages/core/src/application/playback/SeekOrder.js +36 -0
  61. package/dist/packages/core/src/application/playback/SessionLifecycle.js +42 -0
  62. package/dist/packages/core/src/application/playback/StartGate.js +55 -0
  63. package/dist/packages/core/src/application/playback/TimeUpdates.js +31 -0
  64. package/dist/packages/core/src/application/playback/WallClock.js +46 -0
  65. package/dist/packages/core/src/application/playback/keyframeTimeAt.js +15 -0
  66. package/dist/packages/core/src/application/playback/probeDecoding.js +168 -0
  67. package/dist/packages/core/src/application/recording/frameTimesOf.js +22 -0
  68. package/dist/packages/core/src/application/recording/inspectLayout.js +21 -0
  69. package/dist/packages/core/src/application/recording/inspectRecording.js +124 -0
  70. package/dist/packages/core/src/application/recording/locateOtherLensFile.js +18 -0
  71. package/dist/packages/core/src/application/recording/motionOf.js +39 -0
  72. package/dist/packages/core/src/application/recording/readRecording.js +31 -0
  73. package/dist/packages/core/src/application/recording/timeRecording.js +57 -0
  74. package/dist/packages/core/src/application/stabilization/StabilizingFrameSink.js +31 -0
  75. package/dist/packages/core/src/domain/format/boxes/BoxLayout.js +7 -0
  76. package/dist/packages/core/src/domain/format/boxes/boxConstants.js +16 -0
  77. package/dist/packages/core/src/domain/format/boxes/scanBoxes.js +59 -0
  78. package/dist/packages/core/src/domain/format/calibration/CalibrationVersion.js +13 -0
  79. package/dist/packages/core/src/domain/format/calibration/layouts/CalibrationStringLayout.js +34 -0
  80. package/dist/packages/core/src/domain/format/calibration/layouts/LegacyCalibrationLayout.js +44 -0
  81. package/dist/packages/core/src/domain/format/calibration/layouts/MeiCalibrationLayout.js +45 -0
  82. package/dist/packages/core/src/domain/format/calibration/layouts/PolynomialCalibrationLayout.js +44 -0
  83. package/dist/packages/core/src/domain/format/calibration/offsetTokens.js +65 -0
  84. package/dist/packages/core/src/domain/format/calibration/parseOffsetString.js +52 -0
  85. package/dist/packages/core/src/domain/format/calibration/selectCalibration.js +46 -0
  86. package/dist/packages/core/src/domain/format/captureOrigin.js +20 -0
  87. package/dist/packages/core/src/domain/format/constants.js +33 -0
  88. package/dist/packages/core/src/domain/format/info/infoFields.js +110 -0
  89. package/dist/packages/core/src/domain/format/info/parseInfoRecord.js +90 -0
  90. package/dist/packages/core/src/domain/format/info/readInfoRecord.js +14 -0
  91. package/dist/packages/core/src/domain/format/layout/detectLensLayout.js +122 -0
  92. package/dist/packages/core/src/domain/format/naming/RecordingFileName.js +67 -0
  93. package/dist/packages/core/src/domain/format/records/TrailerRecords.js +83 -0
  94. package/dist/packages/core/src/domain/format/records/exposure/parseExposureRecord.js +48 -0
  95. package/dist/packages/core/src/domain/format/records/gyro/FloatGyroSampleLayout.js +39 -0
  96. package/dist/packages/core/src/domain/format/records/gyro/RawGyroSampleLayout.js +55 -0
  97. package/dist/packages/core/src/domain/format/records/gyro/SampleColumns.js +82 -0
  98. package/dist/packages/core/src/domain/format/records/gyro/gyroLayouts.js +12 -0
  99. package/dist/packages/core/src/domain/format/records/gyro/parseGyroRecord.js +129 -0
  100. package/dist/packages/core/src/domain/format/trailer/RecordHeader.js +23 -0
  101. package/dist/packages/core/src/domain/format/trailer/Trailer.js +23 -0
  102. package/dist/packages/core/src/domain/format/trailer/TrailerFooter.js +27 -0
  103. package/dist/packages/core/src/domain/format/trailer/locateRecords.js +80 -0
  104. package/dist/packages/core/src/domain/format/trailer/parseRecordIndex.js +28 -0
  105. package/dist/packages/core/src/domain/format/trailer/readTrailer.js +32 -0
  106. package/dist/packages/core/src/domain/motion/exposure/ExposureRecord.js +53 -0
  107. package/dist/packages/core/src/domain/motion/gyro/GyroTrack.js +60 -0
  108. package/dist/packages/core/src/domain/motion/gyro/medianStep.js +11 -0
  109. package/dist/packages/core/src/domain/motion/gyro/repairedTimeline.js +101 -0
  110. package/dist/packages/core/src/domain/motion/imu/ImuFrame.js +112 -0
  111. package/dist/packages/core/src/domain/motion/orientation/OrientationTrack.js +146 -0
  112. package/dist/packages/core/src/domain/motion/orientation/estimateGyroBias.js +64 -0
  113. package/dist/packages/core/src/domain/motion/orientation/gravity.js +97 -0
  114. package/dist/packages/core/src/domain/motion/stabilization/Stabilizer.js +13 -0
  115. package/dist/packages/core/src/domain/motion/stabilization/stabilizers.js +86 -0
  116. package/dist/packages/core/src/domain/motion/timing/CaptureClock.js +41 -0
  117. package/dist/packages/core/src/domain/motion/timing/FrameTimes.js +92 -0
  118. package/dist/packages/core/src/domain/motion/timing/frameTimeSources.js +61 -0
  119. package/dist/packages/core/src/domain/motion/timing/resolveFrameTimes.js +68 -0
  120. package/dist/packages/core/src/domain/optics/EquidistantModel.js +39 -0
  121. package/dist/packages/core/src/domain/optics/MeiModel.js +45 -0
  122. package/dist/packages/core/src/domain/optics/PolynomialModel.js +44 -0
  123. package/dist/packages/core/src/domain/optics/gainMatch.js +62 -0
  124. package/dist/packages/core/src/domain/optics/lensGeometry.js +18 -0
  125. package/dist/packages/core/src/domain/optics/lensPose.js +35 -0
  126. package/dist/packages/core/src/domain/optics/opticsConstants.js +5 -0
  127. package/dist/packages/core/src/domain/playback/PlayerState.js +83 -0
  128. package/dist/packages/core/src/domain/stitching/LensLayout.js +21 -0
  129. package/dist/packages/core/src/domain/stitching/StitchingSetup.js +78 -0
  130. package/dist/packages/core/src/domain/stitching/seamDisparity.js +4 -0
  131. package/dist/packages/core/src/domain/stitching/seamDisparityField.js +5 -0
  132. package/dist/packages/core/src/domain/stitching/seamJoin.js +16 -0
  133. package/dist/packages/core/src/domain/stitching/seamMismatch.js +1 -0
  134. package/dist/packages/core/src/domain/stitching/seamRing.js +4 -0
  135. package/dist/packages/core/src/domain/stitching/seamStrip.js +26 -0
  136. package/dist/packages/core/src/domain/view/Framing.js +31 -0
  137. package/dist/packages/core/src/domain/view/PictureQuality.js +26 -0
  138. package/dist/packages/core/src/domain/view/ViewMode.js +9 -0
  139. package/dist/packages/core/src/domain/view/ViewState.js +48 -0
  140. package/dist/packages/core/src/domain/view/lensTilesView.js +63 -0
  141. package/dist/packages/core/src/domain/view/magnification.js +103 -0
  142. package/dist/packages/core/src/domain/view/normalView.js +29 -0
  143. package/dist/packages/core/src/domain/view/panoramaView.js +99 -0
  144. package/dist/packages/core/src/domain/view/rectilinear.js +40 -0
  145. package/dist/packages/core/src/domain/view/screenLayout.js +113 -0
  146. package/dist/packages/core/src/domain/view/viewGestures.js +98 -0
  147. package/dist/packages/core/src/domain/view/viewModes.js +14 -0
  148. package/dist/packages/core/src/index.js +59 -0
  149. package/dist/packages/core/src/ports/FramePair.js +6 -0
  150. package/dist/packages/core/src/shared/async/Deferred.js +42 -0
  151. package/dist/packages/core/src/shared/async/RunStop.js +40 -0
  152. package/dist/packages/core/src/shared/binary/ByteRange.js +38 -0
  153. package/dist/packages/core/src/shared/binary/ByteReader.js +77 -0
  154. package/dist/packages/core/src/shared/errors/GyroViewError.js +81 -0
  155. package/dist/packages/core/src/shared/errors/reportLater.js +11 -0
  156. package/dist/packages/core/src/shared/events/Outbox.js +68 -0
  157. package/dist/packages/core/src/shared/events/TypedEmitter.js +55 -0
  158. package/dist/packages/core/src/shared/keysOf.js +10 -0
  159. package/dist/packages/core/src/shared/lazy.js +11 -0
  160. package/dist/packages/core/src/shared/math/Matrix3.js +93 -0
  161. package/dist/packages/core/src/shared/math/Quaternion.js +133 -0
  162. package/dist/packages/core/src/shared/math/Vector3.js +54 -0
  163. package/dist/packages/core/src/shared/math/clamp.js +9 -0
  164. package/dist/packages/core/src/shared/protobuf/ProtobufMessage.js +157 -0
  165. package/dist/packages/core/src/shared/text/urlPath.js +31 -0
  166. package/dist/packages/core/src/shared/text/utf8.js +12 -0
  167. package/dist/packages/core/src/shared/units/angle.js +22 -0
  168. package/dist/packages/core/src/shared/units/time.js +13 -0
  169. package/dist/packages/player/src/PlayerSource.js +15 -0
  170. package/dist/packages/player/src/browserPlayer.js +18 -0
  171. package/dist/packages/player/src/choices.js +25 -0
  172. package/dist/packages/player/src/composition/Disposables.js +31 -0
  173. package/dist/packages/player/src/composition/browserPorts.js +46 -0
  174. package/dist/packages/player/src/composition/buildPipeline.js +153 -0
  175. package/dist/packages/player/src/composition/ensureDecodable.js +63 -0
  176. package/dist/packages/player/src/composition/openInputs.js +179 -0
  177. package/dist/packages/player/src/composition/openRecording.js +42 -0
  178. package/dist/packages/player/src/controls/ChoiceMenu.js +113 -0
  179. package/dist/packages/player/src/controls/PictureMenus.js +47 -0
  180. package/dist/packages/player/src/controls/SeekBar.js +125 -0
  181. package/dist/packages/player/src/controls/SoundControls.js +41 -0
  182. package/dist/packages/player/src/controls/TransportButtons.js +31 -0
  183. package/dist/packages/player/src/controls/ViewGestures.js +136 -0
  184. package/dist/packages/player/src/controls/Wording.js +78 -0
  185. package/dist/packages/player/src/controls/controlParts.js +36 -0
  186. package/dist/packages/player/src/controls/controls.js +4 -0
  187. package/dist/packages/player/src/controls/controlsBar.js +23 -0
  188. package/dist/packages/player/src/controls/controlsMarkup.js +85 -0
  189. package/dist/packages/player/src/controls/customControls.js +59 -0
  190. package/dist/packages/player/src/controls/formatTime.js +19 -0
  191. package/dist/packages/player/src/controls/icons.js +40 -0
  192. package/dist/packages/player/src/controls/keyboard.js +118 -0
  193. package/dist/packages/player/src/controls/messages.js +94 -0
  194. package/dist/packages/player/src/controls/parseMarkup.js +31 -0
  195. package/dist/packages/player/src/controls/viewButtons.js +14 -0
  196. package/dist/packages/player/src/element/ElementLoads.js +119 -0
  197. package/dist/packages/player/src/element/FullscreenToggle.js +39 -0
  198. package/dist/packages/player/src/element/GyroViewElement.js +313 -0
  199. package/dist/packages/player/src/element/IdleWatcher.js +71 -0
  200. package/dist/packages/player/src/element/TypedEventElement.js +22 -0
  201. package/dist/packages/player/src/element/accessibleRegion.js +19 -0
  202. package/dist/packages/player/src/element/attributeNames.js +51 -0
  203. package/dist/packages/player/src/element/attributes.js +101 -0
  204. package/dist/packages/player/src/element/defineGyroView.js +21 -0
  205. package/dist/packages/player/src/element/earlyProperties.js +36 -0
  206. package/dist/packages/player/src/element/elementSource.js +23 -0
  207. package/dist/packages/player/src/element/liveSettings.js +138 -0
  208. package/dist/packages/player/src/element/playbackAttributes.js +53 -0
  209. package/dist/packages/player/src/element/playerEventMirror.js +33 -0
  210. package/dist/packages/player/src/element/reflectedProperties.js +78 -0
  211. package/dist/packages/player/src/element/styles.js +4 -0
  212. package/dist/packages/player/src/element/template.js +47 -0
  213. package/dist/packages/player/src/index.js +10 -0
  214. package/dist/packages/player/src/inspectRecording.js +22 -0
  215. package/dist/packages/player/src/player/DrawingBufferFit.js +77 -0
  216. package/dist/packages/player/src/player/FrameLoop.js +40 -0
  217. package/dist/packages/player/src/player/PictureSettings.js +60 -0
  218. package/dist/packages/player/src/player/Player.js +377 -0
  219. package/dist/packages/player/src/player/PlayerEvents.js +28 -0
  220. package/dist/packages/player/src/player/PlayerPhase.js +21 -0
  221. package/dist/packages/player/src/player/PlayerSound.js +61 -0
  222. package/dist/packages/player/src/player/PlayerView.js +113 -0
  223. package/dist/packages/player/src/player/SessionRelay.js +66 -0
  224. package/dist/packages/player/src/player/ensureFinite.js +26 -0
  225. package/dist/packages/player/src/player/loadRecording.js +65 -0
  226. package/dist/packages/player/src/player/transportEvents.js +29 -0
  227. package/dist/standalone.js +4919 -0
  228. package/package.json +73 -0
@@ -0,0 +1,417 @@
1
+ import { asGyroViewError } from "../../shared/errors/GyroViewError.js";
2
+ import { seconds } from "../../shared/units/time.js";
3
+ import { clamp } from "../../shared/math/clamp.js";
4
+ import { closeFramePair } from "../../ports/FramePair.js";
5
+ import { Deferred } from "../../shared/async/Deferred.js";
6
+ import { TypedEmitter } from "../../shared/events/TypedEmitter.js";
7
+ import { ClockWatch, isAtEndOfMedia, isStoppedFromOutside } from "./ClockWatch.js";
8
+ import { SeekOrder } from "./SeekOrder.js";
9
+ import { TimeUpdates } from "./TimeUpdates.js";
10
+ import { SessionLifecycle } from "./SessionLifecycle.js";
11
+ import { DecodeRun } from "./DecodeRun.js";
12
+ import { keyframeTimeAt } from "./keyframeTimeAt.js";
13
+ //#region ../../packages/core/src/application/playback/PlaybackSession.ts
14
+ /**
15
+ * Use case: drives one recording through the decode pipeline in step with the clock and hands
16
+ * frame pairs to the sink. The host calls {@link tick} once per animation frame; everything
17
+ * else is event-driven. Sound follows the picture: the clock waits in `buffering` until frames
18
+ * are ready, on starting, after a seek and whenever decoding falls behind.
19
+ *
20
+ * Every change is made whole before its listeners hear of it (ADR 0021): what a method does
21
+ * after announcing never depends on what a listener did meanwhile. The one exception is the
22
+ * clock's start, which waits for `playing` to be heard, so that a listener pausing on it keeps
23
+ * the sound from ever starting.
24
+ */
25
+ var PlaybackSession = class {
26
+ parts;
27
+ events = new TypedEmitter();
28
+ lifecycle = new SessionLifecycle(this.events);
29
+ /**
30
+ * The decode under way, if any: one per start or seek, replaced whole.
31
+ */
32
+ run;
33
+ /**
34
+ * The pair on screen, kept open until the next one replaces it.
35
+ */
36
+ presented;
37
+ timeUpdates = new TimeUpdates(this.lifecycle);
38
+ /**
39
+ * The `play` call waiting for the clock to start once frames are ready.
40
+ */
41
+ startAttempt;
42
+ clockWatch = new ClockWatch();
43
+ seeks = new SeekOrder();
44
+ constructor(parts) {
45
+ this.parts = parts;
46
+ }
47
+ get state() {
48
+ return this.lifecycle.current;
49
+ }
50
+ get currentTime() {
51
+ return this.parts.clock.currentTime;
52
+ }
53
+ /**
54
+ * Starts or resumes, resolving once the clock runs. A paused session keeps its pipeline and
55
+ * the pairs it prefetched; a fresh one starts decoding anew, and an ended one or one paused at
56
+ * the end starts over from the beginning, and waits in `buffering` for the first pairs.
57
+ * Rejects, back in `paused`, when the clock refuses to start (autoplay policy); the host then
58
+ * waits for a user gesture. Pausing meanwhile, a listener's pause included, resolves quietly.
59
+ */
60
+ async play() {
61
+ if (this.lifecycle.current === "playing") return;
62
+ if (this.lifecycle.current === "buffering") {
63
+ await this.startAttempt?.promise;
64
+ return;
65
+ }
66
+ await this.lifecycle.change(() => this.beginStart());
67
+ }
68
+ /**
69
+ * Stops the clock, whatever the state says: a clock start still under way may have been asked
70
+ * for before a pause. A running clock stops where it ran to, which no tick takes for a move
71
+ * from outside.
72
+ */
73
+ pause() {
74
+ this.lifecycle.change(() => {
75
+ const wasRunning = this.parts.clock.isRunning;
76
+ this.parts.clock.pause();
77
+ if (wasRunning) this.clockWatch.stoppedAt(this.parts.clock.currentTime);
78
+ if (!this.lifecycle.canMoveTo("paused")) return;
79
+ this.lifecycle.moveTo("paused");
80
+ this.settleStartAttempt();
81
+ this.timeUpdates.announce(this.parts.clock.currentTime);
82
+ });
83
+ }
84
+ /**
85
+ * Pause and return to the beginning, showing the first frame again on the next tick.
86
+ */
87
+ stop() {
88
+ this.lifecycle.change(() => {
89
+ this.pause();
90
+ this.seek(seconds(0));
91
+ });
92
+ }
93
+ /**
94
+ * Jumps to a time. A playing session resumes through `buffering` once the frames there have
95
+ * been decoded; a paused one shows the target frame and stays paused.
96
+ */
97
+ seek(time) {
98
+ if (this.lifecycle.isOneOf("disposed", "error")) return;
99
+ this.lifecycle.change(() => {
100
+ const target = seconds(clamp(time, 0, this.parts.duration));
101
+ this.seeks.begin(this.lifecycle.current);
102
+ this.moveClockTo(target);
103
+ this.startRun(target);
104
+ this.lifecycle.moveTo("seeking");
105
+ this.lifecycle.moveTo(this.seeks.resumesPlaying ? "buffering" : "paused");
106
+ this.timeUpdates.announce(target);
107
+ });
108
+ }
109
+ /**
110
+ * Seeks to the key frame at or before `time`: what a dragged seek bar can show at once,
111
+ * without decoding a whole group of pictures first.
112
+ */
113
+ async scrub(time) {
114
+ const [track] = this.parts.frameSources;
115
+ if (!track || this.lifecycle.isOneOf("disposed", "error")) return;
116
+ const target = seconds(clamp(time, 0, this.parts.duration));
117
+ const ticket = this.seeks.claimScrub();
118
+ const keyframeTime = await keyframeTimeAt(track, target);
119
+ if (!this.seeks.isScrubCurrent(ticket) || this.lifecycle.isOneOf("disposed", "error")) return;
120
+ this.seek(keyframeTime);
121
+ }
122
+ /**
123
+ * Starts decoding at the current time without playing, so the frame there shows while the
124
+ * session is still `ready`.
125
+ */
126
+ preload() {
127
+ if (this.run || this.lifecycle.current !== "ready") return;
128
+ this.lifecycle.change(() => {
129
+ this.startRun(this.parts.clock.currentTime);
130
+ });
131
+ }
132
+ /**
133
+ * Presents the frame pair due at the clock's time, if a newer one has arrived, and follows
134
+ * the clock: to the end, into `buffering`, into failure, and into a start or a stop the
135
+ * platform made by itself (media keys, an audio interruption).
136
+ */
137
+ tick() {
138
+ if (this.lifecycle.isOneOf("disposed", "error")) return;
139
+ this.lifecycle.change(() => {
140
+ const { failure } = this.parts.clock;
141
+ if (failure) {
142
+ this.fail(failure);
143
+ return;
144
+ }
145
+ const now = this.parts.clock.currentTime;
146
+ if (this.clockWatch.wasMovedFromOutside({
147
+ now,
148
+ state: this.lifecycle.current
149
+ })) {
150
+ this.followMoveFromOutside(now);
151
+ return;
152
+ }
153
+ if (this.haveTicksStopped(now)) {
154
+ this.restartAt(now);
155
+ return;
156
+ }
157
+ this.presentDue(now);
158
+ if (this.lifecycle.is("playing")) this.followClock(now);
159
+ else if (this.isStartedFromOutside()) this.followStartFromOutside();
160
+ });
161
+ }
162
+ /**
163
+ * Hands the pair on screen to the sink again, so a change in how it is drawn shows while the
164
+ * picture stands still; while playing, the next pair shows it soon enough.
165
+ */
166
+ redraw() {
167
+ if (!this.presented || this.lifecycle.isOneOf("playing", "error")) return;
168
+ const { presented } = this;
169
+ this.lifecycle.change(() => this.draw(presented));
170
+ }
171
+ dispose() {
172
+ if (this.lifecycle.current === "disposed") return;
173
+ this.lifecycle.change(() => {
174
+ this.abortRun();
175
+ if (this.presented) closeFramePair(this.presented.pair);
176
+ this.presented = void 0;
177
+ this.parts.clock.pause();
178
+ this.lifecycle.moveTo("disposed");
179
+ this.settleStartAttempt();
180
+ });
181
+ this.events.removeAll();
182
+ }
183
+ /**
184
+ * A start from where the session stands, from the beginning when it stands at the end: what
185
+ * to await once it has been announced, or nothing when there is nothing to start.
186
+ */
187
+ beginStart() {
188
+ if (this.isAtTheEnd()) this.seek(seconds(0));
189
+ if (!this.lifecycle.canMoveTo("buffering")) return void 0;
190
+ if (!this.run) this.startRun(this.parts.clock.currentTime);
191
+ if (!this.isPrimed()) return this.startOncePrimed();
192
+ this.enterPlaying();
193
+ return this.startClockOnceHeard();
194
+ }
195
+ /**
196
+ * `buffering` until the frames are ready, the clock held meanwhile, even one the platform
197
+ * started (a media key's play). A pause settles the wait.
198
+ */
199
+ startOncePrimed() {
200
+ const attempt = new Deferred();
201
+ this.startAttempt = attempt;
202
+ this.parts.clock.pause();
203
+ this.lifecycle.moveTo("buffering");
204
+ return attempt.promise;
205
+ }
206
+ /**
207
+ * Starts the clock once `playing` has been heard, unless a listener paused on it: an audio
208
+ * element told to pause while it starts refuses the start.
209
+ */
210
+ async startClockOnceHeard() {
211
+ await Promise.resolve();
212
+ if (this.lifecycle.is("playing")) await this.startClock();
213
+ }
214
+ /**
215
+ * Playing from the clock's time now: ticks that never come from here on are missed ticks
216
+ * too, as when playback starts in a hidden tab or an offscreen frame.
217
+ */
218
+ enterPlaying() {
219
+ this.clockWatch.playingFrom(this.parts.clock.currentTime);
220
+ this.lifecycle.moveTo("playing");
221
+ }
222
+ presentDue(now) {
223
+ const pair = this.run?.takePairAt(now);
224
+ if (pair) this.present(pair, now);
225
+ }
226
+ /**
227
+ * The end first, then a stop from outside, then starvation: a clock that ran out while the
228
+ * ticks were away leaves stale pairs behind, which must not hold it in `buffering`.
229
+ */
230
+ followClock(now) {
231
+ if (this.isPlayedOut(now)) {
232
+ this.end();
233
+ return;
234
+ }
235
+ if (isStoppedFromOutside(this.parts.clock)) {
236
+ this.lifecycle.moveTo("paused");
237
+ this.timeUpdates.announce(now);
238
+ return;
239
+ }
240
+ if (this.run?.isStarvedAt(now, this.presented?.pair.timestamp) === true) {
241
+ this.parts.clock.pause();
242
+ this.lifecycle.moveTo("buffering");
243
+ return;
244
+ }
245
+ this.timeUpdates.followPlayback(now);
246
+ }
247
+ /**
248
+ * The platform started the clock by itself (a media key's play) while the session stood
249
+ * still: the session follows, as it follows a stop.
250
+ */
251
+ isStartedFromOutside() {
252
+ return this.parts.clock.isRunning && this.lifecycle.isOneOf("ready", "paused", "ended");
253
+ }
254
+ async followStartFromOutside() {
255
+ try {
256
+ await this.play();
257
+ } catch {}
258
+ }
259
+ /**
260
+ * Stops the clock and puts it at `time`, remembering where it landed (a clock clamps to the
261
+ * end of its own media).
262
+ */
263
+ moveClockTo(time) {
264
+ this.parts.clock.pause();
265
+ this.parts.clock.seek(time);
266
+ this.clockWatch.placedAt(this.parts.clock.currentTime);
267
+ }
268
+ /**
269
+ * Follows the clock where the platform moved it, as a seek there: a stop that came with the
270
+ * move is followed first and a start after it, as each would be followed alone.
271
+ */
272
+ followMoveFromOutside(now) {
273
+ const wasRunning = this.parts.clock.isRunning;
274
+ if (!wasRunning && this.lifecycle.is("playing")) this.pause();
275
+ this.seek(now);
276
+ if (wasRunning && this.lifecycle.is("paused")) this.followStartFromOutside();
277
+ }
278
+ /**
279
+ * Ticks were missed while the clock ran on; an ended clock is left to end the session.
280
+ */
281
+ haveTicksStopped(now) {
282
+ return this.clockWatch.wereTicksMissed({
283
+ now,
284
+ state: this.lifecycle.current
285
+ }) && !this.parts.clock.hasEnded;
286
+ }
287
+ /**
288
+ * Decodes anew from the clock's time, holding the clock until the frames there are ready:
289
+ * the seek a page would take for the viewer's is not needed, the clock is where it must be.
290
+ * A clock past the end ends there; one the platform stopped meanwhile lands `paused`.
291
+ */
292
+ restartAt(now) {
293
+ const { clock, duration } = this.parts;
294
+ if (now >= duration) {
295
+ this.moveClockTo(duration);
296
+ this.end();
297
+ return;
298
+ }
299
+ const wasRunning = clock.isRunning;
300
+ clock.pause();
301
+ this.startRun(now);
302
+ this.lifecycle.moveTo(wasRunning ? "buffering" : "paused");
303
+ if (!wasRunning) this.timeUpdates.announce(now);
304
+ }
305
+ /**
306
+ * Ended, or paused at the end: a play from there starts over, as a media element does.
307
+ */
308
+ isAtTheEnd() {
309
+ return this.lifecycle.current === "ended" || isAtEndOfMedia(this.parts.clock, this.parts.duration);
310
+ }
311
+ isPrimed() {
312
+ return this.run?.isPrimedAt(this.parts.clock.currentTime) === true;
313
+ }
314
+ /**
315
+ * Called as pairs arrive and when a run ends: the moment `buffering` has what it waits for.
316
+ */
317
+ resumeIfPrimed() {
318
+ if (this.lifecycle.current === "buffering" && this.isPrimed()) this.resume();
319
+ }
320
+ /**
321
+ * Leaves `buffering` for `playing` and starts the clock; a refusal (only possible before the
322
+ * first start) lands `paused` and is reported to the waiting `play`.
323
+ */
324
+ async resume() {
325
+ const attempt = this.takeStartAttempt();
326
+ this.enterPlaying();
327
+ try {
328
+ await this.startClockOnceHeard();
329
+ attempt?.resolve();
330
+ } catch (error) {
331
+ attempt?.reject(error);
332
+ }
333
+ }
334
+ async startClock() {
335
+ try {
336
+ await this.parts.clock.start();
337
+ } catch (error) {
338
+ if (this.lifecycle.is("playing")) this.lifecycle.moveTo("paused");
339
+ throw error;
340
+ }
341
+ }
342
+ settleStartAttempt() {
343
+ this.takeStartAttempt()?.resolve();
344
+ }
345
+ takeStartAttempt() {
346
+ const attempt = this.startAttempt;
347
+ this.startAttempt = void 0;
348
+ return attempt;
349
+ }
350
+ /**
351
+ * The clock's own media ran out (an audio track shorter than the video: the pairs after it are
352
+ * unreachable, and the decoders may wait for room to decode them forever), or everything
353
+ * decoded to the end has been shown and the clock passed the duration.
354
+ */
355
+ isPlayedOut(now) {
356
+ const hasShownAll = this.run?.isDrained === true && now >= this.parts.duration;
357
+ return this.parts.clock.hasEnded || hasShownAll;
358
+ }
359
+ present(pair, mediaTime) {
360
+ if (this.presented) closeFramePair(this.presented.pair);
361
+ this.presented = {
362
+ pair,
363
+ mediaTime
364
+ };
365
+ if (this.draw(this.presented)) this.lifecycle.announce("present", mediaTime);
366
+ }
367
+ /**
368
+ * A sink that cannot draw (a renderer the GPU refused) fails the session, never unheard.
369
+ */
370
+ draw(presentation) {
371
+ try {
372
+ this.parts.sink.present(presentation);
373
+ return true;
374
+ } catch (error) {
375
+ this.fail(asGyroViewError(error, "render-unavailable", "the picture could not be drawn"));
376
+ return false;
377
+ }
378
+ }
379
+ /**
380
+ * Replaces the run under way; the one replaced reports to nobody, so only the current run may
381
+ * wake a waiting start, end or fail the session.
382
+ */
383
+ startRun(from) {
384
+ this.abortRun();
385
+ this.run = DecodeRun.start(this.parts, from, {
386
+ onProgress: () => {
387
+ this.resumeIfPrimed();
388
+ },
389
+ onFailure: (error) => {
390
+ this.fail(error);
391
+ }
392
+ });
393
+ }
394
+ abortRun() {
395
+ this.run?.abort();
396
+ this.run = void 0;
397
+ }
398
+ end() {
399
+ if (!this.lifecycle.canMoveTo("ended")) return;
400
+ this.parts.clock.pause();
401
+ this.lifecycle.moveTo("ended");
402
+ this.timeUpdates.announce(this.parts.clock.currentTime);
403
+ this.lifecycle.announce("ended", void 0);
404
+ }
405
+ fail(error) {
406
+ if (!this.lifecycle.canMoveTo("error")) return;
407
+ this.lifecycle.change(() => {
408
+ this.parts.clock.pause();
409
+ this.abortRun();
410
+ this.lifecycle.moveTo("error");
411
+ this.settleStartAttempt();
412
+ this.lifecycle.announce("error", asGyroViewError(error, "decode", "playback failed"));
413
+ });
414
+ }
415
+ };
416
+ //#endregion
417
+ export { PlaybackSession };
@@ -0,0 +1,36 @@
1
+ import { isFlowing } from "../../domain/playback/PlayerState.js";
2
+ //#region ../../packages/core/src/application/playback/SeekOrder.ts
3
+ /**
4
+ * The seeks of a session in order, and whether the latest resumes playing. A scrub lands only if
5
+ * no seek or scrub came after it while it looked up its key frame.
6
+ */
7
+ var SeekOrder = class {
8
+ generation = 0;
9
+ scrubs = 0;
10
+ isFromFlowing = false;
11
+ /**
12
+ * Whether the seek under way resumes playing once its frames are ready.
13
+ */
14
+ get resumesPlaying() {
15
+ return this.isFromFlowing;
16
+ }
17
+ /**
18
+ * A new seek, begun from `state`.
19
+ */
20
+ begin(state) {
21
+ this.isFromFlowing = isFlowing(state);
22
+ this.generation += 1;
23
+ }
24
+ claimScrub() {
25
+ this.scrubs += 1;
26
+ return {
27
+ seek: this.generation,
28
+ scrub: this.scrubs
29
+ };
30
+ }
31
+ isScrubCurrent(ticket) {
32
+ return ticket.seek === this.generation && ticket.scrub === this.scrubs;
33
+ }
34
+ };
35
+ //#endregion
36
+ export { SeekOrder };
@@ -0,0 +1,42 @@
1
+ import { Outbox } from "../../shared/events/Outbox.js";
2
+ import { PlayerStateMachine } from "../../domain/playback/PlayerState.js";
3
+ //#region ../../packages/core/src/application/playback/SessionLifecycle.ts
4
+ /**
5
+ * A session's state and what it announces. Each change the session makes through
6
+ * {@link change} is heard once it is whole (ADR 0021): no listener runs in the middle of one,
7
+ * and a listener's own change supersedes what the older one had still to announce.
8
+ */
9
+ var SessionLifecycle = class {
10
+ machine = new PlayerStateMachine();
11
+ outbox;
12
+ constructor(events) {
13
+ this.outbox = new Outbox(events);
14
+ }
15
+ get current() {
16
+ return this.machine.state;
17
+ }
18
+ is(state) {
19
+ return this.machine.state === state;
20
+ }
21
+ isOneOf(...states) {
22
+ return this.machine.isOneOf(...states);
23
+ }
24
+ canMoveTo(next) {
25
+ return this.machine.canTransitionTo(next);
26
+ }
27
+ moveTo(next) {
28
+ this.machine.transitionTo(next);
29
+ this.announce("statechange", next);
30
+ }
31
+ announce(name, payload) {
32
+ this.outbox.emit(name, payload);
33
+ }
34
+ /**
35
+ * Runs `make` as one change, announcing what it announced once the outermost change is whole.
36
+ */
37
+ change(make) {
38
+ return this.outbox.change(make);
39
+ }
40
+ };
41
+ //#endregion
42
+ export { SessionLifecycle };
@@ -0,0 +1,55 @@
1
+ import { closeFramePair } from "../../ports/FramePair.js";
2
+ //#region ../../packages/core/src/application/playback/StartGate.ts
3
+ /**
4
+ * Sits between the pairer and the output of one decode run. Pairs at or after the start pass
5
+ * straight through. Pairs before the start exist only because decoding had to begin at a key
6
+ * frame; of those the gate keeps the newest and releases it ahead of the first pair after the
7
+ * start, because that is the frame on screen at the start. Older ones are closed.
8
+ */
9
+ var StartGate = class {
10
+ from;
11
+ deliver;
12
+ held;
13
+ droppedCount = 0;
14
+ constructor(from, deliver) {
15
+ this.from = from;
16
+ this.deliver = deliver;
17
+ }
18
+ /**
19
+ * How many pairs before the start were superseded by a newer one and closed.
20
+ */
21
+ get dropped() {
22
+ return this.droppedCount;
23
+ }
24
+ push(pair) {
25
+ if (pair.timestamp >= this.from) {
26
+ this.release();
27
+ this.deliver(pair);
28
+ return;
29
+ }
30
+ this.hold(pair);
31
+ }
32
+ /**
33
+ * Delivers the held pair, if any. A run that ends before reaching the start (a start at or
34
+ * past the last frame) calls this so that last frame is still shown.
35
+ */
36
+ release() {
37
+ if (!this.held) return;
38
+ const pair = this.held;
39
+ this.held = void 0;
40
+ this.deliver(pair);
41
+ }
42
+ discard() {
43
+ if (this.held) closeFramePair(this.held);
44
+ this.held = void 0;
45
+ }
46
+ hold(pair) {
47
+ if (this.held) {
48
+ closeFramePair(this.held);
49
+ this.droppedCount += 1;
50
+ }
51
+ this.held = pair;
52
+ }
53
+ };
54
+ //#endregion
55
+ export { StartGate };
@@ -0,0 +1,31 @@
1
+ //#region ../../packages/core/src/application/playback/TimeUpdates.ts
2
+ /**
3
+ * How much media time passes between two announcements while playing: the slowest cadence of a
4
+ * media element's `timeupdate`, often enough for a seek bar and cheap to relay across
5
+ * `postMessage`.
6
+ */
7
+ var INTERVAL_SECONDS = .25;
8
+ /**
9
+ * The cadence of a session's `timeupdate`: every quarter second of playback, and at once when
10
+ * the session says so (a pause, a seek, the end).
11
+ */
12
+ var TimeUpdates = class {
13
+ lifecycle;
14
+ announced;
15
+ constructor(lifecycle) {
16
+ this.lifecycle = lifecycle;
17
+ }
18
+ announce(time) {
19
+ this.announced = time;
20
+ this.lifecycle.announce("timeupdate", time);
21
+ }
22
+ /**
23
+ * Announces the time if a quarter second of playback passed since the last announcement.
24
+ */
25
+ followPlayback(now) {
26
+ const { announced } = this;
27
+ if (announced === void 0 || Math.abs(now - announced) >= INTERVAL_SECONDS) this.announce(now);
28
+ }
29
+ };
30
+ //#endregion
31
+ export { TimeUpdates };
@@ -0,0 +1,46 @@
1
+ import { milliseconds, millisecondsToSeconds, seconds } from "../../shared/units/time.js";
2
+ //#region ../../packages/core/src/application/playback/WallClock.ts
3
+ /**
4
+ * PlaybackClock that advances with `now` (milliseconds of real time, from a monotonic source)
5
+ * and never ends or fails by itself: for recordings without a playable audio track, and tests.
6
+ */
7
+ var WallClock = class {
8
+ now;
9
+ hasEnded = false;
10
+ failure = void 0;
11
+ isRunningNow = false;
12
+ positionAtAnchor = seconds(0);
13
+ anchorMs = 0;
14
+ constructor(now) {
15
+ this.now = now;
16
+ }
17
+ get isRunning() {
18
+ return this.isRunningNow;
19
+ }
20
+ get currentTime() {
21
+ if (!this.isRunningNow) return this.positionAtAnchor;
22
+ const elapsed = millisecondsToSeconds(milliseconds(this.now() - this.anchorMs));
23
+ return seconds(this.positionAtAnchor + elapsed);
24
+ }
25
+ start() {
26
+ if (!this.isRunningNow) {
27
+ this.anchorMs = this.now();
28
+ this.isRunningNow = true;
29
+ }
30
+ return Promise.resolve();
31
+ }
32
+ pause() {
33
+ if (!this.isRunningNow) return;
34
+ this.positionAtAnchor = this.currentTime;
35
+ this.isRunningNow = false;
36
+ }
37
+ seek(time) {
38
+ this.positionAtAnchor = time;
39
+ this.anchorMs = this.now();
40
+ }
41
+ dispose() {
42
+ this.pause();
43
+ }
44
+ };
45
+ //#endregion
46
+ export { WallClock };
@@ -0,0 +1,15 @@
1
+ //#region ../../packages/core/src/application/playback/keyframeTimeAt.ts
2
+ /**
3
+ * The time of the key frame at or before `time`, where a dragged seek bar can show a picture at
4
+ * once; `time` itself when the track has none there or cannot say (a recording let go meanwhile).
5
+ * A read that is truly broken then fails the decode, where the session reports it.
6
+ */
7
+ async function keyframeTimeAt(track, time) {
8
+ try {
9
+ return (await track.keyPacketAt(time))?.timestamp ?? time;
10
+ } catch {
11
+ return time;
12
+ }
13
+ }
14
+ //#endregion
15
+ export { keyframeTimeAt };