mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-17 11:20:29 +01:00
feat(player): add media segment skip with all 5 Jellyfin segment types
Closes #1312 Fixes #883 Adds a unified segment skip feature using the Jellyfin 10.11+ MediaSegments API. Replaces the legacy intro-only and credits-only hooks with a single useSegmentSkipper hook covering Intro, Outro, Recap, Commercial, and Preview. Three modes per segment type: none, ask (show button), auto (skip automatically). A dedicated submenu under Playback Controls keeps the main settings page uncluttered. Highlights: - utils/segments.ts uses getMediaSegmentsApi from @jellyfin/sdk so includeSegmentTypes is serialized as repeated keys instead of the bracket-encoded form axios produces by default (the Jellyfin server silently ignored the filter otherwise). Falls back to the pre-10.11 intro-skipper / chapter-credits plugin endpoints when the new API is unavailable. - hooks/useSegmentSkipper.ts stores seek and haptic in refs so the auto-skip effect does not re-run when their identities change (useHaptic returns a fresh no-op every render when disabled). currentSegment is memoized; the per-segment-type setting lookup uses a small map instead of a switch IIFE. - components/video-player/controls/Controls.tsx prioritizes Commercial > Recap > Intro > Preview > Outro when multiple segments overlap and exposes the active type to BottomControls via skipButtonText. - components/video-player/controls/BottomControls.tsx accepts the dynamic skipButtonText/skipCreditButtonText props. - providers/Downloads/types.ts extends DownloadedItem with the three new segment buckets for offline playback. - utils/atoms/settings.ts adds SegmentSkipMode and the five skip settings, defaulting to "ask". - app/(auth)/(tabs)/(home)/settings/segment-skip/page.tsx renders the five dropdowns from a data table. - translations/en.json and translations/fr.json add the new keys.
This commit is contained in:
@@ -19,10 +19,27 @@ import { Text } from "@/components/common/Text";
|
||||
import { scaleSize } from "@/utils/scaleSize";
|
||||
import { useTVFocusAnimation } from "./hooks/useTVFocusAnimation";
|
||||
|
||||
export type TVSkipSegmentType =
|
||||
| "intro"
|
||||
| "credits"
|
||||
| "outro"
|
||||
| "recap"
|
||||
| "commercial"
|
||||
| "preview";
|
||||
|
||||
const SEGMENT_LABEL_KEY: Record<TVSkipSegmentType, string> = {
|
||||
intro: "player.skip_intro",
|
||||
credits: "player.skip_credits",
|
||||
outro: "player.skip_outro",
|
||||
recap: "player.skip_recap",
|
||||
commercial: "player.skip_commercial",
|
||||
preview: "player.skip_preview",
|
||||
};
|
||||
|
||||
export interface TVSkipSegmentCardProps {
|
||||
show: boolean;
|
||||
onPress: () => void;
|
||||
type: "intro" | "credits";
|
||||
type: TVSkipSegmentType;
|
||||
/** Whether controls are visible - affects card position */
|
||||
controlsVisible?: boolean;
|
||||
/** Callback ref setter for focus guide destination pattern */
|
||||
@@ -72,8 +89,7 @@ export const TVSkipSegmentCard: FC<TVSkipSegmentCardProps> = ({
|
||||
bottom: bottomPosition.value,
|
||||
}));
|
||||
|
||||
const labelText =
|
||||
type === "intro" ? t("player.skip_intro") : t("player.skip_credits");
|
||||
const labelText = t(SEGMENT_LABEL_KEY[type]);
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user