def __init__(self): super().__init__() self.__playlist = Playlist() self.__rds_updater = RdsUpdater() self.__resume_file = config.get_resume_file() self.__skip = threading.Event() self.output_stream = BytearrayIO()
def __init__(self, bt_addr): super().__init__() self.__rds_updater = RdsUpdater() self.__bt_addr = bt_addr self.__cmd_arr = [ "sudo", "dbus-send", "--system", "--type=method_call", "--dest=org.bluez", "/org/bluez/hci0/dev_" + bt_addr.replace(":", "_").upper() + "/player0", "org.bluez.MediaPlayer1.Pause" ]
def __init__(self): super().__init__() self.__playlist = Playlist() self.__rds_updater = RdsUpdater() self.__resume_file = config.get_resume_file()
class StoragePlayer(Player): stream = None __tmp_stream = None __terminating = False __playlist = None __now_playing = None __timer = None __resume_file = None __rds_updater = None def __init__(self): super().__init__() self.__playlist = Playlist() self.__rds_updater = RdsUpdater() self.__resume_file = config.get_resume_file() def playback_position(self): return self.__timer.get_time() def __update_playback_position_thread(self): while not self.__terminating: if self.__now_playing is not None: self.__now_playing["position"] = self.playback_position() with open(self.__resume_file, "w") as f: j = json.dumps(self.__now_playing) f.write(j) time.sleep(5) def __update_playback_position(self): threading.Thread(target=self.__update_playback_position_thread).start() def __retrive_last_boot_playback(self): if not path.isfile(self.__resume_file): # start the timer from 0 self.__timer = Timer() return with open(self.__resume_file) as file: song = json.load(file) if song is not None: self.__playlist.add(song) self.__playlist.set_resuming() # resume the timer from previous state try: self.__timer = Timer(song["position"]) except TypeError: self.__timer = Timer() def run(self): threading.Thread(target=self.__run).start() def __run(self): self.__retrive_last_boot_playback() self.__timer.start() self.__rds_updater.run() self.__update_playback_position() for song in self.__playlist: self.__now_playing = song print("storage_player playing:", song["path"]) self.play(song) # blocking if self.__terminating: return def play(self, song): resume_time = song.get("position") if resume_time is not None: res = str(resume_time) else: res = "0" self.__rds_updater.set(song) self.__tmp_stream = None self.stream = subprocess.Popen([ "ffmpeg", "-i", song["path"], "-ss", res, "-vn", "-f", "wav", "pipe:1" ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # set the player to non-blocking output: flags = fcntl(self.stream.stdout, F_GETFL) # get current stdout flags fcntl(self.stream.stdout, F_SETFL, flags | O_NONBLOCK) # Wait until process terminates while self.stream.poll() is None: time.sleep(0.02) self.__timer.reset() def pause(self): pause_sound = config.get_sounds_folder() + config.get_stop_sound() self.__timer.pause() self.__tmp_stream = self.stream.stdout self.stream.stdout = subprocess.Popen( ["ffmpeg", "-i", pause_sound, "-vn", "-f", "wav", "pipe:1"], stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout self.stream.send_signal(signal.SIGSTOP) def resume(self): if self.__tmp_stream is not None: self.stream.stdout = self.__tmp_stream self.stream.send_signal(signal.SIGCONT) self.__timer.resume() def next(self): self.stream.kill() def previous(self): self.__playlist.back(n=1) self.next() def repeat(self): self.__playlist.back() def fast_forward(self): pass def rewind(self): self.__playlist.back() self.next() def stop(self): self.__terminating = True self.__timer.stop() self.stream.kill() self.__rds_updater.stop() def song_name(self): return self.__now_playing["title"] def song_artist(self): return self.__now_playing["artist"] def song_year(self): return self.__now_playing["year"] def song_album(self): return self.__now_playing["album"]
class BtPlayer(Player): __bt_addr = None __cmd_arr = None __rds_updater = None __now_playing = None def __init__(self, bt_addr): super().__init__() self.__rds_updater = RdsUpdater() self.__bt_addr = bt_addr self.__cmd_arr = [ "sudo", "dbus-send", "--system", "--type=method_call", "--dest=org.bluez", "/org/bluez/hci0/dev_" + bt_addr.replace(":", "_").upper() + "/player0", "org.bluez.MediaPlayer1.Pause" ] def playback_position(self): pass def resume(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Play" subprocess.call(self.__cmd_arr) def run(self): threading.Thread(target=self.__run).start() def __run(self): print("playing bluetooth:", self.__bt_addr) dev = "bluealsa:HCI=hci0,DEV=" + self.__bt_addr self.stream = subprocess.Popen( ["arecord", "-D", dev, "-f", "cd", "-c", "2"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # set the player to non-blocking output: flags = fcntl(self.stream.stdout, F_GETFL) # get current stdout flags fcntl(self.stream.stdout, F_SETFL, flags | O_NONBLOCK) self.output_stream = self.stream.stdout self.ready.set() self.__rds_updater.run() # Wait until process terminates while self.stream.poll() is None: self.get_now_playing() self.__rds_updater.set(self.__now_playing) time.sleep(3) def get_now_playing(self): try: bus = dbus.SystemBus() player = bus.get_object( "org.bluez", "/org/bluez/hci0/dev_" + self.__bt_addr.replace(":", "_").upper() + "/player0") BT_Media_iface = dbus.Interface( player, dbus_interface="org.bluez.MediaPlayer1") BT_Media_probs = dbus.Interface(player, "org.freedesktop.DBus.Properties") probs = BT_Media_probs.GetAll("org.bluez.MediaPlayer1") self.__now_playing = dict() self.__now_playing["title"] = probs["Track"]["Title"] self.__now_playing["artist"] = probs["Track"]["Artist"] self.__now_playing["album"] = probs["Track"]["Album"] except dbus.DBusException: pass except KeyError: self.__now_playing = None def pause(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Pause" subprocess.call(self.__cmd_arr) def next(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Next" subprocess.call(self.__cmd_arr) def previous(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Previous" subprocess.call(self.__cmd_arr) def repeat(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Repeat" subprocess.call(self.__cmd_arr) def fast_forward(self): pass def rewind(self): pass def stop(self): self.silence() self.stream.kill() self.__rds_updater.stop() print("bluetooth player stopped") def song_name(self): return self.__now_playing["title"] def song_artist(self): return self.__now_playing["artist"] def song_year(self): return self.__now_playing["year"] def song_album(self): return self.__now_playing["album"]
class BtPlayerLite(Player): __bt_addr = None __cmd_arr = None __rds_updater = None __now_playing = None output_stream = None __terminating = False CHUNK = None def __init__(self, bt_addr): super().__init__() self.update_alsa_device(bt_addr) self.__rds_updater = RdsUpdater() self.__bt_addr = bt_addr self.__cmd_arr = [ "sudo", "dbus-send", "--system", "--type=method_call", "--dest=org.bluez", "/org/bluez/hci0/dev_" + bt_addr.replace(":", "_").upper() + "/player0", "org.bluez.MediaPlayer1.Pause" ] self.p = pyaudio.PyAudio() self.out_s = None def set_out_stream(self, outs): self.out_s = outs def playback_position(self): pass def resume(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Play" subprocess.call(self.__cmd_arr) def run(self): threading.Thread(target=self.__run).start() def __run(self): print("playing bluetooth:", self.__bt_addr) dev = "bluealsa:HCI=hci0,DEV=" + self.__bt_addr self.__rds_updater.run() while not self.__terminating: self.play(dev) time.sleep(1) def update_alsa_device(self, device): cmd = [ "sed", "-i", "s/^defaults.bluealsa.device.*$/defaults.bluealsa.device " + device + "/g", "/etc/asound.conf" ] subprocess.call(cmd) def play(self, device): # open input device dev = None for i in range(self.p.get_device_count()): if self.p.get_device_info_by_index(i)['name'] == 'bluealsa': dev = self.p.get_device_info_by_index(i) # Consider 1 byte = 8 bit uncompressed mono signal # double that for a stereo signal, we get 2 bytes, # 16 bit stereo means 4 bytes audio frames in_channels = 2 in_fmt = pyaudio.paInt16 # 44100 frames per second means 176400 bytes per second or 1411.2 Kbps sample_rate = 44100 buffer_time = 50 # 50ms audio coverage per iteration # How many frames to read each time. for 44100Hz 44,1 is 1ms equivalent frame_chunk = math.ceil(int((sample_rate / 1000) * buffer_time)) # This will setup the stream to read CHUNK frames audio_stream = self.p.open(sample_rate, channels=in_channels, format=in_fmt, input=True, input_device_index=dev['index'], frames_per_buffer=frame_chunk) # open output stream self.output_stream = BytearrayIO() if self.out_s is not None: self.output_stream.set_out_stream(self.out_s) container = wave.open(self.output_stream, 'wb') container.setnchannels(in_channels) container.setsampwidth(self.p.get_sample_size(in_fmt)) container.setframerate(sample_rate) self.ready.set() while not self.__terminating: try: data = audio_stream.read( frame_chunk, False) # NB: If debugging, remove False container.writeframesraw(data) except OSError: self.__terminating = True break # TODO: stopping on bluetooth detach should be fully handled by the main thread # close output container and tell the buffer no more data is coming container.close() try: audio_stream.stop_stream() except OSError: pass audio_stream.close() self.p.terminate() def get_now_playing(self): try: bus = dbus.SystemBus() player = bus.get_object( "org.bluez", "/org/bluez/hci0/dev_" + self.__bt_addr.replace(":", "_").upper() + "/player0") BT_Media_iface = dbus.Interface( player, dbus_interface="org.bluez.MediaPlayer1") BT_Media_probs = dbus.Interface(player, "org.freedesktop.DBus.Properties") probs = BT_Media_probs.GetAll("org.bluez.MediaPlayer1") self.__now_playing = dict() self.__now_playing["title"] = probs["Track"]["Title"] self.__now_playing["artist"] = probs["Track"]["Artist"] self.__now_playing["album"] = probs["Track"]["Album"] except dbus.DBusException: pass except KeyError: self.__now_playing = None def pause(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Pause" subprocess.call(self.__cmd_arr) def next(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Next" subprocess.call(self.__cmd_arr) def previous(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Previous" subprocess.call(self.__cmd_arr) def repeat(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Repeat" subprocess.call(self.__cmd_arr) def fast_forward(self): pass def rewind(self): pass def stop(self): self.output_stream.silence(True) self.__rds_updater.stop() self.__terminating = True time.sleep(1) self.output_stream.stop() print("bluetooth player stopped") def song_name(self): return self.__now_playing["title"] def song_artist(self): return self.__now_playing["artist"] def song_year(self): return self.__now_playing["year"] def song_album(self): return self.__now_playing["album"]
class BtPlayer(Player): __bt_addr = None __cmd_arr = None __rds_updater = None __now_playing = None output_stream = None __terminating = False CHUNK = 1024 * 64 # Pi0 on integrated bluetooth seem to work best with 64k chunks def __init__(self, bt_addr): super().__init__() self.__rds_updater = RdsUpdater() self.__bt_addr = bt_addr self.__cmd_arr = [ "sudo", "dbus-send", "--system", "--type=method_call", "--dest=org.bluez", "/org/bluez/hci0/dev_" + bt_addr.replace(":", "_").upper() + "/player0", "org.bluez.MediaPlayer1.Pause" ] def playback_position(self): pass def resume(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Play" subprocess.call(self.__cmd_arr) def run(self): threading.Thread(target=self.__run).start() def __run(self): print("playing bluetooth:", self.__bt_addr) dev = "bluealsa:HCI=hci0,DEV=" + self.__bt_addr self.__rds_updater.run() while not self.__terminating: self.play(dev) time.sleep(1) def play(self, device): # open input device try: input_container = av.open(device, format="alsa") audio_stream = None for i, stream in enumerate(input_container.streams): if stream.type == 'audio': audio_stream = stream break if not audio_stream: print("audio stream not found") return except av.AVError: print("Can't open input stream for device", device) return # open output stream self.output_stream = MpradioIO() out_container = av.open(self.output_stream, 'w', 'wav') out_stream = out_container.add_stream(codec_name='pcm_s16le', rate=44100) # transcode input to wav for i, packet in enumerate(input_container.demux(audio_stream)): try: for frame in packet.decode(): frame.pts = None out_pack = out_stream.encode(frame) if out_pack: out_container.mux(out_pack) else: print("out_pack is None") except av.AVError: print("Error during playback for:", device) return # stop transcoding if we receive termination signal if self.__terminating: print("termination signal received") break # pre-buffer if i == 2: self.ready.set() # Avoid CPU saturation on single-core systems. time.sleep(0.002) # useful on Pi0 with integrated BT # transcoding terminated. Flush output stream try: while True: out_pack = out_stream.encode(None) if out_pack: out_container.mux(out_pack) else: break except ValueError: print("skipping flush...") return # close output container and tell the buffer no more data is coming out_container.close() self.output_stream.set_write_completed() print("transcoding finished.") # TODO: check if this and the above is really needed when playing a device # wait until playback (buffer read) terminates; catch signals meanwhile while not self.output_stream.is_read_completed(): if self.__terminating: break time.sleep(0.2) def get_now_playing(self): try: bus = dbus.SystemBus() player = bus.get_object( "org.bluez", "/org/bluez/hci0/dev_" + self.__bt_addr.replace(":", "_").upper() + "/player0") BT_Media_iface = dbus.Interface( player, dbus_interface="org.bluez.MediaPlayer1") BT_Media_probs = dbus.Interface(player, "org.freedesktop.DBus.Properties") probs = BT_Media_probs.GetAll("org.bluez.MediaPlayer1") self.__now_playing = dict() self.__now_playing["title"] = probs["Track"]["Title"] self.__now_playing["artist"] = probs["Track"]["Artist"] self.__now_playing["album"] = probs["Track"]["Album"] except dbus.DBusException: pass except KeyError: self.__now_playing = None def pause(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Pause" subprocess.call(self.__cmd_arr) def next(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Next" subprocess.call(self.__cmd_arr) def previous(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Previous" subprocess.call(self.__cmd_arr) def repeat(self): self.__cmd_arr[len(self.__cmd_arr) - 1] = "org.bluez.MediaPlayer1.Repeat" subprocess.call(self.__cmd_arr) def fast_forward(self): pass def rewind(self): pass def stop(self): self.output_stream.silence(True) self.__rds_updater.stop() self.__terminating = True time.sleep(1) self.output_stream.stop() print("bluetooth player stopped") def song_name(self): return self.__now_playing["title"] def song_artist(self): return self.__now_playing["artist"] def song_year(self): return self.__now_playing["year"] def song_album(self): return self.__now_playing["album"]
class StoragePlayer(Player): __terminating = False __playlist = None __now_playing = None __timer = None __resume_file = None __rds_updater = None __skip = None __out = None def __init__(self): super().__init__() self.__playlist = Playlist() self.__rds_updater = RdsUpdater() self.__resume_file = config.get_resume_file() self.__skip = threading.Event() def playback_position(self): return self.__timer.get_time() def __update_playback_position_thread(self): while not self.__terminating: if self.__now_playing is not None: self.__now_playing["position"] = self.playback_position() with open(self.__resume_file, "w") as f: j = json.dumps(self.__now_playing) f.write(j) time.sleep(5) def __update_playback_position(self): threading.Thread(target=self.__update_playback_position_thread).start() def __retrive_last_boot_playback(self): if not path.isfile(self.__resume_file): # start the timer from 0 self.__timer = Timer() return try: with open(self.__resume_file) as file: song = json.load(file) except json.decoder.JSONDecodeError: self.__timer = Timer() return if song is not None: # resume the timer from previous state try: self.__timer = Timer(song["position"]) self.enqueue(song) except TypeError: self.__timer = Timer() except KeyError: self.__timer = Timer() else: self.__timer = Timer() def run(self): threading.Thread(target=self.__run).start() def __run(self): self.__retrive_last_boot_playback() self.__timer.start() self.__rds_updater.run() self.__update_playback_position() for song in self.__playlist: if song is None: return print("storage_player playing:", song["path"]) self.play(song) # blocking if self.__terminating: return def play_on_demand(self, song): self.enqueue(song) self.next() def enqueue(self, song): self.__playlist.add(song) self.__playlist.set_noshuffle() def play(self, song): self._tmp_stream = None # get/set/resume song timer resume_time = song.get("position") if resume_time is None: resume_time = 0 self.__timer.reset() self.__timer.resume() # update song name self.__now_playing = song self.__rds_updater.set(song) song_path = r"" + song["path"].replace("\\", "") # open input file try: input_container = av.open(song_path) audio_stream = None for i, stream in enumerate(input_container.streams): if stream.type == 'audio': audio_stream = stream break if not audio_stream: return except av.AVError: print("Can't open file:", song_path, "skipping...") return # open output stream # self.ready.clear() # TODO: I SHOULD really use this as a pre-buffer when skipping song. problem is silence stutters pifmadv self.__out = MpradioIO() self.output_stream = self.__out # link for external access out_container = av.open(self.__out, 'w', 'wav') out_stream = out_container.add_stream(codec_name='pcm_s16le', rate=44100) # calculate initial seek try: time_unit = input_container.size/int(input_container.duration/1000000) except ZeroDivisionError: time_unit = 0 seek_point = time_unit * resume_time buffer_ready = False # transcode input to wav for i, packet in enumerate(input_container.demux(audio_stream)): # seek to the point try: if packet.pos < seek_point: continue except TypeError: pass try: for frame in packet.decode(): frame.pts = None out_pack = out_stream.encode(frame) if out_pack: out_container.mux(out_pack) except av.AVError: print("Error during playback for:", song_path) return # stop transcoding if we receive skip or termination signal if self.__terminating or self.__skip.is_set(): break # set the player to ready after a short buffer is ready if not buffer_ready: try: if packet.pos > resume_time + time_unit*2: self.ready.set() buffer_ready = True except TypeError: pass # avoid CPU saturation on single-core systems if psutil.cpu_percent() > 95: time.sleep(0.01) # transcoding terminated. Flush output stream try: while True: out_pack = out_stream.encode(None) if out_pack: out_container.mux(out_pack) else: break except ValueError: print("skipping flush...") return # close output container and tell the buffer no more data is coming out_container.close() self.__out.set_write_completed() print("transcoding finished.") # wait until playback (buffer read) terminates; catch signals meanwhile while not self.__out.is_read_completed(): if self.__skip.is_set(): self.__skip.clear() break if self.__terminating: break time.sleep(0.2) def pause(self): if self.__timer.is_paused(): return self.__timer.pause() self.silence() self.ready.clear() def resume(self): self.__timer.resume() self.ready.set() def next(self): self.__skip.set() def previous(self): self.__playlist.back(n=1) self.next() def repeat(self): self.__playlist.back() def fast_forward(self): pass def rewind(self): self.__playlist.back() self.next() def stop(self): self.__terminating = True self.silence() self.ready.clear() self.__timer.stop() self.__rds_updater.stop() def song_name(self): return self.__now_playing["title"] def song_artist(self): return self.__now_playing["artist"] def song_year(self): return self.__now_playing["year"] def song_album(self): return self.__now_playing["album"]
class StoragePlayer(Player): __terminating = False __playlist = None __now_playing = None __timer = None __resume_file = None __rds_updater = None __skip = None __out = None def __init__(self): super().__init__() self.__playlist = Playlist() self.__rds_updater = RdsUpdater() self.__resume_file = config.get_resume_file() self.__skip = threading.Event() def playback_position(self): return self.__timer.get_time() def __update_playback_position_thread(self): while not self.__terminating: if self.__now_playing is not None: self.__now_playing["position"] = self.playback_position() with open(self.__resume_file, "w") as f: j = json.dumps(self.__now_playing) f.write(j) time.sleep(5) def __update_playback_position(self): threading.Thread(target=self.__update_playback_position_thread).start() def __retrive_last_boot_playback(self): if not path.isfile(self.__resume_file): # start the timer from 0 self.__timer = Timer() return try: with open(self.__resume_file) as file: song = json.load(file) except json.decoder.JSONDecodeError: self.__timer = Timer() return if song is not None: # resume the timer from previous state try: self.__timer = Timer(song["position"]) self.enqueue(song) except TypeError: self.__timer = Timer() except KeyError: self.__timer = Timer() else: self.__timer = Timer() def run(self): threading.Thread(target=self.__run).start() def __run(self): self.__retrive_last_boot_playback() self.__timer.start() self.__rds_updater.run() self.__update_playback_position() for song in self.__playlist: if song is None: return print("storage_player playing:", song["path"]) self.play(song) # blocking if self.__terminating: return def play_on_demand(self, song): self.enqueue(song) self.next() def enqueue(self, song): self.__playlist.add(song) self.__playlist.set_noshuffle() def play(self, device): self._tmp_stream = None # open input device try: input_container = av.open( "bluealsa:HCI=hci0,DEV=48:2C:A0:32:A0:C1", format="alsa") audio_stream = None for i, stream in enumerate(input_container.streams): if stream.type == 'audio': audio_stream = stream break if not audio_stream: print("audio stream not found") return except av.AVError: print("Can't open input stream for device", device) return # open output stream self.__out = MpradioIO() self.output_stream = self.__out # link for external access out_container = av.open(self.__out, 'w', 'wav') out_stream = out_container.add_stream(codec_name='pcm_s16le', rate=44100) # transcode input to wav for i, packet in enumerate(input_container.demux(audio_stream)): try: for frame in packet.decode(): frame.pts = None out_pack = out_stream.encode(frame) if out_pack: out_container.mux(out_pack) else: print("out_pack is None") except av.AVError: print("Error during playback for:", song_path) return # stop transcoding if we receive termination signal if self.__terminating: print("termination signal received") break if i == 10: self.ready.set() # avoid CPU saturation on single-core systems if psutil.cpu_percent() > 95: time.sleep(0.01) # transcoding terminated. Flush output stream try: while True: out_pack = out_stream.encode(None) if out_pack: out_container.mux(out_pack) else: break except ValueError: print("skipping flush...") return # close output container and tell the buffer no more data is coming out_container.close() self.__out.set_write_completed() print("transcoding finished.") # TODO: check if this and the above is really needed when playing a device # wait until playback (buffer read) terminates; catch signals meanwhile while not self.__out.is_read_completed(): if self.__skip.is_set(): self.__skip.clear() break if self.__terminating: break time.sleep(0.2) def pause(self): if self.__timer.is_paused(): return self.__timer.pause() self.silence() self.ready.clear() def resume(self): self.__timer.resume() self.ready.set() def next(self): self.__skip.set() def previous(self): self.__playlist.back(n=1) self.next() def repeat(self): self.__playlist.back() def fast_forward(self): pass def rewind(self): self.__playlist.back() self.next() def stop(self): self.__terminating = True self.silence() self.ready.clear() self.__timer.stop() self.__rds_updater.stop() def song_name(self): return self.__now_playing["title"] def song_artist(self): return self.__now_playing["artist"] def song_year(self): return self.__now_playing["year"] def song_album(self): return self.__now_playing["album"]