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,
+ })
}
/**