Show indicator on current playing song. Fixes #128

This commit is contained in:
Deluan
2020-06-19 09:12:45 -04:00
parent eb4c0f0b84
commit 9d23b191b5
11 changed files with 129 additions and 27 deletions
+8 -19
View File
@@ -2,7 +2,6 @@ import React from 'react'
import { import {
BulkActionsToolbar, BulkActionsToolbar,
DatagridLoading, DatagridLoading,
FunctionField,
ListToolbar, ListToolbar,
TextField, TextField,
useListController, useListController,
@@ -15,9 +14,10 @@ import StarBorderIcon from '@material-ui/icons/StarBorder'
import { playTracks } from '../audioplayer' import { playTracks } from '../audioplayer'
import { import {
DurationField, DurationField,
SongDetails,
SongDatagrid,
SongContextMenu, SongContextMenu,
SongDatagrid,
SongDetails,
SongTitleField,
} from '../common' } from '../common'
import AddToPlaylistDialog from '../dialogs/AddToPlaylistDialog' 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 AlbumSongs = (props) => {
const classes = useStyles(props) const classes = useStyles(props)
const classesToolbar = useStylesListToolbar(props) const classesToolbar = useStylesListToolbar(props)
@@ -132,14 +124,11 @@ const AlbumSongs = (props) => {
sortable={false} sortable={false}
/> />
)} )}
{isDesktop && <TextField source="title" sortable={false} />} <SongTitleField
{!isDesktop && ( source="title"
<FunctionField sortable={false}
source="title" showTrackNumbers={!isDesktop}
render={trackName} />
sortable={false}
/>
)}
{isDesktop && <TextField source="artist" sortable={false} />} {isDesktop && <TextField source="artist" sortable={false} />}
<DurationField source="duration" sortable={false} /> <DurationField source="duration" sortable={false} />
<SongContextMenu <SongContextMenu
+13 -4
View File
@@ -5,7 +5,7 @@ import { useAuthState, useDataProvider, useTranslate } from 'react-admin'
import ReactJkMusicPlayer from 'react-jinke-music-player' import ReactJkMusicPlayer from 'react-jinke-music-player'
import 'react-jinke-music-player/assets/index.css' import 'react-jinke-music-player/assets/index.css'
import subsonic from '../subsonic' import subsonic from '../subsonic'
import { scrobble, syncQueue } from './queue' import { scrobble, syncQueue, currentPlaying } from './queue'
import themes from '../themes' import themes from '../themes'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
@@ -100,6 +100,9 @@ const Player = () => {
} }
const OnAudioProgress = (info) => { const OnAudioProgress = (info) => {
if (info.ended) {
document.title = 'Navidrome'
}
const progress = (info.currentTime / info.duration) * 100 const progress = (info.currentTime / info.duration) * 100
if (isNaN(info.duration) || progress < 90) { if (isNaN(info.duration) || progress < 90) {
return return
@@ -112,16 +115,21 @@ const Player = () => {
} }
const OnAudioPlay = (info) => { const OnAudioPlay = (info) => {
dispatch(currentPlaying(info))
if (info.duration) { if (info.duration) {
document.title = `${info.name} - ${info.singer} - Navidrome` document.title = `${info.name} - ${info.singer} - Navidrome`
dispatch(scrobble(info.trackId, false)) dispatch(scrobble(info.trackId, false))
subsonic.scrobble(info.trackId, false) subsonic.scrobble(info.trackId, false)
dataProvider.getOne('keepalive', { id: info.trackId })
} }
} }
const onAudioEnded = () => { const onAudioPause = (info) => {
document.title = 'Navidrome' dispatch(currentPlaying(info))
}
const onAudioEnded = (currentPlayId, audioLists, info) => {
dispatch(currentPlaying(info))
dataProvider.getOne('keepalive', { id: info.trackId })
} }
if (authenticated && options.audioLists.length > 0) { if (authenticated && options.audioLists.length > 0) {
@@ -131,6 +139,7 @@ const Player = () => {
onAudioListsChange={OnAudioListsChange} onAudioListsChange={OnAudioListsChange}
onAudioProgress={OnAudioProgress} onAudioProgress={OnAudioProgress}
onAudioPlay={OnAudioPlay} onAudioPlay={OnAudioPlay}
onAudioPause={onAudioPause}
onAudioEnded={onAudioEnded} onAudioEnded={onAudioEnded}
/> />
) )
+23 -2
View File
@@ -6,6 +6,7 @@ const PLAYER_SET_TRACK = 'PLAYER_SET_TRACK'
const PLAYER_SYNC_QUEUE = 'PLAYER_SYNC_QUEUE' const PLAYER_SYNC_QUEUE = 'PLAYER_SYNC_QUEUE'
const PLAYER_SCROBBLE = 'PLAYER_SCROBBLE' const PLAYER_SCROBBLE = 'PLAYER_SCROBBLE'
const PLAYER_PLAY_TRACKS = 'PLAYER_PLAY_TRACKS' const PLAYER_PLAY_TRACKS = 'PLAYER_PLAY_TRACKS'
const PLAYER_CURRENT = 'PLAYER_CURRENT'
const mapToAudioLists = (item) => { const mapToAudioLists = (item) => {
// If item comes from a playlist, id is mediaFileId // If item comes from a playlist, id is mediaFileId
@@ -88,13 +89,30 @@ const scrobble = (id, submit) => ({
submit, submit,
}) })
const currentPlaying = (audioInfo) => ({
type: PLAYER_CURRENT,
data: audioInfo,
})
const playQueueReducer = ( const playQueueReducer = (
previousState = { queue: [], clear: true, playing: false }, previousState = { queue: [], clear: true, playing: false, current: {} },
payload payload
) => { ) => {
let queue let queue, current
const { type, data } = payload const { type, data } = payload
switch (type) { switch (type) {
case PLAYER_CURRENT:
queue = previousState.queue
current = data.ended
? {}
: {
trackId: data.trackId,
paused: data.paused,
}
return {
...previousState,
current,
}
case PLAYER_ADD_TRACKS: case PLAYER_ADD_TRACKS:
queue = previousState.queue queue = previousState.queue
Object.keys(data).forEach((id) => { Object.keys(data).forEach((id) => {
@@ -109,10 +127,12 @@ const playQueueReducer = (
playing: true, playing: true,
} }
case PLAYER_SYNC_QUEUE: case PLAYER_SYNC_QUEUE:
current = data.length > 0 ? previousState.current : {}
return { return {
...previousState, ...previousState,
queue: data, queue: data,
clear: false, clear: false,
current,
} }
case PLAYER_SCROBBLE: case PLAYER_SCROBBLE:
const newQueue = previousState.queue.map((item) => { const newQueue = previousState.queue.map((item) => {
@@ -156,6 +176,7 @@ export {
playTracks, playTracks,
syncQueue, syncQueue,
scrobble, scrobble,
currentPlaying,
shuffleTracks, shuffleTracks,
playQueueReducer, playQueueReducer,
} }
+79
View File
@@ -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 (
<img
src={icon}
className={classes.icon}
alt={paused ? 'paused' : 'playing'}
/>
)
}
return (
<>
{isCurrent && <Icon />}
<FunctionField
{...props}
source="title"
render={trackName}
className={classes.text}
/>
</>
)
}
SongTitleField.propTypes = {
record: PropTypes.object,
showTrackNumbers: PropTypes.bool,
}
export default SongTitleField
+2
View File
@@ -12,6 +12,7 @@ import DocLink from './DocLink'
import List from './List' import List from './List'
import { SongDatagrid, SongDatagridRow } from './SongDatagrid' import { SongDatagrid, SongDatagridRow } from './SongDatagrid'
import SongContextMenu from './SongContextMenu' import SongContextMenu from './SongContextMenu'
import SongTitleField from './SongTitleField'
import QuickFilter from './QuickFilter' import QuickFilter from './QuickFilter'
import useAlbumsPerPage from './useAlbumsPerPage' import useAlbumsPerPage from './useAlbumsPerPage'
@@ -28,6 +29,7 @@ export {
SongDetails, SongDetails,
SongDatagrid, SongDatagrid,
SongDatagridRow, SongDatagridRow,
SongTitleField,
DocLink, DocLink,
formatRange, formatRange,
ArtistLinkField, ArtistLinkField,
Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

+2 -1
View File
@@ -19,6 +19,7 @@ import {
SongDetails, SongDetails,
SongContextMenu, SongContextMenu,
SongDatagrid, SongDatagrid,
SongTitleField,
} from '../common' } from '../common'
import AddToPlaylistDialog from '../dialogs/AddToPlaylistDialog' import AddToPlaylistDialog from '../dialogs/AddToPlaylistDialog'
import { AlbumLinkField } from '../song/AlbumLinkField' import { AlbumLinkField } from '../song/AlbumLinkField'
@@ -160,7 +161,7 @@ const PlaylistSongs = (props) => {
contextAlwaysVisible={!isDesktop} contextAlwaysVisible={!isDesktop}
> >
{isDesktop && <TextField source="id" label={'#'} />} {isDesktop && <TextField source="id" label={'#'} />}
<TextField source="title" /> <SongTitleField source="title" showTrackNumbers={false} />
{isDesktop && <AlbumLinkField source="album" />} {isDesktop && <AlbumLinkField source="album" />}
{isDesktop && <TextField source="artist" />} {isDesktop && <TextField source="artist" />}
<DurationField <DurationField
+2 -1
View File
@@ -18,6 +18,7 @@ import {
SongDatagrid, SongDatagrid,
SongDetails, SongDetails,
QuickFilter, QuickFilter,
SongTitleField,
} from '../common' } from '../common'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { setTrack } from '../audioplayer' import { setTrack } from '../audioplayer'
@@ -83,7 +84,7 @@ const SongList = (props) => {
rowClick={handleRowClick} rowClick={handleRowClick}
contextAlwaysVisible={!isDesktop} contextAlwaysVisible={!isDesktop}
> >
<TextField source="title" /> <SongTitleField source="title" showTrackNumbers={false} />
{isDesktop && <AlbumLinkField source="album" />} {isDesktop && <AlbumLinkField source="album" />}
<TextField source="artist" /> <TextField source="artist" />
{isDesktop && <NumberField source="trackNumber" />} {isDesktop && <NumberField source="trackNumber" />}