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) => {
+ if (info.ended) {
+ document.title = 'Navidrome'
+ }
const progress = (info.currentTime / info.duration) * 100
if (isNaN(info.duration) || progress < 90) {
return
@@ -112,16 +115,21 @@ const Player = () => {
}
const OnAudioPlay = (info) => {
+ dispatch(currentPlaying(info))
if (info.duration) {
document.title = `${info.name} - ${info.singer} - Navidrome`
dispatch(scrobble(info.trackId, false))
subsonic.scrobble(info.trackId, false)
- dataProvider.getOne('keepalive', { id: info.trackId })
}
}
- const onAudioEnded = () => {
- document.title = 'Navidrome'
+ const onAudioPause = (info) => {
+ dispatch(currentPlaying(info))
+ }
+
+ const onAudioEnded = (currentPlayId, audioLists, info) => {
+ dispatch(currentPlaying(info))
+ dataProvider.getOne('keepalive', { id: info.trackId })
}
if (authenticated && options.audioLists.length > 0) {
@@ -131,6 +139,7 @@ const Player = () => {
onAudioListsChange={OnAudioListsChange}
onAudioProgress={OnAudioProgress}
onAudioPlay={OnAudioPlay}
+ onAudioPause={onAudioPause}
onAudioEnded={onAudioEnded}
/>
)
diff --git a/ui/src/audioplayer/queue.js b/ui/src/audioplayer/queue.js
index e5f3d74dd..251030987 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_CURRENT = 'PLAYER_CURRENT'
const mapToAudioLists = (item) => {
// If item comes from a playlist, id is mediaFileId
@@ -88,13 +89,30 @@ const scrobble = (id, submit) => ({
submit,
})
+const currentPlaying = (audioInfo) => ({
+ type: PLAYER_CURRENT,
+ 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_CURRENT:
+ 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,
+ currentPlaying,
shuffleTracks,
playQueueReducer,
}
diff --git a/ui/src/common/SongTitleField.js b/ui/src/common/SongTitleField.js
new file mode 100644
index 000000000..f7e94495f
--- /dev/null
+++ b/ui/src/common/SongTitleField.js
@@ -0,0 +1,79 @@
+import { makeStyles } from '@material-ui/core/styles'
+import React from 'react'
+import PropTypes from 'prop-types'
+import { useSelector } from 'react-redux'
+import { FunctionField } from 'react-admin'
+import get from 'lodash.get'
+import { useTheme } from '@material-ui/core/styles'
+import PlayingLight from '../icons/playing-light.gif'
+import PlayingDark from '../icons/playing-dark.gif'
+import PausedLight from '../icons/paused-light.png'
+import PausedDark from '../icons/paused-dark.png'
+
+const useStyles = makeStyles({
+ icon: {
+ width: '32px',
+ height: '32px',
+ verticalAlign: 'text-top',
+ marginLeft: '-8px',
+ marginTop: '-7px',
+ paddingRight: '3px',
+ },
+ text: {
+ verticalAlign: 'text-top',
+ },
+})
+
+const SongTitleField = ({ showTrackNumbers, ...props }) => {
+ const theme = useTheme()
+ 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 && (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
+ }
+
+ const Icon = () => {
+ let icon
+ if (paused) {
+ icon = theme.palette.type === 'light' ? PausedLight : PausedDark
+ } else {
+ icon = theme.palette.type === 'light' ? PlayingLight : PlayingDark
+ }
+ return (
+
+ )
+ }
+
+ return (
+ <>
+ {isCurrent && }
+
+ >
+ )
+}
+
+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/paused-dark.png b/ui/src/icons/paused-dark.png
new file mode 100644
index 000000000..548f9a7f0
Binary files /dev/null and b/ui/src/icons/paused-dark.png differ
diff --git a/ui/src/icons/paused-light.png b/ui/src/icons/paused-light.png
new file mode 100644
index 000000000..bf08e24ec
Binary files /dev/null and b/ui/src/icons/paused-light.png differ
diff --git a/ui/src/icons/playing-dark.gif b/ui/src/icons/playing-dark.gif
new file mode 100644
index 000000000..9413d54af
Binary files /dev/null and b/ui/src/icons/playing-dark.gif differ
diff --git a/ui/src/icons/playing-light.gif b/ui/src/icons/playing-light.gif
new file mode 100644
index 000000000..40bb3c428
Binary files /dev/null and b/ui/src/icons/playing-light.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 && }