diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/albums/[albumId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/albums/[albumId].tsx
deleted file mode 100644
index 565f84c8..00000000
--- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/albums/[albumId].tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import { Chromecast } from "@/components/Chromecast";
-import { ItemImage } from "@/components/common/ItemImage";
-import { Text } from "@/components/common/Text";
-import { TouchableItemRouter } from "@/components/common/TouchableItemRouter";
-import { SongsList } from "@/components/music/SongsList";
-import { ParallaxScrollView } from "@/components/ParallaxPage";
-import ArtistPoster from "@/components/posters/ArtistPoster";
-import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
-import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
-import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
-import { useQuery } from "@tanstack/react-query";
-import { router, useLocalSearchParams, useNavigation } from "expo-router";
-import { useAtom } from "jotai";
-import { useEffect, useState } from "react";
-import { ScrollView, TouchableOpacity, View } from "react-native";
-import { useSafeAreaInsets } from "react-native-safe-area-context";
-
-export default function page() {
- const searchParams = useLocalSearchParams();
- const { collectionId, artistId, albumId } = searchParams as {
- collectionId: string;
- artistId: string;
- albumId: string;
- };
-
- const [api] = useAtom(apiAtom);
- const [user] = useAtom(userAtom);
-
- const navigation = useNavigation();
-
- useEffect(() => {
- navigation.setOptions({
- headerRight: () => (
-
-
-
- ),
- });
- });
-
- const { data: album } = useQuery({
- queryKey: ["album", albumId, artistId],
- queryFn: async () => {
- if (!api) return null;
- const response = await getItemsApi(api).getItems({
- userId: user?.Id,
- ids: [albumId],
- });
- const data = response.data.Items?.[0];
- return data;
- },
- enabled: !!api && !!user?.Id && !!albumId,
- staleTime: 0,
- });
-
- const {
- data: songs,
- isLoading,
- isError,
- } = useQuery<{
- Items: BaseItemDto[];
- TotalRecordCount: number;
- }>({
- queryKey: ["songs", artistId, albumId],
- queryFn: async () => {
- if (!api)
- return {
- Items: [],
- TotalRecordCount: 0,
- };
-
- const response = await getItemsApi(api).getItems({
- userId: user?.Id,
- parentId: albumId,
- fields: [
- "ItemCounts",
- "PrimaryImageAspectRatio",
- "CanDelete",
- "MediaSourceCount",
- ],
- sortBy: ["ParentIndexNumber", "IndexNumber", "SortName"],
- });
-
- const data = response.data.Items;
-
- return {
- Items: data || [],
- TotalRecordCount: response.data.TotalRecordCount || 0,
- };
- },
- enabled: !!api && !!user?.Id,
- });
-
- const insets = useSafeAreaInsets();
-
- if (!album) return null;
-
- return (
-
- }
- >
-
- {album?.Name}
-
- {songs?.TotalRecordCount} songs
-
-
-
-
-
-
- );
-}
diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/[artistId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/[artistId].tsx
deleted file mode 100644
index 8d82d205..00000000
--- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/[artistId].tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import ArtistPoster from "@/components/posters/ArtistPoster";
-import { Text } from "@/components/common/Text";
-import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
-import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
-import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
-import { useQuery } from "@tanstack/react-query";
-import { router, useLocalSearchParams, useNavigation } from "expo-router";
-import { useAtom } from "jotai";
-import { useEffect, useState } from "react";
-import { FlatList, ScrollView, TouchableOpacity, View } from "react-native";
-import { useSafeAreaInsets } from "react-native-safe-area-context";
-import { ItemImage } from "@/components/common/ItemImage";
-import { ParallaxScrollView } from "@/components/ParallaxPage";
-import { TouchableItemRouter } from "@/components/common/TouchableItemRouter";
-
-export default function page() {
- const searchParams = useLocalSearchParams();
- const { artistId } = searchParams as {
- artistId: string;
- };
-
- const [api] = useAtom(apiAtom);
- const [user] = useAtom(userAtom);
-
- const navigation = useNavigation();
-
- const [startIndex, setStartIndex] = useState(0);
-
- const { data: artist } = useQuery({
- queryKey: ["album", artistId],
- queryFn: async () => {
- if (!api) return null;
- const response = await getItemsApi(api).getItems({
- userId: user?.Id,
- ids: [artistId],
- });
- const data = response.data.Items?.[0];
- return data;
- },
- enabled: !!api && !!user?.Id && !!artistId,
- staleTime: 0,
- });
-
- const {
- data: albums,
- isLoading,
- isError,
- } = useQuery<{
- Items: BaseItemDto[];
- TotalRecordCount: number;
- }>({
- queryKey: ["albums", artistId, startIndex],
- queryFn: async () => {
- if (!api)
- return {
- Items: [],
- TotalRecordCount: 0,
- };
-
- const response = await getItemsApi(api).getItems({
- userId: user?.Id,
- parentId: artistId,
- sortOrder: ["Descending", "Descending", "Ascending"],
- includeItemTypes: ["MusicAlbum"],
- recursive: true,
- fields: [
- "ParentId",
- "PrimaryImageAspectRatio",
- "ParentId",
- "PrimaryImageAspectRatio",
- ],
- collapseBoxSetItems: false,
- albumArtistIds: [artistId],
- startIndex,
- limit: 100,
- sortBy: ["PremiereDate", "ProductionYear", "SortName"],
- });
-
- const data = response.data.Items;
-
- return {
- Items: data || [],
- TotalRecordCount: response.data.TotalRecordCount || 0,
- };
- },
- enabled: !!api && !!user?.Id,
- });
-
- const insets = useSafeAreaInsets();
-
- if (!artist || !albums) return null;
-
- return (
-
- }
- >
-
- {artist?.Name}
-
- {albums.TotalRecordCount} albums
-
-
-
- {albums.Items.map((item, idx) => (
-
-
-
- {item.Name}
- {item.ProductionYear}
-
-
- ))}
-
-
- );
-}
diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/index.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/index.tsx
deleted file mode 100644
index 4827287e..00000000
--- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/index.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { Text } from "@/components/common/Text";
-import { TouchableItemRouter } from "@/components/common/TouchableItemRouter";
-import ArtistPoster from "@/components/posters/ArtistPoster";
-import MoviePoster from "@/components/posters/MoviePoster";
-import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
-import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
-import { getArtistsApi, getItemsApi } from "@jellyfin/sdk/lib/utils/api";
-import { useQuery } from "@tanstack/react-query";
-import { router, useLocalSearchParams } from "expo-router";
-import { useAtom } from "jotai";
-import { useMemo, useState } from "react";
-import { FlatList, TouchableOpacity, View } from "react-native";
-
-export default function page() {
- const searchParams = useLocalSearchParams();
- const { collectionId } = searchParams as { collectionId: string };
-
- const [api] = useAtom(apiAtom);
- const [user] = useAtom(userAtom);
-
- const { data: collection } = useQuery({
- queryKey: ["collection", collectionId],
- queryFn: async () => {
- if (!api) return null;
- const response = await getItemsApi(api).getItems({
- userId: user?.Id,
- ids: [collectionId],
- });
- const data = response.data.Items?.[0];
- return data;
- },
- enabled: !!api && !!user?.Id && !!collectionId,
- staleTime: 0,
- });
-
- const [startIndex, setStartIndex] = useState(0);
-
- const { data, isLoading, isError } = useQuery<{
- Items: BaseItemDto[];
- TotalRecordCount: number;
- }>({
- queryKey: ["collection-items", collection?.Id, startIndex],
- queryFn: async () => {
- if (!api || !collectionId)
- return {
- Items: [],
- TotalRecordCount: 0,
- };
-
- const response = await getArtistsApi(api).getArtists({
- sortBy: ["SortName"],
- sortOrder: ["Ascending"],
- fields: ["PrimaryImageAspectRatio", "SortName"],
- imageTypeLimit: 1,
- enableImageTypes: ["Primary", "Backdrop", "Banner", "Thumb"],
- parentId: collectionId,
- userId: user?.Id,
- });
-
- const data = response.data.Items;
-
- return {
- Items: data || [],
- TotalRecordCount: response.data.TotalRecordCount || 0,
- };
- },
- enabled: !!collection?.Id && !!api && !!user?.Id,
- });
-
- const totalItems = useMemo(() => {
- return data?.TotalRecordCount;
- }, [data]);
-
- if (!data) return null;
-
- return (
-
- Artists
-
- }
- nestedScrollEnabled
- data={data.Items}
- numColumns={3}
- columnWrapperStyle={{
- justifyContent: "space-between",
- }}
- renderItem={({ item, index }) => (
-
-
- {collection?.CollectionType === "movies" && (
-
- )}
- {collection?.CollectionType === "music" && (
-
- )}
- {item.Name}
- {item.ProductionYear}
-
-
- )}
- keyExtractor={(item) => item.Id || ""}
- />
- );
-}
diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/collections/[collectionId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites)/collections/[collectionId].tsx
index 4c2b72ae..57393ce5 100644
--- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/collections/[collectionId].tsx
+++ b/app/(auth)/(tabs)/(home,libraries,search,favorites)/collections/[collectionId].tsx
@@ -109,7 +109,7 @@ const page: React.FC = () => {
genres: selectedGenres,
tags: selectedTags,
years: selectedYears.map((year) => parseInt(year)),
- includeItemTypes: ["Movie", "Series", "MusicAlbum"],
+ includeItemTypes: ["Movie", "Series"],
});
return response.data || null;
diff --git a/app/(auth)/(tabs)/(libraries)/[libraryId].tsx b/app/(auth)/(tabs)/(libraries)/[libraryId].tsx
index 7c0dbc91..414f6e90 100644
--- a/app/(auth)/(tabs)/(libraries)/[libraryId].tsx
+++ b/app/(auth)/(tabs)/(libraries)/[libraryId].tsx
@@ -150,8 +150,6 @@ const Page = () => {
itemType = "Series";
} else if (library.CollectionType === "boxsets") {
itemType = "BoxSet";
- } else if (library.CollectionType === "music") {
- itemType = "MusicAlbum";
}
const response = await getItemsApi(api).getItems({
diff --git a/app/(auth)/(tabs)/(libraries)/index.tsx b/app/(auth)/(tabs)/(libraries)/index.tsx
index e8c3f766..08adacc5 100644
--- a/app/(auth)/(tabs)/(libraries)/index.tsx
+++ b/app/(auth)/(tabs)/(libraries)/index.tsx
@@ -33,7 +33,11 @@ export default function index() {
});
const libraries = useMemo(
- () => data?.filter((l) => !settings?.hiddenLibraries?.includes(l.Id!)),
+ () =>
+ data
+ ?.filter((l) => !settings?.hiddenLibraries?.includes(l.Id!))
+ .filter((l) => l.CollectionType !== "music")
+ .filter((l) => l.CollectionType !== "books") || [],
[data, settings?.hiddenLibraries]
);
diff --git a/app/(auth)/(tabs)/(search)/index.tsx b/app/(auth)/(tabs)/(search)/index.tsx
index 39bb0b2a..b420be69 100644
--- a/app/(auth)/(tabs)/(search)/index.tsx
+++ b/app/(auth)/(tabs)/(search)/index.tsx
@@ -5,7 +5,6 @@ import ContinueWatchingPoster from "@/components/ContinueWatchingPoster";
import { Tag } from "@/components/GenreTags";
import { ItemCardText } from "@/components/ItemCardText";
import { JellyserrIndexPage } from "@/components/jellyseerr/JellyseerrIndexPage";
-import AlbumCover from "@/components/posters/AlbumCover";
import MoviePoster from "@/components/posters/MoviePoster";
import SeriesPoster from "@/components/posters/SeriesPoster";
import { LoadingSkeleton } from "@/components/search/LoadingSkeleton";
@@ -184,52 +183,19 @@ export default function search() {
enabled: searchType === "Library" && debouncedSearch.length > 0,
});
- const { data: artists, isFetching: l4 } = useQuery({
- queryKey: ["search", "artists", debouncedSearch],
- queryFn: () =>
- searchFn({
- query: debouncedSearch,
- types: ["MusicArtist"],
- }),
- enabled: searchType === "Library" && debouncedSearch.length > 0,
- });
-
- const { data: albums, isFetching: l5 } = useQuery({
- queryKey: ["search", "albums", debouncedSearch],
- queryFn: () =>
- searchFn({
- query: debouncedSearch,
- types: ["MusicAlbum"],
- }),
- enabled: searchType === "Library" && debouncedSearch.length > 0,
- });
-
- const { data: songs, isFetching: l6 } = useQuery({
- queryKey: ["search", "songs", debouncedSearch],
- queryFn: () =>
- searchFn({
- query: debouncedSearch,
- types: ["Audio"],
- }),
- enabled: searchType === "Library" && debouncedSearch.length > 0,
- });
-
const noResults = useMemo(() => {
return !(
- artists?.length ||
- albums?.length ||
- songs?.length ||
movies?.length ||
episodes?.length ||
series?.length ||
collections?.length ||
actors?.length
);
- }, [artists, episodes, albums, songs, movies, series, collections, actors]);
+ }, [episodes, movies, series, collections, actors]);
const loading = useMemo(() => {
- return l1 || l2 || l3 || l4 || l5 || l6 || l7 || l8;
- }, [l1, l2, l3, l4, l5, l6, l7, l8]);
+ return l1 || l2 || l3 || l7 || l8;
+ }, [l1, l2, l3, l7, l8]);
return (
<>
@@ -365,48 +331,6 @@ export default function search() {
)}
/>
- m.Id!)}
- header="Artists"
- renderItem={(item: BaseItemDto) => (
-
-
-
-
- )}
- />
- m.Id!)}
- header="Albums"
- renderItem={(item: BaseItemDto) => (
-
-
-
-
- )}
- />
- m.Id!)}
- header="Songs"
- renderItem={(item: BaseItemDto) => (
-
-
-
-
- )}
- />
) : (
diff --git a/app/(auth)/player/_layout.tsx b/app/(auth)/player/_layout.tsx
index 96d08058..4b0ae1c1 100644
--- a/app/(auth)/player/_layout.tsx
+++ b/app/(auth)/player/_layout.tsx
@@ -25,15 +25,6 @@ export default function Layout() {
animation: "fade",
}}
/>
-
>
);
diff --git a/app/(auth)/player/music-player.tsx b/app/(auth)/player/music-player.tsx
deleted file mode 100644
index fc4b8863..00000000
--- a/app/(auth)/player/music-player.tsx
+++ /dev/null
@@ -1,419 +0,0 @@
-import { Text } from "@/components/common/Text";
-import { Loader } from "@/components/Loader";
-import { Controls } from "@/components/video-player/controls/Controls";
-import { useOrientation } from "@/hooks/useOrientation";
-import { useOrientationSettings } from "@/hooks/useOrientationSettings";
-import { useWebSocket } from "@/hooks/useWebsockets";
-import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
-import { useSettings } from "@/utils/atoms/settings";
-import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
-import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
-import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
-import { secondsToTicks } from "@/utils/secondsToTicks";
-import { Api } from "@jellyfin/sdk";
-import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
-import {
- getPlaystateApi,
- getUserLibraryApi,
-} from "@jellyfin/sdk/lib/utils/api";
-import { useQuery } from "@tanstack/react-query";
-import { useHaptic } from "@/hooks/useHaptic";
-import { Image } from "expo-image";
-import { useFocusEffect, useLocalSearchParams } from "expo-router";
-import { useAtomValue } from "jotai";
-import React, { useCallback, useMemo, useRef, useState } from "react";
-import { Pressable, useWindowDimensions, View } from "react-native";
-import { useSharedValue } from "react-native-reanimated";
-import Video, { OnProgressData, VideoRef } from "react-native-video";
-
-export default function page() {
- const api = useAtomValue(apiAtom);
- const user = useAtomValue(userAtom);
- const [settings] = useSettings();
- const videoRef = useRef(null);
- const windowDimensions = useWindowDimensions();
-
- const firstTime = useRef(true);
-
- const [isPlaybackStopped, setIsPlaybackStopped] = useState(false);
- const [showControls, setShowControls] = useState(true);
- const [ignoreSafeAreas, setIgnoreSafeAreas] = useState(false);
- const [isPlaying, setIsPlaying] = useState(false);
- const [isBuffering, setIsBuffering] = useState(true);
-
- const progress = useSharedValue(0);
- const isSeeking = useSharedValue(false);
- const cacheProgress = useSharedValue(0);
-
- const lightHapticFeedback = useHaptic("light");
-
- const {
- itemId,
- audioIndex: audioIndexStr,
- subtitleIndex: subtitleIndexStr,
- mediaSourceId,
- bitrateValue: bitrateValueStr,
- } = useLocalSearchParams<{
- itemId: string;
- audioIndex: string;
- subtitleIndex: string;
- mediaSourceId: string;
- bitrateValue: string;
- }>();
-
- const audioIndex = audioIndexStr ? parseInt(audioIndexStr, 10) : undefined;
- const subtitleIndex = subtitleIndexStr
- ? parseInt(subtitleIndexStr, 10)
- : undefined;
- const bitrateValue = bitrateValueStr
- ? parseInt(bitrateValueStr, 10)
- : undefined;
-
- const {
- data: item,
- isLoading: isLoadingItem,
- isError: isErrorItem,
- } = useQuery({
- queryKey: ["item", itemId],
- queryFn: async () => {
- if (!api) return;
- const res = await getUserLibraryApi(api).getItem({
- itemId,
- userId: user?.Id,
- });
-
- return res.data;
- },
- enabled: !!itemId && !!api,
- staleTime: 0,
- });
-
- const {
- data: stream,
- isLoading: isLoadingStreamUrl,
- isError: isErrorStreamUrl,
- } = useQuery({
- queryKey: ["stream-url"],
- queryFn: async () => {
- if (!api) return;
- const res = await getStreamUrl({
- api,
- item,
- startTimeTicks: item?.UserData?.PlaybackPositionTicks!,
- userId: user?.Id,
- audioStreamIndex: audioIndex,
- maxStreamingBitrate: bitrateValue,
- mediaSourceId: mediaSourceId,
- subtitleStreamIndex: subtitleIndex,
- });
-
- if (!res) return null;
-
- const { mediaSource, sessionId, url } = res;
-
- if (!sessionId || !mediaSource || !url) return null;
-
- return {
- mediaSource,
- sessionId,
- url,
- };
- },
- });
-
- const poster = usePoster(item, api);
- const videoSource = useVideoSource(item, api, poster, stream?.url);
-
- const togglePlay = useCallback(
- async (ticks: number) => {
- lightHapticFeedback();
- if (isPlaying) {
- videoRef.current?.pause();
- await getPlaystateApi(api!).onPlaybackProgress({
- itemId: item?.Id!,
- audioStreamIndex: audioIndex ? audioIndex : undefined,
- subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
- mediaSourceId: mediaSourceId,
- positionTicks: Math.floor(ticks),
- isPaused: true,
- playMethod: stream?.url.includes("m3u8")
- ? "Transcode"
- : "DirectStream",
- playSessionId: stream?.sessionId,
- });
- } else {
- videoRef.current?.resume();
- await getPlaystateApi(api!).onPlaybackProgress({
- itemId: item?.Id!,
- audioStreamIndex: audioIndex ? audioIndex : undefined,
- subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
- mediaSourceId: mediaSourceId,
- positionTicks: Math.floor(ticks),
- isPaused: false,
- playMethod: stream?.url.includes("m3u8")
- ? "Transcode"
- : "DirectStream",
- playSessionId: stream?.sessionId,
- });
- }
- },
- [
- isPlaying,
- api,
- item,
- videoRef,
- settings,
- audioIndex,
- subtitleIndex,
- mediaSourceId,
- stream,
- ]
- );
-
- const play = useCallback(() => {
- videoRef.current?.resume();
- reportPlaybackStart();
- }, [videoRef]);
-
- const pause = useCallback(() => {
- videoRef.current?.pause();
- }, [videoRef]);
-
- const stop = useCallback(() => {
- setIsPlaybackStopped(true);
- videoRef.current?.pause();
- reportPlaybackStopped();
- }, [videoRef]);
-
- const seek = useCallback(
- (seconds: number) => {
- videoRef.current?.seek(seconds);
- },
- [videoRef]
- );
-
- const reportPlaybackStopped = async () => {
- if (!item?.Id) return;
- await getPlaystateApi(api!).onPlaybackStopped({
- itemId: item.Id,
- mediaSourceId: mediaSourceId,
- positionTicks: Math.floor(progress.value),
- playSessionId: stream?.sessionId,
- });
- };
-
- const reportPlaybackStart = async () => {
- if (!item?.Id) return;
- await getPlaystateApi(api!).onPlaybackStart({
- itemId: item?.Id,
- audioStreamIndex: audioIndex ? audioIndex : undefined,
- subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
- mediaSourceId: mediaSourceId,
- playMethod: stream?.url.includes("m3u8") ? "Transcode" : "DirectStream",
- playSessionId: stream?.sessionId,
- });
- };
-
- const onProgress = useCallback(
- async (data: OnProgressData) => {
- if (isSeeking.value === true) return;
- if (isPlaybackStopped === true) return;
-
- const ticks = data.currentTime * 10000000;
-
- progress.value = secondsToTicks(data.currentTime);
- cacheProgress.value = secondsToTicks(data.playableDuration);
- setIsBuffering(data.playableDuration === 0);
-
- if (!item?.Id || data.currentTime === 0) return;
-
- await getPlaystateApi(api!).onPlaybackProgress({
- itemId: item.Id!,
- audioStreamIndex: audioIndex ? audioIndex : undefined,
- subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
- mediaSourceId: mediaSourceId,
- positionTicks: Math.round(ticks),
- isPaused: !isPlaying,
- playMethod: stream?.url.includes("m3u8") ? "Transcode" : "DirectStream",
- playSessionId: stream?.sessionId,
- });
- },
- [
- item,
- isPlaying,
- api,
- isPlaybackStopped,
- audioIndex,
- subtitleIndex,
- mediaSourceId,
- stream,
- ]
- );
-
- useFocusEffect(
- useCallback(() => {
- play();
-
- return () => {
- stop();
- };
- }, [play, stop])
- );
-
- useOrientation();
- useOrientationSettings();
-
- useWebSocket({
- isPlaying: isPlaying,
- pauseVideo: pause,
- playVideo: play,
- stopPlayback: stop,
- });
-
- if (isLoadingItem || isLoadingStreamUrl)
- return (
-
-
-
- );
-
- if (isErrorItem || isErrorStreamUrl)
- return (
-
- Error
-
- );
-
- if (!item || !stream)
- return (
-
- Error
-
- );
-
- return (
-
-
-
-
-
- {
- setShowControls(!showControls);
- }}
- className="absolute z-0 h-full w-full opacity-0"
- >
- {videoSource && (
-
-
-
-
- );
-}
-
-export function usePoster(
- item: BaseItemDto | null | undefined,
- api: Api | null
-): string | undefined {
- const poster = useMemo(() => {
- if (!item || !api) return undefined;
- return item.Type === "Audio"
- ? `${api.basePath}/Items/${item.AlbumId}/Images/Primary?tag=${item.AlbumPrimaryImageTag}&quality=90&maxHeight=200&maxWidth=200`
- : getBackdropUrl({
- api,
- item: item,
- quality: 70,
- width: 200,
- });
- }, [item, api]);
-
- return poster ?? undefined;
-}
-
-export function useVideoSource(
- item: BaseItemDto | null | undefined,
- api: Api | null,
- poster: string | undefined,
- url?: string | null
-) {
- const videoSource = useMemo(() => {
- if (!item || !api || !url) {
- return null;
- }
-
- const startPosition = item?.UserData?.PlaybackPositionTicks
- ? Math.round(item.UserData.PlaybackPositionTicks / 10000)
- : 0;
-
- return {
- uri: url,
- isNetwork: true,
- startPosition,
- headers: getAuthHeaders(api),
- metadata: {
- artist: item?.AlbumArtist ?? undefined,
- title: item?.Name || "Unknown",
- description: item?.Overview ?? undefined,
- imageUri: poster,
- subtitle: item?.Album ?? undefined,
- },
- };
- }, [item, api, poster]);
-
- return videoSource;
-}
diff --git a/app/(auth)/player/transcoding-player.tsx b/app/(auth)/player/transcoding-player.tsx
index 971410f4..8c9a4b84 100644
--- a/app/(auth)/player/transcoding-player.tsx
+++ b/app/(auth)/player/transcoding-player.tsx
@@ -414,7 +414,6 @@ const Player = () => {
playWhenInactive={true}
allowsExternalPlayback={true}
playInBackground={true}
- pictureInPicture={true}
showNotificationControls={true}
ignoreSilentSwitch="ignore"
fullscreen={false}
@@ -532,7 +531,6 @@ export function useVideoSource(
startPosition,
headers: getAuthHeaders(api),
metadata: {
- artist: item?.AlbumArtist ?? undefined,
title: item?.Name || "Unknown",
description: item?.Overview ?? undefined,
imageUri: poster,
diff --git a/bun.lockb b/bun.lockb
index 935cb03c..de11fa7d 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/components/common/TouchableItemRouter.tsx b/components/common/TouchableItemRouter.tsx
index 04d36dc7..9ca97bee 100644
--- a/components/common/TouchableItemRouter.tsx
+++ b/components/common/TouchableItemRouter.tsx
@@ -26,18 +26,6 @@ export const itemRouter = (
return `/(auth)/(tabs)/${from}/series/${item.Id}`;
}
- if (item.Type === "MusicAlbum") {
- return `/(auth)/(tabs)/${from}/albums/${item.Id}`;
- }
-
- if (item.Type === "Audio") {
- return `/(auth)/(tabs)/${from}/albums/${item.AlbumId}`;
- }
-
- if (item.Type === "MusicArtist") {
- return `/(auth)/(tabs)/${from}/artists/${item.Id}`;
- }
-
if (item.Type === "Person" || item.Type === "Actor") {
return `/(auth)/(tabs)/${from}/actors/${item.Id}`;
}
diff --git a/components/home/Favorites.tsx b/components/home/Favorites.tsx
index 6cf05109..95920ea5 100644
--- a/components/home/Favorites.tsx
+++ b/components/home/Favorites.tsx
@@ -54,14 +54,6 @@ export const Favorites = () => {
() => fetchFavoritesByType("Playlist"),
[fetchFavoritesByType]
);
- const fetchFavoriteMusicAlbum = useCallback(
- () => fetchFavoritesByType("MusicAlbum"),
- [fetchFavoritesByType]
- );
- const fetchFavoriteAudio = useCallback(
- () => fetchFavoritesByType("Audio"),
- [fetchFavoritesByType]
- );
return (
@@ -102,18 +94,6 @@ export const Favorites = () => {
title="Playlists"
hideIfEmpty
/>
-
-
);
};
diff --git a/components/library/LibraryItemCard.tsx b/components/library/LibraryItemCard.tsx
index 17595db6..7a370405 100644
--- a/components/library/LibraryItemCard.tsx
+++ b/components/library/LibraryItemCard.tsx
@@ -60,8 +60,6 @@ export const LibraryItemCard: React.FC = ({ library, ...props }) => {
_itemType = "Series";
} else if (library.CollectionType === "boxsets") {
_itemType = "BoxSet";
- } else if (library.CollectionType === "music") {
- _itemType = "MusicAlbum";
}
return _itemType;
@@ -76,8 +74,6 @@ export const LibraryItemCard: React.FC = ({ library, ...props }) => {
nameStr = "series";
} else if (library.CollectionType === "boxsets") {
nameStr = "box sets";
- } else if (library.CollectionType === "music") {
- nameStr = "albums";
} else {
nameStr = "items";
}
diff --git a/components/music/SongsList.tsx b/components/music/SongsList.tsx
deleted file mode 100644
index 4d576f3c..00000000
--- a/components/music/SongsList.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
-import { useRouter } from "expo-router";
-import { View, ViewProps } from "react-native";
-import { SongsListItem } from "./SongsListItem";
-
-interface Props extends ViewProps {
- songs?: BaseItemDto[] | null;
- collectionId: string;
- artistId: string;
- albumId: string;
-}
-
-export const SongsList: React.FC = ({
- collectionId,
- artistId,
- albumId,
- songs = [],
- ...props
-}) => {
- const router = useRouter();
- return (
-
- {songs?.map((item: BaseItemDto, index: number) => (
-
- ))}
-
- );
-};
diff --git a/components/music/SongsListItem.tsx b/components/music/SongsListItem.tsx
deleted file mode 100644
index 552baa69..00000000
--- a/components/music/SongsListItem.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import { Text } from "@/components/common/Text";
-import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
-import { usePlaySettings } from "@/providers/PlaySettingsProvider";
-import { runtimeTicksToSeconds } from "@/utils/time";
-import { useActionSheet } from "@expo/react-native-action-sheet";
-import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
-import { useRouter } from "expo-router";
-import { useAtom } from "jotai";
-import { useCallback } from "react";
-import { TouchableOpacity, TouchableOpacityProps, View } from "react-native";
-import CastContext, {
- PlayServicesState,
- useCastDevice,
- useRemoteMediaClient,
-} from "react-native-google-cast";
-
-interface Props extends TouchableOpacityProps {
- collectionId: string;
- artistId: string;
- albumId: string;
- item: BaseItemDto;
- index: number;
-}
-
-export const SongsListItem: React.FC = ({
- collectionId,
- artistId,
- albumId,
- item,
- index,
- ...props
-}) => {
- const [api] = useAtom(apiAtom);
- const [user] = useAtom(userAtom);
- const castDevice = useCastDevice();
- const router = useRouter();
- const client = useRemoteMediaClient();
- const { showActionSheetWithOptions } = useActionSheet();
-
- const { setPlaySettings } = usePlaySettings();
-
- const openSelect = () => {
- if (!castDevice?.deviceId) {
- play("device");
- return;
- }
-
- const options = ["Chromecast", "Device", "Cancel"];
- const cancelButtonIndex = 2;
-
- showActionSheetWithOptions(
- {
- options,
- cancelButtonIndex,
- },
- (selectedIndex: number | undefined) => {
- switch (selectedIndex) {
- case 0:
- play("cast");
- break;
- case 1:
- play("device");
- break;
- case cancelButtonIndex:
- break;
- }
- }
- );
- };
-
- const play = useCallback(async (type: "device" | "cast") => {
- if (!user?.Id || !api || !item.Id) {
- console.warn("No user, api or item", user, api, item.Id);
- return;
- }
-
- const data = await setPlaySettings({
- item,
- });
-
- if (!data?.url) {
- throw new Error("play-music ~ No stream url");
- }
-
- if (type === "cast" && client) {
- await CastContext.getPlayServicesState().then((state) => {
- if (state && state !== PlayServicesState.SUCCESS)
- CastContext.showPlayServicesErrorDialog(state);
- else {
- client.loadMedia({
- mediaInfo: {
- contentUrl: data.url!,
- contentType: "video/mp4",
- metadata: {
- type: item.Type === "Episode" ? "tvShow" : "movie",
- title: item.Name || "",
- subtitle: item.Overview || "",
- },
- },
- startTime: 0,
- });
- }
- });
- } else {
- console.log("Playing on device", data.url, item.Id);
- router.push("/music-player");
- }
- }, []);
-
- return (
- {
- openSelect();
- }}
- {...props}
- >
-
- {index + 1}
-
- {item.Name}
-
- {runtimeTicksToSeconds(item.RunTimeTicks)}
-
-
-
-
- );
-};
diff --git a/components/posters/AlbumCover.tsx b/components/posters/AlbumCover.tsx
deleted file mode 100644
index 870dce6a..00000000
--- a/components/posters/AlbumCover.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { apiAtom } from "@/providers/JellyfinProvider";
-import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
-import { getPrimaryImageUrlById } from "@/utils/jellyfin/image/getPrimaryImageUrlById";
-import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
-import { Image } from "expo-image";
-import { useAtom } from "jotai";
-import { useMemo } from "react";
-import { View } from "react-native";
-
-type ArtistPosterProps = {
- item?: BaseItemDto | null;
- id?: string | null;
- showProgress?: boolean;
-};
-
-const AlbumCover: React.FC = ({ item, id }) => {
- const [api] = useAtom(apiAtom);
-
- const url = useMemo(() => {
- const u = getPrimaryImageUrl({
- api,
- item,
- });
- return u;
- }, [item]);
-
- const url2 = useMemo(() => {
- const u = getPrimaryImageUrlById({
- api,
- id,
- quality: 85,
- width: 300,
- });
- return u;
- }, [item]);
-
- if (!item && id)
- return (
-
-
-
- );
-
- if (item)
- return (
-
-
-
- );
-};
-
-export default AlbumCover;
diff --git a/components/posters/ArtistPoster.tsx b/components/posters/ArtistPoster.tsx
deleted file mode 100644
index d64818b6..00000000
--- a/components/posters/ArtistPoster.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { apiAtom } from "@/providers/JellyfinProvider";
-import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
-import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
-import { Image } from "expo-image";
-import { useAtom } from "jotai";
-import { useMemo } from "react";
-import { View } from "react-native";
-
-type ArtistPosterProps = {
- item: BaseItemDto;
- showProgress?: boolean;
-};
-
-const ArtistPoster: React.FC = ({
- item,
- showProgress = false,
-}) => {
- const [api] = useAtom(apiAtom);
-
- const url = useMemo(
- () =>
- getPrimaryImageUrl({
- api,
- item,
- }),
- [item]
- );
-
- if (!url)
- return (
-
- );
-
- return (
-
-
-
- );
-};
-
-export default ArtistPoster;
diff --git a/components/stacks/NestedTabPageStack.tsx b/components/stacks/NestedTabPageStack.tsx
index 024b1272..2cfeed1d 100644
--- a/components/stacks/NestedTabPageStack.tsx
+++ b/components/stacks/NestedTabPageStack.tsx
@@ -17,14 +17,7 @@ export const commonScreenOptions: ICommonScreenOptions = {
headerLeft: () => ,
};
-const routes = [
- "actors/[actorId]",
- "albums/[albumId]",
- "artists/index",
- "artists/[artistId]",
- "items/page",
- "series/[id]",
-];
+const routes = ["actors/[actorId]", "items/page", "series/[id]"];
export const nestedTabPageScreenOptions: Record =
Object.fromEntries(routes.map((route) => [route, commonScreenOptions]));
diff --git a/components/video-player/controls/Controls.tsx b/components/video-player/controls/Controls.tsx
index 6a5f3caa..f209a411 100644
--- a/components/video-player/controls/Controls.tsx
+++ b/components/video-player/controls/Controls.tsx
@@ -449,12 +449,11 @@ export const Controls: React.FC = ({
= ({
},
]}
pointerEvents={showControls ? "auto" : "none"}
- className={`flex flex-row w-full p-4 `}
+ className={`flex flex-row w-full pt-2`}
>
= ({
onPress={() => {
switchOnEpisodeMode();
}}
- className="aspect-square flex flex-col bg-neutral-800/90 rounded-xl items-center justify-center p-2"
+ className="aspect-square flex flex-col rounded-xl items-center justify-center p-2"
>
@@ -565,7 +564,7 @@ export const Controls: React.FC = ({
{previousItem && !offline && (
@@ -574,7 +573,7 @@ export const Controls: React.FC = ({
{nextItem && !offline && (
@@ -583,7 +582,7 @@ export const Controls: React.FC = ({
{/* {mediaSource?.TranscodingUrl && ( */}
= ({
);
router.back();
}}
- className="aspect-square flex flex-col bg-neutral-800/90 rounded-xl items-center justify-center p-2"
+ className="aspect-square flex flex-col rounded-xl items-center justify-center p-2"
>
@@ -730,11 +729,11 @@ export const Controls: React.FC = ({
bottom: settings?.safeAreaInControlsEnabled ? insets.bottom : 0,
},
]}
- className={`flex flex-col p-4`}
+ className={`flex flex-col px-2`}
onTouchStart={handleControlsInteraction}
>
= ({
}}
pointerEvents={showControls ? "box-none" : "none"}
>
- {item?.Name}
{item?.Type === "Episode" && (
- {item.SeriesName}
+
+ {`${item.SeriesName} - ${item.SeasonName} Episode ${item.IndexNumber}`}
+
)}
+ {item?.Name}
{item?.Type === "Movie" && (
{item?.ProductionYear}
@@ -786,7 +787,7 @@ export const Controls: React.FC = ({
= ({
bubbleTextColor: "#666",
heartbeatColor: "#999",
}}
- renderThumb={() => (
-
- )}
+ renderThumb={() => null}
cache={cacheProgress}
onSlidingStart={handleSliderStart}
onSlidingComplete={handleSliderComplete}
@@ -829,7 +818,7 @@ export const Controls: React.FC = ({
minimumValue={min}
maximumValue={max}
/>
-
+
{formatTimeString(currentTime, isVlc ? "ms" : "s")}
diff --git a/components/video-player/controls/VideoTouchOverlay.tsx b/components/video-player/controls/VideoTouchOverlay.tsx
index 03b0b8a3..85385acf 100644
--- a/components/video-player/controls/VideoTouchOverlay.tsx
+++ b/components/video-player/controls/VideoTouchOverlay.tsx
@@ -31,7 +31,7 @@ export const VideoTouchOverlay = ({
right: 0,
top: 0,
bottom: 0,
- opacity: showControls ? 0.5 : 0,
+ opacity: showControls ? 0.75 : 0,
}}
/>
);
diff --git a/components/video-player/controls/dropdown/DropdownViewDirect.tsx b/components/video-player/controls/dropdown/DropdownViewDirect.tsx
index 28b55fa0..e2ba25fd 100644
--- a/components/video-player/controls/dropdown/DropdownViewDirect.tsx
+++ b/components/video-player/controls/dropdown/DropdownViewDirect.tsx
@@ -74,7 +74,7 @@ const DropdownViewDirect: React.FC = ({
return (
-
+
diff --git a/components/video-player/controls/dropdown/DropdownViewTranscoding.tsx b/components/video-player/controls/dropdown/DropdownViewTranscoding.tsx
index d57cc126..5a05dd17 100644
--- a/components/video-player/controls/dropdown/DropdownViewTranscoding.tsx
+++ b/components/video-player/controls/dropdown/DropdownViewTranscoding.tsx
@@ -121,7 +121,7 @@ const DropdownView: React.FC = ({ showControls }) => {
-
+
diff --git a/package.json b/package.json
index 07fedc03..f3cc630f 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,8 @@
"preset": "jest-expo"
},
"dependencies": {
- "@bottom-tabs/react-navigation": "^0.7.1",
+ "@bottom-tabs/react-navigation": "0.7.8",
+ "react-native-bottom-tabs": "0.7.8",
"@config-plugins/ffmpeg-kit-react-native": "^8.0.0",
"@expo/react-native-action-sheet": "^4.1.0",
"@expo/vector-icons": "^14.0.4",
@@ -73,7 +74,6 @@
"react-dom": "18.2.0",
"react-native": "0.74.5",
"react-native-awesome-slider": "^2.5.6",
- "react-native-bottom-tabs": "0.7.8",
"react-native-circular-progress": "^1.4.1",
"react-native-compressor": "^1.9.0",
"react-native-country-flag": "^2.0.2",
diff --git a/providers/PlaySettingsProvider.tsx b/providers/PlaySettingsProvider.tsx
index 50f780cd..ff80bb9e 100644
--- a/providers/PlaySettingsProvider.tsx
+++ b/providers/PlaySettingsProvider.tsx
@@ -38,7 +38,6 @@ type PlaySettingsContextType = {
setPlayUrl: React.Dispatch>;
playSessionId?: string | null;
setOfflineSettings: (data: PlaybackType) => void;
- setMusicPlaySettings: (item: BaseItemDto, url: string) => void;
};
const PlaySettingsContext = createContext(
@@ -61,13 +60,6 @@ export const PlaySettingsProvider: React.FC<{ children: React.ReactNode }> = ({
_setPlaySettings(data);
}, []);
- const setMusicPlaySettings = (item: BaseItemDto, url: string) => {
- setPlaySettings({
- item: item,
- });
- setPlayUrl(url);
- };
-
const setPlaySettings = useCallback(
async (
dataOrUpdater:
@@ -147,7 +139,6 @@ export const PlaySettingsProvider: React.FC<{ children: React.ReactNode }> = ({
setPlaySettings,
playUrl,
setPlayUrl,
- setMusicPlaySettings,
setOfflineSettings,
playSessionId,
mediaSource,
diff --git a/utils/collectionTypeToItemType.ts b/utils/collectionTypeToItemType.ts
index 64c23ff0..f37fe5f4 100644
--- a/utils/collectionTypeToItemType.ts
+++ b/utils/collectionTypeToItemType.ts
@@ -10,8 +10,6 @@ import {
* readonly Unknown: "unknown";
readonly Movies: "movies";
readonly Tvshows: "tvshows";
- readonly Music: "music";
- readonly Musicvideos: "musicvideos";
readonly Trailers: "trailers";
readonly Homevideos: "homevideos";
readonly Boxsets: "boxsets";
@@ -33,8 +31,6 @@ export const colletionTypeToItemType = (
return BaseItemKind.Series;
case CollectionType.Homevideos:
return BaseItemKind.Video;
- case CollectionType.Musicvideos:
- return BaseItemKind.MusicVideo;
case CollectionType.Books:
return BaseItemKind.Book;
case CollectionType.Playlists: