ADd playlists to home screen

lots of backend player adjustments to get lastfm scrobbling
This commit is contained in:
Violet Caulfield
2025-01-07 06:56:05 -06:00
parent d8ab107a2f
commit 1dd0c06d05
10 changed files with 138 additions and 12 deletions

View File

@@ -0,0 +1,32 @@
import { Api } from "@jellyfin/sdk";
import { BaseItemDto, ItemSortBy, SortOrder } from "@jellyfin/sdk/lib/generated-client/models";
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
export function fetchUserPlaylists(api: Api, userId: string, playlistLibraryId: string): Promise<BaseItemDto[]> {
console.debug("Fetching user playlists");
return new Promise(async (resolve, reject) => {
getItemsApi(api)
.getItems({
userId: userId,
parentId: playlistLibraryId,
sortBy: [
ItemSortBy.IsFolder,
ItemSortBy.SortName
],
sortOrder: [
SortOrder.Ascending
]
})
.then((response) => {
if (response.data.Items)
resolve(response.data.Items)
else
resolve([]);
})
.catch((error) => {
console.error(error);
reject(error)
})
})
}

View File

@@ -29,9 +29,9 @@ export function fetchRecentlyPlayed(api: Api, libraryId: string): Promise<BaseIt
if (response.data.Items)
resolve(response.data.Items);
else {
else
resolve([]);
}
}).catch((error) => {
console.error(error);
reject(error);

15
api/queries/playlist.ts Normal file
View File

@@ -0,0 +1,15 @@
import { QueryKeys } from "@/enums/query-keys";
import { Api } from "@jellyfin/sdk";
import { useQuery } from "@tanstack/react-query";
import { fetchUserPlaylists } from "./functions/playlists";
export const useUserPlaylists = (api: Api, userId: string, playlistLibraryId: string) => useQuery({
queryKey: [QueryKeys.UserPlaylists, api, userId, playlistLibraryId],
queryFn: ({ queryKey }) => {
const api: Api = queryKey[1] as Api;
const userId: string = queryKey[2] as string;
const playlistLibraryId: string = queryKey[3] as string;
return fetchUserPlaylists(api, userId, playlistLibraryId);
}
})

View File

@@ -85,8 +85,13 @@ export default function Album(props: AlbumProps): React.JSX.Element {
}}/>
<XStack alignContent="flex-end">
<Text style={{display: "block"}}>Total Runtime:</Text>
<XStack justifyContent="flex-end">
<Text
color={"$gray10"}
style={{ display: "block"}}
>
Total Runtime:
</Text>
<RunTimeTicks>{ props.album.RunTimeTicks }</RunTimeTicks>
</XStack>
</ScrollView>

View File

@@ -11,6 +11,7 @@ import { HomeArtistScreen } from "./screens/artist";
import { SafeAreaView } from "react-native-safe-area-context";
import Avatar from "../Global/avatar";
import { HomeAlbumScreen } from "./screens/album";
import Playlists from "./helpers/playlists";
export const HomeStack = createNativeStackNavigator<StackParamList>();
@@ -80,11 +81,18 @@ function ProvidedHome({ route, navigation }: ProvidedHomeProps): React.JSX.Eleme
<YStack />
<Avatar maxHeight={30} itemId={user!.id} />
</XStack>
<Separator marginVertical={15} />
<RecentArtists route={route} navigation={navigation} />
<Separator marginVertical={15} />
<RecentlyPlayed />
<Separator marginVertical={15} />
<Playlists />
</YStack>
</ScrollView>
</SafeAreaView>

View File

@@ -0,0 +1,32 @@
import { useUserPlaylists } from "@/api/queries/playlist";
import { Card } from "@/components/Global/card";
import { H2 } from "@/components/Global/text";
import { useApiClientContext } from "@/components/jellyfin-api-provider";
import React from "react";
import { FlatList } from "react-native";
import { ScrollView, View } from "tamagui";
export default function Playlists() : React.JSX.Element {
const { apiClient, user, library } = useApiClientContext();
const { data: playlists } = useUserPlaylists(apiClient!, user!.id, library!.playlistLibraryId);
return (
<View>
<H2>Your Playlists</H2>
<FlatList horizontal
data={playlists}
renderItem={({ item: playlist }) => {
return (
<Card
itemId={playlist.Id!}
caption={playlist.Name ?? "Untitled Playlist"}
onPress={() => {
}} />
)
}} />
</View>
)
}

View File

@@ -1,7 +1,7 @@
import React, { useEffect } from "react";
import { H5, ScrollView, View } from "tamagui";
import { ScrollView, View } from "tamagui";
import { useHomeContext } from "../provider";
import { H2, Text } from "../../Global/text";
import { H2 } from "../../Global/text";
import { Card } from "../../Global/card";
import { mapDtoToTrack } from "../../../helpers/mappings";
import { useApiClientContext } from "../../jellyfin-api-provider";

View File

@@ -27,4 +27,5 @@ export enum QueryKeys {
ArtistImage = "ArtistImage",
PlaybackStateChange = "PlaybackStateChange",
Player = "Player",
UserPlaylists = "UserPlaylists",
}

View File

@@ -1,8 +1,36 @@
import { Progress } from "react-native-track-player";
import { Progress, State } from "react-native-track-player";
import { JellifyTrack } from "../types/JellifyTrack";
import { PlaystateApi } from "@jellyfin/sdk/lib/generated-client/api/playstate-api";
import { convertSecondsToRunTimeTicks } from "@/helpers/runtimeticks";
export async function handlePlaybackState(sessionId: string, playstateApi: PlaystateApi, track: JellifyTrack, state: State) {
switch (state) {
case (State.Playing) : {
return playstateApi.reportPlaybackStart({
playbackStartInfo: {
SessionId: sessionId,
ItemId: track.ItemId
}
})
}
case (State.Ended) :
case (State.Paused) :
case (State.Stopped) : {
return playstateApi.reportPlaybackStopped({
playbackStopInfo: {
SessionId: sessionId,
ItemId: track.ItemId
}
})
}
default : {
}
}
}
export async function handlePlaybackStopped(sessionId: string, playstateApi: PlaystateApi, track: JellifyTrack) {
console.debug("Stopping playback for session");

View File

@@ -8,7 +8,7 @@ import _ from "lodash";
import { buildNewQueue } from "./helpers/queue";
import { useApiClientContext } from "../components/jellyfin-api-provider";
import { getPlaystateApi } from "@jellyfin/sdk/lib/utils/api";
import { handlePlaybackProgressUpdated, handlePlaybackStarted, handlePlaybackStopped } from "./handlers";
import { handlePlaybackProgressUpdated, handlePlaybackStarted, handlePlaybackState, handlePlaybackStopped } from "./handlers";
import { useSetupPlayer } from "@/components/Player/hooks";
import { UPDATE_INTERVAL } from "./config";
import { sleep } from "@/helpers/sleep";
@@ -85,14 +85,19 @@ const PlayerContextInitializer = () => {
useTrackPlayerEvents([
Event.PlaybackProgressUpdated,
Event.PlaybackState,
Event.PlaybackActiveTrackChanged,
], async (event) => {
switch (event.type) {
case (Event.PlaybackState) : {
handlePlaybackState(sessionId, playStateApi, nowPlaying!, event.state);
break;
}
case (Event.PlaybackProgressUpdated) : {
if (event.position === event.duration - 10)
handlePlaybackStopped(sessionId, playStateApi, nowPlaying!)
else
handlePlaybackProgressUpdated(sessionId, playStateApi, nowPlaying!, event)
handlePlaybackProgressUpdated(sessionId, playStateApi, nowPlaying!, event);
break;
}
case (Event.PlaybackActiveTrackChanged) : {