2021-02-25 00:07:17 +01:00
|
|
|
from __future__ import annotations
|
2021-04-10 01:16:20 +02:00
|
|
|
|
2021-02-25 00:07:17 +01:00
|
|
|
import base64
|
|
|
|
import concurrent.futures
|
|
|
|
import enum
|
|
|
|
import logging
|
|
|
|
import time
|
|
|
|
import typing
|
|
|
|
import urllib.parse
|
|
|
|
|
2021-04-10 01:16:20 +02:00
|
|
|
from librespot.common import Utils
|
|
|
|
from librespot.core import Session
|
|
|
|
from librespot.player import PlayerConfiguration
|
2021-04-10 01:16:25 +02:00
|
|
|
from librespot.proto import Connect
|
|
|
|
from librespot.proto import Player
|
2021-04-10 01:16:20 +02:00
|
|
|
|
2021-02-25 00:07:17 +01:00
|
|
|
|
|
|
|
class DeviceStateHandler:
|
|
|
|
_LOGGER: logging = logging.getLogger(__name__)
|
|
|
|
_session: Session = None
|
|
|
|
_deviceInfo: Connect.DeviceInfo = None
|
2021-04-24 12:17:02 +02:00
|
|
|
_listeners: typing.List[DeviceStateHandler.Listener] = []
|
2021-02-25 00:07:17 +01:00
|
|
|
_putState: Connect.PutStateRequest = None
|
2021-04-10 01:16:18 +02:00
|
|
|
_putStateWorker: concurrent.futures.ThreadPoolExecutor = (
|
2021-04-10 01:16:32 +02:00
|
|
|
concurrent.futures.ThreadPoolExecutor())
|
2021-02-25 00:07:17 +01:00
|
|
|
_connectionId: str = None
|
|
|
|
|
|
|
|
def __init__(self, session: Session, player, conf: PlayerConfiguration):
|
|
|
|
self._session = session
|
|
|
|
self._deviceInfo = None
|
|
|
|
self._putState = Connect.PutStateRequest()
|
|
|
|
|
|
|
|
def _update_connection_id(self, newer: str) -> None:
|
|
|
|
newer = urllib.parse.unquote(newer, "UTF-8")
|
|
|
|
|
2021-04-10 01:16:18 +02:00
|
|
|
if self._connectionId is None or self._connectionId != newer:
|
2021-02-25 00:07:17 +01:00
|
|
|
self._connectionId = newer
|
2021-04-10 01:16:32 +02:00
|
|
|
self._LOGGER.debug("Updated Spotify-Connection-Id: {}".format(
|
|
|
|
self._connectionId))
|
2021-02-25 00:07:17 +01:00
|
|
|
self._notify_ready()
|
|
|
|
|
2021-02-25 10:22:28 +01:00
|
|
|
def add_listener(self, listener: DeviceStateHandler.Listener):
|
|
|
|
self._listeners.append(listener)
|
|
|
|
|
2021-02-25 00:07:17 +01:00
|
|
|
def _notify_ready(self) -> None:
|
|
|
|
for listener in self._listeners:
|
|
|
|
listener.ready()
|
|
|
|
|
2021-04-10 01:16:18 +02:00
|
|
|
def update_state(
|
|
|
|
self,
|
|
|
|
reason: Connect.PutStateReason,
|
|
|
|
player_time: int,
|
|
|
|
state: Player.PlayerState,
|
|
|
|
):
|
2021-02-25 00:07:17 +01:00
|
|
|
if self._connectionId is None:
|
|
|
|
raise TypeError()
|
|
|
|
|
|
|
|
if player_time == -1:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
pass
|
|
|
|
|
|
|
|
self._putState.put_state_reason = reason
|
|
|
|
self._putState.client_side_timestamp = int(time.time() * 1000)
|
|
|
|
self._putState.device.device_info = self._deviceInfo
|
|
|
|
self._putState.device.player_state = state
|
|
|
|
|
|
|
|
self._putStateWorker.submit(self._put_connect_state, self._putState)
|
|
|
|
|
|
|
|
def _put_connect_state(self, req: Connect.PutStateRequest):
|
|
|
|
self._session.api().put_connect_state(self._connectionId, req)
|
2021-04-10 01:16:32 +02:00
|
|
|
self._LOGGER.info("Put state. ts: {}, connId: {}, reason: {}".format(
|
|
|
|
req.client_side_timestamp,
|
|
|
|
Utils.truncate_middle(self._connectionId, 10),
|
|
|
|
req.put_state_reason,
|
|
|
|
))
|
2021-02-25 00:07:17 +01:00
|
|
|
|
|
|
|
class Endpoint(enum.Enum):
|
|
|
|
Play: str = "play"
|
|
|
|
Pause: str = "pause"
|
|
|
|
Resume: str = "resume"
|
|
|
|
SeekTo: str = "seek_to"
|
|
|
|
SkipNext: str = "skip_next"
|
|
|
|
SkipPrev: str = "skip_prev"
|
|
|
|
|
|
|
|
class Listener:
|
|
|
|
def ready(self) -> None:
|
|
|
|
pass
|
|
|
|
|
2021-04-10 01:16:18 +02:00
|
|
|
def command(
|
|
|
|
self,
|
|
|
|
endpoint: DeviceStateHandler.Endpoint,
|
|
|
|
data: DeviceStateHandler.CommandBody,
|
|
|
|
) -> None:
|
2021-02-25 00:07:17 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
def volume_changed(self) -> None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def not_active(self) -> None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
class CommandBody:
|
|
|
|
_obj: typing.Any = None
|
|
|
|
_data: bytes = None
|
|
|
|
_value: str = None
|
|
|
|
|
|
|
|
def __init__(self, obj: typing.Any):
|
|
|
|
self._obj = obj
|
|
|
|
|
|
|
|
if obj.get("data") is not None:
|
|
|
|
self._data = base64.b64decode(obj.get("data"))
|
|
|
|
|
|
|
|
if obj.get("value") is not None:
|
|
|
|
self._value = obj.get("value")
|