2016-10-14 22:42:45 +13:00
|
|
|
|
|
|
|
|
import Mopidy from 'mopidy'
|
2016-10-20 21:38:01 +13:00
|
|
|
var actions = require('./actions.js')
|
2016-10-14 22:42:45 +13:00
|
|
|
|
|
|
|
|
const MopidyMiddleware = (function(){
|
|
|
|
|
|
2016-10-16 12:37:37 +13:00
|
|
|
// container for the actual Mopidy socket
|
2016-10-14 22:42:45 +13:00
|
|
|
var socket = null;
|
|
|
|
|
|
2016-10-16 12:37:37 +13:00
|
|
|
// handle all manner of socket messages
|
2016-10-14 22:42:45 +13:00
|
|
|
const handleMessage = (ws, store, type, data) => {
|
|
|
|
|
switch( type ){
|
|
|
|
|
|
|
|
|
|
case 'state:online':
|
2016-10-16 12:37:37 +13:00
|
|
|
store.dispatch({ type: 'MOPIDY_CONNECTED' });
|
2016-10-14 22:42:45 +13:00
|
|
|
instruct( ws, store, 'playback.getState' );
|
|
|
|
|
instruct( ws, store, 'playback.getVolume' );
|
|
|
|
|
instruct( ws, store, 'tracklist.getConsume' );
|
|
|
|
|
instruct( ws, store, 'tracklist.getRandom' );
|
|
|
|
|
instruct( ws, store, 'tracklist.getRepeat' );
|
|
|
|
|
instruct( ws, store, 'tracklist.getTlTracks' );
|
2016-10-22 19:13:06 +13:00
|
|
|
instruct( ws, store, 'playback.getCurrentTlTrack' );
|
2016-11-03 18:22:26 +13:00
|
|
|
instruct( ws, store, 'playback.getTimePosition' );
|
2016-11-06 18:29:49 +13:00
|
|
|
instruct( ws, store, 'getUriSchemes' );
|
2016-10-14 22:42:45 +13:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'state:offline':
|
2016-10-16 12:37:37 +13:00
|
|
|
store.dispatch({ type: 'MOPIDY_DISCONNECTED' });
|
2016-10-14 22:42:45 +13:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'event:tracklistChanged':
|
2016-10-22 19:13:06 +13:00
|
|
|
instruct( ws, store, 'tracklist.getTlTracks' );
|
2016-10-14 22:42:45 +13:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'event:playbackStateChanged':
|
|
|
|
|
instruct( ws, store, 'playback.getState' );
|
2016-10-24 21:26:01 +13:00
|
|
|
break;
|
|
|
|
|
|
2016-11-03 18:22:26 +13:00
|
|
|
case 'event:seeked':
|
|
|
|
|
store.dispatch({ type: 'MOPIDY_TIMEPOSITION', data: data.time_position });
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'event:trackPlaybackEnded':
|
|
|
|
|
instruct( ws, store, 'playback.getTimePosition' );
|
|
|
|
|
break;
|
|
|
|
|
|
2016-10-24 21:26:01 +13:00
|
|
|
case 'event:trackPlaybackStarted':
|
2016-10-22 19:13:06 +13:00
|
|
|
instruct( ws, store, 'playback.getCurrentTlTrack' );
|
2016-10-14 22:42:45 +13:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'event:volumeChanged':
|
2016-10-20 21:38:01 +13:00
|
|
|
store.dispatch({ type: 'MOPIDY_VOLUME', data: data.volume });
|
2016-10-14 22:42:45 +13:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'event:optionsChanged':
|
|
|
|
|
instruct( ws, store, 'tracklist.getConsume' );
|
|
|
|
|
instruct( ws, store, 'tracklist.getRandom' );
|
|
|
|
|
instruct( ws, store, 'tracklist.getRepeat' );
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
2016-10-16 12:37:37 +13:00
|
|
|
//console.log( 'MopidyService: Unhandled message', type, message );
|
2016-10-14 22:42:45 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2016-10-16 12:37:37 +13:00
|
|
|
* Call something with Mopidy
|
2016-10-14 22:42:45 +13:00
|
|
|
*
|
|
|
|
|
* Sends request to Mopidy server, and updates our local storage on return
|
2016-10-16 12:37:37 +13:00
|
|
|
* @param object ws = Mopidy class that wraps a Websocket
|
|
|
|
|
* @param string model = which Mopidy model (playback, tracklist, etc)
|
|
|
|
|
* @param string property = TlTracks, Consume, etc
|
|
|
|
|
* @param string value (optional) = value of the property to pass
|
2016-10-20 21:38:01 +13:00
|
|
|
* @return promise
|
2016-10-14 22:42:45 +13:00
|
|
|
**/
|
2016-10-20 21:38:01 +13:00
|
|
|
const instruct = ( ws, store, call, value = {} ) => {
|
2016-10-16 12:37:37 +13:00
|
|
|
|
2016-10-14 22:42:45 +13:00
|
|
|
var callParts = call.split('.');
|
|
|
|
|
var model = callParts[0];
|
|
|
|
|
var method = callParts[1];
|
2016-11-06 18:29:49 +13:00
|
|
|
if( method ){
|
|
|
|
|
var mopidyObject = ws[model][method]
|
|
|
|
|
var property = method;
|
|
|
|
|
}else{
|
|
|
|
|
var mopidyObject = ws[model]
|
|
|
|
|
var property = model;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-14 22:42:45 +13:00
|
|
|
property = property.replace('get','');
|
|
|
|
|
property = property.replace('set','');
|
|
|
|
|
|
2016-10-20 21:38:01 +13:00
|
|
|
return new Promise( (resolve, reject) => {
|
2016-11-06 18:29:49 +13:00
|
|
|
mopidyObject( value )
|
2016-10-14 22:42:45 +13:00
|
|
|
.then(
|
2016-10-20 21:38:01 +13:00
|
|
|
response => {
|
2016-11-06 18:29:49 +13:00
|
|
|
store.dispatch({ type: 'MOPIDY_'+property.toUpperCase(), call: call, data: response });
|
2016-10-20 21:38:01 +13:00
|
|
|
resolve(response);
|
|
|
|
|
},
|
|
|
|
|
error => {
|
|
|
|
|
console.error(error)
|
|
|
|
|
reject(error);
|
|
|
|
|
}
|
2016-10-14 22:42:45 +13:00
|
|
|
);
|
2016-10-20 21:38:01 +13:00
|
|
|
});
|
2016-10-14 22:42:45 +13:00
|
|
|
}
|
|
|
|
|
|
2016-10-16 12:37:37 +13:00
|
|
|
/**
|
|
|
|
|
* Middleware
|
|
|
|
|
*
|
|
|
|
|
* This behaves like an action interceptor. We listen for specific actions
|
|
|
|
|
* and handle special functionality. If the action is not in our switch, then
|
|
|
|
|
* it just proceeds to the next middleware, or default functionality
|
|
|
|
|
**/
|
2016-10-14 22:42:45 +13:00
|
|
|
return store => next => action => {
|
2016-10-20 21:38:01 +13:00
|
|
|
|
2016-10-14 22:42:45 +13:00
|
|
|
switch(action.type) {
|
|
|
|
|
|
2016-10-16 12:37:37 +13:00
|
|
|
case 'MOPIDY_CONNECT':
|
2016-10-14 22:42:45 +13:00
|
|
|
|
|
|
|
|
if(socket != null) socket.close();
|
2016-10-16 12:37:37 +13:00
|
|
|
store.dispatch({ type: 'MOPIDY_CONNECTING' });
|
2016-10-14 22:42:45 +13:00
|
|
|
|
2016-10-18 08:56:53 +13:00
|
|
|
var state = store.getState();
|
|
|
|
|
|
2016-10-14 22:42:45 +13:00
|
|
|
socket = new Mopidy({
|
2016-10-18 08:56:53 +13:00
|
|
|
webSocketUrl: 'ws://'+state.mopidy.host+':'+state.mopidy.port+'/mopidy/ws',
|
2016-10-14 22:42:45 +13:00
|
|
|
callingConvention: 'by-position-or-by-name'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
socket.on( (type, data) => handleMessage( socket, store, type, data ) );
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
2016-10-16 12:37:37 +13:00
|
|
|
case 'MOPIDY_DISCONNECT':
|
2016-10-14 22:42:45 +13:00
|
|
|
if(socket != null) socket.close();
|
|
|
|
|
socket = null;
|
2016-10-16 12:37:37 +13:00
|
|
|
store.dispatch({ type: 'MOPIDY_DISCONNECTED' });
|
2016-10-14 22:42:45 +13:00
|
|
|
break;
|
|
|
|
|
|
2016-10-16 12:37:37 +13:00
|
|
|
// send an instruction to the websocket
|
|
|
|
|
case 'MOPIDY_INSTRUCT':
|
2016-10-14 22:42:45 +13:00
|
|
|
instruct( socket, store, action.call, action.value )
|
|
|
|
|
break;
|
|
|
|
|
|
2016-10-19 14:09:34 +13:00
|
|
|
case 'MOPIDY_PLAY_TRACKS':
|
2016-10-20 21:38:01 +13:00
|
|
|
|
|
|
|
|
// add our first track
|
|
|
|
|
instruct( socket, store, 'tracklist.add', { uri: action.uris[0], at_position: 0 } )
|
|
|
|
|
.then( response => {
|
|
|
|
|
|
|
|
|
|
// play it
|
|
|
|
|
store.dispatch( actions.changeTrack( response[0].tlid ) );
|
|
|
|
|
|
2016-10-21 16:34:16 +13:00
|
|
|
// TODO: perhaps force update of currentTlTrack before we proceed?
|
|
|
|
|
// this will make the UI feel snappier...
|
|
|
|
|
|
2016-10-20 21:38:01 +13:00
|
|
|
// add the rest of our uris (if any)
|
|
|
|
|
action.uris.shift();
|
|
|
|
|
if( action.uris.length > 0 ){
|
|
|
|
|
store.dispatch( actions.enqueueTracks( action.uris, 1 ) )
|
|
|
|
|
}
|
|
|
|
|
})
|
2016-10-19 14:09:34 +13:00
|
|
|
break;
|
|
|
|
|
|
2016-10-21 23:27:50 +13:00
|
|
|
case 'MOPIDY_PLAYLISTS':
|
|
|
|
|
instruct( socket, store, 'playlists.asList' )
|
|
|
|
|
.then( response => {
|
|
|
|
|
store.dispatch({ type: 'MOPIDY_PLAYLISTS_LOADED', data: response });
|
|
|
|
|
})
|
|
|
|
|
break;
|
|
|
|
|
|
2016-11-01 11:49:04 +13:00
|
|
|
case 'MOPIDY_PLAYLIST':
|
2016-11-01 19:27:59 +13:00
|
|
|
store.dispatch({ type: 'MOPIDY_PLAYLIST_LOADED', data: false });
|
2016-11-01 11:49:04 +13:00
|
|
|
instruct( socket, store, 'playlists.lookup', action.data )
|
|
|
|
|
.then( response => {
|
|
|
|
|
var playlist = response;
|
|
|
|
|
playlist.tracks = {
|
|
|
|
|
items: response.tracks,
|
|
|
|
|
total: response.tracks.length
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var uris = [];
|
|
|
|
|
for( var i = 0; i < playlist.tracks.items.length; i++ ){
|
|
|
|
|
uris.push( playlist.tracks.items[i].uri );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
instruct( socket, store, 'library.lookup', { uris: uris } )
|
|
|
|
|
.then( response => {
|
|
|
|
|
|
|
|
|
|
for(var uri in response){
|
|
|
|
|
if (response.hasOwnProperty(uri)) {
|
|
|
|
|
|
|
|
|
|
var track = response[uri][0];
|
|
|
|
|
|
|
|
|
|
// find the track reference, and drop in the full track data
|
|
|
|
|
function getByURI( trackReference ){
|
|
|
|
|
return track.uri == trackReference.uri
|
|
|
|
|
}
|
|
|
|
|
var trackReferences = playlist.tracks.items.filter(getByURI);
|
|
|
|
|
|
|
|
|
|
// there could be multiple instances of this track, so accommodate this
|
|
|
|
|
for( var j = 0; j < trackReferences.length; j++){
|
|
|
|
|
var key = playlist.tracks.items.indexOf( trackReferences[j] );
|
|
|
|
|
playlist.tracks.items[ key ] = track;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
store.dispatch({ type: 'MOPIDY_PLAYLIST_LOADED', data: playlist });
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
break;
|
|
|
|
|
|
2016-11-01 13:19:51 +13:00
|
|
|
case 'MOPIDY_ALBUM':
|
2016-11-01 19:27:59 +13:00
|
|
|
store.dispatch({ type: 'MOPIDY_ALBUM_LOADED', data: false });
|
2016-11-01 13:19:51 +13:00
|
|
|
instruct( socket, store, 'library.lookup', action.data )
|
|
|
|
|
.then( response => {
|
|
|
|
|
var album = response[0].album;
|
2016-11-01 16:41:04 +13:00
|
|
|
album.artists = response[0].artists;
|
2016-11-01 13:19:51 +13:00
|
|
|
album.tracks = {
|
|
|
|
|
items: response,
|
|
|
|
|
total: response.length
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var uris = [];
|
|
|
|
|
for( var i = 0; i < album.tracks.items.length; i++ ){
|
|
|
|
|
uris.push( album.tracks.items[i].uri );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
instruct( socket, store, 'library.lookup', { uris: uris } )
|
|
|
|
|
.then( response => {
|
|
|
|
|
|
|
|
|
|
for(var uri in response){
|
|
|
|
|
if (response.hasOwnProperty(uri)) {
|
|
|
|
|
|
|
|
|
|
var track = response[uri][0];
|
|
|
|
|
|
|
|
|
|
// find the track reference, and drop in the full track data
|
|
|
|
|
function getByURI( trackReference ){
|
|
|
|
|
return track.uri == trackReference.uri
|
|
|
|
|
}
|
|
|
|
|
var trackReferences = album.tracks.items.filter(getByURI);
|
|
|
|
|
|
|
|
|
|
// there could be multiple instances of this track, so accommodate this
|
|
|
|
|
for( var j = 0; j < trackReferences.length; j++){
|
|
|
|
|
var key = album.tracks.items.indexOf( trackReferences[j] );
|
|
|
|
|
album.tracks.items[ key ] = track;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
store.dispatch({ type: 'MOPIDY_ALBUM_LOADED', data: album });
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
break;
|
|
|
|
|
|
2016-11-01 16:41:04 +13:00
|
|
|
case 'MOPIDY_ARTIST':
|
2016-11-01 19:27:59 +13:00
|
|
|
store.dispatch({ type: 'MOPIDY_ARTIST_LOADED', data: false });
|
2016-11-01 16:41:04 +13:00
|
|
|
instruct( socket, store, 'library.lookup', action.data )
|
|
|
|
|
.then( response => {
|
2016-11-01 19:27:59 +13:00
|
|
|
var artist = response[0].artists[0];
|
|
|
|
|
artist.images = [];
|
|
|
|
|
artist.albums = [];
|
|
|
|
|
artist.tracks = response.slice(0,10);
|
2016-11-01 16:41:04 +13:00
|
|
|
|
|
|
|
|
for( var i = 0; i < response.length; i++ ){
|
|
|
|
|
var album = response[i].album;
|
|
|
|
|
|
|
|
|
|
function getByURI( albumToCheck ){
|
|
|
|
|
return album.uri == albumToCheck.uri
|
|
|
|
|
}
|
|
|
|
|
var existingAlbum = artist.albums.find(getByURI);
|
|
|
|
|
if( !existingAlbum ){
|
|
|
|
|
artist.albums.push(album)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
store.dispatch({ type: 'MOPIDY_ARTIST_LOADED', data: artist });
|
|
|
|
|
})
|
|
|
|
|
break;
|
|
|
|
|
|
2016-11-02 16:51:00 +13:00
|
|
|
case 'MOPIDY_DIRECTORY':
|
|
|
|
|
store.dispatch({ type: 'MOPIDY_DIRECTORY_LOADED', data: false });
|
|
|
|
|
instruct( socket, store, 'library.browse', action.data )
|
|
|
|
|
.then( response => {
|
|
|
|
|
store.dispatch({ type: 'MOPIDY_DIRECTORY_LOADED', data: response });
|
|
|
|
|
})
|
|
|
|
|
break;
|
|
|
|
|
|
2016-11-01 19:27:59 +13:00
|
|
|
case 'MOPIDY_ARTISTS':
|
|
|
|
|
store.dispatch({ type: 'MOPIDY_ARTISTS_LOADED', data: false });
|
|
|
|
|
instruct( socket, store, 'library.browse', { uri: 'local:directory?type=artist' } )
|
2016-11-02 16:51:00 +13:00
|
|
|
.then( response => {
|
|
|
|
|
store.dispatch({ type: 'MOPIDY_ARTISTS_LOADED', data: response });
|
|
|
|
|
})
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'MOPIDY_ALBUMS':
|
|
|
|
|
store.dispatch({ type: 'MOPIDY_ALBUMS_LOADED', data: false });
|
|
|
|
|
instruct( socket, store, 'library.browse', { uri: 'local:directory?type=album' } )
|
|
|
|
|
.then( response => {
|
|
|
|
|
store.dispatch({ type: 'MOPIDY_ALBUMS_LOADED', data: response });
|
2016-11-01 19:27:59 +13:00
|
|
|
})
|
|
|
|
|
break;
|
|
|
|
|
|
2016-10-16 12:37:37 +13:00
|
|
|
// This action is irrelevant to us, pass it on to the next middleware
|
2016-10-14 22:42:45 +13:00
|
|
|
default:
|
|
|
|
|
return next(action);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
export default MopidyMiddleware
|