mirror of
https://github.com/p-stream/p-stream.git
synced 2026-04-21 15:02:18 +00:00
add pause overlay
This commit is contained in:
parent
a128a2cb19
commit
887dfa2ad5
14 changed files with 190 additions and 5 deletions
|
|
@ -1257,7 +1257,10 @@
|
||||||
"homeSectionOrder": "Home section order",
|
"homeSectionOrder": "Home section order",
|
||||||
"homeSectionOrderDescription": "Drag and drop to reorder the watching and bookmarks sections on your homepage. Group order can be editied from the home page.",
|
"homeSectionOrderDescription": "Drag and drop to reorder the watching and bookmarks sections on your homepage. Group order can be editied from the home page.",
|
||||||
"forceCompactEpisodeViewLabel": "Compact episodes",
|
"forceCompactEpisodeViewLabel": "Compact episodes",
|
||||||
"homeSectionOrderGroups": "Reorder bookmark groups"
|
"homeSectionOrderGroups": "Reorder bookmark groups",
|
||||||
|
"pauseOverlay": "Pause overlay",
|
||||||
|
"pauseOverlayDescription": "Show a title/logo and description overlay when the player is paused and idle.",
|
||||||
|
"pauseOverlayLabel": "Pause overlay"
|
||||||
},
|
},
|
||||||
"sections": {
|
"sections": {
|
||||||
"watching": "Currently Watching",
|
"watching": "Currently Watching",
|
||||||
|
|
@ -1442,8 +1445,7 @@
|
||||||
"genreMovies": "{{genre}} Movies",
|
"genreMovies": "{{genre}} Movies",
|
||||||
"genreShows": "{{genre}} Shows",
|
"genreShows": "{{genre}} Shows",
|
||||||
"categoryMovies": "{{category}} Movies",
|
"categoryMovies": "{{category}} Movies",
|
||||||
"categoryShows": "{{category}} Shows",
|
"categoryShows": "{{category}} Shows"
|
||||||
"top10": "Top 10"
|
|
||||||
},
|
},
|
||||||
"change": "Change",
|
"change": "Change",
|
||||||
"more": "View more"
|
"more": "View more"
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ export interface SettingsInput {
|
||||||
manualSourceSelection?: boolean;
|
manualSourceSelection?: boolean;
|
||||||
enableDoubleClickToSeek?: boolean;
|
enableDoubleClickToSeek?: boolean;
|
||||||
enableAutoResumeOnPlaybackError?: boolean;
|
enableAutoResumeOnPlaybackError?: boolean;
|
||||||
|
enablePauseOverlay?: boolean;
|
||||||
enableNumberKeySeeking?: boolean;
|
enableNumberKeySeeking?: boolean;
|
||||||
keyboardShortcuts?: KeyboardShortcuts;
|
keyboardShortcuts?: KeyboardShortcuts;
|
||||||
customTheme?: CustomThemeSettings;
|
customTheme?: CustomThemeSettings;
|
||||||
|
|
@ -83,6 +84,7 @@ export interface SettingsResponse {
|
||||||
manualSourceSelection?: boolean;
|
manualSourceSelection?: boolean;
|
||||||
enableDoubleClickToSeek?: boolean;
|
enableDoubleClickToSeek?: boolean;
|
||||||
enableAutoResumeOnPlaybackError?: boolean;
|
enableAutoResumeOnPlaybackError?: boolean;
|
||||||
|
enablePauseOverlay?: boolean;
|
||||||
enableNumberKeySeeking?: boolean;
|
enableNumberKeySeeking?: boolean;
|
||||||
keyboardShortcuts?: KeyboardShortcuts;
|
keyboardShortcuts?: KeyboardShortcuts;
|
||||||
customTheme?: CustomThemeSettings;
|
customTheme?: CustomThemeSettings;
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ export function formatTMDBMetaResult(
|
||||||
object_type: mediaTypeToTMDB(type),
|
object_type: mediaTypeToTMDB(type),
|
||||||
poster: getMediaPoster(movie.poster_path) ?? undefined,
|
poster: getMediaPoster(movie.poster_path) ?? undefined,
|
||||||
original_release_date: new Date(movie.release_date),
|
original_release_date: new Date(movie.release_date),
|
||||||
|
overview: movie.overview || undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (type === MWMediaType.SERIES) {
|
if (type === MWMediaType.SERIES) {
|
||||||
|
|
@ -62,6 +63,7 @@ export function formatTMDBMetaResult(
|
||||||
})),
|
})),
|
||||||
poster: getMediaPoster(show.poster_path) ?? undefined,
|
poster: getMediaPoster(show.poster_path) ?? undefined,
|
||||||
original_release_date: new Date(show.first_air_date),
|
original_release_date: new Date(show.first_air_date),
|
||||||
|
overview: show.overview,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ export function formatTMDBMeta(
|
||||||
year: media.original_release_date?.getFullYear()?.toString(),
|
year: media.original_release_date?.getFullYear()?.toString(),
|
||||||
poster: media.poster,
|
poster: media.poster,
|
||||||
type,
|
type,
|
||||||
|
overview: media.overview,
|
||||||
seasons: seasons as any,
|
seasons: seasons as any,
|
||||||
seasonData: season
|
seasonData: season
|
||||||
? {
|
? {
|
||||||
|
|
@ -408,8 +409,18 @@ export async function getMediaDetails<
|
||||||
const item = seasonsQueue.shift();
|
const item = seasonsQueue.shift();
|
||||||
if (!item) break;
|
if (!item) break;
|
||||||
const { season, index } = item;
|
const { season, index } = item;
|
||||||
const episodes = await getSeasonDetails(id, season.season_number);
|
const seasonData = await get<TMDBSeason>(
|
||||||
allEpisodesBySeason[index] = episodes;
|
`/tv/${id}/season/${season.season_number}`,
|
||||||
|
);
|
||||||
|
allEpisodesBySeason[index] = seasonData.episodes.map((episode) => ({
|
||||||
|
id: episode.id,
|
||||||
|
name: episode.name,
|
||||||
|
episode_number: episode.episode_number,
|
||||||
|
overview: episode.overview,
|
||||||
|
still_path: episode.still_path,
|
||||||
|
air_date: episode.air_date,
|
||||||
|
season_number: season.season_number,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ type MWMediaMetaBase = {
|
||||||
id: string;
|
id: string;
|
||||||
year?: string;
|
year?: string;
|
||||||
poster?: string;
|
poster?: string;
|
||||||
|
overview?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type MWMediaMetaSpecific =
|
type MWMediaMetaSpecific =
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ export type TMDBMediaResult = {
|
||||||
original_release_date?: Date;
|
original_release_date?: Date;
|
||||||
object_type: TMDBContentTypes;
|
object_type: TMDBContentTypes;
|
||||||
seasons?: TMDBSeasonShort[];
|
seasons?: TMDBSeasonShort[];
|
||||||
|
overview?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TMDBSeasonMetaResult = {
|
export type TMDBSeasonMetaResult = {
|
||||||
|
|
|
||||||
|
|
@ -36,17 +36,20 @@ export function usePlayerMeta() {
|
||||||
poster: m.meta.poster,
|
poster: m.meta.poster,
|
||||||
tmdbId: m.tmdbId ?? "",
|
tmdbId: m.tmdbId ?? "",
|
||||||
imdbId: m.imdbId,
|
imdbId: m.imdbId,
|
||||||
|
overview: m.meta.overview,
|
||||||
episodes: m.meta.seasonData.episodes.map((v) => ({
|
episodes: m.meta.seasonData.episodes.map((v) => ({
|
||||||
number: v.number,
|
number: v.number,
|
||||||
title: v.title,
|
title: v.title,
|
||||||
tmdbId: v.id,
|
tmdbId: v.id,
|
||||||
air_date: v.air_date,
|
air_date: v.air_date,
|
||||||
|
overview: v.overview,
|
||||||
})),
|
})),
|
||||||
episode: {
|
episode: {
|
||||||
number: ep.number,
|
number: ep.number,
|
||||||
title: ep.title,
|
title: ep.title,
|
||||||
tmdbId: ep.id,
|
tmdbId: ep.id,
|
||||||
air_date: ep.air_date,
|
air_date: ep.air_date,
|
||||||
|
overview: ep.overview,
|
||||||
},
|
},
|
||||||
season: {
|
season: {
|
||||||
number: m.meta.seasonData.number,
|
number: m.meta.seasonData.number,
|
||||||
|
|
@ -62,6 +65,7 @@ export function usePlayerMeta() {
|
||||||
poster: m.meta.poster,
|
poster: m.meta.poster,
|
||||||
tmdbId: m.tmdbId ?? "",
|
tmdbId: m.tmdbId ?? "",
|
||||||
imdbId: m.imdbId,
|
imdbId: m.imdbId,
|
||||||
|
overview: m.meta.overview,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
setDirectMeta(playerMeta);
|
setDirectMeta(playerMeta);
|
||||||
|
|
|
||||||
87
src/components/player/overlays/PauseOverlay.tsx
Normal file
87
src/components/player/overlays/PauseOverlay.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useIdle } from "react-use";
|
||||||
|
|
||||||
|
import { getMediaLogo } from "@/backend/metadata/tmdb";
|
||||||
|
import { TMDBContentTypes } from "@/backend/metadata/types/tmdb";
|
||||||
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
import { usePreferencesStore } from "@/stores/preferences";
|
||||||
|
|
||||||
|
export function PauseOverlay() {
|
||||||
|
const isIdle = useIdle(10e3); // 10 seconds
|
||||||
|
const isPaused = usePlayerStore((s) => s.mediaPlaying.isPaused);
|
||||||
|
const meta = usePlayerStore((s) => s.meta);
|
||||||
|
const enablePauseOverlay = usePreferencesStore((s) => s.enablePauseOverlay);
|
||||||
|
const enableImageLogos = usePreferencesStore((s) => s.enableImageLogos);
|
||||||
|
const [logoUrl, setLogoUrl] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const shouldShow = isPaused && isIdle && enablePauseOverlay;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let mounted = true;
|
||||||
|
const fetchLogo = async () => {
|
||||||
|
if (!meta?.tmdbId || !enableImageLogos) {
|
||||||
|
setLogoUrl(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const type =
|
||||||
|
meta.type === "movie" ? TMDBContentTypes.MOVIE : TMDBContentTypes.TV;
|
||||||
|
const url = await getMediaLogo(meta.tmdbId, type);
|
||||||
|
if (mounted) setLogoUrl(url || null);
|
||||||
|
} catch {
|
||||||
|
if (mounted) setLogoUrl(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchLogo();
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
};
|
||||||
|
}, [meta?.tmdbId, meta?.type, enableImageLogos]);
|
||||||
|
|
||||||
|
if (!meta) return null;
|
||||||
|
|
||||||
|
const overview =
|
||||||
|
meta.type === "show" ? meta.episode?.overview : meta.overview;
|
||||||
|
|
||||||
|
// Don't render anything if we don't have content, but keep structure for fade if valid
|
||||||
|
const hasContent = overview || logoUrl || meta.title;
|
||||||
|
if (!hasContent) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`absolute inset-0 z-[60] flex items-center bg-black/60 transition-opacity duration-500 ${
|
||||||
|
shouldShow
|
||||||
|
? "opacity-100 pointer-events-auto"
|
||||||
|
: "opacity-0 pointer-events-none"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="ml-16 max-w-2xl p-8 pointer-events-auto">
|
||||||
|
{logoUrl ? (
|
||||||
|
<img
|
||||||
|
src={logoUrl}
|
||||||
|
alt={meta.title}
|
||||||
|
className="mb-6 max-h-32 object-contain drop-shadow-lg"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<h1 className="mb-4 text-4xl font-bold text-white drop-shadow-lg">
|
||||||
|
{meta.title}
|
||||||
|
</h1>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{meta.type === "show" && meta.episode && (
|
||||||
|
<h2 className="mb-2 text-2xl font-semibold text-white/90 drop-shadow-md">
|
||||||
|
{meta.episode.title}
|
||||||
|
</h2>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{overview && (
|
||||||
|
<p className="text-lg text-white/80 drop-shadow-md line-clamp-6">
|
||||||
|
{overview}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -81,6 +81,7 @@ export function useSettingsState(
|
||||||
manualSourceSelection: boolean,
|
manualSourceSelection: boolean,
|
||||||
enableDoubleClickToSeek: boolean,
|
enableDoubleClickToSeek: boolean,
|
||||||
enableAutoResumeOnPlaybackError: boolean,
|
enableAutoResumeOnPlaybackError: boolean,
|
||||||
|
enablePauseOverlay: boolean,
|
||||||
customTheme: {
|
customTheme: {
|
||||||
primary: string;
|
primary: string;
|
||||||
secondary: string;
|
secondary: string;
|
||||||
|
|
@ -277,6 +278,12 @@ export function useSettingsState(
|
||||||
resetEnableAutoResumeOnPlaybackError,
|
resetEnableAutoResumeOnPlaybackError,
|
||||||
enableAutoResumeOnPlaybackErrorChanged,
|
enableAutoResumeOnPlaybackErrorChanged,
|
||||||
] = useDerived(enableAutoResumeOnPlaybackError);
|
] = useDerived(enableAutoResumeOnPlaybackError);
|
||||||
|
const [
|
||||||
|
enablePauseOverlayState,
|
||||||
|
setEnablePauseOverlayState,
|
||||||
|
resetEnablePauseOverlay,
|
||||||
|
enablePauseOverlayChanged,
|
||||||
|
] = useDerived(enablePauseOverlay);
|
||||||
const [
|
const [
|
||||||
customThemeState,
|
customThemeState,
|
||||||
setCustomThemeState,
|
setCustomThemeState,
|
||||||
|
|
@ -323,6 +330,7 @@ export function useSettingsState(
|
||||||
resetManualSourceSelection();
|
resetManualSourceSelection();
|
||||||
resetEnableDoubleClickToSeek();
|
resetEnableDoubleClickToSeek();
|
||||||
resetEnableAutoResumeOnPlaybackError();
|
resetEnableAutoResumeOnPlaybackError();
|
||||||
|
resetEnablePauseOverlay();
|
||||||
resetCustomTheme();
|
resetCustomTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -364,6 +372,7 @@ export function useSettingsState(
|
||||||
manualSourceSelectionChanged ||
|
manualSourceSelectionChanged ||
|
||||||
enableDoubleClickToSeekChanged ||
|
enableDoubleClickToSeekChanged ||
|
||||||
enableAutoResumeOnPlaybackErrorChanged ||
|
enableAutoResumeOnPlaybackErrorChanged ||
|
||||||
|
enablePauseOverlayChanged ||
|
||||||
customThemeChanged;
|
customThemeChanged;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -554,6 +563,11 @@ export function useSettingsState(
|
||||||
set: setEnableAutoResumeOnPlaybackErrorState,
|
set: setEnableAutoResumeOnPlaybackErrorState,
|
||||||
changed: enableAutoResumeOnPlaybackErrorChanged,
|
changed: enableAutoResumeOnPlaybackErrorChanged,
|
||||||
},
|
},
|
||||||
|
enablePauseOverlay: {
|
||||||
|
state: enablePauseOverlayState,
|
||||||
|
set: setEnablePauseOverlayState,
|
||||||
|
changed: enablePauseOverlayChanged,
|
||||||
|
},
|
||||||
customTheme: {
|
customTheme: {
|
||||||
state: customThemeState,
|
state: customThemeState,
|
||||||
set: (v: { primary: string; secondary: string; tertiary: string }) => {
|
set: (v: { primary: string; secondary: string; tertiary: string }) => {
|
||||||
|
|
|
||||||
|
|
@ -531,6 +531,11 @@ export function SettingsPage() {
|
||||||
(s) => s.setEnableAutoResumeOnPlaybackError,
|
(s) => s.setEnableAutoResumeOnPlaybackError,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const enablePauseOverlay = usePreferencesStore((s) => s.enablePauseOverlay);
|
||||||
|
const setEnablePauseOverlay = usePreferencesStore(
|
||||||
|
(s) => s.setEnablePauseOverlay,
|
||||||
|
);
|
||||||
|
|
||||||
const account = useAuthStore((s) => s.account);
|
const account = useAuthStore((s) => s.account);
|
||||||
const updateProfile = useAuthStore((s) => s.setAccountProfile);
|
const updateProfile = useAuthStore((s) => s.setAccountProfile);
|
||||||
const updateDeviceName = useAuthStore((s) => s.updateDeviceName);
|
const updateDeviceName = useAuthStore((s) => s.updateDeviceName);
|
||||||
|
|
@ -647,6 +652,9 @@ export function SettingsPage() {
|
||||||
settings.enableAutoResumeOnPlaybackError,
|
settings.enableAutoResumeOnPlaybackError,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (settings.enablePauseOverlay !== undefined) {
|
||||||
|
setEnablePauseOverlay(settings.enablePauseOverlay);
|
||||||
|
}
|
||||||
if (settings.customTheme) {
|
if (settings.customTheme) {
|
||||||
setCustomTheme(settings.customTheme);
|
setCustomTheme(settings.customTheme);
|
||||||
setCustomThemeBaseline(settings.customTheme);
|
setCustomThemeBaseline(settings.customTheme);
|
||||||
|
|
@ -687,6 +695,7 @@ export function SettingsPage() {
|
||||||
setManualSourceSelection,
|
setManualSourceSelection,
|
||||||
setEnableDoubleClickToSeek,
|
setEnableDoubleClickToSeek,
|
||||||
setEnableAutoResumeOnPlaybackError,
|
setEnableAutoResumeOnPlaybackError,
|
||||||
|
setEnablePauseOverlay,
|
||||||
setCustomTheme,
|
setCustomTheme,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -728,6 +737,7 @@ export function SettingsPage() {
|
||||||
manualSourceSelection,
|
manualSourceSelection,
|
||||||
enableDoubleClickToSeek,
|
enableDoubleClickToSeek,
|
||||||
enableAutoResumeOnPlaybackError,
|
enableAutoResumeOnPlaybackError,
|
||||||
|
enablePauseOverlay,
|
||||||
customThemeBaseline ?? customTheme,
|
customThemeBaseline ?? customTheme,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -797,6 +807,7 @@ export function SettingsPage() {
|
||||||
state.manualSourceSelection.changed ||
|
state.manualSourceSelection.changed ||
|
||||||
state.enableDoubleClickToSeek.changed ||
|
state.enableDoubleClickToSeek.changed ||
|
||||||
state.enableAutoResumeOnPlaybackError.changed ||
|
state.enableAutoResumeOnPlaybackError.changed ||
|
||||||
|
state.enablePauseOverlay.changed ||
|
||||||
state.customTheme.changed
|
state.customTheme.changed
|
||||||
) {
|
) {
|
||||||
await updateSettings(backendUrl, account, {
|
await updateSettings(backendUrl, account, {
|
||||||
|
|
@ -829,6 +840,7 @@ export function SettingsPage() {
|
||||||
enableDoubleClickToSeek: state.enableDoubleClickToSeek.state,
|
enableDoubleClickToSeek: state.enableDoubleClickToSeek.state,
|
||||||
enableAutoResumeOnPlaybackError:
|
enableAutoResumeOnPlaybackError:
|
||||||
state.enableAutoResumeOnPlaybackError.state,
|
state.enableAutoResumeOnPlaybackError.state,
|
||||||
|
enablePauseOverlay: state.enablePauseOverlay.state,
|
||||||
customTheme: state.customTheme.state,
|
customTheme: state.customTheme.state,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -889,6 +901,7 @@ export function SettingsPage() {
|
||||||
setEnableAutoResumeOnPlaybackError(
|
setEnableAutoResumeOnPlaybackError(
|
||||||
state.enableAutoResumeOnPlaybackError.state,
|
state.enableAutoResumeOnPlaybackError.state,
|
||||||
);
|
);
|
||||||
|
setEnablePauseOverlay(state.enablePauseOverlay.state);
|
||||||
setCustomTheme(state.customTheme.state);
|
setCustomTheme(state.customTheme.state);
|
||||||
setCustomThemeBaseline(state.customTheme.state);
|
setCustomThemeBaseline(state.customTheme.state);
|
||||||
|
|
||||||
|
|
@ -951,6 +964,7 @@ export function SettingsPage() {
|
||||||
setManualSourceSelection,
|
setManualSourceSelection,
|
||||||
setEnableDoubleClickToSeek,
|
setEnableDoubleClickToSeek,
|
||||||
setEnableAutoResumeOnPlaybackError,
|
setEnableAutoResumeOnPlaybackError,
|
||||||
|
setEnablePauseOverlay,
|
||||||
setCustomTheme,
|
setCustomTheme,
|
||||||
]);
|
]);
|
||||||
return (
|
return (
|
||||||
|
|
@ -1067,6 +1081,8 @@ export function SettingsPage() {
|
||||||
homeSectionOrder={state.homeSectionOrder.state}
|
homeSectionOrder={state.homeSectionOrder.state}
|
||||||
setHomeSectionOrder={state.homeSectionOrder.set}
|
setHomeSectionOrder={state.homeSectionOrder.set}
|
||||||
enableLowPerformanceMode={state.enableLowPerformanceMode.state}
|
enableLowPerformanceMode={state.enableLowPerformanceMode.state}
|
||||||
|
enablePauseOverlay={state.enablePauseOverlay.state}
|
||||||
|
setEnablePauseOverlay={state.enablePauseOverlay.set}
|
||||||
customTheme={state.customTheme.state}
|
customTheme={state.customTheme.state}
|
||||||
setCustomTheme={state.customTheme.set}
|
setCustomTheme={state.customTheme.set}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
SegmentData,
|
SegmentData,
|
||||||
useSkipTime,
|
useSkipTime,
|
||||||
} from "@/components/player/hooks/useSkipTime";
|
} from "@/components/player/hooks/useSkipTime";
|
||||||
|
import { PauseOverlay } from "@/components/player/overlays/PauseOverlay";
|
||||||
import { useIsMobile } from "@/hooks/useIsMobile";
|
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||||
import { PlayerMeta, playerStatus } from "@/stores/player/slices/source";
|
import { PlayerMeta, playerStatus } from "@/stores/player/slices/source";
|
||||||
import { usePlayerStore } from "@/stores/player/store";
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
|
@ -99,6 +100,7 @@ export function PlayerPart(props: PlayerPartProps) {
|
||||||
return (
|
return (
|
||||||
<Player.Container onLoad={props.onLoad} showingControls={showTargets}>
|
<Player.Container onLoad={props.onLoad} showingControls={showTargets}>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
<PauseOverlay />
|
||||||
<Player.BlackOverlay
|
<Player.BlackOverlay
|
||||||
show={showTargets && status === playerStatus.PLAYING}
|
show={showTargets && status === playerStatus.PLAYING}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,9 @@ export function AppearancePart(props: {
|
||||||
enableImageLogos: boolean;
|
enableImageLogos: boolean;
|
||||||
setEnableImageLogos: (v: boolean) => void;
|
setEnableImageLogos: (v: boolean) => void;
|
||||||
|
|
||||||
|
enablePauseOverlay: boolean;
|
||||||
|
setEnablePauseOverlay: (v: boolean) => void;
|
||||||
|
|
||||||
enableCarouselView: boolean;
|
enableCarouselView: boolean;
|
||||||
setEnableCarouselView: (v: boolean) => void;
|
setEnableCarouselView: (v: boolean) => void;
|
||||||
|
|
||||||
|
|
@ -355,6 +358,7 @@ export function AppearancePart(props: {
|
||||||
setEnableFeatured,
|
setEnableFeatured,
|
||||||
setEnableDetailsModal,
|
setEnableDetailsModal,
|
||||||
setEnableImageLogos,
|
setEnableImageLogos,
|
||||||
|
setEnablePauseOverlay,
|
||||||
setForceCompactEpisodeView,
|
setForceCompactEpisodeView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
|
@ -365,6 +369,7 @@ export function AppearancePart(props: {
|
||||||
setEnableFeatured(false);
|
setEnableFeatured(false);
|
||||||
setEnableDetailsModal(false);
|
setEnableDetailsModal(false);
|
||||||
setEnableImageLogos(false);
|
setEnableImageLogos(false);
|
||||||
|
setEnablePauseOverlay(false);
|
||||||
setForceCompactEpisodeView(true);
|
setForceCompactEpisodeView(true);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
|
@ -373,6 +378,7 @@ export function AppearancePart(props: {
|
||||||
setEnableFeatured,
|
setEnableFeatured,
|
||||||
setEnableDetailsModal,
|
setEnableDetailsModal,
|
||||||
setEnableImageLogos,
|
setEnableImageLogos,
|
||||||
|
setEnablePauseOverlay,
|
||||||
setForceCompactEpisodeView,
|
setForceCompactEpisodeView,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -553,6 +559,33 @@ export function AppearancePart(props: {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Pause Overlay */}
|
||||||
|
<div>
|
||||||
|
<p className="text-white font-bold mb-3">
|
||||||
|
{t("settings.appearance.options.pauseOverlay")}
|
||||||
|
</p>
|
||||||
|
<p className="max-w-[25rem] font-medium">
|
||||||
|
{t("settings.appearance.options.pauseOverlayDescription")}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
onClick={() =>
|
||||||
|
!props.enableLowPerformanceMode &&
|
||||||
|
props.setEnablePauseOverlay(!props.enablePauseOverlay)
|
||||||
|
}
|
||||||
|
className={classNames(
|
||||||
|
"bg-dropdown-background hover:bg-dropdown-hoverBackground select-none my-4 cursor-pointer space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg",
|
||||||
|
props.enableLowPerformanceMode
|
||||||
|
? "cursor-not-allowed opacity-50 pointer-events-none"
|
||||||
|
: "cursor-pointer opacity-100 pointer-events-auto",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Toggle enabled={props.enablePauseOverlay} />
|
||||||
|
<p className="flex-1 text-white font-bold">
|
||||||
|
{t("settings.appearance.options.pauseOverlayLabel")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Carousel View */}
|
{/* Carousel View */}
|
||||||
<div>
|
<div>
|
||||||
<p className="text-white font-bold mb-3">
|
<p className="text-white font-bold mb-3">
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ export interface PlayerMetaEpisode {
|
||||||
tmdbId: string;
|
tmdbId: string;
|
||||||
title: string;
|
title: string;
|
||||||
air_date?: string;
|
air_date?: string;
|
||||||
|
overview?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlayerMeta {
|
export interface PlayerMeta {
|
||||||
|
|
@ -38,6 +39,7 @@ export interface PlayerMeta {
|
||||||
imdbId?: string;
|
imdbId?: string;
|
||||||
releaseYear: number;
|
releaseYear: number;
|
||||||
poster?: string;
|
poster?: string;
|
||||||
|
overview?: string;
|
||||||
episodes?: PlayerMetaEpisode[];
|
episodes?: PlayerMetaEpisode[];
|
||||||
episode?: PlayerMetaEpisode;
|
episode?: PlayerMetaEpisode;
|
||||||
season?: {
|
season?: {
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ export interface PreferencesStore {
|
||||||
enableDoubleClickToSeek: boolean;
|
enableDoubleClickToSeek: boolean;
|
||||||
enableAutoResumeOnPlaybackError: boolean;
|
enableAutoResumeOnPlaybackError: boolean;
|
||||||
enableNumberKeySeeking: boolean;
|
enableNumberKeySeeking: boolean;
|
||||||
|
enablePauseOverlay: boolean;
|
||||||
keyboardShortcuts: KeyboardShortcuts;
|
keyboardShortcuts: KeyboardShortcuts;
|
||||||
|
|
||||||
setEnableThumbnails(v: boolean): void;
|
setEnableThumbnails(v: boolean): void;
|
||||||
|
|
@ -72,6 +73,7 @@ export interface PreferencesStore {
|
||||||
setEnableDoubleClickToSeek(v: boolean): void;
|
setEnableDoubleClickToSeek(v: boolean): void;
|
||||||
setEnableAutoResumeOnPlaybackError(v: boolean): void;
|
setEnableAutoResumeOnPlaybackError(v: boolean): void;
|
||||||
setEnableNumberKeySeeking(v: boolean): void;
|
setEnableNumberKeySeeking(v: boolean): void;
|
||||||
|
setEnablePauseOverlay(v: boolean): void;
|
||||||
setKeyboardShortcuts(v: KeyboardShortcuts): void;
|
setKeyboardShortcuts(v: KeyboardShortcuts): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,6 +111,7 @@ export const usePreferencesStore = create(
|
||||||
enableDoubleClickToSeek: false,
|
enableDoubleClickToSeek: false,
|
||||||
enableAutoResumeOnPlaybackError: true,
|
enableAutoResumeOnPlaybackError: true,
|
||||||
enableNumberKeySeeking: true,
|
enableNumberKeySeeking: true,
|
||||||
|
enablePauseOverlay: false,
|
||||||
keyboardShortcuts: DEFAULT_KEYBOARD_SHORTCUTS,
|
keyboardShortcuts: DEFAULT_KEYBOARD_SHORTCUTS,
|
||||||
setEnableThumbnails(v) {
|
setEnableThumbnails(v) {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
|
|
@ -270,6 +273,11 @@ export const usePreferencesStore = create(
|
||||||
s.enableNumberKeySeeking = v;
|
s.enableNumberKeySeeking = v;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setEnablePauseOverlay(v) {
|
||||||
|
set((s) => {
|
||||||
|
s.enablePauseOverlay = v;
|
||||||
|
});
|
||||||
|
},
|
||||||
setKeyboardShortcuts(v) {
|
setKeyboardShortcuts(v) {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
s.keyboardShortcuts = v;
|
s.keyboardShortcuts = v;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue