class MP3k(Widget): playlist_width = NumericProperty() def __init__(self, **kwargs): Globals.CONFIG = App.get_running_app().config Globals.TESTING = Globals.CONFIG.get('Development', 'test_mode') self.playlist_width = int( Globals.CONFIG.get('Playlist', 'playlist_width')) Globals.API = GoogleMusicApi() self.login_failed_popup = None self.google_music_api_login() self.formatter = Formatter() self.player = Player() self.player.set_streaming_quality( Globals.CONFIG.get('Google Play Music', 'quality').split(':')[0]) self.playlist = Playlist() self.librarymanager = LibraryManager() # self.librarymanager.load_library() self.history = History() self.playlist.queue = self.history.playlist_history self.updating_progress = False self.playlist_hidden = False super().__init__(**kwargs) self.player.bind(playing=self.update_play_button_state) self.player.bind(progress_percent=self.update_progress_slider) # Add search result views # Songs self.songlist = SongViewer() # Stations # self.stationlist = StationViewer() self.stationscroll = ScrollView(size_hint=(1, 1)) self.stationscroll.do_scroll_x = False self.stationlist = StackLayout(size_hint_y=None, spacing=10) self.stationlist.bind(minimum_height=self.stationlist.setter('height')) self.stationscroll.add_widget(self.stationlist) # Albums self.albumlist = AlbumViewer() # Create and init screen manager self.sm = ScreenManager() self.init_screens() # Listen for Keyboard events self._keyboard = Window.request_keyboard(None, self, 'text') self._keyboard.bind(on_key_down=self._pressed_key) self.searchbar.focus = True def google_music_api_login(self): if Globals.API.login( Globals.CONFIG.get('Google Play Music', 'login'), Globals.CONFIG.get('Google Play Music', 'password'), Globals.CONFIG.get('Google Play Music', 'device_id')): Logger.debug('Google Music: Login successful') if self.login_failed_popup: self.login_failed_popup.dismiss() self.login_failed_popup = None else: Logger.warning("Google Music: Login Failed") if not self.login_failed_popup: popup = Popup(title='Google Play Music™ login', content=LoginCredentials(), auto_dismiss=False, size_hint=(1, 1)) self.login_failed_popup = popup if Globals.STARTED: # login failed after configuration change self.login_failed_popup.open( ) # open popup because MP3k is completely rendered App.get_running_app().close_settings() else: # login failed on start pass # wait with popup until MP3k is completely rendered (MP3kApp opens the popup in on_start()) def try_login_step_1(self, instance, google_login, google_password): # Credentials login without device ID if Globals.API.login(google_login, google_password, ''): # login successful Globals.CONFIG.set('Google Play Music', 'login', google_login) Globals.CONFIG.set('Google Play Music', 'password', google_password) Globals.CONFIG.write() self.show_device_login() else: instance.login_failed_label.color = (1, 0, 0, 1) def try_login_step_2(self, button_container): # look for selected device for button in button_container.children: if button.state == 'down': # got it Globals.CONFIG.set('Google Play Music', 'device_id', button.device_id) Globals.CONFIG.write() # set device id in config if Globals.API.relogin( Globals.CONFIG.get( 'Google Play Music', 'login'), # re-login with valid device_id Globals.CONFIG.get('Google Play Music', 'password'), Globals.CONFIG.get('Google Play Music', 'device_id')): self.login_failed_popup.dismiss() break else: Logger.error('LOGIN: You have to select a device!') def show_device_login(self): self.login_failed_popup.dismiss() self.login_failed_popup = Popup(title='Google Play Music™ login', content=self.build_devices_login(), auto_dismiss=False, size_hint=(1, 1)) self.login_failed_popup.open() @staticmethod def build_devices_login(): devices = Globals.API.get_registered_mobile_devices( ) # get registered mobile devices login_devices = LoginDevices() # add buttons for devices for device in devices: btn = DeviceButton(text='{} (ID: {})'.format( device['name'], device['id']), device_id=device['id'], size_hint_y=None, height=30, group='devices') login_devices.device_button_container.add_widget(btn) return login_devices def init_screens(self): # Create screens screen_songs = Screen(name='Songs', size_hint=(1, 1)) screen_stations = Screen(name='Stations', size_hint=(1, 1)) screen_albums = Screen(name='Albums', size_hint=(1, 1)) # Add content to screens screen_songs.add_widget(self.songlist) # screen_stations.add_widget(self.stationlist) screen_stations.add_widget(self.stationscroll) screen_albums.add_widget(self.albumlist) # Add screens self.sm.add_widget(screen_songs) self.sm.add_widget(screen_stations) self.sm.add_widget(screen_albums) # Add screen manager and playlist self.screenmanagercontainer.add_widget(self.sm) def _pressed_key(self, keyboard, keycode, text, modifiers): if not self.searchbar.focus: if keycode[1] == 'spacebar' or keycode[0] == 1073742085: Logger.debug('Keyboard: Pressed spacebar/play-pause-media-key') self.playbutton_callback() elif keycode[0] == 1073742083: # 'previous track' media key Logger.debug("Keyboard: Pressed 'previous track' media key") self.previousbutton_callback() elif keycode[0] == 1073742082: # 'next track' media key Logger.debug("Keyboard: Pressed 'next track' media key") self.nextbutton_callback() #else: # print(keycode) def update_play_button_state(self, instance, value): if value: self.playbutton.icon = '../res/icons/glyphicons-175-pause_white.png' else: self.playbutton.icon = '../res/icons/glyphicons-174-play_white.png' def update_progress_slider(self, instance, value): self.progress_slider.value = value def update_playlist_position(self, window, width, height): self.playlist_view.pos_hint = {'x': .3, 'y': 90.0 / height} def update_playlist_view(self, instance, value): print('Updating playlist..') print('data_queue before: ' + str(len(self.playlist_view.data_queue))) # self.playlist_view.data_queue = self.playlist.queue # ListView should be updated if we do it like this.. self.playlist_view.children[0].adapter.data.clear( ) # ..but it isn't, so we do this self.playlist_view.children[0].adapter.data.extend( self.playlist.queue) # and then this # self.playlist_view.content.listview.adapter.data = self.playlist.queue # never do this, it replaces the # ObservableList and breaks kivy functionality print('data_queue after: ' + str(len(self.playlist_view.data_queue))) def _update_progress_interval(self, delta_time): if not self.updating_progress: self.updating_progress = True if self.player.current_track and self.player.playback_started and self.player.playing: # time_played_ms = pygame.mixer.music.get_pos() # duration_ms = int(self.musicmanager.current_track['duration_ms']) # progress = time_played_ms / (duration_ms / 100) progress_percent = self.player.send_cmd_to_mplayer( 'get_percent_pos', 'ANS_PERCENT_POSITION') if progress_percent is not False and progress_percent is not None: old_progress_percent = self.player.progress_percent self.player.progress_percent = interpolate( old_progress_percent, int(progress_percent)) Logger.trace('Progress: ' + str(self.player.progress_percent)) elif progress_percent is False: Logger.debug('_update_progress_interval: Received ' + str(progress_percent) + ' as progress') self.player.playback_finished() self.play_next_track() else: Logger.debug('_update_progress_interval: Received ' + str(progress_percent) + ' as progress') # remove schedule if no track selected elif not self.player.playback_started and not self.player.playing: Logger.debug( 'No song playing, removing slider update interval..') self.updating_progress = False return False self.updating_progress = False def on_config_changed(self, section, key, value): Logger.debug('Config: Config changed') if key == 'playlist_width': self.playlist_width = int(value) elif section == 'Google Play Music': Globals.API.logout() self.google_music_api_login() elif section == 'Development': if key == 'test_mode': Globals.TESTING = True if value == '1' else False def fix_scrolling_workaround(self): self.playlist_view.listview._reset_spopulate() def playbutton_state(self): Logger.debug('Playbuttonstate: ' + self.playbutton.state) return 'down' if self.player.playing else 'normal' def mark_playing_track(self): # track_item = self.playlist_view.get_track(0) # track_item.update_image('../res/icons/equalizer.gif') playing_text = '{} - {}'.format(self.player.current_track['title'], self.player.current_track['artist']) self.playinglabel.text = playing_text App.get_running_app().title = playing_text def restart_track(self): Logger.info('Restarting track..') self.play_track(self.player.current_track) def play_previous_track(self): Logger.info('Playing previous track') track = self.playlist.get_previous_track() if track: self.play_track(track) def play_next_track(self): Logger.info('Playing next track') track = self.playlist.get_next_track() if track: self.play_track(track) else: App.get_running_app( ).title = 'MusicPlayer 3000 for Google Play Music™' def switch_screen_callback(self, screen_title): self.sm.current = screen_title def play_callback(self, track, index): Logger.debug('Playing from songlist (left): Index ' + str(index)) self.playlist.add_track_and_set_current(track) self.fix_scrolling_workaround() self.play_track(track) def play_album_callback(self, album_id): index = len(self.playlist.queue) self.add_album_to_playlist_callback(album_id) idx, track = self.playlist.set_current_track(index) self.play_track(track) def play_from_playlist_callback(self, track, index): Logger.debug('Playing from playlist (right): Index ' + str(index)) self.playlist.set_current_track(index) self.play_track(track) def play_track(self, track): Logger.info('Playing track: ' + track['title']) self.player.play_track_from_id(track) self.mark_playing_track() # self.set_playing_icon() # unschedule possible previous intervals Clock.unschedule(self._update_progress_interval) # start interval for updating the progress slider Clock.schedule_interval(self._update_progress_interval, .1) def set_playing_icon(self): index, current_track = self.playlist.get_current_track() # self.playlist_view.children[0].adapter.data[index] def playbutton_callback(self): if self.player.current_track: # we have a track selected if self.player.playback_started and self.player.playing: # pause track self.player.pause_current_track() elif self.player.playback_started and not self.player.playing: # resume track self.player.resume_current_track() else: # playback has finished, restart track self.restart_track() else: # No track selected but maybe we have elements in the playlist Logger.debug('No current track set!') track = self.playlist.get_start() if track: self.play_track(track) else: # do nothing if no track selected #self.librarymanager.synchronize_library() pass def nextbutton_callback(self): if self.player.current_track: # we have a track selected self.play_next_track() else: # No track selected but maybe we have some in the playlist Logger.debug('No current track set!') track = self.playlist.get_start() if track: self.play_track(track) else: # do nothing if no track selected pass def previousbutton_callback(self): if self.player.current_track: # we have a track selected self.play_previous_track() else: # No track selected but maybe we have some in the playlist Logger.debug('No current track set!') track = self.playlist.get_start() if track: self.play_track(track) else: # do nothing if no track selected pass def shufflebutton_callback(self): if self.playlist.shuffle: Logger.info("I won't shuffle anymore..") self.playlist.shuffle = False self.shufflebutton.source = self.shufflebutton.source_img_alt else: Logger.info("Everyday I'm shuffling..") self.playlist.shuffle = True self.shufflebutton.source = self.shufflebutton.source_img def skip_callback(self, touch_pos): width = self.progress_slider.width touch_pos_x = touch_pos[0] position = touch_pos_x / (width / 100) if self.player.current_track: # we need a track to skip into if not self.player.playback_started: # song is not playing, restart song self.restart_track() self.player.skip_track_to(position) # skip to position else: # self.progress_slider.value = 0 # keep slider position at 0 # TODO: Look into slider implementation to keep slider at 0 pass def add_to_playlist_callback(self, track): self.playlist.add_track(QueryDict(track)) self.fix_scrolling_workaround() def remove_from_playlist_callback(self, index): self.playlist.remove_track(index) def add_album_to_playlist_callback(self, album_id): album = Globals.API.get_album_info(album_id) album_tracks = album['tracks'] if album_tracks: album_tracks = self.formatter.format_tracks_list(album_tracks) for track in album_tracks: self.playlist.add_track(QueryDict(track)) self.fix_scrolling_workaround() def play_station_callback(self, title, seed): tracks = Globals.API.get_station_tracks(title, seed) if tracks: tracks = self.formatter.format_tracks_list(tracks) self.playlist.clear() for track in tracks: self.playlist.add_track(track) self.fix_scrolling_workaround() # self.playlist.set_current_track(0) #self.playbutton_callback() track = self.playlist.get_start() if track: self.play_track(track) else: # do nothing if no track selected pass def playlist_button_callback(self): if self.playlist_hidden: self.playlist_container.width = self.playlist_width self.playlist_hidden = False else: self.playlist_container.width = 0 self.playlist_hidden = True def clear_playlist_callback(self): Logger.info('Clearing playlist') self.playlist.clear() # self.playlist.set_current_track(0) def search(self, text): if len(text) >= 3: try: search_results = Globals.API.search(text) # with open('search_test.json', 'w') as outfile: # json.dump(search_results, outfile) self.display_search_results(search_results) except CallFailure: Logger.warning("Search: No All Access for this account!") # TODO: Show login popup # TODO: Remove try..except block when gmusicapi 9.0.1 is stable else: with open('search_test.json') as outfile: search_results = json.load(outfile) self.display_search_results(search_results) def display_search_results(self, search_results): Logger.info("Displaying results..") Logger.debug("Displaying song results") tracks = [] for entry in search_results['song_hits']: tracks.append(entry['track']) # songs_sorted = sorted(songs, key=self.get_song_key) tracks_formatted = self.formatter.format_tracks_list(tracks) # self.ids['list_songs'].data_songs = tracks_formatted self.songlist.data_songs = tracks_formatted Logger.debug("Displaying station results") stations = [] for entry in search_results['station_hits']: stations.append(entry['station']) stations_formatted = self.formatter.format_stations_list(stations) # add station list items # self.stationlist.data_stations = stations_formatted # add station panels self.stationlist.clear_widgets() for station in stations_formatted: self.stationlist.add_widget(StationPanelItem(station)) Logger.debug("Displaying album results") albums = [] for entry in search_results['album_hits']: albums.append(entry['album']) albums_formatted = self.formatter.format_albums_list(albums) self.albumlist.data_albums = albums_formatted