@bubo-squared/gyroview 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/LICENSE +21 -0
- package/README.md +216 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.js +4 -0
- package/dist/index.d.ts +959 -0
- package/dist/index.js +13 -0
- package/dist/packages/adapters/blob/src/BlobRandomAccessSource.js +37 -0
- package/dist/packages/adapters/blob/src/index.js +2 -0
- package/dist/packages/adapters/fetch/src/HttpRangeSource.js +194 -0
- package/dist/packages/adapters/fetch/src/HttpResourceLocator.js +35 -0
- package/dist/packages/adapters/fetch/src/httpRequest.js +126 -0
- package/dist/packages/adapters/fetch/src/index.js +3 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyAudioSegments.js +133 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyDemuxer.js +63 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyVideoTrackReader.js +84 -0
- package/dist/packages/adapters/mediabunny/src/SegmentChannel.js +84 -0
- package/dist/packages/adapters/mediabunny/src/bufferSources.js +12 -0
- package/dist/packages/adapters/mediabunny/src/index.js +2 -0
- package/dist/packages/adapters/mse-audio/src/MediaSourceAudioClock.js +139 -0
- package/dist/packages/adapters/mse-audio/src/SourceBufferFeeder.js +174 -0
- package/dist/packages/adapters/mse-audio/src/events.js +26 -0
- package/dist/packages/adapters/mse-audio/src/index.js +2 -0
- package/dist/packages/adapters/mse-audio/src/mediaSourceSupport.js +82 -0
- package/dist/packages/adapters/three/src/ThreeFrameRenderer.js +274 -0
- package/dist/packages/adapters/three/src/compileAndProve.js +14 -0
- package/dist/packages/adapters/three/src/fullscreenPass.js +52 -0
- package/dist/packages/adapters/three/src/index.js +2 -0
- package/dist/packages/adapters/three/src/lensTextures.js +25 -0
- package/dist/packages/adapters/three/src/pictureMaterials.js +44 -0
- package/dist/packages/adapters/three/src/renderInto.js +15 -0
- package/dist/packages/adapters/three/src/rendererUniforms.js +157 -0
- package/dist/packages/adapters/three/src/samplingStrategies.js +26 -0
- package/dist/packages/adapters/three/src/seamMeter/SeamMeterPass.js +79 -0
- package/dist/packages/adapters/three/src/seamMeter/rowMeans.js +38 -0
- package/dist/packages/adapters/three/src/shaderPrograms.js +66 -0
- package/dist/packages/adapters/three/src/shaders/analysis.frag.js +4 -0
- package/dist/packages/adapters/three/src/shaders/equirectangularRays.js +4 -0
- package/dist/packages/adapters/three/src/shaders/fixedJoin.js +4 -0
- package/dist/packages/adapters/three/src/shaders/fullscreen.vert.js +4 -0
- package/dist/packages/adapters/three/src/shaders/header.js +4 -0
- package/dist/packages/adapters/three/src/shaders/lensModels.js +4 -0
- package/dist/packages/adapters/three/src/shaders/lensSampling.js +4 -0
- package/dist/packages/adapters/three/src/shaders/lensTextures.js +4 -0
- package/dist/packages/adapters/three/src/shaders/rawLenses.frag.js +4 -0
- package/dist/packages/adapters/three/src/shaders/rectilinearRays.js +4 -0
- package/dist/packages/adapters/three/src/shaders/screenAreas.js +4 -0
- package/dist/packages/adapters/three/src/shaders/stitch.frag.js +4 -0
- package/dist/packages/adapters/three/src/webglRenderer.js +37 -0
- package/dist/packages/adapters/webcodecs/src/WebCodecsVideoDecoderPort.js +135 -0
- package/dist/packages/adapters/webcodecs/src/index.js +2 -0
- package/dist/packages/core/src/application/gainMatching/GainMatching.js +66 -0
- package/dist/packages/core/src/application/gainMatching/GainMatchingFrameSink.js +62 -0
- package/dist/packages/core/src/application/playback/ClockWatch.js +76 -0
- package/dist/packages/core/src/application/playback/DecodePipeline.js +188 -0
- package/dist/packages/core/src/application/playback/DecodeRun.js +93 -0
- package/dist/packages/core/src/application/playback/FramePairQueue.js +93 -0
- package/dist/packages/core/src/application/playback/FramePairer.js +60 -0
- package/dist/packages/core/src/application/playback/PlaybackSession.js +417 -0
- package/dist/packages/core/src/application/playback/SeekOrder.js +36 -0
- package/dist/packages/core/src/application/playback/SessionLifecycle.js +42 -0
- package/dist/packages/core/src/application/playback/StartGate.js +55 -0
- package/dist/packages/core/src/application/playback/TimeUpdates.js +31 -0
- package/dist/packages/core/src/application/playback/WallClock.js +46 -0
- package/dist/packages/core/src/application/playback/keyframeTimeAt.js +15 -0
- package/dist/packages/core/src/application/playback/probeDecoding.js +168 -0
- package/dist/packages/core/src/application/recording/frameTimesOf.js +22 -0
- package/dist/packages/core/src/application/recording/inspectLayout.js +21 -0
- package/dist/packages/core/src/application/recording/inspectRecording.js +124 -0
- package/dist/packages/core/src/application/recording/locateOtherLensFile.js +18 -0
- package/dist/packages/core/src/application/recording/motionOf.js +39 -0
- package/dist/packages/core/src/application/recording/readRecording.js +31 -0
- package/dist/packages/core/src/application/recording/timeRecording.js +57 -0
- package/dist/packages/core/src/application/stabilization/StabilizingFrameSink.js +31 -0
- package/dist/packages/core/src/domain/format/boxes/BoxLayout.js +7 -0
- package/dist/packages/core/src/domain/format/boxes/boxConstants.js +16 -0
- package/dist/packages/core/src/domain/format/boxes/scanBoxes.js +59 -0
- package/dist/packages/core/src/domain/format/calibration/CalibrationVersion.js +13 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/CalibrationStringLayout.js +34 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/LegacyCalibrationLayout.js +44 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/MeiCalibrationLayout.js +45 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/PolynomialCalibrationLayout.js +44 -0
- package/dist/packages/core/src/domain/format/calibration/offsetTokens.js +65 -0
- package/dist/packages/core/src/domain/format/calibration/parseOffsetString.js +52 -0
- package/dist/packages/core/src/domain/format/calibration/selectCalibration.js +46 -0
- package/dist/packages/core/src/domain/format/captureOrigin.js +20 -0
- package/dist/packages/core/src/domain/format/constants.js +33 -0
- package/dist/packages/core/src/domain/format/info/infoFields.js +110 -0
- package/dist/packages/core/src/domain/format/info/parseInfoRecord.js +90 -0
- package/dist/packages/core/src/domain/format/info/readInfoRecord.js +14 -0
- package/dist/packages/core/src/domain/format/layout/detectLensLayout.js +122 -0
- package/dist/packages/core/src/domain/format/naming/RecordingFileName.js +67 -0
- package/dist/packages/core/src/domain/format/records/TrailerRecords.js +83 -0
- package/dist/packages/core/src/domain/format/records/exposure/parseExposureRecord.js +48 -0
- package/dist/packages/core/src/domain/format/records/gyro/FloatGyroSampleLayout.js +39 -0
- package/dist/packages/core/src/domain/format/records/gyro/RawGyroSampleLayout.js +55 -0
- package/dist/packages/core/src/domain/format/records/gyro/SampleColumns.js +82 -0
- package/dist/packages/core/src/domain/format/records/gyro/gyroLayouts.js +12 -0
- package/dist/packages/core/src/domain/format/records/gyro/parseGyroRecord.js +129 -0
- package/dist/packages/core/src/domain/format/trailer/RecordHeader.js +23 -0
- package/dist/packages/core/src/domain/format/trailer/Trailer.js +23 -0
- package/dist/packages/core/src/domain/format/trailer/TrailerFooter.js +27 -0
- package/dist/packages/core/src/domain/format/trailer/locateRecords.js +80 -0
- package/dist/packages/core/src/domain/format/trailer/parseRecordIndex.js +28 -0
- package/dist/packages/core/src/domain/format/trailer/readTrailer.js +32 -0
- package/dist/packages/core/src/domain/motion/exposure/ExposureRecord.js +53 -0
- package/dist/packages/core/src/domain/motion/gyro/GyroTrack.js +60 -0
- package/dist/packages/core/src/domain/motion/gyro/medianStep.js +11 -0
- package/dist/packages/core/src/domain/motion/gyro/repairedTimeline.js +101 -0
- package/dist/packages/core/src/domain/motion/imu/ImuFrame.js +112 -0
- package/dist/packages/core/src/domain/motion/orientation/OrientationTrack.js +146 -0
- package/dist/packages/core/src/domain/motion/orientation/estimateGyroBias.js +64 -0
- package/dist/packages/core/src/domain/motion/orientation/gravity.js +97 -0
- package/dist/packages/core/src/domain/motion/stabilization/Stabilizer.js +13 -0
- package/dist/packages/core/src/domain/motion/stabilization/stabilizers.js +86 -0
- package/dist/packages/core/src/domain/motion/timing/CaptureClock.js +41 -0
- package/dist/packages/core/src/domain/motion/timing/FrameTimes.js +92 -0
- package/dist/packages/core/src/domain/motion/timing/frameTimeSources.js +61 -0
- package/dist/packages/core/src/domain/motion/timing/resolveFrameTimes.js +68 -0
- package/dist/packages/core/src/domain/optics/EquidistantModel.js +39 -0
- package/dist/packages/core/src/domain/optics/MeiModel.js +45 -0
- package/dist/packages/core/src/domain/optics/PolynomialModel.js +44 -0
- package/dist/packages/core/src/domain/optics/gainMatch.js +62 -0
- package/dist/packages/core/src/domain/optics/lensGeometry.js +18 -0
- package/dist/packages/core/src/domain/optics/lensPose.js +35 -0
- package/dist/packages/core/src/domain/optics/opticsConstants.js +5 -0
- package/dist/packages/core/src/domain/playback/PlayerState.js +83 -0
- package/dist/packages/core/src/domain/stitching/LensLayout.js +21 -0
- package/dist/packages/core/src/domain/stitching/StitchingSetup.js +78 -0
- package/dist/packages/core/src/domain/stitching/seamDisparity.js +4 -0
- package/dist/packages/core/src/domain/stitching/seamDisparityField.js +5 -0
- package/dist/packages/core/src/domain/stitching/seamJoin.js +16 -0
- package/dist/packages/core/src/domain/stitching/seamMismatch.js +1 -0
- package/dist/packages/core/src/domain/stitching/seamRing.js +4 -0
- package/dist/packages/core/src/domain/stitching/seamStrip.js +26 -0
- package/dist/packages/core/src/domain/view/Framing.js +31 -0
- package/dist/packages/core/src/domain/view/PictureQuality.js +26 -0
- package/dist/packages/core/src/domain/view/ViewMode.js +9 -0
- package/dist/packages/core/src/domain/view/ViewState.js +48 -0
- package/dist/packages/core/src/domain/view/lensTilesView.js +63 -0
- package/dist/packages/core/src/domain/view/magnification.js +103 -0
- package/dist/packages/core/src/domain/view/normalView.js +29 -0
- package/dist/packages/core/src/domain/view/panoramaView.js +99 -0
- package/dist/packages/core/src/domain/view/rectilinear.js +40 -0
- package/dist/packages/core/src/domain/view/screenLayout.js +113 -0
- package/dist/packages/core/src/domain/view/viewGestures.js +98 -0
- package/dist/packages/core/src/domain/view/viewModes.js +14 -0
- package/dist/packages/core/src/index.js +59 -0
- package/dist/packages/core/src/ports/FramePair.js +6 -0
- package/dist/packages/core/src/shared/async/Deferred.js +42 -0
- package/dist/packages/core/src/shared/async/RunStop.js +40 -0
- package/dist/packages/core/src/shared/binary/ByteRange.js +38 -0
- package/dist/packages/core/src/shared/binary/ByteReader.js +77 -0
- package/dist/packages/core/src/shared/errors/GyroViewError.js +81 -0
- package/dist/packages/core/src/shared/errors/reportLater.js +11 -0
- package/dist/packages/core/src/shared/events/Outbox.js +68 -0
- package/dist/packages/core/src/shared/events/TypedEmitter.js +55 -0
- package/dist/packages/core/src/shared/keysOf.js +10 -0
- package/dist/packages/core/src/shared/lazy.js +11 -0
- package/dist/packages/core/src/shared/math/Matrix3.js +93 -0
- package/dist/packages/core/src/shared/math/Quaternion.js +133 -0
- package/dist/packages/core/src/shared/math/Vector3.js +54 -0
- package/dist/packages/core/src/shared/math/clamp.js +9 -0
- package/dist/packages/core/src/shared/protobuf/ProtobufMessage.js +157 -0
- package/dist/packages/core/src/shared/text/urlPath.js +31 -0
- package/dist/packages/core/src/shared/text/utf8.js +12 -0
- package/dist/packages/core/src/shared/units/angle.js +22 -0
- package/dist/packages/core/src/shared/units/time.js +13 -0
- package/dist/packages/player/src/PlayerSource.js +15 -0
- package/dist/packages/player/src/browserPlayer.js +18 -0
- package/dist/packages/player/src/choices.js +25 -0
- package/dist/packages/player/src/composition/Disposables.js +31 -0
- package/dist/packages/player/src/composition/browserPorts.js +46 -0
- package/dist/packages/player/src/composition/buildPipeline.js +153 -0
- package/dist/packages/player/src/composition/ensureDecodable.js +63 -0
- package/dist/packages/player/src/composition/openInputs.js +179 -0
- package/dist/packages/player/src/composition/openRecording.js +42 -0
- package/dist/packages/player/src/controls/ChoiceMenu.js +113 -0
- package/dist/packages/player/src/controls/PictureMenus.js +47 -0
- package/dist/packages/player/src/controls/SeekBar.js +125 -0
- package/dist/packages/player/src/controls/SoundControls.js +41 -0
- package/dist/packages/player/src/controls/TransportButtons.js +31 -0
- package/dist/packages/player/src/controls/ViewGestures.js +136 -0
- package/dist/packages/player/src/controls/Wording.js +78 -0
- package/dist/packages/player/src/controls/controlParts.js +36 -0
- package/dist/packages/player/src/controls/controls.js +4 -0
- package/dist/packages/player/src/controls/controlsBar.js +23 -0
- package/dist/packages/player/src/controls/controlsMarkup.js +85 -0
- package/dist/packages/player/src/controls/customControls.js +59 -0
- package/dist/packages/player/src/controls/formatTime.js +19 -0
- package/dist/packages/player/src/controls/icons.js +40 -0
- package/dist/packages/player/src/controls/keyboard.js +118 -0
- package/dist/packages/player/src/controls/messages.js +94 -0
- package/dist/packages/player/src/controls/parseMarkup.js +31 -0
- package/dist/packages/player/src/controls/viewButtons.js +14 -0
- package/dist/packages/player/src/element/ElementLoads.js +119 -0
- package/dist/packages/player/src/element/FullscreenToggle.js +39 -0
- package/dist/packages/player/src/element/GyroViewElement.js +313 -0
- package/dist/packages/player/src/element/IdleWatcher.js +71 -0
- package/dist/packages/player/src/element/TypedEventElement.js +22 -0
- package/dist/packages/player/src/element/accessibleRegion.js +19 -0
- package/dist/packages/player/src/element/attributeNames.js +51 -0
- package/dist/packages/player/src/element/attributes.js +101 -0
- package/dist/packages/player/src/element/defineGyroView.js +21 -0
- package/dist/packages/player/src/element/earlyProperties.js +36 -0
- package/dist/packages/player/src/element/elementSource.js +23 -0
- package/dist/packages/player/src/element/liveSettings.js +138 -0
- package/dist/packages/player/src/element/playbackAttributes.js +53 -0
- package/dist/packages/player/src/element/playerEventMirror.js +33 -0
- package/dist/packages/player/src/element/reflectedProperties.js +78 -0
- package/dist/packages/player/src/element/styles.js +4 -0
- package/dist/packages/player/src/element/template.js +47 -0
- package/dist/packages/player/src/index.js +10 -0
- package/dist/packages/player/src/inspectRecording.js +22 -0
- package/dist/packages/player/src/player/DrawingBufferFit.js +77 -0
- package/dist/packages/player/src/player/FrameLoop.js +40 -0
- package/dist/packages/player/src/player/PictureSettings.js +60 -0
- package/dist/packages/player/src/player/Player.js +377 -0
- package/dist/packages/player/src/player/PlayerEvents.js +28 -0
- package/dist/packages/player/src/player/PlayerPhase.js +21 -0
- package/dist/packages/player/src/player/PlayerSound.js +61 -0
- package/dist/packages/player/src/player/PlayerView.js +113 -0
- package/dist/packages/player/src/player/SessionRelay.js +66 -0
- package/dist/packages/player/src/player/ensureFinite.js +26 -0
- package/dist/packages/player/src/player/loadRecording.js +65 -0
- package/dist/packages/player/src/player/transportEvents.js +29 -0
- package/dist/standalone.js +4919 -0
- package/package.json +73 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
//#region ../../packages/player/src/controls/ChoiceMenu.ts
|
|
2
|
+
/**
|
|
3
|
+
* Where each navigation key moves the focus among `count` choices from the one at `index`.
|
|
4
|
+
*/
|
|
5
|
+
var FOCUS_STEPS = /* @__PURE__ */ new Map([
|
|
6
|
+
["ArrowDown", (index, count) => (index + 1) % count],
|
|
7
|
+
["ArrowUp", (index, count) => (index - 1 + count) % count],
|
|
8
|
+
["Home", () => 0],
|
|
9
|
+
["End", (_index, count) => count - 1]
|
|
10
|
+
]);
|
|
11
|
+
/**
|
|
12
|
+
* Presses are seen on their way in, before the control they land on.
|
|
13
|
+
*/
|
|
14
|
+
var ON_THE_WAY_IN = { capture: true };
|
|
15
|
+
/**
|
|
16
|
+
* A button opening a popup of choices with the current one checked, as the ARIA menu pattern
|
|
17
|
+
* has it. The button toggles it; a choice, Escape, Tab, a press outside it or the focus moving
|
|
18
|
+
* elsewhere closes it; the arrow keys move between the choices, which are out of the tab order.
|
|
19
|
+
* A press outside only closes it, so a tap on the picture does not also toggle play, and keys
|
|
20
|
+
* pressed in it stay with it, so one Escape does not also leave fullscreen and the arrows do not
|
|
21
|
+
* turn the view.
|
|
22
|
+
*/
|
|
23
|
+
var ChoiceMenu = class {
|
|
24
|
+
parts;
|
|
25
|
+
items;
|
|
26
|
+
constructor(root, parts, choose) {
|
|
27
|
+
this.parts = parts;
|
|
28
|
+
this.items = [...parts.popup.querySelectorAll(":scope [role=\"menuitemradio\"]")];
|
|
29
|
+
for (const item of this.items) item.addEventListener("click", () => {
|
|
30
|
+
this.closeToButton();
|
|
31
|
+
const { choice } = item.dataset;
|
|
32
|
+
if (choice !== void 0) choose(choice);
|
|
33
|
+
});
|
|
34
|
+
this.bindButton();
|
|
35
|
+
this.bindDismissal(root);
|
|
36
|
+
this.bindKeys(root);
|
|
37
|
+
}
|
|
38
|
+
get isOpen() {
|
|
39
|
+
return !this.parts.popup.hidden;
|
|
40
|
+
}
|
|
41
|
+
markChosen(choice) {
|
|
42
|
+
for (const item of this.items) item.setAttribute("aria-checked", String(item.dataset["choice"] === choice));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Shows or hides the button; a menu that goes away closes.
|
|
46
|
+
*/
|
|
47
|
+
setAvailable(isAvailable) {
|
|
48
|
+
this.parts.button.hidden = !isAvailable;
|
|
49
|
+
if (!isAvailable) this.setOpen(false);
|
|
50
|
+
}
|
|
51
|
+
bindButton() {
|
|
52
|
+
this.parts.button.addEventListener("click", () => {
|
|
53
|
+
this.setOpen(!this.isOpen);
|
|
54
|
+
if (this.isOpen) this.focusItem(this.items.findIndex((item) => isChecked(item)));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A press outside, or the focus moving elsewhere, closes the menu.
|
|
59
|
+
*/
|
|
60
|
+
bindDismissal(root) {
|
|
61
|
+
const { button, popup } = this.parts;
|
|
62
|
+
root.addEventListener("pointerdown", (event) => {
|
|
63
|
+
if (!this.isOpen) return;
|
|
64
|
+
if (event.composedPath().some((node) => node === popup || node === button)) return;
|
|
65
|
+
this.setOpen(false);
|
|
66
|
+
event.stopPropagation();
|
|
67
|
+
}, ON_THE_WAY_IN);
|
|
68
|
+
root.addEventListener("focusout", (event) => {
|
|
69
|
+
const next = event instanceof FocusEvent ? event.relatedTarget : null;
|
|
70
|
+
if (next !== button && next instanceof Node && !popup.contains(next)) this.setOpen(false);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
bindKeys(root) {
|
|
74
|
+
root.addEventListener("keydown", (event) => {
|
|
75
|
+
if (this.isOpen && event instanceof KeyboardEvent) this.onKey(event);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
onKey(event) {
|
|
79
|
+
const path = event.composedPath();
|
|
80
|
+
if (event.key === "Escape") {
|
|
81
|
+
this.closeToButton();
|
|
82
|
+
event.stopPropagation();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!path.includes(this.parts.popup)) return;
|
|
86
|
+
event.stopPropagation();
|
|
87
|
+
if (event.key === "Tab") this.setOpen(false);
|
|
88
|
+
else this.moveFocus(event, path);
|
|
89
|
+
}
|
|
90
|
+
moveFocus(event, path) {
|
|
91
|
+
const step = FOCUS_STEPS.get(event.key);
|
|
92
|
+
if (!step) return;
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
const current = this.items.findIndex((item) => path.includes(item));
|
|
95
|
+
this.focusItem(step(current, this.items.length));
|
|
96
|
+
}
|
|
97
|
+
focusItem(index) {
|
|
98
|
+
(this.items[index] ?? this.items[0])?.focus();
|
|
99
|
+
}
|
|
100
|
+
closeToButton() {
|
|
101
|
+
this.setOpen(false);
|
|
102
|
+
this.parts.button.focus();
|
|
103
|
+
}
|
|
104
|
+
setOpen(isOpen) {
|
|
105
|
+
this.parts.popup.hidden = !isOpen;
|
|
106
|
+
this.parts.button.setAttribute("aria-expanded", String(isOpen));
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
function isChecked(item) {
|
|
110
|
+
return item.getAttribute("aria-checked") === "true";
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
export { ChoiceMenu };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { viewModeRulesFor } from "../../../core/src/domain/view/viewModes.js";
|
|
2
|
+
import "../../../core/src/index.js";
|
|
3
|
+
import { stabilizationModeOf, viewModeOf } from "../choices.js";
|
|
4
|
+
import { ChoiceMenu } from "./ChoiceMenu.js";
|
|
5
|
+
//#region ../../packages/player/src/controls/PictureMenus.ts
|
|
6
|
+
/**
|
|
7
|
+
* The view mode and stabilization menus, each behind a button of its own. A choice sets the
|
|
8
|
+
* player's mode, and each menu checks the mode in effect however it was last changed.
|
|
9
|
+
* Stabilization is offered only where it changes the picture: a loaded recording with a gyro,
|
|
10
|
+
* shown in a stitched view mode.
|
|
11
|
+
*/
|
|
12
|
+
var PictureMenus = class {
|
|
13
|
+
player;
|
|
14
|
+
viewMode;
|
|
15
|
+
stabilization;
|
|
16
|
+
constructor(root, parts, player) {
|
|
17
|
+
this.player = player;
|
|
18
|
+
this.viewMode = new ChoiceMenu(root, parts.viewMode, (choice) => {
|
|
19
|
+
const mode = viewModeOf(choice);
|
|
20
|
+
if (mode) player.setViewMode(mode);
|
|
21
|
+
});
|
|
22
|
+
this.stabilization = new ChoiceMenu(root, parts.stabilization, (choice) => {
|
|
23
|
+
const mode = stabilizationModeOf(choice);
|
|
24
|
+
if (mode) player.setStabilization(mode);
|
|
25
|
+
});
|
|
26
|
+
player.events.on("viewmodechange", (mode) => {
|
|
27
|
+
this.viewMode.markChosen(mode);
|
|
28
|
+
this.offerStabilization();
|
|
29
|
+
});
|
|
30
|
+
player.events.on("stabilizationchange", (mode) => {
|
|
31
|
+
this.stabilization.markChosen(mode);
|
|
32
|
+
});
|
|
33
|
+
player.events.on("statuschange", () => {
|
|
34
|
+
this.offerStabilization();
|
|
35
|
+
});
|
|
36
|
+
this.viewMode.markChosen(player.viewMode);
|
|
37
|
+
this.stabilization.markChosen(player.stabilization);
|
|
38
|
+
this.offerStabilization();
|
|
39
|
+
}
|
|
40
|
+
offerStabilization() {
|
|
41
|
+
const hasGyro = this.player.metadata?.hasGyro === true;
|
|
42
|
+
const isStabilized = viewModeRulesFor(this.player.viewMode).isStabilized;
|
|
43
|
+
this.stabilization.setAvailable(hasGyro && isStabilized);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { PictureMenus };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { messageOf } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
import "../../../core/src/index.js";
|
|
3
|
+
import "./keyboard.js";
|
|
4
|
+
import { formatTime } from "./formatTime.js";
|
|
5
|
+
//#region ../../packages/player/src/controls/SeekBar.ts
|
|
6
|
+
/**
|
|
7
|
+
* The slider's arrow keys and the direction each seeks in: the native step is the one the
|
|
8
|
+
* thumb is dragged in, a hundredth of a second, too fine for a key.
|
|
9
|
+
*/
|
|
10
|
+
var ARROW_DIRECTIONS = /* @__PURE__ */ new Map([
|
|
11
|
+
["ArrowLeft", -1],
|
|
12
|
+
["ArrowDown", -1],
|
|
13
|
+
["ArrowRight", 1],
|
|
14
|
+
["ArrowUp", 1]
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* The events that end a drag of the thumb. A drag released where it began commits no `change`,
|
|
18
|
+
* so the release ends it too, whichever comes first.
|
|
19
|
+
*/
|
|
20
|
+
var SCRUB_ENDINGS = [
|
|
21
|
+
"pointerup",
|
|
22
|
+
"pointercancel",
|
|
23
|
+
"change"
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* The seek bar and the time beside it: dragging scrubs to key frames, releasing seeks exactly,
|
|
27
|
+
* and the bar follows playback while it is not held. Its range is the recording's duration,
|
|
28
|
+
* read on every status change, so a failed or unloaded recording leaves an empty bar.
|
|
29
|
+
*/
|
|
30
|
+
var SeekBar = class {
|
|
31
|
+
parts;
|
|
32
|
+
host;
|
|
33
|
+
isScrubbing = false;
|
|
34
|
+
scrubTarget;
|
|
35
|
+
scrubbing;
|
|
36
|
+
constructor(parts, host) {
|
|
37
|
+
this.parts = parts;
|
|
38
|
+
this.host = host;
|
|
39
|
+
this.bindInput();
|
|
40
|
+
const { events } = this.host.player;
|
|
41
|
+
events.on("statuschange", () => {
|
|
42
|
+
this.showRecording();
|
|
43
|
+
});
|
|
44
|
+
events.on("timeupdate", (time) => {
|
|
45
|
+
this.follow(time);
|
|
46
|
+
});
|
|
47
|
+
this.showRecording();
|
|
48
|
+
}
|
|
49
|
+
bindInput() {
|
|
50
|
+
const { seek } = this.parts;
|
|
51
|
+
seek.addEventListener("input", () => {
|
|
52
|
+
this.isScrubbing = true;
|
|
53
|
+
const time = Number(seek.value);
|
|
54
|
+
this.showTime(time);
|
|
55
|
+
this.scrubTo(time);
|
|
56
|
+
});
|
|
57
|
+
for (const ending of SCRUB_ENDINGS) seek.addEventListener(ending, () => {
|
|
58
|
+
this.endScrub();
|
|
59
|
+
});
|
|
60
|
+
seek.addEventListener("keydown", (event) => {
|
|
61
|
+
const direction = ARROW_DIRECTIONS.get(event.key);
|
|
62
|
+
if (direction === void 0) return;
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
const { player } = this.host;
|
|
65
|
+
player.seek(player.currentTime + direction * 5);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
showRecording() {
|
|
69
|
+
const { duration, currentTime } = this.host.player;
|
|
70
|
+
this.parts.seek.max = String(duration);
|
|
71
|
+
this.follow(currentTime);
|
|
72
|
+
}
|
|
73
|
+
follow(time) {
|
|
74
|
+
if (this.isScrubbing) return;
|
|
75
|
+
this.parts.seek.value = String(time);
|
|
76
|
+
this.showTime(time);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* One scrub in flight at a time; a newer target replaces one still waiting, so the picture
|
|
80
|
+
* follows the thumb without queueing every position it passed.
|
|
81
|
+
*/
|
|
82
|
+
scrubTo(time) {
|
|
83
|
+
this.scrubTarget = time;
|
|
84
|
+
this.scrubbing ??= this.drainScrubs();
|
|
85
|
+
}
|
|
86
|
+
async drainScrubs() {
|
|
87
|
+
try {
|
|
88
|
+
while (this.scrubTarget !== void 0) {
|
|
89
|
+
const target = this.scrubTarget;
|
|
90
|
+
this.scrubTarget = void 0;
|
|
91
|
+
await this.host.player.scrub(target);
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
this.host.warn({
|
|
95
|
+
code: "playback-failed",
|
|
96
|
+
message: `the seek bar could not show that moment: ${messageOf(error)}`
|
|
97
|
+
});
|
|
98
|
+
} finally {
|
|
99
|
+
this.scrubbing = void 0;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Seeks exactly where the thumb was let go; a scrub still in flight yields to the seek (the
|
|
104
|
+
* session drops a scrub a seek overtook).
|
|
105
|
+
*/
|
|
106
|
+
endScrub() {
|
|
107
|
+
if (!this.isScrubbing) return;
|
|
108
|
+
this.isScrubbing = false;
|
|
109
|
+
this.scrubTarget = void 0;
|
|
110
|
+
this.host.player.seek(Number(this.parts.seek.value));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The time beside the bar, and the same time for screen readers, which would read the slider's
|
|
114
|
+
* raw seconds.
|
|
115
|
+
*/
|
|
116
|
+
showTime(time) {
|
|
117
|
+
const [shown, total] = [formatTime(time), formatTime(this.host.player.duration)];
|
|
118
|
+
this.parts.time.textContent = `${shown} / ${total}`;
|
|
119
|
+
const { position } = this.host.wording.current.labels;
|
|
120
|
+
const spoken = position.replaceAll("{time}", () => shown).replaceAll("{duration}", () => total);
|
|
121
|
+
this.parts.seek.setAttribute("aria-valuetext", spoken);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
//#endregion
|
|
125
|
+
export { SeekBar };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { iconNode } from "./icons.js";
|
|
2
|
+
//#region ../../packages/player/src/controls/SoundControls.ts
|
|
3
|
+
var PERCENT = 100;
|
|
4
|
+
/**
|
|
5
|
+
* The mute button and the volume slider, showing the sound however it was last changed. The
|
|
6
|
+
* button is a toggle named "Mute": pressed means muted, as assistive technology expects. The
|
|
7
|
+
* slider shows only where a page can set the volume.
|
|
8
|
+
*/
|
|
9
|
+
var SoundControls = class {
|
|
10
|
+
parts;
|
|
11
|
+
player;
|
|
12
|
+
constructor(parts, player) {
|
|
13
|
+
this.parts = parts;
|
|
14
|
+
this.player = player;
|
|
15
|
+
parts.volume.hidden = !player.canSetVolume;
|
|
16
|
+
parts.mute.addEventListener("click", () => {
|
|
17
|
+
player.setMuted(!player.isMuted);
|
|
18
|
+
});
|
|
19
|
+
parts.volume.addEventListener("input", () => {
|
|
20
|
+
player.setVolume(Number(parts.volume.value));
|
|
21
|
+
if (player.isMuted) player.setMuted(false);
|
|
22
|
+
});
|
|
23
|
+
player.events.on("volumechange", () => {
|
|
24
|
+
this.reflect();
|
|
25
|
+
});
|
|
26
|
+
this.reflect();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The slider says its volume as a percentage, not the fraction a screen reader would read.
|
|
30
|
+
*/
|
|
31
|
+
reflect() {
|
|
32
|
+
const { isMuted, volume } = this.player;
|
|
33
|
+
const shown = isMuted ? 0 : volume;
|
|
34
|
+
this.parts.mute.replaceChildren(iconNode(isMuted ? "muted" : "sound"));
|
|
35
|
+
this.parts.mute.setAttribute("aria-pressed", String(isMuted));
|
|
36
|
+
this.parts.volume.value = String(shown);
|
|
37
|
+
this.parts.volume.setAttribute("aria-valuetext", `${Math.round(shown * PERCENT)}%`);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { SoundControls };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { iconNode } from "./icons.js";
|
|
2
|
+
//#region ../../packages/player/src/controls/TransportButtons.ts
|
|
3
|
+
/**
|
|
4
|
+
* Play and pause, on the bar and over the picture, and stop; the play buttons say what a press
|
|
5
|
+
* does.
|
|
6
|
+
*/
|
|
7
|
+
var TransportButtons = class {
|
|
8
|
+
parts;
|
|
9
|
+
host;
|
|
10
|
+
constructor(parts, host) {
|
|
11
|
+
this.parts = parts;
|
|
12
|
+
this.host = host;
|
|
13
|
+
const { player } = host;
|
|
14
|
+
for (const button of [parts.play, parts.bigPlay]) button.addEventListener("click", () => {
|
|
15
|
+
host.togglePlay();
|
|
16
|
+
});
|
|
17
|
+
parts.stop.addEventListener("click", () => {
|
|
18
|
+
player.stop();
|
|
19
|
+
});
|
|
20
|
+
player.events.on("statuschange", () => {
|
|
21
|
+
this.reflect(player.isPaused);
|
|
22
|
+
});
|
|
23
|
+
this.reflect(player.isPaused);
|
|
24
|
+
}
|
|
25
|
+
reflect(isPaused) {
|
|
26
|
+
for (const button of [this.parts.play, this.parts.bigPlay]) this.host.wording.label(button, isPaused ? "play" : "pause");
|
|
27
|
+
this.parts.play.replaceChildren(iconNode(isPaused ? "play" : "pause"));
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { TransportButtons };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { zoomStepsForPinch } from "../../../core/src/domain/view/viewGestures.js";
|
|
2
|
+
import "../../../core/src/index.js";
|
|
3
|
+
//#region ../../packages/player/src/controls/ViewGestures.ts
|
|
4
|
+
/**
|
|
5
|
+
* A wheel notch on most mice reports about 100 pixels; one notch is one zoom step.
|
|
6
|
+
*/
|
|
7
|
+
var WHEEL_PIXELS_PER_STEP = 100;
|
|
8
|
+
/**
|
|
9
|
+
* On the surface while a drag would move the picture; `controls.css` shows the grab hand for it.
|
|
10
|
+
*/
|
|
11
|
+
var DRAGGABLE_ATTRIBUTE = "data-draggable";
|
|
12
|
+
/**
|
|
13
|
+
* `PointerEvent.button` of the main mouse button.
|
|
14
|
+
*/
|
|
15
|
+
var PRIMARY_BUTTON = 0;
|
|
16
|
+
/**
|
|
17
|
+
* Movement below this is a tap, not a drag.
|
|
18
|
+
*/
|
|
19
|
+
var TAP_TOLERANCE_PIXELS = 4;
|
|
20
|
+
/**
|
|
21
|
+
* Turns pointer drags, two-finger pinches and wheel turns on the surface into view changes, the
|
|
22
|
+
* zooms toward the pointer or the fingers, and reports a tap (a press without a drag) for the
|
|
23
|
+
* host to treat as a play toggle. Whether a drag would move the picture is checked whenever the
|
|
24
|
+
* pointer moves over the surface, so the cursor shows a hand only then.
|
|
25
|
+
*/
|
|
26
|
+
var ViewGestures = class {
|
|
27
|
+
surface;
|
|
28
|
+
player;
|
|
29
|
+
onTap;
|
|
30
|
+
pointers = /* @__PURE__ */ new Map();
|
|
31
|
+
listening = new AbortController();
|
|
32
|
+
dragDistance = 0;
|
|
33
|
+
constructor(surface, player, onTap) {
|
|
34
|
+
this.surface = surface;
|
|
35
|
+
this.player = player;
|
|
36
|
+
this.onTap = onTap;
|
|
37
|
+
const { signal } = this.listening;
|
|
38
|
+
surface.addEventListener("pointerenter", this.reflectDraggable, { signal });
|
|
39
|
+
surface.addEventListener("pointerdown", this.onPointerDown, { signal });
|
|
40
|
+
surface.addEventListener("pointermove", this.onPointerMove, { signal });
|
|
41
|
+
surface.addEventListener("pointerup", this.onPointerUp, { signal });
|
|
42
|
+
surface.addEventListener("pointercancel", this.onPointerCancel, { signal });
|
|
43
|
+
surface.addEventListener("lostpointercapture", this.onPointerCancel, { signal });
|
|
44
|
+
surface.addEventListener("wheel", this.onWheel, {
|
|
45
|
+
passive: false,
|
|
46
|
+
signal
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Stops answering the surface's gestures.
|
|
51
|
+
*/
|
|
52
|
+
detach() {
|
|
53
|
+
this.listening.abort();
|
|
54
|
+
this.pointers.clear();
|
|
55
|
+
this.surface.removeAttribute(DRAGGABLE_ATTRIBUTE);
|
|
56
|
+
}
|
|
57
|
+
onPointerDown = (event) => {
|
|
58
|
+
if (event.pointerType === "mouse" && event.button !== PRIMARY_BUTTON) return;
|
|
59
|
+
if (this.pointers.size === 0) this.dragDistance = 0;
|
|
60
|
+
this.pointers.set(event.pointerId, {
|
|
61
|
+
x: event.clientX,
|
|
62
|
+
y: event.clientY
|
|
63
|
+
});
|
|
64
|
+
this.surface.setPointerCapture(event.pointerId);
|
|
65
|
+
};
|
|
66
|
+
onPointerMove = (event) => {
|
|
67
|
+
this.reflectDraggable();
|
|
68
|
+
const previous = this.pointers.get(event.pointerId);
|
|
69
|
+
if (!previous) return;
|
|
70
|
+
const current = {
|
|
71
|
+
x: event.clientX,
|
|
72
|
+
y: event.clientY
|
|
73
|
+
};
|
|
74
|
+
if (this.pointers.size === 1) this.pan(previous, current);
|
|
75
|
+
else this.pinch(event.pointerId, current);
|
|
76
|
+
this.pointers.set(event.pointerId, current);
|
|
77
|
+
};
|
|
78
|
+
onPointerUp = (event) => {
|
|
79
|
+
if (!this.pointers.delete(event.pointerId) || this.pointers.size > 0) return;
|
|
80
|
+
if (this.dragDistance < TAP_TOLERANCE_PIXELS) this.onTap(event.pointerType);
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* A press the browser took over ends without a tap, however far it had moved.
|
|
84
|
+
*/
|
|
85
|
+
onPointerCancel = (event) => {
|
|
86
|
+
this.pointers.delete(event.pointerId);
|
|
87
|
+
this.dragDistance = Infinity;
|
|
88
|
+
};
|
|
89
|
+
onWheel = (event) => {
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
const pointer = {
|
|
92
|
+
x: event.clientX,
|
|
93
|
+
y: event.clientY
|
|
94
|
+
};
|
|
95
|
+
this.player.zoom(-event.deltaY / WHEEL_PIXELS_PER_STEP, this.focusAt(pointer));
|
|
96
|
+
this.reflectDraggable();
|
|
97
|
+
};
|
|
98
|
+
reflectDraggable = () => {
|
|
99
|
+
this.surface.toggleAttribute(DRAGGABLE_ATTRIBUTE, this.player.canPan);
|
|
100
|
+
};
|
|
101
|
+
pan(previous, current) {
|
|
102
|
+
const delta = {
|
|
103
|
+
x: current.x - previous.x,
|
|
104
|
+
y: current.y - previous.y
|
|
105
|
+
};
|
|
106
|
+
this.dragDistance += Math.hypot(delta.x, delta.y);
|
|
107
|
+
this.player.pan(delta);
|
|
108
|
+
}
|
|
109
|
+
pinch(pointerId, current) {
|
|
110
|
+
const other = [...this.pointers].find(([id]) => id !== pointerId)?.[1];
|
|
111
|
+
const previous = this.pointers.get(pointerId);
|
|
112
|
+
if (!other || !previous) return;
|
|
113
|
+
this.dragDistance = Infinity;
|
|
114
|
+
const steps = zoomStepsForPinch(distanceBetween(previous, other), distanceBetween(current, other));
|
|
115
|
+
const between = {
|
|
116
|
+
x: (current.x + other.x) / 2,
|
|
117
|
+
y: (current.y + other.y) / 2
|
|
118
|
+
};
|
|
119
|
+
this.player.zoom(steps, this.focusAt(between));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A point of the browser's viewport as fractions of the surface, from its top-left corner.
|
|
123
|
+
*/
|
|
124
|
+
focusAt(point) {
|
|
125
|
+
const bounds = this.surface.getBoundingClientRect();
|
|
126
|
+
return {
|
|
127
|
+
x: (point.x - bounds.left) / Math.max(bounds.width, 1),
|
|
128
|
+
y: (point.y - bounds.top) / Math.max(bounds.height, 1)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
function distanceBetween(a, b) {
|
|
133
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
136
|
+
export { ViewGestures };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { isGyroViewErrorCode } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
import "../../../core/src/index.js";
|
|
3
|
+
import { DEFAULT_MESSAGES, isChoiceTable, isLabelName, messagesWith } from "./messages.js";
|
|
4
|
+
//#region ../../packages/player/src/controls/Wording.ts
|
|
5
|
+
var LABEL = "data-label";
|
|
6
|
+
var TEXT = "data-text";
|
|
7
|
+
var CHOICES = "data-choices";
|
|
8
|
+
var ERROR = "data-error";
|
|
9
|
+
var SELECTORS = {
|
|
10
|
+
label: "[data-label]",
|
|
11
|
+
text: "[data-text]",
|
|
12
|
+
choices: "[data-choices]",
|
|
13
|
+
error: "[data-error]"
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The words the element speaks, and where they go. The markup holds no words: each element that
|
|
17
|
+
* says one names it in a data attribute, which `write` fills in, at first and after every change
|
|
18
|
+
* of words.
|
|
19
|
+
* - `data-label`: the label for its `aria-label`;
|
|
20
|
+
* - `data-text`: the label for its text;
|
|
21
|
+
* - `data-choices` on a menu: the table its items' `data-choice` are read from;
|
|
22
|
+
* - `data-error`: the visitor's words for that failure code.
|
|
23
|
+
*/
|
|
24
|
+
var Wording = class {
|
|
25
|
+
words = DEFAULT_MESSAGES;
|
|
26
|
+
get current() {
|
|
27
|
+
return this.words;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The defaults with the page's own words in their place; `write` shows them.
|
|
31
|
+
*/
|
|
32
|
+
replace(overrides) {
|
|
33
|
+
this.words = messagesWith(overrides);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Calls `element` by the label `name` names, now and after every change of words.
|
|
37
|
+
*/
|
|
38
|
+
label(element, name) {
|
|
39
|
+
element.setAttribute(LABEL, name);
|
|
40
|
+
element.setAttribute("aria-label", this.words.labels[name]);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Tells the visitor in `element` that the recording failed with `code`.
|
|
44
|
+
*/
|
|
45
|
+
showError(element, code) {
|
|
46
|
+
element.setAttribute(ERROR, code);
|
|
47
|
+
element.textContent = this.words.errors[code];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Fills in every word below `root` that its markup names.
|
|
51
|
+
*/
|
|
52
|
+
write(root) {
|
|
53
|
+
for (const element of root.querySelectorAll(SELECTORS.label)) this.writeLabel(element);
|
|
54
|
+
for (const element of root.querySelectorAll(SELECTORS.text)) this.writeText(element);
|
|
55
|
+
for (const menu of root.querySelectorAll(SELECTORS.choices)) this.writeChoices(menu);
|
|
56
|
+
for (const element of root.querySelectorAll(SELECTORS.error)) this.writeError(element);
|
|
57
|
+
}
|
|
58
|
+
writeLabel(element) {
|
|
59
|
+
const name = element.getAttribute(LABEL);
|
|
60
|
+
if (isLabelName(name)) element.setAttribute("aria-label", this.words.labels[name]);
|
|
61
|
+
}
|
|
62
|
+
writeText(element) {
|
|
63
|
+
const name = element.getAttribute(TEXT);
|
|
64
|
+
if (isLabelName(name)) element.textContent = this.words.labels[name];
|
|
65
|
+
}
|
|
66
|
+
writeChoices(menu) {
|
|
67
|
+
const name = menu.getAttribute(CHOICES);
|
|
68
|
+
if (!isChoiceTable(name)) return;
|
|
69
|
+
const table = this.words[name];
|
|
70
|
+
for (const item of menu.querySelectorAll("[data-choice]")) item.textContent = table[item.dataset["choice"] ?? ""] ?? "";
|
|
71
|
+
}
|
|
72
|
+
writeError(element) {
|
|
73
|
+
const code = element.getAttribute(ERROR);
|
|
74
|
+
if (isGyroViewErrorCode(code)) element.textContent = this.words.errors[code];
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
export { Wording };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ensureInvariant } from "../../../core/src/shared/errors/GyroViewError.js";
|
|
2
|
+
import "../../../core/src/index.js";
|
|
3
|
+
import { ChoiceMenuName, choiceMenuClasses } from "./controlsMarkup.js";
|
|
4
|
+
//#region ../../packages/player/src/controls/controlParts.ts
|
|
5
|
+
/**
|
|
6
|
+
* The one element matching `selector`, checked to be of the expected kind.
|
|
7
|
+
*/
|
|
8
|
+
function queryShadow(root, selector, kind) {
|
|
9
|
+
const element = root.querySelector(selector);
|
|
10
|
+
ensureInvariant(element instanceof kind, `the element template lacks ${selector}`);
|
|
11
|
+
return element;
|
|
12
|
+
}
|
|
13
|
+
function queryChoiceMenu(root, name) {
|
|
14
|
+
const classes = choiceMenuClasses(name);
|
|
15
|
+
return {
|
|
16
|
+
button: queryShadow(root, `.${classes.button}`, HTMLButtonElement),
|
|
17
|
+
popup: queryShadow(root, `.${classes.popup}`, HTMLElement)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function queryControlParts(root) {
|
|
21
|
+
return {
|
|
22
|
+
seek: queryShadow(root, ".seek", HTMLInputElement),
|
|
23
|
+
play: queryShadow(root, ".play", HTMLButtonElement),
|
|
24
|
+
bigPlay: queryShadow(root, ".big-play", HTMLButtonElement),
|
|
25
|
+
stop: queryShadow(root, ".stop", HTMLButtonElement),
|
|
26
|
+
time: queryShadow(root, ".time", HTMLElement),
|
|
27
|
+
mute: queryShadow(root, ".mute", HTMLButtonElement),
|
|
28
|
+
volume: queryShadow(root, ".volume", HTMLInputElement),
|
|
29
|
+
resetView: queryShadow(root, ".reset-view", HTMLButtonElement),
|
|
30
|
+
stabilization: queryChoiceMenu(root, ChoiceMenuName.Stabilization),
|
|
31
|
+
viewMode: queryChoiceMenu(root, ChoiceMenuName.ViewMode),
|
|
32
|
+
fullscreen: queryShadow(root, ".fullscreen", HTMLButtonElement)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { queryControlParts, queryShadow };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
//#region ../../packages/player/src/controls/controls.css?raw
|
|
2
|
+
var controls_default = "/*\n * The controls' own styles: the big play button, the bar, its buttons, sliders and menus, and the\n * grab hand over the picture. The host's custom properties (`--gyro-view-*`) theme them.\n */\n\n.big-play {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 4.5rem;\n height: 4.5rem;\n transform: translate(-50%, -50%);\n border: 0;\n border-radius: 50%;\n background: var(--gyro-view-controls-background);\n color: var(--gyro-view-text);\n cursor: pointer;\n}\n\n.big-play .icon {\n width: 2rem;\n height: 2rem;\n}\n\n:host(:not([data-status='ready']):not([data-status='paused'])) .big-play,\n:host(:not([controls])) .big-play {\n display: none;\n}\n\n.controls {\n position: absolute;\n inset: auto 0 0 0;\n display: flex;\n flex-direction: column;\n padding: 0.25rem 0.5rem 0.5rem;\n background: linear-gradient(to top, var(--gyro-view-controls-background), transparent);\n transition: opacity 0.25s;\n}\n\n/* A failed player has nothing to control: the error says so, and a press would only fail again. */\n:host(:not([controls])) .controls,\n:host([data-status='idle']) .controls,\n:host([data-status='loading']) .controls,\n:host([data-status='error']) .controls {\n display: none;\n}\n\n/* Controls with an open menu stay up: the viewer is reading it. So do controls holding the\n keyboard's focus, which would otherwise fade with its ring out of sight. */\n:host([data-idle]) .controls:not(:has(.popup:not([hidden]))):not(:has(:focus-visible)) {\n opacity: 0;\n pointer-events: none;\n}\n\n.row {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n\n.spacer {\n flex: 1;\n}\n\n.time {\n font-size: 0.8rem;\n font-variant-numeric: tabular-nums;\n padding: 0 0.5rem;\n white-space: nowrap;\n}\n\nbutton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 2.25rem;\n height: 2.25rem;\n border: 0;\n border-radius: var(--gyro-view-radius);\n background: transparent;\n color: var(--gyro-view-text);\n font: inherit;\n cursor: pointer;\n}\n\n.icon {\n width: 1.25rem;\n height: 1.25rem;\n pointer-events: none;\n}\n\nbutton:hover,\nbutton:focus-visible {\n background: rgb(255 255 255 / 15%);\n}\n\nbutton:focus-visible,\ninput:focus-visible {\n outline: 2px solid var(--gyro-view-accent);\n outline-offset: 1px;\n}\n\ninput[type='range'] {\n accent-color: var(--gyro-view-accent);\n margin: 0;\n}\n\n.seek {\n width: 100%;\n height: 1.25rem;\n}\n\n.volume {\n width: 5rem;\n}\n\nbutton[hidden] {\n display: none;\n}\n\n.choice {\n position: relative;\n display: flex;\n}\n\n/* A menu opens upward from the bar and never past the player's top, which would hide its first\n items out of reach: what does not fit scrolls. The bar's row and gaps take the 3.75rem. */\n.popup {\n position: absolute;\n right: 0;\n bottom: calc(100% + 0.5rem);\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n max-height: calc(100cqh - 3.75rem);\n overflow-y: auto;\n min-width: 9rem;\n padding: 0.35rem;\n border-radius: var(--gyro-view-radius);\n background: var(--gyro-view-controls-background);\n}\n\n.popup[hidden] {\n display: none;\n}\n\n.popup-title {\n margin: 0.2rem 0.5rem 0.35rem;\n font-size: 0.75rem;\n opacity: 0.7;\n}\n\n[role='menu'] {\n display: flex;\n flex-direction: column;\n}\n\n[role='menuitemradio'] {\n justify-content: flex-start;\n gap: 0.4rem;\n height: 2rem;\n padding: 0 0.75rem 0 0.4rem;\n font-size: 0.9rem;\n white-space: nowrap;\n}\n\n[role='menuitemradio']::before {\n content: '\\2713';\n width: 1rem;\n color: var(--gyro-view-accent);\n visibility: hidden;\n}\n\n[role='menuitemradio'][aria-checked='true']::before {\n visibility: visible;\n}\n\n/* A narrowing player, on a phone or in a narrow column, gives up in turn the stop button (play\n pauses), the volume slider (mute stays), the time and Reset view (the 0 key stays), so the\n other buttons stay in reach. Each width is where the bar stops fitting with the part, with\n times up to an hour, in rem like the bar itself, so larger page text moves them with it (490,\n 450, 360 and 256 px at 16 px a rem). */\n@container (max-width: 30.625rem) {\n .stop {\n display: none;\n }\n}\n\n@container (max-width: 28.125rem) {\n .volume {\n display: none;\n }\n}\n\n@container (max-width: 22.5rem) {\n .time {\n display: none;\n }\n}\n\n@container (max-width: 16rem) {\n .reset-view {\n display: none;\n }\n}\n\n/* The hand only where a drag moves the picture (`data-draggable`, set by the view gestures). */\ncanvas[data-draggable] {\n cursor: grab;\n}\n\ncanvas[data-draggable]:active {\n cursor: grabbing;\n}\n\n/* A viewer who asked for less motion sees the controls come and go without fading. */\n@media (prefers-reduced-motion: reduce) {\n .controls {\n transition: none;\n }\n}\n";
|
|
3
|
+
//#endregion
|
|
4
|
+
export { controls_default as default };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { queryControlParts } from "./controlParts.js";
|
|
2
|
+
import { PictureMenus } from "./PictureMenus.js";
|
|
3
|
+
import { SeekBar } from "./SeekBar.js";
|
|
4
|
+
import { SoundControls } from "./SoundControls.js";
|
|
5
|
+
import { TransportButtons } from "./TransportButtons.js";
|
|
6
|
+
import { bindViewButtons } from "./viewButtons.js";
|
|
7
|
+
//#region ../../packages/player/src/controls/controlsBar.ts
|
|
8
|
+
/**
|
|
9
|
+
* Binds the control bar in the shadow tree: transport buttons, seek bar, sound, Reset view and
|
|
10
|
+
* Fullscreen, and the view mode and stabilization menus, each driving the player and showing its
|
|
11
|
+
* state. They live as long as
|
|
12
|
+
* the element: every listener sits on its shadow tree or its player.
|
|
13
|
+
*/
|
|
14
|
+
function bindControlsBar(root, host) {
|
|
15
|
+
const parts = queryControlParts(root);
|
|
16
|
+
new TransportButtons(parts, host);
|
|
17
|
+
bindViewButtons(parts, host);
|
|
18
|
+
new SeekBar(parts, host);
|
|
19
|
+
new SoundControls(parts, host.player);
|
|
20
|
+
new PictureMenus(root, parts, host.player);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { bindControlsBar };
|