mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-19 13:27:42 +03:00
feat: initial integration of react-jinke-music-player
This commit is contained in:
parent
220ffd5324
commit
4a82a6cb02
@ -150,7 +150,7 @@ func (m *transcodedMediaStream) Read(p []byte) (n int, err error) {
|
||||
// a Seek happens. This is ok-ish for audio, but would kill the server for video.
|
||||
func (m *transcodedMediaStream) Seek(offset int64, whence int) (int64, error) {
|
||||
size := int64((m.mf.Duration)*m.bitRate*1000) / 8
|
||||
log.Trace(m.ctx, "Seeking file", "path", m.mf.Path, "offset", offset, "whence", whence, "size", size)
|
||||
log.Trace(m.ctx, "Seeking transcoded stream", "path", m.mf.Path, "offset", offset, "whence", whence, "size", size)
|
||||
|
||||
switch whence {
|
||||
case io.SeekEnd:
|
||||
|
182
ui/package-lock.json
generated
182
ui/package-lock.json
generated
@ -2491,6 +2491,14 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
|
||||
"integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA=="
|
||||
},
|
||||
"add-dom-event-listener": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz",
|
||||
"integrity": "sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==",
|
||||
"requires": {
|
||||
"object-assign": "4.x"
|
||||
}
|
||||
},
|
||||
"address": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz",
|
||||
@ -4039,11 +4047,24 @@
|
||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
|
||||
},
|
||||
"component-classes": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/component-classes/-/component-classes-1.2.6.tgz",
|
||||
"integrity": "sha1-xkI5TDYYpNiwuJGe/Mu9kw5c1pE=",
|
||||
"requires": {
|
||||
"component-indexof": "0.0.3"
|
||||
}
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
|
||||
},
|
||||
"component-indexof": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/component-indexof/-/component-indexof-0.0.3.tgz",
|
||||
"integrity": "sha1-EdCRMSI5648yyPJa6csAL/6NPCQ="
|
||||
},
|
||||
"compose-function": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/compose-function/-/compose-function-3.0.3.tgz",
|
||||
@ -4352,6 +4373,15 @@
|
||||
"urix": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"css-animation": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.6.1.tgz",
|
||||
"integrity": "sha512-/48+/BaEaHRY6kNQ2OIPzKf9A6g8WjZYjhiNDNuIVbsm5tXCGIAsHDjB4Xu1C4vXJtUWZo26O68OQkDpNBaPog==",
|
||||
"requires": {
|
||||
"babel-runtime": "6.x",
|
||||
"component-classes": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"css-blank-pseudo": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz",
|
||||
@ -4906,6 +4936,11 @@
|
||||
"esutils": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"dom-align": {
|
||||
"version": "1.10.4",
|
||||
"resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.10.4.tgz",
|
||||
"integrity": "sha512-wytDzaru67AmqFOY4B9GUb/hrwWagezoYYK97D/vpK+ezg+cnuZO0Q2gltUPa7KfNmIqfRIYVCF8UhRDEHAmgQ=="
|
||||
},
|
||||
"dom-converter": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
|
||||
@ -4992,6 +5027,11 @@
|
||||
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
|
||||
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
|
||||
},
|
||||
"downloadjs": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz",
|
||||
"integrity": "sha1-9p+W+UDg0FU9rCkROYZaPNAQHjw="
|
||||
},
|
||||
"downshift": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/downshift/-/downshift-3.2.7.tgz",
|
||||
@ -7564,6 +7604,11 @@
|
||||
"resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
|
||||
"integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
|
||||
},
|
||||
"is-mobile": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-2.1.0.tgz",
|
||||
"integrity": "sha512-M5OhlZwh+aTlmRUvDg0Wq3uWVNa+w4DyZ2SjbrS+BhSLu9Po+JXHendC305ZEu+Hh7lywb19Zu4kYXu3L1Oo8A=="
|
||||
},
|
||||
"is-number": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
||||
@ -13046,6 +13091,92 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"rc-align": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/rc-align/-/rc-align-2.4.5.tgz",
|
||||
"integrity": "sha512-nv9wYUYdfyfK+qskThf4BQUSIadeI/dCsfaMZfNEoxm9HwOIioQ+LyqmMK6jWHAZQgOzMLaqawhuBXlF63vgjw==",
|
||||
"requires": {
|
||||
"babel-runtime": "^6.26.0",
|
||||
"dom-align": "^1.7.0",
|
||||
"prop-types": "^15.5.8",
|
||||
"rc-util": "^4.0.4"
|
||||
}
|
||||
},
|
||||
"rc-animate": {
|
||||
"version": "2.10.2",
|
||||
"resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.10.2.tgz",
|
||||
"integrity": "sha512-cE/A7piAzoWFSgUD69NmmMraqCeqVBa51UErod8NS3LUEqWfppSVagHfa0qHAlwPVPiIBg3emRONyny3eiH0Dg==",
|
||||
"requires": {
|
||||
"babel-runtime": "6.x",
|
||||
"classnames": "^2.2.6",
|
||||
"css-animation": "^1.3.2",
|
||||
"prop-types": "15.x",
|
||||
"raf": "^3.4.0",
|
||||
"rc-util": "^4.15.3",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"rc-slider": {
|
||||
"version": "8.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-8.7.1.tgz",
|
||||
"integrity": "sha512-WMT5mRFUEcrLWwTxsyS8jYmlaMsTVCZIGENLikHsNv+tE8ThU2lCoPfi/xFNUfJFNFSBFP3MwPez9ZsJmNp13g==",
|
||||
"requires": {
|
||||
"babel-runtime": "6.x",
|
||||
"classnames": "^2.2.5",
|
||||
"prop-types": "^15.5.4",
|
||||
"rc-tooltip": "^3.7.0",
|
||||
"rc-util": "^4.0.4",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"shallowequal": "^1.1.0",
|
||||
"warning": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"rc-switch": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-1.9.0.tgz",
|
||||
"integrity": "sha512-Isas+egaK6qSk64jaEw4GgPStY4umYDbT7ZY93bZF1Af+b/JEsKsJdNOU2qG3WI0Z6tXo2DDq0kJCv8Yhu0zww==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.1",
|
||||
"prop-types": "^15.5.6",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"rc-tooltip": {
|
||||
"version": "3.7.3",
|
||||
"resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-3.7.3.tgz",
|
||||
"integrity": "sha512-dE2ibukxxkrde7wH9W8ozHKUO4aQnPZ6qBHtrTH9LoO836PjDdiaWO73fgPB05VfJs9FbZdmGPVEbXCeOP99Ww==",
|
||||
"requires": {
|
||||
"babel-runtime": "6.x",
|
||||
"prop-types": "^15.5.8",
|
||||
"rc-trigger": "^2.2.2"
|
||||
}
|
||||
},
|
||||
"rc-trigger": {
|
||||
"version": "2.6.5",
|
||||
"resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-2.6.5.tgz",
|
||||
"integrity": "sha512-m6Cts9hLeZWsTvWnuMm7oElhf+03GOjOLfTuU0QmdB9ZrW7jR2IpI5rpNM7i9MvAAlMAmTx5Zr7g3uu/aMvZAw==",
|
||||
"requires": {
|
||||
"babel-runtime": "6.x",
|
||||
"classnames": "^2.2.6",
|
||||
"prop-types": "15.x",
|
||||
"rc-align": "^2.4.0",
|
||||
"rc-animate": "2.x",
|
||||
"rc-util": "^4.4.0",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"rc-util": {
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.19.0.tgz",
|
||||
"integrity": "sha512-mptALlLwpeczS3nrv83DbwJNeupolbuvlIEjcvimSiWI8NUBjpF0HgG3kWp1RymiuiRCNm9yhaXqDz0a99dpgQ==",
|
||||
"requires": {
|
||||
"add-dom-event-listener": "^1.1.0",
|
||||
"babel-runtime": "6.x",
|
||||
"prop-types": "^15.5.10",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"shallowequal": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"react": {
|
||||
"version": "16.12.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz",
|
||||
@ -13231,6 +13362,23 @@
|
||||
"scheduler": "^0.18.0"
|
||||
}
|
||||
},
|
||||
"react-drag-listview": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/react-drag-listview/-/react-drag-listview-0.1.6.tgz",
|
||||
"integrity": "sha512-0nSWkR1bMLKgLZIYY2YVURYapppzy46FNSs9uAcCxceo2lnajngzLQ3tBgWaTjKTlWMXD0MAcDUWFDYdqMPYUg==",
|
||||
"requires": {
|
||||
"prop-types": "^15.5.8"
|
||||
}
|
||||
},
|
||||
"react-draggable": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-3.3.2.tgz",
|
||||
"integrity": "sha512-oaz8a6enjbPtx5qb0oDWxtDNuybOylvto1QLydsXgKmwT7e3GXC2eMVDwEMIUYJIFqVG72XpOv673UuuAq6LhA==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.5",
|
||||
"prop-types": "^15.6.0"
|
||||
}
|
||||
},
|
||||
"react-dropzone": {
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-10.2.1.tgz",
|
||||
@ -13263,11 +13411,40 @@
|
||||
"@babel/runtime": "^7.4.5"
|
||||
}
|
||||
},
|
||||
"react-icon-base": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icon-base/-/react-icon-base-2.1.0.tgz",
|
||||
"integrity": "sha1-oZbjP98eeqof2jrvu2i9rZ6Cp50="
|
||||
},
|
||||
"react-icons": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-2.2.7.tgz",
|
||||
"integrity": "sha512-0n4lcGqzJFcIQLoQytLdJCE0DKSA9dkwEZRYoGrIDJZFvIT6Hbajx5mv9geqhqFiNjUgtxg8kPyDfjlhymbGFg==",
|
||||
"requires": {
|
||||
"react-icon-base": "2.1.0"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.12.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
|
||||
"integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q=="
|
||||
},
|
||||
"react-jinke-music-player": {
|
||||
"version": "4.7.2",
|
||||
"resolved": "https://registry.npmjs.org/react-jinke-music-player/-/react-jinke-music-player-4.7.2.tgz",
|
||||
"integrity": "sha512-r2P1gf7nsOBBXqVaKbN73POomWXAYiHuOq5q6AIiUPCVvKx19pCiOsVqwN0vB3kN5tK3Vypm1tO0GkFBVVK11Q==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.6",
|
||||
"downloadjs": "^1.4.7",
|
||||
"is-mobile": "^2.1.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"rc-slider": "^8.7.1",
|
||||
"rc-switch": "^1.9.0",
|
||||
"react-drag-listview": "^0.1.6",
|
||||
"react-draggable": "^3.3.2",
|
||||
"react-icons": "^2.2.5"
|
||||
}
|
||||
},
|
||||
"react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
@ -14262,6 +14439,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"shallowequal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
|
@ -12,6 +12,7 @@
|
||||
"react": "^16.12.0",
|
||||
"react-admin": "^3.1.2",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-jinke-music-player": "^4.7.2",
|
||||
"react-scripts": "3.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -1,31 +1,41 @@
|
||||
// in src/App.js
|
||||
import React from 'react'
|
||||
import { Admin, Resource } from 'react-admin'
|
||||
import dataProvider from './dataProvider'
|
||||
import authProvider from './authProvider'
|
||||
import { Login, Layout, DarkTheme } from './layout'
|
||||
import { DarkTheme, Layout, Login } from './layout'
|
||||
import user from './user'
|
||||
import song from './song'
|
||||
import album from './album'
|
||||
import artist from './artist'
|
||||
import { createMuiTheme } from '@material-ui/core/styles'
|
||||
import { Player, playQueueReducer } from './player'
|
||||
|
||||
const theme = createMuiTheme(DarkTheme)
|
||||
|
||||
const App = () => (
|
||||
<Admin
|
||||
theme={theme}
|
||||
dataProvider={dataProvider}
|
||||
authProvider={authProvider}
|
||||
layout={Layout}
|
||||
loginPage={Login}
|
||||
>
|
||||
{(permissions) => [
|
||||
<Resource name="artist" {...artist} options={{ subMenu: 'library' }} />,
|
||||
<Resource name="album" {...album} options={{ subMenu: 'library' }} />,
|
||||
<Resource name="song" {...song} options={{ subMenu: 'library' }} />,
|
||||
permissions === 'admin' ? <Resource name="user" {...user} /> : null
|
||||
]}
|
||||
</Admin>
|
||||
<>
|
||||
<div>
|
||||
<Admin
|
||||
theme={theme}
|
||||
customReducers={{ queue: playQueueReducer }}
|
||||
dataProvider={dataProvider}
|
||||
authProvider={authProvider}
|
||||
layout={Layout}
|
||||
loginPage={Login}
|
||||
>
|
||||
{(permissions) => [
|
||||
<Resource
|
||||
name="artist"
|
||||
{...artist}
|
||||
options={{ subMenu: 'library' }}
|
||||
/>,
|
||||
<Resource name="album" {...album} options={{ subMenu: 'library' }} />,
|
||||
<Resource name="song" {...song} options={{ subMenu: 'library' }} />,
|
||||
permissions === 'admin' ? <Resource name="user" {...user} /> : null,
|
||||
<Player />
|
||||
]}
|
||||
</Admin>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
export default App
|
||||
|
@ -50,7 +50,6 @@ const AlbumList = (props) => (
|
||||
exporter={false}
|
||||
bulkActionButtons={false}
|
||||
filters={<AlbumFilter />}
|
||||
perPage={15}
|
||||
>
|
||||
<Datagrid expand={<AlbumDetails />} rowClick={albumRowClick}>
|
||||
<TextField source="name" />
|
||||
|
@ -28,7 +28,6 @@ const ArtistList = (props) => (
|
||||
exporter={false}
|
||||
bulkActionButtons={false}
|
||||
filters={<ArtistFilter />}
|
||||
perPage={15}
|
||||
>
|
||||
<Datagrid rowClick={artistRowClick}>
|
||||
<TextField source="name" />
|
||||
|
@ -1,4 +1,3 @@
|
||||
// in src/Menu.js
|
||||
import React, { useState, createElement } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useMediaQuery } from '@material-ui/core'
|
||||
|
66
ui/src/player/Player.js
Normal file
66
ui/src/player/Player.js
Normal file
@ -0,0 +1,66 @@
|
||||
import React from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useAuthState } from 'react-admin'
|
||||
import ReactJkMusicPlayer from 'react-jinke-music-player'
|
||||
import 'react-jinke-music-player/assets/index.css'
|
||||
import { syncQueue } from './queue'
|
||||
|
||||
const defaultOptions = {
|
||||
bounds: 'body',
|
||||
mode: 'full',
|
||||
autoPlay: true,
|
||||
preload: true,
|
||||
autoPlayInitLoadPlayList: true,
|
||||
clearPriorAudioLists: false,
|
||||
showDownload: false,
|
||||
showReload: false,
|
||||
glassBg: false,
|
||||
showThemeSwitch: false,
|
||||
playModeText: {
|
||||
order: 'order',
|
||||
orderLoop: 'orderLoop',
|
||||
singleLoop: 'singleLoop',
|
||||
shufflePlay: 'shufflePlay'
|
||||
},
|
||||
defaultPosition: {
|
||||
top: 300,
|
||||
left: 120
|
||||
}
|
||||
}
|
||||
|
||||
const addQueueToOptions = (queue) => {
|
||||
return {
|
||||
...defaultOptions,
|
||||
autoPlay: true,
|
||||
clearPriorAudioLists: queue.clear,
|
||||
audioLists: queue.queue.map((item) => item)
|
||||
}
|
||||
}
|
||||
|
||||
const Player = () => {
|
||||
const dispatch = useDispatch()
|
||||
const queue = useSelector((state) => state.queue)
|
||||
const options = addQueueToOptions(queue)
|
||||
const { authenticated } = useAuthState()
|
||||
|
||||
const OnAudioListsChange = (currentPlayIndex, audioLists) => {
|
||||
dispatch(syncQueue(audioLists))
|
||||
}
|
||||
|
||||
const OnAudioProgress = (info) => {
|
||||
const progress = (info.currentTime / info.duration) * 100
|
||||
}
|
||||
|
||||
if (authenticated && options.audioLists.length > 0) {
|
||||
return (
|
||||
<ReactJkMusicPlayer
|
||||
{...options}
|
||||
onAudioListsChange={OnAudioListsChange}
|
||||
onAudioProgress={OnAudioProgress}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return <div />
|
||||
}
|
||||
|
||||
export default Player
|
4
ui/src/player/index.js
Normal file
4
ui/src/player/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
import Player from './Player'
|
||||
import { addTrack, setTrack, playQueueReducer } from './queue'
|
||||
|
||||
export { Player, addTrack, setTrack, playQueueReducer }
|
50
ui/src/player/queue.js
Normal file
50
ui/src/player/queue.js
Normal file
@ -0,0 +1,50 @@
|
||||
import 'react-jinke-music-player/assets/index.css'
|
||||
|
||||
const PLAYER_ADD_TRACK = 'PLAYER_ADD_TRACK'
|
||||
const PLAYER_SET_TRACK = 'PLAYER_SET_TRACK'
|
||||
const PLAYER_SYNC_QUEUE = 'PLAYER_SYNC_QUEUE'
|
||||
|
||||
const mapToAudioLists = (item) => ({
|
||||
id: item.id,
|
||||
name: item.title,
|
||||
singer: item.artist,
|
||||
cover: `/rest/getCoverArt.view?u=admin&p=enc:73756e6461&f=json&v=1.8.0&c=Jamstash&size=300&id=${item.id}`,
|
||||
musicSrc: `/rest/stream.view?u=admin&p=enc:73756e6461&f=json&v=1.8.0&c=Jamstash&id=${
|
||||
item.id
|
||||
}&ts=${new Date().getTime()}`
|
||||
})
|
||||
|
||||
const addTrack = (data) => ({
|
||||
type: PLAYER_ADD_TRACK,
|
||||
data
|
||||
})
|
||||
|
||||
const setTrack = (data) => ({
|
||||
type: PLAYER_SET_TRACK,
|
||||
data
|
||||
})
|
||||
|
||||
const syncQueue = (data) => ({
|
||||
type: PLAYER_SYNC_QUEUE,
|
||||
data
|
||||
})
|
||||
|
||||
const playQueueReducer = (
|
||||
previousState = { queue: [], clear: true },
|
||||
{ type, data }
|
||||
) => {
|
||||
switch (type) {
|
||||
case PLAYER_ADD_TRACK:
|
||||
const queue = previousState.queue
|
||||
queue.push(mapToAudioLists(data))
|
||||
return { queue, clear: false }
|
||||
case PLAYER_SET_TRACK:
|
||||
return { queue: [mapToAudioLists(data)], clear: true }
|
||||
case PLAYER_SYNC_QUEUE:
|
||||
return { queue: data, clear: false }
|
||||
default:
|
||||
return previousState
|
||||
}
|
||||
}
|
||||
|
||||
export { addTrack, setTrack, syncQueue, playQueueReducer }
|
23
ui/src/song/AddToQueueButton.js
Normal file
23
ui/src/song/AddToQueueButton.js
Normal file
@ -0,0 +1,23 @@
|
||||
import React from 'react'
|
||||
import { Button, useDataProvider, useUnselectAll } from 'react-admin'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { addTrack } from '../player'
|
||||
|
||||
const AddToQueueButton = ({ selectedIds }) => {
|
||||
const dispatch = useDispatch()
|
||||
const dataProvider = useDataProvider()
|
||||
const unselectAll = useUnselectAll()
|
||||
const addToQueue = () => {
|
||||
selectedIds.forEach((id) => {
|
||||
dataProvider.getOne('song', { id }).then((response) => {
|
||||
console.log(response.data)
|
||||
dispatch(addTrack(response.data))
|
||||
})
|
||||
})
|
||||
unselectAll('song')
|
||||
}
|
||||
|
||||
return <Button color="secondary" label="Add To Queue" onClick={addToQueue} />
|
||||
}
|
||||
|
||||
export default AddToQueueButton
|
30
ui/src/song/PlayButton.js
Normal file
30
ui/src/song/PlayButton.js
Normal file
@ -0,0 +1,30 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import PlayArrowIcon from '@material-ui/icons/PlayArrow'
|
||||
import { IconButton } from '@material-ui/core'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { setTrack } from '../player'
|
||||
|
||||
const defaultIcon = <PlayArrowIcon fontSize="small" />
|
||||
|
||||
const PlayButton = ({
|
||||
record,
|
||||
icon = defaultIcon,
|
||||
action = setTrack,
|
||||
...rest
|
||||
}) => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
return (
|
||||
<IconButton onClick={() => dispatch(action(record))} {...rest}>
|
||||
{icon}
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
|
||||
PlayButton.propTypes = {
|
||||
record: PropTypes.any,
|
||||
icon: PropTypes.element,
|
||||
action: PropTypes.func
|
||||
}
|
||||
export default PlayButton
|
@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, { Fragment } from 'react'
|
||||
import {
|
||||
BooleanField,
|
||||
Datagrid,
|
||||
@ -7,12 +7,14 @@ import {
|
||||
List,
|
||||
NumberField,
|
||||
SearchInput,
|
||||
TextInput,
|
||||
Show,
|
||||
SimpleShowLayout,
|
||||
TextField
|
||||
TextField,
|
||||
TextInput
|
||||
} from 'react-admin'
|
||||
import { BitrateField, DurationField, Title } from '../common'
|
||||
import AddToQueueButton from './AddToQueueButton'
|
||||
import PlayButton from './PlayButton'
|
||||
|
||||
const SongFilter = (props) => (
|
||||
<Filter {...props}>
|
||||
@ -22,6 +24,12 @@ const SongFilter = (props) => (
|
||||
</Filter>
|
||||
)
|
||||
|
||||
const SongBulkActionButtons = (props) => (
|
||||
<Fragment>
|
||||
<AddToQueueButton {...props} />
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
const SongDetails = (props) => {
|
||||
return (
|
||||
<Show {...props} title=" ">
|
||||
@ -37,26 +45,28 @@ const SongDetails = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
const SongList = (props) => (
|
||||
<List
|
||||
{...props}
|
||||
title={<Title subTitle={'Songs'} />}
|
||||
sort={{ field: 'title', order: 'ASC' }}
|
||||
exporter={false}
|
||||
bulkActionButtons={false}
|
||||
filters={<SongFilter />}
|
||||
perPage={15}
|
||||
>
|
||||
<Datagrid expand={<SongDetails />}>
|
||||
<TextField source="title" />
|
||||
<TextField source="album" />
|
||||
<TextField source="artist" />
|
||||
<NumberField label="Track #" source="trackNumber" />
|
||||
<NumberField label="Disc #" source="discNumber" />
|
||||
<TextField source="year" />
|
||||
<DurationField label="Time" source="duration" />
|
||||
</Datagrid>
|
||||
</List>
|
||||
)
|
||||
const SongList = (props) => {
|
||||
return (
|
||||
<List
|
||||
{...props}
|
||||
title={<Title subTitle={'Songs'} />}
|
||||
sort={{ field: 'title', order: 'ASC' }}
|
||||
exporter={false}
|
||||
bulkActionButtons={<SongBulkActionButtons />}
|
||||
filters={<SongFilter />}
|
||||
>
|
||||
<Datagrid expand={<SongDetails />}>
|
||||
<PlayButton {...props} />
|
||||
<TextField source="title" />
|
||||
<TextField source="album" />
|
||||
<TextField source="artist" />
|
||||
<NumberField label="Track #" source="trackNumber" />
|
||||
<NumberField label="Disc #" source="discNumber" />
|
||||
<TextField source="year" />
|
||||
<DurationField label="Time" source="duration" />
|
||||
</Datagrid>
|
||||
</List>
|
||||
)
|
||||
}
|
||||
|
||||
export default SongList
|
||||
|
Loading…
x
Reference in New Issue
Block a user