diff --git a/app/(auth)/(tabs)/(home)/settings.tsx b/app/(auth)/(tabs)/(home)/settings.tsx index c688f9b2..b89821ce 100644 --- a/app/(auth)/(tabs)/(home)/settings.tsx +++ b/app/(auth)/(tabs)/(home)/settings.tsx @@ -35,11 +35,13 @@ export default function settings() { }} > - Information + + Information - - - + + + + @@ -71,26 +73,27 @@ export default function settings() { Delete all logs - - Logs - - {logs?.map((log, index) => ( - - + Logs + + {logs?.map((log, index) => ( + + - {log.level} - - {log.message} - - ))} - {logs?.length === 0 && ( - No logs available - )} + > + {log.level} + + {log.message} + + ))} + {logs?.length === 0 && ( + No logs available + )} + diff --git a/components/AudioTrackSelector.tsx b/components/AudioTrackSelector.tsx index 5b3f91db..9a55adf3 100644 --- a/components/AudioTrackSelector.tsx +++ b/components/AudioTrackSelector.tsx @@ -9,6 +9,7 @@ import { import { useEffect, useMemo } from "react"; import { MediaStream } from "@jellyfin/sdk/lib/generated-client/models"; import { tc } from "@/utils/textTools"; +import { useSettings } from "@/utils/atoms/settings"; interface Props extends React.ComponentProps { source: MediaSourceInfo; @@ -22,6 +23,8 @@ export const AudioTrackSelector: React.FC = ({ selected, ...props }) => { + const [settings] = useSettings(); + const audioStreams = useMemo( () => source.MediaStreams?.filter((x) => x.Type === "Audio"), [source] @@ -33,9 +36,22 @@ export const AudioTrackSelector: React.FC = ({ ); useEffect(() => { + const defaultAudioIndex = audioStreams?.find( + (x) => x.Language === settings?.defaultAudioLanguage + )?.Index; + if (defaultAudioIndex !== undefined && defaultAudioIndex !== null) { + onChange(defaultAudioIndex); + return; + } const index = source.DefaultAudioStreamIndex; - if (index !== undefined && index !== null) onChange(index); - }, []); + if (index !== undefined && index !== null) { + console.log("DefaultAudioStreamIndex", index); + onChange(index); + return; + } + + onChange(0); + }, [audioStreams, settings]); return ( { source: MediaSourceInfo; @@ -22,6 +23,8 @@ export const SubtitleTrackSelector: React.FC = ({ selected, ...props }) => { + const [settings] = useSettings(); + const subtitleStreams = useMemo( () => source.MediaStreams?.filter((x) => x.Type === "Subtitle") ?? [], [source] @@ -33,13 +36,21 @@ export const SubtitleTrackSelector: React.FC = ({ ); useEffect(() => { - const index = source.DefaultSubtitleStreamIndex; - if (index !== undefined && index !== null) { - onChange(index); - } else { - onChange(-1); + // const index = source.DefaultAudioStreamIndex; + // if (index !== undefined && index !== null) { + // onChange(index); + // return; + // } + const defaultSubIndex = subtitleStreams?.find( + (x) => x.Language === settings?.defaultSubtitleLanguage?.value + )?.Index; + if (defaultSubIndex !== undefined && defaultSubIndex !== null) { + onChange(defaultSubIndex); + return; } - }, []); + + onChange(-1); + }, [subtitleStreams, settings]); if (subtitleStreams.length === 0) return null; diff --git a/components/settings/SettingToggles.tsx b/components/settings/SettingToggles.tsx index c10f84f9..b9330003 100644 --- a/components/settings/SettingToggles.tsx +++ b/components/settings/SettingToggles.tsx @@ -1,5 +1,9 @@ import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; -import { DownloadOptions, useSettings } from "@/utils/atoms/settings"; +import { + DefaultLanguageOption, + DownloadOptions, + useSettings, +} from "@/utils/atoms/settings"; import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useAtom } from "jotai"; @@ -11,6 +15,14 @@ import { Input } from "../common/Input"; import { useState } from "react"; import { Button } from "../Button"; +const LANGUAGES: DefaultLanguageOption[] = [ + { label: "eng", value: "eng" }, + { + label: "sv", + value: "sv", + }, +]; + export const SettingToggles: React.FC = () => { const [settings, updateSettings] = useSettings(); @@ -44,314 +56,397 @@ export const SettingToggles: React.FC = () => { }); return ( - - - - Auto rotate - - Important on android since the video player orientation is locked to - the app orientation. - - - updateSettings({ autoRotate: value })} - /> - - {/* - - Download quality - - Choose the download quality. - - - - - - {settings?.downloadQuality?.label} - - - + + Downloads + + - Quality - {DownloadOptions.map((option) => ( - { - updateSettings({ downloadQuality: option }); - }} + + Audio language + + Choose a default audio language for downloads. + + + + + + {settings?.defaultAudioLanguage?.label || "None"} + + + - {option.label} - - ))} - - - */} - - - Start videos in fullscreen - - Clicking a video will start it in fullscreen mode, instead of - inline. - - - - updateSettings({ openFullScreenVideoPlayerByDefault: value }) - } - /> - - - - - Use external player (VLC) - - Open all videos in VLC instead of the default player. This requries - VLC to be installed on the phone. - - - { - updateSettings({ openInVLC: value, forceDirectPlay: value }); - }} - /> - - - - - - Use popular lists plugin - Made by: lostb1t - { - Linking.openURL( - "https://github.com/lostb1t/jellyfin-plugin-media-lists" - ); - }} - > - More info - - - - updateSettings({ usePopularPlugin: value }) - } - /> - - {settings?.usePopularPlugin && ( - - {mediaListCollections?.map((mlc) => ( - - - {mlc.Name} - - { - if (!settings.mediaListCollectionIds) { - updateSettings({ - mediaListCollectionIds: [mlc.Id!], - }); - return; - } - + Languages + { updateSettings({ - mediaListCollectionIds: - settings?.mediaListCollectionIds.includes(mlc.Id!) - ? settings?.mediaListCollectionIds.filter( - (id) => id !== mlc.Id - ) - : [...settings?.mediaListCollectionIds, mlc.Id!], + defaultAudioLanguage: null, }); }} - /> + > + None + + {LANGUAGES.map((l) => ( + { + updateSettings({ + defaultAudioLanguage: l, + }); + }} + > + {l.label} + + ))} + + + + + + Subtitle language + + Choose a default subtitle language for downloads. + + + + + + + {settings?.defaultSubtitleLanguage?.label || "None"} + + + + + Languages + { + updateSettings({ + defaultSubtitleLanguage: null, + }); + }} + > + None + + {LANGUAGES.map((l) => ( + { + updateSettings({ + defaultSubtitleLanguage: l, + }); + }} + > + {l.label} + + ))} + + + + + + + + Other + + + + + Auto rotate + + Important on android since the video player orientation is + locked to the app orientation. + + + updateSettings({ autoRotate: value })} + /> + + + + + Start videos in fullscreen + + Clicking a video will start it in fullscreen mode, instead of + inline. + + + + updateSettings({ openFullScreenVideoPlayerByDefault: value }) + } + /> + + + + + Use external player (VLC) + + Open all videos in VLC instead of the default player. This + requries VLC to be installed on the phone. + + + { + updateSettings({ openInVLC: value, forceDirectPlay: value }); + }} + /> + + + + + + Use popular lists plugin + Made by: lostb1t + { + Linking.openURL( + "https://github.com/lostb1t/jellyfin-plugin-media-lists" + ); + }} + > + More info + - ))} - {isLoadingMediaListCollections && ( - - - - )} - {mediaListCollections?.length === 0 && ( - - - No collections found. Add some in Jellyfin. - + + updateSettings({ usePopularPlugin: value }) + } + /> + + {settings?.usePopularPlugin && ( + + {mediaListCollections?.map((mlc) => ( + + + {mlc.Name} + + { + if (!settings.mediaListCollectionIds) { + updateSettings({ + mediaListCollectionIds: [mlc.Id!], + }); + return; + } + + updateSettings({ + mediaListCollectionIds: + settings?.mediaListCollectionIds.includes(mlc.Id!) + ? settings?.mediaListCollectionIds.filter( + (id) => id !== mlc.Id + ) + : [...settings?.mediaListCollectionIds, mlc.Id!], + }); + }} + /> + + ))} + {isLoadingMediaListCollections && ( + + + + )} + {mediaListCollections?.length === 0 && ( + + + No collections found. Add some in Jellyfin. + + + )} )} - )} - - - - Force direct play - - This will always request direct play. This is good if you want to - try to stream movies you think the device supports. - - - updateSettings({ forceDirectPlay: value })} - /> - + + + Force direct play + + This will always request direct play. This is good if you want + to try to stream movies you think the device supports. + + + + updateSettings({ forceDirectPlay: value }) + } + /> + - - - Device profile - - A profile used for deciding what audio and video codecs the device - supports. - - - - - - {settings?.deviceProfile} - - - - Profiles - { - updateSettings({ deviceProfile: "Expo" }); - }} - > - Expo - - { - updateSettings({ deviceProfile: "Native" }); - }} - > - Native - - { - updateSettings({ deviceProfile: "Old" }); - }} - > - Old - - - - - - - - Search engine - - Choose the search engine you want to use. - - - - - - {settings?.searchEngine} - - - - Profiles - { - updateSettings({ searchEngine: "Jellyfin" }); - queryClient.invalidateQueries({ queryKey: ["search"] }); - }} + + Device profile + + A profile used for deciding what audio and video codecs the + device supports. + + + + + + {settings?.deviceProfile} + + + - Jellyfin - - { - updateSettings({ searchEngine: "Marlin" }); - queryClient.invalidateQueries({ queryKey: ["search"] }); - }} - > - Marlin - - - - - {settings?.searchEngine === "Marlin" && ( - - <> - - - setMarlinUrl(text)} - /> - - - - - - {settings?.marlinServerUrl} - - + Expo + + { + updateSettings({ deviceProfile: "Native" }); + }} + > + Native + + { + updateSettings({ deviceProfile: "Old" }); + }} + > + Old + + + - )} + + + + Search engine + + Choose the search engine you want to use. + + + + + + {settings?.searchEngine} + + + + Profiles + { + updateSettings({ searchEngine: "Jellyfin" }); + queryClient.invalidateQueries({ queryKey: ["search"] }); + }} + > + Jellyfin + + { + updateSettings({ searchEngine: "Marlin" }); + queryClient.invalidateQueries({ queryKey: ["search"] }); + }} + > + Marlin + + + + + {settings?.searchEngine === "Marlin" && ( + + <> + + + setMarlinUrl(text)} + /> + + + + + + {settings?.marlinServerUrl} + + + + )} + + ); diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index 1342061f..cde69411 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -32,6 +32,11 @@ export type LibraryOptions = { showStats: boolean; }; +export type DefaultLanguageOption = { + value: string; + label: string; +}; + type Settings = { autoRotate?: boolean; forceLandscapeInVideoPlayer?: boolean; @@ -45,6 +50,8 @@ type Settings = { openInVLC?: boolean; downloadQuality?: DownloadOption; libraryOptions: LibraryOptions; + defaultSubtitleLanguage: DefaultLanguageOption | null; + defaultAudioLanguage: DefaultLanguageOption | null; }; /** @@ -75,6 +82,8 @@ const loadSettings = async (): Promise => { showTitles: true, showStats: true, }, + defaultAudioLanguage: null, + defaultSubtitleLanguage: null, }; try {