diff --git a/src/components/Album/header.tsx b/src/components/Album/header.tsx index 47cfbef2..c6221eb3 100644 --- a/src/components/Album/header.tsx +++ b/src/components/Album/header.tsx @@ -89,7 +89,7 @@ export default function AlbumTrackListHeader({ album }: { album: BaseItemDto }): ) : null} - + {album.RunTimeTicks} diff --git a/src/components/Global/helpers/switch-with-label.tsx b/src/components/Global/helpers/switch-with-label.tsx index b8eb1c59..44e82a92 100644 --- a/src/components/Global/helpers/switch-with-label.tsx +++ b/src/components/Global/helpers/switch-with-label.tsx @@ -12,8 +12,8 @@ interface SwitchWithLabelProps { // Use theme tokens so thumb colors follow the active color preset const JellifySliderThumb = styled(Switch.Thumb, { - borderColor: '$primary', - backgroundColor: '$background', + borderColor: '$color', + backgroundColor: '$color', }) export function SwitchWithLabel(props: SwitchWithLabelProps) { @@ -36,7 +36,7 @@ export function SwitchWithLabel(props: SwitchWithLabelProps) { > - + diff --git a/src/hooks/player/functions/controls.ts b/src/hooks/player/functions/controls.ts index 40c1c0ad..c18af869 100644 --- a/src/hooks/player/functions/controls.ts +++ b/src/hooks/player/functions/controls.ts @@ -1,6 +1,8 @@ +import { usePlayerQueueStore } from '../../../stores/player/queue' import { SKIP_TO_PREVIOUS_THRESHOLD } from '../../../configs/player.config' import { isUndefined } from 'lodash' import { TrackPlayer } from 'react-native-nitro-player' +import { usePlayerPlaybackStore } from '../../../stores/player/playback' /** * A function that will skip to the previous track if @@ -15,9 +17,15 @@ import { TrackPlayer } from 'react-native-nitro-player' * Does not resume playback if the player was paused */ export async function previous(): Promise { - const { currentPosition, currentState } = await TrackPlayer.getState() + const { currentState } = await TrackPlayer.getState() - if (Math.floor(currentPosition) < SKIP_TO_PREVIOUS_THRESHOLD) { + const { position } = usePlayerPlaybackStore.getState() + + const { currentIndex } = usePlayerQueueStore.getState() + + if (isUndefined(currentIndex)) return + + if (Math.floor(position) < SKIP_TO_PREVIOUS_THRESHOLD) { TrackPlayer.skipToPrevious() } else { TrackPlayer.seek(0) @@ -37,13 +45,11 @@ export async function previous(): Promise { * @param index The track index to skip to, to skip multiple tracks */ export async function skip(index: number | undefined): Promise { - if (!isUndefined(index)) { - const { currentIndex } = await TrackPlayer.getState() + const { currentIndex } = usePlayerQueueStore.getState() + if (!isUndefined(index)) { if (index === currentIndex) return - else { - TrackPlayer.skipToIndex(index) - } + TrackPlayer.skipToIndex(index) } else { TrackPlayer.skipToNext() } diff --git a/src/hooks/player/functions/queue.ts b/src/hooks/player/functions/queue.ts index 2ceb32ef..f04c1106 100644 --- a/src/hooks/player/functions/queue.ts +++ b/src/hooks/player/functions/queue.ts @@ -9,6 +9,7 @@ import { isNull } from 'lodash' import { useNetworkStore } from '../../../stores/network' import { DownloadManager, PlayerQueue, TrackItem, TrackPlayer } from 'react-native-nitro-player' import uuid from 'react-native-uuid' +import resolveTrackUrls from '../../../utils/fetching/track-media-info' type LoadQueueResult = { finalStartIndex: number @@ -20,7 +21,6 @@ export async function loadQueue({ tracklist, queue, shuffled = false, - startPlayback, }: QueueMutation): Promise { TrackPlayer.pause() @@ -76,56 +76,31 @@ export async function loadQueue({ * @param item The track to play next */ export const playNextInQueue = async ({ tracks }: AddToQueueMutation) => { + // Add the resolved tracks to the CURRENT playlist (not an orphan one). The + // native updatePlaylist callback will do a soft rebuild that dedups them from + // the remaining queue, so they only appear in the playNext stack. + const currentPlaylistId = PlayerQueue.getCurrentPlaylistId() + if (!currentPlaylistId) return + const tracksToPlayNext = tracks.map((item) => mapDtoToTrack(item)) - const { currentIndex, currentPlaylistId } = await TrackPlayer.getState() + // Resolve URLs before adding to the native queue. If we add unresolved tracks + // (empty URL) and then call playNext, ExoPlayer/AVPlayer immediately errors on + // the empty URI and skips the track. We must have the real URL in the playlist + // before playNext references it by ID. + const resolvedTracks = await resolveTrackUrls(tracksToPlayNext, 'stream') - if (currentPlaylistId) { - const currentQueue = PlayerQueue.getPlaylist(currentPlaylistId!)!.tracks + PlayerQueue.addTracksToPlaylist(currentPlaylistId, resolvedTracks) - PlayerQueue.addTracksToPlaylist(currentPlaylistId, tracksToPlayNext) + await Promise.all(resolvedTracks.map(({ id }) => TrackPlayer.playNext(id))) - await Promise.all(tracksToPlayNext.map(({ id }) => TrackPlayer.playNext(id))) + // Get the active queue, put it in Zustand + const updatedQueue = await TrackPlayer.getActualQueue() + usePlayerQueueStore.getState().setQueue([...updatedQueue]) - // Get the active queue, put it in Zustand - const updatedQueue = await TrackPlayer.getActualQueue() - usePlayerQueueStore.getState().setQueue([...updatedQueue]) - - // Add to the state unshuffled queue, using the currently playing track as the index - // TODO: Check this - usePlayerQueueStore - .getState() - .setUnshuffledQueue([ - ...usePlayerQueueStore - .getState() - .unShuffledQueue.slice( - 0, - usePlayerQueueStore - .getState() - .unShuffledQueue.indexOf(currentQueue[currentIndex!]) + 1, - ), - ...tracksToPlayNext, - ...usePlayerQueueStore - .getState() - .unShuffledQueue.slice( - usePlayerQueueStore - .getState() - .unShuffledQueue.indexOf(currentQueue[currentIndex!]) + 1, - ), - ]) - } - // If there isn't a current playlist, create one with the track to play next and load it - else { - const currentPlaylistId = PlayerQueue.createPlaylist('Playlist') - - PlayerQueue.addTracksToPlaylist(currentPlaylistId, tracksToPlayNext) - PlayerQueue.loadPlaylist(currentPlaylistId) - await TrackPlayer.playNext(tracksToPlayNext[0].id) - - const updatedQueue = await TrackPlayer.getActualQueue() - usePlayerQueueStore.getState().setQueue([...updatedQueue]) - usePlayerQueueStore.getState().setUnshuffledQueue([...tracksToPlayNext]) - } + usePlayerQueueStore + .getState() + .setUnshuffledQueue([...usePlayerQueueStore.getState().unShuffledQueue, ...resolvedTracks]) } export const playLaterInQueue = async ({ tracks }: AddToQueueMutation) => { diff --git a/src/providers/Player/utils/event-handlers.ts b/src/providers/Player/utils/event-handlers.ts index b7bf40b9..be82aabc 100644 --- a/src/providers/Player/utils/event-handlers.ts +++ b/src/providers/Player/utils/event-handlers.ts @@ -4,11 +4,7 @@ import reportPlaybackProgress from '../../../api/mutations/playback/functions/pl import reportPlaybackStarted from '../../../api/mutations/playback/functions/playback-started' import reportPlaybackStopped from '../../../api/mutations/playback/functions/playback-stopped' import isPlaybackFinished from '../../../api/mutations/playback/utils' -import { - usePlayerPlaybackStore, - setPlaybackPosition, - setTotalDuration, -} from '../../../stores/player/playback' +import { usePlayerPlaybackStore } from '../../../stores/player/playback' import { usePlayerQueueStore } from '../../../stores/player/queue' import { usePlayerSettingsStore } from '../../../stores/settings/player' import { useUsageSettingsStore } from '../../../stores/settings/usage' @@ -19,6 +15,7 @@ import { Reason, TrackPlayerState, TrackItem, + PlayerQueue, } from 'react-native-nitro-player' /** @@ -65,6 +62,7 @@ export async function onChangeTrack() { } const { currentIndex, currentTrack } = await TrackPlayer.getState() + const actualQueue = await TrackPlayer.getActualQueue() // Get the last track and the last known position... const previousTrack = usePlayerQueueStore.getState().currentTrack @@ -78,12 +76,17 @@ export async function onChangeTrack() { } // Then we can update the store... - usePlayerQueueStore.getState().setCurrentIndex(currentIndex) - usePlayerQueueStore.getState().setCurrentTrack(currentTrack!) + usePlayerQueueStore.setState((state) => ({ + ...state, + currentIndex, + currentTrack: currentTrack ?? undefined, + queue: actualQueue, + })) // ...report that playback has started for the new track... await reportPlaybackStarted(currentTrack!, 0) + // TODO: Fix audio normalization logic against nitro player const { enableAudioNormalization } = usePlayerSettingsStore.getState() // ...and apply audio normalization if enabled in settings @@ -94,8 +97,10 @@ export async function onChangeTrack() { } export async function onPlaybackProgress(position: number, totalDuration: number) { - setPlaybackPosition(position) - setTotalDuration(totalDuration) + usePlayerPlaybackStore.setState({ + position, + totalDuration, + }) const { currentTrack } = usePlayerQueueStore.getState() @@ -105,16 +110,12 @@ export async function onPlaybackProgress(position: number, totalDuration: number const { autoDownload } = useUsageSettingsStore.getState() - const isDownloadedOrDownloadPending = - (await DownloadManager.isTrackDownloaded(currentTrack?.id ?? '')) || - (await DownloadManager.isDownloading(currentTrack?.id ?? '')) + if (position / totalDuration > 0.3 && currentTrack && autoDownload) { + const isDownloadedOrDownloadPending = + (await DownloadManager.isTrackDownloaded(currentTrack?.id ?? '')) || + (await DownloadManager.isDownloading(currentTrack?.id ?? '')) - if ( - position / totalDuration > 0.3 && - currentTrack && - autoDownload && - !isDownloadedOrDownloadPending - ) { + if (isDownloadedOrDownloadPending) return try { await DownloadManager.downloadTrack(currentTrack) } catch (error) { diff --git a/src/stores/player/queue.ts b/src/stores/player/queue.ts index 2ec33c06..b8477124 100644 --- a/src/stores/player/queue.ts +++ b/src/stores/player/queue.ts @@ -169,11 +169,13 @@ export const setNewQueue = ( index: number, shuffled: boolean, ) => { - usePlayerQueueStore.getState().setCurrentIndex(index) - usePlayerQueueStore.getState().setQueueRef(queueRef) - usePlayerQueueStore.getState().setQueue(queue) - usePlayerQueueStore.getState().setCurrentTrack(queue[index]) - usePlayerQueueStore.getState().setShuffled(shuffled) + usePlayerQueueStore.setState({ + queue, + queueRef, + currentIndex: index, + currentTrack: queue[index], + shuffled, + }) } /**