Пример #1
0
    def __init__(self, tauon):
        self.tauon = tauon
        self.strings = tauon.strings
        self.start_timer = Timer()
        self.status = 0
        self.spotify = None
        self.loaded_art = ""
        self.playing = False
        self.coasting = False
        self.paused = False
        self.token = None
        self.cred = None
        self.started_once = False
        self.redirect_uri = f"http://localhost:7811/spotredir"
        self.current_imports = {}
        self.spotify_com = False
        self.sender = None
        self.cache_saved_albums = []
        self.scope = "user-read-playback-position streaming user-modify-playback-state user-library-modify user-library-read user-read-currently-playing user-read-playback-state playlist-read-private playlist-modify-private playlist-modify-public"
        self.launching_spotify = False
        self.progress_timer = Timer()
        self.update_timer = Timer()

        self.token_path = os.path.join(self.tauon.user_directory, "spot-token-pkce")
        self.pkce_code = None
Пример #2
0
    def __init__(self, tauon):
        self.tauon = tauon
        self.strings = tauon.strings
        self.start_timer = Timer()
        self.status = 0
        self.spotify = None
        self.loaded_art = ""
        self.playing = False
        self.coasting = False
        self.paused = False
        self.token = None
        self.cred = None
        self.started_once = False
        self.redirect_uri = f"http://localhost:7811/spotredir"
        self.current_imports = {}
        self.spotify_com = False

        self.progress_timer = Timer()
        self.update_timer = Timer()

        self.token_path = os.path.join(self.tauon.user_directory, "spot-r-token")
Пример #3
0
    def __init__(self, tauon):
        self.tauon = tauon
        self.pctl = tauon.pctl
        self.prefs = tauon.prefs
        self.gui = tauon.gui

        self.scanning = False
        self.connected = False

        self.accessToken = None
        self.userId = None
        self.currentId = None

        self.session_thread_active = False
        self.session_status = 0
        self.session_item_id = None
        self.session_update_timer = Timer()
        self.session_last_item = None
Пример #4
0
        def __init__(self):
            self.encoder = None
            self.decoder = None

            self.raw_buffer = io.BytesIO()
            self.raw_buffer_size = 0

            self.output_buffer = io.BytesIO()
            self.output_buffer_size = 0

            self.temp_buffer = io.BytesIO()
            self.temp_buffer_size = 0

            self.stream_time = Timer()
            self.bytes_sent = 0

            self.track_bytes_sent = 0

            self.dry = 0
Пример #5
0
        def __init__(self):

            # This is used to keep track of time between callbacks to progress the seek bar
            self.player_timer = Timer()

            # This is used to keep note of what state of playing we should be in
            self.play_state = 0  # 0 is stopped, 1 is playing, 2 is paused

            # Initiate GSteamer
            Gst.init([])
            self.mainloop = GLib.MainLoop()

            # Create main "playbin" pipeline thingy for simple playback
            self.pl = Gst.ElementFactory.make("playbin", "player")

            # Set callback for the main callback loop
            GLib.timeout_add(500, self.main_callback)

            # self.pl.connect("about-to-finish", self.about_to_finish)

            self.mainloop.run()
