예제 #1
0
 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()
예제 #2
0
 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"
     ]
예제 #3
0
 def __init__(self):
     super().__init__()
     self.__playlist = Playlist()
     self.__rds_updater = RdsUpdater()
     self.__resume_file = config.get_resume_file()
예제 #4
0
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"]
예제 #5
0
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"]
예제 #7
0
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"]
예제 #8
0
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"]