@blcklab/anyo-player 0.5.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 (181) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +304 -0
  4. package/dist/createAnyoPlayer.d.ts +3 -0
  5. package/dist/createAnyoPlayer.d.ts.map +1 -0
  6. package/dist/createAnyoPlayer.js +5 -0
  7. package/dist/createAnyoPlayer.js.map +1 -0
  8. package/dist/element-define.d.ts +2 -0
  9. package/dist/element-define.d.ts.map +1 -0
  10. package/dist/element-define.js +4 -0
  11. package/dist/element-define.js.map +1 -0
  12. package/dist/element.d.ts +66 -0
  13. package/dist/element.d.ts.map +1 -0
  14. package/dist/element.js +363 -0
  15. package/dist/element.js.map +1 -0
  16. package/dist/errors.d.ts +11 -0
  17. package/dist/errors.d.ts.map +1 -0
  18. package/dist/errors.js +21 -0
  19. package/dist/errors.js.map +1 -0
  20. package/dist/index.d.ts +13 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +10 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/inputBindings.d.ts +3 -0
  25. package/dist/inputBindings.d.ts.map +1 -0
  26. package/dist/inputBindings.js +3 -0
  27. package/dist/inputBindings.js.map +1 -0
  28. package/dist/internal/AccessibilityController.d.ts +50 -0
  29. package/dist/internal/AccessibilityController.d.ts.map +1 -0
  30. package/dist/internal/AccessibilityController.js +212 -0
  31. package/dist/internal/AccessibilityController.js.map +1 -0
  32. package/dist/internal/AnyoPlayerCore.d.ts +357 -0
  33. package/dist/internal/AnyoPlayerCore.d.ts.map +1 -0
  34. package/dist/internal/AnyoPlayerCore.js +3880 -0
  35. package/dist/internal/AnyoPlayerCore.js.map +1 -0
  36. package/dist/internal/AudioController.d.ts +37 -0
  37. package/dist/internal/AudioController.d.ts.map +1 -0
  38. package/dist/internal/AudioController.js +167 -0
  39. package/dist/internal/AudioController.js.map +1 -0
  40. package/dist/internal/CaptionController.d.ts +16 -0
  41. package/dist/internal/CaptionController.d.ts.map +1 -0
  42. package/dist/internal/CaptionController.js +83 -0
  43. package/dist/internal/CaptionController.js.map +1 -0
  44. package/dist/internal/ContainerHost.d.ts +17 -0
  45. package/dist/internal/ContainerHost.d.ts.map +1 -0
  46. package/dist/internal/ContainerHost.js +110 -0
  47. package/dist/internal/ContainerHost.js.map +1 -0
  48. package/dist/internal/DesktopInputController.d.ts +80 -0
  49. package/dist/internal/DesktopInputController.d.ts.map +1 -0
  50. package/dist/internal/DesktopInputController.js +367 -0
  51. package/dist/internal/DesktopInputController.js.map +1 -0
  52. package/dist/internal/EmbeddingController.d.ts +33 -0
  53. package/dist/internal/EmbeddingController.d.ts.map +1 -0
  54. package/dist/internal/EmbeddingController.js +161 -0
  55. package/dist/internal/EmbeddingController.js.map +1 -0
  56. package/dist/internal/EventHub.d.ts +7 -0
  57. package/dist/internal/EventHub.d.ts.map +1 -0
  58. package/dist/internal/EventHub.js +27 -0
  59. package/dist/internal/EventHub.js.map +1 -0
  60. package/dist/internal/FrameworkAdapterController.d.ts +46 -0
  61. package/dist/internal/FrameworkAdapterController.d.ts.map +1 -0
  62. package/dist/internal/FrameworkAdapterController.js +165 -0
  63. package/dist/internal/FrameworkAdapterController.js.map +1 -0
  64. package/dist/internal/FullscreenController.d.ts +29 -0
  65. package/dist/internal/FullscreenController.d.ts.map +1 -0
  66. package/dist/internal/FullscreenController.js +122 -0
  67. package/dist/internal/FullscreenController.js.map +1 -0
  68. package/dist/internal/GamepadController.d.ts +54 -0
  69. package/dist/internal/GamepadController.d.ts.map +1 -0
  70. package/dist/internal/GamepadController.js +276 -0
  71. package/dist/internal/GamepadController.js.map +1 -0
  72. package/dist/internal/InputBindingsController.d.ts +34 -0
  73. package/dist/internal/InputBindingsController.d.ts.map +1 -0
  74. package/dist/internal/InputBindingsController.js +223 -0
  75. package/dist/internal/InputBindingsController.js.map +1 -0
  76. package/dist/internal/InteractionPresentationController.d.ts +38 -0
  77. package/dist/internal/InteractionPresentationController.d.ts.map +1 -0
  78. package/dist/internal/InteractionPresentationController.js +208 -0
  79. package/dist/internal/InteractionPresentationController.js.map +1 -0
  80. package/dist/internal/MapController.d.ts +16 -0
  81. package/dist/internal/MapController.d.ts.map +1 -0
  82. package/dist/internal/MapController.js +68 -0
  83. package/dist/internal/MapController.js.map +1 -0
  84. package/dist/internal/PerformanceController.d.ts +94 -0
  85. package/dist/internal/PerformanceController.d.ts.map +1 -0
  86. package/dist/internal/PerformanceController.js +455 -0
  87. package/dist/internal/PerformanceController.js.map +1 -0
  88. package/dist/internal/PlayerCameraController.d.ts +79 -0
  89. package/dist/internal/PlayerCameraController.d.ts.map +1 -0
  90. package/dist/internal/PlayerCameraController.js +551 -0
  91. package/dist/internal/PlayerCameraController.js.map +1 -0
  92. package/dist/internal/ResponsiveController.d.ts +37 -0
  93. package/dist/internal/ResponsiveController.d.ts.map +1 -0
  94. package/dist/internal/ResponsiveController.js +143 -0
  95. package/dist/internal/ResponsiveController.js.map +1 -0
  96. package/dist/internal/RuntimeFactory.d.ts +42 -0
  97. package/dist/internal/RuntimeFactory.d.ts.map +1 -0
  98. package/dist/internal/RuntimeFactory.js +93 -0
  99. package/dist/internal/RuntimeFactory.js.map +1 -0
  100. package/dist/internal/ScreenshotController.d.ts +8 -0
  101. package/dist/internal/ScreenshotController.d.ts.map +1 -0
  102. package/dist/internal/ScreenshotController.js +47 -0
  103. package/dist/internal/ScreenshotController.js.map +1 -0
  104. package/dist/internal/SessionController.d.ts +44 -0
  105. package/dist/internal/SessionController.d.ts.map +1 -0
  106. package/dist/internal/SessionController.js +411 -0
  107. package/dist/internal/SessionController.js.map +1 -0
  108. package/dist/internal/SourceResolver.d.ts +31 -0
  109. package/dist/internal/SourceResolver.d.ts.map +1 -0
  110. package/dist/internal/SourceResolver.js +362 -0
  111. package/dist/internal/SourceResolver.js.map +1 -0
  112. package/dist/internal/StateMachine.d.ts +10 -0
  113. package/dist/internal/StateMachine.d.ts.map +1 -0
  114. package/dist/internal/StateMachine.js +35 -0
  115. package/dist/internal/StateMachine.js.map +1 -0
  116. package/dist/internal/StyleController.d.ts +14 -0
  117. package/dist/internal/StyleController.d.ts.map +1 -0
  118. package/dist/internal/StyleController.js +116 -0
  119. package/dist/internal/StyleController.js.map +1 -0
  120. package/dist/internal/TelemetryController.d.ts +28 -0
  121. package/dist/internal/TelemetryController.d.ts.map +1 -0
  122. package/dist/internal/TelemetryController.js +113 -0
  123. package/dist/internal/TelemetryController.js.map +1 -0
  124. package/dist/internal/TouchInputController.d.ts +73 -0
  125. package/dist/internal/TouchInputController.d.ts.map +1 -0
  126. package/dist/internal/TouchInputController.js +379 -0
  127. package/dist/internal/TouchInputController.js.map +1 -0
  128. package/dist/internal/VfxController.d.ts +11 -0
  129. package/dist/internal/VfxController.d.ts.map +1 -0
  130. package/dist/internal/VfxController.js +23 -0
  131. package/dist/internal/VfxController.js.map +1 -0
  132. package/dist/internal/ViewPreferenceController.d.ts +40 -0
  133. package/dist/internal/ViewPreferenceController.d.ts.map +1 -0
  134. package/dist/internal/ViewPreferenceController.js +132 -0
  135. package/dist/internal/ViewPreferenceController.js.map +1 -0
  136. package/dist/internal/VisibilityController.d.ts +26 -0
  137. package/dist/internal/VisibilityController.d.ts.map +1 -0
  138. package/dist/internal/VisibilityController.js +62 -0
  139. package/dist/internal/VisibilityController.js.map +1 -0
  140. package/dist/internal/XRController.d.ts +42 -0
  141. package/dist/internal/XRController.d.ts.map +1 -0
  142. package/dist/internal/XRController.js +169 -0
  143. package/dist/internal/XRController.js.map +1 -0
  144. package/dist/internal/sourceUrls.d.ts +6 -0
  145. package/dist/internal/sourceUrls.d.ts.map +1 -0
  146. package/dist/internal/sourceUrls.js +79 -0
  147. package/dist/internal/sourceUrls.js.map +1 -0
  148. package/dist/react.d.ts +44 -0
  149. package/dist/react.d.ts.map +1 -0
  150. package/dist/react.js +115 -0
  151. package/dist/react.js.map +1 -0
  152. package/dist/session.d.ts +3 -0
  153. package/dist/session.d.ts.map +1 -0
  154. package/dist/session.js +3 -0
  155. package/dist/session.js.map +1 -0
  156. package/dist/styleText.d.ts +2 -0
  157. package/dist/styleText.d.ts.map +1 -0
  158. package/dist/styleText.js +2 -0
  159. package/dist/styleText.js.map +1 -0
  160. package/dist/styles.css +573 -0
  161. package/dist/types.d.ts +1476 -0
  162. package/dist/types.d.ts.map +1 -0
  163. package/dist/types.js +2 -0
  164. package/dist/types.js.map +1 -0
  165. package/dist/ui/PlayerUI.d.ts +193 -0
  166. package/dist/ui/PlayerUI.d.ts.map +1 -0
  167. package/dist/ui/PlayerUI.js +1201 -0
  168. package/dist/ui/PlayerUI.js.map +1 -0
  169. package/dist/vue.d.ts +97 -0
  170. package/dist/vue.d.ts.map +1 -0
  171. package/dist/vue.js +126 -0
  172. package/dist/vue.js.map +1 -0
  173. package/dist/worldCache.d.ts +16 -0
  174. package/dist/worldCache.d.ts.map +1 -0
  175. package/dist/worldCache.js +46 -0
  176. package/dist/worldCache.js.map +1 -0
  177. package/dist/worldSources.d.ts +19 -0
  178. package/dist/worldSources.d.ts.map +1 -0
  179. package/dist/worldSources.js +51 -0
  180. package/dist/worldSources.js.map +1 -0
  181. package/package.json +114 -0
@@ -0,0 +1,1201 @@
1
+ export const DEFAULT_UI_LABELS = {
2
+ loadingTitle: 'Loading world',
3
+ resolvingSource: 'Resolving world source…',
4
+ creatingRuntime: 'Starting renderer…',
5
+ loadingWorld: 'Building world…',
6
+ waitingReady: 'Preparing world runtime…',
7
+ loadingAssets: 'Loading world assets…',
8
+ replacingWorld: 'Replacing world…',
9
+ replacementFailed: 'The new world could not be loaded. The previous world is still available.',
10
+ readyWithWarnings: '{failed} asset(s) failed to load. The world is ready with warnings.',
11
+ enterTitle: 'Enter world',
12
+ enterMessage: 'Click to focus the world and enable controls.',
13
+ enterButton: 'Enter',
14
+ reenterMessage: 'Click to resume world controls.',
15
+ pointerLockFailed: 'Mouse capture was unavailable. Click to try again.',
16
+ pause: 'Pause',
17
+ resume: 'Resume',
18
+ muteAudio: 'Mute audio',
19
+ unmuteAudio: 'Unmute audio',
20
+ unlockAudio: 'Enable audio',
21
+ audioUnavailable: 'Audio control is unavailable.',
22
+ takeScreenshot: 'Take screenshot',
23
+ screenshotCaptured: 'Screenshot captured.',
24
+ screenshotFailed: 'Screenshot could not be captured.',
25
+ pauseMenuTitle: 'World paused',
26
+ pauseMenuMessage: 'Choose an action or resume the world.',
27
+ pausedTitle: 'World paused',
28
+ pausedMessage: 'Rendering and world updates are paused.',
29
+ visibilityPaused: 'The world paused while this page was hidden.',
30
+ offscreenPaused: 'The world paused while it was outside the visible page area.',
31
+ enterFullscreen: 'Enter fullscreen',
32
+ exitFullscreen: 'Exit fullscreen',
33
+ fullscreenFailed: 'Fullscreen was unavailable. Check browser or iframe permissions.',
34
+ touchMove: 'Move through the world',
35
+ touchRun: 'Run',
36
+ touchJump: 'Jump',
37
+ interactionDefault: 'Interact',
38
+ interactionAction: 'Interact',
39
+ interactionHint: 'Press {key}',
40
+ vrChecking: 'Checking VR support…',
41
+ enterVR: 'Enter VR',
42
+ exitVR: 'Exit VR',
43
+ vrUnsupported: 'VR unavailable',
44
+ vrEntering: 'Entering VR…',
45
+ vrActive: 'VR active',
46
+ vrExiting: 'Exiting VR…',
47
+ vrTrackingLost: 'Headset tracking lost. Stay still while tracking recovers.',
48
+ vrTrackingRestored: 'Headset tracking restored.',
49
+ vrInputs: '{count} XR input source(s) connected.',
50
+ vrEnterFailed: 'VR could not start. Check headset, browser, and permission state.',
51
+ vrExitFailed: 'VR could not exit cleanly.',
52
+ errorTitle: 'Unable to load world',
53
+ retry: 'Retry',
54
+ webglContextLost: 'Graphics context lost. Waiting for the browser to restore it…',
55
+ webglContextRestored: 'Graphics context restored.',
56
+ webgpuDeviceLost: 'The graphics device was lost. Reload the world to continue.',
57
+ worldControls: 'World controls',
58
+ loadingProgress: 'World loading progress',
59
+ };
60
+ const LOADING_PHASES = new Set([
61
+ 'resolving-source',
62
+ 'creating-runtime',
63
+ 'loading-world',
64
+ 'waiting-ready',
65
+ 'loading-assets',
66
+ 'replacing-world',
67
+ ]);
68
+ function phaseLabel(phase, labels) {
69
+ switch (phase) {
70
+ case 'resolving-source': return labels.resolvingSource;
71
+ case 'creating-runtime': return labels.creatingRuntime;
72
+ case 'loading-world': return labels.loadingWorld;
73
+ case 'waiting-ready': return labels.waitingReady;
74
+ case 'loading-assets': return labels.loadingAssets;
75
+ case 'replacing-world': return labels.replacingWorld;
76
+ default: return '';
77
+ }
78
+ }
79
+ function setHidden(element, hidden) {
80
+ element.hidden = hidden;
81
+ element.setAttribute('aria-hidden', hidden ? 'true' : 'false');
82
+ }
83
+ function clampRatio(value) {
84
+ if (!Number.isFinite(value))
85
+ return 0;
86
+ return Math.min(1, Math.max(0, value));
87
+ }
88
+ function formatProgress(progress) {
89
+ if (progress.total <= 0)
90
+ return 'Preparing world assets…';
91
+ const settled = progress.loaded + progress.failed;
92
+ const failureSuffix = progress.failed > 0 ? `, ${progress.failed} failed` : '';
93
+ const activeSuffix = progress.loading > 0 ? `, ${progress.loading} loading` : '';
94
+ const queuedSuffix = progress.queued > 0 ? `, ${progress.queued} queued` : '';
95
+ return `${settled} of ${progress.total} settled${failureSuffix}${activeSuffix}${queuedSuffix}`;
96
+ }
97
+ function replaceFailedToken(label, failed) {
98
+ return label.replaceAll('{failed}', String(failed));
99
+ }
100
+ function keyboardShortcutValue(code) {
101
+ if (code.startsWith('Key'))
102
+ return code.slice(3);
103
+ if (code.startsWith('Digit'))
104
+ return code.slice(5);
105
+ if (code === 'Space')
106
+ return 'Space';
107
+ return code;
108
+ }
109
+ function keyboardShortcutValues(bindings) {
110
+ return bindings
111
+ .filter(binding => binding.device === 'keyboard' && typeof binding.code === 'string')
112
+ .map(binding => keyboardShortcutValue(binding.code));
113
+ }
114
+ function createUiSlot(document, name, defaultElement) {
115
+ const slot = document.createElement('div');
116
+ slot.classList.add('anyo-player__slot');
117
+ slot.setAttribute('data-anyo-player-slot', name);
118
+ defaultElement.setAttribute('data-anyo-player-default-ui', '');
119
+ slot.appendChild(defaultElement);
120
+ return slot;
121
+ }
122
+ export class NullPlayerUI {
123
+ setPhase() { }
124
+ setProgress() { }
125
+ setReady() { }
126
+ setEntering() { }
127
+ setRunning() { }
128
+ setPaused() { }
129
+ setEnterReady() { }
130
+ setFullscreen() { }
131
+ setInteractionPrompt() { }
132
+ setReticle() { }
133
+ setVRSupport() { }
134
+ setVRSession() { }
135
+ setXRTracking() { }
136
+ setKeyboardShortcuts() { }
137
+ setAudioState() { }
138
+ setMapAttribution() { }
139
+ setCaption() { }
140
+ focusPrimaryAction() { }
141
+ setInputError() { }
142
+ setControlError() { }
143
+ setError() { }
144
+ handleDiagnostic() { }
145
+ handleWarning() { }
146
+ dispose() { }
147
+ }
148
+ export class DefaultPlayerUI {
149
+ root;
150
+ loadingPanel;
151
+ phaseText;
152
+ progressTrack;
153
+ progressFill;
154
+ progressDetails;
155
+ diagnosticPanel;
156
+ diagnosticMessage;
157
+ reticle;
158
+ interactionPrompt;
159
+ interactionPromptTitle;
160
+ interactionPromptMessage;
161
+ interactionPromptDescription;
162
+ interactionPromptHint;
163
+ interactionPromptAction;
164
+ enterPanel;
165
+ enterMessage;
166
+ enterButton;
167
+ pausedPanel;
168
+ pausedMessage;
169
+ pauseMenuActions;
170
+ pauseResumeButton;
171
+ pauseAudioButton;
172
+ pauseFullscreenButton;
173
+ pauseScreenshotButton;
174
+ controls;
175
+ pauseButton;
176
+ audioButton;
177
+ fullscreenButton;
178
+ vrButton;
179
+ xrStatus;
180
+ xrStatusMessage;
181
+ errorPanel;
182
+ mapAttribution;
183
+ captionPanel;
184
+ captionSpeaker;
185
+ captionText;
186
+ errorMessage;
187
+ retryButton;
188
+ labels;
189
+ adapter;
190
+ slots;
191
+ onAdapterError;
192
+ adapterCleanup = null;
193
+ adapterMounted = false;
194
+ options;
195
+ onRetry;
196
+ onEnter;
197
+ onPauseToggle;
198
+ onFullscreenToggle;
199
+ onVRToggle;
200
+ onInteract;
201
+ onAudioToggle;
202
+ onScreenshot;
203
+ pauseMenu;
204
+ keyboardNavigation;
205
+ focusManagement;
206
+ isKeyboardModality;
207
+ diagnosticTimer = null;
208
+ paused = false;
209
+ fullscreen = false;
210
+ vrSupport = 'disabled';
211
+ vrSession = 'idle';
212
+ xrTracking = 'unavailable';
213
+ xrInputCount = 0;
214
+ audioState;
215
+ currentPhase = 'idle';
216
+ currentProgress = { queued: 0, loading: 0, loaded: 0, failed: 0, total: 0, ratio: 1 };
217
+ currentReadyStatus = null;
218
+ currentPauseReason = null;
219
+ currentInteractionPrompt = {
220
+ visible: false,
221
+ text: '',
222
+ title: null,
223
+ description: null,
224
+ actionLabel: null,
225
+ inputHint: null,
226
+ ariaLabel: null,
227
+ actionAvailable: false,
228
+ trigger: null,
229
+ entityId: null,
230
+ primitiveId: null,
231
+ };
232
+ currentReticle = { visible: false, active: false };
233
+ currentCaption = {
234
+ visible: false,
235
+ text: '',
236
+ speaker: null,
237
+ language: null,
238
+ startedAt: null,
239
+ expiresAt: null,
240
+ };
241
+ currentError = null;
242
+ currentDiagnostic = null;
243
+ disposed = false;
244
+ constructor(container, options, onRetry, onEnter, onPauseToggle, onFullscreenToggle, onVRToggle, onInteract, onAudioToggle, onScreenshot, pauseMenu, audioState, accessibility, onAdapterError = () => undefined) {
245
+ const document = container.ownerDocument;
246
+ this.options = {
247
+ showProgress: options.showProgress ?? true,
248
+ showProgressDetails: options.showProgressDetails ?? true,
249
+ showDiagnostics: options.showDiagnostics ?? true,
250
+ showErrors: options.showErrors ?? true,
251
+ showEnterPrompt: options.showEnterPrompt ?? true,
252
+ showControls: options.showControls ?? true,
253
+ showPauseControl: options.showPauseControl ?? true,
254
+ showAudioControl: options.showAudioControl ?? true,
255
+ showFullscreenControl: options.showFullscreenControl ?? true,
256
+ showTouchControls: options.showTouchControls ?? true,
257
+ showInteractionPrompt: options.showInteractionPrompt ?? true,
258
+ showInteractionAction: options.showInteractionAction ?? true,
259
+ showReticle: options.showReticle ?? true,
260
+ showVRControl: options.showVRControl ?? true,
261
+ showXRStatus: options.showXRStatus ?? true,
262
+ allowRetry: options.allowRetry ?? true,
263
+ };
264
+ this.labels = { ...DEFAULT_UI_LABELS, ...options.labels };
265
+ this.adapter = options.adapter ?? null;
266
+ this.onAdapterError = onAdapterError;
267
+ this.onRetry = onRetry;
268
+ this.onEnter = onEnter;
269
+ this.onPauseToggle = onPauseToggle;
270
+ this.onFullscreenToggle = onFullscreenToggle;
271
+ this.onVRToggle = onVRToggle;
272
+ this.onInteract = onInteract;
273
+ this.onAudioToggle = onAudioToggle;
274
+ this.onScreenshot = onScreenshot;
275
+ this.pauseMenu = {
276
+ enabled: pauseMenu !== false && (pauseMenu?.enabled ?? true),
277
+ title: pauseMenu === false ? this.labels.pausedTitle : pauseMenu?.title ?? this.labels.pauseMenuTitle,
278
+ message: pauseMenu === false ? this.labels.pausedMessage : pauseMenu?.message ?? this.labels.pauseMenuMessage,
279
+ showResume: pauseMenu !== false && (pauseMenu?.showResume ?? true),
280
+ showAudio: pauseMenu !== false && (pauseMenu?.showAudio ?? true),
281
+ showFullscreen: pauseMenu !== false && (pauseMenu?.showFullscreen ?? true),
282
+ showScreenshot: pauseMenu !== false && (pauseMenu?.showScreenshot ?? true),
283
+ };
284
+ this.audioState = { ...audioState };
285
+ this.keyboardNavigation = accessibility.options !== false && accessibility.options?.keyboardNavigation !== false;
286
+ this.focusManagement = accessibility.options !== false && accessibility.options?.focusManagement !== false;
287
+ this.isKeyboardModality = accessibility.isKeyboardModality;
288
+ this.root = document.createElement('div');
289
+ this.root.classList.add('anyo-player__ui');
290
+ for (const className of options.className?.split(/\s+/).filter(Boolean) ?? []) {
291
+ this.root.classList.add(className);
292
+ }
293
+ this.root.setAttribute('data-anyo-player-ui', '');
294
+ this.loadingPanel = document.createElement('div');
295
+ this.loadingPanel.classList.add('anyo-player__panel', 'anyo-player__panel--loading');
296
+ this.loadingPanel.setAttribute('role', 'status');
297
+ this.loadingPanel.setAttribute('aria-live', 'polite');
298
+ this.loadingPanel.setAttribute('aria-atomic', 'true');
299
+ const loadingTitle = document.createElement('p');
300
+ loadingTitle.classList.add('anyo-player__title');
301
+ loadingTitle.textContent = this.labels.loadingTitle;
302
+ this.loadingPanel.appendChild(loadingTitle);
303
+ this.phaseText = document.createElement('p');
304
+ this.phaseText.classList.add('anyo-player__message');
305
+ this.loadingPanel.appendChild(this.phaseText);
306
+ this.progressTrack = document.createElement('div');
307
+ this.progressTrack.classList.add('anyo-player__progress');
308
+ this.progressTrack.setAttribute('role', 'progressbar');
309
+ this.progressTrack.setAttribute('aria-label', this.labels.loadingProgress);
310
+ this.progressTrack.setAttribute('aria-valuemin', '0');
311
+ this.progressTrack.setAttribute('aria-valuemax', '100');
312
+ this.progressTrack.setAttribute('aria-valuenow', '0');
313
+ setHidden(this.progressTrack, !this.options.showProgress);
314
+ this.progressFill = document.createElement('div');
315
+ this.progressFill.classList.add('anyo-player__progress-fill');
316
+ this.progressFill.style.width = '0%';
317
+ this.progressTrack.appendChild(this.progressFill);
318
+ this.loadingPanel.appendChild(this.progressTrack);
319
+ this.progressDetails = document.createElement('p');
320
+ this.progressDetails.classList.add('anyo-player__progress-details');
321
+ setHidden(this.progressDetails, !this.options.showProgressDetails);
322
+ this.loadingPanel.appendChild(this.progressDetails);
323
+ this.diagnosticPanel = document.createElement('div');
324
+ this.diagnosticPanel.classList.add('anyo-player__diagnostic');
325
+ this.diagnosticPanel.setAttribute('role', 'status');
326
+ this.diagnosticPanel.setAttribute('aria-live', 'polite');
327
+ this.diagnosticPanel.setAttribute('aria-atomic', 'true');
328
+ this.diagnosticPanel.setAttribute('data-severity', 'info');
329
+ this.diagnosticMessage = document.createElement('p');
330
+ this.diagnosticMessage.classList.add('anyo-player__diagnostic-message');
331
+ this.diagnosticPanel.appendChild(this.diagnosticMessage);
332
+ this.xrStatus = document.createElement('div');
333
+ this.xrStatus.classList.add('anyo-player__xr-status');
334
+ this.xrStatus.setAttribute('data-anyo-player-xr-status', '');
335
+ this.xrStatus.setAttribute('role', 'status');
336
+ this.xrStatus.setAttribute('aria-live', 'polite');
337
+ this.xrStatus.setAttribute('aria-atomic', 'true');
338
+ this.xrStatusMessage = document.createElement('p');
339
+ this.xrStatusMessage.classList.add('anyo-player__xr-status-message');
340
+ this.xrStatus.appendChild(this.xrStatusMessage);
341
+ this.reticle = document.createElement('div');
342
+ this.reticle.classList.add('anyo-player__reticle');
343
+ this.reticle.setAttribute('data-anyo-player-reticle', '');
344
+ this.reticle.setAttribute('aria-hidden', 'true');
345
+ this.reticle.setAttribute('data-active', 'false');
346
+ this.interactionPrompt = document.createElement('div');
347
+ this.interactionPrompt.classList.add('anyo-player__interaction-prompt');
348
+ this.interactionPrompt.setAttribute('data-anyo-player-interaction-prompt', '');
349
+ this.interactionPrompt.setAttribute('role', 'status');
350
+ this.interactionPrompt.setAttribute('aria-live', 'polite');
351
+ this.interactionPrompt.setAttribute('aria-atomic', 'true');
352
+ this.interactionPromptMessage = document.createElement('p');
353
+ this.interactionPromptMessage.classList.add('anyo-player__interaction-prompt-message');
354
+ this.interactionPrompt.appendChild(this.interactionPromptMessage);
355
+ this.interactionPromptTitle = document.createElement('p');
356
+ this.interactionPromptTitle.classList.add('anyo-player__interaction-prompt-title');
357
+ this.interactionPrompt.appendChild(this.interactionPromptTitle);
358
+ this.interactionPromptDescription = document.createElement('p');
359
+ this.interactionPromptDescription.classList.add('anyo-player__interaction-prompt-description');
360
+ this.interactionPrompt.appendChild(this.interactionPromptDescription);
361
+ const interactionPromptFooter = document.createElement('div');
362
+ interactionPromptFooter.classList.add('anyo-player__interaction-prompt-footer');
363
+ this.interactionPromptHint = document.createElement('span');
364
+ this.interactionPromptHint.classList.add('anyo-player__interaction-prompt-hint');
365
+ interactionPromptFooter.appendChild(this.interactionPromptHint);
366
+ this.interactionPromptAction = document.createElement('button');
367
+ this.interactionPromptAction.type = 'button';
368
+ this.interactionPromptAction.classList.add('anyo-player__interaction-action');
369
+ this.interactionPromptAction.addEventListener('click', this.handleInteractionAction);
370
+ interactionPromptFooter.appendChild(this.interactionPromptAction);
371
+ this.interactionPrompt.appendChild(interactionPromptFooter);
372
+ this.enterPanel = document.createElement('div');
373
+ this.enterPanel.classList.add('anyo-player__panel', 'anyo-player__panel--enter');
374
+ this.enterPanel.setAttribute('role', 'group');
375
+ this.enterPanel.setAttribute('aria-label', this.labels.enterTitle);
376
+ const enterTitle = document.createElement('p');
377
+ enterTitle.classList.add('anyo-player__title');
378
+ enterTitle.textContent = this.labels.enterTitle;
379
+ this.enterPanel.appendChild(enterTitle);
380
+ this.enterMessage = document.createElement('p');
381
+ this.enterMessage.classList.add('anyo-player__message');
382
+ this.enterMessage.textContent = this.labels.enterMessage;
383
+ this.enterPanel.appendChild(this.enterMessage);
384
+ this.enterButton = document.createElement('button');
385
+ this.enterButton.classList.add('anyo-player__button');
386
+ this.enterButton.setAttribute('type', 'button');
387
+ this.enterButton.setAttribute('data-anyo-player-enter', '');
388
+ this.enterButton.textContent = this.labels.enterButton;
389
+ this.enterButton.addEventListener('click', this.handleEnter);
390
+ this.enterPanel.appendChild(this.enterButton);
391
+ this.pausedPanel = document.createElement('div');
392
+ this.pausedPanel.classList.add('anyo-player__panel', 'anyo-player__panel--paused');
393
+ this.pausedPanel.setAttribute('role', this.pauseMenu.enabled ? 'dialog' : 'status');
394
+ this.pausedPanel.setAttribute('aria-live', 'polite');
395
+ this.pausedPanel.setAttribute('aria-label', this.pauseMenu.title);
396
+ const pausedTitle = document.createElement('p');
397
+ pausedTitle.classList.add('anyo-player__title');
398
+ pausedTitle.textContent = this.pauseMenu.title;
399
+ this.pausedPanel.appendChild(pausedTitle);
400
+ this.pausedMessage = document.createElement('p');
401
+ this.pausedMessage.classList.add('anyo-player__message');
402
+ this.pausedPanel.appendChild(this.pausedMessage);
403
+ this.pauseMenuActions = document.createElement('div');
404
+ this.pauseMenuActions.classList.add('anyo-player__pause-actions');
405
+ this.pauseResumeButton = this.createPauseAction(document, 'resume', this.labels.resume, this.handlePauseToggle);
406
+ this.pauseAudioButton = this.createPauseAction(document, 'audio', this.labels.muteAudio, this.handleAudioToggle);
407
+ this.pauseFullscreenButton = this.createPauseAction(document, 'fullscreen', this.labels.enterFullscreen, this.handleFullscreenToggle);
408
+ this.pauseScreenshotButton = this.createPauseAction(document, 'screenshot', this.labels.takeScreenshot, this.handleScreenshot);
409
+ setHidden(this.pauseResumeButton, !this.pauseMenu.enabled || !this.pauseMenu.showResume);
410
+ setHidden(this.pauseAudioButton, !this.pauseMenu.enabled || !this.pauseMenu.showAudio || !this.audioState.enabled);
411
+ setHidden(this.pauseFullscreenButton, !this.pauseMenu.enabled || !this.pauseMenu.showFullscreen || !this.options.showFullscreenControl);
412
+ setHidden(this.pauseScreenshotButton, !this.pauseMenu.enabled || !this.pauseMenu.showScreenshot);
413
+ this.pauseMenuActions.appendChild(this.pauseResumeButton);
414
+ this.pauseMenuActions.appendChild(this.pauseAudioButton);
415
+ this.pauseMenuActions.appendChild(this.pauseFullscreenButton);
416
+ this.pauseMenuActions.appendChild(this.pauseScreenshotButton);
417
+ this.pausedPanel.appendChild(this.pauseMenuActions);
418
+ this.controls = document.createElement('div');
419
+ this.controls.classList.add('anyo-player__controls');
420
+ this.controls.setAttribute('role', 'toolbar');
421
+ this.controls.setAttribute('aria-label', this.labels.worldControls);
422
+ this.pauseButton = document.createElement('button');
423
+ this.pauseButton.classList.add('anyo-player__control');
424
+ this.pauseButton.setAttribute('type', 'button');
425
+ this.pauseButton.setAttribute('data-anyo-player-pause', '');
426
+ this.pauseButton.textContent = this.labels.pause;
427
+ this.pauseButton.addEventListener('click', this.handlePauseToggle);
428
+ setHidden(this.pauseButton, !this.options.showPauseControl);
429
+ this.audioButton = document.createElement('button');
430
+ this.audioButton.classList.add('anyo-player__control', 'anyo-player__control--audio');
431
+ this.audioButton.setAttribute('type', 'button');
432
+ this.audioButton.setAttribute('data-anyo-player-audio', '');
433
+ this.audioButton.addEventListener('click', this.handleAudioToggle);
434
+ setHidden(this.audioButton, !this.options.showAudioControl || !this.audioState.enabled);
435
+ this.fullscreenButton = document.createElement('button');
436
+ this.fullscreenButton.classList.add('anyo-player__control');
437
+ this.fullscreenButton.setAttribute('type', 'button');
438
+ this.fullscreenButton.setAttribute('data-anyo-player-fullscreen', '');
439
+ this.fullscreenButton.setAttribute('aria-pressed', 'false');
440
+ this.fullscreenButton.textContent = this.labels.enterFullscreen;
441
+ this.fullscreenButton.addEventListener('click', this.handleFullscreenToggle);
442
+ setHidden(this.fullscreenButton, !this.options.showFullscreenControl);
443
+ this.vrButton = document.createElement('button');
444
+ this.vrButton.classList.add('anyo-player__control', 'anyo-player__control--vr');
445
+ this.vrButton.setAttribute('type', 'button');
446
+ this.vrButton.setAttribute('data-anyo-player-vr', '');
447
+ this.vrButton.setAttribute('aria-pressed', 'false');
448
+ this.vrButton.textContent = this.labels.enterVR;
449
+ this.vrButton.addEventListener('click', this.handleVRToggle);
450
+ setHidden(this.vrButton, true);
451
+ this.controls.appendChild(this.pauseButton);
452
+ this.controls.appendChild(this.fullscreenButton);
453
+ this.controls.appendChild(this.audioButton);
454
+ this.controls.appendChild(this.vrButton);
455
+ this.controls.addEventListener('keydown', this.handleControlsKeyDown);
456
+ this.updateAudioButtons();
457
+ this.updateControlTabStops();
458
+ this.errorPanel = document.createElement('div');
459
+ this.errorPanel.classList.add('anyo-player__panel', 'anyo-player__panel--error');
460
+ this.errorPanel.setAttribute('role', 'alert');
461
+ this.errorPanel.setAttribute('aria-live', 'assertive');
462
+ this.errorPanel.setAttribute('aria-atomic', 'true');
463
+ const errorTitle = document.createElement('p');
464
+ errorTitle.classList.add('anyo-player__title');
465
+ errorTitle.textContent = this.labels.errorTitle;
466
+ this.errorPanel.appendChild(errorTitle);
467
+ this.errorMessage = document.createElement('p');
468
+ this.errorMessage.classList.add('anyo-player__message');
469
+ this.errorPanel.appendChild(this.errorMessage);
470
+ this.retryButton = document.createElement('button');
471
+ this.retryButton.classList.add('anyo-player__button');
472
+ this.retryButton.setAttribute('type', 'button');
473
+ this.retryButton.setAttribute('data-anyo-player-retry', '');
474
+ this.retryButton.textContent = this.labels.retry;
475
+ this.retryButton.addEventListener('click', this.handleRetry);
476
+ setHidden(this.retryButton, !this.options.allowRetry);
477
+ this.errorPanel.appendChild(this.retryButton);
478
+ this.mapAttribution = document.createElement('div');
479
+ this.mapAttribution.classList.add('anyo-player__map-attribution');
480
+ this.mapAttribution.setAttribute('data-anyo-player-map-attribution', '');
481
+ this.mapAttribution.setAttribute('aria-label', 'Map data attribution');
482
+ setHidden(this.mapAttribution, true);
483
+ this.captionPanel = document.createElement('div');
484
+ this.captionPanel.classList.add('anyo-player__caption');
485
+ this.captionPanel.setAttribute('data-anyo-player-caption', '');
486
+ this.captionPanel.setAttribute('role', 'status');
487
+ this.captionPanel.setAttribute('aria-live', 'off');
488
+ this.captionPanel.setAttribute('aria-atomic', 'true');
489
+ this.captionSpeaker = document.createElement('span');
490
+ this.captionSpeaker.classList.add('anyo-player__caption-speaker');
491
+ this.captionText = document.createElement('span');
492
+ this.captionText.classList.add('anyo-player__caption-text');
493
+ this.captionPanel.appendChild(this.captionSpeaker);
494
+ this.captionPanel.appendChild(this.captionText);
495
+ setHidden(this.captionPanel, true);
496
+ this.slots = Object.freeze({
497
+ loading: createUiSlot(document, 'loading', this.loadingPanel),
498
+ diagnostic: createUiSlot(document, 'diagnostic', this.diagnosticPanel),
499
+ 'xr-status': createUiSlot(document, 'xr-status', this.xrStatus),
500
+ reticle: createUiSlot(document, 'reticle', this.reticle),
501
+ interaction: createUiSlot(document, 'interaction', this.interactionPrompt),
502
+ enter: createUiSlot(document, 'enter', this.enterPanel),
503
+ pause: createUiSlot(document, 'pause', this.pausedPanel),
504
+ controls: createUiSlot(document, 'controls', this.controls),
505
+ error: createUiSlot(document, 'error', this.errorPanel),
506
+ });
507
+ for (const slot of Object.values(this.slots))
508
+ this.root.appendChild(slot);
509
+ this.root.appendChild(this.mapAttribution);
510
+ this.root.appendChild(this.captionPanel);
511
+ if (this.adapter)
512
+ this.root.setAttribute('data-anyo-player-adapter-mode', this.adapter.mode ?? 'augment');
513
+ container.appendChild(this.root);
514
+ setHidden(this.loadingPanel, true);
515
+ setHidden(this.diagnosticPanel, true);
516
+ setHidden(this.xrStatus, true);
517
+ setHidden(this.reticle, true);
518
+ setHidden(this.interactionPrompt, true);
519
+ setHidden(this.enterPanel, true);
520
+ setHidden(this.pausedPanel, true);
521
+ setHidden(this.controls, true);
522
+ setHidden(this.errorPanel, true);
523
+ setHidden(this.root, true);
524
+ this.mountAdapter();
525
+ }
526
+ setPhase(phase) {
527
+ if (this.disposed)
528
+ return;
529
+ this.currentPhase = phase;
530
+ if (LOADING_PHASES.has(phase))
531
+ this.currentError = null;
532
+ const loading = LOADING_PHASES.has(phase);
533
+ setHidden(this.loadingPanel, !loading);
534
+ if (loading) {
535
+ this.phaseText.textContent = phaseLabel(phase, this.labels);
536
+ setHidden(this.enterPanel, true);
537
+ setHidden(this.pausedPanel, true);
538
+ setHidden(this.controls, true);
539
+ setHidden(this.errorPanel, true);
540
+ this.updateRootVisibility();
541
+ return;
542
+ }
543
+ if (phase === 'disposing' || phase === 'disposed' || phase === 'idle') {
544
+ setHidden(this.loadingPanel, true);
545
+ setHidden(this.enterPanel, true);
546
+ setHidden(this.pausedPanel, true);
547
+ setHidden(this.controls, true);
548
+ setHidden(this.errorPanel, true);
549
+ this.updateRootVisibility();
550
+ }
551
+ }
552
+ setProgress(progress) {
553
+ if (this.disposed)
554
+ return;
555
+ this.currentProgress = { ...progress };
556
+ const ratio = clampRatio(progress.ratio);
557
+ const percentage = Math.round(ratio * 100);
558
+ this.progressFill.style.width = `${percentage}%`;
559
+ this.progressTrack.setAttribute('aria-valuenow', String(percentage));
560
+ const details = formatProgress(progress);
561
+ this.progressTrack.setAttribute('aria-valuetext', details);
562
+ this.progressDetails.textContent = details;
563
+ this.notifyAdapter();
564
+ }
565
+ setReady(status, progress, enterRequired) {
566
+ if (this.disposed)
567
+ return;
568
+ this.currentReadyStatus = status;
569
+ this.currentProgress = { ...progress };
570
+ this.currentError = null;
571
+ this.currentPauseReason = null;
572
+ this.paused = false;
573
+ this.updatePauseButton();
574
+ setHidden(this.loadingPanel, true);
575
+ setHidden(this.pausedPanel, true);
576
+ setHidden(this.errorPanel, true);
577
+ this.setEnterPanelVisible(enterRequired);
578
+ this.setControlsVisible(true);
579
+ this.enterButton.disabled = false;
580
+ this.enterMessage.textContent = this.labels.enterMessage;
581
+ if (status === 'ready-with-warnings' && this.options.showDiagnostics) {
582
+ this.showDiagnostic(replaceFailedToken(this.labels.readyWithWarnings, progress.failed), 'warning', false);
583
+ }
584
+ else {
585
+ this.clearDiagnostic();
586
+ }
587
+ this.updateRootVisibility();
588
+ if (enterRequired)
589
+ this.maybeFocus('enter');
590
+ }
591
+ setEntering() {
592
+ if (this.disposed)
593
+ return;
594
+ this.currentPauseReason = null;
595
+ this.enterButton.disabled = true;
596
+ this.enterMessage.textContent = this.labels.enterMessage;
597
+ this.setEnterPanelVisible(true);
598
+ this.setControlsVisible(true);
599
+ }
600
+ setRunning() {
601
+ if (this.disposed)
602
+ return;
603
+ this.currentPauseReason = null;
604
+ this.paused = false;
605
+ this.updatePauseButton();
606
+ this.enterButton.disabled = false;
607
+ setHidden(this.enterPanel, true);
608
+ setHidden(this.pausedPanel, true);
609
+ this.setControlsVisible(true);
610
+ this.updateRootVisibility();
611
+ }
612
+ setPaused(reason) {
613
+ if (this.disposed)
614
+ return;
615
+ this.currentPauseReason = reason;
616
+ this.paused = true;
617
+ this.updatePauseButton();
618
+ setHidden(this.loadingPanel, true);
619
+ setHidden(this.enterPanel, true);
620
+ setHidden(this.errorPanel, true);
621
+ this.pausedMessage.textContent = reason === 'visibility'
622
+ ? this.labels.visibilityPaused
623
+ : reason === 'offscreen'
624
+ ? this.labels.offscreenPaused
625
+ : this.pauseMenu.message;
626
+ setHidden(this.pausedPanel, false);
627
+ this.setControlsVisible(true);
628
+ this.updateRootVisibility();
629
+ this.maybeFocus('pause');
630
+ }
631
+ setEnterReady(reason) {
632
+ if (this.disposed)
633
+ return;
634
+ this.currentPauseReason = null;
635
+ this.paused = false;
636
+ this.updatePauseButton();
637
+ setHidden(this.pausedPanel, true);
638
+ this.enterButton.disabled = false;
639
+ this.enterMessage.textContent = reason === 'pointer-lock-error'
640
+ ? this.labels.pointerLockFailed
641
+ : this.labels.reenterMessage;
642
+ this.setEnterPanelVisible(true);
643
+ this.setControlsVisible(true);
644
+ this.maybeFocus('enter');
645
+ }
646
+ setFullscreen(fullscreen) {
647
+ if (this.disposed)
648
+ return;
649
+ this.fullscreen = fullscreen;
650
+ this.fullscreenButton.textContent = fullscreen
651
+ ? this.labels.exitFullscreen
652
+ : this.labels.enterFullscreen;
653
+ this.fullscreenButton.setAttribute('aria-pressed', fullscreen ? 'true' : 'false');
654
+ this.pauseFullscreenButton.textContent = fullscreen ? this.labels.exitFullscreen : this.labels.enterFullscreen;
655
+ this.pauseFullscreenButton.setAttribute('aria-pressed', fullscreen ? 'true' : 'false');
656
+ this.notifyAdapter();
657
+ }
658
+ setInteractionPrompt(prompt) {
659
+ if (this.disposed)
660
+ return;
661
+ this.currentInteractionPrompt = { ...prompt };
662
+ const visible = this.options.showInteractionPrompt && prompt.visible && prompt.text.length > 0;
663
+ this.interactionPromptTitle.textContent = visible ? (prompt.title ?? '') : '';
664
+ this.interactionPromptMessage.textContent = visible ? prompt.text : '';
665
+ this.interactionPromptDescription.textContent = visible ? (prompt.description ?? '') : '';
666
+ this.interactionPromptHint.textContent = visible ? (prompt.inputHint ?? '') : '';
667
+ this.interactionPromptAction.textContent = visible ? (prompt.actionLabel ?? this.labels.interactionAction) : '';
668
+ this.interactionPromptAction.disabled = !prompt.actionAvailable;
669
+ this.interactionPromptAction.setAttribute('aria-label', prompt.ariaLabel ?? prompt.actionLabel ?? prompt.text);
670
+ setHidden(this.interactionPromptTitle, !visible || !prompt.title);
671
+ setHidden(this.interactionPromptDescription, !visible || !prompt.description);
672
+ setHidden(this.interactionPromptHint, !visible || !prompt.inputHint);
673
+ setHidden(this.interactionPromptAction, !visible || !this.options.showInteractionAction || !prompt.actionAvailable);
674
+ this.interactionPrompt.setAttribute('data-trigger', prompt.trigger ?? '');
675
+ setHidden(this.interactionPrompt, !visible);
676
+ this.updateRootVisibility();
677
+ }
678
+ setReticle(state) {
679
+ if (this.disposed)
680
+ return;
681
+ this.currentReticle = { ...state };
682
+ this.reticle.setAttribute('data-active', state.active ? 'true' : 'false');
683
+ setHidden(this.reticle, !this.options.showReticle || !state.visible);
684
+ this.updateRootVisibility();
685
+ }
686
+ setVRSupport(state) {
687
+ if (this.disposed)
688
+ return;
689
+ this.vrSupport = state;
690
+ this.updateVRButton();
691
+ this.setControlsVisible(true);
692
+ }
693
+ setVRSession(state, inputCount) {
694
+ if (this.disposed)
695
+ return;
696
+ this.vrSession = state;
697
+ this.xrInputCount = inputCount;
698
+ this.updateVRButton();
699
+ this.updateXRStatus();
700
+ if (state !== 'idle') {
701
+ setHidden(this.loadingPanel, true);
702
+ setHidden(this.enterPanel, true);
703
+ setHidden(this.pausedPanel, true);
704
+ setHidden(this.errorPanel, true);
705
+ }
706
+ const desktopControlsVisible = state === 'idle';
707
+ setHidden(this.pauseButton, !desktopControlsVisible || !this.options.showPauseControl);
708
+ setHidden(this.audioButton, !desktopControlsVisible || !this.options.showAudioControl || !this.audioState.enabled);
709
+ setHidden(this.fullscreenButton, !desktopControlsVisible || !this.options.showFullscreenControl);
710
+ this.setControlsVisible(true);
711
+ }
712
+ setXRTracking(state) {
713
+ if (this.disposed)
714
+ return;
715
+ const previous = this.xrTracking;
716
+ this.xrTracking = state;
717
+ this.updateXRStatus();
718
+ if (previous === 'lost' && state === 'tracked' && this.options.showDiagnostics) {
719
+ this.showDiagnostic(this.labels.vrTrackingRestored, 'info', true);
720
+ }
721
+ }
722
+ setKeyboardShortcuts(bindings) {
723
+ if (this.disposed)
724
+ return;
725
+ const pause = keyboardShortcutValues(bindings.pause);
726
+ const interact = keyboardShortcutValues(bindings.interact);
727
+ this.enterButton.setAttribute('aria-keyshortcuts', 'Enter Space');
728
+ if (pause.length > 0)
729
+ this.pauseButton.setAttribute('aria-keyshortcuts', pause.join(' '));
730
+ else
731
+ this.pauseButton.removeAttribute('aria-keyshortcuts');
732
+ if (interact.length > 0)
733
+ this.interactionPromptAction.setAttribute('aria-keyshortcuts', interact.join(' '));
734
+ else
735
+ this.interactionPromptAction.removeAttribute('aria-keyshortcuts');
736
+ }
737
+ setAudioState(state) {
738
+ if (this.disposed)
739
+ return;
740
+ this.audioState = { ...state };
741
+ this.updateAudioButtons();
742
+ this.updateControlTabStops();
743
+ this.updateRootVisibility();
744
+ }
745
+ setMapAttribution(text) {
746
+ if (this.disposed)
747
+ return;
748
+ const value = text?.trim() ?? '';
749
+ this.mapAttribution.textContent = value;
750
+ setHidden(this.mapAttribution, !value);
751
+ this.updateRootVisibility();
752
+ }
753
+ setCaption(caption) {
754
+ if (this.disposed)
755
+ return;
756
+ this.currentCaption = { ...caption };
757
+ if (!caption.visible) {
758
+ this.captionSpeaker.textContent = '';
759
+ this.captionText.textContent = '';
760
+ this.captionPanel.removeAttribute('lang');
761
+ setHidden(this.captionPanel, true);
762
+ this.updateRootVisibility();
763
+ return;
764
+ }
765
+ this.captionSpeaker.textContent = caption.speaker ? `${caption.speaker}: ` : '';
766
+ this.captionText.textContent = caption.text;
767
+ if (caption.language)
768
+ this.captionPanel.setAttribute('lang', caption.language);
769
+ else
770
+ this.captionPanel.removeAttribute('lang');
771
+ setHidden(this.captionPanel, false);
772
+ this.updateRootVisibility();
773
+ }
774
+ focusPrimaryAction(target) {
775
+ if (this.disposed)
776
+ return;
777
+ const element = target === 'enter'
778
+ ? this.enterButton
779
+ : target === 'pause'
780
+ ? this.pauseButton
781
+ : target === 'retry'
782
+ ? this.retryButton
783
+ : target === 'interaction'
784
+ ? this.interactionPromptAction
785
+ : this.vrButton;
786
+ if (element.hidden || element.disabled)
787
+ return;
788
+ element.focus?.({ preventScroll: true });
789
+ }
790
+ setInputError(error) {
791
+ if (this.disposed || !this.options.showDiagnostics)
792
+ return;
793
+ this.showDiagnostic(error.code === 'PLAYER_POINTER_LOCK_FAILED' ? this.labels.pointerLockFailed : error.message, 'warning', true);
794
+ }
795
+ setControlError(error) {
796
+ if (this.disposed || !this.options.showDiagnostics)
797
+ return;
798
+ const message = error.code === 'PLAYER_FULLSCREEN_FAILED' || error.code === 'PLAYER_FULLSCREEN_UNAVAILABLE'
799
+ ? this.labels.fullscreenFailed
800
+ : error.code === 'PLAYER_VR_ENTER_FAILED' || error.code === 'PLAYER_VR_UNSUPPORTED'
801
+ ? this.labels.vrEnterFailed
802
+ : error.code === 'PLAYER_VR_EXIT_FAILED'
803
+ ? this.labels.vrExitFailed
804
+ : error.message;
805
+ this.showDiagnostic(message, 'warning', true);
806
+ }
807
+ setError(error) {
808
+ if (this.disposed)
809
+ return;
810
+ this.currentError = error;
811
+ setHidden(this.loadingPanel, true);
812
+ setHidden(this.enterPanel, true);
813
+ setHidden(this.pausedPanel, true);
814
+ setHidden(this.controls, true);
815
+ this.clearDiagnostic();
816
+ if (this.options.showErrors) {
817
+ this.errorMessage.textContent = error.code === 'PLAYER_RENDERER_LOST'
818
+ ? this.labels.webgpuDeviceLost
819
+ : error.message;
820
+ setHidden(this.errorPanel, false);
821
+ }
822
+ else {
823
+ setHidden(this.errorPanel, true);
824
+ }
825
+ this.updateRootVisibility();
826
+ this.maybeFocus('retry');
827
+ }
828
+ handleDiagnostic(diagnostic) {
829
+ if (this.disposed || !this.options.showDiagnostics)
830
+ return;
831
+ if (diagnostic.code === 'SEKAI64_WEBGL_CONTEXT_LOST') {
832
+ this.showDiagnostic(this.labels.webglContextLost, 'error', false);
833
+ return;
834
+ }
835
+ if (diagnostic.code === 'SEKAI64_WEBGL_CONTEXT_RESTORED') {
836
+ this.showDiagnostic(this.labels.webglContextRestored, 'info', true);
837
+ return;
838
+ }
839
+ if (diagnostic.code === 'SEKAI64_WEBGPU_DEVICE_LOST') {
840
+ this.showDiagnostic(this.labels.webgpuDeviceLost, 'error', false);
841
+ return;
842
+ }
843
+ if (diagnostic.severity === 'warning' || diagnostic.severity === 'error') {
844
+ this.showDiagnostic(diagnostic.message, diagnostic.severity, false);
845
+ }
846
+ }
847
+ handleWarning(message) {
848
+ if (this.disposed || !this.options.showDiagnostics)
849
+ return;
850
+ this.showDiagnostic(message, 'warning', false);
851
+ }
852
+ dispose() {
853
+ if (this.disposed)
854
+ return;
855
+ this.disposed = true;
856
+ this.clearDiagnosticTimer();
857
+ this.enterButton.removeEventListener('click', this.handleEnter);
858
+ this.retryButton.removeEventListener('click', this.handleRetry);
859
+ this.pauseButton.removeEventListener('click', this.handlePauseToggle);
860
+ this.audioButton.removeEventListener('click', this.handleAudioToggle);
861
+ this.pauseResumeButton.removeEventListener('click', this.handlePauseToggle);
862
+ this.pauseAudioButton.removeEventListener('click', this.handleAudioToggle);
863
+ this.pauseFullscreenButton.removeEventListener('click', this.handleFullscreenToggle);
864
+ this.pauseScreenshotButton.removeEventListener('click', this.handleScreenshot);
865
+ this.fullscreenButton.removeEventListener('click', this.handleFullscreenToggle);
866
+ this.vrButton.removeEventListener('click', this.handleVRToggle);
867
+ this.interactionPromptAction.removeEventListener('click', this.handleInteractionAction);
868
+ this.controls.removeEventListener('keydown', this.handleControlsKeyDown);
869
+ this.disposeAdapter();
870
+ if (this.root.parentNode)
871
+ this.root.parentNode.removeChild(this.root);
872
+ }
873
+ handleEnter = () => {
874
+ if (!this.disposed)
875
+ this.onEnter();
876
+ };
877
+ handleRetry = () => {
878
+ if (this.disposed)
879
+ return;
880
+ this.retryButton.disabled = true;
881
+ void this.onRetry().catch(() => undefined).finally(() => {
882
+ if (!this.disposed)
883
+ this.retryButton.disabled = false;
884
+ });
885
+ };
886
+ handlePauseToggle = () => {
887
+ if (!this.disposed)
888
+ this.onPauseToggle();
889
+ };
890
+ handleAudioToggle = () => {
891
+ if (this.disposed)
892
+ return;
893
+ this.audioButton.disabled = true;
894
+ this.pauseAudioButton.disabled = true;
895
+ void this.onAudioToggle().finally(() => {
896
+ if (!this.disposed) {
897
+ this.audioButton.disabled = false;
898
+ this.pauseAudioButton.disabled = false;
899
+ }
900
+ });
901
+ };
902
+ handleScreenshot = () => {
903
+ if (this.disposed)
904
+ return;
905
+ this.pauseScreenshotButton.disabled = true;
906
+ void this.onScreenshot().finally(() => {
907
+ if (!this.disposed)
908
+ this.pauseScreenshotButton.disabled = false;
909
+ });
910
+ };
911
+ handleFullscreenToggle = () => {
912
+ if (this.disposed)
913
+ return;
914
+ this.fullscreenButton.disabled = true;
915
+ void this.onFullscreenToggle().catch(() => undefined).finally(() => {
916
+ if (!this.disposed)
917
+ this.fullscreenButton.disabled = false;
918
+ });
919
+ };
920
+ handleInteractionAction = () => {
921
+ if (this.disposed || this.interactionPromptAction.disabled)
922
+ return;
923
+ this.interactionPromptAction.disabled = true;
924
+ void this.onInteract().catch(() => false).finally(() => {
925
+ if (!this.disposed)
926
+ this.interactionPromptAction.disabled = false;
927
+ });
928
+ };
929
+ handleVRToggle = () => {
930
+ if (this.disposed)
931
+ return;
932
+ this.vrButton.disabled = true;
933
+ void this.onVRToggle().catch(() => undefined).finally(() => {
934
+ if (!this.disposed)
935
+ this.updateVRButton();
936
+ });
937
+ };
938
+ setEnterPanelVisible(visible) {
939
+ setHidden(this.enterPanel, !visible || !this.options.showEnterPrompt);
940
+ this.updateRootVisibility();
941
+ }
942
+ setControlsVisible(visible) {
943
+ const noVisibleControls = !this.options.showPauseControl
944
+ && (!this.options.showAudioControl || !this.audioState.enabled)
945
+ && !this.options.showFullscreenControl
946
+ && (!this.options.showVRControl || this.vrSupport === 'disabled');
947
+ setHidden(this.controls, !visible || !this.options.showControls || noVisibleControls);
948
+ this.updateControlTabStops();
949
+ this.updateRootVisibility();
950
+ }
951
+ handleControlsKeyDown = (event) => {
952
+ if (!this.keyboardNavigation)
953
+ return;
954
+ const buttons = this.visibleControlButtons();
955
+ if (buttons.length === 0)
956
+ return;
957
+ const current = this.controls.ownerDocument?.activeElement;
958
+ const index = buttons.indexOf(current);
959
+ let nextIndex = index < 0 ? 0 : index;
960
+ if (event.code === 'ArrowRight' || event.code === 'ArrowDown')
961
+ nextIndex = (nextIndex + 1) % buttons.length;
962
+ else if (event.code === 'ArrowLeft' || event.code === 'ArrowUp')
963
+ nextIndex = (nextIndex - 1 + buttons.length) % buttons.length;
964
+ else if (event.code === 'Home')
965
+ nextIndex = 0;
966
+ else if (event.code === 'End')
967
+ nextIndex = buttons.length - 1;
968
+ else
969
+ return;
970
+ event.preventDefault();
971
+ buttons[nextIndex]?.focus?.({ preventScroll: true });
972
+ this.updateControlTabStops(buttons[nextIndex]);
973
+ };
974
+ visibleControlButtons() {
975
+ return [this.pauseButton, this.fullscreenButton, this.audioButton, this.vrButton]
976
+ .filter(button => !button.hidden && !button.disabled);
977
+ }
978
+ updateControlTabStops(active) {
979
+ const buttons = this.visibleControlButtons();
980
+ const selected = active && buttons.includes(active) ? active : buttons[0];
981
+ for (const button of [this.pauseButton, this.fullscreenButton, this.audioButton, this.vrButton]) {
982
+ button.tabIndex = button === selected ? 0 : -1;
983
+ }
984
+ }
985
+ maybeFocus(target) {
986
+ if (!this.focusManagement || !this.isKeyboardModality())
987
+ return;
988
+ this.focusPrimaryAction(target);
989
+ }
990
+ createPauseAction(document, name, label, listener) {
991
+ const button = document.createElement('button');
992
+ button.type = 'button';
993
+ button.classList.add('anyo-player__button', 'anyo-player__pause-action');
994
+ button.setAttribute(`data-anyo-player-pause-${name}`, '');
995
+ button.textContent = label;
996
+ button.addEventListener('click', listener);
997
+ return button;
998
+ }
999
+ updateAudioButtons() {
1000
+ const label = !this.audioState.unlocked || this.audioState.blocked
1001
+ ? this.labels.unlockAudio
1002
+ : this.audioState.muted
1003
+ ? this.labels.unmuteAudio
1004
+ : this.labels.muteAudio;
1005
+ for (const button of [this.audioButton, this.pauseAudioButton]) {
1006
+ button.textContent = label;
1007
+ button.setAttribute('aria-pressed', this.audioState.muted ? 'true' : 'false');
1008
+ button.setAttribute('data-audio-unlocked', this.audioState.unlocked ? 'true' : 'false');
1009
+ }
1010
+ setHidden(this.audioButton, !this.options.showAudioControl || !this.audioState.enabled);
1011
+ setHidden(this.pauseAudioButton, !this.pauseMenu.enabled || !this.pauseMenu.showAudio || !this.audioState.enabled);
1012
+ }
1013
+ updatePauseButton() {
1014
+ this.pauseButton.textContent = this.paused ? this.labels.resume : this.labels.pause;
1015
+ this.pauseButton.setAttribute('aria-pressed', this.paused ? 'true' : 'false');
1016
+ }
1017
+ updateVRButton() {
1018
+ const visible = this.options.showVRControl && this.vrSupport !== 'disabled';
1019
+ setHidden(this.vrButton, !visible);
1020
+ this.vrButton.setAttribute('aria-pressed', this.vrSession === 'active' ? 'true' : 'false');
1021
+ if (this.vrSession === 'entering') {
1022
+ this.vrButton.textContent = this.labels.vrEntering;
1023
+ this.vrButton.disabled = true;
1024
+ return;
1025
+ }
1026
+ if (this.vrSession === 'exiting') {
1027
+ this.vrButton.textContent = this.labels.vrExiting;
1028
+ this.vrButton.disabled = true;
1029
+ return;
1030
+ }
1031
+ if (this.vrSession === 'active') {
1032
+ this.vrButton.textContent = this.labels.exitVR;
1033
+ this.vrButton.disabled = false;
1034
+ return;
1035
+ }
1036
+ if (this.vrSupport === 'checking' || this.vrSupport === 'unknown') {
1037
+ this.vrButton.textContent = this.labels.vrChecking;
1038
+ this.vrButton.disabled = true;
1039
+ return;
1040
+ }
1041
+ if (this.vrSupport === 'unsupported' || this.vrSupport === 'error') {
1042
+ this.vrButton.textContent = this.labels.vrUnsupported;
1043
+ this.vrButton.disabled = true;
1044
+ return;
1045
+ }
1046
+ this.vrButton.textContent = this.labels.enterVR;
1047
+ this.vrButton.disabled = this.vrSupport !== 'supported';
1048
+ }
1049
+ updateXRStatus() {
1050
+ if (!this.options.showXRStatus) {
1051
+ setHidden(this.xrStatus, true);
1052
+ return;
1053
+ }
1054
+ let text = '';
1055
+ let severity = 'info';
1056
+ if (this.xrTracking === 'lost') {
1057
+ text = this.labels.vrTrackingLost;
1058
+ severity = 'error';
1059
+ }
1060
+ else if (this.vrSession === 'entering') {
1061
+ text = this.labels.vrEntering;
1062
+ }
1063
+ else if (this.vrSession === 'exiting') {
1064
+ text = this.labels.vrExiting;
1065
+ }
1066
+ else if (this.vrSession === 'active') {
1067
+ text = this.xrInputCount > 0
1068
+ ? this.labels.vrInputs.replaceAll('{count}', String(this.xrInputCount))
1069
+ : this.labels.vrActive;
1070
+ }
1071
+ this.xrStatusMessage.textContent = text;
1072
+ this.xrStatus.setAttribute('data-severity', severity);
1073
+ setHidden(this.xrStatus, text.length === 0);
1074
+ this.updateRootVisibility();
1075
+ }
1076
+ showDiagnostic(message, severity, dismissAutomatically) {
1077
+ this.clearDiagnosticTimer();
1078
+ this.currentDiagnostic = { message, severity };
1079
+ this.diagnosticMessage.textContent = message;
1080
+ this.diagnosticPanel.setAttribute('data-severity', severity);
1081
+ setHidden(this.diagnosticPanel, false);
1082
+ this.updateRootVisibility();
1083
+ if (dismissAutomatically) {
1084
+ this.diagnosticTimer = setTimeout(() => this.clearDiagnostic(), 3500);
1085
+ }
1086
+ }
1087
+ clearDiagnostic() {
1088
+ this.clearDiagnosticTimer();
1089
+ setHidden(this.diagnosticPanel, true);
1090
+ this.currentDiagnostic = null;
1091
+ this.diagnosticMessage.textContent = '';
1092
+ this.updateRootVisibility();
1093
+ }
1094
+ clearDiagnosticTimer() {
1095
+ if (this.diagnosticTimer === null)
1096
+ return;
1097
+ clearTimeout(this.diagnosticTimer);
1098
+ this.diagnosticTimer = null;
1099
+ }
1100
+ updateRootVisibility() {
1101
+ const visible = !this.loadingPanel.hidden
1102
+ || !this.diagnosticPanel.hidden
1103
+ || !this.xrStatus.hidden
1104
+ || !this.reticle.hidden
1105
+ || !this.interactionPrompt.hidden
1106
+ || !this.enterPanel.hidden
1107
+ || !this.pausedPanel.hidden
1108
+ || !this.controls.hidden
1109
+ || !this.errorPanel.hidden
1110
+ || !this.mapAttribution.hidden
1111
+ || !this.captionPanel.hidden;
1112
+ setHidden(this.root, !visible);
1113
+ this.notifyAdapter();
1114
+ }
1115
+ mountAdapter() {
1116
+ if (!this.adapter || this.adapterMounted || this.disposed)
1117
+ return;
1118
+ this.adapterMounted = true;
1119
+ const actions = Object.freeze({
1120
+ retry: () => this.onRetry(),
1121
+ enter: () => this.onEnter(),
1122
+ togglePause: () => this.onPauseToggle(),
1123
+ toggleFullscreen: () => this.onFullscreenToggle(),
1124
+ toggleVR: () => this.onVRToggle(),
1125
+ interact: () => this.onInteract(),
1126
+ toggleAudio: () => this.onAudioToggle(),
1127
+ captureScreenshot: () => this.onScreenshot(),
1128
+ });
1129
+ try {
1130
+ const cleanup = this.adapter.mount({
1131
+ root: this.root,
1132
+ slots: this.slots,
1133
+ actions,
1134
+ labels: Object.freeze({ ...this.labels }),
1135
+ getSnapshot: () => this.snapshot(),
1136
+ });
1137
+ if (typeof cleanup === 'function')
1138
+ this.adapterCleanup = cleanup;
1139
+ this.adapter.update?.(this.snapshot());
1140
+ }
1141
+ catch (error) {
1142
+ this.onAdapterError(error);
1143
+ }
1144
+ }
1145
+ notifyAdapter() {
1146
+ if (!this.adapter || !this.adapterMounted || this.disposed)
1147
+ return;
1148
+ try {
1149
+ this.adapter.update?.(this.snapshot());
1150
+ }
1151
+ catch (error) {
1152
+ this.onAdapterError(error);
1153
+ }
1154
+ }
1155
+ disposeAdapter() {
1156
+ if (!this.adapter || !this.adapterMounted)
1157
+ return;
1158
+ this.adapterMounted = false;
1159
+ try {
1160
+ this.adapterCleanup?.();
1161
+ }
1162
+ catch (error) {
1163
+ this.onAdapterError(error);
1164
+ }
1165
+ finally {
1166
+ this.adapterCleanup = null;
1167
+ }
1168
+ try {
1169
+ this.adapter.dispose?.();
1170
+ }
1171
+ catch (error) {
1172
+ this.onAdapterError(error);
1173
+ }
1174
+ }
1175
+ snapshot() {
1176
+ return Object.freeze({
1177
+ phase: this.currentPhase,
1178
+ progress: Object.freeze({ ...this.currentProgress }),
1179
+ readyStatus: this.currentReadyStatus,
1180
+ paused: this.paused,
1181
+ pauseReason: this.currentPauseReason,
1182
+ fullscreen: this.fullscreen,
1183
+ interactionPrompt: Object.freeze({ ...this.currentInteractionPrompt }),
1184
+ reticle: Object.freeze({ ...this.currentReticle }),
1185
+ vrSupport: this.vrSupport,
1186
+ vrSession: this.vrSession,
1187
+ xrTracking: this.xrTracking,
1188
+ xrInputCount: this.xrInputCount,
1189
+ audio: Object.freeze({ ...this.audioState }),
1190
+ caption: Object.freeze({ ...this.currentCaption }),
1191
+ error: this.currentError,
1192
+ diagnostic: this.currentDiagnostic ? Object.freeze({ ...this.currentDiagnostic }) : null,
1193
+ });
1194
+ }
1195
+ }
1196
+ export function createPlayerUI(container, options, onRetry, onEnter, onPauseToggle, onFullscreenToggle, onVRToggle, onInteract, onAudioToggle, onScreenshot, pauseMenu, audioState, accessibility, onAdapterError = () => undefined) {
1197
+ if (options === false)
1198
+ return new NullPlayerUI();
1199
+ return new DefaultPlayerUI(container, options ?? {}, onRetry, onEnter, onPauseToggle, onFullscreenToggle, onVRToggle, onInteract, onAudioToggle, onScreenshot, pauseMenu, audioState, accessibility, onAdapterError);
1200
+ }
1201
+ //# sourceMappingURL=PlayerUI.js.map