@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,84 @@
1
+ import { GyroViewError } from "../../../core/src/shared/errors/GyroViewError.js";
2
+ import { seconds } from "../../../core/src/shared/units/time.js";
3
+ import "../../../core/src/index.js";
4
+ import { copyOfBytes } from "./bufferSources.js";
5
+ import { EncodedPacketSink } from "mediabunny";
6
+ //#region ../../packages/adapters/mediabunny/src/MediabunnyVideoTrackReader.ts
7
+ /**
8
+ * Container metadata may mark a packet as a key packet that is not one; the bitstream decides.
9
+ */
10
+ var VERIFIED = { verifyKeyPackets: true };
11
+ /**
12
+ * VideoTrackReader over one mediabunny video track. Packets handed out are plain data; the
13
+ * mediabunny packet behind each is remembered so iteration can resume from it.
14
+ */
15
+ var MediabunnyVideoTrackReader = class MediabunnyVideoTrackReader {
16
+ track;
17
+ description;
18
+ sink;
19
+ originals = /* @__PURE__ */ new WeakMap();
20
+ constructor(track, description) {
21
+ this.track = track;
22
+ this.description = description;
23
+ this.sink = new EncodedPacketSink(track);
24
+ }
25
+ static async open(track, trackIndex) {
26
+ const [codec, codedWidth, codedHeight] = await Promise.all([
27
+ track.getCodecParameterString(),
28
+ track.getCodedWidth(),
29
+ track.getCodedHeight()
30
+ ]);
31
+ if (codec === null) throw new GyroViewError("codec-unsupported", `video track ${trackIndex} has an unknown codec`);
32
+ return new MediabunnyVideoTrackReader(track, {
33
+ trackIndex,
34
+ codedWidth,
35
+ codedHeight,
36
+ codec
37
+ });
38
+ }
39
+ async decoderConfiguration() {
40
+ const config = await this.track.getDecoderConfig();
41
+ if (config === null) throw new GyroViewError("codec-unsupported", `video track ${this.description.trackIndex} (${this.description.codec}) cannot be configured for decoding`);
42
+ return {
43
+ codec: config.codec,
44
+ codedWidth: config.codedWidth ?? this.description.codedWidth,
45
+ codedHeight: config.codedHeight ?? this.description.codedHeight,
46
+ description: config.description === void 0 ? void 0 : copyOfBytes(config.description),
47
+ isFullRange: config.colorSpace?.fullRange ?? void 0
48
+ };
49
+ }
50
+ async keyPacketAt(time) {
51
+ const packet = await this.sink.getKeyPacket(time, VERIFIED);
52
+ return packet === null ? void 0 : this.wrap(packet);
53
+ }
54
+ async firstKeyPacket() {
55
+ const packet = await this.sink.getFirstKeyPacket(VERIFIED);
56
+ return packet === null ? void 0 : this.wrap(packet);
57
+ }
58
+ async *packetsFrom(start) {
59
+ const original = this.originals.get(start);
60
+ if (!original) throw new GyroViewError("invariant-violation", "packetsFrom needs a packet handed out by this reader");
61
+ for await (const packet of this.sink.packets(original)) yield this.wrap(packet);
62
+ }
63
+ async sampleTimestamps() {
64
+ const timestamps = [];
65
+ const metadataOnly = this.sink.packets(void 0, void 0, { metadataOnly: true });
66
+ for await (const packet of metadataOnly) timestamps.push(seconds(packet.timestamp));
67
+ return timestamps.toSorted((left, right) => left - right);
68
+ }
69
+ async frameCount() {
70
+ return (await this.track.computePacketStats()).packetCount;
71
+ }
72
+ wrap(packet) {
73
+ const wrapped = {
74
+ timestamp: seconds(packet.timestamp),
75
+ duration: seconds(packet.duration),
76
+ isKeyFrame: packet.type === "key",
77
+ data: packet.data
78
+ };
79
+ this.originals.set(wrapped, packet);
80
+ return wrapped;
81
+ }
82
+ };
83
+ //#endregion
84
+ export { MediabunnyVideoTrackReader };
@@ -0,0 +1,84 @@
1
+ import { GyroViewError, ensureInvariant } from "../../../core/src/shared/errors/GyroViewError.js";
2
+ import "../../../core/src/index.js";
3
+ //#region ../../packages/adapters/mediabunny/src/SegmentChannel.ts
4
+ /**
5
+ * Hands segments from the muxer's synchronous callbacks to one asynchronous consumer, with a
6
+ * bound on how many may wait unconsumed so re-packaging never runs far ahead of playback.
7
+ * Closing from either side ends the exchange; a producer failure reaches the consumer as a
8
+ * thrown error once the segments before it are drained. A second concurrent consumer is a
9
+ * programming error.
10
+ */
11
+ var SegmentChannel = class {
12
+ capacity;
13
+ queue = [];
14
+ roomWaiters = [];
15
+ takeWaiter;
16
+ isClosedNow = false;
17
+ failure;
18
+ constructor(capacity) {
19
+ this.capacity = capacity;
20
+ }
21
+ get isClosed() {
22
+ return this.isClosedNow;
23
+ }
24
+ push(segment) {
25
+ if (this.isClosedNow) return;
26
+ this.queue.push(segment);
27
+ this.wakeTaker();
28
+ }
29
+ /**
30
+ * Resolves once fewer than `capacity` segments wait, or at once when the channel is closed.
31
+ */
32
+ waitForRoom() {
33
+ return this.queue.length < this.capacity || this.isClosedNow ? Promise.resolve() : new Promise((resolve) => {
34
+ this.roomWaiters.push(resolve);
35
+ });
36
+ }
37
+ close() {
38
+ this.isClosedNow = true;
39
+ this.wakeTaker();
40
+ this.wakeRoomWaiters();
41
+ }
42
+ fail(error) {
43
+ this.failure ??= error instanceof Error ? error : new GyroViewError("decode", "audio re-packaging failed", { cause: error });
44
+ this.close();
45
+ }
46
+ async *segments() {
47
+ let next = await this.take();
48
+ while (next) {
49
+ yield next;
50
+ next = await this.take();
51
+ }
52
+ }
53
+ /**
54
+ * The next segment; undefined once closed and drained; the producer's failure, if it had one.
55
+ */
56
+ async take() {
57
+ while (this.queue.length === 0 && !this.isClosedNow) await this.nextPush();
58
+ const next = this.queue.shift();
59
+ if (next) {
60
+ this.wakeRoomWaiters();
61
+ return next;
62
+ }
63
+ if (this.failure) throw this.failure;
64
+ }
65
+ nextPush() {
66
+ ensureInvariant(this.takeWaiter === void 0, "a segment channel has one consumer");
67
+ return new Promise((resolve) => {
68
+ this.takeWaiter = resolve;
69
+ });
70
+ }
71
+ wakeTaker() {
72
+ const waiter = this.takeWaiter;
73
+ this.takeWaiter = void 0;
74
+ waiter?.();
75
+ }
76
+ wakeRoomWaiters() {
77
+ if (this.queue.length >= this.capacity && !this.isClosedNow) return;
78
+ const waiters = this.roomWaiters;
79
+ this.roomWaiters = [];
80
+ for (const resolve of waiters) resolve();
81
+ }
82
+ };
83
+ //#endregion
84
+ export { SegmentChannel };
@@ -0,0 +1,12 @@
1
+ //#region ../../packages/adapters/mediabunny/src/bufferSources.ts
2
+ /**
3
+ * A copy of a buffer source as a plain Uint8Array. Decoder descriptions are small; the copy keeps
4
+ * them independent of mediabunny's own buffers and never on shared memory. SharedArrayBuffer is
5
+ * not named, since browsers define it only under cross-origin isolation.
6
+ */
7
+ function copyOfBytes(source) {
8
+ const view = ArrayBuffer.isView(source) ? new Uint8Array(source.buffer, source.byteOffset, source.byteLength) : new Uint8Array(source);
9
+ return Uint8Array.from(view);
10
+ }
11
+ //#endregion
12
+ export { copyOfBytes };
@@ -0,0 +1,2 @@
1
+ import { MediabunnyDemuxer } from "./MediabunnyDemuxer.js";
2
+ export { MediabunnyDemuxer };
@@ -0,0 +1,139 @@
1
+ import { GyroViewError, isAbortError, messageOf } from "../../../core/src/shared/errors/GyroViewError.js";
2
+ import { seconds } from "../../../core/src/shared/units/time.js";
3
+ import "../../../core/src/index.js";
4
+ import { attachMediaSource, isMediaSourceTypeSupported, mediaSourceConstructor } from "./mediaSourceSupport.js";
5
+ import { SourceBufferFeeder } from "./SourceBufferFeeder.js";
6
+ //#region ../../packages/adapters/mse-audio/src/MediaSourceAudioClock.ts
7
+ /**
8
+ * PlaybackClock over an audio element fed through Media Source Extensions with the recording's
9
+ * own audio track, so the picture follows the sound and volume and mute are the element's.
10
+ * The element is borrowed from the host; disposing detaches the media source from it.
11
+ * See ADR 0007.
12
+ */
13
+ var MediaSourceAudioClock = class MediaSourceAudioClock {
14
+ element;
15
+ feeder;
16
+ attached;
17
+ /**
18
+ * The end the clock stands at since a seek there, the element left where it was.
19
+ */
20
+ standingEnd;
21
+ constructor(element, feeder, attached) {
22
+ this.element = element;
23
+ this.feeder = feeder;
24
+ this.attached = attached;
25
+ }
26
+ static isSupported(source) {
27
+ return isMediaSourceTypeSupported(source.mimeType);
28
+ }
29
+ /**
30
+ * Attaches a media source to the element and starts buffering from the beginning.
31
+ */
32
+ static async open(element, source) {
33
+ const mediaSourceClass = mediaSourceConstructor();
34
+ if (!mediaSourceClass?.isTypeSupported(source.mimeType)) throw new GyroViewError("codec-unsupported", `${source.mimeType} cannot play through Media Source Extensions here`);
35
+ const attached = await attachMediaSource(element, mediaSourceClass);
36
+ try {
37
+ const sourceBuffer = attached.mediaSource.addSourceBuffer(source.mimeType);
38
+ attached.mediaSource.duration = source.duration;
39
+ const feeder = new SourceBufferFeeder({
40
+ element,
41
+ mediaSource: attached.mediaSource,
42
+ sourceBuffer,
43
+ source
44
+ });
45
+ feeder.restartFrom(seconds(0));
46
+ return new MediaSourceAudioClock(element, feeder, attached);
47
+ } catch (error) {
48
+ attached.detach();
49
+ throw asClockFailure(error);
50
+ }
51
+ }
52
+ get currentTime() {
53
+ return this.standingEnd ?? seconds(this.element.currentTime);
54
+ }
55
+ /**
56
+ * Whoever paused the element (the session, media keys, an audio interruption): `play()` clears
57
+ * `paused` at once, so a start in flight counts as running.
58
+ */
59
+ get isRunning() {
60
+ return !this.element.paused;
61
+ }
62
+ /**
63
+ * Standing at the end since a seek there, or the element `ended`: it played to its end.
64
+ */
65
+ get hasEnded() {
66
+ return this.standingEnd !== void 0 || this.element.ended;
67
+ }
68
+ /**
69
+ * What the feeder met, or the element's own error: sound that buffered but would not decode,
70
+ * which stalls the element without the feeder ever hearing of it.
71
+ */
72
+ get failure() {
73
+ return this.feeder.failure ?? failureOfElement(this.element);
74
+ }
75
+ /**
76
+ * Resolves once playback has started. A `pause` that interrupts the start is not an error;
77
+ * the autoplay policy refusing to start is reported as `playback-blocked`. A clock at its end
78
+ * stays there: `play()` would play the element from the beginning, or from where a seek to the
79
+ * end left it, under the last picture.
80
+ */
81
+ async start() {
82
+ if (this.hasEnded) return;
83
+ try {
84
+ await this.element.play();
85
+ } catch (error) {
86
+ if (isAbortError(error)) return;
87
+ const isBlocked = isNamed(error, "NotAllowedError");
88
+ throw new GyroViewError(isBlocked ? "playback-blocked" : "decode", isBlocked ? "the browser refused to start audio playback; a user gesture is needed" : "audio playback failed to start", { cause: error });
89
+ }
90
+ }
91
+ pause() {
92
+ this.element.pause();
93
+ }
94
+ /**
95
+ * A seek to the end stands the clock there and leaves the element paused where it was: an
96
+ * element at its end has nothing left to play, and engines differ right at the end. WebKit on
97
+ * Linux, under load, fails an element whose seek to the end of its sound is still pending when
98
+ * the stream ends there (media error 4); macOS WebKit loses a seek just short of the end.
99
+ */
100
+ seek(time) {
101
+ const end = this.attached.mediaSource.duration;
102
+ if (time >= end) {
103
+ this.standAtEnd(seconds(end));
104
+ return;
105
+ }
106
+ this.standingEnd = void 0;
107
+ this.element.currentTime = time;
108
+ this.feeder.restartFrom(time);
109
+ }
110
+ dispose() {
111
+ this.feeder.dispose();
112
+ this.element.pause();
113
+ this.attached.detach();
114
+ }
115
+ standAtEnd(end) {
116
+ this.element.pause();
117
+ this.standingEnd = end;
118
+ }
119
+ };
120
+ function isNamed(error, name) {
121
+ return error instanceof Error && error.name === name;
122
+ }
123
+ function failureOfElement(element) {
124
+ const { error } = element;
125
+ if (error === null) return void 0;
126
+ const message = `the audio element failed (media error ${error.code})`;
127
+ return new GyroViewError("decode", message, { cause: error });
128
+ }
129
+ /**
130
+ * The media source's own exceptions cross the port as typed failures: a type it will not buffer
131
+ * is `codec-unsupported`, anything else `decode`.
132
+ */
133
+ function asClockFailure(error) {
134
+ if (error instanceof GyroViewError) return error;
135
+ const isUnsupported = isNamed(error, "NotSupportedError");
136
+ return new GyroViewError(isUnsupported ? "codec-unsupported" : "decode", `the audio element refused the recording's sound: ${messageOf(error)}`, { cause: error });
137
+ }
138
+ //#endregion
139
+ export { MediaSourceAudioClock };
@@ -0,0 +1,174 @@
1
+ import { GyroViewError, asGyroViewError } from "../../../core/src/shared/errors/GyroViewError.js";
2
+ import { seconds } from "../../../core/src/shared/units/time.js";
3
+ import { RunStop, STOPPED } from "../../../core/src/shared/async/RunStop.js";
4
+ import "../../../core/src/index.js";
5
+ import { nextOfEvents } from "./events.js";
6
+ //#region ../../packages/adapters/mse-audio/src/SourceBufferFeeder.ts
7
+ /**
8
+ * How far past the playhead audio is kept buffered, to ride out a slow network.
9
+ */
10
+ var BUFFER_AHEAD_SECONDS = 30;
11
+ /**
12
+ * Buffered audio kept behind the playhead for small backward seeks; older data is evicted.
13
+ */
14
+ var KEEP_BEHIND_SECONDS = 30;
15
+ /**
16
+ * Eviction runs once this much audio has piled up behind the playhead.
17
+ */
18
+ var EVICT_BEHIND_SECONDS = 60;
19
+ /**
20
+ * How far before its time a run starts feeding: each AAC frame overlaps the one before, so the
21
+ * decoder gets the frames that lead up to the playhead.
22
+ */
23
+ var LEAD_IN_SECONDS = 1;
24
+ /**
25
+ * Events on which the feeder re-checks whether the buffer needs more data.
26
+ */
27
+ var WAKE_EVENTS = [
28
+ "timeupdate",
29
+ "seeking",
30
+ "waiting",
31
+ "play"
32
+ ];
33
+ /**
34
+ * Keeps the source buffer filled from the playhead onwards: appends segments while less than
35
+ * `BUFFER_AHEAD_SECONDS` of audio is buffered past the current time, evicts what lies far behind,
36
+ * and ends the stream when the track ends. A seek restarts it from what the new time is missing;
37
+ * disposal stops it.
38
+ */
39
+ var SourceBufferFeeder = class {
40
+ parts;
41
+ stop = new RunStop();
42
+ run = Promise.resolve();
43
+ failureValue;
44
+ constructor(parts) {
45
+ this.parts = parts;
46
+ }
47
+ /**
48
+ * The first error the feeder met, if any. The element keeps whatever was buffered before.
49
+ */
50
+ get failure() {
51
+ return this.failureValue;
52
+ }
53
+ restartFrom(time) {
54
+ this.stop.stop();
55
+ const stop = new RunStop();
56
+ this.stop = stop;
57
+ this.run = this.feedAfter(this.run, time, stop);
58
+ }
59
+ /**
60
+ * Stops feeding. An append or eviction under way is left to the media source's detachment,
61
+ * which ends it; `abort` would throw while old audio is being evicted.
62
+ */
63
+ dispose() {
64
+ this.stop.stop();
65
+ }
66
+ /**
67
+ * Runs are serialised so a restart never appends while the previous run is mid-append.
68
+ */
69
+ async feedAfter(previous, from, stop) {
70
+ await previous;
71
+ await this.feed(from, stop);
72
+ }
73
+ async feed(from, stop) {
74
+ try {
75
+ await this.settlePendingAppend();
76
+ if (this.hasAllAudioFrom(from)) return;
77
+ await this.feedFrom(this.startOf(from), stop);
78
+ } catch (error) {
79
+ this.failureValue ??= asGyroViewError(error, "decode", "feeding the audio buffer failed");
80
+ }
81
+ }
82
+ async feedFrom(start, stop) {
83
+ const segments = this.parts.source.segmentsFrom(start)[Symbol.asyncIterator]();
84
+ try {
85
+ if (await this.pump(segments, stop)) this.endStream();
86
+ } finally {
87
+ segments.return?.();
88
+ }
89
+ }
90
+ /**
91
+ * The stream ended and `from` lies in the range that runs to its end: nothing is left to
92
+ * append, and a run would only append the last frame again and reopen the stream.
93
+ */
94
+ hasAllAudioFrom(from) {
95
+ const { buffered } = this.parts.element;
96
+ const end = this.bufferedEndAt(from);
97
+ return this.parts.mediaSource.readyState === "ended" && end > from && end === buffered.end(buffered.length - 1);
98
+ }
99
+ /**
100
+ * Where a run for `from` starts: at the end of the audio already buffered around it, so a seek
101
+ * within the buffer appends only what is missing, or a lead-in before it otherwise.
102
+ */
103
+ startOf(from) {
104
+ const bufferedEnd = this.bufferedEndAt(from);
105
+ return seconds(bufferedEnd > from ? bufferedEnd : Math.max(0, from - LEAD_IN_SECONDS));
106
+ }
107
+ /**
108
+ * Appends segments as the playhead needs them. True when the track ended, false when stopped.
109
+ */
110
+ async pump(segments, stop) {
111
+ let next = await this.nextWhenNeeded(segments, stop);
112
+ while (next !== STOPPED) {
113
+ if (next.done === true) return true;
114
+ this.evictBehind();
115
+ await this.append(next.value);
116
+ next = await this.nextWhenNeeded(segments, stop);
117
+ }
118
+ return false;
119
+ }
120
+ /**
121
+ * The next segment once the buffer has room for it, or STOPPED if the run ended meanwhile.
122
+ */
123
+ async nextWhenNeeded(segments, stop) {
124
+ await this.waitUntilNeeded(stop);
125
+ return stop.race(() => segments.next());
126
+ }
127
+ async waitUntilNeeded(stop) {
128
+ while (!stop.wasStopped && this.bufferedAhead() >= BUFFER_AHEAD_SECONDS) {
129
+ const woken = new AbortController();
130
+ await stop.race(() => nextOfEvents(this.parts.element, WAKE_EVENTS, woken.signal));
131
+ woken.abort();
132
+ }
133
+ }
134
+ /**
135
+ * Seconds buffered from the playhead to the end of the range it lies in; zero outside any.
136
+ */
137
+ bufferedAhead() {
138
+ const { currentTime } = this.parts.element;
139
+ return seconds(this.bufferedEndAt(currentTime) - currentTime);
140
+ }
141
+ /**
142
+ * The end of the buffered range `time` lies in; `time` itself outside any.
143
+ */
144
+ bufferedEndAt(time) {
145
+ const { buffered } = this.parts.element;
146
+ for (let index = 0; index < buffered.length; index += 1) if (buffered.start(index) <= time && time < buffered.end(index)) return buffered.end(index);
147
+ return time;
148
+ }
149
+ evictBehind() {
150
+ const { buffered, currentTime } = this.parts.element;
151
+ if (currentTime - (buffered.length > 0 ? buffered.start(0) : currentTime) < EVICT_BEHIND_SECONDS || this.parts.sourceBuffer.updating) return;
152
+ this.parts.sourceBuffer.remove(0, currentTime - KEEP_BEHIND_SECONDS);
153
+ }
154
+ async append(segment) {
155
+ const { sourceBuffer } = this.parts;
156
+ await this.settlePendingAppend();
157
+ const outcome = nextOfEvents(sourceBuffer, ["updateend", "error"]);
158
+ try {
159
+ sourceBuffer.appendBuffer(segment);
160
+ } catch (error) {
161
+ throw new GyroViewError("decode", "the audio buffer rejected a segment", { cause: error });
162
+ }
163
+ if (await outcome === "error") throw new GyroViewError("decode", "the audio buffer could not parse a segment");
164
+ }
165
+ async settlePendingAppend() {
166
+ if (this.parts.sourceBuffer.updating) await nextOfEvents(this.parts.sourceBuffer, ["updateend"]);
167
+ }
168
+ endStream() {
169
+ const { mediaSource, sourceBuffer } = this.parts;
170
+ if (mediaSource.readyState === "open" && !sourceBuffer.updating) mediaSource.endOfStream();
171
+ }
172
+ };
173
+ //#endregion
174
+ export { SourceBufferFeeder };
@@ -0,0 +1,26 @@
1
+ //#region ../../packages/adapters/mse-audio/src/events.ts
2
+ /**
3
+ * Resolves with the type of the first of the given events. Every listener is removed then, or as
4
+ * soon as `cancel` aborts, so a wait that lost a race leaves nothing on the target; the watch on
5
+ * `cancel` goes with them, so a long-lived signal gathers nothing either.
6
+ */
7
+ function nextOfEvents(target, types, cancel) {
8
+ const listening = new AbortController();
9
+ cancel?.addEventListener("abort", () => {
10
+ listening.abort();
11
+ }, {
12
+ once: true,
13
+ signal: listening.signal
14
+ });
15
+ return new Promise((resolve) => {
16
+ for (const type of types) target.addEventListener(type, () => {
17
+ listening.abort();
18
+ resolve(type);
19
+ }, {
20
+ once: true,
21
+ signal: listening.signal
22
+ });
23
+ });
24
+ }
25
+ //#endregion
26
+ export { nextOfEvents };
@@ -0,0 +1,2 @@
1
+ import { MediaSourceAudioClock } from "./MediaSourceAudioClock.js";
2
+ export { MediaSourceAudioClock };
@@ -0,0 +1,82 @@
1
+ import { GyroViewError } from "../../../core/src/shared/errors/GyroViewError.js";
2
+ import "../../../core/src/index.js";
3
+ import { nextOfEvents } from "./events.js";
4
+ //#region ../../packages/adapters/mse-audio/src/mediaSourceSupport.ts
5
+ var MANAGED_MEDIA_SOURCE = "ManagedMediaSource";
6
+ var MEDIA_SOURCE = "MediaSource";
7
+ function isMediaSourceConstructor(value) {
8
+ return typeof value === "function" && typeof value.isTypeSupported === "function";
9
+ }
10
+ function globalConstructor(name) {
11
+ const value = Reflect.get(globalThis, name);
12
+ return isMediaSourceConstructor(value) ? value : void 0;
13
+ }
14
+ /**
15
+ * The media source implementation to use, preferring the managed one where it exists.
16
+ */
17
+ function mediaSourceConstructor() {
18
+ return globalConstructor(MANAGED_MEDIA_SOURCE) ?? globalConstructor(MEDIA_SOURCE);
19
+ }
20
+ function isMediaSourceTypeSupported(mimeType) {
21
+ return mediaSourceConstructor()?.isTypeSupported(mimeType) ?? false;
22
+ }
23
+ /**
24
+ * `sourceopen` follows the attachment within a task or two; a browser that never sends it, or
25
+ * sends the element's `error` instead, must not hold the load, which falls back to a silent clock.
26
+ */
27
+ var SOURCE_OPEN_TIMEOUT_MS = 1e4;
28
+ /**
29
+ * Attaches a fresh media source to the element and resolves once it is open for source buffers;
30
+ * rejects with `decode`, detached again, when the element fails or the source never opens.
31
+ */
32
+ async function attachMediaSource(element, mediaSourceClass) {
33
+ const mediaSource = new mediaSourceClass();
34
+ const outcome = sourceOpenOutcome(element, mediaSource);
35
+ const url = URL.createObjectURL(mediaSource);
36
+ element.disableRemotePlayback = true;
37
+ element.src = url;
38
+ const attached = {
39
+ mediaSource,
40
+ detach: () => {
41
+ if (element.src === url) {
42
+ element.removeAttribute("src");
43
+ element.load();
44
+ }
45
+ URL.revokeObjectURL(url);
46
+ }
47
+ };
48
+ const opened = await outcome;
49
+ if (opened !== "sourceopen") {
50
+ attached.detach();
51
+ throw new GyroViewError("decode", `the audio element could not open its media source (${opened})`);
52
+ }
53
+ return attached;
54
+ }
55
+ /**
56
+ * Whichever comes first; the losers' listeners and the timer go once it is known, so the element
57
+ * the host reuses for every load carries nothing over.
58
+ */
59
+ async function sourceOpenOutcome(element, mediaSource) {
60
+ const settled = new AbortController();
61
+ try {
62
+ return await Promise.race([
63
+ nextOfEvents(mediaSource, ["sourceopen"], settled.signal),
64
+ nextOfEvents(element, ["error"], settled.signal),
65
+ timeoutAfter(SOURCE_OPEN_TIMEOUT_MS, settled.signal)
66
+ ]);
67
+ } finally {
68
+ settled.abort();
69
+ }
70
+ }
71
+ function timeoutAfter(ms, cancel) {
72
+ return new Promise((resolve) => {
73
+ const timer = setTimeout(() => {
74
+ resolve("timeout");
75
+ }, ms);
76
+ cancel.addEventListener("abort", () => {
77
+ clearTimeout(timer);
78
+ }, { once: true });
79
+ });
80
+ }
81
+ //#endregion
82
+ export { attachMediaSource, isMediaSourceTypeSupported, mediaSourceConstructor };