mirror of
https://github.com/p-stream/p-stream.git
synced 2026-01-11 20:10:32 +00:00
make native subtitles a proper setting
This commit is contained in:
parent
30f0d4ac52
commit
0edb4ece08
6 changed files with 260 additions and 189 deletions
|
|
@ -23,6 +23,7 @@ export interface SettingsInput {
|
|||
enableSourceOrder?: boolean;
|
||||
proxyTmdb?: boolean;
|
||||
enableLowPerformanceMode?: boolean;
|
||||
enableNativeSubtitles?: boolean;
|
||||
}
|
||||
|
||||
export interface SettingsResponse {
|
||||
|
|
@ -44,6 +45,7 @@ export interface SettingsResponse {
|
|||
enableSourceOrder?: boolean;
|
||||
proxyTmdb?: boolean;
|
||||
enableLowPerformanceMode?: boolean;
|
||||
enableNativeSubtitles?: boolean;
|
||||
}
|
||||
|
||||
export function updateSettings(
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { Menu } from "@/components/player/internals/ContextMenu";
|
|||
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
|
||||
import { useProgressBar } from "@/hooks/useProgressBar";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
import { usePreferencesStore } from "@/stores/preferences";
|
||||
import { SubtitleStyling, useSubtitleStore } from "@/stores/subtitles";
|
||||
|
||||
export function ColorOption(props: {
|
||||
|
|
@ -229,6 +230,7 @@ export function CaptionSettingsView({
|
|||
const { t } = useTranslation();
|
||||
const router = useOverlayRouter(id);
|
||||
const subtitleStore = useSubtitleStore();
|
||||
const preferencesStore = usePreferencesStore();
|
||||
const styling = subtitleStore.styling;
|
||||
const overrideCasing = subtitleStore.overrideCasing;
|
||||
const delay = subtitleStore.delay;
|
||||
|
|
@ -236,12 +238,17 @@ export function CaptionSettingsView({
|
|||
const setDelay = subtitleStore.setDelay;
|
||||
const updateStyling = subtitleStore.updateStyling;
|
||||
const setCaptionAsTrack = usePlayerStore((s) => s.setCaptionAsTrack);
|
||||
const captionAsTrack = usePlayerStore((s) => s.caption.asTrack);
|
||||
const enableNativeSubtitles = preferencesStore.enableNativeSubtitles;
|
||||
|
||||
useEffect(() => {
|
||||
subtitleStore.updateStyling(styling);
|
||||
}, [styling, subtitleStore]);
|
||||
|
||||
// Sync preferences with player store
|
||||
useEffect(() => {
|
||||
setCaptionAsTrack(enableNativeSubtitles);
|
||||
}, [enableNativeSubtitles, setCaptionAsTrack]);
|
||||
|
||||
const handleStylingChange = (newStyling: SubtitleStyling) => {
|
||||
updateStyling(newStyling);
|
||||
};
|
||||
|
|
@ -267,7 +274,7 @@ export function CaptionSettingsView({
|
|||
{t("player.menus.subtitles.settings.backlink")}
|
||||
</Menu.BackLink>
|
||||
<Menu.Section className="space-y-6 pb-5">
|
||||
{!captionAsTrack ? (
|
||||
{!enableNativeSubtitles ? (
|
||||
<>
|
||||
<div className="flex justify-between items-center">
|
||||
<Menu.FieldTitle>
|
||||
|
|
@ -275,8 +282,12 @@ export function CaptionSettingsView({
|
|||
</Menu.FieldTitle>
|
||||
<div className="flex justify-center items-center">
|
||||
<Toggle
|
||||
enabled={captionAsTrack}
|
||||
onClick={() => setCaptionAsTrack(!captionAsTrack)}
|
||||
enabled={enableNativeSubtitles}
|
||||
onClick={() =>
|
||||
preferencesStore.setEnableNativeSubtitles(
|
||||
!enableNativeSubtitles,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -478,8 +489,12 @@ export function CaptionSettingsView({
|
|||
</Menu.FieldTitle>
|
||||
<div className="flex justify-center items-center">
|
||||
<Toggle
|
||||
enabled={captionAsTrack}
|
||||
onClick={() => setCaptionAsTrack(!captionAsTrack)}
|
||||
enabled={enableNativeSubtitles}
|
||||
onClick={() =>
|
||||
preferencesStore.setEnableNativeSubtitles(
|
||||
!enableNativeSubtitles,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -57,6 +57,9 @@ export function useAuthData() {
|
|||
const setEnableLowPerformanceMode = usePreferencesStore(
|
||||
(s) => s.setEnableLowPerformanceMode,
|
||||
);
|
||||
const setEnableNativeSubtitles = usePreferencesStore(
|
||||
(s) => s.setEnableNativeSubtitles,
|
||||
);
|
||||
|
||||
const login = useCallback(
|
||||
async (
|
||||
|
|
@ -164,6 +167,10 @@ export function useAuthData() {
|
|||
if (settings.enableLowPerformanceMode !== undefined) {
|
||||
setEnableLowPerformanceMode(settings.enableLowPerformanceMode);
|
||||
}
|
||||
|
||||
if (settings.enableNativeSubtitles !== undefined) {
|
||||
setEnableNativeSubtitles(settings.enableNativeSubtitles);
|
||||
}
|
||||
},
|
||||
[
|
||||
replaceBookmarks,
|
||||
|
|
@ -185,6 +192,7 @@ export function useAuthData() {
|
|||
setProxyTmdb,
|
||||
setFebboxKey,
|
||||
setEnableLowPerformanceMode,
|
||||
setEnableNativeSubtitles,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
|
|||
enableSourceOrder: store.enableSourceOrder,
|
||||
proxyTmdb: store.proxyTmdb,
|
||||
febboxKey: store.febboxKey,
|
||||
enableLowPerformanceMode: store.enableLowPerformanceMode,
|
||||
enableNativeSubtitles: store.enableNativeSubtitles,
|
||||
}));
|
||||
|
||||
const backendUrl = useBackendUrl();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import { Menu } from "@/components/player/internals/ContextMenu";
|
|||
import { CaptionCue } from "@/components/player/Player";
|
||||
import { Heading1 } from "@/components/utils/Text";
|
||||
import { Transition } from "@/components/utils/Transition";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
import { usePreferencesStore } from "@/stores/preferences";
|
||||
import { SubtitleStyling, useSubtitleStore } from "@/stores/subtitles";
|
||||
|
||||
export function CaptionPreview(props: {
|
||||
|
|
@ -86,11 +88,19 @@ export function CaptionsPart(props: {
|
|||
const [fullscreenPreview, setFullscreenPreview] = useState(false);
|
||||
|
||||
const subtitleStore = useSubtitleStore();
|
||||
const preferencesStore = usePreferencesStore();
|
||||
const setCaptionAsTrack = usePlayerStore((s) => s.setCaptionAsTrack);
|
||||
const enableNativeSubtitles = preferencesStore.enableNativeSubtitles;
|
||||
|
||||
useEffect(() => {
|
||||
subtitleStore.updateStyling(props.styling);
|
||||
}, [props.styling, subtitleStore, subtitleStore.updateStyling]);
|
||||
|
||||
// Sync preferences with player store
|
||||
useEffect(() => {
|
||||
setCaptionAsTrack(enableNativeSubtitles);
|
||||
}, [enableNativeSubtitles, setCaptionAsTrack]);
|
||||
|
||||
const handleStylingChange = (newStyling: SubtitleStyling) => {
|
||||
props.setStyling(newStyling);
|
||||
subtitleStore.updateStyling(newStyling);
|
||||
|
|
@ -114,203 +124,229 @@ export function CaptionsPart(props: {
|
|||
<Heading1 border>{t("settings.subtitles.title")}</Heading1>
|
||||
<div className="grid md:grid-cols-[1fr,356px] gap-8">
|
||||
<div className="space-y-6">
|
||||
<CaptionSetting
|
||||
label={t("settings.subtitles.backgroundLabel")}
|
||||
max={100}
|
||||
min={0}
|
||||
onChange={(v) =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
backgroundOpacity: v / 100,
|
||||
})
|
||||
}
|
||||
value={props.styling.backgroundOpacity * 100}
|
||||
textTransformer={(s) => `${s}%`}
|
||||
/>
|
||||
<CaptionSetting
|
||||
label={t("settings.subtitles.backgroundBlurLabel")}
|
||||
max={100}
|
||||
min={0}
|
||||
onChange={(v) =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
backgroundBlur: v / 100,
|
||||
})
|
||||
}
|
||||
value={props.styling.backgroundBlur * 100}
|
||||
textTransformer={(s) => `${s}%`}
|
||||
/>
|
||||
<CaptionSetting
|
||||
label={t("settings.subtitles.textSizeLabel")}
|
||||
max={200}
|
||||
min={1}
|
||||
textTransformer={(s) => `${s}%`}
|
||||
onChange={(v) =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
size: v / 100,
|
||||
})
|
||||
}
|
||||
value={props.styling.size * 100}
|
||||
/>
|
||||
<div className="flex justify-between items-center">
|
||||
<Menu.FieldTitle>
|
||||
{t("settings.subtitles.textStyle.title")}
|
||||
</Menu.FieldTitle>
|
||||
<div className="w-30">
|
||||
<Dropdown
|
||||
options={[
|
||||
{
|
||||
id: "default",
|
||||
name: t("settings.subtitles.textStyle.default"),
|
||||
},
|
||||
{
|
||||
id: "raised",
|
||||
name: t("settings.subtitles.textStyle.raised"),
|
||||
},
|
||||
{
|
||||
id: "depressed",
|
||||
name: t("settings.subtitles.textStyle.depressed"),
|
||||
},
|
||||
{
|
||||
id: "uniform",
|
||||
name: t("settings.subtitles.textStyle.uniform"),
|
||||
},
|
||||
{
|
||||
id: "dropShadow",
|
||||
name: t("settings.subtitles.textStyle.dropShadow"),
|
||||
},
|
||||
]}
|
||||
selectedItem={{
|
||||
id: props.styling.fontStyle,
|
||||
name:
|
||||
t(
|
||||
`settings.subtitles.textStyle.${props.styling.fontStyle}`,
|
||||
) || props.styling.fontStyle,
|
||||
}}
|
||||
setSelectedItem={(item) =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
fontStyle: item.id,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<Menu.FieldTitle>
|
||||
{t("settings.subtitles.textBoldLabel")}
|
||||
{t("player.menus.subtitles.useNativeSubtitles")}
|
||||
</Menu.FieldTitle>
|
||||
<div className="flex justify-center items-center">
|
||||
<Toggle
|
||||
enabled={props.styling.bold}
|
||||
enabled={enableNativeSubtitles}
|
||||
onClick={() =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
bold: !props.styling.bold,
|
||||
})
|
||||
preferencesStore.setEnableNativeSubtitles(
|
||||
!enableNativeSubtitles,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<Menu.FieldTitle>
|
||||
{t("settings.subtitles.colorLabel")}
|
||||
</Menu.FieldTitle>
|
||||
<div className="flex justify-center items-center space-x-2">
|
||||
{colors.map((v) => (
|
||||
<ColorOption
|
||||
onClick={() =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
color: v,
|
||||
})
|
||||
}
|
||||
color={v}
|
||||
active={props.styling.color === v}
|
||||
key={v}
|
||||
/>
|
||||
))}
|
||||
<div className="relative">
|
||||
<input
|
||||
type="color"
|
||||
value={props.styling.color}
|
||||
onChange={(e) => {
|
||||
const color = e.target.value;
|
||||
handleStylingChange({ ...props.styling, color });
|
||||
subtitleStore.updateStyling({
|
||||
...props.styling,
|
||||
color,
|
||||
});
|
||||
}}
|
||||
className="absolute opacity-0 cursor-pointer w-8 h-8"
|
||||
/>
|
||||
<div style={{ color: props.styling.color }}>
|
||||
<Icon icon={Icons.BRUSH} className="text-2xl" />
|
||||
<span className="text-xs text-type-secondary">
|
||||
{t("player.menus.subtitles.useNativeSubtitlesDescription")}
|
||||
</span>
|
||||
{!enableNativeSubtitles && (
|
||||
<>
|
||||
<CaptionSetting
|
||||
label={t("settings.subtitles.backgroundLabel")}
|
||||
max={100}
|
||||
min={0}
|
||||
onChange={(v) =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
backgroundOpacity: v / 100,
|
||||
})
|
||||
}
|
||||
value={props.styling.backgroundOpacity * 100}
|
||||
textTransformer={(s) => `${s}%`}
|
||||
/>
|
||||
<CaptionSetting
|
||||
label={t("settings.subtitles.backgroundBlurLabel")}
|
||||
max={100}
|
||||
min={0}
|
||||
onChange={(v) =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
backgroundBlur: v / 100,
|
||||
})
|
||||
}
|
||||
value={props.styling.backgroundBlur * 100}
|
||||
textTransformer={(s) => `${s}%`}
|
||||
/>
|
||||
<CaptionSetting
|
||||
label={t("settings.subtitles.textSizeLabel")}
|
||||
max={200}
|
||||
min={1}
|
||||
textTransformer={(s) => `${s}%`}
|
||||
onChange={(v) =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
size: v / 100,
|
||||
})
|
||||
}
|
||||
value={props.styling.size * 100}
|
||||
/>
|
||||
<div className="flex justify-between items-center">
|
||||
<Menu.FieldTitle>
|
||||
{t("settings.subtitles.textStyle.title")}
|
||||
</Menu.FieldTitle>
|
||||
<div className="w-30">
|
||||
<Dropdown
|
||||
options={[
|
||||
{
|
||||
id: "default",
|
||||
name: t("settings.subtitles.textStyle.default"),
|
||||
},
|
||||
{
|
||||
id: "raised",
|
||||
name: t("settings.subtitles.textStyle.raised"),
|
||||
},
|
||||
{
|
||||
id: "depressed",
|
||||
name: t("settings.subtitles.textStyle.depressed"),
|
||||
},
|
||||
{
|
||||
id: "uniform",
|
||||
name: t("settings.subtitles.textStyle.uniform"),
|
||||
},
|
||||
{
|
||||
id: "dropShadow",
|
||||
name: t("settings.subtitles.textStyle.dropShadow"),
|
||||
},
|
||||
]}
|
||||
selectedItem={{
|
||||
id: props.styling.fontStyle,
|
||||
name:
|
||||
t(
|
||||
`settings.subtitles.textStyle.${props.styling.fontStyle}`,
|
||||
) || props.styling.fontStyle,
|
||||
}}
|
||||
setSelectedItem={(item) =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
fontStyle: item.id,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<Menu.FieldTitle>
|
||||
{t("settings.subtitles.verticalPositionLabel")}
|
||||
</Menu.FieldTitle>
|
||||
<div className="flex justify-center items-center space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
"px-3 py-1 rounded transition-colors duration-100",
|
||||
props.styling.verticalPosition === 3
|
||||
? "bg-video-context-buttonFocus"
|
||||
: "bg-video-context-buttonFocus bg-opacity-0 hover:bg-opacity-50",
|
||||
)}
|
||||
onClick={() =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
verticalPosition: 3,
|
||||
})
|
||||
}
|
||||
<div className="flex justify-between items-center">
|
||||
<Menu.FieldTitle>
|
||||
{t("settings.subtitles.textBoldLabel")}
|
||||
</Menu.FieldTitle>
|
||||
<div className="flex justify-center items-center">
|
||||
<Toggle
|
||||
enabled={props.styling.bold}
|
||||
onClick={() =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
bold: !props.styling.bold,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<Menu.FieldTitle>
|
||||
{t("settings.subtitles.colorLabel")}
|
||||
</Menu.FieldTitle>
|
||||
<div className="flex justify-center items-center space-x-2">
|
||||
{colors.map((v) => (
|
||||
<ColorOption
|
||||
onClick={() =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
color: v,
|
||||
})
|
||||
}
|
||||
color={v}
|
||||
active={props.styling.color === v}
|
||||
key={v}
|
||||
/>
|
||||
))}
|
||||
<div className="relative">
|
||||
<input
|
||||
type="color"
|
||||
value={props.styling.color}
|
||||
onChange={(e) => {
|
||||
const color = e.target.value;
|
||||
handleStylingChange({ ...props.styling, color });
|
||||
subtitleStore.updateStyling({
|
||||
...props.styling,
|
||||
color,
|
||||
});
|
||||
}}
|
||||
className="absolute opacity-0 cursor-pointer w-8 h-8"
|
||||
/>
|
||||
<div style={{ color: props.styling.color }}>
|
||||
<Icon icon={Icons.BRUSH} className="text-2xl" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<Menu.FieldTitle>
|
||||
{t("settings.subtitles.verticalPositionLabel")}
|
||||
</Menu.FieldTitle>
|
||||
<div className="flex justify-center items-center space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
"px-3 py-1 rounded transition-colors duration-100",
|
||||
props.styling.verticalPosition === 3
|
||||
? "bg-video-context-buttonFocus"
|
||||
: "bg-video-context-buttonFocus bg-opacity-0 hover:bg-opacity-50",
|
||||
)}
|
||||
onClick={() =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
verticalPosition: 3,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t("settings.subtitles.default")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
"px-3 py-1 rounded transition-colors duration-100",
|
||||
props.styling.verticalPosition === 1
|
||||
? "bg-video-context-buttonFocus"
|
||||
: "bg-video-context-buttonFocus bg-opacity-0 hover:bg-opacity-50",
|
||||
)}
|
||||
onClick={() =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
verticalPosition: 1,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t("settings.subtitles.low")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="w-full md:w-auto"
|
||||
theme="secondary"
|
||||
onClick={resetSubStyling}
|
||||
>
|
||||
{t("settings.subtitles.default")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
"px-3 py-1 rounded transition-colors duration-100",
|
||||
props.styling.verticalPosition === 1
|
||||
? "bg-video-context-buttonFocus"
|
||||
: "bg-video-context-buttonFocus bg-opacity-0 hover:bg-opacity-50",
|
||||
)}
|
||||
onClick={() =>
|
||||
handleStylingChange({
|
||||
...props.styling,
|
||||
verticalPosition: 1,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t("settings.subtitles.low")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{t("settings.reset")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<CaptionPreview
|
||||
show
|
||||
styling={props.styling}
|
||||
onToggle={() => setFullscreenPreview((s) => !s)}
|
||||
/>
|
||||
<CaptionPreview
|
||||
show={fullscreenPreview}
|
||||
fullscreen
|
||||
styling={props.styling}
|
||||
onToggle={() => setFullscreenPreview((s) => !s)}
|
||||
/>
|
||||
<Button
|
||||
className="w-full md:w-auto"
|
||||
theme="secondary"
|
||||
onClick={resetSubStyling}
|
||||
>
|
||||
{t("settings.reset")}
|
||||
</Button>
|
||||
{!enableNativeSubtitles && (
|
||||
<>
|
||||
<CaptionPreview
|
||||
show
|
||||
styling={props.styling}
|
||||
onToggle={() => setFullscreenPreview((s) => !s)}
|
||||
/>
|
||||
<CaptionPreview
|
||||
show={fullscreenPreview}
|
||||
fullscreen
|
||||
styling={props.styling}
|
||||
onToggle={() => setFullscreenPreview((s) => !s)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export interface PreferencesStore {
|
|||
febboxKey: string | null;
|
||||
realDebridKey: string | null;
|
||||
enableLowPerformanceMode: boolean;
|
||||
enableNativeSubtitles: boolean;
|
||||
|
||||
setEnableThumbnails(v: boolean): void;
|
||||
setEnableAutoplay(v: boolean): void;
|
||||
|
|
@ -34,6 +35,7 @@ export interface PreferencesStore {
|
|||
setFebboxKey(v: string | null): void;
|
||||
setRealDebridKey(v: string | null): void;
|
||||
setEnableLowPerformanceMode(v: boolean): void;
|
||||
setEnableNativeSubtitles(v: boolean): void;
|
||||
}
|
||||
|
||||
export const usePreferencesStore = create(
|
||||
|
|
@ -54,6 +56,7 @@ export const usePreferencesStore = create(
|
|||
febboxKey: null,
|
||||
realDebridKey: null,
|
||||
enableLowPerformanceMode: false,
|
||||
enableNativeSubtitles: false,
|
||||
setEnableThumbnails(v) {
|
||||
set((s) => {
|
||||
s.enableThumbnails = v;
|
||||
|
|
@ -129,6 +132,11 @@ export const usePreferencesStore = create(
|
|||
s.enableLowPerformanceMode = v;
|
||||
});
|
||||
},
|
||||
setEnableNativeSubtitles(v) {
|
||||
set((s) => {
|
||||
s.enableNativeSubtitles = v;
|
||||
});
|
||||
},
|
||||
})),
|
||||
{
|
||||
name: "__MW::preferences",
|
||||
|
|
|
|||
Loading…
Reference in a new issue