Rewrite
This commit is contained in:
@@ -1,70 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sched
|
||||
import time
|
||||
import typing
|
||||
|
||||
from librespot.core.Session import Session
|
||||
from librespot.player import PlayerConfiguration
|
||||
from librespot.player import 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
|
||||
|
||||
|
||||
class Player(Closeable, PlayerSession.Listener, AudioSink.Listener):
|
||||
VOLUME_MAX: int = 65536
|
||||
_LOGGER: logging = logging.getLogger(__name__)
|
||||
_scheduler: sched.scheduler = sched.scheduler(time.time)
|
||||
_session: Session
|
||||
_conf: PlayerConfiguration
|
||||
_events: Player.EventsDispatcher
|
||||
_sink: AudioSink
|
||||
_metrics: typing.Dict[str, PlaybackMetrics] = {}
|
||||
_state: StateWrapper
|
||||
_playerSession: PlayerSession
|
||||
_releaseLineFuture = None
|
||||
_deviceStateListener: DeviceStateHandler.Listener
|
||||
|
||||
def __init__(self, conf: PlayerConfiguration, session: Session):
|
||||
self._conf = conf
|
||||
self._session = session
|
||||
self._events = Player.EventsDispatcher(conf)
|
||||
self._sink = AudioSink(conf, self)
|
||||
|
||||
self.__init_state()
|
||||
|
||||
def __init_state(self):
|
||||
self._state = StateWrapper.StateWrapper(self._session, self,
|
||||
self._conf)
|
||||
|
||||
class Anonymous(DeviceStateHandler.Listener):
|
||||
_player: Player = None
|
||||
|
||||
def __init__(self, player: Player):
|
||||
self._player = player
|
||||
|
||||
def ready(self) -> None:
|
||||
pass
|
||||
|
||||
def command(
|
||||
self,
|
||||
endpoint: DeviceStateHandler.Endpoint,
|
||||
data: DeviceStateHandler.CommandBody,
|
||||
) -> None:
|
||||
self._player._LOGGER.debug(
|
||||
"Received command: {}".format(endpoint))
|
||||
|
||||
self._deviceStateListener = Anonymous(self)
|
||||
self._state.add_listener(self._deviceStateListener)
|
||||
|
||||
def volume_up(self, steps: int = 1):
|
||||
if self._state is None:
|
||||
return
|
||||
|
||||
class EventsDispatcher:
|
||||
def __init__(self, conf: PlayerConfiguration):
|
||||
pass
|
||||
@@ -1,91 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from librespot.audio.decoders import AudioQuality
|
||||
|
||||
|
||||
class PlayerConfiguration:
|
||||
# Audio
|
||||
preferred_quality: AudioQuality
|
||||
enable_normalisation: bool
|
||||
normalisation_pregain: float
|
||||
autoplay_enabled: bool
|
||||
crossfade_duration: int
|
||||
preload_enabled: bool
|
||||
|
||||
# Volume
|
||||
initial_volume: int
|
||||
volume_steps: int
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
preferred_quality: AudioQuality,
|
||||
enable_normalisation: bool,
|
||||
normalisation_pregain: float,
|
||||
autoplay_enabled: bool,
|
||||
crossfade_duration: int,
|
||||
preload_enabled: bool,
|
||||
initial_volume: int,
|
||||
volume_steps: int,
|
||||
):
|
||||
self.preferred_quality = preferred_quality
|
||||
self.enable_normalisation = enable_normalisation
|
||||
self.normalisation_pregain = normalisation_pregain
|
||||
self.autoplay_enabled = autoplay_enabled
|
||||
self.crossfade_duration = crossfade_duration
|
||||
self.preload_enabled = preload_enabled
|
||||
self.initial_volume = initial_volume
|
||||
self.volume_steps = volume_steps
|
||||
|
||||
class Builder:
|
||||
preferred_quality: AudioQuality = AudioQuality.NORMAL
|
||||
enable_normalisation: bool = True
|
||||
normalisation_pregain: float = 3.0
|
||||
autoplay_enabled: bool = True
|
||||
crossfade_duration: int = 0
|
||||
preload_enabled: bool = True
|
||||
|
||||
# Volume
|
||||
initial_volume: int = 65536
|
||||
volume_steps: int = 64
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def set_preferred_quality(
|
||||
self, preferred_quality: AudioQuality) -> __class__:
|
||||
self.preferred_quality = preferred_quality
|
||||
return self
|
||||
|
||||
def set_enable_normalisation(self,
|
||||
enable_normalisation: bool) -> __class__:
|
||||
self.enable_normalisation = enable_normalisation
|
||||
return self
|
||||
|
||||
def set_normalisation_pregain(
|
||||
self, normalisation_pregain: float) -> __class__:
|
||||
self.normalisation_pregain = normalisation_pregain
|
||||
return self
|
||||
|
||||
def set_autoplay_enabled(self, autoplay_enabled: bool) -> __class__:
|
||||
self.autoplay_enabled = autoplay_enabled
|
||||
return self
|
||||
|
||||
def set_crossfade_duration(self, crossfade_duration: int) -> __class__:
|
||||
self.crossfade_duration = crossfade_duration
|
||||
return self
|
||||
|
||||
def set_preload_enabled(self, preload_enabled: bool) -> __class__:
|
||||
self.preload_enabled = preload_enabled
|
||||
return self
|
||||
|
||||
def build(self) -> PlayerConfiguration:
|
||||
return PlayerConfiguration(
|
||||
self.preferred_quality,
|
||||
self.enable_normalisation,
|
||||
self.normalisation_pregain,
|
||||
self.autoplay_enabled,
|
||||
self.crossfade_duration,
|
||||
self.preload_enabled,
|
||||
self.initial_volume,
|
||||
self.volume_steps,
|
||||
)
|
||||
@@ -1,60 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
|
||||
from librespot.core import Session
|
||||
from librespot.dealer import DealerClient
|
||||
from librespot.player import Player
|
||||
from librespot.player import PlayerConfiguration
|
||||
from librespot.player.state import DeviceStateHandler
|
||||
from librespot.proto import Connect_pb2 as Connect
|
||||
from librespot.proto.Player_pb2 import ContextPlayerOptions
|
||||
from librespot.proto.Player_pb2 import PlayerState
|
||||
from librespot.proto.Player_pb2 import Restrictions
|
||||
from librespot.proto.Player_pb2 import Suppressions
|
||||
|
||||
|
||||
class StateWrapper(DeviceStateHandler.Listener, DealerClient.MessageListener):
|
||||
_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()
|
||||
|
||||
self._device.add_listener(self)
|
||||
self._session.dealer().add_message_listener(
|
||||
self,
|
||||
"spotify:user:attributes:update",
|
||||
"hm://playlist/",
|
||||
"hm://collection/collection/" + self._session.username() + "/json",
|
||||
)
|
||||
|
||||
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 add_listener(self, listener: DeviceStateHandler.Listener):
|
||||
self._device.add_listener(listener)
|
||||
|
||||
def ready(self) -> None:
|
||||
self._device.update_state(Connect.PutStateReason.NEW_DEVICE, 0,
|
||||
self._state)
|
||||
|
||||
def on_message(self, uri: str, headers: typing.Dict[str, str],
|
||||
payload: bytes):
|
||||
pass
|
||||
@@ -1,3 +1,88 @@
|
||||
from librespot.player.Player import Player
|
||||
from librespot.player.PlayerConfiguration import PlayerConfiguration
|
||||
from librespot.player.StateWrapper import StateWrapper
|
||||
from __future__ import annotations
|
||||
from librespot.audio.decoders import AudioQuality
|
||||
|
||||
|
||||
class PlayerConfiguration:
|
||||
# Audio
|
||||
preferred_quality: AudioQuality
|
||||
enable_normalisation: bool
|
||||
normalisation_pregain: float
|
||||
autoplay_enabled: bool
|
||||
crossfade_duration: int
|
||||
preload_enabled: bool
|
||||
|
||||
# Volume
|
||||
initial_volume: int
|
||||
volume_steps: int
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
preferred_quality: AudioQuality,
|
||||
enable_normalisation: bool,
|
||||
normalisation_pregain: float,
|
||||
autoplay_enabled: bool,
|
||||
crossfade_duration: int,
|
||||
preload_enabled: bool,
|
||||
initial_volume: int,
|
||||
volume_steps: int,
|
||||
):
|
||||
self.preferred_quality = preferred_quality
|
||||
self.enable_normalisation = enable_normalisation
|
||||
self.normalisation_pregain = normalisation_pregain
|
||||
self.autoplay_enabled = autoplay_enabled
|
||||
self.crossfade_duration = crossfade_duration
|
||||
self.preload_enabled = preload_enabled
|
||||
self.initial_volume = initial_volume
|
||||
self.volume_steps = volume_steps
|
||||
|
||||
class Builder:
|
||||
preferred_quality: AudioQuality = AudioQuality.NORMAL
|
||||
enable_normalisation: bool = True
|
||||
normalisation_pregain: float = 3.0
|
||||
autoplay_enabled: bool = True
|
||||
crossfade_duration: int = 0
|
||||
preload_enabled: bool = True
|
||||
|
||||
# Volume
|
||||
initial_volume: int = 65536
|
||||
volume_steps: int = 64
|
||||
|
||||
def set_preferred_quality(
|
||||
self, preferred_quality: AudioQuality) -> __class__:
|
||||
self.preferred_quality = preferred_quality
|
||||
return self
|
||||
|
||||
def set_enable_normalisation(self,
|
||||
enable_normalisation: bool) -> __class__:
|
||||
self.enable_normalisation = enable_normalisation
|
||||
return self
|
||||
|
||||
def set_normalisation_pregain(
|
||||
self, normalisation_pregain: float) -> __class__:
|
||||
self.normalisation_pregain = normalisation_pregain
|
||||
return self
|
||||
|
||||
def set_autoplay_enabled(self, autoplay_enabled: bool) -> __class__:
|
||||
self.autoplay_enabled = autoplay_enabled
|
||||
return self
|
||||
|
||||
def set_crossfade_duration(self, crossfade_duration: int) -> __class__:
|
||||
self.crossfade_duration = crossfade_duration
|
||||
return self
|
||||
|
||||
def set_preload_enabled(self, preload_enabled: bool) -> __class__:
|
||||
self.preload_enabled = preload_enabled
|
||||
return self
|
||||
|
||||
def build(self) -> PlayerConfiguration:
|
||||
return PlayerConfiguration(
|
||||
self.preferred_quality,
|
||||
self.enable_normalisation,
|
||||
self.normalisation_pregain,
|
||||
self.autoplay_enabled,
|
||||
self.crossfade_duration,
|
||||
self.preload_enabled,
|
||||
self.initial_volume,
|
||||
self.volume_steps,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from librespot.audio import SuperAudioFormat
|
||||
from librespot.audio.decoders import AudioQuality
|
||||
from librespot.proto import Metadata_pb2 as Metadata
|
||||
from librespot.structure import AudioQualityPicker
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from librespot.audio.decoders.AudioQuality import AudioQuality
|
||||
from librespot.audio.format.AudioQualityPicker import AudioQualityPicker
|
||||
from librespot.audio.format.SuperAudioFormat import SuperAudioFormat
|
||||
from librespot.proto import Metadata_pb2 as Metadata
|
||||
|
||||
|
||||
class VorbisOnlyAudioQuality(AudioQualityPicker):
|
||||
_LOGGER: logging = logging.getLogger(__name__)
|
||||
logger = logging.getLogger("Librespot:Player:VorbisOnlyAudioQuality")
|
||||
preferred: AudioQuality
|
||||
|
||||
def __init__(self, preferred: AudioQuality):
|
||||
@@ -19,26 +17,18 @@ class VorbisOnlyAudioQuality(AudioQualityPicker):
|
||||
@staticmethod
|
||||
def get_vorbis_file(files: typing.List[Metadata.AudioFile]):
|
||||
for file in files:
|
||||
if (hasattr(file, "format") and SuperAudioFormat.get(file.format)
|
||||
== SuperAudioFormat.VORBIS):
|
||||
if hasattr(file, "format") and SuperAudioFormat.get(file.format) == SuperAudioFormat.VORBIS:
|
||||
return file
|
||||
|
||||
return None
|
||||
|
||||
def get_file(self, files: typing.List[Metadata.AudioFile]):
|
||||
matches: typing.List[Metadata.AudioFile] = self.preferred.get_matches(
|
||||
files)
|
||||
vorbis: Metadata.AudioFile = VorbisOnlyAudioQuality.get_vorbis_file(
|
||||
matches)
|
||||
matches: typing.List[Metadata.AudioFile] = self.preferred.get_matches(files)
|
||||
vorbis: Metadata.AudioFile = VorbisOnlyAudioQuality.get_vorbis_file(matches)
|
||||
if vorbis is None:
|
||||
vorbis: Metadata.AudioFile = VorbisOnlyAudioQuality.get_vorbis_file(
|
||||
files)
|
||||
vorbis: Metadata.AudioFile = VorbisOnlyAudioQuality.get_vorbis_file(files)
|
||||
if vorbis is not None:
|
||||
self._LOGGER.warning(
|
||||
"Using {} because preferred {} couldn't be found.".format(
|
||||
vorbis.format, self.preferred))
|
||||
self.logger.warning("Using {} because preferred {} couldn't be found."
|
||||
.format(vorbis.format, self.preferred))
|
||||
else:
|
||||
self._LOGGER.fatal(
|
||||
"Couldn't find any Vorbis file, available: {}")
|
||||
|
||||
self.logger.fatal("Couldn't find any Vorbis file, available: {}")
|
||||
return vorbis
|
||||
@@ -1 +0,0 @@
|
||||
from librespot.player.codecs.VorbisOnlyAudioQuality import VorbisOnlyAudioQuality
|
||||
@@ -1,6 +0,0 @@
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
|
||||
|
||||
class PlaybackMetrics:
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -1,13 +0,0 @@
|
||||
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 +0,0 @@
|
||||
from librespot.player.mixing.AudioSink import AudioSink
|
||||
@@ -1,7 +0,0 @@
|
||||
from __future__ import annotations
|
||||
from librespot.standard import Closeable
|
||||
|
||||
|
||||
class PlayerSession(Closeable):
|
||||
class Listener:
|
||||
pass
|
||||
@@ -1 +0,0 @@
|
||||
from librespot.player.playback.PlayerSession import PlayerSession
|
||||
@@ -1,115 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import concurrent.futures
|
||||
import enum
|
||||
import logging
|
||||
import time
|
||||
import typing
|
||||
import urllib.parse
|
||||
|
||||
from librespot.common import Utils
|
||||
from librespot.core import Session
|
||||
from librespot.player import PlayerConfiguration
|
||||
from librespot.proto import Connect_pb2 as Connect
|
||||
from librespot.proto import Player_pb2 as Player
|
||||
|
||||
|
||||
class DeviceStateHandler:
|
||||
_LOGGER: logging = logging.getLogger(__name__)
|
||||
_session: Session
|
||||
_deviceInfo: Connect.DeviceInfo
|
||||
_listeners: typing.List[DeviceStateHandler.Listener] = []
|
||||
_putState: Connect.PutStateRequest
|
||||
_putStateWorker: concurrent.futures.ThreadPoolExecutor = (
|
||||
concurrent.futures.ThreadPoolExecutor())
|
||||
_connectionId: str
|
||||
|
||||
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 add_listener(self, listener: DeviceStateHandler.Listener):
|
||||
self._listeners.append(listener)
|
||||
|
||||
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
|
||||
_data: bytes
|
||||
_value: str
|
||||
|
||||
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 +0,0 @@
|
||||
from librespot.player.state.DeviceStateHandler import DeviceStateHandler
|
||||
Reference in New Issue
Block a user