mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-17 20:42:25 +03:00
Add export as m3u button to playlist
This commit is contained in:
parent
366054e8cc
commit
9df405a8ce
@ -116,7 +116,8 @@
|
||||
},
|
||||
"actions": {
|
||||
"selectPlaylist": "Selecione a playlist:",
|
||||
"addNewPlaylist": "Criar \"%{name}\""
|
||||
"addNewPlaylist": "Criar \"%{name}\"",
|
||||
"export": "Exportar"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -56,16 +56,24 @@ func handleExportPlaylist(ds model.DataStore) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug(ctx, "Exporting playlist as M3U", "playlistId", plsId, "name", pls.Name)
|
||||
w.Header().Set("Content-Type", "audio/x-mpegurl")
|
||||
disposition := fmt.Sprintf("attachment; filename=\"%s.m3u\"", pls.Name)
|
||||
w.Header().Set("Content-Disposition", disposition)
|
||||
|
||||
// TODO: Move this and the import playlist logic to `core`
|
||||
w.Write([]byte("#EXTM3U\n"))
|
||||
_, err = w.Write([]byte("#EXTM3U\n"))
|
||||
if err != nil {
|
||||
log.Error(ctx, "Error sending playlist", "name", pls.Name)
|
||||
return
|
||||
}
|
||||
for _, t := range pls.Tracks {
|
||||
header := fmt.Sprintf("#EXTINF:%.f,%s - %s\n", t.Duration, t.Artist, t.Title)
|
||||
line := t.Path + "\n"
|
||||
_, err := w.Write([]byte(header + line))
|
||||
_, err = w.Write([]byte(header + line))
|
||||
if err != nil {
|
||||
log.Error(ctx, "Error sending playlist", "name", pls.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
3
ui/src/consts.js
Normal file
3
ui/src/consts.js
Normal file
@ -0,0 +1,3 @@
|
||||
export const REST_URL = '/app/api'
|
||||
|
||||
export const M3U_MIME_TYPE = 'audio/x-mpegurl'
|
@ -1,3 +1,6 @@
|
||||
import httpClient from './httpClient'
|
||||
import wrapperDataProvider from './wrapperDataProvider'
|
||||
|
||||
export { httpClient }
|
||||
|
||||
export default wrapperDataProvider
|
||||
|
@ -1,9 +1,8 @@
|
||||
import jsonServerProvider from 'ra-data-json-server'
|
||||
import httpClient from './httpClient'
|
||||
import { REST_URL } from '../consts'
|
||||
|
||||
const restUrl = '/app/api'
|
||||
|
||||
const dataProvider = jsonServerProvider(restUrl, httpClient)
|
||||
const dataProvider = jsonServerProvider(REST_URL, httpClient)
|
||||
|
||||
const mapResource = (resource, params) => {
|
||||
switch (resource) {
|
||||
|
@ -85,7 +85,8 @@
|
||||
},
|
||||
"actions": {
|
||||
"selectPlaylist": "Select a playlist:",
|
||||
"addNewPlaylist": "Create \"%{name}\""
|
||||
"addNewPlaylist": "Create \"%{name}\"",
|
||||
"export": "Export"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
|
@ -9,7 +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 QueueMusicIcon from '@material-ui/icons/QueueMusic'
|
||||
import { httpClient } from '../dataProvider'
|
||||
import { playTracks, shuffleTracks } from '../audioplayer'
|
||||
import { M3U_MIME_TYPE, REST_URL } from '../consts'
|
||||
import subsonic from '../subsonic'
|
||||
|
||||
const PlaylistActions = ({
|
||||
@ -18,38 +21,68 @@ const PlaylistActions = ({
|
||||
data,
|
||||
exporter,
|
||||
permanentFilter,
|
||||
playlistId,
|
||||
record,
|
||||
...rest
|
||||
}) => {
|
||||
const dispatch = useDispatch()
|
||||
const translate = useTranslate()
|
||||
|
||||
const handlePlay = React.useCallback(() => {
|
||||
dispatch(playTracks(data, ids))
|
||||
}, [dispatch, data, ids])
|
||||
|
||||
const handleShuffle = React.useCallback(() => {
|
||||
dispatch(shuffleTracks(data, ids))
|
||||
}, [dispatch, data, ids])
|
||||
|
||||
const handleDownload = React.useCallback(() => {
|
||||
subsonic.download(record.id)
|
||||
}, [record])
|
||||
|
||||
const handleExport = React.useCallback(
|
||||
() =>
|
||||
httpClient(`${REST_URL}/playlist/${record.id}/tracks`, {
|
||||
headers: new Headers({ Accept: M3U_MIME_TYPE }),
|
||||
}).then((res) => {
|
||||
console.log(res)
|
||||
const blob = new Blob([res.body], { type: M3U_MIME_TYPE })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `${record.name}.m3u`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
link.parentNode.removeChild(link)
|
||||
}),
|
||||
[record]
|
||||
)
|
||||
|
||||
return (
|
||||
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
dispatch(playTracks(data, ids))
|
||||
}}
|
||||
onClick={handlePlay}
|
||||
label={translate('resources.album.actions.playAll')}
|
||||
>
|
||||
<PlayArrowIcon />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
dispatch(shuffleTracks(data, ids))
|
||||
}}
|
||||
onClick={handleShuffle}
|
||||
label={translate('resources.album.actions.shuffle')}
|
||||
>
|
||||
<ShuffleIcon />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
subsonic.download(playlistId)
|
||||
}}
|
||||
onClick={handleDownload}
|
||||
label={translate('resources.album.actions.download')}
|
||||
>
|
||||
<CloudDownloadOutlinedIcon />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleExport}
|
||||
label={translate('resources.playlist.actions.export')}
|
||||
>
|
||||
<QueueMusicIcon />
|
||||
</Button>
|
||||
</TopToolbar>
|
||||
)
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ const PlaylistShow = (props) => {
|
||||
playlistId={props.id}
|
||||
readOnly={isReadOnly(record && record.owner)}
|
||||
title={<Title subTitle={record && record.name} />}
|
||||
actions={<PlaylistActions playlistId={props.id} />}
|
||||
actions={<PlaylistActions record={record} />}
|
||||
filter={{ playlist_id: props.id }}
|
||||
resource={'playlistTrack'}
|
||||
exporter={false}
|
||||
|
Loading…
x
Reference in New Issue
Block a user