Пример #6
0
        def __init__(self):

            print("Init GStreamer...")

            # This is used to keep track of time between callbacks.
            self.player_timer = Timer()

            # Store the track object that is currently playing
            self.loaded_track = None

            # This is used to keep note of what state of playing we should be in
            self.play_state = 0  # 0 is stopped, 1 is playing, 2 is paused

            # Initiate GSteamer
            Gst.init([])
            self.mainloop = GLib.MainLoop()

            # Populate list of output devices with defaults
            outputs = {}
            devices = [
                "PulseAudio",
                "ALSA",
                "JACK",
            ]
            if tauon.snap_mode:  # Snap permissions don't support these by default
                devices.remove("JACK")
                devices.remove("ALSA")

            # Get list of available audio device
            self.dm = Gst.DeviceMonitor()
            self.dm.start()
            for device in self.dm.get_devices():
                if device.get_device_class() == "Audio/Sink":
                    element = device.create_element(None)
                    type_name = element.get_factory().get_name()
                    if hasattr(element.props, "device"):
                        device_name = element.props.device
                        display_name = device.get_display_name()

                        # This is used by the UI to present list of options to the user in audio settings
                        outputs[display_name] = (type_name, device_name)
                        devices.append(display_name)

            # dm.stop()  # Causes a segfault sometimes
            pctl.gst_outputs = outputs
            pctl.gst_devices = devices

            # Create main "playbin" pipeline for playback
            self.playbin = Gst.ElementFactory.make("playbin", "player")

            # Create custom output bin from user preferences
            if not prefs.gst_use_custom_output:
                prefs.gst_output = prefs.gen_gst_out()

            self._output = Gst.parse_bin_from_description(
                prefs.gst_output, ghost_unlinked_pads=True)

            # Create a bin for the audio pipeline
            self._sink = Gst.ElementFactory.make("bin", "sink")
            self._sink.add(self._output)

            # Spectrum -------------------------
            # Cant seem to figure out how to process these magnitudes in a way that looks good

            # self.spectrum = Gst.ElementFactory.make("spectrum", "spectrum")
            # self.spectrum.set_property('bands', 20)
            # self.spectrum.set_property('interval', 10000000)
            # self.spectrum.set_property('threshold', -100)
            # self.spectrum.set_property('post-messages', True)
            # self.spectrum.set_property('message-magnitude', True)
            #
            # self.playbin.set_property('audio-filter', self.spectrum)
            # # ------------------------------------

            # # Level Meter -------------------------
            self.level = Gst.ElementFactory.make("level", "level")
            self.level.set_property('interval', 20000000)
            self.playbin.set_property('audio-filter', self.level)
            # # ------------------------------------

            self._eq = Gst.ElementFactory.make("equalizer-nbands", "eq")
            self._vol = Gst.ElementFactory.make("volume", "volume")

            self._sink.add(self._eq)
            self._sink.add(self._vol)

            self._eq.link(self._vol)
            self._vol.link(self._output)

            self._eq.set_property("num-bands", 10 + 2)

            # Set the equalizer based on user preferences

            # Using workaround for "inverted slider" bug.
            # Thanks to Lollypop and Clementine for discovering this.
            # Ref https://github.com/Taiko2k/TauonMusicBox/issues/414

            for i in range(10 + 2):
                band = self._eq.get_child_by_index(i)
                band.set_property("freq", 0)
                band.set_property("bandwidth", 0)
                band.set_property("gain", 0)

            self.set_eq()
            # if prefs.use_eq:
            #     self._eq.set_property("band" + str(i), level * -1)
            # else:
            #     self._eq.set_property("band" + str(i), 0.0)

            # Set up sink pad for the intermediate bin via the
            # first element (volume)
            ghost = Gst.GhostPad.new("sink", self._eq.get_static_pad("sink"))
            self._sink.add_pad(ghost)

            # Connect the playback bin to to the intermediate bin sink pad
            self.playbin.set_property("audio-sink", self._sink)

            # The pipeline should look something like this -
            # (player) -> [(eq) -> (volume) -> (output)]

            # Create controller for pause/resume volume fading
            self.c_source = GstController.InterpolationControlSource()
            self.c_source.set_property('mode',
                                       GstController.InterpolationMode.LINEAR)
            self.c_binding = GstController.DirectControlBinding.new(
                self._vol, "volume", self.c_source)
            self._vol.add_control_binding(self.c_binding)

            # Set callback for the main callback loop
            GLib.timeout_add(50, self.main_callback)

            self.playbin.connect("about-to-finish",
                                 self.about_to_finish)  # Not used

            # Setup bus and select what types of messages we want to listen for
            bus = self.playbin.get_bus()
            bus.add_signal_watch()
            bus.connect('message::element', self.on_message)
            bus.connect('message::buffering', self.on_message)
            bus.connect('message::error', self.on_message)
            bus.connect('message::tag', self.on_message)
            bus.connect('message::warning', self.on_message)
            # bus.connect('message::eos', self.on_message)

            # Variables used with network downloading
            self.temp_id = "a"
            self.url = None
            self.dl_ready = True
            self.using_cache = False
            self.temp_path = ""  # Full path + filename
            # self.level_train = []
            self.seek_timer = Timer()
            self.seek_timer.force_set(10)
            self.buffering = False
            # Other
            self.end_timer = Timer()

            # Start GLib mainloop
            self.mainloop.run()
