diff --git a/src/js/components/ContextMenu.js b/src/js/components/ContextMenu.js index 56e55114..fe2a9391 100755 --- a/src/js/components/ContextMenu.js +++ b/src/js/components/ContextMenu.js @@ -169,6 +169,11 @@ class ContextMenu extends React.Component{ this.props.mopidyActions.playURIs(this.props.menu.uris, this.props.menu.tracklist_uri) } + playPlaylist(e){ + this.props.uiActions.hideContextMenu() + this.props.mopidyActions.playPlaylist(this.props.menu.uris[0]) + } + addToQueue(e, next = false){ this.props.uiActions.hideContextMenu() this.props.mopidyActions.enqueueURIs(this.props.menu.uris, this.props.menu.tracklist_uri, next) @@ -326,6 +331,14 @@ class ContextMenu extends React.Component{ ) + var play_playlist = ( + + this.playPlaylist(e)}> + Play + + + ) + var play_queue_item = ( this.playQueueItem(e)}> @@ -446,7 +459,7 @@ class ContextMenu extends React.Component{ case 'playlist': return (
- {play_uris} + {play_playlist} {this.canBeInLibrary() ? toggle_in_library : null} {context.source == 'spotify' ? go_to_user : null} {copy_uris} diff --git a/src/js/services/mopidy/actions.js b/src/js/services/mopidy/actions.js index 847da96f..ab78b5cb 100755 --- a/src/js/services/mopidy/actions.js +++ b/src/js/services/mopidy/actions.js @@ -67,6 +67,20 @@ export function enqueueURIs( uris, from_uri = null, next = false, at_position = } } +export function playPlaylist(uri){ + return { + type: 'MOPIDY_PLAY_PLAYLIST', + uri: uri + } +} + +export function playAlbum(uri){ + return { + type: 'MOPIDY_PLAY_ALBUM', + uri: uri + } +} + export function removeTracks( tlids ){ return { type: 'MOPIDY_INSTRUCT', diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js index 3155e213..92da4354 100755 --- a/src/js/services/mopidy/middleware.js +++ b/src/js/services/mopidy/middleware.js @@ -4,8 +4,9 @@ import * as helpers from '../../helpers' var mopidyActions = require('./actions.js') var uiActions = require('../ui/actions.js') -var lastfmActions = require('../lastfm/actions.js') +var spotifyActions = require('../spotify/actions.js') var pusherActions = require('../pusher/actions.js') +var lastfmActions = require('../lastfm/actions.js') const MopidyMiddleware = (function(){ @@ -219,7 +220,42 @@ const MopidyMiddleware = (function(){ icon: (store.getState().ui.current_track ? helpers.getTrackIcon( store.getState().ui.current_track ) : false) } store.dispatch( pusherActions.deliverBroadcast(data) ) - break; + break + + case 'MOPIDY_PLAY_PLAYLIST': + if (helpers.uriSource(action.uri) == 'spotify'){ + + // playlist already in index + if (store.getState().ui.playlists.hasOwnProperty(action.uri)){ + + // make sure we didn't get this playlist from Mopidy-Spotify + // if we did, we'd have a cached version on server so no need to fetch + if (!store.getState().ui.playlists[action.uri].is_mopidy){ + store.dispatch(uiActions.startProcess('MOPIDY_ENQUEUE_URIS', 'Fetching tracks')) + store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri)) + break + } + + // not loaded, so we need to fetch + } else { + store.dispatch(uiActions.startProcess('MOPIDY_ENQUEUE_URIS', 'Fetching tracks')) + store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri)) + break + } + } + + // default to load it as per usual + store.dispatch(mopidyActions.playURIs([action.uri], action.uri)) + + break + + case 'SPOTIFY_ALL_PLAYLIST_TRACKS_LOADED_FOR_PLAYING': + var uris = [] + for (var i = 0; i < action.tracks.length; i++){ + uris.push(action.tracks[i].track.uri) + } + store.dispatch(mopidyActions.playURIs(uris, action.uri)) + break case 'MOPIDY_ENQUEUE_URIS': var uris_added = 0 @@ -273,7 +309,7 @@ const MopidyMiddleware = (function(){ } store.dispatch(pusherActions.addQueueMetadata(tlids, action.from_uri)) - // still more URIs? run again in 200ms + // still more URIs? run again in 100ms // this gives our server time to handle other requests // crude, but prevents locking the server if (remaining_uris.length > 0){ @@ -281,7 +317,7 @@ const MopidyMiddleware = (function(){ function(){ process_batch() }, - 200 + 100 ) // all done @@ -306,45 +342,35 @@ const MopidyMiddleware = (function(){ var first_uri = action.uris[0] - // spotify playlist and albums are handled differently - if (helpers.uriSource(first_uri) == 'spotify'){ - switch (helpers.uriType(first_uri)){ - case 'playlist': - // TODO - // trigger loading of all the playlist tracks - // once loaded, re-run play uris - // maybe create new play_album play_playlist actions to handle - // as we'd then reuse play_uris for the loaded track uris - break - - case 'album': - break - } - } - // add our first track - instruct( socket, store, 'tracklist.add', { uri: first_uri, at_position: 0 } ) - .then( response => { + instruct(socket, store, 'tracklist.add', { uri: first_uri, at_position: 0 }) + .then(response => { - // treat empty response as a failed lookup - if( !response || response.length <= 0 ){ - store.dispatch( uiActions.createNotification('Failed to load URI(s)', 'bad') ) - console.error(action) - }else{ - // play it - store.dispatch( mopidyActions.changeTrack( response[0].tlid ) ); + // play it (only if we got a successful lookup) + if (response.length > 0){ + store.dispatch(mopidyActions.changeTrack(response[0].tlid)); var tlids = [] for (var i = 0; i < response.length; i++){ tlids.push(response[i].tlid) } - store.dispatch( pusherActions.addQueueMetadata(tlids, action.from_uri) ) + store.dispatch(pusherActions.addQueueMetadata(tlids, action.from_uri)) + } else { + store.dispatch(uiActions.createNotification('Failed to add some URI(s)', 'bad')) + } - // add the rest of our uris (if any) - action.uris.shift(); - if( action.uris.length > 0 ){ - store.dispatch( mopidyActions.enqueueURIs( action.uris, action.from_uri, 1 ) ) - } + // add the rest of our uris (if any) + action.uris.shift(); + if( action.uris.length > 0 ){ + + // wait 100ms so the server can trigger track_changed etc + // this means our UI feels snappier as the first track shows up quickly + setTimeout( + function(){ + store.dispatch(mopidyActions.enqueueURIs( action.uris, action.from_uri, 1 )) + }, + 100 + ) } }) break; @@ -490,7 +516,8 @@ const MopidyMiddleware = (function(){ type: 'playlist', name: response.name, uri: response.uri, - source: (source == 'spotify' ? 'local' : source), + source: source, + is_mopidy: true, last_modified: response.last_modified, tracks_total: ( response.tracks ? response.tracks.length : 0 ) } @@ -514,6 +541,7 @@ const MopidyMiddleware = (function(){ response, { type: 'playlist', + is_mopidy: true, tracks: ( response.tracks ? response.tracks : [] ), tracks_total: ( response.tracks ? response.tracks.length : [] ) } @@ -738,6 +766,7 @@ const MopidyMiddleware = (function(){ var album = Object.assign( {}, { + is_mopidy: true, artists: response[uri][0].artists, tracks: response[uri], tracks_total: response[uri].length @@ -766,6 +795,7 @@ const MopidyMiddleware = (function(){ { images: [] }, response[0].album, { + is_mopidy: true, artists: response[0].artists, tracks: response, tracks_total: response.length @@ -869,6 +899,7 @@ const MopidyMiddleware = (function(){ {}, (response ? response[0].artists[0] : {}), { + is_mopidy: true, albums_uris: helpers.asURIs(albums), tracks: response.slice(0,10) } diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js index fb2ea24c..2d215e7b 100755 --- a/src/js/services/spotify/actions.js +++ b/src/js/services/spotify/actions.js @@ -890,7 +890,7 @@ export function createPlaylist( name, is_public ){ } } -export function savePlaylist( uri, name, is_public ){ +export function savePlaylist(uri, name, is_public){ return (dispatch, getState) => { sendRequest( dispatch, getState, 'users/'+ getState().spotify.me.id +'/playlists/'+ helpers.getFromUri('playlistid',uri), 'PUT', { name: name, public: is_public } ) @@ -906,7 +906,7 @@ export function savePlaylist( uri, name, is_public ){ } } -export function getPlaylist( uri ){ +export function getPlaylist(uri){ return (dispatch, getState) => { // get the main playlist object @@ -943,7 +943,44 @@ export function getPlaylist( uri ){ } } -function loadNextPlaylistsBatch( dispatch, getState, playlists, lastResponse ){ +/** + * Get all tracks for a playlist + * + * Recursively get .next until we have all tracks + **/ +function loadNextPlaylistTracksBatch(dispatch, getState, uri, tracks, lastResponse){ + if( lastResponse.next ){ + sendRequest(dispatch, getState, lastResponse.next) + .then( response => { + tracks = [...tracks, ...response.items] + loadNextPlaylistTracksBatch(dispatch, getState, uri, tracks, response) + }); + }else{ + dispatch({ + type: 'SPOTIFY_ALL_PLAYLIST_TRACKS_LOADED_FOR_PLAYING', + uri: uri, + tracks: tracks + }); + } +} + +export function getAllPlaylistTracks(uri){ + return (dispatch, getState) => { + sendRequest(dispatch, getState, 'users/'+ helpers.getFromUri('userid',uri) +'/playlists/'+ helpers.getFromUri('playlistid',uri) +'/tracks?market='+getState().spotify.country) + .then( response => { + loadNextPlaylistTracksBatch(dispatch, getState, uri, response.items, response) + }); + } +} + + +/** + * Get all of our playlists in one foul swoop + * + * Recursively gets playlists until no more .next value. Fails when user has + * a lot of playlists, and we hit the API limits + **/ +function loadNextPlaylistsBatch(dispatch, getState, playlists, lastResponse){ if( lastResponse.next ){ sendRequest( dispatch, getState, lastResponse.next ) .then( response => { @@ -967,7 +1004,7 @@ export function getAllLibraryPlaylists(){ } } -export function toggleFollowingPlaylist( uri, method ){ +export function toggleFollowingPlaylist(uri, method){ if( method == 'PUT' ) var new_state = 1 if( method == 'DELETE' ) var new_state = 0 diff --git a/src/js/views/Playlist.js b/src/js/views/Playlist.js index e023d27c..dc7489d0 100755 --- a/src/js/views/Playlist.js +++ b/src/js/views/Playlist.js @@ -70,7 +70,7 @@ class Playlist extends React.Component{ play(){ ReactGA.event({ category: 'Playlist', action: 'Play', label: this.props.playlist.uri }) - this.props.mopidyActions.playURIs([this.props.playlist.uri], this.props.params.uri) + this.props.mopidyActions.playPlaylist(this.props.playlist.uri) } follow(){