@bubo-squared/gyroview 0.1.0 → 0.2.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 +34 -0
- package/README.md +7 -2
- package/dist/index.d.ts +23 -2
- package/dist/packages/adapters/fetch/src/httpRequest.js +10 -1
- package/dist/packages/adapters/three/src/shaders/rawLenses.frag.js +1 -1
- package/dist/packages/core/src/domain/view/lensTilesView.js +2 -2
- package/dist/packages/core/src/domain/view/magnification.js +1 -1
- package/dist/packages/core/src/domain/view/panoramaView.js +1 -1
- package/dist/packages/player/src/composition/browserPorts.js +16 -2
- package/dist/packages/player/src/composition/openInputs.js +2 -4
- package/dist/packages/player/src/composition/openRecording.js +13 -2
- package/dist/packages/player/src/controls/ChoiceMenu.js +16 -4
- package/dist/packages/player/src/controls/PictureMenus.js +22 -8
- package/dist/packages/player/src/controls/SeekBar.js +6 -3
- package/dist/packages/player/src/controls/SoundControls.js +2 -0
- package/dist/packages/player/src/controls/TransportButtons.js +2 -5
- package/dist/packages/player/src/controls/Wording.js +11 -9
- package/dist/packages/player/src/controls/controlParts.js +5 -3
- package/dist/packages/player/src/controls/controls.js +1 -1
- package/dist/packages/player/src/controls/controlsBar.js +4 -1
- package/dist/packages/player/src/controls/controlsMarkup.js +36 -21
- package/dist/packages/player/src/controls/icons.js +32 -8
- package/dist/packages/player/src/controls/messages.js +26 -4
- package/dist/packages/player/src/controls/sliderFill.js +16 -0
- package/dist/packages/player/src/element/ElementLoads.js +7 -0
- package/dist/packages/player/src/element/GyroViewElement.js +8 -3
- package/dist/packages/player/src/element/attributeNames.js +12 -1
- package/dist/packages/player/src/element/attributes.js +29 -7
- package/dist/packages/player/src/element/reflectedProperties.js +19 -1
- package/dist/packages/player/src/element/styles.js +1 -1
- package/dist/standalone.js +385 -126
- package/package.json +1 -1
|
@@ -2,7 +2,8 @@ import { STABILIZATION_MODES } from "../../../core/src/domain/motion/stabilizati
|
|
|
2
2
|
import { VIEW_MODES } from "../../../core/src/domain/view/ViewMode.js";
|
|
3
3
|
import "../../../core/src/index.js";
|
|
4
4
|
import controls_default from "./controls.js";
|
|
5
|
-
import { ICONS } from "./icons.js";
|
|
5
|
+
import { ICONS, STABILIZATION_ICONS, VIEW_MODE_ICONS } from "./icons.js";
|
|
6
|
+
import { STABILIZATION_WORDS, VIEW_MODE_WORDS } from "./messages.js";
|
|
6
7
|
//#region ../../packages/player/src/controls/controlsMarkup.ts
|
|
7
8
|
/**
|
|
8
9
|
* The bar's choice menus, by the name their parts are found under.
|
|
@@ -20,20 +21,35 @@ function choiceMenuClasses(name) {
|
|
|
20
21
|
popup: `${name}-menu`
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Each choice names itself in `data-choice` and shows its icon, and the words below it fill in
|
|
26
|
+
* from the menu's tables: its name labels it for assistive technology and its description
|
|
27
|
+
* describes it. Ids need be unique only within one element's shadow tree.
|
|
28
|
+
*/
|
|
29
|
+
function menuItemsOf(menu) {
|
|
30
|
+
return menu.choices.map((choice) => {
|
|
31
|
+
const id = `${menu.name}-${choice}`;
|
|
32
|
+
return `<button type="button" role="menuitemradio" aria-checked="false" tabindex="-1" data-choice="${choice}" aria-labelledby="${id}-name" aria-describedby="${id}-description"><span class="choice-icon">${menu.icons[choice] ?? ""}</span><span class="choice-name" id="${id}-name" data-choice-text="${menu.words.names}"></span><span class="choice-description" id="${id}-description" data-choice-text="${menu.words.descriptions}"></span></button>`;
|
|
33
|
+
}).join("");
|
|
25
34
|
}
|
|
26
35
|
/**
|
|
27
|
-
* A button opening a popup of choices
|
|
28
|
-
*
|
|
36
|
+
* A setting button opening a popup of choices. The button shows the icon of the choice in effect,
|
|
37
|
+
* which its `data-choice` names, and is named by words it never shows: what it sets and the
|
|
38
|
+
* choice's name, which hidden elements still give through `aria-labelledby`. The popup's title
|
|
39
|
+
* repeats what the button sets for sight; its close button serves the popup shown over the whole
|
|
40
|
+
* player.
|
|
29
41
|
*/
|
|
30
42
|
function choiceMenuOf(menu) {
|
|
31
43
|
const classes = choiceMenuClasses(menu.name);
|
|
44
|
+
const [label, value] = [`${menu.name}-label`, `${menu.name}-value`];
|
|
32
45
|
return `<div class="choice">
|
|
33
|
-
<button class="${classes.button}" type="button"
|
|
46
|
+
<button class="icon-button setting ${classes.button}" type="button" aria-haspopup="menu" aria-expanded="false" aria-labelledby="${label} ${value}"><span class="setting-icon"></span><span hidden><span id="${label}" data-text="${menu.title}"></span> <span id="${value}" data-choice-text="${menu.words.names}"></span></span></button>
|
|
34
47
|
<div class="popup ${classes.popup}" hidden>
|
|
35
|
-
<
|
|
36
|
-
|
|
48
|
+
<div class="popup-header">
|
|
49
|
+
<p class="popup-title" aria-hidden="true" data-text="${menu.title}"></p>
|
|
50
|
+
<button class="icon-button popup-close" type="button" data-label="close">${ICONS.close}</button>
|
|
51
|
+
</div>
|
|
52
|
+
<div role="menu" data-label="${menu.title}">${menuItemsOf(menu)}</div>
|
|
37
53
|
</div>
|
|
38
54
|
</div>`;
|
|
39
55
|
}
|
|
@@ -41,15 +57,15 @@ function pictureMenusMarkup() {
|
|
|
41
57
|
return choiceMenuOf({
|
|
42
58
|
name: ChoiceMenuName.Stabilization,
|
|
43
59
|
title: "stabilization",
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
60
|
+
choices: STABILIZATION_MODES,
|
|
61
|
+
icons: STABILIZATION_ICONS,
|
|
62
|
+
words: STABILIZATION_WORDS
|
|
47
63
|
}) + choiceMenuOf({
|
|
48
64
|
name: ChoiceMenuName.ViewMode,
|
|
49
65
|
title: "viewMode",
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
choices: VIEW_MODES,
|
|
67
|
+
icons: VIEW_MODE_ICONS,
|
|
68
|
+
words: VIEW_MODE_WORDS
|
|
53
69
|
});
|
|
54
70
|
}
|
|
55
71
|
/**
|
|
@@ -69,15 +85,14 @@ function controlsMarkup() {
|
|
|
69
85
|
<div class="controls" part="controls">
|
|
70
86
|
<input class="seek" type="range" min="0" max="0" step="0.01" value="0" data-label="seek" />
|
|
71
87
|
<div class="row">
|
|
72
|
-
<button class="play" type="button"></button>
|
|
73
|
-
<button class="
|
|
74
|
-
<span class="time">0:00 / 0:00</span>
|
|
75
|
-
<span class="spacer"></span>
|
|
76
|
-
<button class="mute" type="button" data-label="mute"></button>
|
|
88
|
+
<button class="icon-button play" type="button"></button>
|
|
89
|
+
<button class="icon-button mute" type="button" data-label="mute"></button>
|
|
77
90
|
<input class="volume" type="range" min="0" max="1" step="0.01" value="1" data-label="volume" />
|
|
78
|
-
<
|
|
91
|
+
<span class="time"><span class="elapsed">0:00</span> <span class="total">/ 0:00</span></span>
|
|
92
|
+
<span class="spacer"></span>
|
|
79
93
|
${pictureMenusMarkup()}
|
|
80
|
-
<button class="
|
|
94
|
+
<button class="icon-button reset-view" type="button" data-label="resetView">${ICONS.resetView}</button>
|
|
95
|
+
<button class="icon-button fullscreen" type="button" data-label="fullscreen">${ICONS.fullscreen}</button>
|
|
81
96
|
</div>
|
|
82
97
|
</div>`;
|
|
83
98
|
}
|
|
@@ -2,12 +2,20 @@ import { parseMarkup } from "./parseMarkup.js";
|
|
|
2
2
|
//#region ../../packages/player/src/controls/icons.ts
|
|
3
3
|
/**
|
|
4
4
|
* Wraps an icon's shapes in a 24-unit square. Shapes are stroked in `currentColor` unless they
|
|
5
|
-
* say otherwise, so every icon takes the controls' text colour.
|
|
5
|
+
* say otherwise, so every icon takes the controls' text colour. The stroke is 1.8 units: a line
|
|
6
|
+
* 1.5 pixels wide at the 20 pixels the bar draws its icons at.
|
|
6
7
|
*/
|
|
7
8
|
function icon(shapes) {
|
|
8
|
-
return `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="
|
|
9
|
+
return `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">${shapes}</svg>`;
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Shapes drawn on a 20-unit grid with a 1.5-unit stroke, scaled onto the 24-unit one: the stroke
|
|
13
|
+
* scales with them to the 1.8 units of every other icon.
|
|
14
|
+
*/
|
|
15
|
+
function fromTwentyUnitGrid(shapes) {
|
|
16
|
+
return icon(`<g transform="scale(1.2)" stroke-width="1.5">${shapes}</g>`);
|
|
17
|
+
}
|
|
18
|
+
var SPEAKER = "<path d=\"M4 9.5v5h3.5L12 19V5L7.5 9.5z\"/>";
|
|
11
19
|
/**
|
|
12
20
|
* The controls' icons, drawn as inline SVG in `currentColor` only: a text glyph is left to the
|
|
13
21
|
* platform's fonts, some of which draw it as a colour emoji.
|
|
@@ -15,13 +23,29 @@ var SPEAKER = "<path d=\"M4 9.5v5h3.5L12 19V5L7.5 9.5z\" fill=\"currentColor\"/>
|
|
|
15
23
|
var ICONS = {
|
|
16
24
|
play: icon("<path d=\"M8 5v14l11-7z\" fill=\"currentColor\"/>"),
|
|
17
25
|
pause: icon("<rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/><rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/>"),
|
|
18
|
-
stop: icon("<rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"1.5\" fill=\"currentColor\" stroke=\"none\"/>"),
|
|
19
26
|
sound: icon(`${SPEAKER}<path d="M15.5 9a4 4 0 0 1 0 6"/><path d="M18.5 6a8 8 0 0 1 0 12"/>`),
|
|
20
27
|
muted: icon(`${SPEAKER}<path d="M16 9.5l5 5M21 9.5l-5 5"/>`),
|
|
21
28
|
resetView: icon("<path d=\"M4.5 12a7.5 7.5 0 1 0 2.2-5.3\"/><path d=\"M4.5 4.5v4h4\"/>"),
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
fullscreen: icon("<path d=\"M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5\"/>"),
|
|
30
|
+
close: icon("<path d=\"M6 6l12 12M18 6L6 18\"/>")
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Each stabilization mode's icon, which its menu shows beside it and its button while it is in
|
|
34
|
+
* effect: records, so a mode cannot be left without one.
|
|
35
|
+
*/
|
|
36
|
+
var STABILIZATION_ICONS = {
|
|
37
|
+
off: fromTwentyUnitGrid("<path d=\"M2 11l2.5-3 3 4.5 3-6 3 5 2-2.5L18 11\"/>"),
|
|
38
|
+
lock: fromTwentyUnitGrid("<rect x=\"4.5\" y=\"9\" width=\"11\" height=\"8\" rx=\"1.5\"/><path d=\"M7 9V6.5a3 3 0 016 0V9\"/>"),
|
|
39
|
+
horizon: fromTwentyUnitGrid("<circle cx=\"10\" cy=\"10\" r=\"7\"/><path d=\"M3 10h14M8 13h4\"/>"),
|
|
40
|
+
follow: fromTwentyUnitGrid("<path d=\"M3 15c3.5 0 4-9 8.5-9 2 0 3 1 4.5 1\"/><path d=\"M14 4.5L16.5 7 14 9.5\"/>")
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Each view mode's icon, as the stabilization modes have theirs.
|
|
44
|
+
*/
|
|
45
|
+
var VIEW_MODE_ICONS = {
|
|
46
|
+
"raw-lenses": fromTwentyUnitGrid("<circle cx=\"6\" cy=\"10\" r=\"4.25\"/><circle cx=\"14\" cy=\"10\" r=\"4.25\"/>"),
|
|
47
|
+
equirectangular: fromTwentyUnitGrid("<rect x=\"2\" y=\"5\" width=\"16\" height=\"10\" rx=\"1.5\"/><path d=\"M2 10h16M7.5 5c-1.2 3.3-1.2 6.7 0 10M12.5 5c1.2 3.3 1.2 6.7 0 10\"/>"),
|
|
48
|
+
normal: fromTwentyUnitGrid("<path d=\"M10 16L3.2 7.2a10 10 0 0113.6 0z\"/>")
|
|
25
49
|
};
|
|
26
50
|
var parsedIcons = /* @__PURE__ */ new Map();
|
|
27
51
|
/**
|
|
@@ -37,4 +61,4 @@ function iconNode(name) {
|
|
|
37
61
|
return parsed.cloneNode(true);
|
|
38
62
|
}
|
|
39
63
|
//#endregion
|
|
40
|
-
export { ICONS, iconNode };
|
|
64
|
+
export { ICONS, STABILIZATION_ICONS, VIEW_MODE_ICONS, iconNode };
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
//#region ../../packages/player/src/controls/messages.ts
|
|
2
|
+
var STABILIZATION_WORDS = {
|
|
3
|
+
names: "stabilizationModes",
|
|
4
|
+
descriptions: "stabilizationModeDescriptions"
|
|
5
|
+
};
|
|
6
|
+
var VIEW_MODE_WORDS = {
|
|
7
|
+
names: "viewModes",
|
|
8
|
+
descriptions: "viewModeDescriptions"
|
|
9
|
+
};
|
|
10
|
+
var CHOICE_TABLES = new Set([STABILIZATION_WORDS, VIEW_MODE_WORDS].flatMap((words) => [words.names, words.descriptions]));
|
|
2
11
|
var UNREACHABLE = "The video could not be loaded.";
|
|
3
12
|
var UNSUPPORTED_BROWSER = "This browser cannot play this video.";
|
|
4
13
|
var UNREADABLE = "This file cannot be played.";
|
|
@@ -9,7 +18,6 @@ var DEFAULT_MESSAGES = {
|
|
|
9
18
|
loading: "Loading",
|
|
10
19
|
play: "Play",
|
|
11
20
|
pause: "Pause",
|
|
12
|
-
stop: "Stop",
|
|
13
21
|
seek: "Seek",
|
|
14
22
|
position: "{time} of {duration}",
|
|
15
23
|
mute: "Mute",
|
|
@@ -17,7 +25,8 @@ var DEFAULT_MESSAGES = {
|
|
|
17
25
|
resetView: "Reset view",
|
|
18
26
|
stabilization: "Stabilization",
|
|
19
27
|
viewMode: "View",
|
|
20
|
-
fullscreen: "Fullscreen"
|
|
28
|
+
fullscreen: "Fullscreen",
|
|
29
|
+
close: "Close"
|
|
21
30
|
},
|
|
22
31
|
stabilizationModes: {
|
|
23
32
|
off: "Off",
|
|
@@ -25,11 +34,22 @@ var DEFAULT_MESSAGES = {
|
|
|
25
34
|
horizon: "Horizon",
|
|
26
35
|
follow: "Follow"
|
|
27
36
|
},
|
|
37
|
+
stabilizationModeDescriptions: {
|
|
38
|
+
off: "Footage as the camera moved",
|
|
39
|
+
lock: "Orientation fixed to the world",
|
|
40
|
+
horizon: "Level horizon, follows heading",
|
|
41
|
+
follow: "Follows turns, smooths out shake"
|
|
42
|
+
},
|
|
28
43
|
viewModes: {
|
|
29
44
|
"raw-lenses": "Raw lenses",
|
|
30
45
|
equirectangular: "Equirectangular",
|
|
31
46
|
normal: "Normal"
|
|
32
47
|
},
|
|
48
|
+
viewModeDescriptions: {
|
|
49
|
+
"raw-lenses": "Both fisheye images as recorded",
|
|
50
|
+
equirectangular: "The full 360° frame, unwrapped",
|
|
51
|
+
normal: "Standard view, drag to look around"
|
|
52
|
+
},
|
|
33
53
|
errors: {
|
|
34
54
|
"binary-out-of-bounds": UNREADABLE,
|
|
35
55
|
"binary-unsafe-integer": UNREADABLE,
|
|
@@ -69,7 +89,9 @@ function messagesWith(overrides) {
|
|
|
69
89
|
return {
|
|
70
90
|
labels: tableWith(DEFAULT_MESSAGES.labels, given["labels"]),
|
|
71
91
|
stabilizationModes: tableWith(DEFAULT_MESSAGES.stabilizationModes, given["stabilizationModes"]),
|
|
92
|
+
stabilizationModeDescriptions: tableWith(DEFAULT_MESSAGES.stabilizationModeDescriptions, given["stabilizationModeDescriptions"]),
|
|
72
93
|
viewModes: tableWith(DEFAULT_MESSAGES.viewModes, given["viewModes"]),
|
|
94
|
+
viewModeDescriptions: tableWith(DEFAULT_MESSAGES.viewModeDescriptions, given["viewModeDescriptions"]),
|
|
73
95
|
errors: tableWith(DEFAULT_MESSAGES.errors, given["errors"])
|
|
74
96
|
};
|
|
75
97
|
}
|
|
@@ -77,7 +99,7 @@ function isLabelName(name) {
|
|
|
77
99
|
return name !== null && Object.hasOwn(DEFAULT_MESSAGES.labels, name);
|
|
78
100
|
}
|
|
79
101
|
function isChoiceTable(name) {
|
|
80
|
-
return name
|
|
102
|
+
return name !== null && CHOICE_TABLES.has(name);
|
|
81
103
|
}
|
|
82
104
|
function tableWith(defaults, overrides) {
|
|
83
105
|
if (!isRecord(overrides)) return defaults;
|
|
@@ -91,4 +113,4 @@ function isRecord(value) {
|
|
|
91
113
|
return typeof value === "object" && value !== null;
|
|
92
114
|
}
|
|
93
115
|
//#endregion
|
|
94
|
-
export { DEFAULT_MESSAGES, isChoiceTable, isLabelName, messagesWith };
|
|
116
|
+
export { DEFAULT_MESSAGES, STABILIZATION_WORDS, VIEW_MODE_WORDS, isChoiceTable, isLabelName, messagesWith };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { clamp } from "../../../core/src/shared/math/clamp.js";
|
|
2
|
+
import "../../../core/src/index.js";
|
|
3
|
+
//#region ../../packages/player/src/controls/sliderFill.ts
|
|
4
|
+
/**
|
|
5
|
+
* Tells the stylesheet how far a range input is filled, as a fraction in `--fill`: only Firefox
|
|
6
|
+
* styles the filled part of a range itself. Set through the CSSOM, which a Content Security
|
|
7
|
+
* Policy does not govern as it governs style attributes.
|
|
8
|
+
*/
|
|
9
|
+
function showSliderFill(slider) {
|
|
10
|
+
const minimum = Number(slider.min);
|
|
11
|
+
const span = Number(slider.max) - minimum;
|
|
12
|
+
const fraction = span > 0 ? (Number(slider.value) - minimum) / span : 0;
|
|
13
|
+
slider.style.setProperty("--fill", String(clamp(fraction, 0, 1)));
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { showSliderFill };
|
|
@@ -49,6 +49,13 @@ var ElementLoads = class {
|
|
|
49
49
|
this.files = void 0;
|
|
50
50
|
this.schedule();
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* How the recording is fetched changed: one named by URL loads again, read the new way; files
|
|
54
|
+
* handed in are not fetched, so they play on.
|
|
55
|
+
*/
|
|
56
|
+
requestChanged() {
|
|
57
|
+
if (this.files === void 0) this.schedule();
|
|
58
|
+
}
|
|
52
59
|
loadFiles(files) {
|
|
53
60
|
this.files = files;
|
|
54
61
|
this.schedule();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { defineBooleanProperties, defineKeywordProperties, defineStringProperties, propertyNameOf } from "./reflectedProperties.js";
|
|
1
|
+
import { defineBooleanProperties, defineKeywordProperties, defineNullableKeywordProperties, defineStringProperties, propertyNameOf } from "./reflectedProperties.js";
|
|
2
2
|
import { ensureFinite } from "../player/ensureFinite.js";
|
|
3
3
|
import { bindKeyboard } from "../controls/keyboard.js";
|
|
4
4
|
import { ViewGestures } from "../controls/ViewGestures.js";
|
|
5
5
|
import { togglePlayback } from "../controls/customControls.js";
|
|
6
6
|
import { createBrowserPlayer } from "../browserPlayer.js";
|
|
7
|
-
import { OBSERVED_ATTRIBUTES, PlaybackAttribute, SourceAttribute, ViewAttribute } from "./attributeNames.js";
|
|
8
|
-
import { GAIN_MATCH, PRELOAD, unreadableAngleWarning, viewAfterAttribute } from "./attributes.js";
|
|
7
|
+
import { OBSERVED_ATTRIBUTES, PlaybackAttribute, RequestAttribute, SourceAttribute, ViewAttribute } from "./attributeNames.js";
|
|
8
|
+
import { CROSS_ORIGIN, GAIN_MATCH, PRELOAD, unreadableAngleWarning, viewAfterAttribute } from "./attributes.js";
|
|
9
9
|
import { ElementLoads } from "./ElementLoads.js";
|
|
10
10
|
import { FullscreenToggle } from "./FullscreenToggle.js";
|
|
11
11
|
import { IdleWatcher } from "./IdleWatcher.js";
|
|
@@ -27,8 +27,10 @@ import { Wording } from "../controls/Wording.js";
|
|
|
27
27
|
*/
|
|
28
28
|
var STRING_ATTRIBUTES = [...Object.values(SourceAttribute), PlaybackAttribute.Poster];
|
|
29
29
|
var KEYWORD_ATTRIBUTES = [PRELOAD, GAIN_MATCH];
|
|
30
|
+
var NULLABLE_KEYWORD_ATTRIBUTES = [CROSS_ORIGIN];
|
|
30
31
|
var BOOLEAN_ATTRIBUTES = [PlaybackAttribute.Autoplay, PlaybackAttribute.Controls];
|
|
31
32
|
var SOURCE_ATTRIBUTES = Object.values(SourceAttribute);
|
|
33
|
+
var REQUEST_ATTRIBUTES = Object.values(RequestAttribute);
|
|
32
34
|
/**
|
|
33
35
|
* The properties a page may set before the element is defined, all kept for it.
|
|
34
36
|
*/
|
|
@@ -38,6 +40,7 @@ var PUBLIC_PROPERTIES = [
|
|
|
38
40
|
...KEYWORD_ATTRIBUTES.map(({ name }) => name),
|
|
39
41
|
...BOOLEAN_ATTRIBUTES
|
|
40
42
|
].map((name) => propertyNameOf(name)),
|
|
43
|
+
...NULLABLE_KEYWORD_ATTRIBUTES.map(({ property }) => property),
|
|
41
44
|
...LIVE_SETTING_NAMES,
|
|
42
45
|
"currentTime",
|
|
43
46
|
"messages"
|
|
@@ -66,6 +69,7 @@ var GyroViewElement = class extends TypedEventElement {
|
|
|
66
69
|
this.earlyProperties = takeEarlyProperties(this, PUBLIC_PROPERTIES);
|
|
67
70
|
defineStringProperties(this, STRING_ATTRIBUTES);
|
|
68
71
|
defineKeywordProperties(this, KEYWORD_ATTRIBUTES);
|
|
72
|
+
defineNullableKeywordProperties(this, NULLABLE_KEYWORD_ATTRIBUTES);
|
|
69
73
|
defineBooleanProperties(this, BOOLEAN_ATTRIBUTES);
|
|
70
74
|
const shadow = this.attachShadow({ mode: "open" });
|
|
71
75
|
renderShadowTree(shadow);
|
|
@@ -158,6 +162,7 @@ var GyroViewElement = class extends TypedEventElement {
|
|
|
158
162
|
}
|
|
159
163
|
attributeChangedCallback(name, _previous, value) {
|
|
160
164
|
if (SOURCE_ATTRIBUTES.includes(name)) this.loads.sourceChanged();
|
|
165
|
+
else if (REQUEST_ATTRIBUTES.includes(name)) this.loads.requestChanged();
|
|
161
166
|
else if (VIEW_ATTRIBUTES.includes(name)) {
|
|
162
167
|
this.player.setView(viewAfterAttribute(this.player.view, name, value));
|
|
163
168
|
const warning = unreadableAngleWarning(name, value);
|
|
@@ -11,6 +11,16 @@ var SourceAttribute = {
|
|
|
11
11
|
Src: "src",
|
|
12
12
|
Src2: "src2"
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* `Request` ones say how the recording is fetched; changing any of them reloads a recording
|
|
16
|
+
* named by URL. The iframe embed carries none of them (ADR 0027).
|
|
17
|
+
*/
|
|
18
|
+
var RequestAttribute = {
|
|
19
|
+
/**
|
|
20
|
+
* `use-credentials` fetches with the visitor's cookies, as a media element's `crossorigin`
|
|
21
|
+
* does; `anonymous`, the default, sends them to the page's own origin only.
|
|
22
|
+
*/
|
|
23
|
+
CrossOrigin: "crossorigin" };
|
|
14
24
|
var ViewAttribute = {
|
|
15
25
|
FieldOfView: "fov",
|
|
16
26
|
Yaw: "yaw",
|
|
@@ -44,8 +54,9 @@ var PlaybackAttribute = {
|
|
|
44
54
|
};
|
|
45
55
|
var OBSERVED_ATTRIBUTES = [
|
|
46
56
|
...Object.values(SourceAttribute),
|
|
57
|
+
...Object.values(RequestAttribute),
|
|
47
58
|
...Object.values(ViewAttribute),
|
|
48
59
|
...Object.values(PlaybackAttribute)
|
|
49
60
|
];
|
|
50
61
|
//#endregion
|
|
51
|
-
export { OBSERVED_ATTRIBUTES, PlaybackAttribute, SourceAttribute, ViewAttribute };
|
|
62
|
+
export { OBSERVED_ATTRIBUTES, PlaybackAttribute, RequestAttribute, SourceAttribute, ViewAttribute };
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import { keywordOf } from "./reflectedProperties.js";
|
|
2
|
-
import { PlaybackAttribute, SourceAttribute, ViewAttribute } from "./attributeNames.js";
|
|
2
|
+
import { PlaybackAttribute, RequestAttribute, SourceAttribute, ViewAttribute } from "./attributeNames.js";
|
|
3
3
|
//#region ../../packages/player/src/element/attributes.ts
|
|
4
4
|
/**
|
|
5
5
|
* The source the attributes describe, or nothing when `src` is absent. URLs resolve against
|
|
6
|
-
* the document, as an image's would.
|
|
6
|
+
* the document, as an image's would, and both files of a pair are read alike.
|
|
7
7
|
*/
|
|
8
8
|
function sourceFromAttributes(read, baseUrl) {
|
|
9
|
-
const
|
|
9
|
+
const reading = readingOf(read(RequestAttribute.CrossOrigin));
|
|
10
|
+
const main = urlInputOf(read(SourceAttribute.Src), baseUrl, reading);
|
|
10
11
|
return main && {
|
|
11
12
|
main,
|
|
12
|
-
second: urlInputOf(read(SourceAttribute.Src2), baseUrl)
|
|
13
|
+
second: urlInputOf(read(SourceAttribute.Src2), baseUrl, reading)
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
|
-
function urlInputOf(value, baseUrl) {
|
|
16
|
+
function urlInputOf(value, baseUrl, reading) {
|
|
16
17
|
const trimmed = value?.trim() ?? "";
|
|
17
|
-
return trimmed === "" ? void 0 : {
|
|
18
|
+
return trimmed === "" ? void 0 : {
|
|
19
|
+
url: resolvedOrAsWritten(trimmed, baseUrl),
|
|
20
|
+
...reading
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* `use-credentials` sends the visitor's cookies to any origin; anything else leaves fetch's own
|
|
25
|
+
* default, which sends them to the page's origin only, as `anonymous` does for a media element.
|
|
26
|
+
*/
|
|
27
|
+
function readingOf(crossOrigin) {
|
|
28
|
+
return keywordOf(CROSS_ORIGIN, crossOrigin) === "use-credentials" ? { credentials: "include" } : {};
|
|
18
29
|
}
|
|
19
30
|
/**
|
|
20
31
|
* The URL resolved against the document; one that does not parse goes on as written, so the
|
|
@@ -91,6 +102,17 @@ var GAIN_MATCH = {
|
|
|
91
102
|
keywords: ["on", "off"],
|
|
92
103
|
fallback: "on"
|
|
93
104
|
};
|
|
105
|
+
/**
|
|
106
|
+
* `use-credentials` fetches the recording with the visitor's cookies; `anonymous`, in effect for
|
|
107
|
+
* an empty or unknown value, without them across origins. Absent, its property reads `null`, as
|
|
108
|
+
* a media element's `crossOrigin` does.
|
|
109
|
+
*/
|
|
110
|
+
var CROSS_ORIGIN = {
|
|
111
|
+
name: RequestAttribute.CrossOrigin,
|
|
112
|
+
property: "crossOrigin",
|
|
113
|
+
keywords: ["anonymous", "use-credentials"],
|
|
114
|
+
fallback: "anonymous"
|
|
115
|
+
};
|
|
94
116
|
function shouldPreload(value) {
|
|
95
117
|
return keywordOf(PRELOAD, value) === "auto";
|
|
96
118
|
}
|
|
@@ -98,4 +120,4 @@ function shouldMatchGains(value) {
|
|
|
98
120
|
return keywordOf(GAIN_MATCH, value) === "on";
|
|
99
121
|
}
|
|
100
122
|
//#endregion
|
|
101
|
-
export { GAIN_MATCH, PRELOAD, ignoredChoiceWarning, isBooleanAttributeSet, parseNumber, shouldMatchGains, shouldPreload, sourceFromAttributes, unreadableAngleWarning, viewAfterAttribute };
|
|
123
|
+
export { CROSS_ORIGIN, GAIN_MATCH, PRELOAD, ignoredChoiceWarning, isBooleanAttributeSet, parseNumber, shouldMatchGains, shouldPreload, sourceFromAttributes, unreadableAngleWarning, viewAfterAttribute };
|
|
@@ -48,6 +48,24 @@ function defineKeywordProperties(element, attributes) {
|
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
+
* Enumerated attributes whose property reads `null` while the attribute is absent and the keyword
|
|
52
|
+
* in effect otherwise, the fallback for an empty or unknown value, as the DOM reflects
|
|
53
|
+
* `crossOrigin`; it writes the attribute.
|
|
54
|
+
*/
|
|
55
|
+
function defineNullableKeywordProperties(element, attributes) {
|
|
56
|
+
for (const attribute of attributes) Object.defineProperty(element, attribute.property, {
|
|
57
|
+
configurable: true,
|
|
58
|
+
enumerable: true,
|
|
59
|
+
get() {
|
|
60
|
+
const value = this.getAttribute(attribute.name);
|
|
61
|
+
return value === null ? null : keywordOf(attribute, value);
|
|
62
|
+
},
|
|
63
|
+
set(value) {
|
|
64
|
+
writeAttribute(this, attribute.name, value);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
51
69
|
* The keyword an enumerated attribute's value names, ignoring case and spacing; the fallback for
|
|
52
70
|
* an absent or unknown one.
|
|
53
71
|
*/
|
|
@@ -75,4 +93,4 @@ function propertyNameOf(attribute) {
|
|
|
75
93
|
return attribute.replaceAll(HYPHENATED_LETTER, (_match, letter) => letter.toUpperCase());
|
|
76
94
|
}
|
|
77
95
|
//#endregion
|
|
78
|
-
export { defineBooleanProperties, defineKeywordProperties, defineStringProperties, keywordOf, propertyNameOf, writeAttribute };
|
|
96
|
+
export { defineBooleanProperties, defineKeywordProperties, defineNullableKeywordProperties, defineStringProperties, keywordOf, propertyNameOf, writeAttribute };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
//#region ../../packages/player/src/element/styles.css?raw
|
|
2
|
-
var styles_default = ":host {\n --gyro-view-accent: #
|
|
2
|
+
var styles_default = ":host {\n --gyro-view-accent: #fff;\n --gyro-view-controls-background: rgb(0 0 0 / 60%);\n --gyro-view-menu-background: rgb(24 24 23 / 92%);\n --gyro-view-text: #fff;\n --gyro-view-font: system-ui, -apple-system, 'Segoe UI', sans-serif;\n --gyro-view-radius: 8px;\n\n display: block;\n position: relative;\n width: 100%;\n aspect-ratio: 16 / 9;\n background: #000;\n color: var(--gyro-view-text);\n font-family: var(--gyro-view-font);\n overflow: hidden;\n user-select: none;\n -webkit-user-select: none;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n:host(:focus-visible) {\n outline: 2px solid var(--gyro-view-accent);\n outline-offset: 2px;\n}\n\n/*\n * Pinned over the page where the Fullscreen API is refused (iPhone Safari). Like the browser's\n * own fullscreen rules it overrides the size, margins and place the page gave the element, and\n * the insets size it to the visible viewport, where 100vh would reach under Safari's toolbars.\n */\n:host([data-fill]) {\n position: fixed !important;\n inset: 0 !important;\n box-sizing: border-box !important;\n width: auto !important;\n height: auto !important;\n min-width: 0 !important;\n min-height: 0 !important;\n max-width: none !important;\n max-height: none !important;\n margin: 0 !important;\n transform: none !important;\n aspect-ratio: auto !important;\n z-index: 2147483000 !important;\n}\n\n:host(:fullscreen) {\n width: 100%;\n height: 100%;\n aspect-ratio: auto;\n}\n\n/* The size container the controls fit themselves to: the player's size, not the page's. Its\n size never depends on its content, which size containment requires. */\n.stage {\n position: absolute;\n inset: 0;\n container-type: size;\n}\n\ncanvas {\n display: block;\n width: 100%;\n height: 100%;\n touch-action: none;\n}\n\n.poster {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n pointer-events: none;\n}\n\n:host([data-has-frame]) .poster,\n:host(:not([poster])) .poster {\n display: none;\n}\n\n.overlay {\n position: absolute;\n inset: 0;\n display: none;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n gap: 0.5rem;\n padding: 1rem;\n text-align: center;\n background: rgb(0 0 0 / 45%);\n}\n\n:host([data-status='loading']) .overlay.loading,\n:host([data-status='buffering']) .overlay.loading,\n:host([data-status='error']) .overlay.error {\n display: flex;\n}\n\n:host([data-status='buffering']) .overlay.loading {\n background: transparent;\n pointer-events: none;\n}\n\n.spinner {\n width: 2.5rem;\n height: 2.5rem;\n border: 3px solid rgb(255 255 255 / 25%);\n border-top-color: var(--gyro-view-accent);\n border-radius: 50%;\n animation: gyro-view-spin 0.9s linear infinite;\n}\n\n@keyframes gyro-view-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* A viewer who asked for less motion still sees that it loads, three times slower. */\n@media (prefers-reduced-motion: reduce) {\n .spinner {\n animation-duration: 2.7s;\n }\n}\n\n.error-code {\n font-size: 0.8rem;\n opacity: 0.7;\n}\n";
|
|
3
3
|
//#endregion
|
|
4
4
|
export { styles_default as default };
|