From 750caba038d584511df4c72364d6405dd5985d42 Mon Sep 17 00:00:00 2001 From: Uruk Date: Fri, 22 May 2026 02:11:20 +0200 Subject: [PATCH] feat(casting): register cast PlaybackController for remote control --- app/(auth)/casting-player.tsx | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/app/(auth)/casting-player.tsx b/app/(auth)/casting-player.tsx index 10f353360..aa81161c2 100644 --- a/app/(auth)/casting-player.tsx +++ b/app/(auth)/casting-player.tsx @@ -44,6 +44,10 @@ import { loadCastMedia } from "@/utils/casting/castLoad"; import { getPosterUrl } from "@/utils/casting/helpers"; import { resolveSelection } from "@/utils/casting/selection"; import type { CastSelection } from "@/utils/casting/types"; +import { + type PlaybackController, + useRegisterPlaybackController, +} from "@/utils/playback/playbackController"; export default function CastingPlayerScreen() { const insets = useSafeAreaInsets(); @@ -177,6 +181,47 @@ export default function CastingPlayerScreen() { settings, }); + // Expose this player to the app-wide remote-control surface while a cast + // session is connected. `castingControls` is the live useCasting result. + const castController = useMemo( + () => ({ + playPause: () => { + castingControls.togglePlayPause(); + }, + pause: () => { + castingControls.pause(); + }, + unpause: () => { + castingControls.play(); + }, + stop: () => { + castingControls.stop(); + }, + seek: (positionMs) => { + castingControls.seek(positionMs); + }, + next: () => { + if (nextEpisode) loadEpisode(nextEpisode); + }, + previous: () => { + const idx = episodes.findIndex((e) => e.Id === currentItem?.Id); + if (idx > 0) loadEpisode(episodes[idx - 1]); + }, + setVolume: (level) => { + castingControls.setVolume(level); + }, + toggleMute: () => { + castingControls.setVolume(castingControls.volume > 0 ? 0 : 1); + }, + }), + [castingControls, episodes, nextEpisode, loadEpisode, currentItem?.Id], + ); + + useRegisterPlaybackController( + castController, + castState === CastState.CONNECTED, + ); + // The MediaSource currently selected, for deriving its tracks. // Derived from fetchedItem: the slim cast-customData item strips per-source // MediaStreams, so only the full fetched item yields correct track lists.