def test_request_user_token(self): c = Credentials('id', 'secret', 'uri') send = MagicMock(return_value=mock_response()) with patch(cred_module + '.send', send): c.request_user_token('code') send.assert_called_once() c.close()
class sofa_spotify_controller(object): def __init__(self, config, app=None): self.config = config self.loop = asyncio.get_event_loop() self.app = app self.device = None self.user_pause = False self.task = None self.active = False self.running = True self.info = {} self.user_info = {} self.token = None self.spotify = Spotify() self.last_update = None self.credentials = Credentials(self.config.client_id, self.config.client_secret, self.config.client_redirect_uri) self.sender = RetryingSender(sender=AsyncSender()) self.playback_device_name = self.config.default_device self.user_info = {} self.now_playing_data = {} self.loop.run_until_complete(self.load_auth()) self.backup_playlist = self.loop.run_until_complete( self.load_and_confirm('backup_playlist')) self.user_playlist = self.loop.run_until_complete( self.load_and_confirm('user_playlist')) self.recent_picks = self.loop.run_until_complete( self.load_and_confirm('recent_picks')) self.blacklist = self.loop.run_until_complete(self.load_blacklist()) self.idle_timeout = 300 self.current_track_user = None async def start(self): try: nowplaying = await self.update_now_playing() except: logger.error('.! error starting initial nowplaying check', exc_info=True) self.active = False @property def auth_url(self): try: return self.credentials.user_authorisation_url( scope=tekore.scope.every) except: logger.error('.. error retrieving authorization url', exc_info=True) return "" async def load_blacklist(self): try: return await FileUtils.get_data_from_file( self.config.data_directory, 'blacklist') except: logger.error('!! could not load blacklist') return [] async def load_and_confirm(self, list_name): # Load queues from disk and ensure they have a selection_tracker uuid to help deal with unique key requirements in the client playlist = {} try: playlist = await FileUtils.get_data_from_file( self.config.data_directory, list_name) if playlist == None: playlist = {'tracks': []} if type(playlist) == list: logger.error('!! Error - legacy format playlist for %s' % list_name) playlist = {"tracks": playlist} await FileUtils.save_data_to_file(self.config.data_directory, list_name, playlist) if 'tracks' in playlist and len(playlist['tracks']) > 0: for item in playlist['tracks']: if 'selection_tracker' not in item: item['selection_tracker'] = str(uuid.uuid4()) else: playlist['tracks'] = [] except: logger.error('!! error loading and checking list: %s' % listname) return playlist async def load_auth(self): try: token_contents = await FileUtils.get_data_from_file( self.config.data_directory, 'token') #conf=(self.config["client_id"], self.config["client_secret"], self.config["client_redirect_uri"]) #if isfile(os.path.join(self.config['base_directory'], 'token.json')): # with open(os.path.join(self.config['base_directory'], 'token.json'),'r') as jsonfile: # token_contents=json.loads(jsonfile.read()) #self.token = RefreshingToken(None, self.credentials) #self.token.refresh_user_token(conf,token_contents['refresh_token']) #logger.info('Using refresh Token: %s' % token_contents['refresh_token']) token = self.credentials.refresh_user_token( token_contents['refresh_token']) #logger.info('pre Token: %s' % token) self.token = RefreshingToken(token, self.credentials) self.spotify = Spotify(token=self.token, sender=self.sender, max_limits_on=True) #else: # logger.error('!! Error token file not found and no refresh token available.') except: logger.error('.. Error loading token', exc_info=True) async def save_auth(self, token=None, code=None): try: token_data = { 'last_code': code, "type": token.token_type, "access_token": token.access_token, "refresh_token": token.refresh_token, "expires_at": token.expires_at } logger.info('.. saving token data: %s' % token_data) async with aiofiles.open( os.path.join(self.config.data_directory, 'token.json'), 'w') as f: await f.write(json.dumps(token_data)) except: logger.error('.. Error saving token and code' % (token[:10], code[:10]), exc_info=True) async def set_token(self, code): try: logger.info('.. Setting token from code: %s...' % code[:10]) self.code = code token = self.credentials.request_user_token(code) self.token = RefreshingToken(token, self.credentials) logger.info('.. Token is now: %s' % self.token) await self.save_auth(token=self.token, code=self.code) self.spotify = Spotify(token=self.token, sender=self.sender, max_limits_on=True) #await self.monitor_token() # This is currently removed for troubleshooting when a device gets picked #if self.spotify: # if not self.device: # await self.set_playback_device(self.config['default_device']) await self.update_list('update') await self.update_now_playing() except: logger.error('Error setting token from code %s' % code[:10], exc_info=True) def authenticated(func): def wrapper(self): #logger.info('checking authentication') if self.token and self.spotify: return func(self) else: logger.info('must be authenticated before using spotify API') #return False raise AuthorizationNeeded return wrapper async def get_user(self): try: if self.token: #logger.info('user: %s' % await self.spotify.current_user()) userobj = await self.spotify.current_user() return userobj.asbuiltin() except tekore.client.decor.error.Unauthorised: logger.error('.. Invalid access token: %s' % self.token.access_token) except: logger.error('.. error getting user info', exc_info=True) return {} async def add_blacklist_term(self, term): if not self.blacklist: self.blacklist = [] if term not in self.blacklist: self.blacklist.append(term) logger.info('.. added %s to blacklist' % term) await FileUtils.save_data_to_file(self.config.data_directory, 'blacklist', self.blacklist) return True return False async def remove_blacklist_term(self, term): if not self.blacklist: self.blacklist = [] if self.blacklist and term in self.blacklist: self.blacklist.remove(term) logger.info('.. removed %s from blacklist' % term) await FileUtils.save_data_to_file(self.config.data_directory, 'blacklist', self.blacklist) return True return False async def filter_tracks_blacklist(self, tracks): results = [] for track in tracks: artist_title = ("%s %s" % (track['artist'], track['name'])).lower() if any(word.lower() in artist_title for word in self.blacklist): logger.info('.. filtered result: %s' % artist_title) else: results.append(track) return results async def restart_local_playback_device(self): # This allows you to select a playback device by name try: stdoutdata = subprocess.getoutput("systemctl restart raspotify") logger.info('>> restart local playback device %s' % stdoutdata) return True except: logger.error('Error restarting local playback', exc_info=True) return False async def set_playback_device(self, name, restart=True): # This allows you to select a playback device by name try: # try to restart the local spotifyd since it tends to fail over time devs = await self.spotify.playback_devices() for dev in devs: if dev.id == name or dev.name == name: logger.info('.. transferring to device %s (%s)' % (dev.name, dev.id)) await self.spotify.playback_transfer(dev.id) self.device = dev.id self.playback_device_name = dev.name return True logger.info('did not find local playback device %s. restarting' % name) await self.restart_local_playback_device() await asyncio.sleep(2) devs = await self.spotify.playback_devices() for dev in devs: if dev.name == name: logger.info('transferring to %s' % dev.id) await self.spotify.playback_transfer(dev.id) self.device = dev.id self.playback_device_name = dev.name return True return False except: logger.error('Error setting playback device to %s' % name, exc_info=True) async def check_playback_devices(self): # This allows you to select a playback device by name try: devs = await self.spotify.playback_devices() for dev in devs: logger.info('Device: %s' % dev) except: logger.error('Error checking playback devices', exc_info=True) return False async def check_playback_device(self): try: devs = await self.spotify.playback_devices() for dev in devs: if dev.is_active: return True except: logger.error('Error checking playback device', exc_info=True) return False @authenticated async def get_active_playback_device(self): # This allows you to select a playback device by name try: devs = await self.spotify.playback_devices() for dev in devs: #logger.info('dev: %s %s' % (dev.is_active, dev)) if dev.is_active: return { "name": dev.name, "id": dev.id, "volume": dev.volume_percent } except: logger.error('Error checking playback devices', exc_info=True) return {} @authenticated async def get_playback_devices(self): try: result = [] devs = await self.spotify.playback_devices() for dev in devs: result.append({ "name": dev.name, "id": dev.id, "is_active": dev.is_active, "volume_percent": dev.volume_percent }) except: logger.error('!! Error listing playback devices', exc_info=True) return result async def get_user_playlist(self, name): try: playlists = await self.spotify.followed_playlists() for playlist in playlists.items: if playlist.name == name: logger.info('found playlist: %s %s' % (playlist.name, playlist.owner)) return {"name": playlist.name, "id": playlist.id} return {} except: logger.error('Error searching spotify', exc_info=True) return {} async def get_playlist_source_data(self, id, list_type): if list_type == "playlist": playlist = await self.spotify.playlist(id) covers = await self.spotify.playlist_cover_image(playlist.id) if len(covers) > 0: cover = covers[0].url return { "name": playlist.name, "art": cover, "owner": playlist.owner.id, "display": playlist.name } if list_type == "radio": track = await self.spotify.track(id) return { "name": track.name, "art": track.album.images[0].url, "artist": track.artists[0].name, "album": track.album.name, "display": track.name + " - " + track.artists[0].name + " radio" } return {} async def get_user_playlists(self): try: display_list = [] playlists = await self.spotify.followed_playlists() #playlists = self.spotify.followed_playlists() for playlist in playlists.items: #logger.info('found playlist: %s %s' % (playlist.name, playlist.owner.id)) try: cover = "" covers = await self.spotify.playlist_cover_image( playlist.id) if len(covers) > 0: cover = covers[0].url except concurrent.futures._base.CancelledError: logger.error('Error getting cover for %s (cancelled)' % playlist.name) except: logger.error('Error getting cover for %s' % playlist.name, exc_info=True) display_list.append({ "name": playlist.name, "id": playlist.id, "art": cover, "owner": playlist.owner.id }) return display_list except: logger.error('Error getting user playlists from spotify', exc_info=True) return [] async def get_playlist_tracks(self, id): try: display_list = [] #playlist = self.spotify.playlist(id) tracks = await self.spotify.playlist_items(id) #tracks = await self.spotify.playlist_tracks(id) tracks = self.spotify.all_items(tracks) logger.info('.. Tracks: %s' % tracks) async for track in tracks: display_list.append({ "id": track.track.id, 'selection_tracker': str(uuid.uuid4()), "name": track.track.name, "art": track.track.album.images[0].url, "artist": track.track.artists[0].name, "album": track.track.album.name, "url": track.track.href }) return display_list except: logger.error('Error getting spotify playlist tracks', exc_info=True) return [] async def add_radio(self, song_id, limit=50): try: #track = await self.spotify.track(song_id) recommendations = await self.spotify.recommendations( track_ids=[song_id], limit=limit) #logger.info('recommendations: %s' % recommendations) track_list = [] for track in recommendations.tracks: pltrack = { "id": track.id, "name": track.name, "art": track.album.images[0].url, "artist": track.artists[0].name, "album": track.album.name, "url": track.href, "votes": 1, "count": 0 } logger.info('Adding track: %s - %s' % (pltrack['artist'], pltrack['name'])) track_list.append(pltrack) track_list = await self.filter_tracks_blacklist(track_list) self.backup_playlist = { "id": song_id, "type": "radio", "tracks": list(track_list) } self.backup_playlist.update(await self.get_playlist_source_data( song_id, "radio")) await FileUtils.save_data_to_file(self.config.data_directory, 'backup_playlist', self.backup_playlist) return track_list except: logger.error('Error setting backup playlist from radio track %s' % song_id, exc_info=True) return [] async def search(self, search, types=('track', ), limit=20): try: display_list = [] result = await self.spotify.search(search, types=types, limit=limit) for track in result[0].items: display_list.append({ "id": track.id, "name": track.name, "art": track.album.images[0].url, "artist": track.artists[0].name, "album": track.album.name, "url": track.href }) display_list = await self.filter_tracks_blacklist(display_list) return display_list except: logger.error('Error searching spotify', exc_info=True) return [] async def add_track_to_playlist(self, song_id, playlist_id): try: #playlist=await self.get_user_playlist("Discovered") #playlist_id=playlist['id'] track = await self.spotify.track(song_id) track_data = self.get_track_data(track) await self.spotify.playlist_add(playlist_id, [track.uri]) return track_data except: logger.error('Error adding tracks to playlist %s' % dir(track), exc_info=True) return {} async def add_to_recent_picks(self, track, user=None): recent = [] if 'tracks' in self.recent_picks: for prev_pick in self.recent_picks['tracks']: logger.info('prev: %s' % prev_pick) if prev_pick['id'] != track.id: recent.append(prev_pick) new_track = { "id": track.id, "name": track.name, "art": track.album.images[0].url, "artist": track.artists[0].name, "album": track.album.name, "url": track.href, "user": user, "time": datetime.now().isoformat() } recent.append(new_track) self.recent_picks['tracks'] = recent[-25:] await FileUtils.save_data_to_file(self.config.data_directory, 'recent_picks', {self.recent_picks}) async def add_track(self, song_id, user=None): try: track = await self.spotify.track(song_id) track_json = { "id": track.id, "name": track.name, "art": track.album.images[0].url, "artist": track.artists[0].name, "album": track.album.name, "url": track.href, "user": user, "time": datetime.now().isoformat() } logger.info('Adding track for %s: %s - %s' % (user, track_json['artist'], track_json['name'])) self.user_playlist['tracks'].append(track_json) self.user_playlist['tracks'] = await self.filter_tracks_blacklist( self.user_playlist['tracks']) await FileUtils.save_data_to_file(self.config.data_directory, 'user_playlist', self.user_playlist) self.loop.create_task(self.add_to_recent_picks(track, user)) await self.update_list('update') track_data = await self.get_track_data( track) # get json version for returning to web user return track_data except: logger.error('Error adding song %s' % song_id, exc_info=True) return {} async def del_track(self, song_id): try: remove_count = 0 newlist = [] for song in self.user_playlist['tracks']: if song['id'] != song_id: logger.info('Adding non-delete: %s vs %s' % (song['id'], song_id)) newlist.append(song) else: remove_count += 1 self.user_playlist['tracks'] = newlist await FileUtils.save_data_to_file(self.config.data_directory, 'user_playlist', self.user_playlist) #self.app.saveJSON('user_playlist', self.user_playlist) newlist = [] for song in self.backup_playlist['tracks']: if song['id'] != song_id: newlist.append(song) else: remove_count += 1 self.backup_playlist['tracks'] = newlist await FileUtils.save_data_to_file(self.config.data_directory, 'backup_playlist', self.backup_playlist) #self.app.saveJSON('backup_playlist', self.backup_playlist) await self.update_list('update') return {"removed": remove_count} except: logger.error('Error adding song %s' % song_id, exc_info=True) return [] async def shuffle_backup(self): try: promoted_list = [] working_backup = [] ids = [] for item in self.backup_playlist['tracks']: if item['id'] not in ids: ids.append(item['id']) else: logger.info('dupe track: %s' % item) if 'promoted' in item and item['promoted'] == True: promoted_list.append(item) else: working_backup.append(item) random.shuffle(working_backup) self.backup_playlist['tracks'] = promoted_list + working_backup #logger.info('.. new backup list: %s' % self.backup_playlist) return self.backup_playlist except: logger.error('Error shuffling backup list', exc_info=True) return [] async def get_queue(self): return {'user': self.user_playlist, 'backup': self.backup_playlist} async def get_recent_picks(self): return {"recent": self.recent_picks} async def clear_queue(self): self.user_playlist = {} self.backup_playlist = {} async def list_next_tracks(self, maxcount=5): try: next_tracks = [] next_tracks = self.user_playlist['tracks'][:maxcount] if len(next_tracks) < maxcount: remaining = maxcount - len(next_tracks) next_tracks = next_tracks + self.backup_playlist[ 'tracks'][:remaining] return next_tracks except: logger.error('Error getting next tracks', exc_info=True) return [] async def update_list(self, action): try: nowplaying = await self.now_playing() await self.app.update({'playlist': action}) #await self.app.server.add_sse_update({'playlist':action}) except: logger.error('Error updating now playing subscribers', exc_info=True) return [] async def get_track_data(self, track): try: #logger.info('track type: %s' % track.json()) if not track: return {} item = getattr(track, 'item', track) data = { "id": item.id, "name": item.name, "art": item.album.images[0].url, "artist": item.artists[0].name, "album": item.album.name, "url": item.href, "is_playing": getattr(track, 'is_playing', False), "length": int(item.duration_ms / 1000), "position": int(getattr(track, 'progress_ms', 0) / 1000) } return data except: logger.error('.. error getting track data from %s' % track, exc_info=True) return {} async def now_playing(self): try: nowplaying = {} npdata = None #pb=await self.spotify.playback_recently_played() #logger.info('test: %s' % pb) if await self.check_playback_device(): npdata = await self.spotify.playback_currently_playing() else: await self.set_playback_device('jukebox') recent = await self.spotify.playback_recently_played(limit=1) npdata = recent.items[0].track nowplaying = await self.get_track_data(npdata) if self.current_track_user: nowplaying['user'] = self.current_track_user except requests.exceptions.HTTPError: logger.warn('.. Token may have expired: %s' % self.token) self.active = False except tekore.Unauthorised: logger.error( '!! Error - Unauthorized to Spotify - token may be missing or expired.' ) self.active = False except: logger.error('Error getting now playing', exc_info=True) self.active = False return nowplaying async def pause(self): try: if await self.check_playback_device(): logger.info('-> sending pause to spotify') await self.spotify.playback_pause() await self.update_now_playing() self.user_pause = True return True except: logger.error('Error pausing', exc_info=True) return False async def play(self): try: if not await self.get_active_playback_device(): #logger.error('!! error - no playback device: %s' % await self.get_active_playback_device()) await self.set_playback_device(self.playback_device_name) if not await self.get_active_playback_device(): return False logger.info(".. playing on %s" % await self.get_active_playback_device()) playing = await self.spotify.playback_currently_playing() if playing.item == None: await self.next_track() try: await self.spotify.playback_resume() except tekore.Forbidden: logger.error('!! error - could not resume playback', exc_info=True) await self.next_track() self.active = True await self.update_now_playing() self.user_pause = False return True # TODO: need handler for this error: # tekore.client.decor.error.NotFound: Error in https://api.spotify.com/v1/me/player/play: # 404: Player command failed: No active device found # Requires an active device and the user has none. except: logger.error('Error playing', exc_info=True) return False async def set_backup_playlist(self, playlist_id): try: track_list = await self.get_playlist_tracks(playlist_id) for item in track_list: item['selection_tracker'] = str(uuid.uuid4()) self.backup_playlist = { "id": playlist_id, "type": "playlist", "tracks": list(track_list) } self.backup_playlist.update(await self.get_playlist_source_data( playlist_id, "playlist")) await FileUtils.save_data_to_file(self.config.data_directory, 'backup_playlist', self.backup_playlist) return track_list except: logger.error('Error setting backup playlist', exc_info=True) return [] async def get_playlist(self, playlist_id): try: track_list = await self.get_playlist_tracks(playlist_id) return list(track_list) except: logger.error('Error setting backup playlist', exc_info=True) return [] async def track_ready(self): try: if 'tracks' in self.user_playlist and len( self.user_playlist['tracks']) > 0: return True if 'tracks' in self.backup_playlist and len( self.backup_playlist['tracks']) > 0: return True except: logger.error('Error checking for ready track from queues', exc_info=True) return False async def get_next_track(self): try: next_track = {} next_track = await self.pop_user_track() if next_track: self.current_track_user = next_track['user'] logger.info('.. pulling user track: %s - %s' % (next_track['artist'], next_track['name'])) else: next_track = await self.pop_backup_track() if next_track: self.current_track_user = None logger.info('.. pulling backup track: %s - %s' % (next_track['artist'], next_track['name'])) return next_track except: logger.error('!! Error getting next track from queues', exc_info=True) return {} async def pop_user_track(self): try: if 'tracks' in self.user_playlist and len( self.user_playlist['tracks']) > 0: next_track = self.user_playlist['tracks'].pop(0) await FileUtils.save_data_to_file(self.config.data_directory, 'user_playlist', self.user_playlist) return next_track else: return {} except: logger.error('Error getting track from backup playlist') return {} async def pop_backup_track(self): try: if 'tracks' in self.backup_playlist and len( self.backup_playlist['tracks']) > 0: next_track = self.backup_playlist['tracks'].pop(0) await FileUtils.save_data_to_file(self.config.data_directory, 'backup_playlist', self.backup_playlist) #self.app.saveJSON('backup_playlist', self.backup_playlist) return next_track else: return {} except: logger.error('Error getting track from backup playlist', exc_info=True) return {} async def promote_backup_track(self, song_id, super_promote=False): try: newlist = [] promoted_track = None promoted_count = 0 for song in self.backup_playlist['tracks']: if song['id'] == song_id: promoted_track = song else: if 'promoted' in song and song['promoted'] == True: promoted_count += 1 newlist.append(song) if promoted_track: if super_promote: result = await self.add_track(promoted_track['id']) else: promoted_track['promoted'] = True if promoted_count == 0: newlist.insert(0, promoted_track) else: newlist.insert(promoted_count, promoted_track) self.backup_playlist['tracks'] = newlist await FileUtils.save_data_to_file(self.config.data_directory, 'backup_playlist', self.backup_playlist) await self.update_list('update') return {"promoted": song_id} except: logger.error('Error adding song %s' % song_id, exc_info=True) return [] async def next_track(self): try: next_track = await self.get_next_track() if next_track: self.active = True result = await self.play_id(next_track['id']) await self.update_list('pop') await self.update_now_playing() return result else: logger.warning('!! No more tracks to play') self.active = False except: logger.error('Error trying to play', exc_info=True) self.active = False async def play_id(self, id): try: result = await self.spotify.playback_start_tracks([id]) self.active = True return result except: logger.error('!! Error trying to play id %s' % id, exc_info=True) self.active = False return False async def seek_pos(self, position): try: logger.info('.. seeking to %s / %s' % (int(position) * 1000, self.now_playing_data['nowplaying']['length'])) await self.spotify.playback_seek(int(position) * 1000) result = await self.update_now_playing() self.active = True return result except: logger.error('!! Error trying to seek to position %s' % position, exc_info=True) self.active = False def stop(self): self.task.cancel() async def update_now_playing(self, event=None): try: try: iso = self.last_update.isoformat() except: iso = None track_change = False if "nowplaying" in self.now_playing_data: now_playing_data = dict(self.now_playing_data["nowplaying"]) if event == None: now_playing_data = await self.now_playing() logger.info('.. Updating nowplaying data: %s' % now_playing_data) else: if event['player_event'] == 'change': logger.info('.. event change: %s / %s' % (event, now_playing_data)) if event['track_id'] == event['old_track_id']: track_change = True elif 'track_id' in self.now_playing_data[ 'nowplaying'] and event[ 'track_id'] != now_playing_data['track_id']: now_playing_data = await self.now_playing() logger.info('.. Updating nowplaying data: %s' % now_playing_data) elif event['player_event'] == "playing": logger.info( '.. Updating nowplaying data play state only') now_playing_data['is_playing'] = True elif event['player_event'] == "paused": logger.info( '.. Updating nowplaying data paused state only') now_playing_data['is_playing'] = False else: now_playing_data = await self.now_playing() logger.info('.. Updating nowplaying data: %s' % now_playing_data) idle = True if 'is_playing' in now_playing_data: if now_playing_data['is_playing']: self.active = True idle = False self.last_update = datetime.now() # or now_playing_data['position']==0 if track_change and not self.user_pause: if not event or event['old_track_id'] == event['track_id']: logger.info( '.. track ended: %s - %s' % (now_playing_data['artist'], now_playing_data['name'])) self.active = False idle = False await self.next_track() # If there is a next track, it should trigger the librespot reply if self.last_update and (datetime.now() - self.last_update ).total_seconds() < self.idle_timeout: idle = False #new_data={"idle": idle, "user_pause": self.user_pause, "last_update": iso, "nowplaying": now_playing_data, "device": await self.get_active_playback_device(), "devices": await self.get_playback_devices() } new_data = { "idle": idle, "user_pause": self.user_pause, "last_update": iso, "nowplaying": now_playing_data } await self.app.update(new_data) self.now_playing_data = new_data return self.now_playing_data except: logger.error('Error updating now playing subscribers', exc_info=True) return {} async def control(self, command, params=None): # consolidating most of the media controls down to a single control function that calls the actual tekore/spotify # commands. Only commands that return updated now playing data should be used through control. Other reporting # commands should use specific functions. # Each of these commands needs to be reviewed for duplication of the update_now_playing data if command == "nowplaying": pass # no command needed, update now playing will run elif command == "play": result = await self.app.spotify_controller.play() elif command == "pause": result = await self.app.spotify_controller.pause() elif command == "next": result = await self.app.spotify_controller.next_track() elif command == "seek": result = await self.app.spotify_controller.seek_pos(params[0]) elif command == "set_device": result = await self.app.spotify_controller.set_playback_device( params[0]) else: return {"error": "invalid command: %s" % command} #result = await self.app.spotify_controller.update_now_playing() if not self.now_playing_data: result = await self.app.spotify_controller.update_now_playing() else: result = self.now_playing_data #logger.info('.. nowplaying update: %s / %s' % (command, result)) return result