def __init__(self, **kwargs): super(SqueezePlayerScreen, self).__init__(**kwargs) # We need the path to this folder to load icons scr = sys.modules[self.__class__.__module__].__file__ self.plugindir = os.path.dirname(scr) # Set variables based on the parameters in the config file p = kwargs["params"] self.host = p["host"]["address"] self.webport = p["host"]["webport"] self.telnetport = p["host"]["telnetport"] self.cur_track["name"] = "Loading..." # Get reference to the box layout. self.bx = self.ids.squeeze_box # Create an object to handle retrieving artwork URLs self.awr = ArtworkResolver(host=self.host, port=self.webport, default="images/10x10_transparent.png") # Initialise some variables that we'll need later self.backendonline = False self.lms = None self.squeezeplayers = [] self.cur_player = None self.now_playing = None self.currenttrack = None self.ct = {} self.inactive = True self.timer = None self.cbs = None self.sync_groups = [] self.checker = None
class SqueezePlayerScreen(Screen): """Main screen object for SqueezePlayer. This screen handles the main initial contact with the server and sets up child objects as required. """ cur_track = DictProperty({"name": "Loading..."}) currentArt = StringProperty("images/10x10_transparent.png") def __init__(self, **kwargs): super(SqueezePlayerScreen, self).__init__(**kwargs) # We need the path to this folder to load icons scr = sys.modules[self.__class__.__module__].__file__ self.plugindir = os.path.dirname(scr) # Set variables based on the parameters in the config file p = kwargs["params"] self.host = p["host"]["address"] self.webport = p["host"]["webport"] self.telnetport = p["host"]["telnetport"] self.cur_track["name"] = "Loading..." # Get reference to the box layout. self.bx = self.ids.squeeze_box # Create an object to handle retrieving artwork URLs self.awr = ArtworkResolver(host=self.host, port=self.webport, default="images/10x10_transparent.png") # Initialise some variables that we'll need later self.backendonline = False self.lms = None self.squeezeplayers = [] self.cur_player = None self.now_playing = None self.currenttrack = None self.ct = {} self.inactive = True self.timer = None self.cbs = None self.sync_groups = [] self.checker = None def on_enter(self): """Start the screen running.""" self.timer = Clock.schedule_once(self.update, 0.1) if self.now_playing: self.now_playing.start() def on_leave(self): """Stop the screen.""" Clock.unschedule(self.timer) if self.now_playing: self.now_playing.quit() def lmsLogon(self, host, port): """Log on to the Logitect Server and return a Server object.""" try: sc = LMSServer(hostname=host, port=port) sc.connect() except: sc = None return sc def changePlayer(self, player): """Method to change the current player and update the screen.""" if player != self.cur_player: self.cur_player = player self.squeezePlayer = self.getPlayer(self.cur_player) self.now_playing.player = self.squeezePlayer self.playlist_changed() self.track_changed() self.now_playing.update_players(self.squeezeplayers) def getSqueezePlayers(self, server): """Method to return list of currently active players.""" try: sq = server.get_players() except: sq = None return sq def getPlayer(self, cur_player): """Method to return the current player. If current player is no longer available, this method returns the first available player.""" pl = {x.get_ref(): x for x in self.squeezeplayers} if cur_player in pl: return pl[cur_player] else: return self.squeezeplayers[0] # Get current track information def getCurrentTrackInfo(self, playlist, pos): """Method to update the current playing track info with extra info.""" track = {} # Need to check if there's a playlist, if not this would cause a crash if playlist: track = playlist[pos] track["pos"] = pos + 1 track["elapsed"] = self.squeezePlayer.get_time_elapsed() # Get the artwork - get large version if possible... track["art"] = self.awr.getURL(track, size=(800, 800)) # ...as we'll use as background too self.currentArt = track["art"] # No playlist so send some dummy info else: track = {"artist": "Playlist is empty", "album": "Playlist is empty", "title": "Playlist is empty", "elapsed": 0, "duration": 1, "art": "10x10_transparent.png", "pos": 0} return track def getCallbackServer(self): """Method to create and set up a callback server.""" # Create the server cbs = LMSCallbackServer(hostname=self.host, port=self.telnetport) # Se up our callbacks cbs.add_callback(cbs.VOLUME_CHANGE, self.volume_change) cbs.add_callback(cbs.CLIENT_ALL, self.client_event) cbs.add_callback(cbs.PLAY_PAUSE, self.play_pause) cbs.add_callback(cbs.PLAYLIST_CHANGED, self.playlist_changed) cbs.add_callback(cbs.PLAYLIST_CHANGE_TRACK, self.track_changed) cbs.add_callback(cbs.SYNC, self.sync_event) # Deamonise the object so it dies if the main program dies. cbs.daemon = True return cbs def getCallbackPlayer(self, event): """Return the player reference from the callback event.""" return self.cur_player if event is None else event.split(" ")[0] def checkCallbackServer(self, *args): """Checks if there's still a connection to the server and deletes callback server instance if there isn't. """ self.cbs.check_connection() def cur_or_sync(self, ref): """Method to determine if the event player is our player or in a sync group with our player. """ if ref == self.cur_player: return True else: for gr in self.sync_groups: if ref in gr and self.cur_player in gr: return True return False def volume_change(self, event=None): """Method to handle callback for volume change event. Event should be: [player_ref] mixer volume [vol_amount] """ if self.getCallbackPlayer(event) == self.cur_player: vol = self.squeezePlayer.get_volume() self.now_playing.vol_change(vol, False) def client_event(self, event=None): """Method to handle callback for client event. Expected events are: [player_ref] client new [player_ref] client disconnect [player_ref] client reconnect [player_ref] client forget """ # Get the list of current players self.squeezeplayers = self.getSqueezePlayers(self.lms) # If there are none if not self.squeezeplayers: # update the screen to tell the user self.drawNoPlayer() # If our player disconnected, then we need to show a new one elif (self.getCallbackPlayer(event) == self.cur_player and event.split()[2] == "forget"): # get the player and update the screen self.squeezePlayer = self.getPlayer(self.cur_player) self.changePlayer(self.squeezePlayer.get_ref()) # If this is the first player connecting elif self.squeezeplayers and not self.now_playing: # Get the player details self.squeezePlayer = self.getPlayer(self.cur_player) self.cur_player = self.squeezePlayer.get_ref() # Draw the screen self.createPlayerScreen() self.drawSqueezePlayers(self.squeezeplayers) # Another player connected/disconnected else: # Update the list of players self.now_playing.update_players(self.squeezeplayers) # Update list of sync groups if self.squeezeplayers: self.sync_groups = self.lms.get_sync_groups() def play_pause(self, event=None): """Method to handle callback for play pause event. Expected event is: [player_ref] playlist pause 0|1 """ # We're only interested in our sync group if (self.cur_or_sync(self.getCallbackPlayer(event)) and self.now_playing): # Update the now playing screen paused = (event.split()[3] == "1") self.now_playing.play_pause(paused) def playlist_changed(self, event=None): """Method to handle callback for a playlist change. Expected events are: [player_ref] playlist addtracks [player_ref] playlist loadtracks [player_ref] playlist delete """ # If our playlist has changed if (self.cur_or_sync(self.getCallbackPlayer(event)) and self.now_playing): # Update the screen self.now_playing.updatePlaylist(self.getCurrentPlaylist()) try: ev = event.split() if ev[2] == "clear": # We know there are no tracks. self.ct = self.getCurrentTrackInfo({}, 0) self.now_playing.update(self.ct) except (IndexError, AttributeError): pass def track_changed(self, event=None): """Method to handle track change callback. Expected event: [player_ref] playlist newsong [playlist_position] """ # We're only interested in our sync group if (self.cur_or_sync(self.getCallbackPlayer(event)) and self.now_playing): # Work out where we are in the playlist pos = int(self.squeezePlayer.playlist_get_position()) self.playlistposition = pos plyl = {"pos": self.playlistposition, "playlist": self.playlist} # Get the info for the current track self.ct = self.getCurrentTrackInfo(self.playlist, self.playlistposition) # Update the screen self.now_playing.update(self.ct) def sync_event(self, event=None): """Method to handle sync callback. Expected event: [player_ref] sync """ self.now_playing.updatePlaylist(self.getCurrentPlaylist()) self.squeezeplayers = self.getSqueezePlayers(self.lms) self.sync_groups = self.lms.get_sync_groups() pos = int(self.squeezePlayer.playlist_get_position()) self.playlistposition = pos plyl = {"pos": self.playlistposition, "playlist": self.playlist} self.ct = self.getCurrentTrackInfo(self.playlist, self.playlistposition) self.now_playing.update(self.ct) def drawNoServer(self): """Method to tell the user that there's no server.""" # Clear the screen self.bx.clear_widgets() self.now_playing = None # Create the label and display it. lb = Label(text="No Squeezeserver found. Please check your settings.") self.bx.add_widget(lb) def drawNoPlayer(self): """Method to tell the user that there are no players.""" # Clear the screen self.bx.clear_widgets() self.now_playing = None # Create the label and display it. lb = Label(text="There are no players connected to your server.") self.bx.add_widget(lb) def checkForPlayers(self): """Before the callback server is running we need to run regular checks on the network.""" # Clear the screen if there was something there if self.inactive: self.bx.clear_widgets() # Get list of players self.squeezeplayers = self.getSqueezePlayers(self.lms) # If there are players we need to set up our screen if self.squeezeplayers: self.squeezePlayer = self.getPlayer(self.cur_player) self.cur_player = self.squeezePlayer.get_ref() self.sync_groups = self.lms.get_sync_groups() self.inactive = False self.createPlayerScreen() self.drawSqueezePlayers(self.squeezeplayers) # If not, then we should say there are no players else: self.drawNoPlayer() self.inactive = True def getCurrentPlaylist(self): """Method to return the playlist for the current player.""" # Get the playlist and current position self.playlist = self.squeezePlayer.playlist_get_info(taglist=TAGLIST) try: pos = int(self.squeezePlayer.playlist_get_position()) except ValueError: pos = 0 self.playlistposition = pos # Combine into a dict plyl = {"pos": self.playlistposition, "playlist": self.playlist} return plyl def createPlayerScreen(self): """Method to create the Now Playing screen.""" # Clear the screen self.bx.clear_widgets() # Get the playlist plyl = self.getCurrentPlaylist() # Get the current track info self.ct = self.getCurrentTrackInfo(self.playlist, self.playlistposition) # Create the Now Playing object self.now_playing = SqueezeNowPlaying(cur_track=self.ct, height=480, size_hint_y=None, player=self.squeezePlayer, playlist=plyl, plugindir=self.plugindir, sq_root=self) # Add to the screen self.bx.add_widget(self.now_playing) # Make sure we start off showing the "Now Playing" section for c in self.now_playing.children: if c.title == "Now Playing": c.collapse = False def drawSqueezePlayers(self, sps): """Method to trigger the update of the list of squeeze players.""" if self.now_playing: self.now_playing.update_players(sps) def update(self, *args): """Method to be run on clock interval.""" # Set default update time interval = 5 # Check if there is a CallbackServer instance. if not self.cbs: # No CallbackServer. Can we connect to LMS? self.lms = self.lmsLogon(self.host, self.telnetport) # If so then we can set up a few things if self.lms: # Create a CallbackServer instance... self.cbs = self.getCallbackServer() # ...and start it running self.cbs.start() # Set up a timer to check if the server is active check = self.checkCallbackServer self.checker = Clock.schedule_interval(check, 5) # If we don't have a Now Playing screen initialised if not self.now_playing: # then we need to make one self.checkForPlayers() # We've got a callback server running so we don't need # a regular interval now but we may want to test the # connection every 15 seconds or so interval = 15 else: # There's no active server found self.drawNoServer() self.inactive = True else: # If the callback server has died (e.g. no connection)... if not self.cbs.isAlive(): # Stop checking for the connection Clock.unschedule(self.checker) # Stop timers for now playing screen if self.now_playing: self.now_playing.quit() # Remove the callback server del self.cbs self.cbs = None self.timer = Clock.schedule_once(self.update, interval)