From bf3b2bfba1bd2cb66ae625566308aa99d0c95d82 Mon Sep 17 00:00:00 2001 From: James Barnsley Date: Tue, 20 Dec 2016 16:58:20 +1300 Subject: [PATCH] Preliminary radio --- mopidy_iris/frontend.py | 27 ++++++++++++++++++++++ mopidy_iris/pusher.py | 2 +- src/js/services/localstorage/middleware.js | 2 +- src/js/services/pusher/actions.js | 13 +++++++++++ src/js/services/pusher/middleware.js | 26 ++++++++++++++++++++- src/js/services/ui/reducer.js | 4 ++++ src/js/views/Artist.js | 8 ++++++- src/js/views/Queue.js | 14 +++++++++++ 8 files changed, 92 insertions(+), 4 deletions(-) diff --git a/mopidy_iris/frontend.py b/mopidy_iris/frontend.py index 940dba3d..09ca4e90 100755 --- a/mopidy_iris/frontend.py +++ b/mopidy_iris/frontend.py @@ -51,6 +51,33 @@ class IrisFrontend(pykka.ThreadingActor, CoreListener): # get a fresh spotify authentication token and store for future use # self.refresh_spotify_token() + + + ## + # Get a new spotify authentication token for server-side use + # + # Uses the Client Credentials Flow, so is invisible to the user. We need this token for + # any backend spotify requests (we don't tap in to Mopidy-Spotify, yet). Also used for + # passing token to frontend for javascript requests without use of the Authorization Code Flow. + ## + def refresh_spotify_token( self ): + + url = 'https://accounts.spotify.com/api/token' + authorization = 'YTg3ZmI0ZGJlZDMwNDc1YjhjZWMzODUyM2RmZjUzZTI6ZDdjODlkMDc1M2VmNDA2OGJiYTE2NzhjNmNmMjZlZDY=' + + headers = {'Authorization' : 'Basic ' + authorization} + data = {'grant_type': 'client_credentials'} + data_encoded = urllib.urlencode( data ) + req = urllib2.Request(url, data_encoded, headers) + + try: + response = urllib2.urlopen(req, timeout=30).read() + response_dict = json.loads(response) + self.spotify_token = response_dict + return response_dict + except urllib2.HTTPError as e: + return e + ## # Listen for core events, and update our frontend as required diff --git a/mopidy_iris/pusher.py b/mopidy_iris/pusher.py index 7cd9547d..1ebffe33 100755 --- a/mopidy_iris/pusher.py +++ b/mopidy_iris/pusher.py @@ -194,7 +194,7 @@ class PusherWebsocketHandler(tornado.websocket.WebSocketHandler): send_message( self.connectionid, 'response', - messageJson['action'], + 'radio_started', messageJson['message_id'], { 'radio': radio } ) diff --git a/src/js/services/localstorage/middleware.js b/src/js/services/localstorage/middleware.js index 3c323be8..67bac113 100755 --- a/src/js/services/localstorage/middleware.js +++ b/src/js/services/localstorage/middleware.js @@ -12,7 +12,7 @@ const localstorageMiddleware = (function(){ // append our state to a global variable. This gives us access to debug the store at any point window._store = store - //console.log(action) + console.log(action) switch( action.type ){ diff --git a/src/js/services/pusher/actions.js b/src/js/services/pusher/actions.js index 8c7c6382..7a6abb62 100755 --- a/src/js/services/pusher/actions.js +++ b/src/js/services/pusher/actions.js @@ -43,6 +43,19 @@ export function instruct( message_type, data = null ){ } } +export function startRadio( uris ){ + return { + type: 'PUSHER_START_RADIO', + uris: uris + } +} + +export function stopRadio( uris ){ + return { + type: 'PUSHER_STOP_RADIO' + } +} + export function debug( call, data = null ){ return { type: 'PUSHER_DEBUG', diff --git a/src/js/services/pusher/middleware.js b/src/js/services/pusher/middleware.js index 1bb7fc08..b0f2b165 100755 --- a/src/js/services/pusher/middleware.js +++ b/src/js/services/pusher/middleware.js @@ -63,6 +63,7 @@ const PusherMiddleware = (function(){ socket.onopen = () => { store.dispatch({ type: 'PUSHER_CONNECTED', connection: connection }); store.dispatch({ type: 'PUSHER_SET_USERNAME', username: connection.username }); + makeRequest({ action: 'get_radio' }); }; socket.onmessage = (message) => { @@ -149,7 +150,7 @@ const PusherMiddleware = (function(){ // make it so var notification = new notification( title, options ); - break; + break case 'PUSHER_SEND_AUTHORIZATION': if( window.confirm('Spotify authorization for user '+action.data.me.id+' received. Do you want to import?') ){ @@ -165,6 +166,29 @@ const PusherMiddleware = (function(){ } break + case 'PUSHER_START_RADIO': + + var data = { + action: 'start_radio', + seed_artists: [], + seed_genres: [], + seed_tracks: [] + } + + for( var i = 0; i < action.uris.length; i++){ + switch( helpers.uriType( action.uris[i] ) ){ + case 'artist': + data.seed_artists.push( action.uris[i] ); + break; + case 'track': + data.seed_tracks.push( action.uris[i] ); + break; + } + } + + makeRequest( data ) + break + // This action is irrelevant to us, pass it on to the next middleware default: return next(action); diff --git a/src/js/services/ui/reducer.js b/src/js/services/ui/reducer.js index d43dfb2e..9c6f73e0 100755 --- a/src/js/services/ui/reducer.js +++ b/src/js/services/ui/reducer.js @@ -117,6 +117,10 @@ export default function reducer(ui = {}, action){ case 'FOLLOWING_LOADING': return Object.assign({}, ui, { following_loading: true }) + case 'PUSHER_RADIO': + case 'PUSHER_START_RADIO': + return Object.assign({}, ui, { radio: action.data }) + diff --git a/src/js/views/Artist.js b/src/js/views/Artist.js index e853813c..b341912c 100755 --- a/src/js/views/Artist.js +++ b/src/js/views/Artist.js @@ -16,7 +16,9 @@ import FollowButton from '../components/FollowButton' import SidebarToggleButton from '../components/SidebarToggleButton' import * as helpers from '../helpers' +import * as uiActions from '../services/ui/actions' import * as mopidyActions from '../services/mopidy/actions' +import * as pusherActions from '../services/pusher/actions' import * as lastfmActions from '../services/lastfm/actions' import * as spotifyActions from '../services/spotify/actions' @@ -67,7 +69,9 @@ class Artist extends React.Component{ } play(){ - alert('Yet to be implemented') + if (!this.props.artist.uri) return + this.props.uiActions.createNotification('Starting radio...') + this.props.pusherActions.startRadio([this.props.artist.uri]) } renderSubViewMenu(){ @@ -204,7 +208,9 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = (dispatch) => { return { + uiActions: bindActionCreators(uiActions, dispatch), mopidyActions: bindActionCreators(mopidyActions, dispatch), + pusherActions: bindActionCreators(pusherActions, dispatch), lastfmActions: bindActionCreators(lastfmActions, dispatch), spotifyActions: bindActionCreators(spotifyActions, dispatch) } diff --git a/src/js/views/Queue.js b/src/js/views/Queue.js index ff27091e..95b11e21 100755 --- a/src/js/views/Queue.js +++ b/src/js/views/Queue.js @@ -41,6 +41,17 @@ class Queue extends React.Component{ this.props.mopidyActions.reorderTracklist( indexes, index ) } + renderRadio(){ + if (!this.props.radio) return null + + return ( +
+ Playing radio + +
+ ) + } + render(){ return (
@@ -49,6 +60,8 @@ class Queue extends React.Component{ + { this.renderRadio() } +
{ return { + radio: state.ui.radio, current_tracklist: state.ui.current_tracklist } }