diff --git a/api/queries/functions/playlists.ts b/api/queries/functions/playlists.ts new file mode 100644 index 00000000..471a1e66 --- /dev/null +++ b/api/queries/functions/playlists.ts @@ -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 { + 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) + }) + }) +} \ No newline at end of file diff --git a/api/queries/functions/recents.ts b/api/queries/functions/recents.ts index 3ff83640..3e28412c 100644 --- a/api/queries/functions/recents.ts +++ b/api/queries/functions/recents.ts @@ -29,9 +29,9 @@ export function fetchRecentlyPlayed(api: Api, libraryId: string): Promise { console.error(error); reject(error); diff --git a/api/queries/playlist.ts b/api/queries/playlist.ts new file mode 100644 index 00000000..1f7db24c --- /dev/null +++ b/api/queries/playlist.ts @@ -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); + } +}) \ No newline at end of file diff --git a/components/Album/component.tsx b/components/Album/component.tsx index 4a409cb2..bf97f720 100644 --- a/components/Album/component.tsx +++ b/components/Album/component.tsx @@ -85,8 +85,13 @@ export default function Album(props: AlbumProps): React.JSX.Element { }}/> - - Total Runtime: + + + Total Runtime: + { props.album.RunTimeTicks } diff --git a/components/Home/component.tsx b/components/Home/component.tsx index 1db1d8ef..25e83f0f 100644 --- a/components/Home/component.tsx +++ b/components/Home/component.tsx @@ -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(); @@ -80,11 +81,18 @@ function ProvidedHome({ route, navigation }: ProvidedHomeProps): React.JSX.Eleme + + + + + + + diff --git a/components/Home/helpers/playlists.tsx b/components/Home/helpers/playlists.tsx new file mode 100644 index 00000000..7e7b6498 --- /dev/null +++ b/components/Home/helpers/playlists.tsx @@ -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 ( + +

Your Playlists

+ { + return ( + { + + }} /> + ) + }} /> +
+ ) +} \ No newline at end of file diff --git a/components/Home/helpers/recently-played.tsx b/components/Home/helpers/recently-played.tsx index bfeb0a38..65f2880b 100644 --- a/components/Home/helpers/recently-played.tsx +++ b/components/Home/helpers/recently-played.tsx @@ -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"; diff --git a/enums/query-keys.ts b/enums/query-keys.ts index 6e2f8d34..cf97651d 100644 --- a/enums/query-keys.ts +++ b/enums/query-keys.ts @@ -27,4 +27,5 @@ export enum QueryKeys { ArtistImage = "ArtistImage", PlaybackStateChange = "PlaybackStateChange", Player = "Player", + UserPlaylists = "UserPlaylists", } \ No newline at end of file diff --git a/player/handlers.ts b/player/handlers.ts index bc5f2105..f2b3ff7b 100644 --- a/player/handlers.ts +++ b/player/handlers.ts @@ -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"); diff --git a/player/provider.tsx b/player/provider.tsx index eeb29a7f..dd0f470e 100644 --- a/player/provider.tsx +++ b/player/provider.tsx @@ -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) : {