From c480104759b6a6423ea5c04c68ca7ad05dbdcb89 Mon Sep 17 00:00:00 2001 From: Mikolaj Adamowicz Date: Tue, 9 Jun 2026 20:13:20 +0200 Subject: [PATCH 1/2] Destroy player and listeners on web unmount --- src/internal/THEOplayerView.web.tsx | 28 ++++++++++++++----- src/internal/adapter/THEOplayerWebAdapter.ts | 7 +++++ .../adapter/web/WebPresentationModeManager.ts | 8 ++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/internal/THEOplayerView.web.tsx b/src/internal/THEOplayerView.web.tsx index de3767aeb..836223bfd 100644 --- a/src/internal/THEOplayerView.web.tsx +++ b/src/internal/THEOplayerView.web.tsx @@ -32,12 +32,11 @@ export function THEOplayerView(props: React.PropsWithChildren { - // Notify the player will be destroyed. - if (adapter?.current && onPlayerDestroy) { - onPlayerDestroy(adapter?.current); + const adapterRef = adapter.current; + const playerRef = player.current; + if (adapterRef) { + onPlayerDestroy?.(adapterRef); + adapterRef.destroy(); + adapter.current = null; + } else if (playerRef) { + // Adapter construction failed between `new ChromelessPlayer(...)` + // and `new THEOplayerWebAdapter(...)` — destroy the raw player ourselves. + playerRef.destroy(); + } + player.current = null; + if (typeof window !== 'undefined') { + // Only clear globals if they still point to our instances — a second + // player may have mounted and overwritten them. + // @ts-ignore + if (window.player === adapterRef) window.player = undefined; + // @ts-ignore + if (window.nativePlayer === playerRef) window.nativePlayer = undefined; } - adapter?.current?.destroy(); }; // TODO: Follow the rules of react hooks, to be fixed in next major because it's a breaking change for some customers. // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/internal/adapter/THEOplayerWebAdapter.ts b/src/internal/adapter/THEOplayerWebAdapter.ts index 1f4a1642e..44fd0617c 100644 --- a/src/internal/adapter/THEOplayerWebAdapter.ts +++ b/src/internal/adapter/THEOplayerWebAdapter.ts @@ -385,6 +385,7 @@ export class THEOplayerWebAdapter extends DefaultEventDispatcher this.dispatchEvent(new BaseEvent(PlayerEventType.DESTROY)); this._eventForwarder?.unload(); this._mediaSession?.destroy(); + this._presentationModeManager?.destroy(); document.removeEventListener('visibilitychange', this.onVisibilityChange); this._eventForwarder = undefined; this._mediaSession = undefined; @@ -393,6 +394,12 @@ export class THEOplayerWebAdapter extends DefaultEventDispatcher this._player?.removeEventListener('dimensionchange', this.onPlayerDimensionChange); this._player?.destroy(); this._player = undefined; + + // @ts-ignore + if (typeof window !== 'undefined' && window.__onGCastApiAvailable) { + // @ts-ignore + window.__onGCastApiAvailable = undefined; + } } private readonly onVisibilityChange = () => { diff --git a/src/internal/adapter/web/WebPresentationModeManager.ts b/src/internal/adapter/web/WebPresentationModeManager.ts index dd45a8132..dbc380fef 100644 --- a/src/internal/adapter/web/WebPresentationModeManager.ts +++ b/src/internal/adapter/web/WebPresentationModeManager.ts @@ -65,6 +65,14 @@ export class WebPresentationModeManager { } } + destroy(): void { + if (fullscreenAPI !== undefined) { + document.removeEventListener(fullscreenAPI.fullscreenchange_, this.updatePresentationMode); + document.removeEventListener(fullscreenAPI.fullscreenerror_, this.updatePresentationMode); + } + this._player?.presentation?.removeEventListener('presentationmodechange', this.updatePresentationMode); + } + private updatePresentationMode = () => { // detect new presentation mode let newPresentationMode: PresentationMode = PresentationMode.inline; From fbc485bf18c41d90e47ab1e29d69d063df8f19eb Mon Sep 17 00:00:00 2001 From: Mikolaj Adamowicz Date: Sun, 14 Jun 2026 21:21:01 +0200 Subject: [PATCH 2/2] chore: adjusted to comments --- src/internal/adapter/THEOplayerWebAdapter.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/internal/adapter/THEOplayerWebAdapter.ts b/src/internal/adapter/THEOplayerWebAdapter.ts index 44fd0617c..4d4317a23 100644 --- a/src/internal/adapter/THEOplayerWebAdapter.ts +++ b/src/internal/adapter/THEOplayerWebAdapter.ts @@ -395,6 +395,9 @@ export class THEOplayerWebAdapter extends DefaultEventDispatcher this._player?.destroy(); this._player = undefined; + // We clear this global always. If there are two players on the same page, + // this can also clear the callback of the other player. This is fine, + // because using Cast with multiple players on web is not supported. // @ts-ignore if (typeof window !== 'undefined' && window.__onGCastApiAvailable) { // @ts-ignore