From 79b87b3d726caae708b9b6f4ecbaf83e231cbf22 Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Thu, 29 Aug 2024 08:42:16 +0200 Subject: [PATCH] fix: song pages --- .../albums/[albumId].tsx | 48 ++-- .../artists/[artistId].tsx | 79 +++-- .../songs/[songId].tsx | 271 ------------------ components/music/SongsListItem.tsx | 12 +- components/stacks/NestedTabPageStack.tsx | 1 - 5 files changed, 75 insertions(+), 336 deletions(-) delete mode 100644 app/(auth)/(tabs)/(home,libraries,search)/songs/[songId].tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search)/albums/[albumId].tsx b/app/(auth)/(tabs)/(home,libraries,search)/albums/[albumId].tsx index 3e17610d..565f84c8 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/albums/[albumId].tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/albums/[albumId].tsx @@ -1,7 +1,9 @@ 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"; @@ -11,6 +13,7 @@ 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(); @@ -88,30 +91,31 @@ export default function page() { enabled: !!api && !!user?.Id, }); + const insets = useSafeAreaInsets(); + if (!album) return null; return ( - - - - - - - - {album?.Name} - {album?.ProductionYear} - - - {album.AlbumArtists?.map((a) => ( - - - {album?.AlbumArtist} - - - ))} - - - + + } + > + + {album?.Name} + + {songs?.TotalRecordCount} songs + + + - + ); } diff --git a/app/(auth)/(tabs)/(home,libraries,search)/artists/[artistId].tsx b/app/(auth)/(tabs)/(home,libraries,search)/artists/[artistId].tsx index 4a60fb06..8d82d205 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/artists/[artistId].tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/artists/[artistId].tsx @@ -8,6 +8,10 @@ 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(); @@ -82,50 +86,45 @@ export default function page() { enabled: !!api && !!user?.Id, }); - useEffect(() => { - navigation.setOptions({ - title: albums?.Items?.[0]?.AlbumArtist || "", - }); - }, [albums]); + const insets = useSafeAreaInsets(); if (!artist || !albums) return null; return ( - - - - - Albums - - } - nestedScrollEnabled - data={albums.Items} - numColumns={3} - columnWrapperStyle={{ - justifyContent: "space-between", - }} - renderItem={({ item, index }) => ( - { - router.push(`/albums/${item.Id}`); + - - - {item.Name} - {item.ProductionYear} - - - )} - keyExtractor={(item) => item.Id || ""} - /> + /> + } + > + + {artist?.Name} + + {albums.TotalRecordCount} albums + + + + {albums.Items.map((item, idx) => ( + + + + {item.Name} + {item.ProductionYear} + + + ))} + + ); } diff --git a/app/(auth)/(tabs)/(home,libraries,search)/songs/[songId].tsx b/app/(auth)/(tabs)/(home,libraries,search)/songs/[songId].tsx deleted file mode 100644 index 8a93d33d..00000000 --- a/app/(auth)/(tabs)/(home,libraries,search)/songs/[songId].tsx +++ /dev/null @@ -1,271 +0,0 @@ -import { AudioTrackSelector } from "@/components/AudioTrackSelector"; -import { Bitrate, BitrateSelector } from "@/components/BitrateSelector"; -import { Chromecast } from "@/components/Chromecast"; -import { Text } from "@/components/common/Text"; -import { DownloadItem } from "@/components/DownloadItem"; -import { Loader } from "@/components/Loader"; -import { MoviesTitleHeader } from "@/components/movies/MoviesTitleHeader"; -import { ParallaxScrollView } from "@/components/ParallaxPage"; -import { PlayButton } from "@/components/PlayButton"; -import { NextEpisodeButton } from "@/components/series/NextEpisodeButton"; -import { SimilarItems } from "@/components/SimilarItems"; -import { SubtitleTrackSelector } from "@/components/SubtitleTrackSelector"; -import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; -import { usePlayback } from "@/providers/PlaybackProvider"; -import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl"; -import { getLogoImageUrlById } from "@/utils/jellyfin/image/getLogoImageUrlById"; -import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl"; -import { getUserItemData } from "@/utils/jellyfin/user-library/getUserItemData"; -import { chromecastProfile } from "@/utils/profiles/chromecast"; -import ios from "@/utils/profiles/ios"; -import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api"; -import { useQuery } from "@tanstack/react-query"; -import { Image } from "expo-image"; -import { useLocalSearchParams, useNavigation } from "expo-router"; -import { useAtom } from "jotai"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { ScrollView, View } from "react-native"; -import CastContext, { - PlayServicesState, - useCastDevice, - useRemoteMediaClient, -} from "react-native-google-cast"; - -const page: React.FC = () => { - const local = useLocalSearchParams(); - const { songId: id } = local as { songId: string }; - - const [api] = useAtom(apiAtom); - const [user] = useAtom(userAtom); - - const { setCurrentlyPlayingState } = usePlayback(); - - const castDevice = useCastDevice(); - const navigation = useNavigation(); - - useEffect(() => { - navigation.setOptions({ - headerRight: () => ( - - - - ), - }); - }); - - const chromecastReady = useMemo(() => !!castDevice?.deviceId, [castDevice]); - const [selectedAudioStream, setSelectedAudioStream] = useState(-1); - const [selectedSubtitleStream, setSelectedSubtitleStream] = - useState(0); - const [maxBitrate, setMaxBitrate] = useState({ - key: "Max", - value: undefined, - }); - - const { data: item, isLoading: l1 } = useQuery({ - queryKey: ["item", id], - queryFn: async () => - await getUserItemData({ - api, - userId: user?.Id, - itemId: id, - }), - enabled: !!id && !!api, - staleTime: 60 * 1000, - }); - - const backdropUrl = useMemo( - () => - getBackdropUrl({ - api, - item, - quality: 90, - width: 1000, - }), - [item] - ); - - const logoUrl = useMemo( - () => (item?.Type === "Movie" ? getLogoImageUrlById({ api, item }) : null), - [item] - ); - - const { data: sessionData } = useQuery({ - queryKey: ["sessionData", item?.Id], - queryFn: async () => { - if (!api || !user?.Id || !item?.Id) return null; - const playbackData = await getMediaInfoApi(api!).getPlaybackInfo({ - itemId: item?.Id, - userId: user?.Id, - }); - - return playbackData.data; - }, - enabled: !!item?.Id && !!api && !!user?.Id, - staleTime: 0, - }); - - const { data: playbackUrl } = useQuery({ - queryKey: [ - "playbackUrl", - item?.Id, - maxBitrate, - castDevice, - selectedAudioStream, - selectedSubtitleStream, - ], - queryFn: async () => { - if (!api || !user?.Id || !sessionData) return null; - - const url = await getStreamUrl({ - api, - userId: user.Id, - item, - startTimeTicks: item?.UserData?.PlaybackPositionTicks || 0, - maxStreamingBitrate: maxBitrate.value, - sessionData, - deviceProfile: castDevice?.deviceId ? chromecastProfile : ios, - audioStreamIndex: selectedAudioStream, - subtitleStreamIndex: selectedSubtitleStream, - }); - - console.log("Transcode URL: ", url); - - return url; - }, - enabled: !!sessionData, - staleTime: 0, - }); - - const client = useRemoteMediaClient(); - - const onPressPlay = useCallback( - async (type: "device" | "cast" = "device") => { - if (!playbackUrl || !item) return; - - if (type === "cast" && client) { - await CastContext.getPlayServicesState().then((state) => { - if (state && state !== PlayServicesState.SUCCESS) - CastContext.showPlayServicesErrorDialog(state); - else { - client.loadMedia({ - mediaInfo: { - contentUrl: playbackUrl, - contentType: "video/mp4", - metadata: { - type: item.Type === "Episode" ? "tvShow" : "movie", - title: item.Name || "", - subtitle: item.Overview || "", - }, - }, - startTime: 0, - }); - } - }); - } else { - setCurrentlyPlayingState({ - item, - url: playbackUrl, - }); - } - }, - [playbackUrl, item] - ); - - if (l1) - return ( - - - - ); - - if (!item?.Id || !backdropUrl) return null; - - return ( - - } - logo={ - <> - {logoUrl ? ( - - ) : null} - - } - > - - - - {item?.ProductionYear} - - - - {playbackUrl ? ( - - ) : ( - - )} - - - - - setMaxBitrate(val)} - selected={maxBitrate} - /> - - - - - - - - - - - - - Audio - - - - {item.MediaStreams?.find((i) => i.Type === "Audio")?.DisplayTitle} - - - - - - - - - - ); -}; - -export default page; diff --git a/components/music/SongsListItem.tsx b/components/music/SongsListItem.tsx index d2688b5d..76ed9f73 100644 --- a/components/music/SongsListItem.tsx +++ b/components/music/SongsListItem.tsx @@ -71,7 +71,10 @@ export const SongsListItem: React.FC = ({ }; const play = async (type: "device" | "cast") => { - if (!user?.Id || !api || !item.Id) return; + if (!user?.Id || !api || !item.Id) { + console.warn("No user, api or item", user, api, item.Id); + return; + } const response = await getMediaInfoApi(api!).getPlaybackInfo({ itemId: item?.Id, @@ -87,9 +90,13 @@ export const SongsListItem: React.FC = ({ startTimeTicks: item?.UserData?.PlaybackPositionTicks || 0, sessionData, deviceProfile: castDevice?.deviceId ? chromecastProfile : ios, + mediaSourceId: item.Id, }); - if (!url || !item) return; + if (!url || !item) { + console.warn("No url or item", url, item.Id); + return; + } if (type === "cast" && client) { await CastContext.getPlayServicesState().then((state) => { @@ -111,6 +118,7 @@ export const SongsListItem: React.FC = ({ } }); } else { + console.log("Playing on device", url, item.Id); setCurrentlyPlayingState({ item, url, diff --git a/components/stacks/NestedTabPageStack.tsx b/components/stacks/NestedTabPageStack.tsx index 930c4bfc..32caef76 100644 --- a/components/stacks/NestedTabPageStack.tsx +++ b/components/stacks/NestedTabPageStack.tsx @@ -17,7 +17,6 @@ const routes = [ "artists/[artistId]", "collections/[collectionId]", "items/page", - "songs/[songId]", "series/[id]", ];