From a74818bfd99f29c48316ba6e51f4cbd643278532 Mon Sep 17 00:00:00 2001 From: ceyhun-o Date: Tue, 16 Jun 2026 13:13:22 +0200 Subject: [PATCH 01/14] Bump player version --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index dfdb023b..df021b4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@theoplayer/web-ui", - "version": "2.1.1", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@theoplayer/web-ui", - "version": "2.1.1", + "version": "2.2.0", "license": "MIT", "workspaces": [ ".", @@ -44,7 +44,7 @@ "rollup-plugin-string": "^3.0.0", "rollup-plugin-swc3": "^0.12.1", "serve": "^14.2.6", - "theoplayer": "^11.0.0", + "theoplayer": "^11.4.0", "tslib": "^2.8.1", "typedoc": "^0.28.19", "typedoc-plugin-mdn-links": "^5.1.1", @@ -9945,9 +9945,9 @@ } }, "node_modules/theoplayer": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/theoplayer/-/theoplayer-11.0.0.tgz", - "integrity": "sha512-pxjpNtd++m87H5ndEPlNbKpHhXCNbFDgnXB0g6RfTv/v7zSpSLwiB0F0vIJe5asaqEJ6pKH7kvpTgnaEN2TElw==", + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/theoplayer/-/theoplayer-11.4.0.tgz", + "integrity": "sha512-LJEXxDOW41NWM62dyj/4coc1a5jksriFXTGCuGVV+x31DGiAffHdhUBJo7So7wsvMzsoBTzuZZF6Ei/FsIFYJA==", "dev": true, "license": "SEE LICENSE AT https://www.theoplayer.com/terms" }, @@ -10478,11 +10478,11 @@ }, "react": { "name": "@theoplayer/react-ui", - "version": "2.1.1", + "version": "2.2.0", "license": "MIT", "dependencies": { "@lit/react": "^1.0.8", - "@theoplayer/web-ui": "^2.1.1" + "@theoplayer/web-ui": "^2.2.0" }, "devDependencies": { "@rollup/plugin-json": "^6.1.0", diff --git a/package.json b/package.json index f2d77955..57e909ed 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "rollup-plugin-string": "^3.0.0", "rollup-plugin-swc3": "^0.12.1", "serve": "^14.2.6", - "theoplayer": "^11.0.0", + "theoplayer": "^11.4.0", "tslib": "^2.8.1", "typedoc": "^0.28.19", "typedoc-plugin-mdn-links": "^5.1.1", From 16852100b9d5b6ecde1acc9014b023743fb3d8b1 Mon Sep 17 00:00:00 2001 From: ceyhun-o Date: Tue, 16 Jun 2026 13:20:28 +0200 Subject: [PATCH 02/14] Drop internal API check to fix type error --- src/UIContainer.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/UIContainer.ts b/src/UIContainer.ts index 5d212736..994a6fff 100644 --- a/src/UIContainer.ts +++ b/src/UIContainer.ts @@ -969,9 +969,6 @@ export class UIContainer extends LitElement { if (streamType) { return streamType; } - if (source?.dvr) { - return 'dvr'; - } // Assume VOD. return 'vod'; } else if (duration === Infinity) { From ff5a08381d09875c9f91255f270098491aae0d1f Mon Sep 17 00:00:00 2001 From: ceyhun-o Date: Tue, 16 Jun 2026 13:21:30 +0200 Subject: [PATCH 03/14] Add VR UI components --- src/components/VRButton.css | 10 +++ src/components/VRButton.ts | 103 ++++++++++++++++++++++++++++++ src/components/VRCompass.css | 72 +++++++++++++++++++++ src/components/VRCompass.ts | 74 +++++++++++++++++++++ src/components/VRIOSFullscreen.ts | 75 ++++++++++++++++++++++ src/components/index.ts | 3 + src/i18n/Locale.ts | 15 +++++ src/icons/vr.svg | 1 + src/util/Attribute.ts | 1 + 9 files changed, 354 insertions(+) create mode 100644 src/components/VRButton.css create mode 100644 src/components/VRButton.ts create mode 100644 src/components/VRCompass.css create mode 100644 src/components/VRCompass.ts create mode 100644 src/components/VRIOSFullscreen.ts create mode 100644 src/icons/vr.svg diff --git a/src/components/VRButton.css b/src/components/VRButton.css new file mode 100644 index 00000000..536dd34e --- /dev/null +++ b/src/components/VRButton.css @@ -0,0 +1,10 @@ +/* + * When no VR capable device is available, the button is disabled/greyed out. + */ +:host([disabled]) svg, +:host([disabled]) img, +:host([disabled]) ::slotted(svg), +:host([disabled]) ::slotted(img) { + color: var(--theoplayer-button-disabled-text-color, #ccc); + opacity: var(--theoplayer-vr-button-disabled-icon-opacity, 0.5); +} diff --git a/src/components/VRButton.ts b/src/components/VRButton.ts new file mode 100644 index 00000000..b35babd5 --- /dev/null +++ b/src/components/VRButton.ts @@ -0,0 +1,103 @@ +import { html, type HTMLTemplateResult, type PropertyValues } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; +import type { ChromelessPlayer } from 'theoplayer/chromeless'; +import { Button } from './Button'; +import vrButtonCss from './VRButton.css'; +import vrIcon from '../icons/vr.svg'; +import { stateReceiver } from './StateReceiverMixin'; +import { Attribute } from '../util/Attribute'; +import { getLocale } from '../i18n'; + +const VR_EVENTS = ['statechange', 'stereochange'] as const; + +/** + * A button that toggles stereoscopic VR mode. + * + * The button is only shown when the current source supports VR (e.g. a 360° video), + * and is disabled when the device cannot present VR content. + */ +@customElement('theoplayer-vr-button') +@stateReceiver(['player', 'lang']) +export class VRButton extends Button { + static styles = [...Button.styles, vrButtonCss]; + + private _player: ChromelessPlayer | undefined; + + override connectedCallback() { + super.connectedCallback(); + this._updateFromPlayer(); + this._updateAriaLabel(); + } + + get player(): ChromelessPlayer | undefined { + return this._player; + } + + @property({ reflect: false, attribute: false }) + set player(player: ChromelessPlayer | undefined) { + if (this._player === player) { + return; + } + this._player?.vr?.removeEventListener(VR_EVENTS, this._updateFromPlayer); + this._player = player; + this._player?.vr?.addEventListener(VR_EVENTS, this._updateFromPlayer); + this._updateFromPlayer(); + } + + /** + * Whether stereoscopic VR mode is currently active. + */ + @property({ reflect: true, type: Boolean, attribute: Attribute.STEREO }) + accessor stereo: boolean = false; + + @property({ reflect: true, type: String, attribute: Attribute.LANG }) + accessor lang: string = ''; + + private readonly _updateFromPlayer = (): void => { + const vr = this._player?.vr; + if (!vr || vr.state === 'unavailable') { + this.stereo = false; + this.hidden = true; + return; + } + this.hidden = false; + this.disabled = !vr.canPresentVR; + this.stereo = vr.stereo; + }; + + protected override handleClick(): void { + const vr = this._player?.vr; + if (!vr || !vr.canPresentVR) { + return; + } + vr.stereo = !vr.stereo; + this.stereo = vr.stereo; + } + + override willUpdate(changedProperties: PropertyValues) { + super.willUpdate(changedProperties); + this._updateAriaLabel(); + } + + private _updateAriaLabel(): void { + const locale = getLocale(this.lang); + if (this.stereo) { + this.ariaLabel = locale.vrExitAria; + } else if (this.disabled) { + this.ariaLabel = locale.vrUnavailableAria; + } else { + this.ariaLabel = locale.vrAria; + } + } + + protected override render(): HTMLTemplateResult { + return html`${unsafeSVG(vrIcon)}`; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'theoplayer-vr-button': VRButton; + } +} diff --git a/src/components/VRCompass.css b/src/components/VRCompass.css new file mode 100644 index 00000000..ffa165d8 --- /dev/null +++ b/src/components/VRCompass.css @@ -0,0 +1,72 @@ +:host { + display: inline-block; + box-sizing: border-box; + vertical-align: middle; + pointer-events: none; +} + +:host([hidden]) { + display: none !important; +} + +[part='dial'] { + position: relative; + box-sizing: border-box; + font-size: var(--theoplayer-vr-compass-size, 26px); + width: 1em; + height: 1em; + border: 0.025em solid var(--theoplayer-vr-compass-color, #fff); + border-radius: 100%; +} + +[part='dial']::before, +[part='dial']::after { + content: ''; + position: absolute; + width: 0; + height: 0; +} + +/* Filled circle in center of compass */ +[part='dial']::before { + top: 50%; + left: 50%; + border: 0.075em solid var(--theoplayer-vr-compass-color, #fff); + border-radius: 100%; + transform: translate(-50%, -50%); +} + +/* Pointy arrow above compass */ +[part='dial']::after { + top: 0; + left: 50%; + border-left: 0.1em solid transparent; + border-right: 0.1em solid transparent; + border-bottom: 0.1em solid var(--theoplayer-vr-compass-color, #fff); + transform: translate(-50%, -100%); + transform-origin: 50% 100%; +} + +/* Field of view indicator */ +[part='fov'] { + overflow: hidden; + position: absolute; + left: 0; + top: 0; + width: 50%; + height: 50%; + transform-origin: 100% 100%; +} + +[part='fov']::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 200%; + height: 200%; + border: 0.35em solid var(--theoplayer-vr-compass-fov-color, rgba(255, 255, 255, 0.75)); + box-sizing: border-box; + border-radius: 100%; + transform: skew(10deg); +} diff --git a/src/components/VRCompass.ts b/src/components/VRCompass.ts new file mode 100644 index 00000000..b441b3e4 --- /dev/null +++ b/src/components/VRCompass.ts @@ -0,0 +1,74 @@ +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import type { ChromelessPlayer } from 'theoplayer/chromeless'; +import { stateReceiver } from './StateReceiverMixin'; +import vrCompassCss from './VRCompass.css'; + +const VR_EVENTS = ['statechange', 'directionchange'] as const; +const FOV_SLICE_ANGLE = 80; // degrees + +/** + * A compass that indicates the current viewing direction of a 360° (VR) video. + * + * @cssproperty `--theoplayer-vr-compass-size` - The size (diameter) of the compass. Defaults to `26px`. + * @cssproperty `--theoplayer-vr-compass-color` - The color of the compass border, center dot and pointer. + * Defaults to `#fff`. + * @cssproperty `--theoplayer-vr-compass-fov-color` - The color of the field-of-view indicator. + * Defaults to `rgba(255, 255, 255, 0.75)`. + */ +@customElement('theoplayer-vr-compass') +@stateReceiver(['player']) +export class VRCompass extends LitElement { + static override styles = [vrCompassCss]; + + private _player: ChromelessPlayer | undefined; + + @state() + private accessor _yaw: number = 0; + + get player(): ChromelessPlayer | undefined { + return this._player; + } + + @property({ reflect: false, attribute: false }) + set player(player: ChromelessPlayer | undefined) { + if (this._player === player) { + return; + } + if (this._player !== undefined) { + this._player.vr?.removeEventListener(VR_EVENTS, this._updateFromPlayer); + this._player.removeEventListener('sourcechange', this._updateFromPlayer); + } + this._player = player; + if (this._player !== undefined) { + this._player.vr?.addEventListener(VR_EVENTS, this._updateFromPlayer); + this._player.addEventListener('sourcechange', this._updateFromPlayer); + } + this._updateFromPlayer(); + } + + private readonly _updateFromPlayer = (): void => { + const vr = this._player?.vr; + // Like the classic UI, only show the compass for non-native 360° VR sources. + // Native VR sources are rendered by the platform, so there's no JS viewing direction. + if (!vr || vr.state === 'unavailable' || this._player?.source?.vr?.nativeVR) { + this.hidden = true; + return; + } + this.hidden = false; + this._yaw = vr.direction.yaw; + }; + + protected override render(): HTMLTemplateResult { + const transformAngle = -this._yaw + FOV_SLICE_ANGLE / 2; + const transformSkew = FOV_SLICE_ANGLE - 90; + const transform = `scale(0.875) rotate(${transformAngle}deg) skew(${transformSkew}deg)`; + return html`
`; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'theoplayer-vr-compass': VRCompass; + } +} diff --git a/src/components/VRIOSFullscreen.ts b/src/components/VRIOSFullscreen.ts new file mode 100644 index 00000000..360762d0 --- /dev/null +++ b/src/components/VRIOSFullscreen.ts @@ -0,0 +1,75 @@ +import { css, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import type { ChromelessPlayer } from 'theoplayer/chromeless'; +import { stateReceiver } from './StateReceiverMixin'; +import { createCustomEvent } from '../util/EventUtils'; +import { ENTER_FULLSCREEN_EVENT } from '../events/EnterFullscreenEvent'; +import { EXIT_FULLSCREEN_EVENT } from '../events/ExitFullscreenEvent'; + +function isIOS(): boolean { + if (typeof navigator === 'undefined') { + return false; + } + const ua = navigator.userAgent || ''; + // iPadOS 13+ reports as Mac, so also check for touch-capable "Macintosh". + return /iPad|iPhone|iPod/.test(ua) || (/Macintosh/.test(ua) && navigator.maxTouchPoints > 1); +} + +/** + * A helper component for iOS that forces the player into fullscreen while VR (stereo) + * content is being presented, and restores the previous state when it stops. + * + * This component renders nothing visible. + */ +@customElement('theoplayer-vr-ios-fullscreen') +@stateReceiver(['player']) +export class VRIOSFullscreen extends LitElement { + static override styles = css` + :host { + display: none; + } + `; + + private _player: ChromelessPlayer | undefined; + private _active: boolean = false; + + get player(): ChromelessPlayer | undefined { + return this._player; + } + + @property({ reflect: false, attribute: false }) + set player(player: ChromelessPlayer | undefined) { + if (this._player === player) { + return; + } + this._player?.vr?.removeEventListener('statechange', this._onStateChange); + this._player = player; + this._player?.vr?.addEventListener('statechange', this._onStateChange); + this._onStateChange(); + } + + private readonly _onStateChange = (): void => { + if (!isIOS()) { + return; + } + const presenting = this._player?.vr?.state === 'presenting'; + if (presenting && !this._active) { + this._active = true; + this.dispatchEvent(createCustomEvent(ENTER_FULLSCREEN_EVENT, { bubbles: true, composed: true })); + } else if (!presenting && this._active) { + this._active = false; + this.dispatchEvent(createCustomEvent(EXIT_FULLSCREEN_EVENT, { bubbles: true, composed: true })); + } + }; + + override disconnectedCallback(): void { + super.disconnectedCallback(); + this._player?.vr?.removeEventListener('statechange', this._onStateChange); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'theoplayer-vr-ios-fullscreen': VRIOSFullscreen; + } +} diff --git a/src/components/index.ts b/src/components/index.ts index 5f527b0f..b6630115 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -32,6 +32,9 @@ export * from './TextTrackStyleMenu'; export * from './SettingsMenu'; export * from './SettingsMenuButton'; export * from './FullscreenButton'; +export * from './VRButton'; +export * from './VRCompass'; +export * from './VRIOSFullscreen'; export * from './Range'; export * from './TimeRange'; export * from './VolumeRange'; diff --git a/src/i18n/Locale.ts b/src/i18n/Locale.ts index e7bbcad7..6027a522 100644 --- a/src/i18n/Locale.ts +++ b/src/i18n/Locale.ts @@ -102,6 +102,18 @@ export interface Locale { * The {@link HTMLElement.ariaLabel | `aria-label`} for a {@link FullscreenButton} when in fullscreen mode. */ fullscreenExitAria: string; + /** + * The {@link HTMLElement.ariaLabel | `aria-label`} for a {@link VRButton}. + */ + vrAria: string; + /** + * The {@link HTMLElement.ariaLabel | `aria-label`} for a {@link VRButton} when VR (stereo) mode is active. + */ + vrExitAria: string; + /** + * The {@link HTMLElement.ariaLabel | `aria-label`} for a {@link VRButton} when no VR capable device is available. + */ + vrUnavailableAria: string; /** * The {@link HTMLElement.ariaLabel | `aria-label`} for an {@link AirPlayButton}. */ @@ -457,6 +469,9 @@ export const defaultLocale: Locale = { seekToLiveAria: 'seek to live', fullscreenAria: 'enter fullscreen', fullscreenExitAria: 'exit fullscreen', + vrAria: 'watch in VR', + vrExitAria: 'stop watching in VR', + vrUnavailableAria: 'no VR capable device found', airplayAria: 'start playing on AirPlay', airplayConnectedAria: 'stop playing on AirPlay', chromecastAria: 'start casting to Chromecast', diff --git a/src/icons/vr.svg b/src/icons/vr.svg new file mode 100644 index 00000000..cbe82cf8 --- /dev/null +++ b/src/icons/vr.svg @@ -0,0 +1 @@ + diff --git a/src/util/Attribute.ts b/src/util/Attribute.ts index 4ca445ac..6eb4b113 100644 --- a/src/util/Attribute.ts +++ b/src/util/Attribute.ts @@ -34,6 +34,7 @@ export enum Attribute { REMAINING = 'remaining', REMAINING_WHEN_LIVE = 'remaining-when-live', SHOW_DURATION = 'show-duration', + STEREO = 'stereo', SEEK_OFFSET = 'seek-offset', MENU = 'menu', MENU_OPENED = 'menu-opened', From 38995bb0fdb86e6b830a7024efe93338f41ec4a6 Mon Sep 17 00:00:00 2001 From: ceyhun-o Date: Tue, 16 Jun 2026 13:22:42 +0200 Subject: [PATCH 04/14] Add useDeviceMotionControls check to PlayButton --- src/components/PlayButton.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/PlayButton.ts b/src/components/PlayButton.ts index 40dd466d..030aff57 100644 --- a/src/components/PlayButton.ts +++ b/src/components/PlayButton.ts @@ -108,6 +108,10 @@ export class PlayButton extends Button { } else { this._player.pause(); } + const vr = this._player.vr; + if (vr !== undefined && vr.state !== 'unavailable') { + vr.useDeviceMotionControls = true; + } } } From 6d8dce38d4d71e0578eb9086cc577b3cf4400943 Mon Sep 17 00:00:00 2001 From: ceyhun-o Date: Tue, 16 Jun 2026 13:23:03 +0200 Subject: [PATCH 05/14] Prevent pausing while dragging on VR video --- src/components/GestureReceiver.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/GestureReceiver.ts b/src/components/GestureReceiver.ts index 9996ea0e..69c5a6ab 100644 --- a/src/components/GestureReceiver.ts +++ b/src/components/GestureReceiver.ts @@ -85,6 +85,11 @@ export class GestureReceiver extends LitElement { // Clicking during a linear ad should open the ad's clickthrough URL instead. return; } + if (this._player.vr && this._player.vr.state !== 'unavailable') { + // While VR is active, mouse drags are used to look around the 360° video, + // so a click should not toggle play/pause. + return; + } if (this._player.paused) { this._player.play(); } else { From 9fe4d567c0cc5a86d1579ffeca345fd77b402ad1 Mon Sep 17 00:00:00 2001 From: ceyhun-o Date: Tue, 16 Jun 2026 13:23:44 +0200 Subject: [PATCH 06/14] Add VR controls to DefaultUI --- src/DefaultUI.css | 9 +++++++++ src/DefaultUI.ts | 3 +++ 2 files changed, 12 insertions(+) diff --git a/src/DefaultUI.css b/src/DefaultUI.css index 3192d960..6eac07e7 100644 --- a/src/DefaultUI.css +++ b/src/DefaultUI.css @@ -55,6 +55,15 @@ theoplayer-ui { pointer-events: none; } +/* Place the VR compass in the top-right corner by default (can be overridden). */ +[part='middle-chrome'] theoplayer-vr-compass { + position: absolute; + top: 0; + right: 0; + z-index: 1; + margin: 0.5em; +} + [part='bottom-chrome'] { position: relative; display: flex; diff --git a/src/DefaultUI.ts b/src/DefaultUI.ts index b4c48b4f..319be433 100644 --- a/src/DefaultUI.ts +++ b/src/DefaultUI.ts @@ -410,6 +410,8 @@ export class DefaultUI extends LitElement {
+ +
@@ -446,6 +448,7 @@ export class DefaultUI extends LitElement { + From 024706b6f0aaa4079004e0ea4be04d084dfa982d Mon Sep 17 00:00:00 2001 From: ceyhun-o Date: Tue, 16 Jun 2026 13:24:23 +0200 Subject: [PATCH 07/14] Add React wrappers for VR components --- react/src/components/VRButton.tsx | 17 +++++++++++++++++ react/src/components/VRCompass.tsx | 15 +++++++++++++++ react/src/components/VRIOSFullscreen.tsx | 15 +++++++++++++++ react/src/components/index.ts | 3 +++ 4 files changed, 50 insertions(+) create mode 100644 react/src/components/VRButton.tsx create mode 100644 react/src/components/VRCompass.tsx create mode 100644 react/src/components/VRIOSFullscreen.tsx diff --git a/react/src/components/VRButton.tsx b/react/src/components/VRButton.tsx new file mode 100644 index 00000000..00c91353 --- /dev/null +++ b/react/src/components/VRButton.tsx @@ -0,0 +1,17 @@ +import { createComponent } from '@lit/react'; +import { VRButton as VRButtonElement } from '@theoplayer/web-ui'; +import * as React from 'react'; +import { ButtonEvents } from './Button'; + +/** + * See {@link @theoplayer/web-ui!VRButton | VRButton in @theoplayer/web-ui}. + * + * @group Components + */ +export const VRButton = createComponent({ + tagName: 'theoplayer-vr-button', + displayName: 'VRButton', + elementClass: VRButtonElement, + react: React, + events: ButtonEvents +}); diff --git a/react/src/components/VRCompass.tsx b/react/src/components/VRCompass.tsx new file mode 100644 index 00000000..39d4be77 --- /dev/null +++ b/react/src/components/VRCompass.tsx @@ -0,0 +1,15 @@ +import { createComponent } from '@lit/react'; +import { VRCompass as VRCompassElement } from '@theoplayer/web-ui'; +import * as React from 'react'; + +/** + * See {@link @theoplayer/web-ui!VRCompass | VRCompass in @theoplayer/web-ui}. + * + * @group Components + */ +export const VRCompass = createComponent({ + tagName: 'theoplayer-vr-compass', + displayName: 'VRCompass', + elementClass: VRCompassElement, + react: React +}); diff --git a/react/src/components/VRIOSFullscreen.tsx b/react/src/components/VRIOSFullscreen.tsx new file mode 100644 index 00000000..fbebd870 --- /dev/null +++ b/react/src/components/VRIOSFullscreen.tsx @@ -0,0 +1,15 @@ +import { createComponent } from '@lit/react'; +import { VRIOSFullscreen as VRIOSFullscreenElement } from '@theoplayer/web-ui'; +import * as React from 'react'; + +/** + * See {@link @theoplayer/web-ui!VRIOSFullscreen | VRIOSFullscreen in @theoplayer/web-ui}. + * + * @group Components + */ +export const VRIOSFullscreen = createComponent({ + tagName: 'theoplayer-vr-ios-fullscreen', + displayName: 'VRIOSFullscreen', + elementClass: VRIOSFullscreenElement, + react: React +}); diff --git a/react/src/components/index.ts b/react/src/components/index.ts index f6156b64..ddc274e0 100644 --- a/react/src/components/index.ts +++ b/react/src/components/index.ts @@ -32,6 +32,9 @@ export * from './TextTrackStyleMenu'; export * from './SettingsMenu'; export * from './SettingsMenuButton'; export * from './FullscreenButton'; +export * from './VRButton'; +export * from './VRCompass'; +export * from './VRIOSFullscreen'; export * from './Range'; export * from './TimeRange'; export * from './VolumeRange'; From 97c417b1cfd5de40f67c61334f953598baf30026 Mon Sep 17 00:00:00 2001 From: ceyhun-o Date: Tue, 16 Jun 2026 13:25:55 +0200 Subject: [PATCH 08/14] Update demos --- examples/default-ui.html | 15 +++++++++++++++ examples/locale/nl.js | 3 +++ examples/react/default-ui.html | 15 +++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/examples/default-ui.html b/examples/default-ui.html index 6c00b9b0..96a1299b 100644 --- a/examples/default-ui.html +++ b/examples/default-ui.html @@ -21,6 +21,11 @@ background: #000; } + + + +