@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,38 @@
1
+ var CHANNEL_MAX = 255;
2
+ /**
3
+ * Mean RGB (0..1) of the texels with non-zero alpha in each row of an RGBA8 image; undefined
4
+ * when some row has none, because a lens that images no part of the seam gives nothing to match.
5
+ */
6
+ function rowMeansOf(pixels, width, rowCount) {
7
+ const means = [];
8
+ for (let row = 0; row < rowCount; row += 1) {
9
+ const mean = rowMean(pixels.subarray(row * width * 4, (row + 1) * width * 4));
10
+ if (!mean) return void 0;
11
+ means.push(mean);
12
+ }
13
+ return means;
14
+ }
15
+ function rowMean(row) {
16
+ const sum = [
17
+ 0,
18
+ 0,
19
+ 0
20
+ ];
21
+ let count = 0;
22
+ for (let offset = 0; offset + 4 <= row.length; offset += 4) {
23
+ if ((row[offset + 4 - 1] ?? 0) === 0) continue;
24
+ sum[0] += row[offset] ?? 0;
25
+ sum[1] += row[offset + 1] ?? 0;
26
+ sum[2] += row[offset + 2] ?? 0;
27
+ count += 1;
28
+ }
29
+ if (count === 0) return void 0;
30
+ const scale = count * CHANNEL_MAX;
31
+ return [
32
+ sum[0] / scale,
33
+ sum[1] / scale,
34
+ sum[2] / scale
35
+ ];
36
+ }
37
+ //#endregion
38
+ export { rowMeansOf };
@@ -0,0 +1,66 @@
1
+ import analysis_frag_default from "./shaders/analysis.frag.js";
2
+ import equirectangularRays_default from "./shaders/equirectangularRays.js";
3
+ import fixedJoin_default from "./shaders/fixedJoin.js";
4
+ import header_default from "./shaders/header.js";
5
+ import lensModels_default from "./shaders/lensModels.js";
6
+ import lensSampling_default from "./shaders/lensSampling.js";
7
+ import lensTextures_default from "./shaders/lensTextures.js";
8
+ import rawLenses_frag_default from "./shaders/rawLenses.frag.js";
9
+ import rectilinearRays_default from "./shaders/rectilinearRays.js";
10
+ import screenAreas_default from "./shaders/screenAreas.js";
11
+ import stitch_frag_default from "./shaders/stitch.frag.js";
12
+ //#region ../../packages/adapters/three/src/shaderPrograms.ts
13
+ /**
14
+ * The fragment programs, each the GLSL chunks it is made of in the order they must appear: a
15
+ * chunk comes after every chunk whose declarations it uses. The only place that order is known.
16
+ */
17
+ var LENS_PROJECTION = [
18
+ lensTextures_default,
19
+ lensModels_default,
20
+ lensSampling_default
21
+ ];
22
+ /**
23
+ * A program that reads the lenses through their calibration, ending in `fragment`.
24
+ */
25
+ function lensProjectionProgram(fragment) {
26
+ return [
27
+ header_default,
28
+ ...LENS_PROJECTION,
29
+ fragment
30
+ ];
31
+ }
32
+ /**
33
+ * The picture programs with a seam join chunk: `fixedJoin.glsl` for the player, or one that
34
+ * implements its three functions otherwise.
35
+ */
36
+ function pictureProgramsWith(seamJoin) {
37
+ const stitchThrough = (rays) => [
38
+ header_default,
39
+ screenAreas_default,
40
+ rays,
41
+ ...LENS_PROJECTION,
42
+ seamJoin,
43
+ stitch_frag_default
44
+ ];
45
+ return {
46
+ rectilinear: stitchThrough(rectilinearRays_default),
47
+ equirectangular: stitchThrough(equirectangularRays_default),
48
+ "lens-tiles": [
49
+ header_default,
50
+ screenAreas_default,
51
+ lensTextures_default,
52
+ rawLenses_frag_default
53
+ ]
54
+ };
55
+ }
56
+ var PICTURE_PROGRAMS = pictureProgramsWith(fixedJoin_default);
57
+ var SEAM_ANALYSIS = lensProjectionProgram(analysis_frag_default);
58
+ /**
59
+ * Every chunk of the given programs once, for checking them against the TypeScript side.
60
+ */
61
+ function chunksOf(programs) {
62
+ return [...new Set(programs.flat())];
63
+ }
64
+ chunksOf([...Object.values(PICTURE_PROGRAMS), SEAM_ANALYSIS]);
65
+ //#endregion
66
+ export { PICTURE_PROGRAMS, SEAM_ANALYSIS, chunksOf, lensProjectionProgram, pictureProgramsWith };
@@ -0,0 +1,4 @@
1
+ //#region ../../packages/adapters/three/src/shaders/analysis.frag.glsl?raw
2
+ var analysis_frag_default = "in vec2 vNdc;\nout vec4 outColor;\n\n// One row per lens: what that lens sees along the seam ring, the circle of directions halfway\n// through the blend band around lens 0's optical axis, sampled around the ring along x. Rows\n// share their directions, so their means differ by exposure (and parallax), not by content.\n// Alpha marks the texels a lens actually images.\nvoid main() {\n int lens = int(floor((vNdc.y + 1.0) * 0.5 * float(MAX_LENSES)));\n float phi = (vNdc.x + 1.0) * PI;\n float ring = 0.5 * (uFeather.x + uFeather.y);\n vec3 inLensZero = vec3(sin(ring) * cos(phi), sin(ring) * sin(phi), cos(ring));\n // The lens rotations take body directions into each lens; the transpose goes back.\n vec3 dirBody = transpose(uLensRotation[0]) * inLensZero;\n if (lens >= uLensCount) {\n outColor = vec4(0.0);\n return;\n }\n LensSample seen = sampleLensWith(lens, dirBody, SAMPLING_BILINEAR);\n outColor = seen.isImaged ? vec4(seen.color, 1.0) : vec4(0.0);\n}\n";
3
+ //#endregion
4
+ export { analysis_frag_default as default };
@@ -0,0 +1,4 @@
1
+ //#region ../../packages/adapters/three/src/shaders/equirectangularRays.glsl?raw
2
+ var equirectangularRays_default = "// An equirectangular picture: a full turn across and a half turn from top to bottom.\n\n// Direction in view space (x right, y down, z forward) seen through a point of the picture, given\n// as fractions of the picture from its top-left corner.\nvec3 rayThroughPicture(vec2 point) {\n vec2 centred = point * 2.0 - 1.0;\n float yaw = centred.x * PI;\n float pitch = -centred.y * HALF_PI;\n return vec3(sin(yaw) * cos(pitch), -sin(pitch), cos(yaw) * cos(pitch));\n}\n";
3
+ //#endregion
4
+ export { equirectangularRays_default as default };
@@ -0,0 +1,4 @@
1
+ //#region ../../packages/adapters/three/src/shaders/fixedJoin.glsl?raw
2
+ var fixedJoin_default = "// The fixed join: every lens read where the calibration's template reads it, for an infinitely\n// far scene, and blended across the whole feather band. The stitch the player draws; the lab's\n// bent join (ADR 0026) implements the same three functions.\nfloat disparityAt(vec3 dirBody) {\n return 0.0;\n}\n\nvec3 seamReadDirection(int i, vec3 dirBody, float disparity) {\n return dirBody;\n}\n\nvec2 seamFeather(float disparity) {\n return uFeather;\n}\n";
3
+ //#endregion
4
+ export { fixedJoin_default as default };
@@ -0,0 +1,4 @@
1
+ //#region ../../packages/adapters/three/src/shaders/fullscreen.vert.glsl?raw
2
+ var fullscreen_vert_default = "// One triangle covering the whole viewport; the fragment shader does all the work.\nin vec3 position;\nout vec2 vNdc;\n\nvoid main() {\n vNdc = position.xy;\n gl_Position = vec4(position.xy, 0.0, 1.0);\n}\n";
3
+ //#endregion
4
+ export { fullscreen_vert_default as default };
@@ -0,0 +1,4 @@
1
+ //#region ../../packages/adapters/three/src/shaders/header.glsl?raw
2
+ var header_default = "// GLSL ES 3.0 needs the default precisions before any float or int is mentioned.\nprecision highp float;\nprecision highp int;\n\nconst float PI = 3.14159265358979;\nconst float HALF_PI = 0.5 * PI;\nconst float TWO_PI = 2.0 * PI;\n";
3
+ //#endregion
4
+ export { header_default as default };
@@ -0,0 +1,4 @@
1
+ //#region ../../packages/adapters/three/src/shaders/lensModels.glsl?raw
2
+ var lensModels_default = "// The depth is positive for every direction inside a lens's field; directions behind it are\n// projected too, for the footprint's derivatives, and must not divide by zero.\nconst float MIN_DEPTH = 1e-3;\n\n// Canvas pixel of a unit direction in the lens frame under the unified (Mei) model: project onto\n// the unit sphere, then from xi behind its centre onto the image plane, distort, scale.\nvec2 projectMei(vec3 d, float xi, vec2 focal, vec2 principalPoint, vec3 radial, vec2 tangential) {\n float depth = max(d.z + xi, MIN_DEPTH);\n vec2 m = d.xy / depth;\n float r2 = dot(m, m);\n float radialFactor = 1.0 + radial.x * r2 + radial.y * r2 * r2 + radial.z * r2 * r2 * r2;\n vec2 tangentialTerm = vec2(\n 2.0 * tangential.x * m.x * m.y + tangential.y * (r2 + 2.0 * m.x * m.x),\n tangential.x * (r2 + 2.0 * m.y * m.y) + 2.0 * tangential.y * m.x * m.y\n );\n return focal * (radialFactor * m + tangentialTerm) + principalPoint;\n}\n\n// Canvas pixel of a unit direction under a radial polynomial in the angle from the optical axis.\nvec2 projectRadialPolynomial(vec3 d, float theta, vec4 c, vec2 principalPoint) {\n float radius = theta * (c.x + theta * (c.y + theta * (c.z + theta * c.w)));\n float lateral = length(d.xy);\n vec2 unit = lateral > 0.0 ? d.xy / lateral : vec2(0.0);\n return principalPoint + radius * unit;\n}\n";
3
+ //#endregion
4
+ export { lensModels_default as default };
@@ -0,0 +1,4 @@
1
+ //#region ../../packages/adapters/three/src/shaders/lensSampling.glsl?raw
2
+ var lensSampling_default = "// How a body direction becomes a texel of one lens, for the stitch and the seam meters: the\n// calibration as uniforms, and the sampling through each lens model. Needs lensTextures.glsl.\nuniform vec2 uFeather;\nuniform mat3 uLensRotation[MAX_LENSES];\nuniform int uLensKind[MAX_LENSES];\nuniform vec2 uLensPrincipalPoint[MAX_LENSES];\nuniform vec2 uLensFocal[MAX_LENSES];\nuniform float uLensXi[MAX_LENSES];\nuniform vec3 uLensRadial[MAX_LENSES];\nuniform vec2 uLensTangential[MAX_LENSES];\nuniform vec4 uLensPolynomial[MAX_LENSES];\nuniform float uLensHalfFov[MAX_LENSES];\nuniform vec4 uLensWindow[MAX_LENSES];\n\nstruct LensSample {\n bool isImaged;\n // Angle from the lens's optical axis.\n float theta;\n vec3 color;\n};\n\n// Where lens i images direction d on its calibration canvas; the principal point, and not\n// known, for a kind of lens this shader does not know.\nvec2 canvasPixel(int i, vec3 d, float theta, out bool isKnown) {\n isKnown = true;\n if (uLensKind[i] == LENS_MEI) {\n return projectMei(\n d,\n uLensXi[i],\n uLensFocal[i],\n uLensPrincipalPoint[i],\n uLensRadial[i],\n uLensTangential[i]\n );\n }\n if (uLensKind[i] == LENS_RADIAL_POLYNOMIAL) {\n return projectRadialPolynomial(d, theta, uLensPolynomial[i], uLensPrincipalPoint[i]);\n }\n isKnown = false;\n return uLensPrincipalPoint[i];\n}\n\n// What lens i shows in a body direction, read as `sampling` says, if it images it at all:\n// outside its field, or beyond the window the frame shows, nothing. The footprint is taken in\n// every fragment before the gates, so its derivatives are defined.\nLensSample sampleLensWith(int i, vec3 dirBody, int sampling) {\n vec3 d = uLensRotation[i] * dirBody;\n // atan keeps its precision near the axis where acos loses it.\n float theta = atan(length(d.xy), d.z);\n bool isKnown;\n vec2 pixel = canvasPixel(i, d, theta, isKnown);\n vec2 windowUv = (pixel - uLensWindow[i].xy) / uLensWindow[i].zw;\n Footprint footprint = footprintOf(i, windowUv);\n bool isInWindow =\n all(greaterThanEqual(windowUv, vec2(0.0))) && all(lessThanEqual(windowUv, vec2(1.0)));\n LensSample result;\n result.theta = theta;\n result.isImaged = isKnown && theta < uLensHalfFov[i] && isInWindow;\n result.color = result.isImaged ? sampleLens(uLensTexture[i], footprint, sampling).rgb : vec3(0.0);\n return result;\n}\n\n// What lens i shows in a body direction, read as the picture is.\nLensSample sampleLensAt(int i, vec3 dirBody) {\n return sampleLensWith(i, dirBody, uSampling);\n}\n";
3
+ //#endregion
4
+ export { lensSampling_default as default };
@@ -0,0 +1,4 @@
1
+ //#region ../../packages/adapters/three/src/shaders/lensTextures.glsl?raw
2
+ var lensTextures_default = "// Where each lens's image is: the decoded frames as textures, each lens's region of its frame,\n// and how a screen pixel reads them. Every program that reads a lens needs these.\nuniform int uLensCount;\nuniform vec4 uLensRegion[MAX_LENSES];\nuniform int uLensTexture[MAX_LENSES];\nuniform sampler2D uTexture0;\nuniform sampler2D uTexture1;\nuniform int uSampling;\n\n// The patch of a lens image one screen pixel covers: where it is read, and how far the next\n// pixel to the right and the next one down move the read, from the screen derivatives.\nstruct Footprint {\n vec2 uv;\n vec2 dx;\n vec2 dy;\n};\n\nvec2 texelSizeOf(int textureIndex) {\n return 1.0 / vec2(textureIndex == 0 ? textureSize(uTexture0, 0) : textureSize(uTexture1, 0));\n}\n\n// One bilinear tap of the base level, whatever the texture's filters: the seam meters read the\n// frames this way, where the pixel footprint means nothing.\nvec4 fetchBase(int textureIndex, vec2 uv) {\n return textureIndex == 0\n ? textureLod(uTexture0, uv, 0.0)\n : textureLod(uTexture1, uv, 0.0);\n}\n\n// One tap through the mip chain over a footprint.\nvec4 fetchFootprint(int textureIndex, vec2 uv, vec2 dx, vec2 dy) {\n return textureIndex == 0\n ? textureGrad(uTexture0, uv, dx, dy)\n : textureGrad(uTexture1, uv, dx, dy);\n}\n\n// Where a fraction of a lens's region is read. The derivatives are taken here, in every\n// fragment alike, before anything decides whether the pixel shows the lens at all; then the read\n// point is kept half a texel inside the region, so a neighbouring region never bleeds in.\nFootprint footprintOf(int i, vec2 regionUv) {\n vec2 uv = uLensRegion[i].xy + regionUv * uLensRegion[i].zw;\n Footprint footprint;\n footprint.dx = dFdx(uv);\n footprint.dy = dFdy(uv);\n vec2 inset = 0.5 * texelSizeOf(uLensTexture[i]);\n vec2 low = uLensRegion[i].xy + inset;\n vec2 high = uLensRegion[i].xy + uLensRegion[i].zw - inset;\n footprint.uv = clamp(uv, low, high);\n return footprint;\n}\n\n// Four taps on a rotated grid within the footprint, each over a quarter of it (Akenine-Möller\n// et al., Real-Time Rendering, 4th ed., section 5.4.1).\nconst vec2 GRID_OFFSETS[4] = vec2[4](\n vec2(-0.375, 0.125),\n vec2(0.125, 0.375),\n vec2(0.375, -0.125),\n vec2(-0.125, -0.375)\n);\n\nvec4 fetchSupersampled(int textureIndex, Footprint footprint) {\n vec4 sum = vec4(0.0);\n for (int tap = 0; tap < 4; tap++) {\n vec2 uv =\n footprint.uv + GRID_OFFSETS[tap].x * footprint.dx + GRID_OFFSETS[tap].y * footprint.dy;\n sum += fetchFootprint(textureIndex, uv, 0.5 * footprint.dx, 0.5 * footprint.dy);\n }\n return 0.25 * sum;\n}\n\nvec4 sampleLens(int textureIndex, Footprint footprint, int sampling) {\n if (sampling == SAMPLING_TRILINEAR) {\n return fetchFootprint(textureIndex, footprint.uv, footprint.dx, footprint.dy);\n }\n if (sampling == SAMPLING_SUPERSAMPLED) return fetchSupersampled(textureIndex, footprint);\n return fetchBase(textureIndex, footprint.uv);\n}\n";
3
+ //#endregion
4
+ export { lensTextures_default as default };
@@ -0,0 +1,4 @@
1
+ //#region ../../packages/adapters/three/src/shaders/rawLenses.frag.glsl?raw
2
+ var rawLenses_frag_default = "// The decoded lens images on their own, unstitched: each lens's region of its frame exactly as\n// the decoder delivered it, in the lens's screen area, with no pose, stabilization or gain. Black\n// around the areas. Every pixel takes each tile's footprint before the area decides, so the\n// derivatives are defined.\nin vec2 vNdc;\nout vec4 outColor;\n\nvoid main() {\n vec3 color = vec3(0.0);\n for (int i = 0; i < MAX_LENSES; i++) {\n if (i >= uLensCount) break;\n vec2 point = pointInArea(vNdc, uScreenArea[i]);\n Footprint footprint = footprintOf(i, point);\n if (isInArea(point)) color = sampleLens(uLensTexture[i], footprint, uSampling).rgb;\n }\n outColor = vec4(color, 1.0);\n}\n";
3
+ //#endregion
4
+ export { rawLenses_frag_default as default };
@@ -0,0 +1,4 @@
1
+ //#region ../../packages/adapters/three/src/shaders/rectilinearRays.glsl?raw
2
+ var rectilinearRays_default = "// A rectilinear picture: the image plane scaled so that the horizontal field of view means what\n// the picture says.\nuniform float uPlaneHalfExtent;\nuniform float uPictureAspect;\n\n// Direction in view space (x right, y down, z forward) seen through a point of the picture, given\n// as fractions of the picture from its top-left corner.\nvec3 rayThroughPicture(vec2 point) {\n vec2 centred = point * 2.0 - 1.0;\n vec2 plane = vec2(centred.x * uPlaneHalfExtent, centred.y * uPlaneHalfExtent / uPictureAspect);\n return normalize(vec3(plane, 1.0));\n}\n";
3
+ //#endregion
4
+ export { rectilinearRays_default as default };
@@ -0,0 +1,4 @@
1
+ //#region ../../packages/adapters/three/src/shaders/screenAreas.glsl?raw
2
+ var screenAreas_default = "// Where on the viewport the picture goes: per area, x, y, width and height as fractions of the\n// viewport from its top-left corner.\nuniform vec4 uScreenArea[MAX_LENSES];\n\n// Where a pixel lies within an area, as fractions of the area from its top-left corner: outside\n// 0 to 1 when the pixel lies outside the area. Normalised device y points up, screen y down.\nvec2 pointInArea(vec2 ndc, vec4 area) {\n vec2 screen = vec2(ndc.x + 1.0, 1.0 - ndc.y) * 0.5;\n return (screen - area.xy) / area.zw;\n}\n\nbool isInArea(vec2 point) {\n return all(greaterThanEqual(point, vec2(0.0))) && all(lessThanEqual(point, vec2(1.0)));\n}\n";
3
+ //#endregion
4
+ export { screenAreas_default as default };
@@ -0,0 +1,4 @@
1
+ //#region ../../packages/adapters/three/src/shaders/stitch.frag.glsl?raw
2
+ var stitch_frag_default = "// The stitched sphere through the picture's rays: each lens's texel for the ray, read and weighted\n// across the feather band as the seam join says, and matched in exposure. Every pixel samples, even outside the picture's area,\n// so the footprints' derivatives are defined; the area decides what shows.\nuniform mat3 uViewRotation;\nuniform mat3 uStabilization;\nuniform vec3 uLensGain[MAX_LENSES];\n\nin vec2 vNdc;\nout vec4 outColor;\n\nvoid main() {\n vec2 point = pointInArea(vNdc, uScreenArea[0]);\n vec3 dirView = rayThroughPicture(point);\n vec3 dirBody = uStabilization * (uViewRotation * dirView);\n vec3 sum = vec3(0.0);\n float weightSum = 0.0;\n float disparity = disparityAt(dirBody);\n vec2 feather = seamFeather(disparity);\n for (int i = 0; i < MAX_LENSES; i++) {\n if (i >= uLensCount) break;\n // A lens silenced through its gain (ADR 0012) leaves the blend altogether, so the other\n // lens fills the feather band alone and a lens-only render shows that lens as it is.\n if (all(equal(uLensGain[i], vec3(0.0)))) continue;\n LensSample lens = sampleLensAt(i, seamReadDirection(i, dirBody, disparity));\n if (!lens.isImaged) continue;\n // Weighed by the angle from the lens's axis where it is read, which a seam join may move.\n float weight = 1.0 - smoothstep(feather.x, feather.y, lens.theta);\n sum += weight * uLensGain[i] * lens.color;\n weightSum += weight;\n }\n bool isShown = isInArea(point) && weightSum > 0.0;\n outColor = isShown ? vec4(sum / weightSum, 1.0) : vec4(0.0, 0.0, 0.0, 1.0);\n}\n";
3
+ //#endregion
4
+ export { stitch_frag_default as default };
@@ -0,0 +1,37 @@
1
+ import { GyroViewError } from "../../../core/src/shared/errors/GyroViewError.js";
2
+ import "../../../core/src/index.js";
3
+ import { WebGLRenderer } from "three";
4
+ //#region ../../packages/adapters/three/src/webglRenderer.ts
5
+ /**
6
+ * The WebGL 2 context a picture renderer draws on, wrapped by three, with every shader failure
7
+ * made an error.
8
+ */
9
+ function createRenderer(canvas, options) {
10
+ const context = canvas.getContext("webgl2", {
11
+ preserveDrawingBuffer: options.preserveDrawingBuffer ?? false,
12
+ antialias: false,
13
+ alpha: false,
14
+ depth: false,
15
+ stencil: false
16
+ });
17
+ if (!context) throw new GyroViewError("render-unavailable", "this browser has no WebGL2 context");
18
+ const renderer = new WebGLRenderer({
19
+ canvas,
20
+ context
21
+ });
22
+ renderer.setPixelRatio(1);
23
+ renderer.debug.onShaderError = (gl, program, ...shaders) => {
24
+ rejectProgram(gl, program, shaders);
25
+ };
26
+ return renderer;
27
+ }
28
+ /**
29
+ * A shader that does not compile or link is a defect, not something to log and draw black over.
30
+ * A compile failure's reason is in its shader's log, a link failure's in the program's.
31
+ */
32
+ function rejectProgram(gl, program, shaders) {
33
+ const log = [gl.getProgramInfoLog(program), ...shaders.map((shader) => gl.getShaderInfoLog(shader))].filter(Boolean).join("\n");
34
+ throw new GyroViewError("render-unavailable", `a shader did not compile or link: ${log}`);
35
+ }
36
+ //#endregion
37
+ export { createRenderer };
@@ -0,0 +1,135 @@
1
+ import { GyroViewError, asGyroViewError, messageOf } from "../../../core/src/shared/errors/GyroViewError.js";
2
+ import { microseconds, microsecondsToSeconds, secondsToMicroseconds } from "../../../core/src/shared/units/time.js";
3
+ import "../../../core/src/index.js";
4
+ //#region ../../packages/adapters/webcodecs/src/WebCodecsVideoDecoderPort.ts
5
+ /**
6
+ * A hard `prefer-hardware` refuses codecs the browser could decode in software (H.264 recordings
7
+ * on machines without a hardware decoder); with no preference the browser still picks hardware
8
+ * when it has it (ADR 0010).
9
+ */
10
+ var HARDWARE_ACCELERATION = "no-preference";
11
+ /**
12
+ * VideoDecoderPort over the browser's WebCodecs `VideoDecoder`. Frames are exposed as
13
+ * `DecodedFrame<VideoFrame>`; the renderer uploads the handle to the GPU and the pipeline closes
14
+ * it afterwards.
15
+ */
16
+ var WebCodecsVideoDecoderPort = class {
17
+ /**
18
+ * A configuration WebCodecs finds malformed is one it does not support.
19
+ */
20
+ async isSupported(configuration) {
21
+ if (typeof VideoDecoder === "undefined") return false;
22
+ try {
23
+ return (await VideoDecoder.isConfigSupported(this.toWebCodecsConfig(configuration))).supported === true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+ create(configuration, callbacks) {
29
+ try {
30
+ return Promise.resolve(this.configureDecoder(configuration, callbacks));
31
+ } catch (error) {
32
+ return Promise.reject(asGyroViewError(error, "codec-unsupported", "the decoder rejected its configuration"));
33
+ }
34
+ }
35
+ configureDecoder(configuration, callbacks) {
36
+ if (typeof VideoDecoder === "undefined") throw new GyroViewError("codec-unsupported", "this browser has no WebCodecs VideoDecoder");
37
+ return new WebCodecsDecoderHandle(this.toWebCodecsConfig(configuration), callbacks);
38
+ }
39
+ toWebCodecsConfig(configuration) {
40
+ const { description, isFullRange } = configuration;
41
+ return {
42
+ codec: configuration.codec,
43
+ codedWidth: configuration.codedWidth,
44
+ codedHeight: configuration.codedHeight,
45
+ ...description && { description },
46
+ ...isFullRange !== void 0 && { colorSpace: { fullRange: isFullRange } },
47
+ hardwareAcceleration: HARDWARE_ACCELERATION,
48
+ optimizeForLatency: false
49
+ };
50
+ }
51
+ };
52
+ /**
53
+ * One configured `VideoDecoder` for its whole life. The error it reported through its callback
54
+ * is kept, so later calls fail with the real cause instead of a bare "closed" state.
55
+ */
56
+ var WebCodecsDecoderHandle = class {
57
+ decoder;
58
+ failure;
59
+ constructor(config, callbacks) {
60
+ this.decoder = new VideoDecoder({
61
+ output: (frame) => {
62
+ callbacks.onFrame(wrapFrame(frame));
63
+ },
64
+ error: (error) => {
65
+ this.failure ??= new GyroViewError("decode", `video decoder failed: ${error.message}`, { cause: error });
66
+ callbacks.onError(this.failure);
67
+ }
68
+ });
69
+ try {
70
+ this.decoder.configure(config);
71
+ } catch (error) {
72
+ this.decoder.close();
73
+ throw error;
74
+ }
75
+ }
76
+ get pendingCount() {
77
+ return this.decoder.decodeQueueSize;
78
+ }
79
+ /**
80
+ * WebCodecs throws platform exceptions for a packet it refuses (a delta frame first, a closed
81
+ * decoder); they cross the port as `decode` failures, as the fake's do.
82
+ */
83
+ decode(packet) {
84
+ if (this.failure) throw this.failure;
85
+ try {
86
+ this.decoder.decode(chunkOf(packet));
87
+ } catch (error) {
88
+ throw new GyroViewError("decode", `the decoder refused a packet: ${messageOf(error)}`, { cause: error });
89
+ }
90
+ }
91
+ /**
92
+ * Resolves on the decoder's `dequeue` events until fewer than `limit` packets are pending, or
93
+ * at once when the decoder is no longer configured.
94
+ */
95
+ waitForPendingBelow(limit) {
96
+ return this.decoder.decodeQueueSize < limit ? Promise.resolve() : new Promise((resolve) => {
97
+ const check = () => {
98
+ if (this.decoder.decodeQueueSize >= limit && this.decoder.state === "configured") return;
99
+ this.decoder.removeEventListener("dequeue", check);
100
+ resolve();
101
+ };
102
+ this.decoder.addEventListener("dequeue", check);
103
+ });
104
+ }
105
+ async flush() {
106
+ if (this.decoder.state !== "configured") throw this.failure ?? new GyroViewError("decode", "flush on a closed decoder");
107
+ try {
108
+ await this.decoder.flush();
109
+ } catch (error) {
110
+ throw this.failure ?? new GyroViewError("decode", "the decoder could not be flushed", { cause: error });
111
+ }
112
+ }
113
+ close() {
114
+ if (this.decoder.state !== "closed") this.decoder.close();
115
+ }
116
+ };
117
+ function chunkOf(packet) {
118
+ return new EncodedVideoChunk({
119
+ type: packet.isKeyFrame ? "key" : "delta",
120
+ timestamp: secondsToMicroseconds(packet.timestamp),
121
+ duration: secondsToMicroseconds(packet.duration),
122
+ data: packet.data
123
+ });
124
+ }
125
+ function wrapFrame(frame) {
126
+ return {
127
+ timestamp: microsecondsToSeconds(microseconds(frame.timestamp)),
128
+ handle: frame,
129
+ close: () => {
130
+ frame.close();
131
+ }
132
+ };
133
+ }
134
+ //#endregion
135
+ export { WebCodecsVideoDecoderPort };
@@ -0,0 +1,2 @@
1
+ import { WebCodecsVideoDecoderPort } from "./WebCodecsVideoDecoderPort.js";
2
+ export { WebCodecsVideoDecoderPort };
@@ -0,0 +1,66 @@
1
+ import { GainMatcher } from "../../domain/optics/gainMatch.js";
2
+ import { reportLater } from "../../shared/errors/reportLater.js";
3
+ //#region ../../packages/core/src/application/gainMatching/GainMatching.ts
4
+ /**
5
+ * Media time between measurements: exposure drifts over seconds, and a read-back per frame
6
+ * would stall the GPU pipeline.
7
+ */
8
+ var MEASURE_INTERVAL_SECONDS = .5;
9
+ /**
10
+ * Keeps the lens gains matched over time: measures the seam after a presented frame every so
11
+ * often, feeds the matcher and applies what it says. One measurement in flight at a time; one
12
+ * that lands after `stop` changes nothing. A meter that fails stops the matching, the gains left
13
+ * as they are, and the failure is reported once. The meter is borrowed: whoever created it
14
+ * disposes it.
15
+ */
16
+ var GainMatching = class {
17
+ meter;
18
+ applyGains;
19
+ reportFailure;
20
+ matcher = new GainMatcher();
21
+ lastMeasuredAt;
22
+ inFlight;
23
+ isStopped = false;
24
+ constructor(meter, applyGains, reportFailure = reportLater) {
25
+ this.meter = meter;
26
+ this.applyGains = applyGains;
27
+ this.reportFailure = reportFailure;
28
+ }
29
+ afterPresent(mediaTime) {
30
+ if ((this.lastMeasuredAt === void 0 || Math.abs(mediaTime - this.lastMeasuredAt) >= MEASURE_INTERVAL_SECONDS) && !this.isStopped) this.matchReporting(mediaTime);
31
+ }
32
+ /**
33
+ * Measures and applies at once; joins a measurement already under way.
34
+ */
35
+ matchNow(mediaTime) {
36
+ this.inFlight ??= this.measureAt(mediaTime);
37
+ return this.inFlight;
38
+ }
39
+ stop() {
40
+ this.isStopped = true;
41
+ }
42
+ async matchReporting(mediaTime) {
43
+ try {
44
+ await this.matchNow(mediaTime);
45
+ } catch (error) {
46
+ this.reportFailure(error);
47
+ }
48
+ }
49
+ /**
50
+ * Counted from its start, so a failing meter is not asked again at every frame.
51
+ */
52
+ async measureAt(mediaTime) {
53
+ this.lastMeasuredAt = mediaTime;
54
+ try {
55
+ const means = await this.meter.measure();
56
+ if (means && !this.isStopped) this.applyGains(this.matcher.update(means, mediaTime));
57
+ } catch (error) {
58
+ this.isStopped = true;
59
+ throw error;
60
+ } finally {
61
+ this.inFlight = void 0;
62
+ }
63
+ }
64
+ };
65
+ //#endregion
66
+ export { GainMatching };
@@ -0,0 +1,62 @@
1
+ import { GainMatching } from "./GainMatching.js";
2
+ //#region ../../packages/core/src/application/gainMatching/GainMatchingFrameSink.ts
3
+ var UNIT_GAIN = [
4
+ 1,
5
+ 1,
6
+ 1
7
+ ];
8
+ /**
9
+ * Use case: while enabled, keeps the lenses' exposure matched along the seam of the pictures the
10
+ * sink draws, measuring after presentations. Off until enabled; disabled, the lenses show their
11
+ * exposure as recorded (ADR 0012).
12
+ */
13
+ var GainMatchingFrameSink = class {
14
+ sink;
15
+ renderer;
16
+ active;
17
+ lastMediaTime;
18
+ constructor(sink, renderer) {
19
+ this.sink = sink;
20
+ this.renderer = renderer;
21
+ }
22
+ enable() {
23
+ if (this.active) return;
24
+ const { renderer } = this;
25
+ const meter = renderer.createSeamMeter();
26
+ const matching = new GainMatching(meter, (gains) => {
27
+ renderer.setLensGains(gains);
28
+ });
29
+ this.active = {
30
+ meter,
31
+ matching
32
+ };
33
+ }
34
+ disable() {
35
+ this.stop();
36
+ const unitGains = Array.from({ length: this.renderer.lensCount }, () => UNIT_GAIN);
37
+ this.renderer.setLensGains(unitGains);
38
+ }
39
+ /**
40
+ * One measurement and adjustment of the picture on screen right now, awaited: how tests
41
+ * observe matching without waiting for the next scheduled measurement.
42
+ */
43
+ async matchNow() {
44
+ if (this.lastMediaTime === void 0) return;
45
+ await this.active?.matching.matchNow(this.lastMediaTime);
46
+ }
47
+ present(presentation) {
48
+ this.sink.present(presentation);
49
+ this.lastMediaTime = presentation.mediaTime;
50
+ this.active?.matching.afterPresent(presentation.mediaTime);
51
+ }
52
+ dispose() {
53
+ this.stop();
54
+ }
55
+ stop() {
56
+ this.active?.matching.stop();
57
+ this.active?.meter.dispose();
58
+ this.active = void 0;
59
+ }
60
+ };
61
+ //#endregion
62
+ export { GainMatchingFrameSink };
@@ -0,0 +1,76 @@
1
+ //#region ../../packages/core/src/application/playback/ClockWatch.ts
2
+ /**
3
+ * This much media time between two playing ticks means the ticks stopped while the clock ran on
4
+ * (a hidden tab or an offscreen frame gets no animation frames). Ticks come with every animation
5
+ * frame, a few hundredths of a second apart at the slowest.
6
+ */
7
+ var MISSED_TICKS_SECONDS = 1;
8
+ /**
9
+ * A clock found this far from where it was last seen or put was moved from outside: the lock
10
+ * screen's scrubber, a media key's play from the end. A running clock covers a few hundredths of
11
+ * a second between two ticks.
12
+ */
13
+ var OUTSIDE_MOVE_SECONDS = .25;
14
+ /**
15
+ * What a playback session's ticks notice about its clock between them: that the platform moved
16
+ * it, or that ticks were missed while it ran on.
17
+ */
18
+ var ClockWatch = class {
19
+ position;
20
+ previousPlayingTick;
21
+ /**
22
+ * The session put the clock here itself.
23
+ */
24
+ placedAt(time) {
25
+ this.position = time;
26
+ }
27
+ /**
28
+ * The session stopped the running clock here: however far it ran on since the last tick, that
29
+ * was playback. A move back is still one from outside, for the next tick to notice.
30
+ */
31
+ stoppedAt(time) {
32
+ if (this.position === void 0 || time >= this.position) this.position = time;
33
+ }
34
+ /**
35
+ * Playing starts from here: ticks that never come from now on are missed ticks too, as when
36
+ * playback starts in a hidden tab.
37
+ */
38
+ playingFrom(time) {
39
+ this.previousPlayingTick = time;
40
+ }
41
+ /**
42
+ * Whether the clock is not where it was last seen or put: moved back while playing, or moved at
43
+ * all while standing still. Notes where it is now.
44
+ */
45
+ wasMovedFromOutside({ now, state }) {
46
+ const known = this.position;
47
+ this.position = now;
48
+ if (known === void 0) return false;
49
+ return (state === "playing" ? known - now : Math.abs(now - known)) > OUTSIDE_MOVE_SECONDS;
50
+ }
51
+ /**
52
+ * Whether the clock ran on for a while since the last playing tick. Notes this tick.
53
+ */
54
+ wereTicksMissed({ now, state }) {
55
+ const isPlaying = state === "playing";
56
+ const previous = this.previousPlayingTick;
57
+ this.previousPlayingTick = isPlaying ? now : void 0;
58
+ const gap = previous === void 0 ? 0 : now - previous;
59
+ return isPlaying && gap > MISSED_TICKS_SECONDS;
60
+ }
61
+ };
62
+ /**
63
+ * The platform stopped the clock by itself (media keys, an audio interruption): not running, yet
64
+ * not at the end of its media.
65
+ */
66
+ function isStoppedFromOutside(clock) {
67
+ return !clock.isRunning && !clock.hasEnded;
68
+ }
69
+ /**
70
+ * At the end of the recording: the clock's own media ran out, or it stands at the duration.
71
+ */
72
+ function isAtEndOfMedia(clock, duration) {
73
+ return clock.hasEnded || clock.currentTime >= duration;
74
+ }
75
+ //#endregion
76
+ export { ClockWatch, isAtEndOfMedia, isStoppedFromOutside };