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 (
-
+