mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 23:59:08 +00:00
feat: KSPlayer as an option for iOS + other improvements (#1266)
This commit is contained in:
committed by
GitHub
parent
d1795c9df8
commit
74d86b5d12
@@ -1,6 +1,7 @@
|
||||
import { atom } from "jotai";
|
||||
import { atomWithStorage } from "jotai/utils";
|
||||
import { storage } from "../mmkv";
|
||||
import { useSettings } from "./settings";
|
||||
|
||||
export enum SortByOption {
|
||||
Default = "Default",
|
||||
@@ -15,13 +16,18 @@ export enum SortByOption {
|
||||
OfficialRating = "OfficialRating",
|
||||
PremiereDate = "PremiereDate",
|
||||
StartDate = "StartDate",
|
||||
IsUnplayed = "IsUnplayed",
|
||||
IsPlayed = "IsPlayed",
|
||||
AirTime = "AirTime",
|
||||
Studio = "Studio",
|
||||
IsFavoriteOrLiked = "IsFavoriteOrLiked",
|
||||
Random = "Random",
|
||||
}
|
||||
export enum FilterByOption {
|
||||
IsFavoriteOrLiked = "IsFavoriteOrLiked",
|
||||
IsUnplayed = "IsUnplayed",
|
||||
IsPlayed = "IsPlayed",
|
||||
Likes = "Likes",
|
||||
IsFavorite = "IsFavorite",
|
||||
IsResumable = "IsResumable",
|
||||
}
|
||||
|
||||
export enum SortOrderOption {
|
||||
Ascending = "Ascending",
|
||||
@@ -44,14 +50,43 @@ export const sortOptions: {
|
||||
{ key: SortByOption.OfficialRating, value: "Official Rating" },
|
||||
{ key: SortByOption.PremiereDate, value: "Premiere Date" },
|
||||
{ key: SortByOption.StartDate, value: "Start Date" },
|
||||
{ key: SortByOption.IsUnplayed, value: "Is Unplayed" },
|
||||
{ key: SortByOption.IsPlayed, value: "Is Played" },
|
||||
|
||||
{ key: SortByOption.AirTime, value: "Air Time" },
|
||||
{ key: SortByOption.Studio, value: "Studio" },
|
||||
{ key: SortByOption.IsFavoriteOrLiked, value: "Is Favorite Or Liked" },
|
||||
|
||||
{ key: SortByOption.Random, value: "Random" },
|
||||
];
|
||||
|
||||
export const useFilterOptions = () => {
|
||||
const { settings } = useSettings();
|
||||
// We want to only show the watchlist option if someone has ticked that setting.
|
||||
const filterOptions = settings?.useKefinTweaks
|
||||
? [
|
||||
{
|
||||
key: FilterByOption.IsFavoriteOrLiked,
|
||||
value: "Is Favorite Or Liked",
|
||||
},
|
||||
{ key: FilterByOption.IsUnplayed, value: "Is Unplayed" },
|
||||
{ key: FilterByOption.IsPlayed, value: "Is Played" },
|
||||
{ key: FilterByOption.IsFavorite, value: "Is Favorite" },
|
||||
{ key: FilterByOption.IsResumable, value: "Is Resumable" },
|
||||
{ key: FilterByOption.Likes, value: "Watchlist" },
|
||||
]
|
||||
: [
|
||||
{
|
||||
key: FilterByOption.IsFavoriteOrLiked,
|
||||
value: "Is Favorite Or Liked",
|
||||
},
|
||||
{ key: FilterByOption.IsUnplayed, value: "Is Unplayed" },
|
||||
{ key: FilterByOption.IsPlayed, value: "Is Played" },
|
||||
{ key: FilterByOption.IsFavorite, value: "Is Favorite" },
|
||||
{ key: FilterByOption.IsResumable, value: "Is Resumable" },
|
||||
];
|
||||
console.log("filterOptions");
|
||||
console.log(filterOptions);
|
||||
return filterOptions;
|
||||
};
|
||||
|
||||
export const sortOrderOptions: {
|
||||
key: SortOrderOption;
|
||||
value: string;
|
||||
@@ -67,6 +102,7 @@ export const sortByAtom = atom<SortByOption[]>([SortByOption.Default]);
|
||||
export const sortOrderAtom = atom<SortOrderOption[]>([
|
||||
SortOrderOption.Ascending,
|
||||
]);
|
||||
export const filterByAtom = atom<FilterByOption[]>([]);
|
||||
|
||||
export interface SortPreference {
|
||||
[libraryId: string]: SortByOption;
|
||||
@@ -76,8 +112,13 @@ export interface SortOrderPreference {
|
||||
[libraryId: string]: SortOrderOption;
|
||||
}
|
||||
|
||||
export interface FilterPreference {
|
||||
[libraryId: string]: FilterByOption;
|
||||
}
|
||||
|
||||
const defaultSortPreference: SortPreference = {};
|
||||
const defaultSortOrderPreference: SortOrderPreference = {};
|
||||
const defaultFilterPreference: FilterPreference = {};
|
||||
|
||||
export const sortByPreferenceAtom = atomWithStorage<SortPreference>(
|
||||
"sortByPreference",
|
||||
@@ -96,6 +137,23 @@ export const sortByPreferenceAtom = atomWithStorage<SortPreference>(
|
||||
},
|
||||
);
|
||||
|
||||
export const FilterByPreferenceAtom = atomWithStorage<FilterPreference>(
|
||||
"filterByPreference",
|
||||
defaultFilterPreference,
|
||||
{
|
||||
getItem: (key) => {
|
||||
const value = storage.getString(key);
|
||||
return value ? JSON.parse(value) : null;
|
||||
},
|
||||
setItem: (key, value) => {
|
||||
storage.set(key, JSON.stringify(value));
|
||||
},
|
||||
removeItem: (key) => {
|
||||
storage.remove(key);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export const sortOrderPreferenceAtom = atomWithStorage<SortOrderPreference>(
|
||||
"sortOrderPreference",
|
||||
defaultSortOrderPreference,
|
||||
@@ -126,3 +184,10 @@ export const getSortOrderPreference = (
|
||||
) => {
|
||||
return preferences?.[libraryId] || null;
|
||||
};
|
||||
|
||||
export const getFilterByPreference = (
|
||||
libraryId: string,
|
||||
preferences: FilterPreference,
|
||||
) => {
|
||||
return preferences?.[libraryId] || null;
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
} from "@jellyfin/sdk/lib/generated-client";
|
||||
import { atom, useAtom, useAtomValue } from "jotai";
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { BITRATES, type Bitrate } from "@/components/BitrateSelector";
|
||||
import * as ScreenOrientation from "@/packages/expo-screen-orientation";
|
||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||
@@ -130,10 +129,15 @@ export type HomeSectionLatestResolver = {
|
||||
includeItemTypes?: Array<BaseItemKind>;
|
||||
};
|
||||
|
||||
// Video player enum - currently only MPV is supported
|
||||
export enum VideoPlayer {
|
||||
// NATIVE, //todo: changes will make this a lot more easier to implement if we want. delete if not wanted
|
||||
VLC_3 = 0,
|
||||
VLC_4 = 1,
|
||||
MPV = 0,
|
||||
}
|
||||
|
||||
// iOS video player selection
|
||||
export enum VideoPlayerIOS {
|
||||
KSPlayer = "ksplayer",
|
||||
VLC = "vlc",
|
||||
}
|
||||
|
||||
export type Settings = {
|
||||
@@ -141,9 +145,12 @@ export type Settings = {
|
||||
deviceProfile?: "Expo" | "Native" | "Old";
|
||||
mediaListCollectionIds?: string[];
|
||||
preferedLanguage?: string;
|
||||
searchEngine: "Marlin" | "Jellyfin";
|
||||
searchEngine: "Marlin" | "Jellyfin" | "Streamystats";
|
||||
marlinServerUrl?: string;
|
||||
openInVLC?: boolean;
|
||||
streamyStatsServerUrl?: string;
|
||||
streamyStatsMovieRecommendations?: boolean;
|
||||
streamyStatsSeriesRecommendations?: boolean;
|
||||
streamyStatsPromotedWatchlists?: boolean;
|
||||
downloadQuality?: DownloadOption;
|
||||
defaultBitrate?: Bitrate;
|
||||
libraryOptions: LibraryOptions;
|
||||
@@ -162,24 +169,31 @@ export type Settings = {
|
||||
subtitleSize: number;
|
||||
safeAreaInControlsEnabled: boolean;
|
||||
jellyseerrServerUrl?: string;
|
||||
useKefinTweaks: boolean;
|
||||
hiddenLibraries?: string[];
|
||||
enableH265ForChromecast: boolean;
|
||||
defaultPlayer: VideoPlayer;
|
||||
maxAutoPlayEpisodeCount: MaxAutoPlayEpisodeCount;
|
||||
autoPlayEpisodeCount: number;
|
||||
vlcTextColor?: string;
|
||||
vlcBackgroundColor?: string;
|
||||
vlcOutlineColor?: string;
|
||||
vlcOutlineThickness?: string;
|
||||
vlcBackgroundOpacity?: number;
|
||||
vlcOutlineOpacity?: number;
|
||||
vlcIsBold?: boolean;
|
||||
// MPV subtitle settings
|
||||
mpvSubtitleScale?: number;
|
||||
mpvSubtitleMarginY?: number;
|
||||
mpvSubtitleAlignX?: "left" | "center" | "right";
|
||||
mpvSubtitleAlignY?: "top" | "center" | "bottom";
|
||||
mpvSubtitleFontSize?: number;
|
||||
// KSPlayer settings
|
||||
ksHardwareDecode: boolean;
|
||||
ksSubtitleColor: string;
|
||||
ksSubtitleBackgroundColor: string;
|
||||
ksSubtitleFontName: string;
|
||||
// Gesture controls
|
||||
enableHorizontalSwipeSkip: boolean;
|
||||
enableLeftSideBrightnessSwipe: boolean;
|
||||
enableRightSideVolumeSwipe: boolean;
|
||||
usePopularPlugin: boolean;
|
||||
showLargeHomeCarousel: boolean;
|
||||
mergeNextUpAndContinueWatching: boolean;
|
||||
// iOS video player selection
|
||||
videoPlayerIOS: VideoPlayerIOS;
|
||||
};
|
||||
|
||||
export interface Lockable<T> {
|
||||
@@ -201,7 +215,10 @@ export const defaultValues: Settings = {
|
||||
preferedLanguage: undefined,
|
||||
searchEngine: "Jellyfin",
|
||||
marlinServerUrl: "",
|
||||
openInVLC: false,
|
||||
streamyStatsServerUrl: "",
|
||||
streamyStatsMovieRecommendations: false,
|
||||
streamyStatsSeriesRecommendations: false,
|
||||
streamyStatsPromotedWatchlists: false,
|
||||
downloadQuality: DownloadOptions[0],
|
||||
defaultBitrate: BITRATES[0],
|
||||
libraryOptions: {
|
||||
@@ -223,27 +240,34 @@ export const defaultValues: Settings = {
|
||||
rewindSkipTime: 10,
|
||||
showCustomMenuLinks: false,
|
||||
disableHapticFeedback: false,
|
||||
subtitleSize: Platform.OS === "ios" ? 60 : 100,
|
||||
subtitleSize: 100, // Scale value * 100, so 100 = 1.0x
|
||||
safeAreaInControlsEnabled: true,
|
||||
jellyseerrServerUrl: undefined,
|
||||
useKefinTweaks: false,
|
||||
hiddenLibraries: [],
|
||||
enableH265ForChromecast: false,
|
||||
defaultPlayer: VideoPlayer.VLC_3, // ios-only setting. does not matter what this is for android
|
||||
maxAutoPlayEpisodeCount: { key: "3", value: 3 },
|
||||
autoPlayEpisodeCount: 0,
|
||||
vlcTextColor: undefined,
|
||||
vlcBackgroundColor: undefined,
|
||||
vlcOutlineColor: undefined,
|
||||
vlcOutlineThickness: undefined,
|
||||
vlcBackgroundOpacity: undefined,
|
||||
vlcOutlineOpacity: undefined,
|
||||
vlcIsBold: undefined,
|
||||
// MPV subtitle defaults
|
||||
mpvSubtitleScale: undefined,
|
||||
mpvSubtitleMarginY: undefined,
|
||||
mpvSubtitleAlignX: undefined,
|
||||
mpvSubtitleAlignY: undefined,
|
||||
mpvSubtitleFontSize: undefined,
|
||||
// KSPlayer defaults
|
||||
ksHardwareDecode: true,
|
||||
ksSubtitleColor: "#FFFFFF",
|
||||
ksSubtitleBackgroundColor: "#00000080",
|
||||
ksSubtitleFontName: "System",
|
||||
// Gesture controls
|
||||
enableHorizontalSwipeSkip: true,
|
||||
enableLeftSideBrightnessSwipe: true,
|
||||
enableRightSideVolumeSwipe: true,
|
||||
usePopularPlugin: true,
|
||||
showLargeHomeCarousel: false,
|
||||
mergeNextUpAndContinueWatching: false,
|
||||
// iOS video player selection - default to VLC
|
||||
videoPlayerIOS: VideoPlayerIOS.VLC,
|
||||
};
|
||||
|
||||
const loadSettings = (): Partial<Settings> => {
|
||||
@@ -309,20 +333,60 @@ export const useSettings = () => {
|
||||
[_setPluginSettings],
|
||||
);
|
||||
|
||||
const refreshStreamyfinPluginSettings = useCallback(async () => {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
const settings = await api.getStreamyfinPluginConfig().then(
|
||||
({ data }) => {
|
||||
writeInfoLog("Got plugin settings", data?.settings);
|
||||
return data?.settings;
|
||||
},
|
||||
(_err) => undefined,
|
||||
);
|
||||
setPluginSettings(settings);
|
||||
return settings;
|
||||
}, [api]);
|
||||
const refreshStreamyfinPluginSettings = useCallback(
|
||||
async (forceOverride = false) => {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
const newPluginSettings = await api.getStreamyfinPluginConfig().then(
|
||||
({ data }) => {
|
||||
writeInfoLog("Got plugin settings", data?.settings);
|
||||
return data?.settings;
|
||||
},
|
||||
(_err) => undefined,
|
||||
);
|
||||
setPluginSettings(newPluginSettings);
|
||||
|
||||
// Apply plugin values to settings
|
||||
if (newPluginSettings && _settings) {
|
||||
const updates: Partial<Settings> = {};
|
||||
for (const [key, setting] of Object.entries(newPluginSettings)) {
|
||||
if (setting && !setting.locked && setting.value !== undefined) {
|
||||
const settingsKey = key as keyof Settings;
|
||||
// Apply if forceOverride is true, or if user hasn't explicitly set this value
|
||||
if (
|
||||
forceOverride ||
|
||||
_settings[settingsKey] === undefined ||
|
||||
_settings[settingsKey] === ""
|
||||
) {
|
||||
(updates as any)[settingsKey] = setting.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-enable Streamystats if server URL is provided
|
||||
const streamyStatsUrl = newPluginSettings.streamyStatsServerUrl;
|
||||
if (
|
||||
streamyStatsUrl?.value &&
|
||||
_settings.searchEngine !== "Streamystats"
|
||||
) {
|
||||
updates.searchEngine = "Streamystats";
|
||||
}
|
||||
if (Object.keys(updates).length > 0) {
|
||||
const newSettings = {
|
||||
...defaultValues,
|
||||
..._settings,
|
||||
...updates,
|
||||
} as Settings;
|
||||
setSettings(newSettings);
|
||||
saveSettings(newSettings);
|
||||
}
|
||||
}
|
||||
|
||||
return newPluginSettings;
|
||||
},
|
||||
[api, _settings],
|
||||
);
|
||||
|
||||
const updateSettings = (update: Partial<Settings>) => {
|
||||
if (!_settings) {
|
||||
|
||||
Reference in New Issue
Block a user