From 7c77c70024fec709e125b21f528ec799d869427b Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Sun, 12 Jan 2025 13:40:01 +0100 Subject: [PATCH] chore: remove everything related to music --- .../albums/[albumId].tsx | 128 ------ .../artists/[artistId].tsx | 130 ------ .../artists/index.tsx | 117 ----- .../collections/[collectionId].tsx | 2 +- app/(auth)/(tabs)/(libraries)/[libraryId].tsx | 2 - app/(auth)/(tabs)/(libraries)/index.tsx | 6 +- app/(auth)/(tabs)/(search)/index.tsx | 82 +--- app/(auth)/player/_layout.tsx | 9 - app/(auth)/player/music-player.tsx | 419 ------------------ app/(auth)/player/transcoding-player.tsx | 2 - bun.lockb | Bin 593505 -> 593049 bytes components/common/TouchableItemRouter.tsx | 12 - components/home/Favorites.tsx | 20 - components/library/LibraryItemCard.tsx | 4 - components/music/SongsList.tsx | 35 -- components/music/SongsListItem.tsx | 128 ------ components/posters/AlbumCover.tsx | 82 ---- components/posters/ArtistPoster.tsx | 57 --- components/stacks/NestedTabPageStack.tsx | 9 +- components/video-player/controls/Controls.tsx | 45 +- .../controls/VideoTouchOverlay.tsx | 2 +- .../controls/dropdown/DropdownViewDirect.tsx | 2 +- .../dropdown/DropdownViewTranscoding.tsx | 2 +- package.json | 4 +- providers/PlaySettingsProvider.tsx | 9 - utils/collectionTypeToItemType.ts | 4 - 26 files changed, 32 insertions(+), 1280 deletions(-) delete mode 100644 app/(auth)/(tabs)/(home,libraries,search,favorites)/albums/[albumId].tsx delete mode 100644 app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/[artistId].tsx delete mode 100644 app/(auth)/(tabs)/(home,libraries,search,favorites)/artists/index.tsx delete mode 100644 app/(auth)/player/music-player.tsx delete mode 100644 components/music/SongsList.tsx delete mode 100644 components/music/SongsListItem.tsx delete mode 100644 components/posters/AlbumCover.tsx delete mode 100644 components/posters/ArtistPoster.tsx 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 935cb03c1af4949cacc791146afacef67f8b1329..de11fa7d5b0be268681518f5ad9c3a1cd7dce36e 100755 GIT binary patch delta 12332 zcmeI2X?PSxx5v9D>2!uYKsJ&9VG9uwlCVrd*cXvi0TB=a21E!D0)%}_P-O9nf+JKw zc{Sn!Dgp`)h$ymeVc!Cx1VPz%7eM6x&U6p(zK{3b5BIC{Jpbp^`B(L+Q>RWTpyDZVQ639GcIppy4k=X<4Yue!K=d&_tSNvmLs|^3!Qp^4a$kOPwKGC&enwFD0 zm4X;JLq%C+b}7v(52A|$n~-IYzkh1!t84>#$dd2{%e;niUqu%?i??WA+4VDPU}uri z?kEz|xx=%E;uZyOdp|9Av^yv^G<*mu{^a&U%6RLowDg|t=CMt4YBAdKu!9F=kCKb8 zeqnVseU+{ihaWyeV&&j!T?=#7zekdeq} z0=Zr)vIjq1G3A4f`u`E)2UalJKdkw}@|_-`yb zZy_bBp~z5V+i$IU16^j}Hti*vrRHeNLXMU@4TBQCZ#P&C289%lUkB8ZTDE_3y?A+9;7U=d|M_X#lP@qgN8_=bj|BO%e z(DdvpERwucqA9d`UM)Yv<=lnV5G}yl z()du%b1PUTrKLLHwvVb3SZZqKhWjO!t>r}4zGn>XwsLT zXvRIXZtj!&8U?cdNH^uhl=Bm`Hfa8;sGOcGb>fie?X(6fyso!pJ}qmpKM^Kz2+fTq z?s<(n&!Rn!W_4VjjY{I8x%2zDoU_rIS#|wfMgdv}ciPreXE@tb2YC9zy#Z+Ly!>)X zPrw`AK?JgCHMXuMV6D-lAFJ2@LX*&90gsNNNw0zK{Jkz`xzDU#{oT>eyPUmkO>;*- z>vF!0CPCBP`A1yN8)#yManVD)<+|1i&7tx;>&gCTb=)UUq#7SlYUcIt7fMZN;pdKC z=W(V=^1@GuhoiEv>l{u@?tP5nYmmgj*&E>4b9`Kac zm}J!|v_!NhcM;m-Y|%1N42N_ooY1wO4o*a zs~yX~QqR7xH!&jF%DYQ5XEvpd56o{slX6pt)8IN z{J}Nt+03m4l)pJS5Jgfvz+J?IwPnAz)}u_{CuowSBox|BG+FSZEOG3^9m2abRDOW& za;`v?urg!AUCwK^W=&NB_lS1X`K#zpS$$}h$1)xJZOvb$PrytFw+o&Y?7_81doXY# z53RLITS_!L(S%GUi<)phkdCS|@i}?^L7g zW)jJp^X(|fs$dMmT+Y|fWbl5fsI2ZXPNT9k(o>yPxZ_AOr(2Tx1vFV$bb>pdqe;SB z&Rs#16lXu{=WjTB_!zC7d(p5|;})fc-oeIm+iFVx-oWXvs-7!YVug?o0`9EA z9qU0_8)MO0c{^T5>A|JWaPDL6eTJORqsi>}dDH20w1+Lra_6)5Pf4d9Xx5EDv^UTm zx?g}MJ2YYJ>2j7Y(6!oV{_Y}nxn^kAp28wE7NGTWrwvJU-lg;)9C|O+a}@(RvxnOi zmzt`mqwdn{szPC>?zM(#s^|tJ-5ykH1O(Wbu#a`eCElfN%s@+Vr|oYPNa5j7BKKRf zZm9gKY@le3yt%QEQW6&%-W9VOO%_Ff%72$u2e`)DY6vCCAT~m77R%ANdR?mXA|+YA zY>C@k&ZL7@!j^TX4{_-;Jqy<8?igKDaU~p~IXpvYiu=6$MC!;qTP{Ev=>M|oAC)`` zX&=M;A(tT?$mK|$M6@+FzYZxskz)62WCZf4%_AFnD~{O>3T=7ZZt!mp^CH*H;{w7+HR zk4oWxwfRS7ApAXe38#bDiCg|iX>XLU!w>)w1yR**P#q~hk>!yo$TG;bc71!1D(bXe zCrAA4g(|WyQhp-E{&Ti2Ql>l0)Jft+tLyALhODkIjAm2mhEk;{sxBCVu*KM-(tw^!A4Jkj7(%}wU7b*64+x(sq zInqqi_Sg+XO2m)Yy2udpOSUdj3|zH!k>co0q;&KvQWE(uq&RXPDL;|C?P%qB;FWeE zwhYUmAPvKj@)OBhiI#{I!}V=mq|9VnTYpqa1UkWsohR*jk#c=kTNfz<>xGo|&mpCK zPP$#_hZN7AN6H8XBE`TEqzqy#(nL-{O1oU7{QgeL^)K1&Ubfqbln$rca)wp!IeJD< z`)5xfF#qf+-o*T8Px)t0`G4*yGkz@N@cdZD?}rsCc%xr?6}Zu_q{^hEUsh{3`gK=p zgoxS%aaCn(f*7<3qCkl2Dtt3U*k*{yn;~wh9YSmuB5n)BuWI}jh;dsWP73k6irorP zb1TH`tq`|Wp%BM}XuJ*Lu9~$CV&*o8%R<~!Dcd2Ew?i!2?$_RN-(OwW?)Qv3Crrm3 zG||<99W=??0dY?VKh<_8#1lIqmhFTv)NLXD5F%|CgsGP9f>^Q(qU3G}r|P*IqQ`EC z^+FU^&H{+Q0*K54h>~ip5Nm{p+5=HqW$b|%vnAH+!^LRIX3h?@H$X77gxSA{|x6Qc0}hzK?70L07#5SN9BQYi-^k`F>G zItWooofqPq5FHOeM5_gdAo31D+!LazYI_*siNg@f4ntH|w}tpah_oXRF>2`%h$TlL zN*;xXRXvYF^f(H!UWi)Cc?=@(7)0hVh&Z)Yh&4h)6++Ze8HEso3Ly%FNL1m+A;OMB zOg;`#SM3mDyAW|FAnL2}Cm_b1fH*0HOT~Tx~5@O~_ zh|5AWRw+L~B>w=h=m&_V>bwx=gy{GqM02&^M~J*1A?^v$Qnft=@x&>JWv3uotJ^~S zAw*gcL>slV2x3VQM9I?-ZB@_H5Is&qtQVraa{dGn_!C6tPY@l|S|Qd55p@RQNtJO1 zV$d0g0wFr9@UswMXCWq^h3Kkw2(ev=xSt`qsqsHUjQbhlq!8Uz>@N^Ce}S0&3q(&< zD8w-#8lQvcrDmOjn0XH3vJh!1yrVu*5Hfe5?;k$D9o zQ>_(ZjSx{+A+l7)Rfs`XAqs>Tp~A00gk6J}d<`O7?GR$S5OLQbMyv7HA;w*YI4Q(f z6?+4s<_(D1Hz3BVLLrU`(fB6BL^bOs#LSxzmxY+DQf@&c--1|l3nIsJ{+8b-{nVTm z{{HII?*986dp`6`>EZvZU+^BzE_s2-m8Z6w8&%ObY+e(8mp!wjH1qe+3zVwctDhAI(a_Kx4EM>Cy#ya+1xQW zc_!qzUVGo>PT1|_z2ZYPVx&>7@ke&mk9HM@%i0o~J7sfa;bz%fkxuf5^iU-xKY+_UfyobX^>XV=7MdG zGdiuj&4t(|IH}Wu)m$+VVpoOR%{gY#LT#>s&2jReMc7;foCJvT2Q5++VIo-G_2gIC zZqC7i)>Aph7%}>bDsc>*+_A$@&2G`07I)z!qSbA#1!Z};mR}7x8EQ-LiD&8XYE|DMYnU`y93n(^c zI(7UGz2kXdhH*vjEN?S2!Bg1n4CIYnR>^%J%i~?7tPoidi-D|y4}qk-r1(-W6G&>m z0{%&mz677D=-Ec4AUQ5t2;Ks3gGHXsvyBM9oF4S>E8Rt7 zMl!WFknFq+cLgk@KAoJ?;Q~N$5CrbQp8%)8S@1JR1TK&aJyE5J&y8hi;n zfINLn8%E(zI_g6wUocF0IZgw8Kwlt9IvixH;JHRjS$Q{JkN@9*Z^4G*s>xg}E@R9mv7QW-tbMr0?1+2Z6I6E7vK}Hl~^ei^p>$MO%607 zfNUMtIY9UgGe^KNAX~>G5Cay24}feP>0mIBePIN6oo?hjK^A^Erxm4jy=W$oT1#k|W@l0K0}*E|OPM2|uLiTY(=5x5Ok6Rsk-&+jh!+qAud4D{!C zZUu(IFwWVwRuLHq9OwA0{pyDq@sc&Amp~Q=r9erW6T{_zNNFRt=Wbk+3;F{Y zj5s5X%Dp%W$elzcM{bc_z&J1l^sPf8@1JG(CYk>>Y-2lblba0z;Ao4NlVu42` zE;CZ2q~}D`PpJ~GMfa7H)YJn;Oo|LdoNNM)QSCzRLN-7)28}>Na1JgR*$*l4N>N1z z*a@XN6}*J?@t~1%9yID0VzHTOe9#Du@(oQ+e&sOMm%kFxR%*mSqY`f+a}OFZIl?>% zIspk#2hbk)V%v&xTOj>C0onj@RbnkUCix+C9?JWG&o_y?@a=%CB+)xk7F%+?0t~%| z_7sFX3`v-}QuetjOG*+#oNKAl4jG{XdQ+5ilB(yBk}Exc%(;Zrmlw}a?go6T^wF_& zhnH)`2U*!-uczHs@+C$U9x@`r#ash=?}e11igyyouBzl=BQ)+mv-CNd`8<$beLePt z@1r_%32$2&hmD97*@@a=Xb4hbCjE;)1JGp+h-JwZU(U;(6L`Q;QK$28E z-VbD^3cclwmN z(CT;vhu$YKJ|RAdjl}b$zj;vC%d4xk&0v)rU{=&Cs_p@1#|YoYXCKebTDS1hT(?6{ zicgA9#EYit+W<3M@8&5CFkQOdMFj<#Z|QlSWr3!j&U^k!C$;_5E~i^`KP8VrF=R?>o9l9WM==Z{Gf%2KC|-#l<`|l?M6;>Yd_d zc%<(;yi1GUPgvCP&&6It3GoTM94uA)ikV4{gG-fvapak$>Pm4lEW-B*;Pl5gOq@}7 z=9Y;S?B}$rMjYGXNzR&(*)85~n+WWbR_6Ueg z2ygm43g!Omy|QDLug?f6uiyPb{Z`tntWWoZmN7f)dLxxq->jfs3c`99l~sdC_EGzT z5L47e5rb9pXvA1Gx274clFOR)^l3`3#gzEIq1!#z-+46U)MUC%N+H)0w0xCcmTSEJ zhdWlRR!`M3lb-Z_lXqgqL4C)j51Kt}@B1!s#>{|>HPcSN{;=ln24H-?Ay1#ZVXlPffMFQy>sp^E7 zt0R0LP_E6qS+zyp+E!MBdhzwOp`QC8=2~4G{wmB|=(xMi^Gvwu(CwhwF;lNrAkLBM zy9#FANZ-ej)0!TNXt!^B8yN(tPfFijugXN2VH^UdM-uN))iQ!)1Cx-)>Nm70reJXRyN5g^K1NJ0Tn=zw->MWFF9c3G#~ST%Dxw zei%u{mAdiHtG(g|&cN9ewi31lYib(!zN9?yP5Z@XUK`Y#26Y(?>&W-5<)8huJ1y>q zzDW&xkkr^2M4yU2!yFeks@hc;cUyJ03d!5IiW%cwg#m^Dm0r^D2=>gJ1%!mTQ> z2CLdyKM}r9IYT;>xVq|_i3KuW*1E8B)>=PyuKH%->Q+@EhFdbHJd(k@8A4V z^=g?Fd|v1i)JwI@)trr{)JAyk25&j;>{PGDv4gg;KQ-M~vD36SDqY$4&|(+$ZJasK zm()ZuLp5_tPXL!zDs>phn@M(E_J$&`9g&6Ti7v+3*yUUTx$8yuDwOs zo6`IQ?E0yd32dppRI%1~nEJRT3DO~vyw%m}L}E8d`6ZcCeZfyw?k2~ zPD5*N85sFL8~5x{uhlbieH(d%@9?!^X7KBGTcm|P9I$tr4tHGrLXBg`_l526ll@qI z^F3={50|u^-@xn;(TPubI1xc|Q6|~*waXl( zKWn~keV{unx&Cf_T&chI@GVt>Kv+yd2zv+-5=a0iA%tC7BL+~`fMF3LI|L*mZ=xWJg2FpW zfrx;+C@-SG0D{VzL%U+w4l=hXRE)v2mer>c4? zy=YP0Q*YLt7a!a6_Vo7K_VnC+=WN20PyMp^Y1ftk;~P!5nlZoA`VN=J?Cnt!;?m@K zetxeS`t8-vWX2dx12rw&b0DTkr#0{C8rgYEkyVgSPZ%>g>!}>=74%T_9#oY4l^^I@ zRpj8|nIlKQDf9=>SA3{zWs%oC>Gi{M2d~hznrM$A#oFzUbgevc+Q+&Uj68yr(k;lZ zh@4PP^T`J2(pn_40YRBBh%>Hd(`LWAlkf@z*UH6ghjdHEY>OnU5J? zT6S(C#m{4qVaVlQS@{|0Vm}=z{w+68V_tH#yki&?e}-+b3Jje*cIaSy5y42Mf)gnd z*leq=RYGpHM|{cd$P?RiEgat8U^{dvH!5?)P;p%%y40_Yl#a#kusTo~y<9HDXMLOHYT6%F50jIzDF|4T|0?-!jy}mX}sqaoikT>|_;K8{BN*#79X)nO1J&yJ`T9l^*o9ob| zy~;kD0c->v&;mW7MM+K+>6qzj{2ypdd+IuAw#1I`hOg^SqNQ53aoiF# zX-8U3a-xWbF)%L4$;K#cIz99EGDT?8CW9K{cCE2B%@f+!?fMB#+@^cxA9TAqvww;a zMuq)3XzkIM!oF^!7_E_~=xB;@hg54{?^=DTYb_}e;0gW0?V5@vy({CZy%DXkFHdY; zvh#vHp`W^4-Pvcw=~i$oK(j(<{$RJO)Xrm6nQm9v&nznr&-^Fdt`=xXzIswJ8%?Ga z&*r)H_f@B*dMnpao3!F5gnh!U@NAM3#V$YH?Rp07F|nme*6B%3_V5HWD=6MV6OS=2 zd~^}b8l{XRhLtZth&Rd0Ky%-%*Pi{b-(77fngmmnZ|2XTwY!`5B3rfMf1zFLbS!6m#t& zm3G&BI46Wmt2LZVw1@p2G2Tb(;0Za|B9O!*o-`I~6AlVlN>=fA(5%HH3+oP=#CDLU z1Z_D-glM`dc@eL1fJk%_OXPJylO<2A%ECgEk^yRdknVOlzqVY;WQ}#ZI@+2wOS8~A zQ#8;w<)vs=Z)JVPa`cE6sM4Rs%xKhRsJ@;W@1Uirw2$!Jy&4@kj2@*3K}-NGK$D=f zhPDsw-atol%*akgyDZ|JXwv;4k1X(N+bt^rDt)M)6o{7O3;DN6$)aE!qus7^XySkX zRZ@}OHRt^Dt!@?iA>KigqArg_>z8Pf|G6vR2sG2Dx36(C&58iTR2HrQ4#uck|=ju zF&vz4ojr3$rx;yHHTMm52B}uG>~o!dM!7!4&j}oWX`ZyLEu19o#BI+ zx0Y)hS{J_!*CsTXngCxc1#>3eEBQ2qkx{O^=D$M9BU0>sT}G=dudLcpJ42*o9JA$dk!s~hJxpynsXKEe?*yDQavCYm z{Zbk|W7j>4l)+uJ%ZZfyA8h@8DRwWzi@ajzi4evDp?Eh4DGfW3Qr>0D;IefzdF#_+>;kb!c|=x5rXnk-1E=&x zxl+H6U92Bc95K+AgOT!x6#GMLU8KzZ1X~v={h4gbsWvZC_!n&bekp!0cBl%^deT9^ATai*P-`2lDioIP(c|=NkMYdj4 zmWh&z#db!iopHYukAG|PA|F7%ZtEhY{!LpKDINUn*U<^_QI|XSXUqDK^=}3A0N=p5icDdPhIgzwiPJ78pXfI0v z@6`4Bz`yq!pC^6$k4(wm`wap2_kJTm@%Mi7|J-ka@9j4;b1FEzITZrVeA@c?;pg+u z9Qe3pVq(Yl+MV0-^k-L=^jl1zoI_c63|Qa-Wm|1LbgI&Q+>8VIJZG;7UG6- zZG%`Z#Hei$H`N9qhHr<6+79uv%GwSQz5}92h+kC14u}FFrtW~ar3!_Zyb~gBC&ce6 zXD3Ace2C*h+*Yyq5Ql}Bn-6hE9T8$y0Yqv6gsx^6KqM7HToEEbB^N?m5MoIogrP19 zvG5y+v~M6xwdfm&j=Lak3*l0oc0v3q#L8U|Wz;PpKHLqFu^S>peY6{*cM(KL5kz^_ zrwGEi2V%1jp~|%fV!aTf_CQop8-y6X7b0peL=}~_7b3hEqDY7^6;TXPAjH&ShzM0E z#N-l)xDtp+l~V#yzZBxQ5K$_&6ymTDb4wv=s3Stm+6R%k52BWuy$>R3Kg1Ow>Zs)X z5Eq14vL7N^T@+&B0f@8%5HV`e0f>$VA#Mv1t2!No_*ICN2O%1$TS9zz2qNPUM4bBQ z5Jd085Fv*l8mT^qA)H4bHVcuUTt^_*3o+^lL=&|^h~eKtM12dxz zI0{i9#MGk@$*NF@$;Tk#jzOfToMRC6k3$?6B2~p6hd3<6+~W|f)DalAbu5MXr~6o`%Rc4UwikIt|hLdx((lA-btP-$OXhKx`HwUAfLctQTU`8HgTggAl{dLPVW~ zcwA+jg$O?fQ6xk!6>$!tK!~a5Ao{36Ats-Ph&vC_PvxA4sDAP3i5HTxn&(j|y1LJU^Pmmn?(vE&lOP<2s=g+D-~{QxmcE&2hX<7J53 zLS(5}-7^hsKeoZwLysC*CC>=Lrhdz*CE1hKokk_jEcAcQ6R+B8xT3FP>9JtLd5+DF-7J42vPqg z#Bm{}s@R(lhlQAX6Czg~fzb232|opVJjgSzcV(4VF)&b_d?K*esK>o%8EHHFJAURd*9_15YaI3+0J{yhKPavgriR#c|~0*}s|Hz?3MZ;&xI zMAZxpObBj0PTo;9t^Z6FJl056UB?>7Vjc1x%5#wSQh84j^*#G1(;=Iax6Sue`Z%Lf z>W6mLQ9DcCC|20qF*tck<(~!G$2NDuF2`-S_K8}Ki7IpLtW$Ot|8vk5cnilF?HpC) zH;X*w>8bRFTR&}qs+CR4lcX0sp=60KxbD@e*u^W^#kmmH9xX*y9>+bJ=j4 zY;J!qk>5je zz++fU2lAs|B0c~Ff_LF0+TRBs010miX9?d=z)H1pmQf?xL-IB7I#>V}f<@pBFbm8E zFL_VRGA1~3C4^rG3xEO(!A(NtCm_H3CIX4&$sh+*AipB01SFm%e#3wSs|2S6VE)UYJs{S8vKgg-@xyHyNo9^6LK|9_zd);f&L%^Ov5+R!3^*+m?Jv;i$G$s9lFG! z1l$_19&7*`!KW&Aj!`32ZlvAz!?o2QU3Jd2kxRgv;3|+``WL*j-!yV{{Tr|AEn}A+{Od9b z@-#f8zFTV4tCfd(fHrcGwZMOoz6P#>E8wy>dYN%PF8T-31Hn-|FLz~Pg?|!iYtbV) zg@pTA)!Ji(Mcky=k6;ejZ-X18<L z0{)<8XPIWwVdOW&!h_&8dJOvaJb$3SN!j0#PVgyqfq1|t3|btrp}JjcL>di9KB%HfjCOjW$}BO$_<@sC!Vsiu z(rhbm7#m5*T}U^wB}fG+-~wDSau8BH(3~{?rSy4ZJGvJX11aAEOd~G`q$<7CXjDs@ zXai0_Nz6%Hegf_eN2QktG1dd1iN5a>nv3E`a#q-9`P zNb&!0q_o2v)YIxxQJfwr-wS6*=^LHq4Wdol$Rz!)ImE@LPG z629{8D(|#ofpC6EUN)R8HGld2aAHHscb7c$o7k8B+B-;9Z4w!>P1FYB5b2)`zPmbp zzzAzDS(Ab6K1a#^2kGa)RB#MkAP0}cfDH4=LnL@Ub9>_t8m;x90gdC`@d-1t4;kL< zLqT01R4-i#s;cr%8dX)>JwYASe%-n5Ode8)G z{0=*;j#E6+|5e~~X_rSYFmD~BK-2gH>Ed-2>cZY_)hO7EsOJBw@bWwFHeQnU$2&el zjpG}$&#zEVy39mJgB9vEku6rJe*~N1k^b))XFT%tl$lLNj=fi7u=?Fa7cI*V`M+~4 zT-9>pke)4SI`qcz55>#RApe(-w?b;KtyFUtt(@`q0dq2WzFXuWml;EWl{5$UeT;#Naqi#*&+1g zwAvhEKFlR%KsmFPBXx~h*2GMV_kXb%oAGWzO8+OT+XKKgqxqT7%TL~0Z&#eOYID{D zmGvHL)QoawP5nFX2j$FkU4KnoX@c{Xs!A2Gze3%qi?&W>RzMt7Q$%c4B{dLxRjn8^ zLKRdno9f5Z{8;A1{{i6c`GKxO$tS1MaAGo1*I2uy5<;ot8$pD_xlWyoH50q}KPWsp zbDv(g?Y-9b%KJa6oH;8fYyI@ouine~t07q5Ia(dE+8E}D^{S^UntQm5m6jceUe&Xb z*|nPg_b?Z!;+JUj26;T~SKi4t5BEly-ywJV!dxh_tvObpqdX6C&%YT}--MdO~O z5=)8LZW6a%jjwDbM*2VIy!u$LS1yE(>|vE?OuS~RuPU2sBmG~0ZWwj5PTPeW+FJ#h z#y8Wpdta?$ZqTK}ox{vG96dI9&xM%|-FB+&GgU1DKWD4-2(w8w|CgTATkVhRTwKsW z20`o-)9GKTWf5jL_hmP#;_oqPALaEK%B*Upag+UcRo0!Iw_KHpgtRp8_(&GAe}2NY zs86byZ~ULm3RllYnZxaXa5UTM{WHoe)%^i-TAivxQ24&;B<9NfxO7gxxM4HtY%+TZ z`+_w!5BWa>J^E#rch0;rygvn+Fd7z;|3lF~25G;wy%Y8tIc!3bV@D8eD%uQpwA-dO z)nweu)yuVf$0@1B^2rZ6!n3wWv{LIa!Qd* znYA?Ru(g(u9j^X~=#a18jHcCI`D$IX+10;r{KHm{)iYn<)^cAxCemkTtsOP$dJF@& zp~}?98!J?Fee>e~wwNRRAG*F!zSqL6wEC^?b?@K$rMAbKRsFrtOI4)@&9(X#RVc!D zA}n@v-=(TGWEb^+Q(Ij9(dqxJakcoK#TBYsLvxrvu8SMe(G2xNLn6~^>#iR+#^J|K zyH(40cIWQ9)%bYxsYw5~yp!H3im#A$x!pax_AX`bOZ6IIx1Va$h<()`E!GkbSIu#b zI@XBL?V}!UjPLdGta-7 zNBVDHtBhEnc!tRaN+NvEL)+lFF9AphL{I7$3cvJIjdvBMht=G)#8tMP$x6qZb z>e9f=Q^Yb0lWWbjYHl-gZLpRkpAfKglf7LZGPCu*1K+j2BA%YqY_~qS+@CS4t { () => 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: