class Player: def __init__(self): self.config = Config() self.ui = Ui() self.popen_handler = None # flag stop, prevent thread start self.playing_flag = False self.pause_flag = False self.process_length = 0 self.process_location = 0 self.process_first = False self.storage = Storage() self.info = self.storage.database["player_info"] self.songs = self.storage.database["songs"] self.playing_id = -1 self.cache = Cache() self.notifier = self.config.get_item("notifier") self.mpg123_parameters = self.config.get_item("mpg123_parameters") self.end_callback = None self.playing_song_changed_callback = None def popen_recall(self, onExit, popenArgs): """ Runs the given args in subprocess.Popen, and then calls the function onExit when the subprocess completes. onExit is a callable object, and popenArgs is a lists/tuple of args that would give to subprocess.Popen. """ def runInThread(onExit, arg): para = ['mpg123', '-R'] para[1:1] = self.mpg123_parameters self.popen_handler = subprocess.Popen(para, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.popen_handler.stdin.write("V " + str(self.info["playing_volume"]) + "\n") if arg: self.popen_handler.stdin.write("L " + arg + "\n") self.process_first = True while True: if self.playing_flag is False: break try: strout = self.popen_handler.stdout.readline() except IOError: break if re.match("^\@F.*$", strout): process_data = strout.split(" ") process_location = float(process_data[4]) if self.process_first: self.process_length = process_location self.process_first = False self.process_location = 0 else: self.process_location = self.process_length - process_location # NOQA continue elif strout[:2] == '@E': # get a alternative url from new api sid = popenArgs['song_id'] new_url = NetEase().songs_detail_new_api([sid])[0]['url'] if new_url is None: log.warning(('Song {} is unavailable ' 'due to copyright issue').format(sid)) break log.error( 'Song {} is not compatible with old api.'.format(sid)) self.popen_handler.stdin.write("\nL " + new_url + "\n") self.popen_handler.stdout.readline() elif strout == "@P 0\n": self.popen_handler.stdin.write("Q\n") self.popen_handler.kill() break if self.playing_flag: self.next_idx() onExit() return def getLyric(): if 'lyric' not in self.songs[str(self.playing_id)].keys(): self.songs[str(self.playing_id)]["lyric"] = [] if len(self.songs[str(self.playing_id)]["lyric"]) > 0: return netease = NetEase() lyric = netease.song_lyric(self.playing_id) if lyric == [] or lyric == '未找到歌词': return lyric = lyric.split('\n') self.songs[str(self.playing_id)]["lyric"] = lyric return def gettLyric(): if 'tlyric' not in self.songs[str(self.playing_id)].keys(): self.songs[str(self.playing_id)]["tlyric"] = [] if len(self.songs[str(self.playing_id)]["tlyric"]) > 0: return netease = NetEase() tlyric = netease.song_tlyric(self.playing_id) if tlyric == [] or tlyric == '未找到歌词翻译': return tlyric = tlyric.split('\n') self.songs[str(self.playing_id)]["tlyric"] = tlyric return def cacheSong(song_id, song_name, artist, song_url): def cacheExit(song_id, path): self.songs[str(song_id)]['cache'] = path self.cache.add(song_id, song_name, artist, song_url, cacheExit) self.cache.start_download() if 'cache' in popenArgs.keys() and os.path.isfile(popenArgs['cache']): thread = threading.Thread(target=runInThread, args=(onExit, popenArgs['cache'])) else: thread = threading.Thread(target=runInThread, args=(onExit, popenArgs['mp3_url'])) cache_thread = threading.Thread( target=cacheSong, args=(popenArgs['song_id'], popenArgs['song_name'], popenArgs['artist'], popenArgs['mp3_url'])) cache_thread.start() thread.start() lyric_download_thread = threading.Thread(target=getLyric, args=()) lyric_download_thread.start() tlyric_download_thread = threading.Thread(target=gettLyric, args=()) tlyric_download_thread.start() # returns immediately after the thread starts return thread def get_playing_id(self): return self.playing_id def recall(self): if self.info["idx"] >= len( self.info["player_list"]) and self.end_callback is not None: self.end_callback() if self.info["idx"] < 0 or self.info["idx"] >= len( self.info["player_list"]): self.info["idx"] = 0 self.stop() return self.playing_flag = True self.pause_flag = False item = self.songs[self.info["player_list"][self.info["idx"]]] self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time()) if self.notifier: self.ui.notify("Now playing", item['song_name'], item['album_name'], item['artist']) self.playing_id = item['song_id'] self.popen_recall(self.recall, item) def generate_shuffle_playing_list(self): del self.info["playing_list"][:] for i in range(0, len(self.info["player_list"])): self.info["playing_list"].append(i) random.shuffle(self.info["playing_list"]) self.info["ridx"] = 0 def new_player_list(self, type, title, datalist, offset): self.info["player_list_type"] = type self.info["player_list_title"] = title self.info["idx"] = offset del self.info["player_list"][:] del self.info["playing_list"][:] self.info["ridx"] = 0 for song in datalist: self.info["player_list"].append(str(song["song_id"])) if str(song["song_id"]) not in self.songs.keys(): self.songs[str(song["song_id"])] = song else: database_song = self.songs[str(song["song_id"])] if (database_song["song_name"] != song["song_name"] or database_song["quality"] != song["quality"]): self.songs[str(song["song_id"])] = song def append_songs(self, datalist): for song in datalist: self.info["player_list"].append(str(song["song_id"])) if str(song["song_id"]) not in self.songs.keys(): self.songs[str(song["song_id"])] = song else: database_song = self.songs[str(song["song_id"])] if database_song["song_name"] != song["song_name"] or \ database_song["quality"] != song["quality"] or \ database_song["mp3_url"] != song["mp3_url"]: if "cache" in self.songs[str(song["song_id"])].keys(): song["cache"] = self.songs[str( song["song_id"])]["cache"] self.songs[str(song["song_id"])] = song if len(datalist) > 0 and self.info["playing_mode"] == 3 or self.info[ "playing_mode"] == 4: self.generate_shuffle_playing_list() def play_and_pause(self, idx): # if same playlists && idx --> same song :: pause/resume it if self.info["idx"] == idx: if self.pause_flag: self.resume() else: self.pause() else: self.info["idx"] = idx # if it's playing if self.playing_flag: self.switch() # start new play else: self.recall() # play another def switch(self): self.stop() # wait process be killed time.sleep(0.1) self.recall() def stop(self): if self.playing_flag and self.popen_handler: self.playing_flag = False try: self.popen_handler.stdin.write("Q\n") except: pass try: self.popen_handler.kill() except: return def pause(self): if not self.playing_flag and not self.popen_handler: return self.pause_flag = True try: self.popen_handler.stdin.write("P\n") except: self.switch() item = self.songs[self.info["player_list"][self.info["idx"]]] self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time(), pause=True) def resume(self): self.pause_flag = False try: self.popen_handler.stdin.write("P\n") except: self.switch() item = self.songs[self.info["player_list"][self.info["idx"]]] self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time()) self.playing_id = item['song_id'] def next_idx(self): if self.info["idx"] < 0 or self.info["idx"] >= len( self.info["player_list"]): self.stop() return # Playing mode. 0 is ordered. 1 is orderde loop. # 2 is single song loop. 3 is single random. 4 is random loop if self.info["playing_mode"] == 0: self.info["idx"] += 1 elif self.info["playing_mode"] == 1: self.info["idx"] = (self.info["idx"] + 1) % len( self.info["player_list"]) elif self.info["playing_mode"] == 2: self.info["idx"] = self.info["idx"] elif self.info["playing_mode"] == 3: if self.info["ridx"] >= len(self.info["playing_list"]): self.generate_shuffle_playing_list() try: now_songs = self.info["playing_list"].index( self.info["idx"]) temp = self.info["playing_list"][0] self.info["playing_list"][0] = self.info["playing_list"][ now_songs] self.info["playing_list"][now_songs] = temp except: self.generate_shuffle_playing_list() elif self.info["playing_list"][ self.info["ridx"]] != self.info["idx"]: self.generate_shuffle_playing_list() try: now_songs = self.info["playing_list"].index( self.info["idx"]) temp = self.info["playing_list"][0] self.info["playing_list"][0] = self.info["playing_list"][ now_songs] self.info["playing_list"][now_songs] = temp except: self.generate_shuffle_playing_list() self.info["ridx"] += 1 if self.info["ridx"] >= len(self.info["playing_list"]): self.info["idx"] = len(self.info["playing_list"]) else: self.info["idx"] = self.info["playing_list"][self.info["ridx"]] elif self.info["playing_mode"] == 4: if self.info["ridx"] >= len(self.info["playing_list"]): self.generate_shuffle_playing_list() try: now_songs = self.info["playing_list"].index( self.info["idx"]) temp = self.info["playing_list"][0] self.info["playing_list"][0] = self.info["playing_list"][ now_songs] self.info["playing_list"][now_songs] = temp except: self.generate_shuffle_playing_list() elif self.info["playing_list"][ self.info["ridx"]] != self.info["idx"]: self.generate_shuffle_playing_list() try: now_songs = self.info["playing_list"].index( self.info["idx"]) temp = self.info["playing_list"][0] self.info["playing_list"][0] = self.info["playing_list"][ now_songs] self.info["playing_list"][now_songs] = temp except: self.generate_shuffle_playing_list() self.info["ridx"] = (self.info["ridx"] + 1) % len( self.info["player_list"]) self.info["idx"] = self.info["playing_list"][self.info["ridx"]] else: self.info["idx"] += 1 if self.playing_song_changed_callback is not None: self.playing_song_changed_callback() def next(self): self.stop() time.sleep(0.01) self.next_idx() self.recall() def prev_idx(self): if self.info["idx"] < 0 or self.info["idx"] >= len( self.info["player_list"]): self.stop() return # Playing mode. 0 is ordered. 1 is orderde loop. # 2 is single song loop. 3 is single random. 4 is random loop if self.info["playing_mode"] == 0: self.info["idx"] -= 1 elif self.info["playing_mode"] == 1: self.info["idx"] = (self.info["idx"] - 1) % len( self.info["player_list"]) elif self.info["playing_mode"] == 2: self.info["idx"] = self.info["idx"] elif self.info["playing_mode"] == 3: if self.info["ridx"] >= len(self.info["playing_list"]): self.generate_shuffle_playing_list() elif self.info["playing_list"][ self.info["ridx"]] != self.info["idx"]: self.generate_shuffle_playing_list() self.info["ridx"] -= 1 if self.info["ridx"] < 0: self.info["ridx"] = 0 return self.info["idx"] = self.info["playing_list"][self.info["ridx"]] elif self.info["playing_mode"] == 4: if self.info["ridx"] >= len(self.info["playing_list"]): self.generate_shuffle_playing_list() elif self.info["playing_list"][ self.info["ridx"]] != self.info["idx"]: self.generate_shuffle_playing_list() self.info["ridx"] = (self.info["ridx"] - 1) % len( self.info["player_list"]) self.info["idx"] = self.info["playing_list"][self.info["ridx"]] else: self.info["idx"] -= 1 if self.playing_song_changed_callback is not None: self.playing_song_changed_callback() def prev(self): self.stop() time.sleep(0.01) self.prev_idx() self.recall() def shuffle(self): self.stop() time.sleep(0.01) self.info["playing_mode"] = 3 self.generate_shuffle_playing_list() self.info["idx"] = self.info["playing_list"][self.info["ridx"]] self.recall() def volume_up(self): self.info["playing_volume"] = self.info["playing_volume"] + 7 if (self.info["playing_volume"] > 100): self.info["playing_volume"] = 100 if not self.playing_flag: return try: self.popen_handler.stdin.write("V " + str(self.info["playing_volume"]) + "\n") except: self.switch() def volume_down(self): self.info["playing_volume"] = self.info["playing_volume"] - 7 if (self.info["playing_volume"] < 0): self.info["playing_volume"] = 0 if not self.playing_flag: return try: self.popen_handler.stdin.write("V " + str(self.info["playing_volume"]) + "\n") except: self.switch() def update_size(self): try: self.ui.update_size() item = self.songs[self.info["player_list"][self.info["idx"]]] if self.playing_flag: self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time()) if self.pause_flag: self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time(), pause=True) except: pass def cacheSong1time(self, song_id, song_name, artist, song_url): def cacheExit(song_id, path): self.songs[str(song_id)]['cache'] = path self.cache.enable = False self.cache.enable = True self.cache.add(song_id, song_name, artist, song_url, cacheExit) self.cache.start_download()
class Player(object): def __init__(self): self.config = Config() self.ui = Ui() self.popen_handler = None # flag stop, prevent thread start self.playing_flag = False self.pause_flag = False self.process_length = 0 self.process_location = 0 self.process_first = False self.storage = Storage() self.info = self.storage.database['player_info'] self.songs = self.storage.database['songs'] self.playing_id = -1 self.cache = Cache() self.notifier = self.config.get_item('notifier') self.mpg123_parameters = self.config.get_item('mpg123_parameters') self.end_callback = None self.playing_song_changed_callback = None def popen_recall(self, onExit, popenArgs): ''' Runs the given args in subprocess.Popen, and then calls the function onExit when the subprocess completes. onExit is a callable object, and popenArgs is a lists/tuple of args that would give to subprocess.Popen. ''' def runInThread(onExit, arg): para = ['mpg123', '-R'] para[1:1] = self.mpg123_parameters self.popen_handler = subprocess.Popen(para, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.popen_handler.stdin.write('V ' + str(self.info['playing_volume']) + '\n') if arg: self.popen_handler.stdin.write('L ' + arg + '\n') else: self.next_idx() onExit() return self.process_first = True while True: if self.playing_flag is False: break strout = self.popen_handler.stdout.readline() if re.match('^\@F.*$', strout): process_data = strout.split(' ') process_location = float(process_data[4]) if self.process_first: self.process_length = process_location self.process_first = False self.process_location = 0 else: self.process_location = self.process_length - process_location # NOQA continue elif strout[:2] == '@E': # get a alternative url from new api sid = popenArgs['song_id'] new_url = NetEase().songs_detail_new_api([sid])[0]['url'] if new_url is None: log.warning(('Song {} is unavailable ' 'due to copyright issue').format(sid)) break log.warning( 'Song {} is not compatible with old api.'.format(sid)) popenArgs['mp3_url'] = new_url self.popen_handler.stdin.write('\nL ' + new_url + '\n') self.popen_handler.stdout.readline() elif strout == '@P 0\n': self.popen_handler.stdin.write('Q\n') self.popen_handler.kill() break if self.playing_flag: self.next_idx() onExit() return def getLyric(): if 'lyric' not in self.songs[str(self.playing_id)].keys(): self.songs[str(self.playing_id)]['lyric'] = [] if len(self.songs[str(self.playing_id)]['lyric']) > 0: return netease = NetEase() lyric = netease.song_lyric(self.playing_id) if lyric == [] or lyric == '未找到歌词': return lyric = lyric.split('\n') self.songs[str(self.playing_id)]['lyric'] = lyric return def gettLyric(): if 'tlyric' not in self.songs[str(self.playing_id)].keys(): self.songs[str(self.playing_id)]['tlyric'] = [] if len(self.songs[str(self.playing_id)]['tlyric']) > 0: return netease = NetEase() tlyric = netease.song_tlyric(self.playing_id) if tlyric == [] or tlyric == '未找到歌词翻译': return tlyric = tlyric.split('\n') self.songs[str(self.playing_id)]['tlyric'] = tlyric return def cacheSong(song_id, song_name, artist, song_url): def cacheExit(song_id, path): self.songs[str(song_id)]['cache'] = path self.cache.add(song_id, song_name, artist, song_url, cacheExit) self.cache.start_download() if 'cache' in popenArgs.keys() and os.path.isfile(popenArgs['cache']): thread = threading.Thread(target=runInThread, args=(onExit, popenArgs['cache'])) else: thread = threading.Thread(target=runInThread, args=(onExit, popenArgs['mp3_url'])) cache_thread = threading.Thread( target=cacheSong, args=(popenArgs['song_id'], popenArgs['song_name'], popenArgs['artist'], popenArgs['mp3_url'])) cache_thread.start() thread.start() lyric_download_thread = threading.Thread(target=getLyric, args=()) lyric_download_thread.start() tlyric_download_thread = threading.Thread(target=gettLyric, args=()) tlyric_download_thread.start() # returns immediately after the thread starts return thread def get_playing_id(self): return self.playing_id def recall(self): if self.info['idx'] >= len( self.info['player_list']) and self.end_callback is not None: log.debug('Callback') self.end_callback() if self.info['idx'] < 0 or self.info['idx'] >= len( self.info['player_list']): self.info['idx'] = 0 self.stop() return self.playing_flag = True self.pause_flag = False item = self.songs[self.info['player_list'][self.info['idx']]] self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time()) if self.notifier: self.ui.notify('Now playing', item['song_name'], item['album_name'], item['artist']) self.playing_id = item['song_id'] self.popen_recall(self.recall, item) def generate_shuffle_playing_list(self): del self.info['playing_list'][:] for i in range(0, len(self.info['player_list'])): self.info['playing_list'].append(i) random.shuffle(self.info['playing_list']) self.info['ridx'] = 0 def new_player_list(self, type, title, datalist, offset): self.info['player_list_type'] = type self.info['player_list_title'] = title self.info['idx'] = offset del self.info['player_list'][:] del self.info['playing_list'][:] self.info['ridx'] = 0 for song in datalist: self.info['player_list'].append(str(song['song_id'])) if str(song['song_id']) not in self.songs.keys(): self.songs[str(song['song_id'])] = song else: database_song = self.songs[str(song['song_id'])] if (database_song['song_name'] != song['song_name'] or database_song['quality'] != song['quality']): self.songs[str(song['song_id'])] = song def append_songs(self, datalist): for song in datalist: self.info['player_list'].append(str(song['song_id'])) if str(song['song_id']) not in self.songs.keys(): self.songs[str(song['song_id'])] = song else: database_song = self.songs[str(song['song_id'])] cond = any([ database_song[k] != song[k] for k in ('song_name', 'quality', 'mp3_url') ]) if cond: if 'cache' in self.songs[str(song['song_id'])].keys(): song['cache'] = self.songs[str( song['song_id'])]['cache'] self.songs[str(song['song_id'])] = song if len(datalist) > 0 and self.info['playing_mode'] == 3 or self.info[ 'playing_mode'] == 4: self.generate_shuffle_playing_list() def play_and_pause(self, idx): # if same playlists && idx --> same song :: pause/resume it if self.info['idx'] == idx: if self.pause_flag: self.resume() else: self.pause() else: self.info['idx'] = idx # if it's playing if self.playing_flag: self.switch() # start new play else: self.recall() # play another def switch(self): self.stop() # wait process be killed time.sleep(0.1) self.recall() def stop(self): if self.playing_flag and self.popen_handler: self.playing_flag = False self.popen_handler.stdin.write('Q\n') try: self.popen_handler.kill() except OSError as e: log.error(e) return def pause(self): if not self.playing_flag and not self.popen_handler: return self.pause_flag = True self.popen_handler.stdin.write('P\n') item = self.songs[self.info['player_list'][self.info['idx']]] self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time(), pause=True) def resume(self): self.pause_flag = False self.popen_handler.stdin.write('P\n') item = self.songs[self.info['player_list'][self.info['idx']]] self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time()) self.playing_id = item['song_id'] def _swap_song(self): plist = self.info['playing_list'] now_songs = plist.index(self.info['idx']) plist[0], plist[now_songs] = plist[now_songs], plist[0] def _is_idx_valid(self): return 0 <= self.info['idx'] < len(self.info['player_list']) def _inc_idx(self): if self.info['idx'] < len(self.info['player_list']): self.info['idx'] += 1 def _dec_idx(self): if self.info['idx'] > 0: self.info['idx'] -= 1 def _need_to_shuffle(self): playing_list = self.info['playing_list'] ridx = self.info['ridx'] idx = self.info['idx'] if ridx >= len(playing_list) or playing_list[ridx] != idx: return True else: return False def next_idx(self): if not self._is_idx_valid(): self.stop() return playlist_len = len(self.info['player_list']) playinglist_len = len(self.info['playing_list']) # Playing mode. 0 is ordered. 1 is orderde loop. # 2 is single song loop. 3 is single random. 4 is random loop if self.info['playing_mode'] == 0: self._inc_idx() elif self.info['playing_mode'] == 1: self.info['idx'] = (self.info['idx'] + 1) % playlist_len elif self.info['playing_mode'] == 2: self.info['idx'] = self.info['idx'] elif self.info['playing_mode'] == 3 or self.info['playing_mode'] == 4: if self._need_to_shuffle(): self.generate_shuffle_playing_list() playinglist_len = len(self.info['playing_list']) # When you regenerate playing list # you should keep previous song same. try: self._swap_song() except Exception as e: log.error(e) self.info['ridx'] += 1 # Out of border if self.info['playing_mode'] == 4: self.info['ridx'] %= playinglist_len if self.info['ridx'] >= playinglist_len: self.info['idx'] = playlist_len else: self.info['idx'] = self.info['playing_list'][self.info['ridx']] else: self.info['idx'] += 1 if self.playing_song_changed_callback is not None: self.playing_song_changed_callback() def next(self): self.stop() time.sleep(0.01) self.next_idx() self.recall() def prev_idx(self): if not self._is_idx_valid(): self.stop() return playlist_len = len(self.info['player_list']) playinglist_len = len(self.info['playing_list']) # Playing mode. 0 is ordered. 1 is orderde loop. # 2 is single song loop. 3 is single random. 4 is random loop if self.info['playing_mode'] == 0: self._dec_idx() elif self.info['playing_mode'] == 1: self.info['idx'] = (self.info['idx'] - 1) % playlist_len elif self.info['playing_mode'] == 2: self.info['idx'] = self.info['idx'] elif self.info['playing_mode'] == 3 or self.info['playing_mode'] == 4: if self._need_to_shuffle(): self.generate_shuffle_playing_list() playinglist_len = len(self.info['playing_list']) self.info['ridx'] -= 1 if self.info['ridx'] < 0: if self.info['playing_mode'] == 3: self.info['ridx'] = 0 else: self.info['ridx'] %= playinglist_len self.info['idx'] = self.info['playing_list'][self.info['ridx']] else: self.info['idx'] -= 1 if self.playing_song_changed_callback is not None: self.playing_song_changed_callback() def prev(self): self.stop() time.sleep(0.01) self.prev_idx() self.recall() def shuffle(self): self.stop() time.sleep(0.01) self.info['playing_mode'] = 3 self.generate_shuffle_playing_list() self.info['idx'] = self.info['playing_list'][self.info['ridx']] self.recall() def volume_up(self): self.info['playing_volume'] = self.info['playing_volume'] + 7 if (self.info['playing_volume'] > 100): self.info['playing_volume'] = 100 if not self.playing_flag: return self.popen_handler.stdin.write('V ' + str(self.info['playing_volume']) + '\n') def volume_down(self): self.info['playing_volume'] = self.info['playing_volume'] - 7 if (self.info['playing_volume'] < 0): self.info['playing_volume'] = 0 if not self.playing_flag: return self.popen_handler.stdin.write('V ' + str(self.info['playing_volume']) + '\n') def update_size(self): try: self.ui.update_size() item = self.songs[self.info['player_list'][self.info['idx']]] if self.playing_flag: self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time()) if self.pause_flag: self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time(), pause=True) except Exception as e: log.error(e) pass def cacheSong1time(self, song_id, song_name, artist, song_url): def cacheExit(song_id, path): self.songs[str(song_id)]['cache'] = path self.cache.enable = False self.cache.enable = True self.cache.add(song_id, song_name, artist, song_url, cacheExit) self.cache.start_download()
class Player: def __init__(self): self.config = Config() self.ui = Ui() self.popen_handler = None # flag stop, prevent thread start self.playing_flag = False self.pause_flag = False self.process_length = 0 self.process_location = 0 self.process_first = False self.storage = Storage() self.info = self.storage.database["player_info"] self.songs = self.storage.database["songs"] self.playing_id = -1 self.cache = Cache() self.notifier = self.config.get_item("notifier") self.mpg123_parameters = self.config.get_item("mpg123_parameters") self.end_callback = None self.playing_song_changed_callback = None def popen_recall(self, onExit, popenArgs): """ Runs the given args in subprocess.Popen, and then calls the function onExit when the subprocess completes. onExit is a callable object, and popenArgs is a lists/tuple of args that would give to subprocess.Popen. """ def runInThread(onExit, arg): para = ['mpg123', '-R'] para[1:1] = self.mpg123_parameters self.popen_handler = subprocess.Popen(para, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.popen_handler.stdin.write("V " + str(self.info[ "playing_volume"]) + "\n") if arg: self.popen_handler.stdin.write("L " + arg + "\n") else: self.next_idx() onExit() return self.process_first = True while True: if self.playing_flag is False: break strout = self.popen_handler.stdout.readline() if re.match("^\@F.*$", strout): process_data = strout.split(" ") process_location = float(process_data[4]) if self.process_first: self.process_length = process_location self.process_first = False self.process_location = 0 else: self.process_location = self.process_length - process_location # NOQA continue elif strout[:2] == '@E': # get a alternative url from new api sid = popenArgs['song_id'] new_url = NetEase().songs_detail_new_api([sid])[0]['url'] if new_url is None: log.warning(('Song {} is unavailable ' 'due to copyright issue').format(sid)) break log.error('Song {} is not compatible with old api.'.format( sid)) self.popen_handler.stdin.write("\nL " + new_url + "\n") self.popen_handler.stdout.readline() elif strout == "@P 0\n": self.popen_handler.stdin.write("Q\n") self.popen_handler.kill() break if self.playing_flag: self.next_idx() onExit() return def getLyric(): if 'lyric' not in self.songs[str(self.playing_id)].keys(): self.songs[str(self.playing_id)]["lyric"] = [] if len(self.songs[str(self.playing_id)]["lyric"]) > 0: return netease = NetEase() lyric = netease.song_lyric(self.playing_id) if lyric == [] or lyric == '未找到歌词': return lyric = lyric.split('\n') self.songs[str(self.playing_id)]["lyric"] = lyric return def gettLyric(): if 'tlyric' not in self.songs[str(self.playing_id)].keys(): self.songs[str(self.playing_id)]["tlyric"] = [] if len(self.songs[str(self.playing_id)]["tlyric"]) > 0: return netease = NetEase() tlyric = netease.song_tlyric(self.playing_id) if tlyric == [] or tlyric == '未找到歌词翻译': return tlyric = tlyric.split('\n') self.songs[str(self.playing_id)]["tlyric"] = tlyric return def cacheSong(song_id, song_name, artist, song_url): def cacheExit(song_id, path): self.songs[str(song_id)]['cache'] = path self.cache.add(song_id, song_name, artist, song_url, cacheExit) self.cache.start_download() if 'cache' in popenArgs.keys() and os.path.isfile(popenArgs['cache']): thread = threading.Thread(target=runInThread, args=(onExit, popenArgs['cache'])) else: thread = threading.Thread(target=runInThread, args=(onExit, popenArgs['mp3_url'])) cache_thread = threading.Thread( target=cacheSong, args=(popenArgs['song_id'], popenArgs['song_name'], popenArgs[ 'artist'], popenArgs['mp3_url'])) cache_thread.start() thread.start() lyric_download_thread = threading.Thread(target=getLyric, args=()) lyric_download_thread.start() tlyric_download_thread = threading.Thread(target=gettLyric, args=()) tlyric_download_thread.start() # returns immediately after the thread starts return thread def get_playing_id(self): return self.playing_id def recall(self): if self.info["idx"] >= len(self.info["player_list"]) and self.end_callback is not None: self.end_callback() if self.info["idx"] < 0 or self.info["idx"] >= len(self.info["player_list"]): self.info["idx"] = 0 self.stop() return self.playing_flag = True self.pause_flag = False item = self.songs[self.info["player_list"][self.info["idx"]]] self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time()) if self.notifier: self.ui.notify("Now playing", item['song_name'], item['album_name'], item['artist']) self.playing_id = item['song_id'] self.popen_recall(self.recall, item) def generate_shuffle_playing_list(self): del self.info["playing_list"][:] for i in range(0, len(self.info["player_list"])): self.info["playing_list"].append(i) random.shuffle(self.info["playing_list"]) self.info["ridx"] = 0 def new_player_list(self, type, title, datalist, offset): self.info["player_list_type"] = type self.info["player_list_title"] = title self.info["idx"] = offset del self.info["player_list"][:] del self.info["playing_list"][:] self.info["ridx"] = 0 for song in datalist: self.info["player_list"].append(str(song["song_id"])) if str(song["song_id"]) not in self.songs.keys(): self.songs[str(song["song_id"])] = song else: database_song = self.songs[str(song["song_id"])] if (database_song["song_name"] != song["song_name"] or database_song["quality"] != song["quality"]): self.songs[str(song["song_id"])] = song def append_songs(self, datalist): for song in datalist: self.info["player_list"].append(str(song["song_id"])) if str(song["song_id"]) not in self.songs.keys(): self.songs[str(song["song_id"])] = song else: database_song = self.songs[str(song["song_id"])] if database_song["song_name"] != song["song_name"] or \ database_song["quality"] != song["quality"] or \ database_song["mp3_url"] != song["mp3_url"]: if "cache" in self.songs[str(song["song_id"])].keys(): song["cache"] = self.songs[str(song["song_id"])][ "cache"] self.songs[str(song["song_id"])] = song if len(datalist) > 0 and self.info["playing_mode"] == 3 or self.info[ "playing_mode"] == 4: self.generate_shuffle_playing_list() def play_and_pause(self, idx): # if same playlists && idx --> same song :: pause/resume it if self.info["idx"] == idx: if self.pause_flag: self.resume() else: self.pause() else: self.info["idx"] = idx # if it's playing if self.playing_flag: self.switch() # start new play else: self.recall() # play another def switch(self): self.stop() # wait process be killed time.sleep(0.1) self.recall() def stop(self): if self.playing_flag and self.popen_handler: self.playing_flag = False self.popen_handler.stdin.write("Q\n") try: self.popen_handler.kill() except OSError as e: log.error(e) return def pause(self): if not self.playing_flag and not self.popen_handler: return self.pause_flag = True self.popen_handler.stdin.write("P\n") item = self.songs[self.info["player_list"][self.info["idx"]]] self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time(), pause=True) def resume(self): self.pause_flag = False self.popen_handler.stdin.write("P\n") item = self.songs[self.info["player_list"][self.info["idx"]]] self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time()) self.playing_id = item['song_id'] def _swap_song(self): plist = self.info["playing_list"] now_songs = plist.index(self.info["idx"]) plist[0], plist[now_songs] = plist[now_songs], plist[0] def _is_idx_valid(self): return 0 <= self.info["idx"] < len(self.info["player_list"]) def _inc_idx(self): if self.info["idx"] < len(self.info["player_list"]) - 1: self.info["idx"] += 1 def _dec_idx(self): if self.info["idx"] > 0: self.info["idx"] -= 1 def next_idx(self): if not self._is_idx_valid(): self.stop() return playlist_len = len(self.info["player_list"]) playinglist_len = len(self.info["playing_list"]) # Playing mode. 0 is ordered. 1 is orderde loop. # 2 is single song loop. 3 is single random. 4 is random loop if self.info["playing_mode"] == 0: self._inc_idx() elif self.info["playing_mode"] == 1: self.info["idx"] = (self.info["idx"] + 1) % playlist_len elif self.info["playing_mode"] == 2: self.info["idx"] = self.info["idx"] elif self.info["playing_mode"] == 3 or self.info["playing_mode"] == 4: # First is out of border. Second is change playlist. if self.info["ridx"] >= playinglist_len or self.info["playing_list"][self.info["ridx"]] != self.info["idx"]: self.generate_shuffle_playing_list() playinglist_len = len(self.info["playing_list"]) # When you regenerate playing list, you should keep previous song same. try: self._swap_song() except Exception as e: log.error(e) self.info["ridx"] += 1 # Out of border if self.info["playing_mode"] == 4: self.info["ridx"] %= playinglist_len if self.info["ridx"] >= playinglist_len: self.info["idx"] = playlist_len else: self.info["idx"] = self.info["playing_list"][self.info["ridx"]] else: self.info["idx"] += 1 if self.playing_song_changed_callback is not None: self.playing_song_changed_callback() def next(self): self.stop() time.sleep(0.01) self.next_idx() self.recall() def prev_idx(self): if not self._is_idx_valid(): self.stop() return playlist_len = len(self.info["player_list"]) playinglist_len = len(self.info["playing_list"]) # Playing mode. 0 is ordered. 1 is orderde loop. # 2 is single song loop. 3 is single random. 4 is random loop if self.info["playing_mode"] == 0: self._dec_idx() elif self.info["playing_mode"] == 1: self.info["idx"] = (self.info["idx"] - 1) % playlist_len elif self.info["playing_mode"] == 2: self.info["idx"] = self.info["idx"] elif self.info["playing_mode"] == 3 or self.info["playing_mode"] == 4: if self.info["ridx"] >= len(self.info["playing_list"]) or \ self.info["playing_list"][self.info["ridx"]] != self.info["idx"]: self.generate_shuffle_playing_list() playinglist_len = len(self.info["playing_list"]) self.info["ridx"] -= 1 if self.info["ridx"] < 0: if self.info["playing_mode"] == 3: self.info["ridx"] = 0 else: self.info["ridx"] %= playinglist_len self.info["idx"] = self.info["playing_list"][self.info["ridx"]] else: self.info["idx"] -= 1 if self.playing_song_changed_callback is not None: self.playing_song_changed_callback() def prev(self): self.stop() time.sleep(0.01) self.prev_idx() self.recall() def shuffle(self): self.stop() time.sleep(0.01) self.info["playing_mode"] = 3 self.generate_shuffle_playing_list() self.info["idx"] = self.info["playing_list"][self.info["ridx"]] self.recall() def volume_up(self): self.info["playing_volume"] = self.info["playing_volume"] + 7 if (self.info["playing_volume"] > 100): self.info["playing_volume"] = 100 if not self.playing_flag: return self.popen_handler.stdin.write("V " + str(self.info[ "playing_volume"]) + "\n") def volume_down(self): self.info["playing_volume"] = self.info["playing_volume"] - 7 if (self.info["playing_volume"] < 0): self.info["playing_volume"] = 0 if not self.playing_flag: return self.popen_handler.stdin.write("V " + str(self.info[ "playing_volume"]) + "\n") def update_size(self): try: self.ui.update_size() item = self.songs[self.info["player_list"][self.info["idx"]]] if self.playing_flag: self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time()) if self.pause_flag: self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time(), pause=True) except Exception as e: log.error(e) pass def cacheSong1time(self, song_id, song_name, artist, song_url): def cacheExit(song_id, path): self.songs[str(song_id)]['cache'] = path self.cache.enable = False self.cache.enable = True self.cache.add(song_id, song_name, artist, song_url, cacheExit) self.cache.start_download()
class Player: def __init__(self): self.config = Config() self.ui = Ui() self.popen_handler = None # flag stop, prevent thread start self.playing_flag = False self.pause_flag = False self.process_length = 0 self.process_location = 0 self.process_first = False self.storage = Storage() self.info = self.storage.database["player_info"] self.songs = self.storage.database["songs"] self.playing_id = -1 self.cache = Cache() self.notifier = self.config.get_item("notifier") self.mpg123_parameters = self.config.get_item("mpg123_parameters") self.end_callback = None self.playing_song_changed_callback = None def popen_recall(self, onExit, popenArgs): """ Runs the given args in a subprocess.Popen, and then calls the function onExit when the subprocess completes. onExit is a callable object, and popenArgs is a lists/tuple of args that would give to subprocess.Popen. """ def runInThread(onExit, popenArgs): para = ['mpg123', '-R'] para[1:1] = self.mpg123_parameters self.popen_handler = subprocess.Popen(para, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.popen_handler.stdin.write("V " + str(self.info["playing_volume"]) + "\n") self.popen_handler.stdin.write("L " + popenArgs + "\n") self.process_first = True while (True): if self.playing_flag == False: break try: strout = self.popen_handler.stdout.readline() except IOError: break if re.match("^\@F.*$", strout): process_data = strout.split(" ") process_location = float(process_data[4]) if self.process_first: self.process_length = process_location self.process_first = False self.process_location = 0 else: self.process_location = self.process_length - process_location continue if strout == "@P 0\n": self.popen_handler.stdin.write("Q\n") self.popen_handler.kill() break if self.playing_flag: self.next_idx() onExit() return def getLyric(): if 'lyric' not in self.songs[str(self.playing_id)].keys(): self.songs[str(self.playing_id)]["lyric"] = [] if len(self.songs[str(self.playing_id)]["lyric"]) > 0: return netease = NetEase() lyric = netease.song_lyric(self.playing_id) if lyric == [] or lyric == '未找到歌词': return lyric = lyric.split('\n') self.songs[str(self.playing_id)]["lyric"] = lyric return def gettLyric(): if 'tlyric' not in self.songs[str(self.playing_id)].keys(): self.songs[str(self.playing_id)]["tlyric"] = [] if len(self.songs[str(self.playing_id)]["tlyric"]) > 0: return netease = NetEase() tlyric = netease.song_tlyric(self.playing_id) if tlyric == [] or tlyric == '未找到歌词翻译': return tlyric = tlyric.split('\n') self.songs[str(self.playing_id)]["tlyric"] = tlyric return def cacheSong(song_id, song_name, artist, song_url): def cacheExit(song_id, path): self.songs[str(song_id)]['cache'] = path self.cache.add(song_id, song_name, artist, song_url, cacheExit) self.cache.start_download() if 'cache' in popenArgs.keys() and os.path.isfile(popenArgs['cache']): thread = threading.Thread(target=runInThread, args=(onExit, popenArgs['cache'])) else: thread = threading.Thread(target=runInThread, args=(onExit, popenArgs['mp3_url'])) cache_thread = threading.Thread(target=cacheSong, args=( popenArgs['song_id'], popenArgs['song_name'], popenArgs['artist'], popenArgs['mp3_url'])) cache_thread.start() thread.start() lyric_download_thread = threading.Thread(target=getLyric, args=()) lyric_download_thread.start() tlyric_download_thread = threading.Thread(target=gettLyric, args=()) tlyric_download_thread.start() # returns immediately after the thread starts return thread def get_playing_id(self): return self.playing_id def recall(self): if self.info["idx"] >= len(self.info["player_list"]) and self.end_callback != None: self.end_callback() if self.info["idx"] < 0 or self.info["idx"] >= len(self.info["player_list"]): self.info["idx"] = 0 self.stop() return self.playing_flag = True self.pause_flag = False item = self.songs[self.info["player_list"][self.info["idx"]]] self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time()) if self.notifier == True: self.ui.notify("Now playing", item['song_name'], item['album_name'], item['artist']) self.playing_id = item['song_id'] self.popen_recall(self.recall, item) def generate_shuffle_playing_list(self): del self.info["playing_list"][:] for i in range(0, len(self.info["player_list"])): self.info["playing_list"].append(i) random.shuffle(self.info["playing_list"]) self.info["ridx"] = 0 def new_player_list(self, type, title, datalist, offset): self.info["player_list_type"] = type self.info["player_list_title"] = title self.info["idx"] = offset del self.info["player_list"][:] del self.info["playing_list"][:] self.info["ridx"] = 0 for song in datalist: self.info["player_list"].append(str(song["song_id"])) if str(song["song_id"]) not in self.songs.keys(): self.songs[str(song["song_id"])] = song else: database_song = self.songs[str(song["song_id"])] if (database_song["song_name"] != song["song_name"] or database_song["quality"] != song["quality"]): self.songs[str(song["song_id"])] = song def append_songs(self, datalist): for song in datalist: self.info["player_list"].append(str(song["song_id"])) if str(song["song_id"]) not in self.songs.keys(): self.songs[str(song["song_id"])] = song else: database_song = self.songs[str(song["song_id"])] if database_song["song_name"] != song["song_name"] or \ database_song["quality"] != song["quality"] or \ database_song["mp3_url"] != song["mp3_url"]: if "cache" in self.songs[str(song["song_id"])].keys(): song["cache"] = self.songs[str(song["song_id"])]["cache"] self.songs[str(song["song_id"])] = song if len(datalist) > 0 and self.info["playing_mode"] == 3 or self.info["playing_mode"] == 4: self.generate_shuffle_playing_list() def play_and_pause(self, idx): # if same playlists && idx --> same song :: pause/resume it if self.info["idx"] == idx: if self.pause_flag: self.resume() else: self.pause() else: self.info["idx"] = idx # if it's playing if self.playing_flag: self.switch() # start new play else: self.recall() # play another def switch(self): self.stop() # wait process be killed time.sleep(0.1) self.recall() def stop(self): if self.playing_flag and self.popen_handler: self.playing_flag = False try: self.popen_handler.stdin.write("Q\n") except: pass try: self.popen_handler.kill() except: return def pause(self): if not self.playing_flag and not self.popen_handler: return self.pause_flag = True try: self.popen_handler.stdin.write("P\n") except: self.switch() item = self.songs[self.info["player_list"][self.info["idx"]]] self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time(), pause=True) def resume(self): self.pause_flag = False try: self.popen_handler.stdin.write("P\n") except: self.switch() item = self.songs[self.info["player_list"][self.info["idx"]]] self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time()) self.playing_id = item['song_id'] def next_idx(self): if self.info["idx"] < 0 or self.info["idx"] >= len(self.info["player_list"]): self.stop() return # Playing mode. 0 is ordered. 1 is orderde loop. 2 is single song loop. 3 is single random. 4 is random loop if self.info["playing_mode"] == 0: self.info["idx"] += 1 elif self.info["playing_mode"] == 1: self.info["idx"] = (self.info["idx"] + 1) % len(self.info["player_list"]) elif self.info["playing_mode"] == 2: self.info["idx"] = self.info["idx"] elif self.info["playing_mode"] == 3: if self.info["ridx"] >= len(self.info["playing_list"]): self.generate_shuffle_playing_list() try: now_songs = self.info["playing_list"].index(self.info["idx"]) temp = self.info["playing_list"][0] self.info["playing_list"][0] = self.info["playing_list"][now_songs] self.info["playing_list"][now_songs] = temp except: self.generate_shuffle_playing_list() elif self.info["playing_list"][self.info["ridx"]] != self.info["idx"]: self.generate_shuffle_playing_list() try: now_songs = self.info["playing_list"].index(self.info["idx"]) temp = self.info["playing_list"][0] self.info["playing_list"][0] = self.info["playing_list"][now_songs] self.info["playing_list"][now_songs] = temp except: self.generate_shuffle_playing_list() self.info["ridx"] += 1 if self.info["ridx"] >= len(self.info["playing_list"]): self.info["idx"] = len(self.info["playing_list"]) else: self.info["idx"] = self.info["playing_list"][self.info["ridx"]] elif self.info["playing_mode"] == 4: if self.info["ridx"] >= len(self.info["playing_list"]): self.generate_shuffle_playing_list() try: now_songs = self.info["playing_list"].index(self.info["idx"]) temp = self.info["playing_list"][0] self.info["playing_list"][0] = self.info["playing_list"][now_songs] self.info["playing_list"][now_songs] = temp except: self.generate_shuffle_playing_list() elif self.info["playing_list"][self.info["ridx"]] != self.info["idx"]: self.generate_shuffle_playing_list() try: now_songs = self.info["playing_list"].index(self.info["idx"]) temp = self.info["playing_list"][0] self.info["playing_list"][0] = self.info["playing_list"][now_songs] self.info["playing_list"][now_songs] = temp except: self.generate_shuffle_playing_list() self.info["ridx"] = (self.info["ridx"] + 1) % len(self.info["player_list"]) self.info["idx"] = self.info["playing_list"][self.info["ridx"]] else: self.info["idx"] += 1 if self.playing_song_changed_callback is not None: self.playing_song_changed_callback() def next(self): self.stop() time.sleep(0.01) self.next_idx() self.recall() def prev_idx(self): if self.info["idx"] < 0 or self.info["idx"] >= len(self.info["player_list"]): self.stop() return # Playing mode. 0 is ordered. 1 is orderde loop. 2 is single song loop. 3 is single random. 4 is random loop if self.info["playing_mode"] == 0: self.info["idx"] -= 1 elif self.info["playing_mode"] == 1: self.info["idx"] = (self.info["idx"] - 1) % len(self.info["player_list"]) elif self.info["playing_mode"] == 2: self.info["idx"] = self.info["idx"] elif self.info["playing_mode"] == 3: if self.info["ridx"] >= len(self.info["playing_list"]): self.generate_shuffle_playing_list() elif self.info["playing_list"][self.info["ridx"]] != self.info["idx"]: self.generate_shuffle_playing_list() self.info["ridx"] -= 1 if self.info["ridx"] < 0: self.info["ridx"] = 0 return self.info["idx"] = self.info["playing_list"][self.info["ridx"]] elif self.info["playing_mode"] == 4: if self.info["ridx"] >= len(self.info["playing_list"]): self.generate_shuffle_playing_list() elif self.info["playing_list"][self.info["ridx"]] != self.info["idx"]: self.generate_shuffle_playing_list() self.info["ridx"] = (self.info["ridx"] - 1) % len(self.info["player_list"]) self.info["idx"] = self.info["playing_list"][self.info["ridx"]] else: self.info["idx"] -= 1 if self.playing_song_changed_callback is not None: self.playing_song_changed_callback() def prev(self): self.stop() time.sleep(0.01) self.prev_idx() self.recall() def shuffle(self): self.stop() time.sleep(0.01) self.info["playing_mode"] = 3 self.generate_shuffle_playing_list() self.info["idx"] = self.info["playing_list"][self.info["ridx"]] self.recall() def volume_up(self): self.info["playing_volume"] = self.info["playing_volume"] + 7 if (self.info["playing_volume"] > 100): self.info["playing_volume"] = 100 if not self.playing_flag: return try: self.popen_handler.stdin.write("V " + str(self.info["playing_volume"]) + "\n") except: self.switch() def volume_down(self): self.info["playing_volume"] = self.info["playing_volume"] - 7 if (self.info["playing_volume"] < 0): self.info["playing_volume"] = 0 if not self.playing_flag: return try: self.popen_handler.stdin.write("V " + str(self.info["playing_volume"]) + "\n") except: self.switch() def update_size(self): try: self.ui.update_size() item = self.songs[self.info["player_list"][self.info["idx"]]] if self.playing_flag: self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time()) if self.pause_flag: self.ui.build_playinfo(item['song_name'], item['artist'], item['album_name'], item['quality'], time.time(), pause=True) except: pass def cacheSong1time(self, song_id, song_name, artist, song_url): def cacheExit(song_id, path): self.songs[str(song_id)]['cache'] = path self.cache.enable = False self.cache.enable = True self.cache.add(song_id, song_name, artist, song_url, cacheExit) self.cache.start_download()