play next fixes, queue state fixes, styling fixes

This commit is contained in:
Violet Caulfield
2026-03-02 15:28:04 -06:00
parent 4292edf77e
commit 0ef2e91773
6 changed files with 63 additions and 79 deletions

View File

@@ -89,7 +89,7 @@ export default function AlbumTrackListHeader({ album }: { album: BaseItemDto }):
) : null}
</YStack>
<Separator vertical />
<Separator vertical borderColor={'$borderColor'} />
<RunTimeTicks props={{ flex: 1, textAlign: 'left' }}>
{album.RunTimeTicks}

View File

@@ -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) {
>
<JellifySliderThumb transition='bouncy' />
</Switch>
<Separator minHeight={20} vertical />
<Separator minHeight={20} vertical borderColor={'$borderColor'} />
<Label size={props.size} htmlFor={id}>
{props.label}
</Label>

View File

@@ -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<void> {
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<void> {
* @param index The track index to skip to, to skip multiple tracks
*/
export async function skip(index: number | undefined): Promise<void> {
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()
}

View File

@@ -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<LoadQueueResult> {
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) => {

View File

@@ -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) {

View File

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