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..4d4317a23 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,15 @@ export class THEOplayerWebAdapter extends DefaultEventDispatcher this._player?.removeEventListener('dimensionchange', this.onPlayerDimensionChange); 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 + 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;