diff --git a/ui/src/album/AlbumSongs.js b/ui/src/album/AlbumSongs.js index 1ff5418ab..7b64a4fe1 100644 --- a/ui/src/album/AlbumSongs.js +++ b/ui/src/album/AlbumSongs.js @@ -2,7 +2,6 @@ import React from 'react' import { BulkActionsToolbar, DatagridLoading, - FunctionField, ListToolbar, TextField, useListController, @@ -15,9 +14,10 @@ import StarBorderIcon from '@material-ui/icons/StarBorder' import { playTracks } from '../audioplayer' import { DurationField, - SongDetails, - SongDatagrid, SongContextMenu, + SongDatagrid, + SongDetails, + SongTitleField, } from '../common' import AddToPlaylistDialog from '../dialogs/AddToPlaylistDialog' @@ -62,14 +62,6 @@ const useStylesListToolbar = makeStyles({ }, }) -const trackName = (r) => { - const name = r.title - if (r.trackNumber) { - return r.trackNumber.toString().padStart(2, '0') + ' ' + name - } - return name -} - const AlbumSongs = (props) => { const classes = useStyles(props) const classesToolbar = useStylesListToolbar(props) @@ -132,14 +124,11 @@ const AlbumSongs = (props) => { sortable={false} /> )} - {isDesktop && } - {!isDesktop && ( - - )} + {isDesktop && } { } const OnAudioProgress = (info) => { - const progress = (info.currentTime / info.duration) * 100 - if (isNaN(info.duration) || progress < 90) { + if (info.ended) { + document.title = 'Navidrome' + } + dispatch(progress(info)) + const pos = (info.currentTime / info.duration) * 100 + if (isNaN(info.duration) || pos < 90) { return } const item = queue.queue.find((item) => item.trackId === info.trackId) @@ -120,10 +124,6 @@ const Player = () => { } } - const onAudioEnded = () => { - document.title = 'Navidrome' - } - if (authenticated && options.audioLists.length > 0) { return ( { onAudioListsChange={OnAudioListsChange} onAudioProgress={OnAudioProgress} onAudioPlay={OnAudioPlay} - onAudioEnded={onAudioEnded} /> ) } diff --git a/ui/src/audioplayer/queue.js b/ui/src/audioplayer/queue.js index e5f3d74dd..27f245b5c 100644 --- a/ui/src/audioplayer/queue.js +++ b/ui/src/audioplayer/queue.js @@ -6,6 +6,7 @@ const PLAYER_SET_TRACK = 'PLAYER_SET_TRACK' const PLAYER_SYNC_QUEUE = 'PLAYER_SYNC_QUEUE' const PLAYER_SCROBBLE = 'PLAYER_SCROBBLE' const PLAYER_PLAY_TRACKS = 'PLAYER_PLAY_TRACKS' +const PLAYER_PROGRESS = 'PLAYER_PROGRESS' const mapToAudioLists = (item) => { // If item comes from a playlist, id is mediaFileId @@ -88,13 +89,30 @@ const scrobble = (id, submit) => ({ submit, }) +const progress = (audioInfo) => ({ + type: PLAYER_PROGRESS, + data: audioInfo, +}) + const playQueueReducer = ( - previousState = { queue: [], clear: true, playing: false }, + previousState = { queue: [], clear: true, playing: false, current: {} }, payload ) => { - let queue + let queue, current const { type, data } = payload switch (type) { + case PLAYER_PROGRESS: + queue = previousState.queue + current = data.ended + ? {} + : { + trackId: data.trackId, + paused: data.paused, + } + return { + ...previousState, + current, + } case PLAYER_ADD_TRACKS: queue = previousState.queue Object.keys(data).forEach((id) => { @@ -109,10 +127,12 @@ const playQueueReducer = ( playing: true, } case PLAYER_SYNC_QUEUE: + current = data.length > 0 ? previousState.current : {} return { ...previousState, queue: data, clear: false, + current, } case PLAYER_SCROBBLE: const newQueue = previousState.queue.map((item) => { @@ -156,6 +176,7 @@ export { playTracks, syncQueue, scrobble, + progress, shuffleTracks, playQueueReducer, } diff --git a/ui/src/common/SongTitleField.js b/ui/src/common/SongTitleField.js new file mode 100644 index 000000000..173a02078 --- /dev/null +++ b/ui/src/common/SongTitleField.js @@ -0,0 +1,58 @@ +import { makeStyles } from '@material-ui/core/styles' +import React from 'react' +import { useSelector } from 'react-redux' +import get from 'lodash.get' +import playing from '../icons/playing.gif' +import { FunctionField } from 'react-admin' +import PropTypes from 'prop-types' + +const useStyles = makeStyles({ + playingIcon: { + width: '20px', + height: '20px', + verticalAlign: 'text-top', + marginTop: '-2px', + paddingRight: '3px', + }, +}) + +const SongTitleField = ({ showTrackNumbers, ...props }) => { + const classes = useStyles() + const { record } = props + const currentTrack = useSelector((state) => get(state, 'queue.current', {})) + const currentId = currentTrack.trackId + const paused = currentTrack.paused + const isCurrent = + currentId && + !paused && + (currentId === record.id || currentId === record.mediaFileId) + + const trackName = (r) => { + const name = r.title + if (r.trackNumber && showTrackNumbers) { + return r.trackNumber.toString().padStart(2, '0') + ' ' + name + } + return name + } + + return ( + <> + {isCurrent && ( + playing + )} + + + ) +} + +SongTitleField.propTypes = { + record: PropTypes.object, + showTrackNumbers: PropTypes.bool, +} + +export default SongTitleField diff --git a/ui/src/common/index.js b/ui/src/common/index.js index ae720cc86..0ccf8336e 100644 --- a/ui/src/common/index.js +++ b/ui/src/common/index.js @@ -12,6 +12,7 @@ import DocLink from './DocLink' import List from './List' import { SongDatagrid, SongDatagridRow } from './SongDatagrid' import SongContextMenu from './SongContextMenu' +import SongTitleField from './SongTitleField' import QuickFilter from './QuickFilter' import useAlbumsPerPage from './useAlbumsPerPage' @@ -28,6 +29,7 @@ export { SongDetails, SongDatagrid, SongDatagridRow, + SongTitleField, DocLink, formatRange, ArtistLinkField, diff --git a/ui/src/icons/playing.gif b/ui/src/icons/playing.gif new file mode 100644 index 000000000..40bb3c428 Binary files /dev/null and b/ui/src/icons/playing.gif differ diff --git a/ui/src/playlist/PlaylistSongs.js b/ui/src/playlist/PlaylistSongs.js index 4a03c35d3..6f4a57ceb 100644 --- a/ui/src/playlist/PlaylistSongs.js +++ b/ui/src/playlist/PlaylistSongs.js @@ -19,6 +19,7 @@ import { SongDetails, SongContextMenu, SongDatagrid, + SongTitleField, } from '../common' import AddToPlaylistDialog from '../dialogs/AddToPlaylistDialog' import { AlbumLinkField } from '../song/AlbumLinkField' @@ -160,7 +161,7 @@ const PlaylistSongs = (props) => { contextAlwaysVisible={!isDesktop} > {isDesktop && } - + {isDesktop && } {isDesktop && } { rowClick={handleRowClick} contextAlwaysVisible={!isDesktop} > - + {isDesktop && } {isDesktop && }