diff --git a/src/App/App.js b/src/App/App.js
index eecca48ea..375c1f5db 100644
--- a/src/App/App.js
+++ b/src/App/App.js
@@ -6,7 +6,7 @@ const { useTranslation } = require('react-i18next');
const { Router } = require('stremio-router');
const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider, GamepadProvider } = require('stremio/services');
const { NotFound } = require('stremio/routes');
-const { FileDropProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, withCoreSuspender, useShell, useBinaryState } = require('stremio/common');
+const { FileDropProvider, FullscreenProvider, PlatformProvider, ToastProvider, TooltipProvider, ShortcutsProvider, CONSTANTS, withCoreSuspender, useShell, useBinaryState } = require('stremio/common');
const ServicesToaster = require('./ServicesToaster');
const DeepLinkHandler = require('./DeepLinkHandler');
const SearchParamsHandler = require('./SearchParamsHandler');
@@ -231,21 +231,23 @@ const App = () => {
- {
- shortcutModalOpen &&
- }
- {
- gamepadModalOpen &&
- }
-
-
-
-
-
+
+ {
+ shortcutModalOpen &&
+ }
+ {
+ gamepadModalOpen &&
+ }
+
+
+
+
+
+
diff --git a/src/common/Fullscreen/FullscreenContext.ts b/src/common/Fullscreen/FullscreenContext.ts
new file mode 100644
index 000000000..1c9599ffb
--- /dev/null
+++ b/src/common/Fullscreen/FullscreenContext.ts
@@ -0,0 +1,16 @@
+// Copyright (C) 2017-2026 Smart code 203358507
+
+import { createContext } from 'react';
+
+export type FullscreenContextValue = readonly [
+ fullscreen: boolean,
+ requestFullscreen: () => Promise | void,
+ exitFullscreen: () => void,
+ toggleFullscreen: () => void,
+];
+
+const FullscreenContext = createContext(null);
+
+FullscreenContext.displayName = 'FullscreenContext';
+
+export default FullscreenContext;
diff --git a/src/common/Fullscreen/FullscreenProvider.tsx b/src/common/Fullscreen/FullscreenProvider.tsx
new file mode 100644
index 000000000..2300602c5
--- /dev/null
+++ b/src/common/Fullscreen/FullscreenProvider.tsx
@@ -0,0 +1,109 @@
+// Copyright (C) 2017-2026 Smart code 203358507
+
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { withCoreSuspender } from '../CoreSuspender';
+import onShortcut from '../Shortcuts/onShortcut';
+import useSettings from '../useSettings';
+import useShell, { type WindowVisibility } from '../useShell';
+import FullscreenContext, { type FullscreenContextValue } from './FullscreenContext';
+
+type Props = {
+ children: React.ReactNode,
+};
+
+const isTextInputFocused = () => {
+ const activeElement = document.activeElement;
+
+ return activeElement instanceof HTMLElement &&
+ (activeElement.tagName === 'INPUT' ||
+ activeElement.tagName === 'TEXTAREA' ||
+ activeElement.tagName === 'SELECT' ||
+ activeElement.isContentEditable);
+};
+
+const FullscreenProvider = ({ children }: Props) => {
+ const shell = useShell();
+ const [settings] = useSettings();
+ const escExitFullscreen = settings.escExitFullscreen;
+
+ const [fullscreen, setFullscreen] = useState(() => {
+ if (typeof document === 'undefined') return false;
+ return document.fullscreenElement === document.documentElement;
+ });
+
+ const requestFullscreen = useCallback(async () => {
+ if (shell.active) {
+ shell.send('win-set-visibility', { fullscreen: true });
+ } else {
+ try {
+ await document.documentElement.requestFullscreen();
+ } catch (err) {
+ console.error('Error enabling fullscreen', err);
+ }
+ }
+ }, [shell]);
+
+ const exitFullscreen = useCallback(() => {
+ if (shell.active) {
+ shell.send('win-set-visibility', { fullscreen: false });
+ } else {
+ if (document.fullscreenElement === document.documentElement) {
+ document.exitFullscreen();
+ }
+ }
+ }, [shell]);
+
+ const toggleFullscreen = useCallback(() => {
+ fullscreen ? exitFullscreen() : requestFullscreen();
+ }, [fullscreen, exitFullscreen, requestFullscreen]);
+
+ const toggleFullscreenFromShortcut = useCallback(() => {
+ if (isTextInputFocused()) return;
+ toggleFullscreen();
+ }, [toggleFullscreen]);
+
+ onShortcut('fullscreen', toggleFullscreenFromShortcut, [toggleFullscreenFromShortcut]);
+
+ useEffect(() => {
+ const onWindowVisibilityChanged = (state: WindowVisibility) => {
+ setFullscreen(state.isFullscreen === true);
+ };
+
+ const onFullscreenChange = () => {
+ setFullscreen(document.fullscreenElement === document.documentElement);
+ };
+
+ const onKeyDown = (event: KeyboardEvent) => {
+ if (event.code === 'Escape' && escExitFullscreen) {
+ exitFullscreen();
+ }
+
+ if (event.code === 'F11' && shell.active) {
+ toggleFullscreen();
+ }
+ };
+
+ shell.on('win-visibility-changed', onWindowVisibilityChanged);
+ document.addEventListener('keydown', onKeyDown);
+ document.addEventListener('fullscreenchange', onFullscreenChange);
+
+ return () => {
+ shell.off('win-visibility-changed', onWindowVisibilityChanged);
+ document.removeEventListener('keydown', onKeyDown);
+ document.removeEventListener('fullscreenchange', onFullscreenChange);
+ };
+ }, [shell, toggleFullscreen, exitFullscreen, escExitFullscreen]);
+
+ const value = useMemo(
+ () => [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen],
+ [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen]
+ );
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default withCoreSuspender(FullscreenProvider);
diff --git a/src/common/Fullscreen/index.ts b/src/common/Fullscreen/index.ts
new file mode 100644
index 000000000..db65974ac
--- /dev/null
+++ b/src/common/Fullscreen/index.ts
@@ -0,0 +1,7 @@
+// Copyright (C) 2017-2026 Smart code 203358507
+
+import FullscreenProvider from './FullscreenProvider';
+import useFullscreen from './useFullscreen';
+
+export { FullscreenProvider, useFullscreen };
+export default useFullscreen;
diff --git a/src/common/Fullscreen/useFullscreen.ts b/src/common/Fullscreen/useFullscreen.ts
new file mode 100644
index 000000000..5cee0a801
--- /dev/null
+++ b/src/common/Fullscreen/useFullscreen.ts
@@ -0,0 +1,15 @@
+// Copyright (C) 2017-2026 Smart code 203358507
+
+import { useContext } from 'react';
+import FullscreenContext from './FullscreenContext';
+
+const useFullscreen = () => {
+ const value = useContext(FullscreenContext);
+ if (value === null) {
+ throw new Error('useFullscreen must be used inside FullscreenProvider');
+ }
+
+ return value;
+};
+
+export default useFullscreen;
diff --git a/src/common/index.js b/src/common/index.js
index e608e7e23..1963e8995 100644
--- a/src/common/index.js
+++ b/src/common/index.js
@@ -1,6 +1,7 @@
// Copyright (C) 2017-2023 Smart code 203358507
const { FileDropProvider, onFileDrop } = require('./FileDrop');
+const { FullscreenProvider, useFullscreen } = require('./Fullscreen');
const { PlatformProvider, usePlatform } = require('./Platform');
const { ToastProvider, useToast } = require('./Toast');
const { TooltipProvider, Tooltip } = require('./Tooltips');
@@ -14,7 +15,6 @@ const languages = require('./languages');
const routesRegexp = require('./routesRegexp');
const useAnimationFrame = require('./useAnimationFrame');
const useBinaryState = require('./useBinaryState');
-const { default: useFullscreen } = require('./useFullscreen');
const { default: useInterval } = require('./useInterval');
const useLiveRef = require('./useLiveRef');
const useModelState = require('./useModelState');
@@ -34,6 +34,7 @@ const { default: useLanguageSorting } = require('./useLanguageSorting');
module.exports = {
FileDropProvider,
onFileDrop,
+ FullscreenProvider,
PlatformProvider,
usePlatform,
ShortcutsProvider,
diff --git a/src/common/useFullscreen.ts b/src/common/useFullscreen.ts
deleted file mode 100644
index 8a1692254..000000000
--- a/src/common/useFullscreen.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (C) 2017-2023 Smart code 203358507
-
-import { useCallback, useEffect, useState } from 'react';
-import useShell, { type WindowVisibility } from './useShell';
-import useSettings from './useSettings';
-
-const useFullscreen = () => {
- const shell = useShell();
- const [settings] = useSettings();
-
- const [fullscreen, setFullscreen] = useState(false);
-
- const requestFullscreen = useCallback(async () => {
- if (shell.active) {
- shell.send('win-set-visibility', { fullscreen: true });
- } else {
- try {
- await document.documentElement.requestFullscreen();
- } catch (err) {
- console.error('Error enabling fullscreen', err);
- }
- }
- }, []);
-
- const exitFullscreen = useCallback(() => {
- if (shell.active) {
- shell.send('win-set-visibility', { fullscreen: false });
- } else {
- if (document.fullscreenElement === document.documentElement) {
- document.exitFullscreen();
- }
- }
- }, []);
-
- const toggleFullscreen = useCallback(() => {
- fullscreen ? exitFullscreen() : requestFullscreen();
- }, [fullscreen]);
-
- useEffect(() => {
- const onWindowVisibilityChanged = (state: WindowVisibility) => {
- setFullscreen(state.isFullscreen === true);
- };
-
- const onFullscreenChange = () => {
- setFullscreen(document.fullscreenElement === document.documentElement);
- };
-
- const onKeyDown = (event: KeyboardEvent) => {
-
- const activeElement = document.activeElement as HTMLElement;
-
- const inputFocused =
- activeElement &&
- (activeElement.tagName === 'INPUT' ||
- activeElement.tagName === 'TEXTAREA' ||
- activeElement.tagName === 'SELECT' ||
- activeElement.isContentEditable);
-
- if (event.code === 'Escape' && settings.escExitFullscreen) {
- exitFullscreen();
- }
-
- if (event.code === 'KeyF' && !inputFocused) {
- toggleFullscreen();
- }
-
- if (event.code === 'F11' && shell.active) {
- toggleFullscreen();
- }
- };
-
- shell.on('win-visibility-changed', onWindowVisibilityChanged);
- document.addEventListener('keydown', onKeyDown);
- document.addEventListener('fullscreenchange', onFullscreenChange);
-
- return () => {
- shell.off('win-visibility-changed', onWindowVisibilityChanged);
- document.removeEventListener('keydown', onKeyDown);
- document.removeEventListener('fullscreenchange', onFullscreenChange);
- };
- }, [settings.escExitFullscreen, toggleFullscreen]);
-
- return [fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen];
-};
-
-export default useFullscreen;
diff --git a/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js b/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js
index c6c02ab46..b1644c2b3 100644
--- a/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js
+++ b/src/components/NavBar/HorizontalNavBar/HorizontalNavBar.js
@@ -5,7 +5,7 @@ const PropTypes = require('prop-types');
const classnames = require('classnames');
const { default: Icon } = require('@stremio/stremio-icons/react');
const { Button, Image } = require('stremio/components');
-const { default: useFullscreen } = require('stremio/common/useFullscreen');
+const { useFullscreen } = require('stremio/common/Fullscreen');
const usePWA = require('stremio/common/usePWA');
const { useHorizontalNavGamepadNavigation } = require('stremio/services/GamepadNavigation');
const SearchBar = require('./SearchBar');
diff --git a/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js b/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js
index b6309a44c..6615e5b76 100644
--- a/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js
+++ b/src/components/NavBar/HorizontalNavBar/NavMenu/NavMenuContent.js
@@ -7,7 +7,7 @@ const { useTranslation } = require('react-i18next');
const { default: Icon } = require('@stremio/stremio-icons/react');
const { useServices } = require('stremio/services');
const { Button } = require('stremio/components');
-const { default: useFullscreen } = require('stremio/common/useFullscreen');
+const { useFullscreen } = require('stremio/common/Fullscreen');
const useProfile = require('stremio/common/useProfile');
const usePWA = require('stremio/common/usePWA');
const { default: usePlayUrl } = require('stremio/common/usePlayUrl');
diff --git a/src/services/GamepadNavigation/useHorizontalNavGamepadNavigation.tsx b/src/services/GamepadNavigation/useHorizontalNavGamepadNavigation.tsx
index 422489e3e..0d65a3fa1 100644
--- a/src/services/GamepadNavigation/useHorizontalNavGamepadNavigation.tsx
+++ b/src/services/GamepadNavigation/useHorizontalNavGamepadNavigation.tsx
@@ -2,7 +2,7 @@
import { useEffect } from 'react';
import { useGamepad } from '../GamepadContext';
-import useFullscreen from 'stremio/common/useFullscreen';
+import useFullscreen from 'stremio/common/Fullscreen';
const useHorizontalNavGamepadNavigation = (gamepadHandlerId: string, enableGoBack: boolean) => {
const gamepad = useGamepad();