- {play_uris}
+ {play_playlist}
{this.canBeInLibrary() ? toggle_in_library : null}
{context.source == 'spotify' ? go_to_user : null}
{copy_uris}
diff --git a/src/js/components/DebugInfo.js b/src/js/components/DebugInfo.js
index b4d965fc..1716ff51 100755
--- a/src/js/components/DebugInfo.js
+++ b/src/js/components/DebugInfo.js
@@ -74,12 +74,15 @@ class DebugInfo extends React.Component{
Notifications: {this.props.ui.notifications ? Object.keys(this.props.ui.notifications).length : '0'}
+
+ Processes: {this.props.ui.processes ? Object.keys(this.props.ui.processes).length : '0'}
+
_testMode: {window._testMode ? 'on' : 'off'}
- Touch: {helpers.isTouchDevice() ? 'yes' : 'no'}
+ Touch: {helpers.isTouchDevice() ? 'on' : 'off'}
{this.renderLoadQueue()}
diff --git a/src/js/components/Notifications.js b/src/js/components/Notifications.js
index 37ccc332..d3f23002 100755
--- a/src/js/components/Notifications.js
+++ b/src/js/components/Notifications.js
@@ -11,25 +11,65 @@ export default class Notifications extends React.Component{
renderNotifications(){
if (!this.props.notifications || this.props.notifications.length <= 0) return null
- // we only care about the last notification
- var notification = this.props.notifications[this.props.notifications.length-1]
+ return (
+
{this.renderNotifications()}
- {this.renderLoader()}
+ {this.renderProcesses()}
)
}
diff --git a/src/js/helpers.js b/src/js/helpers.js
index e282c8c9..a1573e08 100755
--- a/src/js/helpers.js
+++ b/src/js/helpers.js
@@ -266,7 +266,7 @@ export let asURIs = function(items){
* Merge duplicated items in an array
*
* @param list Array the unclean array
- * @param key string = the unique key (uri, tlid, etc)
+ * @param key string = the unique key (id, uri, tlid, etc)
**/
export let mergeDuplicates = function(list, key){
var clean_list = [];
diff --git a/src/js/services/mopidy/actions.js b/src/js/services/mopidy/actions.js
index 00c2f2bf..ab78b5cb 100755
--- a/src/js/services/mopidy/actions.js
+++ b/src/js/services/mopidy/actions.js
@@ -57,24 +57,30 @@ export function playURIs( uris, from_uri = null ){
}
}
-export function enqueueURIsNext( uris, from_uri = null ){
- if( typeof(uris) !== 'object' ) uris = [uris]
- return {
- type: 'MOPIDY_ENQUEUE_URIS_NEXT',
- uris: uris,
- from_uri: from_uri
- }
-}
-
-export function enqueueURIs( uris, from_uri = null, at_position = false ){
+export function enqueueURIs( uris, from_uri = null, next = false, at_position = null ){
return {
type: 'MOPIDY_ENQUEUE_URIS',
uris: uris,
at_position: at_position,
+ next: next,
from_uri: from_uri
}
}
+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 e75a3c06..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,38 +220,46 @@ const MopidyMiddleware = (function(){
icon: (store.getState().ui.current_track ? helpers.getTrackIcon( store.getState().ui.current_track ) : false)
}
store.dispatch( pusherActions.deliverBroadcast(data) )
- break;
-
- case 'MOPIDY_ENQUEUE_URIS':
-
- // create batches of 20 uris to load
- var all_uris = Object.assign([], action.uris)
- var batched_uris = []
- while (all_uris.length > 0){
- batched_uris.push( all_uris.splice(0,20) )
- }
-
- // run each batch
- for (var i = 0; i < batched_uris.length; i++){
- var value = { uris: batched_uris[i] }
- if (action.at_position){
- value.at_position = action.at_position
- }
-
- instruct( socket, store, 'tracklist.add', value )
- .then( response => {
- var tlids = []
- for (var i = 0; i < response.length; i++){
- tlids.push(response[i].tlid)
- }
- store.dispatch( pusherActions.addQueueMetadata(tlids, action.from_uri) )
- console.info('Added '+tlids.length+' URI(s) to queue')
- })
- }
break
- case 'MOPIDY_ENQUEUE_URIS_NEXT':
+ 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
+ var remaining_uris = Object.assign([], action.uris)
var current_track = store.getState().ui.current_track
var current_tracklist = store.getState().ui.current_tracklist
var current_track_index = -1
@@ -264,18 +273,64 @@ const MopidyMiddleware = (function(){
}
}
- var at_position = null
- if( current_track_index > -1 ) at_position = current_track_index + 1
+ let process_batch = function(){
- instruct( socket, store, 'tracklist.add', { uris: action.uris, at_position: at_position } )
- .then( response => {
- var tlids = []
- for (var i = 0; i < response.length; i++){
- tlids.push(response[i].tlid)
+ // process is not running
+ if (store.getState().ui.processes && store.getState().ui.processes[action.type]){
+ if (store.getState().ui.processes[action.type].cancelling){
+ // recognise as cancelled
+ store.dispatch(uiActions.stopProcess(action.type))
+ return false
}
- store.dispatch( pusherActions.addQueueMetadata(tlids, action.from_uri) )
- store.dispatch( uiActions.createNotification('Added '+tlids.length+' URI(s) to queue') )
- })
+ } else {
+ return false
+ }
+
+ // update our process details
+ store.dispatch(uiActions.startProcess(action.type, 'Adding '+remaining_uris.length+' URI(s)'))
+
+ var params = {uris: remaining_uris.splice(0,5)}
+ if (action.next && current_track_index > -1){
+ params.at_position = current_track_index + uris_added + 1
+ } else if (action.at_position){
+ params.at_position = action.at_position
+ }
+
+ instruct(socket, store, 'tracklist.add', params)
+ .then( response => {
+
+ // append our counter
+ uris_added += response.length
+
+ // add metadata to queue
+ var tlids = []
+ for (var i = 0; i < response.length; i++){
+ tlids.push(response[i].tlid)
+ }
+ store.dispatch(pusherActions.addQueueMetadata(tlids, action.from_uri))
+
+ // 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){
+ setTimeout(
+ function(){
+ process_batch()
+ },
+ 100
+ )
+
+ // all done
+ } else {
+ store.dispatch(uiActions.stopProcess(action.type))
+ }
+ })
+ }
+
+ // start processing
+ store.dispatch(uiActions.startProcess(action.type, 'Adding '+remaining_uris.length+' URI(s)'))
+ process_batch()
+
break
case 'MOPIDY_PLAY_URIS':
@@ -285,29 +340,37 @@ const MopidyMiddleware = (function(){
store.dispatch( pusherActions.stopRadio() )
}
- // add our first track
- instruct( socket, store, 'tracklist.add', { uri: action.uris[0], at_position: 0 } )
- .then( response => {
+ var first_uri = action.uris[0]
- // 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 ) );
+ // add our first track
+ instruct(socket, store, 'tracklist.add', { uri: first_uri, at_position: 0 })
+ .then(response => {
+
+ // 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;
@@ -453,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 )
}
@@ -477,6 +541,7 @@ const MopidyMiddleware = (function(){
response,
{
type: 'playlist',
+ is_mopidy: true,
tracks: ( response.tracks ? response.tracks : [] ),
tracks_total: ( response.tracks ? response.tracks.length : [] )
}
@@ -701,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
@@ -729,6 +795,7 @@ const MopidyMiddleware = (function(){
{ images: [] },
response[0].album,
{
+ is_mopidy: true,
artists: response[0].artists,
tracks: response,
tracks_total: response.length
@@ -832,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 ba3a2ff3..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
@@ -919,6 +919,7 @@ export function getPlaylist( uri ){
description = response.description
description = description.split('