Merge pull request #16 from raitonoberu/main
Fix some critical bugs and update the example
This commit is contained in:
10
README.md
10
README.md
@@ -29,7 +29,7 @@ from librespot.core import Session
|
|||||||
|
|
||||||
|
|
||||||
session = Session.Builder() \
|
session = Session.Builder() \
|
||||||
.user_pass("<Username>", "<Password>") \
|
.user_pass("Username", "Password") \
|
||||||
.create()
|
.create()
|
||||||
|
|
||||||
aceess_token = session.tokens().get("playlist-read")
|
aceess_token = session.tokens().get("playlist-read")
|
||||||
@@ -38,16 +38,16 @@ aceess_token = session.tokens().get("playlist-read")
|
|||||||
\*Currently, music streaming is supported, but it may cause unintended behavior.
|
\*Currently, music streaming is supported, but it may cause unintended behavior.
|
||||||
```python
|
```python
|
||||||
from librespot.core import Session
|
from librespot.core import Session
|
||||||
|
from librespot.metadata import TrackId
|
||||||
|
from librespot.player.codecs import AudioQuality, VorbisOnlyAudioQuality
|
||||||
|
|
||||||
|
|
||||||
session = Session.Builder() \
|
session = Session.Builder() \
|
||||||
.user_pass("<Username>", "<Password>") \
|
.user_pass("Username", "Password") \
|
||||||
.create()
|
.create()
|
||||||
|
|
||||||
track_id = TrackId.from_uri("<TrackID(ex, spotify:track:xxxxxxxxxxxxxxxxxxxxxx)>")
|
track_id = TrackId.from_uri("spotify:track:xxxxxxxxxxxxxxxxxxxxxx")
|
||||||
|
|
||||||
stream = session.content_feeder().load(track_id, VorbisOnlyAudioQuality(AudioQuality.AudioQuality.VERY_HIGH), False, None)
|
stream = session.content_feeder().load(track_id, VorbisOnlyAudioQuality(AudioQuality.AudioQuality.VERY_HIGH), False, None)
|
||||||
|
|
||||||
# stream.input_stream.stream().read() to get one byte of the music stream
|
# stream.input_stream.stream().read() to get one byte of the music stream
|
||||||
```
|
```
|
||||||
Please read [this document](https://librespot-python.rtfd.io) for detailed specifications.
|
Please read [this document](https://librespot-python.rtfd.io) for detailed specifications.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class AbsChunkedInputStream(InputStream, HaltListener):
|
|||||||
preload_chunk_retries: typing.Final[int] = 2
|
preload_chunk_retries: typing.Final[int] = 2
|
||||||
max_chunk_tries: typing.Final[int] = 128
|
max_chunk_tries: typing.Final[int] = 128
|
||||||
wait_lock: threading.Condition = threading.Condition()
|
wait_lock: threading.Condition = threading.Condition()
|
||||||
retries: list[int]
|
retries: typing.List[int]
|
||||||
retry_on_chunk_error: bool
|
retry_on_chunk_error: bool
|
||||||
chunk_exception = None
|
chunk_exception = None
|
||||||
wait_for_chunk: int = -1
|
wait_for_chunk: int = -1
|
||||||
@@ -22,7 +22,7 @@ class AbsChunkedInputStream(InputStream, HaltListener):
|
|||||||
_decoded_length: int = 0
|
_decoded_length: int = 0
|
||||||
|
|
||||||
def __init__(self, retry_on_chunk_error: bool):
|
def __init__(self, retry_on_chunk_error: bool):
|
||||||
self.retries: typing.Final[list[int]] = [
|
self.retries: typing.Final[typing.List[int]] = [
|
||||||
0 for _ in range(self.chunks())
|
0 for _ in range(self.chunks())
|
||||||
]
|
]
|
||||||
self.retry_on_chunk_error = retry_on_chunk_error
|
self.retry_on_chunk_error = retry_on_chunk_error
|
||||||
@@ -30,7 +30,7 @@ class AbsChunkedInputStream(InputStream, HaltListener):
|
|||||||
def is_closed(self) -> bool:
|
def is_closed(self) -> bool:
|
||||||
return self.closed
|
return self.closed
|
||||||
|
|
||||||
def buffer(self) -> list[bytearray]:
|
def buffer(self) -> typing.List[bytearray]:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def size(self) -> int:
|
def size(self) -> int:
|
||||||
@@ -83,10 +83,10 @@ class AbsChunkedInputStream(InputStream, HaltListener):
|
|||||||
|
|
||||||
return k
|
return k
|
||||||
|
|
||||||
def requested_chunks(self) -> list[bool]:
|
def requested_chunks(self) -> typing.List[bool]:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def available_chunks(self) -> list[bool]:
|
def available_chunks(self) -> typing.List[bool]:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def chunks(self) -> int:
|
def chunks(self) -> int:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from librespot.standard import BytesInputStream, ByteArrayOutputStream
|
|||||||
import logging
|
import logging
|
||||||
import queue
|
import queue
|
||||||
import threading
|
import threading
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
class AudioKeyManager(PacketsReceiver):
|
class AudioKeyManager(PacketsReceiver):
|
||||||
@@ -15,7 +16,7 @@ class AudioKeyManager(PacketsReceiver):
|
|||||||
_AUDIO_KEY_REQUEST_TIMEOUT: int = 20
|
_AUDIO_KEY_REQUEST_TIMEOUT: int = 20
|
||||||
_seqHolder: int = 0
|
_seqHolder: int = 0
|
||||||
_seqHolderLock: threading.Condition = threading.Condition()
|
_seqHolderLock: threading.Condition = threading.Condition()
|
||||||
_callbacks: dict[int, AudioKeyManager.Callback] = {}
|
_callbacks: typing.Dict[int, AudioKeyManager.Callback] = {}
|
||||||
_session: Session = None
|
_session: Session = None
|
||||||
|
|
||||||
def __init__(self, session: Session):
|
def __init__(self, session: Session):
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ class CdnManager:
|
|||||||
|
|
||||||
class InternalResponse:
|
class InternalResponse:
|
||||||
_buffer: bytearray
|
_buffer: bytearray
|
||||||
_headers: dict[str, str]
|
_headers: typing.Dict[str, str]
|
||||||
|
|
||||||
def __init__(self, buffer: bytearray, headers: dict[str, str]):
|
def __init__(self, buffer: bytearray, headers: typing.Dict[str, str]):
|
||||||
self._buffer = buffer
|
self._buffer = buffer
|
||||||
self._headers = headers
|
self._headers = headers
|
||||||
|
|
||||||
@@ -163,9 +163,9 @@ class CdnManager:
|
|||||||
_audioDecrypt: AudioDecrypt = None
|
_audioDecrypt: AudioDecrypt = None
|
||||||
_cdnUrl = None
|
_cdnUrl = None
|
||||||
_size: int
|
_size: int
|
||||||
_buffer: list[bytearray]
|
_buffer: typing.List[bytearray]
|
||||||
_available: list[bool]
|
_available: typing.List[bool]
|
||||||
_requested: list[bool]
|
_requested: typing.List[bool]
|
||||||
_chunks: int
|
_chunks: int
|
||||||
_internalStream: CdnManager.Streamer.InternalStream = None
|
_internalStream: CdnManager.Streamer.InternalStream = None
|
||||||
_haltListener: HaltListener = None
|
_haltListener: HaltListener = None
|
||||||
@@ -269,16 +269,16 @@ class CdnManager:
|
|||||||
self.streamer: CdnManager.Streamer = streamer
|
self.streamer: CdnManager.Streamer = streamer
|
||||||
super().__init__(retry_on_chunk_error)
|
super().__init__(retry_on_chunk_error)
|
||||||
|
|
||||||
def buffer(self) -> list[bytearray]:
|
def buffer(self) -> typing.List[bytearray]:
|
||||||
return self.streamer._buffer
|
return self.streamer._buffer
|
||||||
|
|
||||||
def size(self) -> int:
|
def size(self) -> int:
|
||||||
return self.streamer._size
|
return self.streamer._size
|
||||||
|
|
||||||
def requested_chunks(self) -> list[bool]:
|
def requested_chunks(self) -> typing.List[bool]:
|
||||||
return self.streamer._requested
|
return self.streamer._requested
|
||||||
|
|
||||||
def available_chunks(self) -> list[bool]:
|
def available_chunks(self) -> typing.List[bool]:
|
||||||
return self.streamer._available
|
return self.streamer._available
|
||||||
|
|
||||||
def chunks(self) -> int:
|
def chunks(self) -> int:
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ if typing.TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class AudioQualityPicker:
|
class AudioQualityPicker:
|
||||||
def get_file(self, files: list[Metadata.AudioFile]) -> Metadata.AudioFile:
|
def get_file(self, files: typing.List[Metadata.AudioFile]) -> Metadata.AudioFile:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ from librespot.standard import BytesInputStream, BytesOutputStream, Closeable, R
|
|||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
|
class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
|
||||||
CHUNK_SIZE: int = 128 * 1024
|
CHUNK_SIZE: int = 128 * 1024
|
||||||
_LOGGER: logging = logging.getLogger(__name__)
|
_LOGGER: logging = logging.getLogger(__name__)
|
||||||
_channels: dict[int, Channel] = {}
|
_channels: typing.Dict[int, Channel] = {}
|
||||||
_seqHolder: int = 0
|
_seqHolder: int = 0
|
||||||
_seqHolderLock: threading.Condition = threading.Condition()
|
_seqHolderLock: threading.Condition = threading.Condition()
|
||||||
_executorService: concurrent.futures.ThreadPoolExecutor = concurrent.futures.ThreadPoolExecutor(
|
_executorService: concurrent.futures.ThreadPoolExecutor = concurrent.futures.ThreadPoolExecutor(
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import socket
|
|||||||
import struct
|
import struct
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
class Session(Closeable, SubListener, DealerClient.MessageListener):
|
class Session(Closeable, SubListener, DealerClient.MessageListener):
|
||||||
@@ -60,11 +61,11 @@ class Session(Closeable, SubListener, DealerClient.MessageListener):
|
|||||||
_authLock: threading.Condition = threading.Condition()
|
_authLock: threading.Condition = threading.Condition()
|
||||||
_authLockBool: bool = False
|
_authLockBool: bool = False
|
||||||
_client: requests.Session = None
|
_client: requests.Session = None
|
||||||
_closeListeners: list[Session.CloseListener] = []
|
_closeListeners: typing.List[Session.CloseListener] = []
|
||||||
_closeListenersLock: threading.Condition = threading.Condition()
|
_closeListenersLock: threading.Condition = threading.Condition()
|
||||||
_reconnectionListeners: list[Session.ReconnectionListener] = []
|
_reconnectionListeners: typing.List[Session.ReconnectionListener] = []
|
||||||
_reconnectionListenersLock: threading.Condition = threading.Condition()
|
_reconnectionListenersLock: threading.Condition = threading.Condition()
|
||||||
_userAttributes: dict[str, str] = {}
|
_userAttributes: typing.Dict[str, str] = {}
|
||||||
_conn: Session.ConnectionHolder = None
|
_conn: Session.ConnectionHolder = None
|
||||||
_cipherPair: CipherPair = None
|
_cipherPair: CipherPair = None
|
||||||
_receiver: Session.Receiver = None
|
_receiver: Session.Receiver = None
|
||||||
@@ -379,9 +380,9 @@ class Session(Closeable, SubListener, DealerClient.MessageListener):
|
|||||||
with self._closeListenersLock:
|
with self._closeListenersLock:
|
||||||
for listener in self._closeListeners:
|
for listener in self._closeListeners:
|
||||||
listener.on_closed()
|
listener.on_closed()
|
||||||
self._closeListeners: list[Session.CloseListener] = []
|
self._closeListeners: typing.List[Session.CloseListener] = []
|
||||||
|
|
||||||
self._reconnectionListeners: list[Session.ReconnectionListener] = []
|
self._reconnectionListeners: typing.List[Session.ReconnectionListener] = []
|
||||||
|
|
||||||
self._LOGGER.info("Closed session. device_id: {}".format(
|
self._LOGGER.info("Closed session. device_id: {}".format(
|
||||||
self._inner.device_id))
|
self._inner.device_id))
|
||||||
@@ -586,7 +587,7 @@ class Session(Closeable, SubListener, DealerClient.MessageListener):
|
|||||||
self._LOGGER.info("Updated user attribute: {} -> {}".format(
|
self._LOGGER.info("Updated user attribute: {} -> {}".format(
|
||||||
pair.key, pair.value))
|
pair.key, pair.value))
|
||||||
|
|
||||||
def on_message(self, uri: str, headers: dict[str, str],
|
def on_message(self, uri: str, headers: typing.Dict[str, str],
|
||||||
payload: bytes) -> None:
|
payload: bytes) -> None:
|
||||||
if uri == "hm://connect-state/v1/connect/logout":
|
if uri == "hm://connect-state/v1/connect/logout":
|
||||||
self.close()
|
self.close()
|
||||||
@@ -986,7 +987,7 @@ class Session(Closeable, SubListener, DealerClient.MessageListener):
|
|||||||
self.session._scheduledReconnect)
|
self.session._scheduledReconnect)
|
||||||
|
|
||||||
def anonymous():
|
def anonymous():
|
||||||
self._LOGGER.warning(
|
self.session._LOGGER.warning(
|
||||||
"Socket timed out. Reconnecting...")
|
"Socket timed out. Reconnecting...")
|
||||||
self.session._reconnect()
|
self.session._reconnect()
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,20 @@ from __future__ import annotations
|
|||||||
from librespot.core import Session, TimeProvider
|
from librespot.core import Session, TimeProvider
|
||||||
from librespot.mercury import MercuryRequests
|
from librespot.mercury import MercuryRequests
|
||||||
import logging
|
import logging
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
class TokenProvider:
|
class TokenProvider:
|
||||||
_LOGGER: logging = logging.getLogger(__name__)
|
_LOGGER: logging = logging.getLogger(__name__)
|
||||||
_TOKEN_EXPIRE_THRESHOLD = 10
|
_TOKEN_EXPIRE_THRESHOLD = 10
|
||||||
_session: Session = None
|
_session: Session = None
|
||||||
_tokens: list[TokenProvider.StoredToken] = []
|
_tokens: typing.List[TokenProvider.StoredToken] = []
|
||||||
|
|
||||||
def __init__(self, session: Session):
|
def __init__(self, session: Session):
|
||||||
self._session = session
|
self._session = session
|
||||||
|
|
||||||
def find_token_with_all_scopes(
|
def find_token_with_all_scopes(
|
||||||
self, scopes: list[str]) -> TokenProvider.StoredToken:
|
self, scopes: typing.List[str]) -> TokenProvider.StoredToken:
|
||||||
for token in self._tokens:
|
for token in self._tokens:
|
||||||
if token.has_scopes(scopes):
|
if token.has_scopes(scopes):
|
||||||
return token
|
return token
|
||||||
@@ -55,7 +56,7 @@ class TokenProvider:
|
|||||||
class StoredToken:
|
class StoredToken:
|
||||||
expires_in: int
|
expires_in: int
|
||||||
access_token: str
|
access_token: str
|
||||||
scopes: list[str]
|
scopes: typing.List[str]
|
||||||
timestamp: int
|
timestamp: int
|
||||||
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
@@ -76,7 +77,7 @@ class TokenProvider:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def has_scopes(self, sc: list[str]) -> bool:
|
def has_scopes(self, sc: typing.List[str]) -> bool:
|
||||||
for s in sc:
|
for s in sc:
|
||||||
if not self.has_scope(s):
|
if not self.has_scope(s):
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ from librespot.core.ApResolver import ApResolver
|
|||||||
from librespot.metadata import AlbumId, ArtistId, EpisodeId, TrackId, ShowId
|
from librespot.metadata import AlbumId, ArtistId, EpisodeId, TrackId, ShowId
|
||||||
from librespot.proto import Connect, Metadata
|
from librespot.proto import Connect, Metadata
|
||||||
from librespot.standard import Closeable
|
from librespot.standard import Closeable
|
||||||
from typing import Union
|
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
class ApiClient(Closeable):
|
class ApiClient(Closeable):
|
||||||
@@ -17,8 +17,8 @@ class ApiClient(Closeable):
|
|||||||
self._baseUrl = "https://{}".format(ApResolver.get_random_spclient())
|
self._baseUrl = "https://{}".format(ApResolver.get_random_spclient())
|
||||||
|
|
||||||
def build_request(self, method: str, suffix: str,
|
def build_request(self, method: str, suffix: str,
|
||||||
headers: Union[None, dict[str, str]],
|
headers: typing.Union[None, typing.Dict[str, str]],
|
||||||
body: Union[None, bytes]) -> requests.PreparedRequest:
|
body: typing.Union[None, bytes]) -> requests.PreparedRequest:
|
||||||
request = requests.PreparedRequest()
|
request = requests.PreparedRequest()
|
||||||
request.method = method
|
request.method = method
|
||||||
request.data = body
|
request.data = body
|
||||||
@@ -30,9 +30,9 @@ class ApiClient(Closeable):
|
|||||||
request.url = self._baseUrl + suffix
|
request.url = self._baseUrl + suffix
|
||||||
return request
|
return request
|
||||||
|
|
||||||
def send(self, method: str, suffix: str, headers: Union[None, dict[str,
|
def send(self, method: str, suffix: str, headers: typing.Union[None, typing.Dict[str,
|
||||||
str]],
|
str]],
|
||||||
body: Union[None, bytes]) -> requests.Response:
|
body: typing.Union[None, bytes]) -> requests.Response:
|
||||||
resp = self._session.client().send(
|
resp = self._session.client().send(
|
||||||
self.build_request(method, suffix, headers, body))
|
self.build_request(method, suffix, headers, body))
|
||||||
return resp
|
return resp
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from librespot.standard.Closeable import Closeable
|
from librespot.standard.Closeable import Closeable
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
class DealerClient(Closeable):
|
class DealerClient(Closeable):
|
||||||
@@ -14,6 +15,6 @@ class DealerClient(Closeable):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class MessageListener:
|
class MessageListener:
|
||||||
def on_message(self, uri: str, headers: dict[str, str],
|
def on_message(self, uri: str, headers: typing.Dict[str, str],
|
||||||
payload: bytes):
|
payload: bytes):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ class MercuryClient(PacketsReceiver.PacketsReceiver, Closeable):
|
|||||||
_MERCURY_REQUEST_TIMEOUT: int = 3
|
_MERCURY_REQUEST_TIMEOUT: int = 3
|
||||||
_seqHolder: int = 1
|
_seqHolder: int = 1
|
||||||
_seqHolderLock: threading.Condition = threading.Condition()
|
_seqHolderLock: threading.Condition = threading.Condition()
|
||||||
_callbacks: dict[int, Callback] = {}
|
_callbacks: typing.Dict[int, Callback] = {}
|
||||||
_removeCallbackLock: threading.Condition = threading.Condition()
|
_removeCallbackLock: threading.Condition = threading.Condition()
|
||||||
_subscriptions: list[MercuryClient.InternalSubListener] = []
|
_subscriptions: typing.List[MercuryClient.InternalSubListener] = []
|
||||||
_subscriptionsLock: threading.Condition = threading.Condition()
|
_subscriptionsLock: threading.Condition = threading.Condition()
|
||||||
_partials: dict[int, bytes] = {}
|
_partials: typing.Dict[int, bytes] = {}
|
||||||
_session: Session = None
|
_session: Session = None
|
||||||
|
|
||||||
def __init__(self, session: Session):
|
def __init__(self, session: Session):
|
||||||
@@ -246,10 +246,10 @@ class MercuryClient(PacketsReceiver.PacketsReceiver, Closeable):
|
|||||||
|
|
||||||
class Response:
|
class Response:
|
||||||
uri: str
|
uri: str
|
||||||
payload: list[bytes]
|
payload: typing.List[bytes]
|
||||||
status_code: int
|
status_code: int
|
||||||
|
|
||||||
def __init__(self, header: Mercury.Header, payload: list[bytes]):
|
def __init__(self, header: Mercury.Header, payload: typing.List[bytes]):
|
||||||
self.uri = header.uri
|
self.uri = header.uri
|
||||||
self.status_code = header.status_code
|
self.status_code = header.status_code
|
||||||
self.payload = payload[1:]
|
self.payload = payload[1:]
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
from librespot.proto import Mercury
|
from librespot.proto import Mercury
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
class RawMercuryRequest:
|
class RawMercuryRequest:
|
||||||
header: Mercury.Header
|
header: Mercury.Header
|
||||||
payload: list[bytes]
|
payload: typing.List[bytes]
|
||||||
|
|
||||||
def __init__(self, header: Mercury.Header, payload: list[bytes]):
|
def __init__(self, header: Mercury.Header, payload: typing.List[bytes]):
|
||||||
self.header = header
|
self.header = header
|
||||||
self.payload = payload
|
self.payload = payload
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ class RawMercuryRequest:
|
|||||||
|
|
||||||
class Builder:
|
class Builder:
|
||||||
header_dict: dict
|
header_dict: dict
|
||||||
payload: list[bytes]
|
payload: typing.List[bytes]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.header_dict = {}
|
self.header_dict = {}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from librespot.common import Utils
|
|||||||
from librespot.metadata import SpotifyId
|
from librespot.metadata import SpotifyId
|
||||||
|
|
||||||
|
|
||||||
class ShowId(SpotifyId.SpotifyId):
|
class ShowId(SpotifyId):
|
||||||
_PATTERN = re.compile("spotify:show:(.{22})")
|
_PATTERN = re.compile("spotify:show:(.{22})")
|
||||||
_BASE62 = Base62.create_instance_with_inverted_character_set()
|
_BASE62 = Base62.create_instance_with_inverted_character_set()
|
||||||
_hexId: str
|
_hexId: str
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
import sched
|
import sched
|
||||||
import time
|
import time
|
||||||
|
import typing
|
||||||
|
|
||||||
from librespot.core.Session import Session
|
from librespot.core.Session import Session
|
||||||
from librespot.player import PlayerConfiguration
|
from librespot.player import PlayerConfiguration
|
||||||
@@ -22,7 +23,7 @@ class Player(Closeable, PlayerSession.Listener, AudioSink.Listener):
|
|||||||
_conf: PlayerConfiguration = None
|
_conf: PlayerConfiguration = None
|
||||||
_events: Player.EventsDispatcher = None
|
_events: Player.EventsDispatcher = None
|
||||||
_sink: AudioSink = None
|
_sink: AudioSink = None
|
||||||
_metrics: dict[str, PlaybackMetrics] = {}
|
_metrics: typing.Dict[str, PlaybackMetrics] = {}
|
||||||
_state: StateWrapper = None
|
_state: StateWrapper = None
|
||||||
_playerSession: PlayerSession = None
|
_playerSession: PlayerSession = None
|
||||||
_releaseLineFuture = None
|
_releaseLineFuture = None
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import typing
|
||||||
|
|
||||||
from librespot.core import Session
|
from librespot.core import Session
|
||||||
from librespot.dealer import DealerClient
|
from librespot.dealer import DealerClient
|
||||||
from librespot.player import Player
|
from librespot.player import Player
|
||||||
@@ -53,5 +55,5 @@ class StateWrapper(DeviceStateHandler.Listener, DealerClient.MessageListener):
|
|||||||
self._device.update_state(Connect.PutStateReason.NEW_DEVICE, 0,
|
self._device.update_state(Connect.PutStateReason.NEW_DEVICE, 0,
|
||||||
self._state)
|
self._state)
|
||||||
|
|
||||||
def on_message(self, uri: str, headers: dict[str, str], payload: bytes):
|
def on_message(self, uri: str, headers: typing.Dict[str, str], payload: bytes):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from librespot.proto.Metadata import AudioFile
|
from librespot.proto.Metadata import AudioFile
|
||||||
import enum
|
import enum
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
class AudioQuality(enum.Enum):
|
class AudioQuality(enum.Enum):
|
||||||
@@ -26,7 +27,7 @@ class AudioQuality(enum.Enum):
|
|||||||
return AudioQuality.VERY_HIGH
|
return AudioQuality.VERY_HIGH
|
||||||
raise RuntimeError("Unknown format: {}".format(format))
|
raise RuntimeError("Unknown format: {}".format(format))
|
||||||
|
|
||||||
def get_matches(self, files: list[AudioFile]) -> list[AudioFile]:
|
def get_matches(self, files: typing.List[AudioFile]) -> typing.List[AudioFile]:
|
||||||
file_list = []
|
file_list = []
|
||||||
for file in files:
|
for file in files:
|
||||||
if hasattr(file, "format") and AudioQuality.get_quality(
|
if hasattr(file, "format") and AudioQuality.get_quality(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import typing
|
||||||
|
|
||||||
from librespot.audio.format.AudioQualityPicker import AudioQualityPicker
|
from librespot.audio.format.AudioQualityPicker import AudioQualityPicker
|
||||||
from librespot.audio.format.SuperAudioFormat import SuperAudioFormat
|
from librespot.audio.format.SuperAudioFormat import SuperAudioFormat
|
||||||
@@ -16,7 +17,7 @@ class VorbisOnlyAudioQuality(AudioQualityPicker):
|
|||||||
self.preferred = preferred
|
self.preferred = preferred
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_vorbis_file(files: list[Metadata.AudioFile]):
|
def get_vorbis_file(files: typing.List[Metadata.AudioFile]):
|
||||||
for file in files:
|
for file in files:
|
||||||
if hasattr(file, "format") and SuperAudioFormat.get(
|
if hasattr(file, "format") and SuperAudioFormat.get(
|
||||||
file.format) == SuperAudioFormat.VORBIS:
|
file.format) == SuperAudioFormat.VORBIS:
|
||||||
@@ -24,8 +25,8 @@ class VorbisOnlyAudioQuality(AudioQualityPicker):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_file(self, files: list[Metadata.AudioFile]):
|
def get_file(self, files: typing.List[Metadata.AudioFile]):
|
||||||
matches: list[Metadata.AudioFile] = self.preferred.get_matches(files)
|
matches: typing.List[Metadata.AudioFile] = self.preferred.get_matches(files)
|
||||||
vorbis: Metadata.AudioFile = VorbisOnlyAudioQuality.get_vorbis_file(
|
vorbis: Metadata.AudioFile = VorbisOnlyAudioQuality.get_vorbis_file(
|
||||||
matches)
|
matches)
|
||||||
if vorbis is None:
|
if vorbis is None:
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class DeviceStateHandler:
|
|||||||
_LOGGER: logging = logging.getLogger(__name__)
|
_LOGGER: logging = logging.getLogger(__name__)
|
||||||
_session: Session = None
|
_session: Session = None
|
||||||
_deviceInfo: Connect.DeviceInfo = None
|
_deviceInfo: Connect.DeviceInfo = None
|
||||||
_listeners: list[DeviceStateHandler.Listener] = []
|
_listeners: typing.List[DeviceStateHandler.Listener] = []
|
||||||
_putState: Connect.PutStateRequest = None
|
_putState: Connect.PutStateRequest = None
|
||||||
_putStateWorker: concurrent.futures.ThreadPoolExecutor = (
|
_putStateWorker: concurrent.futures.ThreadPoolExecutor = (
|
||||||
concurrent.futures.ThreadPoolExecutor())
|
concurrent.futures.ThreadPoolExecutor())
|
||||||
|
|||||||
Reference in New Issue
Block a user