diff --git a/app/(auth)/items/[id]/page.tsx b/app/(auth)/items/[id]/page.tsx index 4e85ec07..0abc1e4d 100644 --- a/app/(auth)/items/[id]/page.tsx +++ b/app/(auth)/items/[id]/page.tsx @@ -30,6 +30,7 @@ import { useCastDevice } from "react-native-google-cast"; import { chromecastProfile } from "@/utils/profiles/chromecast"; import ios12 from "@/utils/profiles/ios12"; import { currentlyPlayingItemAtom } from "@/components/CurrentlyPlayingBar"; +import { AudioTrackSelector } from "@/components/AudioTrackSelector"; const page: React.FC = () => { const local = useLocalSearchParams(); @@ -218,10 +219,13 @@ const page: React.FC = () => { {item.Overview} - setMaxBitrate(val)} - selected={maxBitrate} - /> + + setMaxBitrate(val)} + selected={maxBitrate} + /> + {}} selected={null} /> + diff --git a/app/_layout.tsx b/app/_layout.tsx index 7b07e5c1..f437aa0e 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -64,7 +64,7 @@ export default function RootLayout() { - + { const [api] = useAtom(apiAtom); const [serverURL, setServerURL] = useState(""); + const [error, setError] = useState(""); const [credentials, setCredentials] = useState<{ username: string; password: string; @@ -36,7 +38,18 @@ const Login: React.FC = () => { await login(credentials.username, credentials.password); } } catch (error) { - console.error(error); + const e = error as AxiosError | z.ZodError; + if (e instanceof z.ZodError) { + setError("An error occured."); + } else { + if (e.response?.status === 401) { + setError("Invalid credentials."); + } else { + setError( + "A network error occurred. Did you enter the correct server URL?", + ); + } + } } finally { setLoading(false); } @@ -122,6 +135,8 @@ const Login: React.FC = () => { /> + {error} + diff --git a/components/AudioTrackSelector.tsx b/components/AudioTrackSelector.tsx new file mode 100644 index 00000000..5b702d9b --- /dev/null +++ b/components/AudioTrackSelector.tsx @@ -0,0 +1,75 @@ +import { TouchableOpacity, View } from "react-native"; +import * as DropdownMenu from "zeego/dropdown-menu"; +import { Text } from "./common/Text"; +import { atom, useAtom } from "jotai"; +import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; +import { useEffect, useMemo } from "react"; +import { MediaStream } from "@jellyfin/sdk/lib/generated-client/models"; + +interface Props extends React.ComponentProps { + item: BaseItemDto; + onChange: (value: number) => void; + selected: number; +} + +export const AudioTrackSelector: React.FC = ({ + item, + onChange, + selected, + ...props +}) => { + console.log( + item.MediaSources?.[0].MediaStreams?.filter((x) => x.Type === "Audio"), + ); + + const audioStreams = useMemo( + () => + item.MediaSources?.[0].MediaStreams?.filter((x) => x.Type === "Audio"), + [item], + ); + + const selectedAudioSteam = useMemo( + () => audioStreams?.[selected], + [audioStreams, selected], + ); + + return ( + + + + + Bitrate + + + {selectedAudioSteam?.DisplayTitle} + + + + + + Bitrates + {audioStreams?.map((audio, index: number) => ( + { + onChange(index); + }} + > + + {audio.DisplayTitle} + + + ))} + + + + ); +}; diff --git a/components/BitrateSelector.tsx b/components/BitrateSelector.tsx index cd749795..e65b1064 100644 --- a/components/BitrateSelector.tsx +++ b/components/BitrateSelector.tsx @@ -27,14 +27,18 @@ const BITRATES: Bitrate[] = [ }, ]; -type Props = { +interface Props extends React.ComponentProps { onChange: (value: Bitrate) => void; selected: Bitrate; -}; +} -export const BitrateSelector: React.FC = ({ onChange, selected }) => { +export const BitrateSelector: React.FC = ({ + onChange, + selected, + ...props +}) => { return ( - +