Change Directory
This commit is contained in:
@@ -8,6 +8,17 @@ class Utils:
|
|||||||
buffer = os.urandom(int(length / 2))
|
buffer = os.urandom(int(length / 2))
|
||||||
return Utils.bytes_to_hex(buffer)
|
return Utils.bytes_to_hex(buffer)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def truncate_middle(s: str, length: int) -> str:
|
||||||
|
if length <= 1:
|
||||||
|
raise TypeError()
|
||||||
|
|
||||||
|
first = length / 2
|
||||||
|
result = s[:first]
|
||||||
|
result += "..."
|
||||||
|
result += s[len(s) - (length - first):]
|
||||||
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def split(s: str, c: str):
|
def split(s: str, c: str):
|
||||||
return s.split(c)
|
return s.split(c)
|
||||||
52
librespot/player/Player.py
Normal file
52
librespot/player/Player.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from librespot.core.Session import Session
|
||||||
|
from librespot.player import PlayerConfiguration, StateWrapper
|
||||||
|
from librespot.player.metrics import PlaybackMetrics
|
||||||
|
from librespot.player.mixing import AudioSink
|
||||||
|
from librespot.player.playback.PlayerSession import PlayerSession
|
||||||
|
from librespot.player.state.DeviceStateHandler import DeviceStateHandler
|
||||||
|
from librespot.standard.Closeable import Closeable
|
||||||
|
import sched
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class Player(Closeable, PlayerSession.Listener, AudioSink.Listener):
|
||||||
|
VOLUME_MAX: int = 65536
|
||||||
|
_scheduler: sched.scheduler = sched.scheduler(time.time)
|
||||||
|
_session: Session = None
|
||||||
|
_conf: PlayerConfiguration = None
|
||||||
|
_events: Player.EventsDispatcher = None
|
||||||
|
_sink: AudioSink = None
|
||||||
|
_metrics: dict[str, PlaybackMetrics] = dict()
|
||||||
|
_state: StateWrapper = None
|
||||||
|
_playerSession: PlayerSession = None
|
||||||
|
_releaseLineFuture = None
|
||||||
|
_deviceStateListener: DeviceStateHandler.Listener = None
|
||||||
|
|
||||||
|
def __init__(self, conf: PlayerConfiguration, session: Session):
|
||||||
|
self._conf = conf
|
||||||
|
self._session = session
|
||||||
|
self._events = Player.EventsDispatcher(conf)
|
||||||
|
self._sink = AudioSink(conf, self)
|
||||||
|
|
||||||
|
def init_state(self):
|
||||||
|
self._state = StateWrapper(self._session, self, self._conf)
|
||||||
|
|
||||||
|
class Anonymous(DeviceStateHandler.Listener):
|
||||||
|
def ready(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def command(self, endpoint: DeviceStateHandler.Endpoint, data: DeviceStateHandler.CommandBody) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._deviceStateListener = Anonymous()
|
||||||
|
|
||||||
|
|
||||||
|
def volume_up(self, steps: int = 1):
|
||||||
|
if self.state is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class EventsDispatcher:
|
||||||
|
def __init__(self, conf: PlayerConfiguration):
|
||||||
|
pass
|
||||||
38
librespot/player/StateWrapper.py
Normal file
38
librespot/player/StateWrapper.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from librespot.core import Session
|
||||||
|
from librespot.player import Player, PlayerConfiguration
|
||||||
|
from librespot.player.state import DeviceStateHandler
|
||||||
|
from librespot.proto import Connect
|
||||||
|
from librespot.proto.Player import ContextPlayerOptions, PlayerState, Restrictions, Suppressions
|
||||||
|
|
||||||
|
|
||||||
|
class StateWrapper(DeviceStateHandler.Listener):
|
||||||
|
_state: PlayerState = None
|
||||||
|
_session: Session = None
|
||||||
|
_player: Player = None
|
||||||
|
_device: DeviceStateHandler = None
|
||||||
|
|
||||||
|
def __init__(self, session: Session, player: Player,
|
||||||
|
conf: PlayerConfiguration):
|
||||||
|
self._session = session
|
||||||
|
self._player = player
|
||||||
|
self._device = DeviceStateHandler(session, self, conf)
|
||||||
|
self._state = self._init_state()
|
||||||
|
|
||||||
|
def _init_state(self) -> PlayerState:
|
||||||
|
return PlayerState(
|
||||||
|
playback_speed=1.0,
|
||||||
|
suppressions=Suppressions(),
|
||||||
|
context_restrictions=Restrictions(),
|
||||||
|
options=ContextPlayerOptions(
|
||||||
|
repeating_context=False,
|
||||||
|
shuffling_context=False,
|
||||||
|
repeating_track=False
|
||||||
|
),
|
||||||
|
position_as_of_timestamp=0,
|
||||||
|
position=0,
|
||||||
|
is_playing=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def ready(self) -> None:
|
||||||
|
self._device.update_state(Connect.PutStateReason.NEW_DEVICE, 0, self._state)
|
||||||
6
librespot/player/metrics/PlaybackMetrics.py
Normal file
6
librespot/player/metrics/PlaybackMetrics.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class PlaybackMetrics:
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
11
librespot/player/mixing/AudioSink.py
Normal file
11
librespot/player/mixing/AudioSink.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from librespot.player import PlayerConfiguration
|
||||||
|
|
||||||
|
|
||||||
|
class AudioSink:
|
||||||
|
def __init__(self, conf: PlayerConfiguration, listener: AudioSink.Listener):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Listener:
|
||||||
|
def sink_error(self, ex: Exception):
|
||||||
|
pass
|
||||||
1
librespot/player/mixing/__init__.py
Normal file
1
librespot/player/mixing/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from librespot.player.mixing.AudioSink import AudioSink
|
||||||
@@ -3,4 +3,5 @@ from librespot.standard import Closeable
|
|||||||
|
|
||||||
|
|
||||||
class PlayerSession(Closeable):
|
class PlayerSession(Closeable):
|
||||||
pass
|
class Listener:
|
||||||
|
pass
|
||||||
97
librespot/player/state/DeviceStateHandler.py
Normal file
97
librespot/player/state/DeviceStateHandler.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from librespot.common import Utils
|
||||||
|
from librespot.core import Session
|
||||||
|
from librespot.player import PlayerConfiguration
|
||||||
|
from librespot.proto import Connect, Player
|
||||||
|
import base64
|
||||||
|
import concurrent.futures
|
||||||
|
import enum
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import typing
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceStateHandler:
|
||||||
|
_LOGGER: logging = logging.getLogger(__name__)
|
||||||
|
_session: Session = None
|
||||||
|
_deviceInfo: Connect.DeviceInfo = None
|
||||||
|
_listeners: list[DeviceStateHandler.Listener] = list()
|
||||||
|
_putState: Connect.PutStateRequest = None
|
||||||
|
_putStateWorker: concurrent.futures.ThreadPoolExecutor = concurrent.futures.ThreadPoolExecutor()
|
||||||
|
_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")
|
||||||
|
|
||||||
|
if self._connectionId is None or \
|
||||||
|
self._connectionId != newer:
|
||||||
|
self._connectionId = newer
|
||||||
|
self._LOGGER.debug("Updated Spotify-Connection-Id: {}".format(self._connectionId))
|
||||||
|
self._notify_ready()
|
||||||
|
|
||||||
|
def _notify_ready(self) -> None:
|
||||||
|
for listener in self._listeners:
|
||||||
|
listener.ready()
|
||||||
|
|
||||||
|
def update_state(self, reason: Connect.PutStateReason, player_time: int, state: Player.PlayerState):
|
||||||
|
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)
|
||||||
|
self._LOGGER.info("Put state. ts: {}, connId: {}, reason: {}".format(
|
||||||
|
req.client_side_timestamp, Utils.truncate_middle(self._connectionId, 10), req.put_state_reason
|
||||||
|
))
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def command(self, endpoint: DeviceStateHandler.Endpoint, data: DeviceStateHandler.CommandBody) -> None:
|
||||||
|
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")
|
||||||
1
librespot/player/state/__init__.py
Normal file
1
librespot/player/state/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from librespot.player.state.DeviceStateHandler import DeviceStateHandler
|
||||||
@@ -11,7 +11,7 @@ from google.protobuf import symbol_database as _symbol_database
|
|||||||
|
|
||||||
_sym_db = _symbol_database.Default()
|
_sym_db = _symbol_database.Default()
|
||||||
|
|
||||||
import librespot.proto.player_pb2 as player__pb2
|
import librespot.proto.Player as player__pb2
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor.FileDescriptor(
|
DESCRIPTOR = _descriptor.FileDescriptor(
|
||||||
name='connect.proto',
|
name='connect.proto',
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user