mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-02 20:18:29 +01:00
feat(casting): chapter list button in the cast player
This commit is contained in:
@@ -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() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<ChapterList
|
||||
visible={chapterListVisible}
|
||||
chapters={chapters}
|
||||
currentPositionMs={progress * 1000}
|
||||
onSeek={(ms) => {
|
||||
remoteMediaClient?.seek({ position: ms / 1000 });
|
||||
}}
|
||||
onClose={() => setChapterListVisible(false)}
|
||||
/>
|
||||
|
||||
<ChromecastSettingsMenu
|
||||
visible={showSettings}
|
||||
onClose={() => setShowSettings(false)}
|
||||
|
||||
@@ -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<void>;
|
||||
/** 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({
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
{/* Chapter list button - rendered for both episodes and movies when chapters exist */}
|
||||
{hasChapters && (
|
||||
<Pressable onPress={onPressChapters} style={buttonStyle}>
|
||||
<Ionicons name='bookmarks' size={22} color='white' />
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
{/* Previous episode button - only rendered when a previous episode exists */}
|
||||
{hasPrevious && (
|
||||
<Pressable
|
||||
|
||||
Reference in New Issue
Block a user