From 03d2917ca002201ae36d6de2b41fde21fc994f97 Mon Sep 17 00:00:00 2001 From: Uruk Date: Fri, 22 May 2026 11:59:25 +0200 Subject: [PATCH] feat(casting): chapter list button in the cast player --- app/(auth)/casting-player.tsx | 19 +++++++++++++++++++ .../player/CastPlayerEpisodeControls.tsx | 19 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/app/(auth)/casting-player.tsx b/app/(auth)/casting-player.tsx index 63e3e0ec0..55da55a7c 100644 --- a/app/(auth)/casting-player.tsx +++ b/app/(auth)/casting-player.tsx @@ -26,6 +26,7 @@ import { CastPlayerPoster } from "@/components/casting/player/CastPlayerPoster"; import { CastPlayerProgressBar } from "@/components/casting/player/CastPlayerProgressBar"; import { CastPlayerTitle } from "@/components/casting/player/CastPlayerTitle"; import { CastPlayerTransportControls } from "@/components/casting/player/CastPlayerTransportControls"; +import { ChapterList } from "@/components/chapters/ChapterList"; import { ChromecastDeviceSheet } from "@/components/chromecast/ChromecastDeviceSheet"; import { ChromecastEpisodeList } from "@/components/chromecast/ChromecastEpisodeList"; import { ChromecastSettingsMenu } from "@/components/chromecast/ChromecastSettingsMenu"; @@ -44,6 +45,7 @@ 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 { chapterMarkers } from "@/utils/chapters"; import { type PlaybackController, useRegisterPlaybackController, @@ -116,6 +118,11 @@ export default function CastingPlayerScreen() { const [showEpisodeList, setShowEpisodeList] = useState(false); const [showDeviceSheet, setShowDeviceSheet] = useState(false); const [showSettings, setShowSettings] = useState(false); + const [chapterListVisible, setChapterListVisible] = useState(false); + + // Chapter markers (shown for both episodes and movies). + const chapters = currentItem?.Chapters; + const hasChapters = chapterMarkers(chapters, duration * 1000).length > 1; const [currentPlaybackSpeed, setCurrentPlaybackSpeed] = useState(1); @@ -516,6 +523,8 @@ export default function CastingPlayerScreen() { nextEpisode={nextEpisode} remoteMediaClient={remoteMediaClient} onPressEpisodes={() => setShowEpisodeList(true)} + hasChapters={hasChapters} + onPressChapters={() => setChapterListVisible(true)} loadEpisode={loadEpisode} router={router} /> @@ -613,6 +622,16 @@ export default function CastingPlayerScreen() { }} /> + { + remoteMediaClient?.seek({ position: ms / 1000 }); + }} + onClose={() => setChapterListVisible(false)} + /> + setShowSettings(false)} diff --git a/components/casting/player/CastPlayerEpisodeControls.tsx b/components/casting/player/CastPlayerEpisodeControls.tsx index 3ed130b68..56306c422 100644 --- a/components/casting/player/CastPlayerEpisodeControls.tsx +++ b/components/casting/player/CastPlayerEpisodeControls.tsx @@ -27,6 +27,10 @@ interface CastPlayerEpisodeControlsProps { remoteMediaClient: RemoteMediaClient | null; /** Open the episode list modal. */ onPressEpisodes: () => void; + /** Whether the current item exposes chapter markers. */ + hasChapters: boolean; + /** Open the chapter list modal. */ + onPressChapters: () => void; /** Load a different episode on the Chromecast. */ loadEpisode: (episode: BaseItemDto) => Promise; /** Expo Router instance for navigation on stop. */ @@ -40,6 +44,8 @@ export function CastPlayerEpisodeControls({ nextEpisode, remoteMediaClient, onPressEpisodes, + hasChapters, + onPressChapters, loadEpisode, router, }: CastPlayerEpisodeControlsProps) { @@ -51,7 +57,11 @@ export function CastPlayerEpisodeControls({ // Count of buttons actually rendered (Stop is always rendered). const buttonCount = - 1 + (hasEpisodeList ? 1 : 0) + (hasPrevious ? 1 : 0) + (hasNext ? 1 : 0); + 1 + + (hasEpisodeList ? 1 : 0) + + (hasChapters ? 1 : 0) + + (hasPrevious ? 1 : 0) + + (hasNext ? 1 : 0); // When Stop is the only button (movies), render it full-width with a label. const isLoneStop = buttonCount === 1; @@ -89,6 +99,13 @@ export function CastPlayerEpisodeControls({ )} + {/* Chapter list button - rendered for both episodes and movies when chapters exist */} + {hasChapters && ( + + + + )} + {/* Previous episode button - only rendered when a previous episode exists */} {hasPrevious && (