useFullscreen is now a thin useContext consumer of FullscreenProvider,
so all callers share a single fullscreen state owned by the app root.
Why this fixes the desync bug:
stremio-router keeps multiple route layers mounted at once, and each
top-level route (Board, Discover, Library, Calendar, Addons, Settings,
Search) renders its own MainNavBars -> HorizontalNavBar -> useFullscreen.
The previous hook held local useState plus its own listeners, so each
route had an independent boolean. Entering fullscreen, then navigating
to another tab, mounted a fresh hook initialized to false; the icon
flipped back to "enter fullscreen" and clicking it re-requested
fullscreen on top of the existing one, leaving the UI unresponsive
until a route remount happened to coincide with reality.
With one provider above the router, state outlives route remounts and
listeners are attached exactly once. The hook's return tuple shape
([fullscreen, requestFullscreen, exitFullscreen, toggleFullscreen]) is
preserved, so all three call sites (HorizontalNavBar, NavMenuContent,
Player) keep working with no API change.
Also removes the legacy src/common/useFullscreen.ts and routes its
imports through stremio/common/Fullscreen (and the stremio/common
barrel for App.js / Player).
Note: MainNavBars is still rendered per-route. Lifting it to a single
app-level layout above the router is a worthwhile follow-up (eliminates
6+ duplicate mounts) but carries non-trivial CSS / useRouteFocused /
stacked-route risk and is out of scope for this PR; tracking separately.
Made-with: Cursor
Wrap the router with <FullscreenProvider> so a single provider instance
spans the whole app lifetime. The legacy useFullscreen hook is still
intact and continues to drive consumers; the provider is in place but
not yet consumed. Splitting this from the consumer cutover keeps each
commit independently buildable.
Made-with: Cursor
Introduce a single, app-root-owned source of truth for fullscreen state,
mirroring the existing provider pattern (ToastProvider, FileDropProvider).
The provider centralizes the fullscreenchange / win-visibility-changed /
keydown listeners and exposes the same [fullscreen, requestFullscreen,
exitFullscreen, toggleFullscreen] tuple that consumers already destructure.
Not yet wired up - both the legacy src/common/useFullscreen hook and the
new module coexist. Subsequent commits mount the provider in App.js and
switch consumers over.
Made-with: Cursor
The 'next-track' media-key handler called video.setTime(0) before
checking whether a next video existed. onNextVideoRequested() no-ops
when player.nextVideo is null, but the unconditional setTime(0) had
already rewound the current stream — causing movies (which have no
next video) to restart from the beginning when the Windows next-track
media key was pressed.
Guard both calls with the same player.nextVideo check that the
navigator.mediaSession 'nexttrack' handler already uses.