POC for playlist queueing

This commit is contained in:
James Barnsley
2019-03-18 16:26:16 +13:00
parent f824cca396
commit 5aa932d045
10 changed files with 5492 additions and 5372 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -98,7 +98,7 @@
// Release details
// These are automatically injected to built HTML
var build = "1552850454";
var build = "1552871534";
var version = "3.33.0";
// Construct the script tag

View File

@ -218,16 +218,12 @@ class ContextMenu extends React.Component{
playPlaylist(e){
this.props.uiActions.hideContextMenu();
// THis is the ideal setup: one action to play whatever type
// then the action detects playlist vs album vs artist vs track etc and
// loads as is required... then enqueue works the same
this.props.mopidyActions.playURIs(this.props.menu.uris[0], this.props.menu.uris[0]);
this.props.mopidyActions.playPlaylist(this.props.menu.uris[0]);
}
enqueuePlaylist(e){
this.props.uiActions.hideContextMenu();
this.props.mopidyActions.playPlaylist(this.props.menu.uris[0]);
this.props.mopidyActions.enqueuePlaylist(this.props.menu.uris[0]);
}
shufflePlayPlaylist(e){
@ -240,9 +236,9 @@ class ContextMenu extends React.Component{
this.props.spotifyActions.playArtistTopTracks(this.props.menu.uris[0]);
}
addToQueue(e, next = false){
addToQueue(e, play_next = false){
this.props.uiActions.hideContextMenu();
this.props.mopidyActions.enqueueURIs(this.props.menu.uris, this.props.menu.tracklist_uri, next);
this.props.mopidyActions.enqueueURIs(this.props.menu.uris, this.props.menu.tracklist_uri, play_next);
}
addTracksToPlaylist(e, playlist_uri){
@ -516,6 +512,14 @@ class ContextMenu extends React.Component{
</div>
)
var add_playlist_to_queue = (
<div className="context-menu__item">
<a className="context-menu__item__link" onClick={e => this.enqueuePlaylist(e)}>
<span className="context-menu__item__label">Add to queue</span>
</a>
</div>
)
var add_to_playlist = (
<div className="context-menu__item context-menu__item--has-submenu">
<a className="context-menu__item__link" onClick={e => this.setSubmenu('add-to-playlist')}>
@ -699,6 +703,7 @@ class ContextMenu extends React.Component{
<div>
{play_playlist}
{shuffle_play_playlist}
{add_playlist_to_queue}
{this.canBeInLibrary() ? <div className="context-menu__divider" /> : null}
{this.canBeInLibrary() ? toggle_in_library : null}
<div className="context-menu__divider" />

View File

@ -231,48 +231,6 @@ export function removeFromIndex(index_name, key, new_key = null){
/**
* Playing of assetse
**/
/**
* Play one or many URIs
*
* @param uri = String (when playing just one URI, eg playlist)
* @param uris = Array (when playing several URIs, eg tracks)
* @param origin = String (URI from which these URIs came)
* @param shuffle = Boolean (shuffle the order of URIs)
**/
export function playURIs(uri = null, uris = [], origin = null, shuffle = false){
return {
type: 'PLAY_URIS',
uri: uri,
uris: uris,
origin: origin,
shuffle: shuffle
}
}
/**
* Enqueue one or many URIs
*
* @param uris = Array
* @param position = Int (position in track_list in which to insert these URIs)
* @param origin = String (URI from which these URIs came)
* @param shuffle = Boolean (shuffle the order of URIs)
**/
export function enqueueURIs(uris = [], position = null, origin = null, shuffle = false){
return {
type: 'ENQUEUE_URIS',
position: position,
uris: uris,
origin: origin,
shuffle: shuffle
}
}
/**
* Playlist manipulation
**/

View File

@ -176,21 +176,6 @@ const CoreMiddleware = (function(){
break
case 'PLAYLIST_TRACKS_ADDED':
store.dispatch(uiActions.createNotification({type: 'info', content: 'Added '+action.tracks_uris.length+' tracks to playlist'}))
switch(helpers.uriSource(action.key)){
case 'spotify':
store.dispatch(spotifyActions.getPlaylist(action.key));
break
case 'm3u':
if (store.getState().mopidy.connected ) store.dispatch(mopidyActions.getPlaylist(action.key));
break
}
next(action)
break
// Get assets from all of our providers
case 'GET_LIBRARY_PLAYLISTS':
if (store.getState().spotify.connected){
@ -245,6 +230,21 @@ const CoreMiddleware = (function(){
next(action);
break;
case 'PLAYLIST_TRACKS_ADDED':
store.dispatch(uiActions.createNotification({type: 'info', content: 'Added '+action.tracks_uris.length+' tracks to playlist'}))
switch(helpers.uriSource(action.key)){
case 'spotify':
store.dispatch(spotifyActions.getPlaylist(action.key));
break
case 'm3u':
if (store.getState().mopidy.connected ) store.dispatch(mopidyActions.getPlaylist(action.key));
break
}
next(action)
break
case 'PLAYLIST_TRACKS_REORDERED':
var playlists = Object.assign({}, core.playlists);
var playlist = Object.assign({}, playlists[action.key]);

View File

@ -255,7 +255,11 @@ export function changeTrack(tlid){
}
}
export function playURIs(uris, from_uri = null, shuffle = false){
/**
* Playing assets
**/
export function playURIs(uris = [], from_uri = null, shuffle = false){
return {
type: 'MOPIDY_PLAY_URIS',
uris: uris,
@ -263,23 +267,6 @@ export function playURIs(uris, from_uri = null, shuffle = false){
}
}
export function enqueueURIs(uris, from_uri = null, next = false, at_position = null, offset = 0){
return {
type: 'MOPIDY_ENQUEUE_URIS',
uris: uris,
at_position: at_position,
next: next,
from_uri: from_uri,
offset: offset
}
}
export function enqueueURIsBatchDone(){
return {
type: 'MOPIDY_ENQUEUE_URIS_BATCH_DONE'
}
}
export function playAlbum(uri){
return {
type: 'MOPIDY_PLAY_ALBUM',
@ -287,6 +274,53 @@ export function playAlbum(uri){
}
}
export function playPlaylist(uri){
return {
type: 'MOPIDY_PLAY_PLAYLIST',
uri: uri
}
}
export function enqueueURIs(uris = [], from_uri = null, play_next = false, at_position = null, offset = 0){
return {
type: 'MOPIDY_ENQUEUE_URIS',
uris: uris,
from_uri: from_uri,
at_position: at_position,
play_next: play_next,
offset: offset
}
}
export function enqueueAlbum(uri, next = false, at_position = null){
return {
type: 'MOPIDY_ENQUEUE_ALBUM',
uri: uri,
next: next,
at_position: at_position
}
}
export function enqueuePlaylist(uri, play_next = false, at_position = null){
return {
type: 'MOPIDY_ENQUEUE_PLAYLIST',
uri: uri,
play_next: play_next,
at_position: at_position
}
}
export function enqueueURIsBatchDone(){
return {
type: 'MOPIDY_ENQUEUE_URIS_BATCH_DONE'
}
}
/**
* Playlist maniopulation
**/
export function removeTracks(tlids){
return {
type: 'MOPIDY_REMOVE_TRACKS',

View File

@ -597,31 +597,26 @@ const MopidyMiddleware = (function(){
case 'MOPIDY_PLAY_PLAYLIST':
// Clear tracklist (if set)
if (store.getState().ui.clear_tracklist_on_play){
store.dispatch(mopidyActions.clearTracklist())
}
var playlist = store.getState().core.playlists[action.uri];
// playlist already in index
if (store.getState().core.playlists.hasOwnProperty(action.uri)){
// We have the playlist loaded already, and we've got at least 1 track to start playing
if (playlist && playlist.tracks_uris && playlist.tracks_uris.length > 0){
// Spotify-provied playlists need to be handled by the Spotify service
if (store.getState().core.playlists[action.uri].provider == 'spotify'){
store.dispatch(spotifyActions.getPlaylistTracksAndPlay(action.uri, action.shuffle))
break
// We only need to load them if we haven't already got all the tracks
if (playlist.provider == 'spotify' && playlist.tracks_total == playlist.tracks_uris.length){
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'play'));
break;
}
// it's a spotify playlist that we haven't loaded
// we need to fetch via HTTP API to avoid timeout
// It's a Spotify playlist that we haven't loaded yet, so Spotify HTTP API needs to go get it
} else if (helpers.uriSource(action.uri) == 'spotify' && store.getState().spotify.enabled){
store.dispatch(spotifyActions.getPlaylistTracksAndPlay(action.uri, action.shuffle))
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'play'));
break
// Not in index, and Spotify HTTP not enabled, so just play it as-is
}
// fetch the playlist tracks via backend
// add each track by URI
// Not in index, and Spotify HTTP not enabled, so just play it as-is
// Fetch the playlist tracks via backend and add each track by URI
request(socket, store, 'playlists.lookup', {uri: action.uri})
.then(
response => {
@ -644,6 +639,51 @@ const MopidyMiddleware = (function(){
);
break;
case 'MOPIDY_ENQUEUE_PLAYLIST':
console.log(action);
var playlist = store.getState().core.playlists[action.uri];
// We have the playlist loaded already, and we've got at least 1 track to start playing
if (playlist && playlist.tracks_uris && playlist.tracks_uris.length > 0){
// Spotify-provied playlists need to be handled by the Spotify service
// We only need to load them if we haven't already got all the tracks
if (playlist.provider == 'spotify' && playlist.tracks_total == playlist.tracks_uris.length){
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'enqueue'));
break;
}
// It's a Spotify playlist that we haven't loaded yet, so Spotify HTTP API needs to go get it
} else if (helpers.uriSource(action.uri) == 'spotify' && store.getState().spotify.enabled){
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'enqueue'));
break
}
// Not in index, and Spotify HTTP not enabled, so just play it as-is
// Fetch the playlist tracks via backend and add each track by URI
request(socket, store, 'playlists.lookup', {uri: action.uri})
.then(
response => {
if (response.tracks === undefined){
store.dispatch(uiActions.createNotification({content: 'Failed to load playlist tracks', type: 'bad'}));
} else {
var tracks_uris = helpers.arrayOf('uri',response.tracks);
if (action.shuffle){
tracks_uris = helpers.shuffle(tracks_uris);
}
store.dispatch(mopidyActions.enqueueURIs(tracks_uris, action.uri, action.play_next, action.at_position, action.offset))
}
},
error => {
store.dispatch(coreActions.handleException(
"Mopidy: "+(error.message ? error.message : "Lookup failed"),
error
));
}
);
break;
case 'MOPIDY_ENQUEUE_URIS':
if (!action.uris || action.uris.length <= 0){
@ -669,15 +709,15 @@ const MopidyMiddleware = (function(){
batches.push({
uris: uris.splice(0,batch_size),
at_position: action.at_position,
next: action.next,
play_next: action.play_next,
offset: action.offset + (batch_size * batches.length),
from_uri: action.from_uri
})
}
// pass this modified action to the reducer (and other middleware)
action.batches = batches
next(action)
action.batches = batches;
next(action);
// start our processor
store.dispatch(uiActions.startProcess(
@ -703,7 +743,7 @@ const MopidyMiddleware = (function(){
// make sure we have some uris in the queue
} else if (action.data.batches && action.data.batches.length > 0){
var batches = Object.assign([],action.data.batches)
var batches = Object.assign([], action.data.batches)
var batch = batches[0]
var total_uris = 0
for (var i = 0; i < batches.length; i++){
@ -740,7 +780,7 @@ const MopidyMiddleware = (function(){
var params = {uris: batch.uris}
// Play this batch next
if (batch.next){
if (batch.play_next){
// Make sure we're playing something first
if (current_track_index > -1){

View File

@ -1634,32 +1634,36 @@ export function getLibraryTracksAndPlayProcessor(data){
* Recursively get .next until we have all tracks
**/
export function getPlaylistTracksAndPlay(uri, shuffle){
export function getAllPlaylistTracks(uri, shuffle = false, callback_action = null, play_next = false, at_position = null, offset = 0){
return (dispatch, getState) => {
dispatch(uiActions.startProcess(
'SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR',
'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR',
'Loading playlist tracks',
{
uri: uri,
next: 'playlists/'+ helpers.getFromUri('playlistid',uri) +'/tracks?market='+getState().spotify.country,
shuffle: shuffle
shuffle: shuffle,
play_next: play_next,
at_position: at_position,
offset: offset,
callback_action: callback_action
}
))
}
}
export function getPlaylistTracksAndPlayProcessor(data){
export function getAllPlaylistTracksProcessor(data){
return (dispatch, getState) => {
request(dispatch, getState, data.next)
.then(
response => {
// Check to see if we've been cancelled
if (getState().ui.processes['SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR'] !== undefined){
var processor = getState().ui.processes['SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR']
if (getState().ui.processes['SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR'] !== undefined){
var processor = getState().ui.processes['SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR']
if (processor.status == 'cancelling'){
dispatch(uiActions.processCancelled('SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR'))
dispatch(uiActions.processCancelled('SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR'))
return false
}
}
@ -1679,31 +1683,41 @@ export function getPlaylistTracksAndPlayProcessor(data){
// We got a next link, so we've got more work to be done
if (response.next){
dispatch(uiActions.updateProcess(
'SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR',
'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR',
'Loading '+(response.total-uris.length)+' playlist tracks',
{
next: response.next,
total: response.total,
remaining: response.total - uris.length,
shuffle: data.shuffle
}
))
Object.assign(
{},
data,
{
next: response.next,
total: response.total,
remaining: response.total - uris.length
}
)
));
dispatch(uiActions.runProcess(
'SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR',
{
next: response.next,
uris: uris,
shuffle: data.shuffle
}
))
'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR',
Object.assign(
{},
data,
{
next: response.next,
uris: uris,
}
)
));
} else {
if (data.shuffle){
uris = helpers.shuffle(uris);
}
dispatch(mopidyActions.playURIs(uris, data.uri))
dispatch(uiActions.processFinishing('SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR'))
if (data.callback_action == 'enqueue'){
dispatch(mopidyActions.enqueueURIs(uris, data.uri, data.play_next, data.at_position, data.offset));
} else {
dispatch(mopidyActions.playURIs(uris, data.uri));
}
dispatch(uiActions.processFinishing('SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR'))
}
},
error => {

View File

@ -305,14 +305,14 @@ const SpotifyMiddleware = (function(){
store.dispatch(spotifyActions.getLibraryAlbumsProcessor(action.data))
break
case 'SPOTIFY_GET_PLAYLIST_TRACKS_AND_PLAY_PROCESSOR':
store.dispatch(spotifyActions.getPlaylistTracksAndPlayProcessor(action.data))
break
case 'SPOTIFY_GET_LIBRARY_TRACKS_AND_PLAY_PROCESSOR':
store.dispatch(spotifyActions.getLibraryTracksAndPlayProcessor(action.data))
break
case 'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR':
store.dispatch(spotifyActions.getAllPlaylistTracksProcessor(action.data))
break
case 'SPOTIFY_LIBRARY_ALBUMS_LOADED':
var albums = []
for (var i = 0; i < action.albums.length; i++){