Set playIndex when rebuilding the queue in reducePlayNext so the music player library knows which track is currently playing. Without this, the library's loadNewAudioLists defaults playIndex to 0, causing playback to restart from the top of the queue on rapid "Play Next" actions. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
216 lines
4.7 KiB
JavaScript
216 lines
4.7 KiB
JavaScript
import { v4 as uuidv4 } from 'uuid'
|
|
import subsonic from '../subsonic'
|
|
import {
|
|
PLAYER_ADD_TRACKS,
|
|
PLAYER_CLEAR_QUEUE,
|
|
PLAYER_CURRENT,
|
|
PLAYER_PLAY_NEXT,
|
|
PLAYER_PLAY_TRACKS,
|
|
PLAYER_SET_TRACK,
|
|
PLAYER_SET_VOLUME,
|
|
PLAYER_SYNC_QUEUE,
|
|
PLAYER_SET_MODE,
|
|
} from '../actions'
|
|
import config from '../config'
|
|
|
|
const initialState = {
|
|
queue: [],
|
|
current: {},
|
|
clear: false,
|
|
volume: config.defaultUIVolume / 100,
|
|
savedPlayIndex: 0,
|
|
}
|
|
|
|
const pad = (value) => {
|
|
const str = value.toString()
|
|
if (str.length === 1) {
|
|
return `0${str}`
|
|
} else {
|
|
return str
|
|
}
|
|
}
|
|
|
|
const mapToAudioLists = (item) => {
|
|
// If item comes from a playlist, trackId is mediaFileId
|
|
const trackId = item.mediaFileId || item.id
|
|
|
|
if (item.isRadio) {
|
|
return {
|
|
trackId,
|
|
uuid: uuidv4(),
|
|
name: item.name,
|
|
song: item,
|
|
musicSrc: item.streamUrl,
|
|
cover: item.cover,
|
|
isRadio: true,
|
|
}
|
|
}
|
|
|
|
const { lyrics } = item
|
|
let lyricText = ''
|
|
|
|
if (lyrics) {
|
|
const structured = JSON.parse(lyrics)
|
|
for (const structuredLyric of structured) {
|
|
if (structuredLyric.synced) {
|
|
for (const line of structuredLyric.line) {
|
|
let time = Math.floor(line.start / 10)
|
|
const ms = time % 100
|
|
time = Math.floor(time / 100)
|
|
const sec = time % 60
|
|
time = Math.floor(time / 60)
|
|
const min = time % 60
|
|
|
|
ms.toString()
|
|
lyricText += `[${pad(min)}:${pad(sec)}.${pad(ms)}] ${line.value}\n`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
trackId,
|
|
uuid: uuidv4(),
|
|
song: item,
|
|
name: item.title,
|
|
lyric: lyricText,
|
|
singer: item.artist,
|
|
duration: item.duration,
|
|
musicSrc: subsonic.streamUrl(trackId),
|
|
cover: subsonic.getCoverArtUrl(
|
|
{
|
|
id: trackId,
|
|
updatedAt: item.updatedAt,
|
|
album: item.album,
|
|
},
|
|
300,
|
|
),
|
|
}
|
|
}
|
|
|
|
const reduceClearQueue = () => ({ ...initialState, clear: true })
|
|
|
|
const reducePlayTracks = (state, { data, id }) => {
|
|
let playIndex = 0
|
|
const queue = Object.keys(data).map((key, idx) => {
|
|
if (key === id) {
|
|
playIndex = idx
|
|
}
|
|
return mapToAudioLists(data[key])
|
|
})
|
|
return {
|
|
...state,
|
|
queue,
|
|
playIndex,
|
|
clear: true,
|
|
}
|
|
}
|
|
|
|
const reduceSetTrack = (state, { data }) => {
|
|
return {
|
|
...state,
|
|
queue: [mapToAudioLists(data)],
|
|
playIndex: 0,
|
|
clear: true,
|
|
}
|
|
}
|
|
|
|
const reduceAddTracks = (state, { data }) => {
|
|
const queue = state.queue
|
|
Object.keys(data).forEach((id) => {
|
|
queue.push(mapToAudioLists(data[id]))
|
|
})
|
|
return { ...state, queue, clear: false }
|
|
}
|
|
|
|
const reducePlayNext = (state, { data }) => {
|
|
const newQueue = []
|
|
const current = state.current || {}
|
|
let foundPos = false
|
|
let currentIndex = 0
|
|
state.queue.forEach((item) => {
|
|
newQueue.push(item)
|
|
if (item.uuid === current.uuid) {
|
|
foundPos = true
|
|
currentIndex = newQueue.length - 1
|
|
Object.keys(data).forEach((id) => {
|
|
newQueue.push(mapToAudioLists(data[id]))
|
|
})
|
|
}
|
|
})
|
|
if (!foundPos) {
|
|
Object.keys(data).forEach((id) => {
|
|
newQueue.push(mapToAudioLists(data[id]))
|
|
})
|
|
}
|
|
|
|
return {
|
|
...state,
|
|
queue: newQueue,
|
|
playIndex: foundPos ? currentIndex : undefined,
|
|
clear: true,
|
|
}
|
|
}
|
|
|
|
const reduceSetVolume = (state, { data: { volume } }) => {
|
|
return {
|
|
...state,
|
|
volume,
|
|
}
|
|
}
|
|
|
|
const reduceSyncQueue = (state, { data: { audioInfo, audioLists } }) => {
|
|
return {
|
|
...state,
|
|
queue: audioLists,
|
|
clear: false,
|
|
playIndex: undefined,
|
|
}
|
|
}
|
|
|
|
const reduceCurrent = (state, { data }) => {
|
|
const current = data.ended ? {} : data
|
|
const savedPlayIndex = state.queue.findIndex(
|
|
(item) => item.uuid === current.uuid,
|
|
)
|
|
return {
|
|
...state,
|
|
current,
|
|
playIndex: undefined,
|
|
savedPlayIndex,
|
|
volume: data.volume,
|
|
}
|
|
}
|
|
|
|
const reduceMode = (state, { data: { mode } }) => {
|
|
return {
|
|
...state,
|
|
mode,
|
|
}
|
|
}
|
|
|
|
export const playerReducer = (previousState = initialState, payload) => {
|
|
const { type } = payload
|
|
switch (type) {
|
|
case PLAYER_CLEAR_QUEUE:
|
|
return reduceClearQueue()
|
|
case PLAYER_PLAY_TRACKS:
|
|
return reducePlayTracks(previousState, payload)
|
|
case PLAYER_SET_TRACK:
|
|
return reduceSetTrack(previousState, payload)
|
|
case PLAYER_ADD_TRACKS:
|
|
return reduceAddTracks(previousState, payload)
|
|
case PLAYER_PLAY_NEXT:
|
|
return reducePlayNext(previousState, payload)
|
|
case PLAYER_SET_VOLUME:
|
|
return reduceSetVolume(previousState, payload)
|
|
case PLAYER_SYNC_QUEUE:
|
|
return reduceSyncQueue(previousState, payload)
|
|
case PLAYER_CURRENT:
|
|
return reduceCurrent(previousState, payload)
|
|
case PLAYER_SET_MODE:
|
|
return reduceMode(previousState, payload)
|
|
default:
|
|
return previousState
|
|
}
|
|
}
|