mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-17 03:10:23 +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 { CastPlayerProgressBar } from "@/components/casting/player/CastPlayerProgressBar";
|
||||||
import { CastPlayerTitle } from "@/components/casting/player/CastPlayerTitle";
|
import { CastPlayerTitle } from "@/components/casting/player/CastPlayerTitle";
|
||||||
import { CastPlayerTransportControls } from "@/components/casting/player/CastPlayerTransportControls";
|
import { CastPlayerTransportControls } from "@/components/casting/player/CastPlayerTransportControls";
|
||||||
|
import { ChapterList } from "@/components/chapters/ChapterList";
|
||||||
import { ChromecastDeviceSheet } from "@/components/chromecast/ChromecastDeviceSheet";
|
import { ChromecastDeviceSheet } from "@/components/chromecast/ChromecastDeviceSheet";
|
||||||
import { ChromecastEpisodeList } from "@/components/chromecast/ChromecastEpisodeList";
|
import { ChromecastEpisodeList } from "@/components/chromecast/ChromecastEpisodeList";
|
||||||
import { ChromecastSettingsMenu } from "@/components/chromecast/ChromecastSettingsMenu";
|
import { ChromecastSettingsMenu } from "@/components/chromecast/ChromecastSettingsMenu";
|
||||||
@@ -44,6 +45,7 @@ import { loadCastMedia } from "@/utils/casting/castLoad";
|
|||||||
import { getPosterUrl } from "@/utils/casting/helpers";
|
import { getPosterUrl } from "@/utils/casting/helpers";
|
||||||
import { resolveSelection } from "@/utils/casting/selection";
|
import { resolveSelection } from "@/utils/casting/selection";
|
||||||
import type { CastSelection } from "@/utils/casting/types";
|
import type { CastSelection } from "@/utils/casting/types";
|
||||||
|
import { chapterMarkers } from "@/utils/chapters";
|
||||||
import {
|
import {
|
||||||
type PlaybackController,
|
type PlaybackController,
|
||||||
useRegisterPlaybackController,
|
useRegisterPlaybackController,
|
||||||
@@ -116,6 +118,11 @@ export default function CastingPlayerScreen() {
|
|||||||
const [showEpisodeList, setShowEpisodeList] = useState(false);
|
const [showEpisodeList, setShowEpisodeList] = useState(false);
|
||||||
const [showDeviceSheet, setShowDeviceSheet] = useState(false);
|
const [showDeviceSheet, setShowDeviceSheet] = useState(false);
|
||||||
const [showSettings, setShowSettings] = 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);
|
const [currentPlaybackSpeed, setCurrentPlaybackSpeed] = useState(1);
|
||||||
|
|
||||||
@@ -516,6 +523,8 @@ export default function CastingPlayerScreen() {
|
|||||||
nextEpisode={nextEpisode}
|
nextEpisode={nextEpisode}
|
||||||
remoteMediaClient={remoteMediaClient}
|
remoteMediaClient={remoteMediaClient}
|
||||||
onPressEpisodes={() => setShowEpisodeList(true)}
|
onPressEpisodes={() => setShowEpisodeList(true)}
|
||||||
|
hasChapters={hasChapters}
|
||||||
|
onPressChapters={() => setChapterListVisible(true)}
|
||||||
loadEpisode={loadEpisode}
|
loadEpisode={loadEpisode}
|
||||||
router={router}
|
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
|
<ChromecastSettingsMenu
|
||||||
visible={showSettings}
|
visible={showSettings}
|
||||||
onClose={() => setShowSettings(false)}
|
onClose={() => setShowSettings(false)}
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ interface CastPlayerEpisodeControlsProps {
|
|||||||
remoteMediaClient: RemoteMediaClient | null;
|
remoteMediaClient: RemoteMediaClient | null;
|
||||||
/** Open the episode list modal. */
|
/** Open the episode list modal. */
|
||||||
onPressEpisodes: () => void;
|
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. */
|
/** Load a different episode on the Chromecast. */
|
||||||
loadEpisode: (episode: BaseItemDto) => Promise<void>;
|
loadEpisode: (episode: BaseItemDto) => Promise<void>;
|
||||||
/** Expo Router instance for navigation on stop. */
|
/** Expo Router instance for navigation on stop. */
|
||||||
@@ -40,6 +44,8 @@ export function CastPlayerEpisodeControls({
|
|||||||
nextEpisode,
|
nextEpisode,
|
||||||
remoteMediaClient,
|
remoteMediaClient,
|
||||||
onPressEpisodes,
|
onPressEpisodes,
|
||||||
|
hasChapters,
|
||||||
|
onPressChapters,
|
||||||
loadEpisode,
|
loadEpisode,
|
||||||
router,
|
router,
|
||||||
}: CastPlayerEpisodeControlsProps) {
|
}: CastPlayerEpisodeControlsProps) {
|
||||||
@@ -51,7 +57,11 @@ export function CastPlayerEpisodeControls({
|
|||||||
|
|
||||||
// Count of buttons actually rendered (Stop is always rendered).
|
// Count of buttons actually rendered (Stop is always rendered).
|
||||||
const buttonCount =
|
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.
|
// When Stop is the only button (movies), render it full-width with a label.
|
||||||
const isLoneStop = buttonCount === 1;
|
const isLoneStop = buttonCount === 1;
|
||||||
@@ -89,6 +99,13 @@ export function CastPlayerEpisodeControls({
|
|||||||
</Pressable>
|
</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 */}
|
{/* Previous episode button - only rendered when a previous episode exists */}
|
||||||
{hasPrevious && (
|
{hasPrevious && (
|
||||||
<Pressable
|
<Pressable
|
||||||
|
|||||||
Reference in New Issue
Block a user