Made the Player behaviour more consistent

This commit is contained in:
Deluan 2021-07-02 14:07:03 -04:00
parent 26b5e6b1b4
commit ace5c905eb
14 changed files with 374 additions and 377 deletions

View File

@ -19,7 +19,7 @@ import customRoutes from './routes'
import {
themeReducer,
addToPlaylistDialogReducer,
playQueueReducer,
playerReducer,
albumViewReducer,
activityReducer,
settingsReducer,
@ -48,7 +48,7 @@ const App = () => (
dataProvider,
history,
customReducers: {
queue: playQueueReducer,
player: playerReducer,
albumView: albumViewReducer,
theme: themeReducer,
addToPlaylistDialog: addToPlaylistDialogReducer,

View File

@ -1,4 +1,4 @@
export * from './audioplayer'
export * from './player'
export * from './themes'
export * from './albumView'
export * from './dialogs'

View File

@ -3,7 +3,6 @@ export const PLAYER_PLAY_NEXT = 'PLAYER_PLAY_NEXT'
export const PLAYER_SET_TRACK = 'PLAYER_SET_TRACK'
export const PLAYER_SYNC_QUEUE = 'PLAYER_SYNC_QUEUE'
export const PLAYER_CLEAR_QUEUE = 'PLAYER_CLEAR_QUEUE'
export const PLAYER_SCROBBLE = 'PLAYER_SCROBBLE'
export const PLAYER_PLAY_TRACKS = 'PLAYER_PLAY_TRACKS'
export const PLAYER_CURRENT = 'PLAYER_CURRENT'
export const PLAYER_SET_VOLUME = 'PLAYER_SET_VOLUME'
@ -79,12 +78,6 @@ export const clearQueue = () => ({
type: PLAYER_CLEAR_QUEUE,
})
export const scrobble = (id, submit) => ({
type: PLAYER_SCROBBLE,
id,
submit,
})
export const currentPlaying = (audioInfo) => ({
type: PLAYER_CURRENT,
data: audioInfo,

View File

@ -0,0 +1,41 @@
import React from 'react'
import { useMediaQuery } from '@material-ui/core'
import { Link } from 'react-router-dom'
import clsx from 'clsx'
import { QualityInfo } from '../common'
import useStyle from './styles'
const AudioTitle = React.memo(({ audioInfo, isMobile }) => {
const classes = useStyle()
const className = classes.audioTitle
const isDesktop = useMediaQuery('(min-width:810px)')
if (!audioInfo.song) {
return ''
}
const song = audioInfo.song
const qi = { suffix: song.suffix, bitRate: song.bitRate }
return (
<Link to={`/album/${song.albumId}/show`} className={className}>
<span>
<span className={clsx(classes.songTitle, 'songTitle')}>
{song.title}
</span>
{isDesktop && (
<QualityInfo record={qi} className={classes.qualityInfo} />
)}
</span>
{!isMobile && (
<div className={classes.artistAlbum}>
<span className={clsx(classes.songInfo, 'songInfo')}>
{`${song.artist} - ${song.album}`}
</span>
</div>
)}
</Link>
)
})
export default AudioTitle

View File

@ -1,180 +1,50 @@
import React, { useCallback, useMemo, useState } from 'react'
import ReactGA from 'react-ga'
import { useDispatch, useSelector } from 'react-redux'
import { Link } from 'react-router-dom'
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles'
import { useMediaQuery } from '@material-ui/core'
import { useAuthState, useDataProvider, useTranslate } from 'react-admin'
import ReactGA from 'react-ga'
import { GlobalHotKeys } from 'react-hotkeys'
import ReactJkMusicPlayer from 'react-jinke-music-player'
import 'react-jinke-music-player/assets/index.css'
import {
createMuiTheme,
makeStyles,
ThemeProvider,
} from '@material-ui/core/styles'
import { useMediaQuery } from '@material-ui/core'
import { GlobalHotKeys } from 'react-hotkeys'
import clsx from 'clsx'
import subsonic from '../subsonic'
import {
scrobble,
syncQueue,
currentPlaying,
setVolume,
clearQueue,
} from '../actions'
import useCurrentTheme from '../themes/useCurrentTheme'
import config from '../config'
import useStyle from './styles'
import AudioTitle from './AudioTitle'
import { clearQueue, currentPlaying, setVolume, syncQueue } from '../actions'
import PlayerToolbar from './PlayerToolbar'
import { sendNotification } from '../utils'
import subsonic from '../subsonic'
import locale from './locale'
import { keyMap } from '../hotkeys'
import useCurrentTheme from '../themes/useCurrentTheme'
import { QualityInfo } from '../common'
const useStyle = makeStyles(
(theme) => ({
audioTitle: {
textDecoration: 'none',
color: theme.palette.primary.dark,
},
songTitle: {
fontWeight: 'bold',
'&:hover + $qualityInfo': {
opacity: 1,
},
},
songInfo: {
display: 'block',
},
qualityInfo: {
marginTop: '-4px',
opacity: 0,
transition: 'all 500ms ease-out',
},
player: {
display: (props) => (props.visible ? 'block' : 'none'),
'@media screen and (max-width:810px)': {
'& .sound-operation': {
display: 'none',
},
},
'& .progress-bar-content': {
display: 'flex',
flexDirection: 'column',
},
'& .play-mode-title': {
'pointer-events': 'none',
},
'& .music-player-panel .panel-content div.img-rotate': {
'animation-duration': (props) =>
props.enableCoverAnimation ? null : '0s',
},
},
artistAlbum: {
marginTop: '2px',
},
}),
{ name: 'NDAudioPlayer' }
)
let audioInstance = null
const AudioTitle = React.memo(({ audioInfo, isMobile }) => {
const classes = useStyle()
const className = classes.audioTitle
const isDesktop = useMediaQuery('(min-width:810px)')
if (!audioInfo.name) {
return ''
}
const qi = { suffix: audioInfo.suffix, bitRate: audioInfo.bitRate }
return (
<Link to={`/album/${audioInfo.albumId}/show`} className={className}>
<span>
<span className={clsx(classes.songTitle, 'songTitle')}>
{audioInfo.name}
</span>
{isDesktop && (
<QualityInfo record={qi} className={classes.qualityInfo} />
)}
</span>
{!isMobile && (
<div className={classes.artistAlbum}>
<span className={clsx(classes.songInfo, 'songInfo')}>
{`${audioInfo.singer} - ${audioInfo.album}`}
</span>
</div>
)}
</Link>
)
})
import keyHandlers from './keyHandlers'
const Player = () => {
const translate = useTranslate()
const theme = useCurrentTheme()
const playerTheme = (theme.player && theme.player.theme) || 'dark'
const translate = useTranslate()
const playerTheme = theme.player?.theme || 'dark'
const dataProvider = useDataProvider()
const playerState = useSelector((state) => state.player)
const dispatch = useDispatch()
const queue = useSelector((state) => state.queue)
const [startTime, setStartTime] = useState(null)
const [scrobbled, setScrobbled] = useState(false)
const [audioInstance, setAudioInstance] = useState(null)
const isDesktop = useMediaQuery('(min-width:810px)')
const { authenticated } = useAuthState()
const showNotifications = useSelector(
(state) => state.settings.notifications || false
)
const visible = authenticated && queue.queue.length > 0
const visible = authenticated && playerState.queue.length > 0
const classes = useStyle({
visible,
enableCoverAnimation: config.enableCoverAnimation,
})
// Match the medium breakpoint defined in the material-ui theme
// See https://material-ui.com/customization/breakpoints/#breakpoints
const isDesktop = useMediaQuery('(min-width:810px)')
const [startTime, setStartTime] = useState(null)
const nextSong = useCallback(() => {
const idx = queue.queue.findIndex(
(item) => item.uuid === queue.current.uuid
)
return idx !== null ? queue.queue[idx + 1] : null
}, [queue])
const prevSong = useCallback(() => {
const idx = queue.queue.findIndex(
(item) => item.uuid === queue.current.uuid
)
return idx !== null ? queue.queue[idx - 1] : null
}, [queue])
const keyHandlers = {
TOGGLE_PLAY: (e) => {
e.preventDefault()
audioInstance && audioInstance.togglePlay()
},
VOL_UP: () =>
(audioInstance.volume = Math.min(1, audioInstance.volume + 0.1)),
VOL_DOWN: () =>
(audioInstance.volume = Math.max(0, audioInstance.volume - 0.1)),
PREV_SONG: useCallback(
(e) => {
if (!e.metaKey && prevSong()) audioInstance && audioInstance.playPrev()
},
[prevSong]
),
NEXT_SONG: useCallback(
(e) => {
if (!e.metaKey && nextSong()) audioInstance && audioInstance.playNext()
},
[nextSong]
),
}
const showNotifications = useSelector(
(state) => state.settings.notifications || false
)
const defaultOptions = useMemo(
() => ({
theme: playerTheme,
bounds: 'body',
mode: 'full',
autoPlay: false,
preload: true,
autoPlayInitLoadPlayList: true,
loadAudioErrorPlayNext: false,
clearPriorAudioLists: false,
showDestroy: true,
@ -185,6 +55,7 @@ const Player = () => {
showThemeSwitch: false,
showMediaSession: true,
restartCurrentOnPrev: true,
quietUpdate: true,
defaultPosition: {
top: 300,
left: 120,
@ -193,48 +64,22 @@ const Player = () => {
renderAudioTitle: (audioInfo, isMobile) => (
<AudioTitle audioInfo={audioInfo} isMobile={isMobile} />
),
locale: {
playListsText: translate('player.playListsText'),
openText: translate('player.openText'),
closeText: translate('player.closeText'),
notContentText: translate('player.notContentText'),
clickToPlayText: translate('player.clickToPlayText'),
clickToPauseText: translate('player.clickToPauseText'),
nextTrackText: translate('player.nextTrackText'),
previousTrackText: translate('player.previousTrackText'),
reloadText: translate('player.reloadText'),
volumeText: translate('player.volumeText'),
toggleLyricText: translate('player.toggleLyricText'),
toggleMiniModeText: translate('player.toggleMiniModeText'),
destroyText: translate('player.destroyText'),
downloadText: translate('player.downloadText'),
removeAudioListsText: translate('player.removeAudioListsText'),
clickToDeleteText: (name) =>
translate('player.clickToDeleteText', { name }),
emptyLyricText: translate('player.emptyLyricText'),
playModeText: {
order: translate('player.playModeText.order'),
orderLoop: translate('player.playModeText.orderLoop'),
singleLoop: translate('player.playModeText.singleLoop'),
shufflePlay: translate('player.playModeText.shufflePlay'),
},
},
locale: locale(translate),
}),
[isDesktop, playerTheme, translate]
)
const options = useMemo(() => {
const current = queue.current || {}
const current = playerState.current || {}
return {
...defaultOptions,
clearPriorAudioLists: queue.clear,
autoPlay: queue.clear || queue.playIndex === 0,
playIndex: queue.playIndex,
audioLists: queue.queue.map((item) => item),
audioLists: playerState.queue.map((item) => item),
playIndex: playerState.playIndex,
clearPriorAudioLists: playerState.clear,
extendsContent: <PlayerToolbar id={current.trackId} />,
defaultVolume: queue.volume,
defaultVolume: playerState.volume,
}
}, [queue, defaultOptions])
}, [playerState, defaultOptions])
const onAudioListsChange = useCallback(
(currentPlayIndex, audioLists) =>
@ -248,19 +93,17 @@ const Player = () => {
document.title = 'Navidrome'
}
// See https://www.last.fm/api/scrobbling#when-is-a-scrobble-a-scrobble
const progress = (info.currentTime / info.duration) * 100
if (isNaN(info.duration) || (progress < 50 && info.currentTime < 240)) {
return
}
const item = queue.queue.find((item) => item.trackId === info.trackId)
if (item && !item.scrobbled) {
dispatch(scrobble(info.trackId, true))
if (!scrobbled) {
subsonic.scrobble(info.trackId, true, startTime)
setScrobbled(true)
}
},
[dispatch, queue.queue, startTime]
[startTime, scrobbled]
)
const onAudioVolumeChange = useCallback(
@ -274,20 +117,21 @@ const Player = () => {
dispatch(currentPlaying(info))
setStartTime(Date.now())
if (info.duration) {
document.title = `${info.name} - ${info.singer} - Navidrome`
dispatch(scrobble(info.trackId, false))
const song = info.song
document.title = `${song.title} - ${song.artist} - Navidrome`
subsonic.nowPlaying(info.trackId)
setScrobbled(false)
if (config.gaTrackingId) {
ReactGA.event({
category: 'Player',
action: 'Play song',
label: `${info.name} - ${info.singer}`,
label: `${song.title} - ${song.artist}`,
})
}
if (showNotifications) {
sendNotification(
info.name,
`${info.singer} - ${info.album}`,
song.title,
`${song.artist} - ${song.album}`,
info.cover
)
}
@ -312,8 +156,8 @@ const Player = () => {
)
const onCoverClick = useCallback((mode, audioLists, audioInfo) => {
if (mode === 'full') {
window.location.href = `#/album/${audioInfo.albumId}/show`
if (mode === 'full' && audioInfo?.song?.albumId) {
window.location.href = `#/album/${audioInfo.song.albumId}/show`
}
}, [])
@ -328,25 +172,27 @@ const Player = () => {
document.title = 'Navidrome'
}
const handlers = useMemo(
() => keyHandlers(audioInstance, playerState),
[audioInstance, playerState]
)
return (
<ThemeProvider theme={createMuiTheme(theme)}>
<ReactJkMusicPlayer
{...options}
quietUpdate
className={classes.player}
onAudioListsChange={onAudioListsChange}
onAudioVolumeChange={onAudioVolumeChange}
onAudioProgress={onAudioProgress}
onAudioPlay={onAudioPlay}
onAudioPause={onAudioPause}
onAudioEnded={onAudioEnded}
onAudioVolumeChange={onAudioVolumeChange}
onCoverClick={onCoverClick}
onBeforeDestroy={onBeforeDestroy}
getAudioInstance={(instance) => {
audioInstance = instance
}}
getAudioInstance={setAudioInstance}
/>
<GlobalHotKeys handlers={keyHandlers} keyMap={keyMap} allowChanges />
<GlobalHotKeys handlers={handlers} keyMap={keyMap} allowChanges />
</ThemeProvider>
)
}

View File

@ -0,0 +1,35 @@
const keyHandlers = (audioInstance, playerState) => {
const nextSong = () => {
const idx = playerState.queue.findIndex(
(item) => item.uuid === playerState.current.uuid
)
return idx !== null ? playerState.queue[idx + 1] : null
}
const prevSong = () => {
const idx = playerState.queue.findIndex(
(item) => item.uuid === playerState.current.uuid
)
return idx !== null ? playerState.queue[idx - 1] : null
}
return {
TOGGLE_PLAY: (e) => {
e.preventDefault()
audioInstance && audioInstance.togglePlay()
},
VOL_UP: () =>
(audioInstance.volume = Math.min(1, audioInstance.volume + 0.1)),
VOL_DOWN: () =>
(audioInstance.volume = Math.max(0, audioInstance.volume - 0.1)),
PREV_SONG: (e) => {
if (!e.metaKey && prevSong()) audioInstance && audioInstance.playPrev()
},
NEXT_SONG: (e) => {
if (!e.metaKey && nextSong()) audioInstance && audioInstance.playNext()
},
}
}
export default keyHandlers

View File

@ -0,0 +1,27 @@
const locale = (translate) => ({
playListsText: translate('player.playListsText'),
openText: translate('player.openText'),
closeText: translate('player.closeText'),
notContentText: translate('player.notContentText'),
clickToPlayText: translate('player.clickToPlayText'),
clickToPauseText: translate('player.clickToPauseText'),
nextTrackText: translate('player.nextTrackText'),
previousTrackText: translate('player.previousTrackText'),
reloadText: translate('player.reloadText'),
volumeText: translate('player.volumeText'),
toggleLyricText: translate('player.toggleLyricText'),
toggleMiniModeText: translate('player.toggleMiniModeText'),
destroyText: translate('player.destroyText'),
downloadText: translate('player.downloadText'),
removeAudioListsText: translate('player.removeAudioListsText'),
clickToDeleteText: (name) => translate('player.clickToDeleteText', { name }),
emptyLyricText: translate('player.emptyLyricText'),
playModeText: {
order: translate('player.playModeText.order'),
orderLoop: translate('player.playModeText.orderLoop'),
singleLoop: translate('player.playModeText.singleLoop'),
shufflePlay: translate('player.playModeText.shufflePlay'),
},
})
export default locale

View File

@ -0,0 +1,49 @@
import { makeStyles } from '@material-ui/core/styles'
const useStyle = makeStyles(
(theme) => ({
audioTitle: {
textDecoration: 'none',
color: theme.palette.primary.dark,
},
songTitle: {
fontWeight: 'bold',
'&:hover + $qualityInfo': {
opacity: 1,
},
},
songInfo: {
display: 'block',
},
qualityInfo: {
marginTop: '-4px',
opacity: 0,
transition: 'all 500ms ease-out',
},
player: {
display: (props) => (props.visible ? 'block' : 'none'),
'@media screen and (max-width:810px)': {
'& .sound-operation': {
display: 'none',
},
},
'& .progress-bar-content': {
display: 'flex',
flexDirection: 'column',
},
'& .play-mode-title': {
'pointer-events': 'none',
},
'& .music-player-panel .panel-content div.img-rotate': {
'animation-duration': (props) =>
props.enableCoverAnimation ? null : '0s',
},
},
artistAlbum: {
marginTop: '2px',
},
}),
{ name: 'NDAudioPlayer' }
)
export default useStyle

View File

@ -27,7 +27,7 @@ export const SongTitleField = ({ showTrackNumbers, ...props }) => {
const theme = useTheme()
const classes = useStyles()
const { record } = props
const currentTrack = useSelector((state) => state?.queue?.current || {})
const currentTrack = useSelector((state) => state?.player?.current || {})
const currentId = currentTrack.trackId
const paused = currentTrack.paused
const isCurrent =

View File

@ -14,8 +14,8 @@ const useStyles = makeStyles({
const Layout = (props) => {
const theme = useCurrentTheme()
const queue = useSelector((state) => state.queue)
const classes = useStyles({ addPadding: queue.queue.length > 0 })
const queue = useSelector((state) => state.player?.queue)
const classes = useStyles({ addPadding: queue.length > 0 })
const dispatch = useDispatch()
const keyHandlers = {

View File

@ -1,6 +1,6 @@
export * from './themeReducer'
export * from './dialogReducer'
export * from './playQueue'
export * from './playerReducer'
export * from './albumView'
export * from './activityReducer'
export * from './settingsReducer'

View File

@ -1,159 +0,0 @@
import { v4 as uuidv4 } from 'uuid'
import subsonic from '../subsonic'
import config from '../config'
import {
PLAYER_CLEAR_QUEUE,
PLAYER_SET_VOLUME,
PLAYER_CURRENT,
PLAYER_ADD_TRACKS,
PLAYER_PLAY_NEXT,
PLAYER_SET_TRACK,
PLAYER_SYNC_QUEUE,
PLAYER_SCROBBLE,
PLAYER_PLAY_TRACKS,
} from '../actions'
const mapToAudioLists = (item) => {
// If item comes from a playlist, id is mediaFileId
const id = item.mediaFileId || item.id
return {
trackId: id,
name: item.title,
singer: item.artist,
album: item.album,
albumId: item.albumId,
artistId: item.albumArtistId,
duration: item.duration,
suffix: item.suffix,
bitRate: item.bitRate,
musicSrc: subsonic.streamUrl(id),
cover: subsonic.getCoverArtUrl(
{
coverArtId: config.devFastAccessCoverArt ? item.albumId : id,
updatedAt: item.updatedAt,
},
300
),
scrobbled: false,
uuid: uuidv4(),
}
}
const initialState = {
queue: [],
clear: true,
current: {},
volume: 1,
playIndex: 0,
}
export const playQueueReducer = (previousState = initialState, payload) => {
let queue, current
let newQueue
const { type, data } = payload
switch (type) {
case PLAYER_CLEAR_QUEUE:
return initialState
case PLAYER_SET_VOLUME:
return {
...previousState,
playIndex: undefined,
volume: data.volume,
}
case PLAYER_CURRENT:
queue = previousState.queue
current = data.ended
? {}
: {
trackId: data.trackId,
uuid: data.uuid,
paused: data.paused,
}
return {
...previousState,
current,
playIndex: undefined,
volume: data.volume,
}
case PLAYER_ADD_TRACKS:
queue = previousState.queue
Object.keys(data).forEach((id) => {
queue.push(mapToAudioLists(data[id]))
})
return { ...previousState, queue, clear: false, playIndex: undefined }
case PLAYER_PLAY_NEXT:
current = previousState.current?.uuid || ''
newQueue = []
let foundPos = false
previousState.queue.forEach((item) => {
newQueue.push(item)
if (item.uuid === current) {
foundPos = true
Object.keys(data).forEach((id) => {
newQueue.push(mapToAudioLists(data[id]))
})
}
})
if (!foundPos) {
Object.keys(data).forEach((id) => {
newQueue.push(mapToAudioLists(data[id]))
})
}
return {
...previousState,
queue: newQueue,
clear: true,
playIndex: undefined,
}
case PLAYER_SET_TRACK:
return {
...previousState,
queue: [mapToAudioLists(data)],
clear: true,
playIndex: 0,
}
case PLAYER_SYNC_QUEUE:
current = data.length > 0 ? previousState.current : {}
return {
...previousState,
queue: data,
clear: false,
playIndex: undefined,
current,
}
case PLAYER_SCROBBLE:
newQueue = previousState.queue.map((item) => {
return {
...item,
scrobbled:
item.scrobbled || (item.trackId === payload.id && payload.submit),
}
})
return {
...previousState,
queue: newQueue,
playIndex: undefined,
clear: false,
}
case PLAYER_PLAY_TRACKS:
queue = []
let match = false
Object.keys(data).forEach((id) => {
if (id === payload.id) {
match = true
}
if (match) {
queue.push(mapToAudioLists(data[id]))
}
})
return {
...previousState,
queue,
playIndex: 0,
clear: true,
}
default:
return previousState
}
}

View File

@ -0,0 +1,165 @@
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,
} from '../actions'
import config from '../config'
const initialState = {
queue: [],
current: {},
clear: false,
volume: 1,
}
const mapToAudioLists = (item) => {
// If item comes from a playlist, trackId is mediaFileId
const trackId = item.mediaFileId || item.id
return {
trackId,
uuid: uuidv4(),
song: item,
name: item.title,
singer: item.artist,
duration: item.duration,
musicSrc: subsonic.streamUrl(trackId),
cover: subsonic.getCoverArtUrl(
{
coverArtId: config.devFastAccessCoverArt ? item.albumId : trackId,
updatedAt: item.updatedAt,
},
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 reduceSyncQueue = (state, { data }) => {
const current = data.length > 0 ? state.current : {}
return {
...state,
current,
queue: data,
}
}
const reduceSetTrack = (state, { data }) => {
return {
...state,
queue: [mapToAudioLists(0, 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
state.queue.forEach((item) => {
newQueue.push(item)
if (item.uuid === current.uuid) {
foundPos = true
Object.keys(data).forEach((id) => {
newQueue.push(mapToAudioLists(data[id]))
})
}
})
if (!foundPos) {
Object.keys(data).forEach((id) => {
newQueue.push(mapToAudioLists(data[id]))
})
}
const playIndex = state.queue.findIndex((item) => item.uuid === current.uuid)
return {
...state,
queue: newQueue,
// TODO: This is a workaround for a bug in the player that resets the playIndex to 0 when the current playing
// song is not the first one. It is still not great, as it resets the current playing song
playIndex,
clear: true,
}
}
const reduceSetVolume = (state, { data: { volume } }) => {
return {
...state,
volume,
}
}
const reduceCurrent = (state, { data }) => {
const current = data.ended
? {}
: {
idx: data.idx,
trackId: data.trackId,
paused: data.paused,
uuid: data.uuid,
song: data.song,
}
const playIndex = state.queue.findIndex((item) => item.uuid === current.uuid)
return {
...state,
current,
playIndex: playIndex > -1 ? playIndex : undefined,
volume: data.volume,
}
}
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_SYNC_QUEUE:
return reduceSyncQueue(previousState, payload)
case PLAYER_SET_VOLUME:
return reduceSetVolume(previousState, payload)
case PLAYER_CURRENT:
return reduceCurrent(previousState, payload)
default:
return previousState
}
}

View File

@ -48,7 +48,7 @@ const createAdminStore = ({
const state = store.getState()
saveState({
theme: state.theme,
queue: pick(state.queue, ['queue', 'volume']),
player: pick(state.player, ['queue', 'volume', 'playIndex']),
albumView: state.albumView,
settings: state.settings,
})