From 5093a67f2cc314c2fc3ddbed5bc4614cd04eb82f Mon Sep 17 00:00:00 2001 From: Violet Caulfield Date: Sun, 29 Dec 2024 07:01:10 -0600 Subject: [PATCH] reporting playback to jellyfin server for lastfm scrobbling --- components/Global/card.tsx | 1 + components/Home/helpers/recently-played.tsx | 8 ++-- components/Player/mini-player.tsx | 6 +-- components/jellyfin-api-provider.tsx | 8 +++- helpers/mappings.ts | 5 ++- package-lock.json | 20 ++++++++- package.json | 3 +- player/provider.tsx | 45 ++++++++++++++++++++- types/JellifyTrack.ts | 1 + 9 files changed, 84 insertions(+), 13 deletions(-) diff --git a/components/Global/card.tsx b/components/Global/card.tsx index 03185ace..7a1fa471 100644 --- a/components/Global/card.tsx +++ b/components/Global/card.tsx @@ -55,6 +55,7 @@ export function Card(props: CardProps) { {props.blurhash && !imageLoaded && ( )} diff --git a/components/Home/helpers/recently-played.tsx b/components/Home/helpers/recently-played.tsx index a6f5d946..28061679 100644 --- a/components/Home/helpers/recently-played.tsx +++ b/components/Home/helpers/recently-played.tsx @@ -10,8 +10,8 @@ import { usePlayerContext } from "../../../player/provider"; export default function RecentlyPlayed(): React.JSX.Element { - const { resetQueue: clearQueue, addToQueue } = usePlayerContext(); - const { apiClient } = useApiClientContext(); + const { addToQueue, resetQueue } = usePlayerContext(); + const { apiClient, sessionId } = useApiClientContext(); const { recentTracks } = useHomeContext(); useEffect(() => { @@ -37,8 +37,8 @@ export default function RecentlyPlayed(): React.JSX.Element { itemId={recentlyPlayedTrack.AlbumId!} marginRight={20} onPress={async () => { - await clearQueue(); - await addToQueue([mapDtoToTrack(apiClient!, recentlyPlayedTrack)]) + await resetQueue(false); + await addToQueue([mapDtoToTrack(apiClient!, sessionId, recentlyPlayedTrack)]) play(); }} > diff --git a/components/Player/mini-player.tsx b/components/Player/mini-player.tsx index ff721ec3..7145d6e2 100644 --- a/components/Player/mini-player.tsx +++ b/components/Player/mini-player.tsx @@ -6,7 +6,6 @@ import { usePlayerContext } from "../../player/provider"; import { BottomTabNavigationEventMap, BottomTabNavigationProp } from "@react-navigation/bottom-tabs"; import { NavigationHelpers, ParamListBase } from "@react-navigation/native"; import { BlurView } from "@react-native-community/blur"; -import { pause, play, skipToNext } from "react-native-track-player/lib/src/trackPlayer"; import Icon from "../Global/icon"; import { Text } from "../Global/text"; import { Colors } from "../../enums/colors"; @@ -15,10 +14,10 @@ export function Miniplayer({ navigation }: { navigation : NavigationHelpers navigation.navigate("Player")}> @@ -45,7 +44,6 @@ export function Miniplayer({ navigation }: { navigation : NavigationHelpers skipToNext()} /> diff --git a/components/jellyfin-api-provider.tsx b/components/jellyfin-api-provider.tsx index ae89446f..d207efc1 100644 --- a/components/jellyfin-api-provider.tsx +++ b/components/jellyfin-api-provider.tsx @@ -2,16 +2,17 @@ import { Api } from '@jellyfin/sdk'; import React, { createContext, ReactNode, SetStateAction, useContext, useEffect, useState } from 'react'; import { useApi } from '../api/queries'; import _ from 'lodash'; -import { QueryObserverResult, RefetchOptions } from '@tanstack/react-query'; import { storage } from '../constants/storage'; import { MMKVStorageKeys } from '../enums/mmkv-storage-keys'; import { JellifyServer } from '../types/JellifyServer'; import { JellifyLibrary } from '../types/JellifyLibrary'; import { JellifyUser } from '../types/JellifyUser'; +import { uuid } from "uuidv4"; interface JellyfinApiClientContext { apiClient: Api | undefined; apiPending: boolean; + sessionId: string; server: JellifyServer | undefined; setServer: React.Dispatch>; user: JellifyUser | undefined; @@ -27,6 +28,7 @@ const JellyfinApiClientContextInitializer = () => { const serverJson = storage.getString(MMKVStorageKeys.Server); const libraryJson = storage.getString(MMKVStorageKeys.Library); + const [sessionId, setSessionId] = useState(uuid()) const [user, setUser] = useState(userJson ? (JSON.parse(userJson) as JellifyUser) : undefined); const [server, setServer] = useState(serverJson ? (JSON.parse(serverJson) as JellifyServer) : undefined); const [library, setLibrary] = useState(libraryJson ? (JSON.parse(libraryJson) as JellifyLibrary) : undefined); @@ -96,6 +98,7 @@ const JellyfinApiClientContextInitializer = () => { return { apiClient, apiPending, + sessionId, server, setServer, user, @@ -110,6 +113,7 @@ export const JellyfinApiClientContext = createContext({ apiClient: undefined, apiPending: true, + sessionId: "", server: undefined, setServer: () => {}, user: undefined, @@ -125,6 +129,7 @@ export const JellyfinApiClientProvider: ({ children }: { const { apiClient, apiPending, + sessionId, server, setServer, user, @@ -140,6 +145,7 @@ export const JellyfinApiClientProvider: ({ children }: { >; queue: JellifyTrack[]; + play: () => Promise, + pause: () => Promise, resetQueue: (hideMiniplayer : boolean | undefined) => Promise; addToQueue: (tracks: JellifyTrack[]) => Promise; setPlayerState: React.Dispatch>; @@ -21,10 +25,15 @@ interface PlayerContext { const PlayerContextInitializer = () => { const queueJson = storage.getString(MMKVStorageKeys.PlayQueue); + + const { apiClient, sessionId } = useApiClientContext(); + const playStateApi = getPlaystateApi(apiClient!) + //#region State const [showPlayer, setShowPlayer] = useState(false); const [showMiniplayer, setShowMiniplayer] = useState(false); const [queue, setQueue] = useState(queueJson ? JSON.parse(queueJson) : []); + //#endregion State //#region RNTP Setup setupPlayer().then(() => console.debug("Player setup successfully")); @@ -32,6 +41,31 @@ const PlayerContextInitializer = () => { const [playerState, setPlayerState] = useState(null); //#endregion RNTP Setup + //#region Functions + const play = async () => { + rntpPlay(); + + const activeTrack = await getActiveTrack() as JellifyTrack; + playStateApi.reportPlaybackStart({ + playbackStartInfo: { + SessionId: sessionId, + ItemId: activeTrack.ItemId + } + }) + } + + const pause = async () => { + rntpPause(); + + const activeTrack = await getActiveTrack() as JellifyTrack; + playStateApi.reportPlaybackStopped({ + playbackStopInfo: { + SessionId: sessionId, + ItemId: activeTrack.ItemId + } + }) + } + const resetQueue = async (hideMiniplayer: boolean | undefined) => { console.debug("Clearing queue") await reset(); @@ -50,6 +84,7 @@ const PlayerContextInitializer = () => { setShowMiniplayer(true); } + //#endregion Functions return { showPlayer, @@ -57,6 +92,8 @@ const PlayerContextInitializer = () => { showMiniplayer, setShowMiniplayer, queue, + play, + pause, addToQueue, resetQueue, setPlayerState, @@ -69,6 +106,8 @@ export const PlayerContext = createContext({ showMiniplayer: false, setShowMiniplayer: () => {}, queue: [], + play: async () => {}, + pause: async () => {}, resetQueue: async () => {}, addToQueue: async ([]) => {}, setPlayerState: () => {}, @@ -81,6 +120,8 @@ export const PlayerProvider: ({ children }: { children: ReactNode }) => React.JS showMiniplayer, setShowMiniplayer, queue, + play, + pause, resetQueue, addToQueue, setPlayerState, @@ -92,6 +133,8 @@ export const PlayerProvider: ({ children }: { children: ReactNode }) => React.JS showMiniplayer, setShowMiniplayer, queue, + play, + pause, resetQueue, addToQueue, setPlayerState, diff --git a/types/JellifyTrack.ts b/types/JellifyTrack.ts index 3bb89f12..d662be9f 100644 --- a/types/JellifyTrack.ts +++ b/types/JellifyTrack.ts @@ -20,6 +20,7 @@ export interface JellifyTrack extends Track { rating?: RatingType | undefined; isLiveStream?: boolean | undefined; + ItemId: string | undefined; Year?: number | null | undefined; IndexNumber?: number | null | undefined; ParentIndexNumber?: number | null | undefined;