mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-01 11:38:26 +01:00
feat: KSPlayer as an option for iOS + other improvements (#1266)
This commit is contained in:
committed by
GitHub
parent
d1795c9df8
commit
74d86b5d12
@@ -50,6 +50,16 @@ export const AppearanceSettings: React.FC = () => {
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
title={t("home.settings.appearance.merge_next_up_continue_watching")}
|
||||
>
|
||||
<Switch
|
||||
value={settings.mergeNextUpAndContinueWatching}
|
||||
onValueChange={(value) =>
|
||||
updateSettings({ mergeNextUpAndContinueWatching: value })
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
onPress={() =>
|
||||
router.push("/settings/appearance/hide-libraries/page")
|
||||
|
||||
@@ -11,12 +11,12 @@ const DisabledSetting: React.FC<
|
||||
}}
|
||||
>
|
||||
<View {...props}>
|
||||
{children}
|
||||
{disabled && showText && (
|
||||
<Text className='text-center text-red-700 my-4'>
|
||||
{text ?? "Currently disabled by admin."}
|
||||
<Text className='text-xs text-red-600 px-4 mt-1'>
|
||||
{text ?? "Disabled by admin"}
|
||||
</Text>
|
||||
)}
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
40
components/settings/KSPlayerSettings.tsx
Normal file
40
components/settings/KSPlayerSettings.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import type React from "react";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Platform, Switch } from "react-native";
|
||||
import { setHardwareDecode } from "@/modules/sf-player";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { ListGroup } from "../list/ListGroup";
|
||||
import { ListItem } from "../list/ListItem";
|
||||
|
||||
export const KSPlayerSettings: React.FC = () => {
|
||||
const { settings, updateSettings } = useSettings();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleHardwareDecodeChange = useCallback(
|
||||
(value: boolean) => {
|
||||
updateSettings({ ksHardwareDecode: value });
|
||||
setHardwareDecode(value);
|
||||
},
|
||||
[updateSettings],
|
||||
);
|
||||
|
||||
if (Platform.OS !== "ios" || !settings) return null;
|
||||
|
||||
return (
|
||||
<ListGroup
|
||||
title={t("home.settings.subtitles.ksplayer_title")}
|
||||
className='mt-4'
|
||||
>
|
||||
<ListItem
|
||||
title={t("home.settings.subtitles.hardware_decode")}
|
||||
subtitle={t("home.settings.subtitles.hardware_decode_description")}
|
||||
>
|
||||
<Switch
|
||||
value={settings.ksHardwareDecode}
|
||||
onValueChange={handleHardwareDecodeChange}
|
||||
/>
|
||||
</ListItem>
|
||||
</ListGroup>
|
||||
);
|
||||
};
|
||||
33
components/settings/KefinTweaks.tsx
Normal file
33
components/settings/KefinTweaks.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Switch, Text, View } from "react-native";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
|
||||
export const KefinTweaksSettings = () => {
|
||||
const { settings, updateSettings } = useSettings();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isEnabled = settings?.useKefinTweaks ?? false;
|
||||
|
||||
return (
|
||||
<View className=''>
|
||||
<View className='flex flex-col rounded-xl overflow-hidden p-4 bg-neutral-900'>
|
||||
<Text className='text-xs text-red-600 mb-2'>
|
||||
{t("home.settings.plugins.kefinTweaks.watchlist_enabler")}
|
||||
</Text>
|
||||
|
||||
<View className='flex flex-row items-center justify-between mt-2'>
|
||||
<Text className='text-white'>
|
||||
{isEnabled ? t("Watchlist On") : t("Watchlist Off")}
|
||||
</Text>
|
||||
|
||||
<Switch
|
||||
value={isEnabled}
|
||||
onValueChange={(value) => updateSettings({ useKefinTweaks: value })}
|
||||
trackColor={{ false: "#555", true: "purple" }}
|
||||
thumbColor={isEnabled ? "#fff" : "#ccc"}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -12,6 +12,7 @@ import { ScreenOrientationEnum, useSettings } from "@/utils/atoms/settings";
|
||||
import { Text } from "../common/Text";
|
||||
import { ListGroup } from "../list/ListGroup";
|
||||
import { ListItem } from "../list/ListItem";
|
||||
import { VideoPlayerSettings } from "./VideoPlayerSettings";
|
||||
|
||||
export const PlaybackControlsSettings: React.FC = () => {
|
||||
const { settings, updateSettings, pluginSettings } = useSettings();
|
||||
@@ -190,6 +191,8 @@ export const PlaybackControlsSettings: React.FC = () => {
|
||||
/>
|
||||
</ListItem>
|
||||
</ListGroup>
|
||||
|
||||
<VideoPlayerSettings />
|
||||
</DisabledSetting>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,6 +28,16 @@ export const PluginSettings = () => {
|
||||
title='Marlin Search'
|
||||
showArrow
|
||||
/>
|
||||
<ListItem
|
||||
onPress={() => router.push("/settings/plugins/streamystats/page")}
|
||||
title='Streamystats'
|
||||
showArrow
|
||||
/>
|
||||
<ListItem
|
||||
onPress={() => router.push("/settings/plugins/kefinTweaks/page")}
|
||||
title='KefinTweaks'
|
||||
showArrow
|
||||
/>
|
||||
</ListGroup>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,12 +5,6 @@ import { useTranslation } from "react-i18next";
|
||||
import { Platform, View, type ViewProps } from "react-native";
|
||||
import { Switch } from "react-native-gesture-handler";
|
||||
import { Stepper } from "@/components/inputs/Stepper";
|
||||
import {
|
||||
OUTLINE_THICKNESS,
|
||||
type OutlineThickness,
|
||||
VLC_COLORS,
|
||||
type VLCColor,
|
||||
} from "@/constants/SubtitleConstants";
|
||||
import { useSettings } from "@/utils/atoms/settings";
|
||||
import { Text } from "../common/Text";
|
||||
import { ListGroup } from "../list/ListGroup";
|
||||
@@ -92,84 +86,6 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
|
||||
];
|
||||
}, [settings?.subtitleMode, t, updateSettings]);
|
||||
|
||||
const textColorOptionGroups = useMemo(() => {
|
||||
const colors = Object.keys(VLC_COLORS) as VLCColor[];
|
||||
const options = colors.map((color) => ({
|
||||
type: "radio" as const,
|
||||
label: t(`home.settings.subtitles.colors.${color}`),
|
||||
value: color,
|
||||
selected: (settings?.vlcTextColor || "White") === color,
|
||||
onPress: () => updateSettings({ vlcTextColor: color }),
|
||||
}));
|
||||
|
||||
return [{ options }];
|
||||
}, [settings?.vlcTextColor, t, updateSettings]);
|
||||
|
||||
const backgroundColorOptionGroups = useMemo(() => {
|
||||
const colors = Object.keys(VLC_COLORS) as VLCColor[];
|
||||
const options = colors.map((color) => ({
|
||||
type: "radio" as const,
|
||||
label: t(`home.settings.subtitles.colors.${color}`),
|
||||
value: color,
|
||||
selected: (settings?.vlcBackgroundColor || "Black") === color,
|
||||
onPress: () => updateSettings({ vlcBackgroundColor: color }),
|
||||
}));
|
||||
|
||||
return [{ options }];
|
||||
}, [settings?.vlcBackgroundColor, t, updateSettings]);
|
||||
|
||||
const outlineColorOptionGroups = useMemo(() => {
|
||||
const colors = Object.keys(VLC_COLORS) as VLCColor[];
|
||||
const options = colors.map((color) => ({
|
||||
type: "radio" as const,
|
||||
label: t(`home.settings.subtitles.colors.${color}`),
|
||||
value: color,
|
||||
selected: (settings?.vlcOutlineColor || "Black") === color,
|
||||
onPress: () => updateSettings({ vlcOutlineColor: color }),
|
||||
}));
|
||||
|
||||
return [{ options }];
|
||||
}, [settings?.vlcOutlineColor, t, updateSettings]);
|
||||
|
||||
const outlineThicknessOptionGroups = useMemo(() => {
|
||||
const thicknesses = Object.keys(OUTLINE_THICKNESS) as OutlineThickness[];
|
||||
const options = thicknesses.map((thickness) => ({
|
||||
type: "radio" as const,
|
||||
label: t(`home.settings.subtitles.thickness.${thickness}`),
|
||||
value: thickness,
|
||||
selected: (settings?.vlcOutlineThickness || "Normal") === thickness,
|
||||
onPress: () => updateSettings({ vlcOutlineThickness: thickness }),
|
||||
}));
|
||||
|
||||
return [{ options }];
|
||||
}, [settings?.vlcOutlineThickness, t, updateSettings]);
|
||||
|
||||
const backgroundOpacityOptionGroups = useMemo(() => {
|
||||
const opacities = [0, 32, 64, 96, 128, 160, 192, 224, 255];
|
||||
const options = opacities.map((opacity) => ({
|
||||
type: "radio" as const,
|
||||
label: `${Math.round((opacity / 255) * 100)}%`,
|
||||
value: opacity,
|
||||
selected: (settings?.vlcBackgroundOpacity ?? 128) === opacity,
|
||||
onPress: () => updateSettings({ vlcBackgroundOpacity: opacity }),
|
||||
}));
|
||||
|
||||
return [{ options }];
|
||||
}, [settings?.vlcBackgroundOpacity, updateSettings]);
|
||||
|
||||
const outlineOpacityOptionGroups = useMemo(() => {
|
||||
const opacities = [0, 32, 64, 96, 128, 160, 192, 224, 255];
|
||||
const options = opacities.map((opacity) => ({
|
||||
type: "radio" as const,
|
||||
label: `${Math.round((opacity / 255) * 100)}%`,
|
||||
value: opacity,
|
||||
selected: (settings?.vlcOutlineOpacity ?? 255) === opacity,
|
||||
onPress: () => updateSettings({ vlcOutlineOpacity: opacity }),
|
||||
}));
|
||||
|
||||
return [{ options }];
|
||||
}, [settings?.vlcOutlineOpacity, updateSettings]);
|
||||
|
||||
if (isTv) return null;
|
||||
if (!settings) return null;
|
||||
|
||||
@@ -244,130 +160,14 @@ export const SubtitleToggles: React.FC<Props> = ({ ...props }) => {
|
||||
disabled={pluginSettings?.subtitleSize?.locked}
|
||||
>
|
||||
<Stepper
|
||||
value={settings.subtitleSize}
|
||||
value={settings.subtitleSize / 100}
|
||||
disabled={pluginSettings?.subtitleSize?.locked}
|
||||
step={5}
|
||||
min={0}
|
||||
max={120}
|
||||
onUpdate={(subtitleSize) => updateSettings({ subtitleSize })}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem title={t("home.settings.subtitles.text_color")}>
|
||||
<PlatformDropdown
|
||||
groups={textColorOptionGroups}
|
||||
trigger={
|
||||
<View className='flex flex-row items-center justify-between py-1.5 pl-3'>
|
||||
<Text className='mr-1 text-[#8E8D91]'>
|
||||
{t(
|
||||
`home.settings.subtitles.colors.${settings?.vlcTextColor || "White"}`,
|
||||
)}
|
||||
</Text>
|
||||
<Ionicons
|
||||
name='chevron-expand-sharp'
|
||||
size={18}
|
||||
color='#5A5960'
|
||||
/>
|
||||
</View>
|
||||
step={0.1}
|
||||
min={0.3}
|
||||
max={1.5}
|
||||
onUpdate={(value) =>
|
||||
updateSettings({ subtitleSize: Math.round(value * 100) })
|
||||
}
|
||||
title={t("home.settings.subtitles.text_color")}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem title={t("home.settings.subtitles.background_color")}>
|
||||
<PlatformDropdown
|
||||
groups={backgroundColorOptionGroups}
|
||||
trigger={
|
||||
<View className='flex flex-row items-center justify-between py-1.5 pl-3'>
|
||||
<Text className='mr-1 text-[#8E8D91]'>
|
||||
{t(
|
||||
`home.settings.subtitles.colors.${settings?.vlcBackgroundColor || "Black"}`,
|
||||
)}
|
||||
</Text>
|
||||
<Ionicons
|
||||
name='chevron-expand-sharp'
|
||||
size={18}
|
||||
color='#5A5960'
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
title={t("home.settings.subtitles.background_color")}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem title={t("home.settings.subtitles.outline_color")}>
|
||||
<PlatformDropdown
|
||||
groups={outlineColorOptionGroups}
|
||||
trigger={
|
||||
<View className='flex flex-row items-center justify-between py-1.5 pl-3'>
|
||||
<Text className='mr-1 text-[#8E8D91]'>
|
||||
{t(
|
||||
`home.settings.subtitles.colors.${settings?.vlcOutlineColor || "Black"}`,
|
||||
)}
|
||||
</Text>
|
||||
<Ionicons
|
||||
name='chevron-expand-sharp'
|
||||
size={18}
|
||||
color='#5A5960'
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
title={t("home.settings.subtitles.outline_color")}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem title={t("home.settings.subtitles.outline_thickness")}>
|
||||
<PlatformDropdown
|
||||
groups={outlineThicknessOptionGroups}
|
||||
trigger={
|
||||
<View className='flex flex-row items-center justify-between py-1.5 pl-3'>
|
||||
<Text className='mr-1 text-[#8E8D91]'>
|
||||
{t(
|
||||
`home.settings.subtitles.thickness.${settings?.vlcOutlineThickness || "Normal"}`,
|
||||
)}
|
||||
</Text>
|
||||
<Ionicons
|
||||
name='chevron-expand-sharp'
|
||||
size={18}
|
||||
color='#5A5960'
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
title={t("home.settings.subtitles.outline_thickness")}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem title={t("home.settings.subtitles.background_opacity")}>
|
||||
<PlatformDropdown
|
||||
groups={backgroundOpacityOptionGroups}
|
||||
trigger={
|
||||
<View className='flex flex-row items-center justify-between py-1.5 pl-3'>
|
||||
<Text className='mr-1 text-[#8E8D91]'>{`${Math.round(((settings?.vlcBackgroundOpacity ?? 128) / 255) * 100)}%`}</Text>
|
||||
<Ionicons
|
||||
name='chevron-expand-sharp'
|
||||
size={18}
|
||||
color='#5A5960'
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
title={t("home.settings.subtitles.background_opacity")}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem title={t("home.settings.subtitles.outline_opacity")}>
|
||||
<PlatformDropdown
|
||||
groups={outlineOpacityOptionGroups}
|
||||
trigger={
|
||||
<View className='flex flex-row items-center justify-between py-1.5 pl-3'>
|
||||
<Text className='mr-1 text-[#8E8D91]'>{`${Math.round(((settings?.vlcOutlineOpacity ?? 255) / 255) * 100)}%`}</Text>
|
||||
<Ionicons
|
||||
name='chevron-expand-sharp'
|
||||
size={18}
|
||||
color='#5A5960'
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
title={t("home.settings.subtitles.outline_opacity")}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem title={t("home.settings.subtitles.bold_text")}>
|
||||
<Switch
|
||||
value={settings?.vlcIsBold ?? false}
|
||||
onValueChange={(value) => updateSettings({ vlcIsBold: value })}
|
||||
/>
|
||||
</ListItem>
|
||||
</ListGroup>
|
||||
|
||||
93
components/settings/VideoPlayerSettings.tsx
Normal file
93
components/settings/VideoPlayerSettings.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import type React from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Platform, Switch, View } from "react-native";
|
||||
import { setHardwareDecode } from "@/modules/sf-player";
|
||||
import { useSettings, VideoPlayerIOS } from "@/utils/atoms/settings";
|
||||
import { Text } from "../common/Text";
|
||||
import { ListGroup } from "../list/ListGroup";
|
||||
import { ListItem } from "../list/ListItem";
|
||||
import { PlatformDropdown } from "../PlatformDropdown";
|
||||
|
||||
export const VideoPlayerSettings: React.FC = () => {
|
||||
const { settings, updateSettings } = useSettings();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleHardwareDecodeChange = useCallback(
|
||||
(value: boolean) => {
|
||||
updateSettings({ ksHardwareDecode: value });
|
||||
setHardwareDecode(value);
|
||||
},
|
||||
[updateSettings],
|
||||
);
|
||||
|
||||
const videoPlayerOptions = useMemo(
|
||||
() => [
|
||||
{
|
||||
options: [
|
||||
{
|
||||
type: "radio" as const,
|
||||
label: t("home.settings.video_player.ksplayer"),
|
||||
value: VideoPlayerIOS.KSPlayer,
|
||||
selected: settings?.videoPlayerIOS === VideoPlayerIOS.KSPlayer,
|
||||
onPress: () =>
|
||||
updateSettings({ videoPlayerIOS: VideoPlayerIOS.KSPlayer }),
|
||||
},
|
||||
{
|
||||
type: "radio" as const,
|
||||
label: t("home.settings.video_player.vlc"),
|
||||
value: VideoPlayerIOS.VLC,
|
||||
selected: settings?.videoPlayerIOS === VideoPlayerIOS.VLC,
|
||||
onPress: () =>
|
||||
updateSettings({ videoPlayerIOS: VideoPlayerIOS.VLC }),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[settings?.videoPlayerIOS, t, updateSettings],
|
||||
);
|
||||
|
||||
const getPlayerLabel = useCallback(() => {
|
||||
switch (settings?.videoPlayerIOS) {
|
||||
case VideoPlayerIOS.VLC:
|
||||
return t("home.settings.video_player.vlc");
|
||||
default:
|
||||
return t("home.settings.video_player.ksplayer");
|
||||
}
|
||||
}, [settings?.videoPlayerIOS, t]);
|
||||
|
||||
if (Platform.OS !== "ios" || !settings) return null;
|
||||
|
||||
return (
|
||||
<ListGroup title={t("home.settings.video_player.title")} className='mt-4'>
|
||||
<ListItem
|
||||
title={t("home.settings.video_player.video_player")}
|
||||
subtitle={t("home.settings.video_player.video_player_description")}
|
||||
>
|
||||
<PlatformDropdown
|
||||
groups={videoPlayerOptions}
|
||||
trigger={
|
||||
<View className='flex flex-row items-center justify-between py-1.5 pl-3'>
|
||||
<Text className='mr-1 text-[#8E8D91]'>{getPlayerLabel()}</Text>
|
||||
<Ionicons name='chevron-expand-sharp' size={18} color='#5A5960' />
|
||||
</View>
|
||||
}
|
||||
title={t("home.settings.video_player.video_player")}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
{settings.videoPlayerIOS === VideoPlayerIOS.KSPlayer && (
|
||||
<ListItem
|
||||
title={t("home.settings.subtitles.hardware_decode")}
|
||||
subtitle={t("home.settings.subtitles.hardware_decode_description")}
|
||||
>
|
||||
<Switch
|
||||
value={settings.ksHardwareDecode}
|
||||
onValueChange={handleHardwareDecodeChange}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
</ListGroup>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user