Пример #7
0
        def __init__(self):

            # This is used to keep track of time between callbacks to progress the seek bar
            self.player_timer = Timer()

            # This is used to keep note of what state of playing we should be in
            self.play_state = 0  # 0 is stopped, 1 is playing, 2 is paused

            # Initiate GSteamer
            Gst.init([])
            self.mainloop = GLib.MainLoop()

            # Get list of available audio device
            pctl.gst_devices = ["Auto", "PulseAudio", "ALSA", "JACK"]
            if tauon.snap_mode:
                pctl.gst_devices.remove("JACK")
                pctl.gst_devices.remove("ALSA")
            pctl.gst_outputs.clear()
            dm = Gst.DeviceMonitor()
            dm.start()
            for device in dm.get_devices():
                if device.get_device_class() == "Audio/Sink":
                    element = device.create_element(None)
                    type_name = element.get_factory().get_name()
                    device_name = element.props.device
                    display_name = device.get_display_name()

                    # This is used by the UI to present list of options to the user in audio settings
                    pctl.gst_outputs[display_name] = (type_name, device_name)
                    pctl.gst_devices.append(display_name)

            dm.stop()

            # Create main "playbin" pipeline for playback
            self.playbin = Gst.ElementFactory.make("playbin", "player")

            # Create output bin
            if not prefs.gst_use_custom_output:
                prefs.gst_output = prefs.gen_gst_out()

            self._output = Gst.parse_bin_from_description(
                prefs.gst_output, ghost_unlinked_pads=True)

            # Create a bin for the audio pipeline
            self._sink = Gst.ElementFactory.make("bin", "sink")
            self._sink.add(self._output)

            # # Spectrum -------------------------
            # # This kind of works, but is a different result to that of the bass backend.
            # # This seems linear and also less visually appealing.
            #
            # self.spectrum = Gst.ElementFactory.make("spectrum", "spectrum")
            # self.spectrum.set_property('bands', 280)
            # self.spectrum.set_property('interval', 10000000)
            # self.spectrum.set_property('post-messages', True)
            # self.spectrum.set_property('message-magnitude', True)
            #
            # self.playbin.set_property('audio-filter', self.spectrum)
            # # ------------------------------------

            # Create volume element
            self._vol = Gst.ElementFactory.make("volume", "volume")
            self._sink.add(self._vol)
            self._vol.link(self._output)

            # Set up sink pad for the intermediate bin via the
            #  first element (volume)
            ghost = Gst.GhostPad.new("sink", self._vol.get_static_pad("sink"))

            self._sink.add_pad(ghost)

            # Connect the playback bin to to the intermediate bin sink pad
            self.playbin.set_property("audio-sink", self._sink)

            # The pipeline should look something like this -
            # (player) -> [(volume) -> (output)]

            # Set callback for the main callback loop
            GLib.timeout_add(50, self.main_callback)

            # self.playbin.connect("about-to-finish", self.about_to_finish)  # Not used by anything

            # # Enable bus to get spectrum messages
            bus = self.playbin.get_bus()
            bus.add_signal_watch()
            bus.connect('message::element', self.on_message)
            bus.connect('message::buffering', self.on_message)
            bus.connect('message::error', self.on_message)
            bus.connect('message::tag', self.on_message)
            # bus.connect('message::warning', self.on_message)
            # bus.connect('message::eos', self.on_message)

            # Variables used with network downloading
            self.temp_id = "a"
            self.url = None
            self.dl_ready = False
            self.temp_path = ""  # Full path + filename

            # # Broadcasting pipeline ------------
            #
            # # This works, but only for one track, switching tracks seems to be a more complicated process.
            #
            # self.b_playbin = Gst.ElementFactory.make("playbin", "player")
            #
            # # Create output bin
            # # Using tcpserversink seems to mostly work with the html5 player, though an HTTP server may be preferred.
            # self._b_output = Gst.parse_bin_from_description(
            #    "audioconvert ! vorbisenc ! oggmux ! tcpserversink port=8000", ghost_unlinked_pads=True)
            #    #"autoaudiosink", ghost_unlinked_pads=True)
            #
            # # Connect the playback bin to to the output bin
            # self.b_playbin.set_property("audio-sink", self._b_output)
            # # ----------------------------------------

            # Start GLib mainloop
            self.mainloop.run()
