mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-23 23:20:57 +03:00
Add "Play Next" action (finally)
This commit is contained in:
parent
aa133e6b00
commit
7305e3aa17
@ -22,8 +22,9 @@
|
||||
},
|
||||
"actions": {
|
||||
"addToQueue": "Adicionar à fila",
|
||||
"playNow": "Tocar agora",
|
||||
"addToPlaylist": "Adicionar à playlist",
|
||||
"playNow": "Tocar agora",
|
||||
"playNext": "Toca a seguir",
|
||||
"shuffleAll": "Aleatório",
|
||||
"download": "Baixar"
|
||||
}
|
||||
|
27
ui/package-lock.json
generated
27
ui/package-lock.json
generated
@ -14031,6 +14031,13 @@
|
||||
"tough-cookie": "~2.5.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"request-promise-core": {
|
||||
@ -14842,6 +14849,13 @@
|
||||
"faye-websocket": "^0.10.0",
|
||||
"uuid": "^3.4.0",
|
||||
"websocket-driver": "0.6.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"sockjs-client": {
|
||||
@ -16135,9 +16149,9 @@
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz",
|
||||
"integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ=="
|
||||
},
|
||||
"v8-compile-cache": {
|
||||
"version": "2.1.1",
|
||||
@ -16685,6 +16699,13 @@
|
||||
"requires": {
|
||||
"ansi-colors": "^3.0.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"webpack-manifest-plugin": {
|
||||
|
@ -17,11 +17,13 @@
|
||||
"react-dom": "^16.13.1",
|
||||
"react-drag-listview": "^0.1.7",
|
||||
"react-ga": "^3.1.2",
|
||||
"react-icons": "^3.11.0",
|
||||
"react-image-lightbox": "^5.1.1",
|
||||
"react-jinke-music-player": "^4.18.2",
|
||||
"react-measure": "^2.5.2",
|
||||
"react-redux": "^7.2.1",
|
||||
"react-scripts": "^3.4.3"
|
||||
"react-scripts": "^3.4.3",
|
||||
"uuid": "^8.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
|
@ -10,8 +10,8 @@ import {
|
||||
import PlayArrowIcon from '@material-ui/icons/PlayArrow'
|
||||
import ShuffleIcon from '@material-ui/icons/Shuffle'
|
||||
import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined'
|
||||
import AddToQueueIcon from '@material-ui/icons/AddToQueue'
|
||||
import { addTracks, playTracks, shuffleTracks } from '../audioplayer'
|
||||
import { RiPlayListAddFill, RiPlayList2Fill } from 'react-icons/ri'
|
||||
import { playNext, addTracks, playTracks, shuffleTracks } from '../audioplayer'
|
||||
import subsonic from '../subsonic'
|
||||
|
||||
const AlbumActions = ({ className, ids, data, record, ...rest }) => {
|
||||
@ -22,6 +22,10 @@ const AlbumActions = ({ className, ids, data, record, ...rest }) => {
|
||||
dispatch(playTracks(data, ids))
|
||||
}, [dispatch, data, ids])
|
||||
|
||||
const handlePlayNext = React.useCallback(() => {
|
||||
dispatch(playNext(data, ids))
|
||||
}, [dispatch, data, ids])
|
||||
|
||||
const handlePlayLater = React.useCallback(() => {
|
||||
dispatch(addTracks(data, ids))
|
||||
}, [dispatch, data, ids])
|
||||
@ -48,11 +52,17 @@ const AlbumActions = ({ className, ids, data, record, ...rest }) => {
|
||||
>
|
||||
<ShuffleIcon />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handlePlayNext}
|
||||
label={translate('resources.album.actions.playNext')}
|
||||
>
|
||||
<RiPlayList2Fill />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handlePlayLater}
|
||||
label={translate('resources.album.actions.addToQueue')}
|
||||
>
|
||||
<AddToQueueIcon />
|
||||
<RiPlayListAddFill />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleDownload}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Fragment, useEffect } from 'react'
|
||||
import { useUnselectAll } from 'react-admin'
|
||||
import { playNext } from '../audioplayer'
|
||||
import AddToQueueButton from '../song/AddToQueueButton'
|
||||
import AddToPlaylistButton from '../song/AddToPlaylistButton'
|
||||
|
||||
@ -11,6 +12,11 @@ export const AlbumSongBulkActions = (props) => {
|
||||
}, [])
|
||||
return (
|
||||
<Fragment>
|
||||
<AddToQueueButton
|
||||
{...props}
|
||||
action={playNext}
|
||||
label={'resources.song.actions.playNext'}
|
||||
/>
|
||||
<AddToQueueButton {...props} />
|
||||
<AddToPlaylistButton {...props} />
|
||||
</Fragment>
|
||||
|
@ -167,6 +167,7 @@ const Player = () => {
|
||||
return (
|
||||
<ReactJkMusicPlayer
|
||||
{...options}
|
||||
quietUpdate
|
||||
onAudioListsChange={OnAudioListsChange}
|
||||
onAudioProgress={OnAudioProgress}
|
||||
onAudioPlay={OnAudioPlay}
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
setTrack,
|
||||
playQueueReducer,
|
||||
playTracks,
|
||||
playNext,
|
||||
shuffleTracks,
|
||||
clearQueue,
|
||||
} from './queue'
|
||||
@ -13,6 +14,7 @@ export {
|
||||
addTracks,
|
||||
setTrack,
|
||||
playTracks,
|
||||
playNext,
|
||||
playQueueReducer,
|
||||
shuffleTracks,
|
||||
clearQueue,
|
||||
|
@ -1,7 +1,10 @@
|
||||
import 'react-jinke-music-player/assets/index.css'
|
||||
import get from 'lodash.get'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import subsonic from '../subsonic'
|
||||
|
||||
const PLAYER_ADD_TRACKS = 'PLAYER_ADD_TRACKS'
|
||||
const PLAYER_PLAY_NEXT = 'PLAYER_PLAY_NEXT'
|
||||
const PLAYER_SET_TRACK = 'PLAYER_SET_TRACK'
|
||||
const PLAYER_SYNC_QUEUE = 'PLAYER_SYNC_QUEUE'
|
||||
const PLAYER_CLEAR_QUEUE = 'PLAYER_CLEAR_QUEUE'
|
||||
@ -23,6 +26,7 @@ const mapToAudioLists = (item) => {
|
||||
cover: subsonic.url('getCoverArt', id, { size: 300 }),
|
||||
musicSrc: subsonic.url('stream', id, { ts: true }),
|
||||
scrobbled: false,
|
||||
uuid: uuidv4(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,6 +50,14 @@ const addTracks = (data, ids) => {
|
||||
}
|
||||
}
|
||||
|
||||
const playNext = (data, ids) => {
|
||||
const songs = filterSongs(data, ids)
|
||||
return {
|
||||
type: PLAYER_PLAY_NEXT,
|
||||
data: songs,
|
||||
}
|
||||
}
|
||||
|
||||
const shuffle = (data) => {
|
||||
const ids = Object.keys(data)
|
||||
for (let i = ids.length - 1; i > 0; i--) {
|
||||
@ -109,6 +121,7 @@ const initialState = { queue: [], clear: true, current: {}, volume: 1 }
|
||||
|
||||
const playQueueReducer = (previousState = initialState, payload) => {
|
||||
let queue, current
|
||||
let newQueue
|
||||
const { type, data } = payload
|
||||
switch (type) {
|
||||
case PLAYER_CLEAR_QUEUE:
|
||||
@ -124,6 +137,7 @@ const playQueueReducer = (previousState = initialState, payload) => {
|
||||
? {}
|
||||
: {
|
||||
trackId: data.trackId,
|
||||
uuid: data.uuid,
|
||||
paused: data.paused,
|
||||
}
|
||||
return {
|
||||
@ -137,6 +151,25 @@ const playQueueReducer = (previousState = initialState, payload) => {
|
||||
queue.push(mapToAudioLists(data[id]))
|
||||
})
|
||||
return { ...previousState, queue, clear: false }
|
||||
case PLAYER_PLAY_NEXT:
|
||||
current = get(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 }
|
||||
case PLAYER_SET_TRACK:
|
||||
return {
|
||||
...previousState,
|
||||
@ -152,7 +185,7 @@ const playQueueReducer = (previousState = initialState, payload) => {
|
||||
current,
|
||||
}
|
||||
case PLAYER_SCROBBLE:
|
||||
const newQueue = previousState.queue.map((item) => {
|
||||
newQueue = previousState.queue.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
scrobbled:
|
||||
@ -189,6 +222,7 @@ export {
|
||||
addTracks,
|
||||
setTrack,
|
||||
playTracks,
|
||||
playNext,
|
||||
syncQueue,
|
||||
clearQueue,
|
||||
scrobble,
|
||||
|
@ -7,7 +7,7 @@ import MenuItem from '@material-ui/core/MenuItem'
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import { useDataProvider, useNotify, useTranslate } from 'react-admin'
|
||||
import { addTracks, playTracks, shuffleTracks } from '../audioplayer'
|
||||
import { playNext, addTracks, playTracks, shuffleTracks } from '../audioplayer'
|
||||
import { openAddToPlaylist } from '../dialogs/dialogState'
|
||||
import subsonic from '../subsonic'
|
||||
import StarButton from './StarButton'
|
||||
@ -43,6 +43,11 @@ const ContextMenu = ({
|
||||
label: 'resources.album.actions.playAll',
|
||||
action: (data, ids) => dispatch(playTracks(data, ids)),
|
||||
},
|
||||
playNext: {
|
||||
needData: true,
|
||||
label: 'resources.album.actions.playNext',
|
||||
action: (data, ids) => dispatch(playNext(data, ids)),
|
||||
},
|
||||
addToQueue: {
|
||||
needData: true,
|
||||
label: 'resources.album.actions.addToQueue',
|
||||
|
@ -5,7 +5,7 @@ import { useTranslate } from 'react-admin'
|
||||
import { IconButton, Menu, MenuItem } from '@material-ui/core'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert'
|
||||
import { addTracks, setTrack } from '../audioplayer'
|
||||
import { playNext, addTracks, setTrack } from '../audioplayer'
|
||||
import { openAddToPlaylist } from '../dialogs/dialogState'
|
||||
import subsonic from '../subsonic'
|
||||
import StarButton from './StarButton'
|
||||
@ -35,6 +35,10 @@ const SongContextMenu = ({
|
||||
label: 'resources.song.actions.playNow',
|
||||
action: (record) => dispatch(setTrack(record)),
|
||||
},
|
||||
playNext: {
|
||||
label: 'resources.song.actions.playNext',
|
||||
action: (record) => dispatch(playNext({ [record.id]: record })),
|
||||
},
|
||||
addToQueue: {
|
||||
label: 'resources.song.actions.addToQueue',
|
||||
action: (record) => dispatch(addTracks({ [record.id]: record })),
|
||||
|
@ -25,6 +25,7 @@
|
||||
"addToQueue": "Play Later",
|
||||
"addToPlaylist": "Add to Playlist",
|
||||
"playNow": "Play Now",
|
||||
"playNext": "Play Next",
|
||||
"shuffleAll": "Shuffle All",
|
||||
"download": "Download"
|
||||
}
|
||||
|
@ -9,10 +9,10 @@ import {
|
||||
import PlayArrowIcon from '@material-ui/icons/PlayArrow'
|
||||
import ShuffleIcon from '@material-ui/icons/Shuffle'
|
||||
import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined'
|
||||
import AddToQueueIcon from '@material-ui/icons/AddToQueue'
|
||||
import { RiPlayListAddFill, RiPlayList2Fill } from 'react-icons/ri'
|
||||
import QueueMusicIcon from '@material-ui/icons/QueueMusic'
|
||||
import { httpClient } from '../dataProvider'
|
||||
import { addTracks, playTracks, shuffleTracks } from '../audioplayer'
|
||||
import { playNext, addTracks, playTracks, shuffleTracks } from '../audioplayer'
|
||||
import { M3U_MIME_TYPE, REST_URL } from '../consts'
|
||||
import subsonic from '../subsonic'
|
||||
import PropTypes from 'prop-types'
|
||||
@ -25,6 +25,10 @@ const PlaylistActions = ({ className, ids, data, record, ...rest }) => {
|
||||
dispatch(playTracks(data, ids))
|
||||
}, [dispatch, data, ids])
|
||||
|
||||
const handlePlayNext = React.useCallback(() => {
|
||||
dispatch(playNext(data, ids))
|
||||
}, [dispatch, data, ids])
|
||||
|
||||
const handlePlayLater = React.useCallback(() => {
|
||||
dispatch(addTracks(data, ids))
|
||||
}, [dispatch, data, ids])
|
||||
@ -69,11 +73,17 @@ const PlaylistActions = ({ className, ids, data, record, ...rest }) => {
|
||||
>
|
||||
<ShuffleIcon />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handlePlayNext}
|
||||
label={translate('resources.album.actions.playNext')}
|
||||
>
|
||||
<RiPlayList2Fill />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handlePlayLater}
|
||||
label={translate('resources.album.actions.addToQueue')}
|
||||
>
|
||||
<AddToQueueIcon />
|
||||
<RiPlayListAddFill />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleDownload}
|
||||
|
@ -8,9 +8,9 @@ import {
|
||||
} from 'react-admin'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { addTracks } from '../audioplayer'
|
||||
import AddToQueueIcon from '@material-ui/icons/AddToQueue'
|
||||
import { RiPlayListAddFill } from 'react-icons/ri'
|
||||
|
||||
const AddToQueueButton = ({ resource, selectedIds }) => {
|
||||
const AddToQueueButton = ({ resource, selectedIds, action, label, icon }) => {
|
||||
const dispatch = useDispatch()
|
||||
const translate = useTranslate()
|
||||
const dataProvider = useDataProvider()
|
||||
@ -27,7 +27,7 @@ const AddToQueueButton = ({ resource, selectedIds }) => {
|
||||
{}
|
||||
)
|
||||
// Add the tracks to the queue in the selection order
|
||||
dispatch(addTracks(tracks, selectedIds))
|
||||
dispatch(action(tracks, selectedIds))
|
||||
})
|
||||
.catch(() => {
|
||||
notify('ra.page.error', 'warning')
|
||||
@ -36,14 +36,16 @@ const AddToQueueButton = ({ resource, selectedIds }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
color="secondary"
|
||||
onClick={addToQueue}
|
||||
label={translate('resources.song.actions.addToQueue')}
|
||||
>
|
||||
<AddToQueueIcon />
|
||||
<Button color="secondary" onClick={addToQueue} label={translate(label)}>
|
||||
{icon}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
AddToQueueButton.defaultProps = {
|
||||
action: addTracks,
|
||||
label: 'resources.song.actions.addToQueue',
|
||||
icon: <RiPlayListAddFill />,
|
||||
}
|
||||
|
||||
export default AddToQueueButton
|
||||
|
@ -1,10 +1,18 @@
|
||||
import React, { Fragment } from 'react'
|
||||
import AddToQueueButton from './AddToQueueButton'
|
||||
import AddToPlaylistButton from './AddToPlaylistButton'
|
||||
import { RiPlayList2Fill } from 'react-icons/ri'
|
||||
import { playNext } from '../audioplayer'
|
||||
|
||||
export const SongBulkActions = (props) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<AddToQueueButton
|
||||
{...props}
|
||||
action={playNext}
|
||||
label={'resources.song.actions.playNext'}
|
||||
icon={<RiPlayList2Fill />}
|
||||
/>
|
||||
<AddToQueueButton {...props} />
|
||||
<AddToPlaylistButton {...props} />
|
||||
</Fragment>
|
||||
|
Loading…
x
Reference in New Issue
Block a user