Unit Testing Additions, Fix Favorites Library (#298)

Additions to testing files - including ensuring QueueProvider functionality

Fix issue where artists in the favorites wouldn't show up
This commit is contained in:
Violet Caulfield
2025-04-28 07:04:31 -05:00
committed by GitHub
parent 869c26fb2c
commit 767fa3cbcf
22 changed files with 379 additions and 249 deletions

View File

@@ -1,26 +1,18 @@
import { AlbumsProps } from '../types'
import { useSafeAreaFrame } from 'react-native-safe-area-context'
import { ItemCard } from '../Global/components/item-card'
import { FlatList, RefreshControl } from 'react-native'
import { useQuery } from '@tanstack/react-query'
import { QueryKeys } from '../../enums/query-keys'
import { fetchFavoriteAlbums } from '../../api/queries/functions/favorites'
import { fetchRecentlyAdded } from '../../api/queries/functions/recents'
import { QueryConfig } from '../../api/queries/query.config'
export default function Albums({ navigation, route }: AlbumsProps): React.JSX.Element {
const fetchRecentlyAddedAlbums = route.params.query === QueryKeys.RecentlyAdded
const {
data: albums,
refetch,
isPending,
} = useQuery({
queryKey: [route.params.query],
queryFn: () =>
fetchRecentlyAddedAlbums
? fetchRecentlyAdded(QueryConfig.limits.recents * 4, QueryConfig.limits.recents)
: fetchFavoriteAlbums(),
queryKey: [QueryKeys.FavoriteAlbums],
queryFn: fetchFavoriteAlbums,
})
return (
@@ -31,7 +23,7 @@ export default function Albums({ navigation, route }: AlbumsProps): React.JSX.El
}}
contentInsetAdjustmentBehavior='automatic'
numColumns={2}
data={albums}
data={route.params.albums ? route.params.albums : albums ? albums : []}
refreshControl={<RefreshControl refreshing={isPending} onRefresh={refetch} />}
renderItem={({ index, item: album }) => (
<ItemCard

View File

@@ -1,22 +1,23 @@
import { useSafeAreaFrame } from 'react-native-safe-area-context'
import React from 'react'
import { FlatList, RefreshControl } from 'react-native'
import { ItemCard } from '../Global/components/item-card'
import { ArtistsProps } from '../types'
import { QueryKeys } from '../../enums/query-keys'
import { useQuery } from '@tanstack/react-query'
import { fetchRecentlyPlayedArtists } from '../../api/queries/functions/recents'
import { fetchFavoriteArtists } from '../../api/queries/functions/favorites'
import { QueryConfig } from '../../api/queries/query.config'
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
import { YStack } from 'tamagui'
import { Text } from '../Global/helpers/text'
interface ArtistsComponentProps extends ArtistsProps {
artists: BaseItemDto[]
}
import { FlatList } from 'react-native'
export default function Artists({ navigation, route }: ArtistsProps): React.JSX.Element {
const {
data: favoriteArtists,
refetch,
isPending,
} = useQuery({
queryKey: [QueryKeys.FavoriteArtists],
queryFn: fetchFavoriteArtists,
})
return (
<FlatList
contentContainerStyle={{
@@ -25,7 +26,9 @@ export default function Artists({ navigation, route }: ArtistsProps): React.JSX.
}}
contentInsetAdjustmentBehavior='automatic'
numColumns={2}
data={route.params.artists}
data={
route.params.artists ? route.params.artists : favoriteArtists ? favoriteArtists : []
}
renderItem={({ index, item: artist }) => (
<ItemCard
item={artist}

View File

@@ -24,7 +24,6 @@ export default function Index({
paddingBottom={'$15'}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={refresh} />}
>
<H2>{`Recently added`}</H2>
<RecentlyAdded navigation={navigation} />
</ScrollView>
</SafeAreaView>

View File

@@ -1,9 +1,11 @@
import { StackParamList } from '../../../components/types'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { QueryKeys } from '../../../enums/query-keys'
import HorizontalCardList from '../../../components/Global/components/horizontal-list'
import { ItemCard } from '../../../components/Global/components/item-card'
import { useDiscoverContext } from '../provider'
import { View, XStack } from 'tamagui'
import { H2 } from '../../../components/Global/helpers/text'
import Icon from '../../../components/Global/helpers/icon'
export default function RecentlyAdded({
navigation,
@@ -13,28 +15,42 @@ export default function RecentlyAdded({
const { recentlyAdded } = useDiscoverContext()
return (
<HorizontalCardList
squared
data={recentlyAdded}
onSeeMore={() => {
navigation.navigate('Albums', {
query: QueryKeys.RecentlyAdded,
})
}}
renderItem={({ item }) => (
<ItemCard
caption={item.Name}
subCaption={`${item.Artists?.join(', ')}`}
squared
size={'$12'}
item={item}
onPress={() => {
navigation.navigate('Album', {
album: item,
})
}}
/>
)}
/>
<View>
<XStack
alignItems='center'
onPress={() => {
navigation.navigate('Albums', {
albums: recentlyAdded,
})
}}
>
<H2 marginLeft={'$2'}>Recently Added</H2>
<Icon name='arrow-right' />
</XStack>
<HorizontalCardList
squared
data={recentlyAdded?.length ?? 0 > 10 ? recentlyAdded!.slice(0, 10) : recentlyAdded}
onSeeMore={() => {
navigation.navigate('Albums', {
albums: recentlyAdded,
})
}}
renderItem={({ item }) => (
<ItemCard
caption={item.Name}
subCaption={`${item.Artists?.join(', ')}`}
squared
size={'$12'}
item={item}
onPress={() => {
navigation.navigate('Album', {
album: item,
})
}}
/>
)}
/>
</View>
)
}

View File

@@ -7,19 +7,15 @@ interface CategoryRoute {
name: any // ¯\_(ツ)_/¯
iconName: string
params?: {
query?: QueryKeys
queue?: Queue
tracks?: BaseItemDto[]
artists?: BaseItemDto[]
}
}
const Categories: CategoryRoute[] = [
{
name: 'Artists',
iconName: 'microphone-variant',
params: { query: QueryKeys.FavoriteArtists },
},
{ name: 'Albums', iconName: 'music-box-multiple', params: { query: QueryKeys.FavoriteAlbums } },
{ name: 'Artists', iconName: 'microphone-variant', params: {} },
{ name: 'Albums', iconName: 'music-box-multiple', params: {} },
{ name: 'Tracks', iconName: 'music-note', params: { queue: 'Favorite Tracks' } },
{ name: 'Playlists', iconName: 'playlist-music' },
]

View File

@@ -22,7 +22,7 @@ export default function PlayerScreen({
}: {
navigation: NativeStackNavigationProp<StackParamList>
}): React.JSX.Element {
const { nowPlayingIsFavorite, setNowPlayingIsFavorite, nowPlaying } = usePlayerContext()
const { nowPlaying } = usePlayerContext()
const { queueRef } = useQueueContext()
@@ -155,10 +155,7 @@ export default function PlayerScreen({
<Spacer />
<FavoriteButton
item={nowPlaying!.item}
onToggle={() => setNowPlayingIsFavorite(!nowPlayingIsFavorite)}
/>
<FavoriteButton item={nowPlaying!.item} />
</XStack>
</XStack>

View File

@@ -34,7 +34,7 @@ export type StackParamList = {
artists: BaseItemDto[] | undefined
}
Albums: {
query: QueryKeys.FavoriteAlbums | QueryKeys.RecentlyAdded
albums: BaseItemDto[] | undefined
}
Tracks: {
tracks: BaseItemDto[] | undefined

View File

@@ -3,11 +3,13 @@ module.exports = {
preset: 'jest-expo',
setupFiles: ['./node_modules/react-native-gesture-handler/jestSetup.js'],
setupFilesAfterEnv: [
'./jest/setup.js',
'./jest/setup-carplay.js',
'./jest/setup-blurhash.js',
'./jest/setup-reanimated.js',
'./jest/setup-rnfs.js',
'./jest/setup.ts',
'./jest/setup-carplay.ts',
'./jest/setup-blurhash.ts',
'./jest/setup-device-info.js', // JS to prevent Typescript implicit any warning
'./jest/setup-reanimated.ts',
'./jest/setup-rnfs.ts',
'./jest/setup-rntp.ts',
'./tamagui.config.ts',
],
extensionsToTreatAsEsm: ['.ts', '.tsx'],

View File

@@ -1,7 +1,3 @@
/**
* @format
*/
import 'react-native'
import React from 'react'
import App from '../App'

View File

@@ -0,0 +1,22 @@
import 'react-native'
import React from 'react'
import { render } from '@testing-library/react-native'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { QueueProvider } from '../player/queue-provider'
import { PlayerProvider } from '../player/player-provider'
import { View } from 'react-native'
const queryClient = new QueryClient()
test(`${PlayerProvider.name} renders correctly`, () => {
render(
<QueryClientProvider client={queryClient}>
<QueueProvider>
<PlayerProvider>
<View />
</PlayerProvider>
</QueueProvider>
</QueryClientProvider>,
)
})

View File

@@ -0,0 +1,47 @@
import 'react-native'
import React from 'react'
import { render, screen, waitFor } from '@testing-library/react-native'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { QueueProvider, useQueueContext } from '../player/queue-provider'
import { Button, Text } from 'react-native'
import { Event } from 'react-native-track-player'
import { eventHandler } from './setup-rntp'
const queryClient = new QueryClient()
const QueueConsumer = () => {
const { currentIndex, useSkip } = useQueueContext()
return (
<>
<Text testID='current-index'>{currentIndex}</Text>
<Button title='skip' testID='use-skip' onPress={() => useSkip.mutate(undefined)} />
</>
)
}
test(`${QueueProvider.name} renders and functions correctly`, async () => {
const queueProvider = render(
<QueryClientProvider client={queryClient}>
<QueueProvider>
<QueueConsumer />
</QueueProvider>
</QueryClientProvider>,
)
const currentIndex = screen.getByTestId('current-index')
expect(currentIndex.props.children).toBe(-1)
eventHandler({
type: Event.PlaybackActiveTrackChanged,
index: 3,
})
await waitFor(() => {
const updatedIndex = screen.getByTestId('current-index')
expect(updatedIndex.props.children).toBe(3)
})
})

View File

@@ -0,0 +1,4 @@
// https://github.com/react-native-device-info/react-native-device-info/issues/1360
import mockRNDeviceInfo from 'react-native-device-info/jest/react-native-device-info-mock'
jest.mock('react-native-device-info', () => mockRNDeviceInfo)

View File

@@ -1,30 +1,11 @@
// https://github.com/react-native-device-info/react-native-device-info/issues/1360
import mockRNDeviceInfo from 'react-native-device-info/jest/react-native-device-info-mock'
/* eslint-disable @typescript-eslint/no-explicit-any */
jest.mock('../api/client')
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter')
jest.mock('react-native-device-info', () => mockRNDeviceInfo)
jest.mock('react-native-haptic-feedback', () => {
return {
default: {
trigger: jest.fn(),
},
}
})
jest.mock('burnt', () => {
return {
default: {
alert: jest.fn(),
},
}
})
import * as TrackPlayer from 'react-native-track-player'
// https://github.com/doublesymmetry/react-native-track-player/issues/501
jest.mock('react-native-track-player', () => {
const listeners = new Map()
return {
__esModule: true,
default: {
@@ -68,6 +49,12 @@ jest.mock('react-native-track-player', () => {
buffered: 150,
duration: 200,
}),
usePlaybackState: () => 'playing',
// eslint-disable @typescript-eslint/no-explicit-any
useTrackPlayerEvents: (events: TrackPlayer.Event[], handler: (variables: any) => void) => {
eventHandler = handler
},
Capability: {
Play: 1,
PlayFromId: 2,
@@ -108,5 +95,18 @@ jest.mock('react-native-track-player', () => {
Record: 'record',
PlayAndRecord: 'playAndRecord',
},
Event: {
PlaybackActiveTrackChanged: 'playbackActiveTrackChanged',
},
}
})
export let eventHandler: any
beforeEach(() => {
const player = TrackPlayer as any
player.useTrackPlayerEvents = (events: Event[], handler: (variables: any) => void) => {
eventHandler = handler
}
})

19
jest/setup.ts Normal file
View File

@@ -0,0 +1,19 @@
jest.mock('../api/client')
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter')
jest.mock('react-native-haptic-feedback', () => {
return {
default: {
trigger: jest.fn(),
},
}
})
jest.mock('burnt', () => {
return {
default: {
alert: jest.fn(),
},
}
})

View File

@@ -1,122 +1,123 @@
{
"name": "jellify",
"version": "0.11.10",
"private": true,
"scripts": {
"init:ios": "yarn install && yarn pod:install",
"reinstall": "rm -rf ./node_modules && yarn install",
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest",
"clean:ios": "cd ios && pod deintegrate",
"clean:android": "cd android && rm -rf app/ build/",
"pod:install": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=0 bundle exec pod install",
"pod:install-new-arch": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install",
"fastlane:ios:build": "cd ios && bundle exec fastlane build",
"fastlane:ios:match": "cd ios && bundle exec fastlane match development",
"fastlane:ios:beta": "cd ios && bundle exec fastlane beta",
"fastlane:android:build": "cd android && bundle install && bundle exec fastlane build",
"androidBuild": "cd android && ./gradlew clean && ./gradlew assembleRelease && cd .. && echo 'find apk in android/app/build/outputs/apk/release'",
"prepare": "husky",
"format:check": "prettier --check .",
"format": "prettier --write .",
"postinstall": "patch-package"
},
"dependencies": {
"@jellyfin/sdk": "^0.11.0",
"@react-native-community/blur": "^4.4.1",
"@react-native-community/cli": "^15.1.3",
"@react-native-community/netinfo": "^11.4.1",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/material-top-tabs": "^7.2.10",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.10",
"@react-navigation/stack": "^7.2.10",
"@tamagui/config": "^1.126.1",
"@tamagui/toast": "^1.126.1",
"@tanstack/query-sync-storage-persister": "^5.74.6",
"@tanstack/react-query": "^5.74.4",
"@tanstack/react-query-persist-client": "^5.74.6",
"axios": "^1.8.4",
"bundle": "^2.1.0",
"burnt": "^0.13.0",
"expo": "^52.0.46",
"expo-image": "^2.0.7",
"gem": "^2.4.3",
"invert-color": "^2.0.0",
"jest-expo": "^52.0.6",
"lodash": "^4.17.21",
"npm-bundle": "^3.0.3",
"patch-package": "^8.0.0",
"react": "18.3.1",
"react-freeze": "^1.0.4",
"react-native": "0.77.0",
"react-native-background-actions": "^4.0.1",
"react-native-blurhash": "^2.1.1",
"react-native-boost": "^0.5.6",
"react-native-carplay": "^2.4.1-beta.0",
"react-native-device-info": "^14.0.4",
"react-native-draggable-flatlist": "^4.0.2",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.25.0",
"react-native-haptic-feedback": "^2.3.3",
"react-native-mmkv": "^2.12.2",
"react-native-pager-view": "^6.7.1",
"react-native-reanimated": "^3.17.5",
"react-native-safe-area-context": "^5.4.0",
"react-native-screens": "^4.10.0",
"react-native-swipeable-item": "^2.0.9",
"react-native-text-ticker": "^1.14.0",
"react-native-track-player": "^4.1.1",
"react-native-url-polyfill": "^2.0.0",
"react-native-uuid": "^2.0.3",
"react-native-vector-icons": "^10.2.0",
"ruby": "^0.6.1",
"tamagui": "^1.126.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli-platform-android": "15.1.3",
"@react-native-community/cli-platform-ios": "15.1.3",
"@react-native/babel-preset": "0.77.0",
"@react-native/eslint-config": "0.77.0",
"@react-native/metro-config": "0.77.0",
"@react-native/typescript-config": "0.77.0",
"@types/jest": "^29.5.13",
"@types/lodash": "^4.17.10",
"@types/react": "^18.2.6",
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^8.29.1",
"@typescript-eslint/parser": "^8.29.1",
"babel-plugin-module-resolver": "^5.0.2",
"eslint": "^8.57.1",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-native": "^5.0.0",
"husky": "^9.1.7",
"jest": "^29.6.3",
"jscodeshift": "^0.15.2",
"lint-staged": "^15.5.0",
"prettier": "^2.8.8",
"react-native-cli-bump-version": "^1.5.1",
"react-test-renderer": "18.3.1",
"typescript": "5.7.3"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint --fix"
]
},
"engines": {
"node": ">=18"
}
}
"name": "jellify",
"version": "0.11.10",
"private": true,
"scripts": {
"init:ios": "yarn install && yarn pod:install",
"reinstall": "rm -rf ./node_modules && yarn install",
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest",
"clean:ios": "cd ios && pod deintegrate",
"clean:android": "cd android && rm -rf app/ build/",
"pod:install": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=0 bundle exec pod install",
"pod:install-new-arch": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install",
"fastlane:ios:build": "cd ios && bundle exec fastlane build",
"fastlane:ios:match": "cd ios && bundle exec fastlane match development",
"fastlane:ios:beta": "cd ios && bundle exec fastlane beta",
"fastlane:android:build": "cd android && bundle install && bundle exec fastlane build",
"androidBuild": "cd android && ./gradlew clean && ./gradlew assembleRelease && cd .. && echo 'find apk in android/app/build/outputs/apk/release'",
"prepare": "husky",
"format:check": "prettier --check .",
"format": "prettier --write .",
"postinstall": "patch-package"
},
"dependencies": {
"@jellyfin/sdk": "^0.11.0",
"@react-native-community/blur": "^4.4.1",
"@react-native-community/cli": "^15.1.3",
"@react-native-community/netinfo": "^11.4.1",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/material-top-tabs": "^7.2.10",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.10",
"@react-navigation/stack": "^7.2.10",
"@tamagui/config": "^1.126.1",
"@tamagui/toast": "^1.126.1",
"@tanstack/query-sync-storage-persister": "^5.74.6",
"@tanstack/react-query": "^5.74.4",
"@tanstack/react-query-persist-client": "^5.74.6",
"@testing-library/react-native": "^13.2.0",
"axios": "^1.8.4",
"bundle": "^2.1.0",
"burnt": "^0.13.0",
"expo": "^52.0.46",
"expo-image": "^2.0.7",
"gem": "^2.4.3",
"invert-color": "^2.0.0",
"jest-expo": "^52.0.6",
"lodash": "^4.17.21",
"npm-bundle": "^3.0.3",
"patch-package": "^8.0.0",
"react": "18.3.1",
"react-freeze": "^1.0.4",
"react-native": "0.77.0",
"react-native-background-actions": "^4.0.1",
"react-native-blurhash": "^2.1.1",
"react-native-boost": "^0.5.6",
"react-native-carplay": "^2.4.1-beta.0",
"react-native-device-info": "^14.0.4",
"react-native-draggable-flatlist": "^4.0.2",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.25.0",
"react-native-haptic-feedback": "^2.3.3",
"react-native-mmkv": "^2.12.2",
"react-native-pager-view": "^6.7.1",
"react-native-reanimated": "^3.17.5",
"react-native-safe-area-context": "^5.4.0",
"react-native-screens": "^4.10.0",
"react-native-swipeable-item": "^2.0.9",
"react-native-text-ticker": "^1.14.0",
"react-native-track-player": "^4.1.1",
"react-native-url-polyfill": "^2.0.0",
"react-native-uuid": "^2.0.3",
"react-native-vector-icons": "^10.2.0",
"ruby": "^0.6.1",
"tamagui": "^1.126.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli-platform-android": "15.1.3",
"@react-native-community/cli-platform-ios": "15.1.3",
"@react-native/babel-preset": "0.77.0",
"@react-native/eslint-config": "0.77.0",
"@react-native/metro-config": "0.77.0",
"@react-native/typescript-config": "0.77.0",
"@types/jest": "^29.5.13",
"@types/lodash": "^4.17.10",
"@types/react": "^18.2.6",
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^8.29.1",
"@typescript-eslint/parser": "^8.29.1",
"babel-plugin-module-resolver": "^5.0.2",
"eslint": "^8.57.1",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-native": "^5.0.0",
"husky": "^9.1.7",
"jest": "^29.6.3",
"jscodeshift": "^0.15.2",
"lint-staged": "^15.5.0",
"prettier": "^2.8.8",
"react-native-cli-bump-version": "^1.5.1",
"react-test-renderer": "18.3.1",
"typescript": "5.7.3"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint --fix"
]
},
"engines": {
"node": ">=18"
}
}

View File

@@ -5,56 +5,59 @@ import { convertSecondsToRunTimeTicks } from '../helpers/runtimeticks'
export async function handlePlaybackState(
sessionId: string,
playstateApi: PlaystateApi,
playstateApi: PlaystateApi | undefined,
track: JellifyTrack,
state: State,
) {
switch (state) {
case State.Playing: {
console.debug('Report playback started')
await playstateApi.reportPlaybackStart({
playbackStartInfo: {
SessionId: sessionId,
ItemId: track.item.Id,
},
})
break
}
if (playstateApi)
switch (state) {
case State.Playing: {
console.debug('Report playback started')
await playstateApi.reportPlaybackStart({
playbackStartInfo: {
SessionId: sessionId,
ItemId: track.item.Id,
},
})
break
}
case State.Ended:
case State.Paused:
case State.Stopped: {
console.debug('Report playback stopped')
await playstateApi.reportPlaybackStopped({
playbackStopInfo: {
SessionId: sessionId,
ItemId: track.item.Id,
},
})
break
}
case State.Ended:
case State.Paused:
case State.Stopped: {
console.debug('Report playback stopped')
await playstateApi.reportPlaybackStopped({
playbackStopInfo: {
SessionId: sessionId,
ItemId: track.item.Id,
},
})
break
}
default: {
return
default: {
return
}
}
}
}
export async function handlePlaybackProgressUpdated(
sessionId: string,
playstateApi: PlaystateApi,
playstateApi: PlaystateApi | undefined,
track: JellifyTrack,
progress: Progress,
) {
if (Math.floor(progress.duration - progress.position) === 5) {
console.debug('Track finished, scrobbling...')
await playstateApi.reportPlaybackStopped({
playbackStopInfo: {
SessionId: sessionId,
ItemId: track.item.Id,
PositionTicks: convertSecondsToRunTimeTicks(track.duration!),
},
})
console.debug(`Track finished. ${playstateApi ? 'scrobbling...' : ''}`)
if (playstateApi)
await playstateApi.reportPlaybackStopped({
playbackStopInfo: {
SessionId: sessionId,
ItemId: track.item.Id,
PositionTicks: convertSecondsToRunTimeTicks(track.duration!),
},
})
} else {
// DO NOTHING, reporting playback will just eat up power
// Jellyfin can keep track of progress, we're going to intentionally

View File

@@ -18,6 +18,7 @@ import Client from '../api/client'
import { getPlaystateApi } from '@jellyfin/sdk/lib/utils/api'
import { useNetworkContext } from '../components/Network/provider'
import { useQueueContext } from './queue-provider'
import { PlaystateApi } from '@jellyfin/sdk/lib/generated-client/api/playstate-api'
interface PlayerContext {
nowPlaying: JellifyTrack | undefined
@@ -30,7 +31,9 @@ interface PlayerContext {
const PlayerContextInitializer = () => {
const nowPlayingJson = storage.getString(MMKVStorageKeys.NowPlaying)
const playStateApi = getPlaystateApi(Client.api!)
let playStateApi: PlaystateApi | undefined
if (Client.api) getPlaystateApi(Client.api)
//#region State
const [nowPlaying, setNowPlaying] = useState<JellifyTrack | undefined>(

View File

@@ -3583,6 +3583,16 @@
dependencies:
"@tanstack/query-core" "5.74.4"
"@testing-library/react-native@^13.2.0":
version "13.2.0"
resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-13.2.0.tgz#b4f53c69a889728abe8bc3115ba803824bcafe10"
integrity sha512-3FX+vW/JScXkoH8VSCRTYF4KCHC56y4AI6TMDISfLna6r+z8kaSEmxH1I6NVaFOxoWX9yaHDyI26xh7BykmqKw==
dependencies:
chalk "^4.1.2"
jest-matcher-utils "^29.7.0"
pretty-format "^29.7.0"
redent "^3.0.0"
"@tootallnate/once@2":
version "2.0.0"
resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz"
@@ -8478,6 +8488,11 @@ mimic-function@^5.0.0:
resolved "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz"
integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==
min-indent@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
@@ -9704,6 +9719,14 @@ recast@^0.23.3, recast@^0.23.9:
tiny-invariant "^1.3.3"
tslib "^2.0.1"
redent@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
dependencies:
indent-string "^4.0.0"
strip-indent "^3.0.0"
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
version "1.0.10"
resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz"
@@ -10594,6 +10617,13 @@ strip-final-newline@^3.0.0:
resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz"
integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==
strip-indent@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==
dependencies:
min-indent "^1.0.0"
strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"