Skip to content

Commit

Permalink
Add export as m3u button to playlist
Browse files Browse the repository at this point in the history
  • Loading branch information
deluan committed Aug 22, 2020
1 parent 366054e commit 9df405a
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 18 deletions.
3 changes: 2 additions & 1 deletion resources/i18n/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@
},
"actions": {
"selectPlaylist": "Selecione a playlist:",
"addNewPlaylist": "Criar \"%{name}\""
"addNewPlaylist": "Criar \"%{name}\"",
"export": "Exportar"
}
}
},
Expand Down
12 changes: 10 additions & 2 deletions server/app/playlists.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions ui/src/consts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const REST_URL = '/app/api'

export const M3U_MIME_TYPE = 'audio/x-mpegurl'
3 changes: 3 additions & 0 deletions ui/src/dataProvider/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import httpClient from './httpClient'
import wrapperDataProvider from './wrapperDataProvider'

export { httpClient }

export default wrapperDataProvider
5 changes: 2 additions & 3 deletions ui/src/dataProvider/wrapperDataProvider.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion ui/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@
},
"actions": {
"selectPlaylist": "Select a playlist:",
"addNewPlaylist": "Create \"%{name}\""
"addNewPlaylist": "Create \"%{name}\"",
"export": "Export"
}
},
"user": {
Expand Down
53 changes: 43 additions & 10 deletions ui/src/playlist/PlaylistActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ({
Expand All @@ -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>
)
}
Expand Down
2 changes: 1 addition & 1 deletion ui/src/playlist/PlaylistShow.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down

0 comments on commit 9df405a

Please sign in to comment.