mirror of
https://github.com/Jellify-Music/App.git
synced 2026-03-14 16:09:49 -05:00
reporting playback to jellyfin server for lastfm scrobbling
This commit is contained in:
@@ -55,6 +55,7 @@ export function Card(props: CardProps) {
|
||||
{props.blurhash && !imageLoaded && (
|
||||
<Blurhash
|
||||
blurhash={props.blurhash}
|
||||
{...dimensions}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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<Para
|
||||
|
||||
const playbackState = usePlaybackState();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const activeTrack = useActiveTrack() as JellifyTrack | undefined;
|
||||
|
||||
const { play, pause } = usePlayerContext();
|
||||
|
||||
return (
|
||||
<BlurView>
|
||||
<XStack height={"$8"} onPress={() => navigation.navigate("Player")}>
|
||||
@@ -45,7 +44,6 @@ export function Miniplayer({ navigation }: { navigation : NavigationHelpers<Para
|
||||
<Icon
|
||||
large
|
||||
name="fast-forward"
|
||||
onPress={() => skipToNext()}
|
||||
/>
|
||||
</XStack>
|
||||
</XStack>
|
||||
|
||||
@@ -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<SetStateAction<JellifyServer | undefined>>;
|
||||
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<string>(uuid())
|
||||
const [user, setUser] = useState<JellifyUser | undefined>(userJson ? (JSON.parse(userJson) as JellifyUser) : undefined);
|
||||
const [server, setServer] = useState<JellifyServer | undefined>(serverJson ? (JSON.parse(serverJson) as JellifyServer) : undefined);
|
||||
const [library, setLibrary] = useState<JellifyLibrary | undefined>(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<JellyfinApiClientContext>({
|
||||
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 }: {
|
||||
<JellyfinApiClientContext.Provider value={{
|
||||
apiClient,
|
||||
apiPending,
|
||||
sessionId,
|
||||
server,
|
||||
setServer,
|
||||
user,
|
||||
|
||||
@@ -10,7 +10,7 @@ const container = "opus,mp3,aac,m4a,flac,webma,webm,wav,ogg,mpa,wma";
|
||||
// TODO: Make this configurable
|
||||
const transcodingContainer = "m4a";
|
||||
|
||||
export function mapDtoToTrack(api: Api, item: BaseItemDto, queuingType?: QueuingType) {
|
||||
export function mapDtoToTrack(api: Api, sessionId: string, item: BaseItemDto, queuingType?: QueuingType) {
|
||||
|
||||
const urlParams = {
|
||||
"Container": container,
|
||||
@@ -20,6 +20,7 @@ export function mapDtoToTrack(api: Api, item: BaseItemDto, queuingType?: Queuing
|
||||
"EnableRedirection": true,
|
||||
"api_key": api.accessToken,
|
||||
"StartTimeTicks": 0,
|
||||
"PlaySessionId": sessionId,
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -32,6 +33,8 @@ export function mapDtoToTrack(api: Api, item: BaseItemDto, queuingType?: Queuing
|
||||
album: item.Album,
|
||||
artist: item.Artists?.join(", "),
|
||||
duration: item.RunTimeTicks,
|
||||
|
||||
ItemId: item.Id!,
|
||||
QueuingType: queuingType ?? QueuingType.DirectlyQueued
|
||||
} as JellifyTrack
|
||||
}
|
||||
20
package-lock.json
generated
20
package-lock.json
generated
@@ -42,7 +42,8 @@
|
||||
"react-native-track-player": "^4.1.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-vector-icons": "^10.2.0",
|
||||
"tamagui": "^1.115.5"
|
||||
"tamagui": "^1.115.5",
|
||||
"uuidv4": "^6.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
@@ -9148,6 +9149,12 @@
|
||||
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "17.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
||||
@@ -22278,6 +22285,17 @@
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/uuidv4": {
|
||||
"version": "6.2.13",
|
||||
"resolved": "https://registry.npmjs.org/uuidv4/-/uuidv4-6.2.13.tgz",
|
||||
"integrity": "sha512-AXyzMjazYB3ovL3q051VLH06Ixj//Knx7QnUSi1T//Ie3io6CpsPu9nVMOx5MoLWh6xV0B9J0hIaxungxXUbPQ==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/uuid": "8.3.4",
|
||||
"uuid": "8.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-to-istanbul": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
|
||||
|
||||
@@ -44,7 +44,8 @@
|
||||
"react-native-track-player": "^4.1.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-vector-icons": "^10.2.0",
|
||||
"tamagui": "^1.115.5"
|
||||
"tamagui": "^1.115.5",
|
||||
"uuidv4": "^6.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
|
||||
@@ -3,9 +3,11 @@ import { JellifyTrack } from "../types/JellifyTrack";
|
||||
import { storage } from "../constants/storage";
|
||||
import { MMKVStorageKeys } from "../enums/mmkv-storage-keys";
|
||||
import { findPlayQueueIndexStart } from "./helpers";
|
||||
import { add, reset, setupPlayer } from "react-native-track-player/lib/src/trackPlayer";
|
||||
import { add, reset, play as rntpPlay, pause as rntpPause, skipToNext, skipToPrevious, setupPlayer, getActiveTrack } from "react-native-track-player/lib/src/trackPlayer";
|
||||
import _ from "lodash";
|
||||
import { buildNewQueue } from "./helpers/queue";
|
||||
import { useApiClientContext } from "../components/jellyfin-api-provider";
|
||||
import { getPlaystateApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
|
||||
interface PlayerContext {
|
||||
showPlayer: boolean;
|
||||
@@ -13,6 +15,8 @@ interface PlayerContext {
|
||||
showMiniplayer: boolean;
|
||||
setShowMiniplayer: React.Dispatch<SetStateAction<boolean>>;
|
||||
queue: JellifyTrack[];
|
||||
play: () => Promise<void>,
|
||||
pause: () => Promise<void>,
|
||||
resetQueue: (hideMiniplayer : boolean | undefined) => Promise<void>;
|
||||
addToQueue: (tracks: JellifyTrack[]) => Promise<void>;
|
||||
setPlayerState: React.Dispatch<SetStateAction<null>>;
|
||||
@@ -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<boolean>(false);
|
||||
const [showMiniplayer, setShowMiniplayer] = useState<boolean>(false);
|
||||
const [queue, setQueue] = useState<JellifyTrack[]>(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<PlayerContext>({
|
||||
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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user