Пример #8
0
def webserve2(pctl, prefs, gui, album_art_gen, install_directory, strings,
              tauon):

    play_timer = Timer()

    class Server(BaseHTTPRequestHandler):
        def run_command(self, callback):
            self.send_response(200)
            #self.send_header("Content-type", "application/json")
            self.end_headers()
            callback()
            self.wfile.write(b"OK")

        def parse_trail(self, text):

            params = {}
            both = text.split("?")
            levels = both[0].split("/")
            if len(both) > 1:
                pairs = both[2].split("&")
                for p in pairs:
                    aa, bb = p.split("=")
                    params[aa] = bb

            return levels, params

        def get_track(self,
                      track_position,
                      playlist_index=None,
                      track=None,
                      album_id=-1):
            if track is None:
                if playlist_index is None:
                    playlist = pctl.multi_playlist[
                        pctl.active_playlist_playing][2]
                else:
                    playlist = pctl.multi_playlist[playlist_index][2]
                track_id = playlist[track_position]
                track = pctl.g(track_id)

            data = {}
            data["title"] = track.title
            data["artist"] = track.artist
            data["album"] = track.album
            data["album_artist"] = track.album_artist
            if not track.album_artist:
                data["album_artist"] = track.artist
            data["duration"] = int(track.length * 1000)
            data["id"] = track.index
            data["position"] = track_position
            data["album_id"] = album_id
            data["has_lyrics"] = track.lyrics != ""
            data["track_number"] = str(track.track_number).lstrip("0")
            data["can_download"] = not track.is_cue and not track.is_network

            return data

        def send_file(self, path, mime):

            range_req = False
            start = 0
            end = 0

            if "Range" in self.headers:
                range_req = True
                b = self.headers["Range"].split("=")[1]
                start, end = b.split("-")
                start = int(start)

            with open(path, "rb") as f:

                f.seek(0, 2)
                length = f.tell()
                f.seek(0, 0)

                l = str(length)

                remain = length - start

                if range_req:
                    self.send_response(206)
                    self.send_header("Content-type", mime)
                    self.send_header("Content-Range", f"bytes={start}-/{l}")
                    self.send_header("Content-Length", str(remain))
                    f.seek(start)

                else:
                    self.send_response(200)
                    self.send_header("Content-type", mime)
                    self.send_header("Content-Length", l)

                self.end_headers()

                while True:
                    data = f.read(5000)
                    if not data:
                        break
                    self.wfile.write(data)

        def do_GET(self):

            path = self.path

            if path.startswith("/api1/pic/small/"):
                value = path[16:]
                if value.isalnum() and int(value) in pctl.master_library:
                    track = pctl.g(int(value))
                    raw = album_art_gen.save_thumb(track, (75, 75), "")
                    if raw:
                        self.send_response(200)
                        self.send_header("Content-type", "image/jpg")
                        self.end_headers()
                        self.wfile.write(raw.read())
                    else:
                        self.send_response(404)
                        self.end_headers()
                        self.wfile.write(b"No image found")

                else:
                    self.send_response(404)
                    self.end_headers()
                    self.wfile.write(b"Invalid parameter")

            if path.startswith("/api1/pic/medium/"):
                value = path[17:]
                if value.isalnum() and int(value) in pctl.master_library:
                    track = pctl.g(int(value))
                    raw = album_art_gen.save_thumb(track, (1000, 1000), "")
                    if raw:
                        self.send_response(200)
                        self.send_header("Content-type", "image/jpg")
                        self.end_headers()
                        self.wfile.write(raw.read())
                    else:
                        self.send_response(404)
                        self.end_headers()
                        self.wfile.write(b"No image found")

                else:
                    self.send_response(404)
                    self.end_headers()
                    self.wfile.write(b"Invalid parameter")

            if path.startswith("/api1/lyrics/"):
                value = path[13:]
                if value.isalnum() and int(value) in pctl.master_library:
                    track = pctl.g(int(value))
                    data = {}
                    data["track_id"] = track.index
                    data["lyrics_text"] = track.lyrics

                    self.send_response(200)
                    self.send_header("Content-type", "application/json")
                    self.end_headers()
                    data = json.dumps(data).encode()
                    self.wfile.write(data)
                else:
                    self.send_response(404)
                    self.end_headers()
                    self.wfile.write(b"Invalid parameter")

            # elif path.startswith("/api1/stream/"):
            #     param = path[13:]
            #
            #     if param.isdigit() and int(param) in pctl.master_library:
            #         track = pctl.master_library[int(param)]
            #         mime = "audio/mpeg"
            #         #mime = "audio/ogg"
            #         self.send_response(200)
            #         self.send_header("Content-type", mime)
            #         self.end_headers()
            #
            #         cmd = ["ffmpeg", "-i", track.fullpath, "-c:a", "libopus", "-f", "ogg", "-"]
            #         #cmd = ["ffmpeg", "-i", track.fullpath, "-c:a", "libvorbis", "-f", "ogg", "-"]
            #         #cmd = ["ffmpeg", "-i", track.fullpath, "-c:a", "libmp3lame", "-f", "mp3", "-"]
            #         encoder = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
            #         while True:
            #             data = encoder.stdout.read(1024)
            #             if data:
            #                 self.wfile.write(data)
            elif path.startswith("/api1/playinghit/"):
                param = path[17:]
                if param.isdigit() and int(param) in pctl.master_library:
                    t = play_timer.hit()
                    if 0 < t < 5:
                        tauon.star_store.add(int(param), t)

                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.end_headers()
                self.wfile.write(b"OK")

            elif path.startswith("/api1/file/"):
                param = path[11:]

                #print(self.headers)
                play_timer.hit()

                if param.isdigit() and int(param) in pctl.master_library:
                    track = pctl.master_library[int(param)]
                    mime = "audio/mpeg"
                    if track.file_ext == "FLAC":
                        mime = "audio/flac"
                    if track.file_ext == "OGG" or track.file_ext == "OPUS" or track.file_ext == "OGA":
                        mime = "audio/ogg"
                    if track.file_ext == "M4A":
                        mime = "audio/mp4"
                    self.send_file(track.fullpath, mime)

            elif path.startswith("/api1/start/"):

                levels, _ = self.parse_trail(path)
                if len(levels) == 5:
                    playlist = levels[3]
                    position = levels[4]
                    if playlist.isdigit() and position.isdigit():
                        position = int(position)
                        playlist = int(playlist)
                        pl = tauon.id_to_pl(int(playlist))
                        if pl is not None and pl < len(pctl.multi_playlist):
                            playlist = pctl.multi_playlist[pl][2]
                            if position < len(playlist):
                                tauon.switch_playlist(pl,
                                                      cycle=False,
                                                      quiet=True)
                                pctl.jump(playlist[position], position)

                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.end_headers()
                self.wfile.write(b"OK")

            elif path == "/api1/play":
                self.run_command(tauon.pctl.play)
            elif path == "/api1/pause":
                self.run_command(tauon.pctl.pause_only)
            elif path == "/api1/next":
                self.run_command(tauon.pctl.advance)
            elif path == "/api1/back":
                self.run_command(tauon.pctl.back)
            elif path == "/api1/shuffle":
                self.run_command(tauon.toggle_random)
            elif path == "/api1/repeat":
                self.run_command(tauon.toggle_repeat)
            elif path == "/api1/version":
                data = {"version": 1}
                self.send_response(200)
                self.send_header("Content-type", "application/json")
                self.end_headers()
                data = json.dumps(data).encode()
                self.wfile.write(data)

            elif path == "/api1/playlists":

                l = []
                for item in pctl.multi_playlist:
                    p = {}
                    p["name"] = item[0]
                    p["id"] = str(item[6])
                    p["count"] = len(item[2])
                    l.append(p)
                data = {"playlists": l}
                self.send_response(200)
                self.send_header("Content-type", "application/json")
                self.end_headers()
                data = json.dumps(data).encode()
                self.wfile.write(data)

            elif path.startswith("/api1/albumtracks/"):
                # Get tracks that appear in an album /albumtracks/plid/albumid
                levels, _ = self.parse_trail(path)
                l = []
                if len(levels) == 5 and levels[3].isdigit(
                ) and levels[4].isdigit():
                    pl = tauon.id_to_pl(int(levels[3]))
                    if pl is not None:
                        _, album, _ = tauon.get_album_info(int(levels[4]), pl)
                        # print(album)
                        for p in album:
                            l.append(
                                self.get_track(p, pl, album_id=int(levels[4])))

                data = {"tracks": l}
                self.send_response(200)
                self.send_header("Content-type", "application/json")
                self.end_headers()
                data = json.dumps(data).encode()
                self.wfile.write(data)

            elif path.startswith("/api1/trackposition/"):
                # get track /trackposition/plid/playlistposition
                levels, _ = self.parse_trail(path)
                if len(levels) == 5 and levels[3].isdigit(
                ) and levels[4].isdigit():
                    pl = tauon.id_to_pl(int(levels[3]))
                    if pl is not None:

                        data = self.get_track(int(levels[4]), pl)

                        playlist = pctl.multi_playlist[pl][2]
                        p = int(levels[4])
                        if p < len(playlist):
                            track = pctl.g(playlist[p])
                            while True:
                                if p < 0 or pctl.g(
                                        playlist[p]
                                ).parent_folder_path != track.parent_folder_path:
                                    p += 1
                                    break
                                p -= 1
                            data["album_id"] = p

                            self.send_response(200)
                            self.send_header("Content-type",
                                             "application/json")
                            self.end_headers()
                            data = json.dumps(data).encode()
                            self.wfile.write(data)
                        else:
                            self.send_response(404)
                            self.send_header("Content-type", "text/plain")
                            self.end_headers()
                            self.wfile.write(b"404 invalid track position")
                    else:
                        self.send_response(404)
                        self.send_header("Content-type", "text/plain")
                        self.end_headers()
                        self.wfile.write(b"404 playlist not found")
                else:
                    self.send_response(404)
                    self.send_header("Content-type", "text/plain")
                    self.end_headers()
                    self.wfile.write(b"404 invalid track")

            elif path.startswith("/api1/setvolume/"):
                key = path[16:]
                if key.isdigit():
                    volume = int(key)
                    volume = max(volume, 0)
                    volume = min(volume, 100)
                    pctl.player_volume = volume
                    pctl.set_volume()

                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.end_headers()
                self.wfile.write(b"OK")

            elif path.startswith("/api1/seek1k/"):
                key = path[13:]
                if key.isdigit():
                    pctl.seek_decimal(int(key) / 1000)

                self.send_response(200)
                self.send_header("Content-type", "text/plain")
                self.end_headers()
                self.wfile.write(b"OK")

            elif path.startswith("/api1/tracklist/"):
                # Return all tracks in a playlist /tracklist/plid
                key = path[16:]
                l = []
                if key.isdigit():
                    pl = tauon.id_to_pl(int(key))
                    if pl is not None and pl < len(pctl.multi_playlist):
                        playlist = pctl.multi_playlist[pl][2]
                        parent = ""
                        album_id = 0
                        for i, id in enumerate(playlist):
                            tr = pctl.g(id)
                            if i == 0:
                                parent = tr.parent_folder_path
                            elif parent != tr.parent_folder_path:
                                parent = tr.parent_folder_path
                                album_id = i

                            l.append(self.get_track(i, pl, album_id=album_id))

                data = {"tracks": l}
                self.send_response(200)
                self.send_header("Content-type", "application/json")
                self.end_headers()
                data = json.dumps(data).encode()
                self.wfile.write(data)

            elif path.startswith("/api1/albums/"):
                # Returns lists of tracks that are start of albums /albums/plid
                key = path[13:]
                l = []
                if key.isdigit():
                    pl = tauon.id_to_pl(int(key))
                    if pl is not None:
                        dex = tauon.reload_albums(True, pl)
                        # print(dex)
                        for a in dex:
                            l.append(self.get_track(a, pl, album_id=a))

                data = {"albums": l}
                self.send_response(200)
                self.send_header("Content-type", "application/json")
                self.end_headers()
                data = json.dumps(data).encode()
                self.wfile.write(data)

            elif path == "/api1/status":
                self.send_response(200)
                self.send_header("Content-type", "application/json")
                self.end_headers()
                data = {
                    "status":
                    "stopped",
                    "inc":
                    pctl.db_inc,
                    "shuffle":
                    pctl.random_mode == True,
                    "repeat":
                    pctl.repeat_mode == True,
                    "progress":
                    0,
                    "volume":
                    pctl.player_volume,
                    "playlist":
                    str(tauon.get_playing_playlist_id()),
                    "playlist_length":
                    len(pctl.multi_playlist[pctl.active_playlist_playing][2])
                }
                if pctl.playing_state == 1:
                    data["status"] = "playing"
                if pctl.playing_state == 2:
                    data["status"] = "paused"
                track = pctl.playing_object()
                if track:
                    data["id"] = track.index
                    data["title"] = track.title
                    data["artist"] = track.artist
                    data["album"] = track.album
                    data["progress"] = int(round(pctl.playing_time * 1000))
                    data["track"] = self.get_track(0, 0, track)

                p = pctl.playlist_playing_position
                data["position"] = p
                data["album_id"] = 0
                playlist = pctl.playing_playlist()

                if p < len(playlist):
                    while True:
                        if p < 0 or pctl.g(
                                playlist[p]
                        ).parent_folder_path != track.parent_folder_path:
                            p += 1
                            break
                        p -= 1
                    data["album_id"] = p

                data = json.dumps(data).encode()
                self.wfile.write(data)

            else:
                self.send_response(404)
                self.end_headers()
                self.wfile.write(b"404 Not found")

    class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
        pass

    try:
        httpd = ThreadedHTTPServer(("0.0.0.0", 7814), Server)
        httpd.serve_forever()
        httpd.server_close()
    except OSError:
        print("Not starting web api server, already running?")