mirror of
https://github.com/Jellify-Music/App.git
synced 2026-05-13 06:48:43 -05:00
Bugfix/player controls performance (#539)
* make player buttons and scrubber bar snappier by removing unnecessary state subscriptions caused by the useMutation hook * fix similar artists from being cutoff on the artist page
This commit is contained in:
+2
-2
@@ -47,7 +47,7 @@
|
||||
"@react-navigation/native-stack": "^7.3.26",
|
||||
"@sentry/react-native": "7.1.0",
|
||||
"@shopify/flash-list": "^2.0.3",
|
||||
"@tamagui/config": "1.132.25",
|
||||
"@tamagui/config": "1.133.0",
|
||||
"@tanstack/query-async-storage-persister": "5.89.0",
|
||||
"@tanstack/react-query": "5.89.0",
|
||||
"@tanstack/react-query-persist-client": "5.89.0",
|
||||
@@ -94,7 +94,7 @@
|
||||
"react-native-worklets": "0.4.1",
|
||||
"ruby": "^0.6.1",
|
||||
"scheduler": "^0.26.0",
|
||||
"tamagui": "1.132.25",
|
||||
"tamagui": "1.133.0",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -136,7 +136,7 @@ function AlbumTrackListHeader(): React.JSX.Element {
|
||||
const [networkStatus] = useNetworkStatus()
|
||||
const streamingDeviceProfile = useStreamingDeviceProfile()
|
||||
|
||||
const { mutate: loadNewQueue } = useLoadNewQueue()
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
|
||||
const { album, discs } = useAlbumContext()
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function ArtistHeader(): React.JSX.Element {
|
||||
|
||||
const streamingDeviceProfile = useStreamingDeviceProfile()
|
||||
|
||||
const { mutate: loadNewQueue } = useLoadNewQueue()
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import ItemRow from '../Global/components/item-row'
|
||||
import ArtistHeader from './header'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import SimilarArtists from './similar'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
|
||||
export default function ArtistNavigation({
|
||||
navigation,
|
||||
@@ -55,13 +56,15 @@ export default function ArtistNavigation({
|
||||
)
|
||||
|
||||
return (
|
||||
<SectionList
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
sections={sections}
|
||||
ListHeaderComponent={ArtistHeader}
|
||||
renderSectionHeader={renderSectionHeader}
|
||||
renderItem={({ item }) => <ItemRow item={item} navigation={navigation} />}
|
||||
ListFooterComponent={SimilarArtists}
|
||||
/>
|
||||
<SafeAreaView edges={['right', 'left']}>
|
||||
<SectionList
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
sections={sections}
|
||||
ListHeaderComponent={ArtistHeader}
|
||||
renderSectionHeader={renderSectionHeader}
|
||||
renderItem={({ item }) => <ItemRow item={item} navigation={navigation} />}
|
||||
ListFooterComponent={SimilarArtists}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function SimilarArtists(): React.JSX.Element {
|
||||
return (
|
||||
<YStack flex={1}>
|
||||
<Text
|
||||
padding={'$3'}
|
||||
margin={'$3'}
|
||||
fontSize={'$6'}
|
||||
bold
|
||||
>{`Similar to ${artist.Name ?? 'Unknown Artist'}`}</Text>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import React from 'react'
|
||||
import { getToken, ScrollView, Separator, View, YStack } from 'tamagui'
|
||||
import { getToken, ScrollView, View, YStack } from 'tamagui'
|
||||
import RecentlyAdded from './helpers/just-added'
|
||||
import { useDiscoverContext } from '../../providers/Discover'
|
||||
import { RefreshControl } from 'react-native'
|
||||
import PublicPlaylists from './helpers/public-playlists'
|
||||
import SuggestedArtists from './helpers/suggested-artists'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
|
||||
export default function Index(): React.JSX.Element {
|
||||
const { refreshing, refresh, publicPlaylists, suggestedArtistsInfiniteQuery } =
|
||||
@@ -19,7 +18,6 @@ export default function Index(): React.JSX.Element {
|
||||
}}
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
removeClippedSubviews
|
||||
paddingBottom={'$15'}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={refresh} />}
|
||||
>
|
||||
<YStack gap={'$3'}>
|
||||
|
||||
@@ -50,7 +50,7 @@ export default function ItemRow({
|
||||
|
||||
const deviceProfile = useStreamingDeviceProfile()
|
||||
|
||||
const { mutate: loadNewQueue } = useLoadNewQueue()
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
|
||||
const warmContext = useItemContext()
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ export default function Track({
|
||||
|
||||
const { data: nowPlaying } = useNowPlaying()
|
||||
const { data: playQueue } = useQueue()
|
||||
const { mutate: loadNewQueue } = useLoadNewQueue()
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
const [networkStatus] = useNetworkStatus()
|
||||
|
||||
const { data: mediaInfo } = useStreamedMediaInfo(track.Id)
|
||||
@@ -172,6 +172,7 @@ export default function Track({
|
||||
marginRight={'$2'}
|
||||
animation={'quick'}
|
||||
pressStyle={{ opacity: 0.5 }}
|
||||
backgroundColor={'$background'}
|
||||
>
|
||||
<XStack
|
||||
alignContent='center'
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function FrequentlyPlayedTracks(): React.JSX.Element {
|
||||
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
const { mutate: loadNewQueue } = useLoadNewQueue()
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
const { horizontalItems } = useDisplayContext()
|
||||
|
||||
return (
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function RecentlyPlayed(): React.JSX.Element {
|
||||
const navigation = useNavigation<NativeStackNavigationProp<HomeStackParamList>>()
|
||||
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
const { mutate: loadNewQueue } = useLoadNewQueue()
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
|
||||
const tracksInfiniteQuery = useRecentlyPlayedTracks()
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { ScrollView, RefreshControl } from 'react-native'
|
||||
import { YStack, Separator, getToken } from 'tamagui'
|
||||
import { YStack, getToken } from 'tamagui'
|
||||
import RecentArtists from './helpers/recent-artists'
|
||||
import RecentlyPlayed from './helpers/recently-played'
|
||||
import FrequentArtists from './helpers/frequent-artists'
|
||||
import FrequentlyPlayedTracks from './helpers/frequent-tracks'
|
||||
import { usePreventRemove } from '@react-navigation/native'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import useHomeQueries from '../../api/queries/home'
|
||||
import { usePerformanceMonitor } from '../../hooks/use-performance-monitor'
|
||||
|
||||
|
||||
@@ -4,60 +4,61 @@ import IconButton from '../../../components/Global/helpers/icon-button'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { useTogglePlayback } from '../../../providers/Player/hooks/mutations'
|
||||
import { usePlaybackState } from '../../../providers/Player/hooks/queries'
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
export default function PlayPauseButton({
|
||||
function PlayPauseButtonComponent({
|
||||
size,
|
||||
flex,
|
||||
}: {
|
||||
size?: number | undefined
|
||||
flex?: number | undefined
|
||||
}): React.JSX.Element {
|
||||
const { mutate: togglePlayback } = useTogglePlayback()
|
||||
const togglePlayback = useTogglePlayback()
|
||||
|
||||
const state = usePlaybackState()
|
||||
|
||||
let button: React.JSX.Element = <></>
|
||||
const largeIcon = useMemo(() => isUndefined(size) || size >= 20, [size])
|
||||
|
||||
console.log('state', state)
|
||||
switch (state) {
|
||||
case State.Playing: {
|
||||
button = (
|
||||
<IconButton
|
||||
circular
|
||||
largeIcon={isUndefined(size) || size >= 20}
|
||||
size={size}
|
||||
name='pause'
|
||||
testID='pause-button-test-id'
|
||||
onPress={togglePlayback}
|
||||
/>
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
case State.Buffering:
|
||||
case State.Loading: {
|
||||
button = (
|
||||
<Circle size={size} disabled borderWidth={'$1.5'} borderColor={'$primary'}>
|
||||
<Spinner margin={10} size='small' color={'$primary'} />
|
||||
</Circle>
|
||||
)
|
||||
break
|
||||
}
|
||||
const button = useMemo(() => {
|
||||
switch (state) {
|
||||
case State.Playing: {
|
||||
return (
|
||||
<IconButton
|
||||
circular
|
||||
largeIcon={largeIcon}
|
||||
size={size}
|
||||
name='pause'
|
||||
testID='pause-button-test-id'
|
||||
onPress={togglePlayback}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
default: {
|
||||
button = (
|
||||
<IconButton
|
||||
circular
|
||||
largeIcon={isUndefined(size) || size >= 20}
|
||||
size={size}
|
||||
name='play'
|
||||
testID='play-button-test-id'
|
||||
onPress={togglePlayback}
|
||||
/>
|
||||
)
|
||||
break
|
||||
case State.Buffering:
|
||||
case State.Loading: {
|
||||
return (
|
||||
<Circle size={size} disabled borderWidth={'$1.5'} borderColor={'$primary'}>
|
||||
<Spinner margin={10} size='small' color={'$primary'} />
|
||||
</Circle>
|
||||
)
|
||||
}
|
||||
|
||||
default: {
|
||||
return (
|
||||
<IconButton
|
||||
circular
|
||||
largeIcon={largeIcon}
|
||||
size={size}
|
||||
name='play'
|
||||
testID='play-button-test-id'
|
||||
onPress={togglePlayback}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [state, size, largeIcon, togglePlayback])
|
||||
|
||||
return (
|
||||
<View justifyContent='center' alignItems='center' flex={flex}>
|
||||
@@ -65,3 +66,7 @@ export default function PlayPauseButton({
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const PlayPauseButton = React.memo(PlayPauseButtonComponent)
|
||||
|
||||
export default PlayPauseButton
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
import { useShuffle } from '../../../stores/player/queue'
|
||||
|
||||
export default function Controls(): React.JSX.Element {
|
||||
const { mutate: previous } = usePrevious()
|
||||
const { mutate: skip } = useSkip()
|
||||
const previous = usePrevious()
|
||||
const skip = useSkip()
|
||||
const { data: repeatMode } = useRepeatMode()
|
||||
|
||||
const { mutate: toggleRepeatMode } = useToggleRepeatMode()
|
||||
|
||||
@@ -173,7 +173,7 @@ export default function Lyrics({
|
||||
const { lyrics } = route.params
|
||||
const { width, height } = useWindowDimensions()
|
||||
const { position } = useProgress(UPDATE_INTERVAL)
|
||||
const { mutate: seekTo } = useSeekTo()
|
||||
const seekTo = useSeekTo()
|
||||
const theme = useTheme()
|
||||
|
||||
const flatListRef = useRef<FlatList<ParsedLyricLine>>(null)
|
||||
|
||||
@@ -16,7 +16,7 @@ import useHapticFeedback from '../../../hooks/use-haptic-feedback'
|
||||
const scrubGesture = Gesture.Pan()
|
||||
|
||||
export default function Scrubber(): React.JSX.Element {
|
||||
const { isPending: seekPending, mutateAsync: seekToAsync } = useSeekTo()
|
||||
const seekTo = useSeekTo()
|
||||
const { data: nowPlaying } = useNowPlaying()
|
||||
const { width } = useSafeAreaFrame()
|
||||
|
||||
@@ -56,13 +56,12 @@ export default function Scrubber(): React.JSX.Element {
|
||||
if (
|
||||
!isUserInteractingRef.current &&
|
||||
Date.now() - lastSeekTimeRef.current > 200 && // 200ms debounce after seeking
|
||||
!seekPending &&
|
||||
Math.abs(calculatedPosition - lastPositionRef.current) > 1 // Only update if position changed significantly
|
||||
) {
|
||||
setDisplayPosition(calculatedPosition)
|
||||
lastPositionRef.current = calculatedPosition
|
||||
}
|
||||
}, [calculatedPosition, seekPending])
|
||||
}, [calculatedPosition])
|
||||
|
||||
// Handle track changes
|
||||
useEffect(() => {
|
||||
@@ -83,14 +82,14 @@ export default function Scrubber(): React.JSX.Element {
|
||||
const seekTime = Math.max(0, position / ProgressMultiplier)
|
||||
lastSeekTimeRef.current = Date.now()
|
||||
|
||||
return seekToAsync(seekTime).finally(() => {
|
||||
return seekTo(seekTime).finally(() => {
|
||||
// Small delay to let the seek settle before allowing updates
|
||||
setTimeout(() => {
|
||||
isUserInteractingRef.current = false
|
||||
}, 100)
|
||||
})
|
||||
},
|
||||
[seekToAsync],
|
||||
[seekTo],
|
||||
)
|
||||
|
||||
// Memoize time calculations to prevent unnecessary re-renders
|
||||
|
||||
@@ -26,8 +26,8 @@ import { usePrevious, useSkip } from '../../providers/Player/hooks/mutations'
|
||||
|
||||
export const Miniplayer = React.memo(function Miniplayer(): React.JSX.Element {
|
||||
const { data: nowPlaying } = useNowPlaying()
|
||||
const { mutate: skip } = useSkip()
|
||||
const { mutate: previous } = usePrevious()
|
||||
const skip = useSkip()
|
||||
const previous = usePrevious()
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
|
||||
@@ -83,8 +83,8 @@ export const Miniplayer = React.memo(function Miniplayer(): React.JSX.Element {
|
||||
)
|
||||
|
||||
return (
|
||||
<View testID='miniplayer-test-id'>
|
||||
<GestureDetector gesture={gesture}>
|
||||
<GestureDetector gesture={gesture}>
|
||||
<Animated.View testID='miniplayer-test-id' entering={FadeIn} exiting={FadeOut}>
|
||||
<YStack>
|
||||
<MiniPlayerProgress />
|
||||
<XStack paddingBottom={'$1'} alignItems='center' onPress={openPlayer}>
|
||||
@@ -138,8 +138,8 @@ export const Miniplayer = React.memo(function Miniplayer(): React.JSX.Element {
|
||||
</XStack>
|
||||
</XStack>
|
||||
</YStack>
|
||||
</GestureDetector>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</GestureDetector>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function Queue({
|
||||
const { mutate: removeUpcomingTracks } = useRemoveUpcomingTracks()
|
||||
const { mutate: removeFromQueue } = useRemoveFromQueue()
|
||||
const { mutate: reorderQueue } = useReorderQueue()
|
||||
const { mutate: skip } = useSkip()
|
||||
const skip = useSkip()
|
||||
|
||||
const trigger = useHapticFeedback()
|
||||
|
||||
|
||||
@@ -30,8 +30,6 @@ export default function PlayliistTracklistHeader(
|
||||
playlistTracks: BaseItemDto[],
|
||||
canEdit: boolean | undefined,
|
||||
): React.JSX.Element {
|
||||
const { api } = useJellifyContext()
|
||||
|
||||
const { width } = useSafeAreaFrame()
|
||||
|
||||
const { setEditing, scroll } = usePlaylistContext()
|
||||
@@ -136,7 +134,7 @@ function PlaylistHeaderControls({
|
||||
const { addToDownloadQueue, pendingDownloads } = useNetworkContext()
|
||||
const streamingDeviceProfile = useStreamingDeviceProfile()
|
||||
const downloadingDeviceProfile = useDownloadingDeviceProfile()
|
||||
const { mutate: loadNewQueue } = useLoadNewQueue()
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
const isDownloading = pendingDownloads.length != 0
|
||||
const { api } = useJellifyContext()
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ const CarPlayContextInitializer = () => {
|
||||
|
||||
const deviceProfile = useStreamingDeviceProfile()
|
||||
|
||||
const { mutate: loadNewQueue } = useLoadNewQueue()
|
||||
const loadNewQueue = useLoadNewQueue()
|
||||
|
||||
useEffect(() => {
|
||||
function onConnect() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { isUndefined } from 'lodash'
|
||||
import { SKIP_TO_PREVIOUS_THRESHOLD } from '../../../player/config'
|
||||
import TrackPlayer from 'react-native-track-player'
|
||||
import JellifyTrack from '../../../types/JellifyTrack'
|
||||
|
||||
export async function previous(): Promise<void> {
|
||||
const { position } = await TrackPlayer.getProgress()
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
PLAY_QUEUE_QUERY_KEY,
|
||||
} from '../constants/query-keys'
|
||||
import { usePlayerQueueStore, useShuffle } from '../../../stores/player/queue'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
const PLAYER_MUTATION_OPTIONS = {
|
||||
retry: false,
|
||||
@@ -51,51 +52,49 @@ export const usePlay = () => {
|
||||
* A mutation to handle toggling the playback state
|
||||
*/
|
||||
export const useTogglePlayback = () => {
|
||||
const state = usePlaybackState()
|
||||
const isCasting =
|
||||
usePlayerEngineStore((state) => state.playerEngineData) === PlayerEngine.GOOGLE_CAST
|
||||
const remoteClient = useRemoteMediaClient()
|
||||
|
||||
const trigger = useHapticFeedback()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
trigger('impactMedium')
|
||||
return useCallback(async () => {
|
||||
trigger('impactMedium')
|
||||
const { state } = await TrackPlayer.getPlaybackState()
|
||||
|
||||
if (state === State.Playing) {
|
||||
console.debug('Pausing playback')
|
||||
// handlePlaybackStateChanged(State.Paused)
|
||||
if (isCasting && remoteClient) {
|
||||
remoteClient.pause()
|
||||
return
|
||||
} else {
|
||||
TrackPlayer.pause()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const { duration, position } = await TrackPlayer.getProgress()
|
||||
if (state === State.Playing) {
|
||||
console.debug('Pausing playback')
|
||||
// handlePlaybackStateChanged(State.Paused)
|
||||
if (isCasting && remoteClient) {
|
||||
const mediaStatus = await remoteClient.getMediaStatus()
|
||||
const streamPosition = mediaStatus?.streamPosition
|
||||
if (streamPosition && duration <= streamPosition) {
|
||||
await remoteClient.seek({
|
||||
position: 0,
|
||||
resumeState: 'play',
|
||||
})
|
||||
}
|
||||
await remoteClient.play()
|
||||
remoteClient.pause()
|
||||
return
|
||||
} else {
|
||||
TrackPlayer.pause()
|
||||
return
|
||||
}
|
||||
// if the track has ended, seek to start and play
|
||||
if (duration <= position) {
|
||||
await TrackPlayer.seekTo(0)
|
||||
}
|
||||
}
|
||||
|
||||
// handlePlaybackStateChanged(State.Playing)
|
||||
return TrackPlayer.play()
|
||||
},
|
||||
})
|
||||
const { duration, position } = await TrackPlayer.getProgress()
|
||||
if (isCasting && remoteClient) {
|
||||
const mediaStatus = await remoteClient.getMediaStatus()
|
||||
const streamPosition = mediaStatus?.streamPosition
|
||||
if (streamPosition && duration <= streamPosition) {
|
||||
await remoteClient.seek({
|
||||
position: 0,
|
||||
resumeState: 'play',
|
||||
})
|
||||
}
|
||||
await remoteClient.play()
|
||||
return
|
||||
}
|
||||
// if the track has ended, seek to start and play
|
||||
if (duration <= position) {
|
||||
await TrackPlayer.seekTo(0)
|
||||
}
|
||||
|
||||
// handlePlaybackStateChanged(State.Playing)
|
||||
return TrackPlayer.play()
|
||||
}, [isCasting, remoteClient, trigger])
|
||||
}
|
||||
|
||||
export const useToggleRepeatMode = () => {
|
||||
@@ -131,9 +130,10 @@ export const useSeekTo = () => {
|
||||
|
||||
const trigger = useHapticFeedback()
|
||||
|
||||
return useMutation({
|
||||
onMutate: () => trigger('impactLight'),
|
||||
mutationFn: async (position: number) => {
|
||||
return useCallback(
|
||||
async (position: number) => {
|
||||
trigger('impactLight')
|
||||
|
||||
console.log('position', position)
|
||||
if (isCasting && remoteClient) {
|
||||
await remoteClient.seek({
|
||||
@@ -144,7 +144,8 @@ export const useSeekTo = () => {
|
||||
}
|
||||
await TrackPlayer.seekTo(position)
|
||||
},
|
||||
})
|
||||
[isCasting, remoteClient, trigger],
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,13 +211,11 @@ export const useLoadNewQueue = () => {
|
||||
|
||||
const trigger = useHapticFeedback()
|
||||
|
||||
return useMutation({
|
||||
onMutate: async () => {
|
||||
return useCallback(
|
||||
async (variables: QueueMutation) => {
|
||||
trigger('impactLight')
|
||||
await TrackPlayer.pause()
|
||||
},
|
||||
mutationFn: (variables: QueueMutation) => loadQueue({ ...variables, downloadedTracks }),
|
||||
onSuccess: async ({ finalStartIndex, tracks }, { startPlayback, queue }) => {
|
||||
const { finalStartIndex, tracks } = await loadQueue({ ...variables, downloadedTracks })
|
||||
console.debug('Successfully loaded new queue')
|
||||
if (isCasting && remoteClient) {
|
||||
await TrackPlayer.skip(finalStartIndex)
|
||||
@@ -226,54 +225,47 @@ export const useLoadNewQueue = () => {
|
||||
|
||||
await TrackPlayer.skip(finalStartIndex)
|
||||
|
||||
if (startPlayback) await TrackPlayer.play()
|
||||
if (variables.startPlayback) await TrackPlayer.play()
|
||||
|
||||
queryClient.setQueryData(PLAY_QUEUE_QUERY_KEY, tracks)
|
||||
queryClient.setQueryData(ACTIVE_INDEX_QUERY_KEY, finalStartIndex)
|
||||
queryClient.setQueryData(NOW_PLAYING_QUERY_KEY, tracks[finalStartIndex])
|
||||
|
||||
usePlayerQueueStore.getState().setQueueRef(queue)
|
||||
usePlayerQueueStore.getState().setQueueRef(variables.queue)
|
||||
refetchPlayerQueue()
|
||||
},
|
||||
onError: async (error: Error) => {
|
||||
trigger('notificationError')
|
||||
console.error('Failed to load new queue', error)
|
||||
},
|
||||
onSettled: refetchPlayerQueue,
|
||||
})
|
||||
[isCasting, remoteClient, navigation, downloadedTracks, trigger],
|
||||
)
|
||||
}
|
||||
|
||||
export const usePrevious = () =>
|
||||
useMutation({
|
||||
mutationFn: previous,
|
||||
onSuccess: async () => {
|
||||
console.debug('Skipped to previous track')
|
||||
refetchNowPlaying()
|
||||
},
|
||||
onError: async (error: Error) => {
|
||||
console.error('Failed to skip to previous track:', error)
|
||||
},
|
||||
})
|
||||
export const usePrevious = () => {
|
||||
const trigger = useHapticFeedback()
|
||||
|
||||
return useCallback(async () => {
|
||||
trigger('impactMedium')
|
||||
|
||||
await previous()
|
||||
console.debug('Skipped to previous track')
|
||||
refetchNowPlaying()
|
||||
}, [trigger])
|
||||
}
|
||||
|
||||
export const useSkip = () => {
|
||||
const trigger = useHapticFeedback()
|
||||
|
||||
return useMutation({
|
||||
onMutate: (index?: number | undefined) => {
|
||||
return useCallback(
|
||||
async (index?: number | undefined) => {
|
||||
trigger('impactMedium')
|
||||
|
||||
console.debug(
|
||||
`Skip to next triggered. ${!isUndefined(index) ? `Index is using ${index} as index since it was provided` : ''}`,
|
||||
)
|
||||
},
|
||||
mutationFn: skip,
|
||||
onSuccess: async () => {
|
||||
skip(index)
|
||||
console.debug('Skipped to next track')
|
||||
refetchNowPlaying()
|
||||
},
|
||||
onError: async (error: Error) => {
|
||||
console.error('Failed to skip to next track:', error)
|
||||
},
|
||||
})
|
||||
[trigger],
|
||||
)
|
||||
}
|
||||
|
||||
export const useRemoveFromQueue = () => {
|
||||
@@ -365,13 +357,10 @@ export const useToggleShuffle = () => {
|
||||
}
|
||||
|
||||
export const useAudioNormalization = () =>
|
||||
useMutation({
|
||||
onMutate: () => console.debug('Normalizing audio level'),
|
||||
mutationFn: async (track: JellifyTrack) => {
|
||||
const volume = calculateTrackVolume(track)
|
||||
await TrackPlayer.setVolume(volume)
|
||||
return volume
|
||||
},
|
||||
onSuccess: (volume) => console.debug(`Audio level set to ${volume}`),
|
||||
onError: (error) => console.error('Failed to apply audio normalization', error),
|
||||
})
|
||||
useCallback(async (track: JellifyTrack) => {
|
||||
console.debug('Normalizing audio level')
|
||||
const volume = calculateTrackVolume(track)
|
||||
await TrackPlayer.setVolume(volume)
|
||||
console.debug(`Audio level set to ${volume}`)
|
||||
return volume
|
||||
}, [])
|
||||
|
||||
Reference in New Issue
Block a user