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(){