librespot-python/librespot/audio/storage/ChannelManager.py

152 lines
5.4 KiB
Python
Raw Normal View History

2021-02-24 00:46:59 +01:00
from __future__ import annotations
2021-05-22 03:26:35 +02:00
import concurrent.futures
import logging
2021-02-24 00:46:59 +01:00
import queue
2021-05-22 03:26:35 +02:00
import threading
import typing
2021-02-24 00:46:59 +01:00
from librespot.audio.storage import AudioFile
from librespot.common import Utils
2021-05-22 03:26:41 +02:00
from librespot.core import PacketsReceiver
from librespot.core import Session
2021-02-24 00:46:59 +01:00
from librespot.crypto import Packet
2021-05-22 03:26:41 +02:00
from librespot.standard import BytesInputStream
from librespot.standard import BytesOutputStream
from librespot.standard import Closeable
from librespot.standard import Runnable
2021-02-24 00:46:59 +01:00
class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
CHUNK_SIZE: int = 128 * 1024
_LOGGER: logging = logging.getLogger(__name__)
2021-04-24 12:17:02 +02:00
_channels: typing.Dict[int, Channel] = {}
2021-02-24 00:46:59 +01:00
_seqHolder: int = 0
_seqHolderLock: threading.Condition = threading.Condition()
2021-05-22 03:26:33 +02:00
_executorService: concurrent.futures.ThreadPoolExecutor = (
2021-05-22 03:27:30 +02:00
concurrent.futures.ThreadPoolExecutor())
2021-02-24 00:46:59 +01:00
_session: Session = None
def __init__(self, session: Session):
self._session = session
def request_chunk(self, file_id: bytes, index: int, file: AudioFile):
start = int(index * self.CHUNK_SIZE / 4)
end = int((index + 1) * self.CHUNK_SIZE / 4)
channel = ChannelManager.Channel(self, file, index)
self._channels[channel.chunkId] = channel
out = BytesOutputStream()
out.write_short(channel.chunkId)
out.write_int(0x00000000)
out.write_int(0x00000000)
2021-05-22 03:26:33 +02:00
out.write_int(0x00004E20)
out.write_int(0x00030D40)
2021-02-24 00:46:59 +01:00
out.write(file_id)
out.write_int(start)
out.write_int(end)
self._session.send(Packet.Type.stream_chunk, out.buffer)
def dispatch(self, packet: Packet) -> None:
payload = BytesInputStream(packet.payload)
if packet.is_cmd(Packet.Type.stream_chunk_res):
chunk_id = payload.read_short()
channel = self._channels.get(chunk_id)
if channel is None:
self._LOGGER.warning(
"Couldn't find channel, id: {}, received: {}".format(
2021-05-22 03:27:30 +02:00
chunk_id, len(packet.payload)))
2021-02-24 00:46:59 +01:00
return
channel._add_to_queue(payload)
elif packet.is_cmd(Packet.Type.channel_error):
chunk_id = payload.read_short()
channel = self._channels.get(chunk_id)
if channel is None:
self._LOGGER.warning(
"Dropping channel error, id: {}, code: {}".format(
2021-05-22 03:27:30 +02:00
chunk_id, payload.read_short()))
2021-02-24 00:46:59 +01:00
return
channel.stream_error(payload.read_short())
else:
self._LOGGER.warning(
"Couldn't handle packet, cmd: {}, payload: {}".format(
2021-05-26 10:52:44 +02:00
packet.cmd, Utils.bytes_to_hex(packet.payload)))
2021-02-24 00:46:59 +01:00
def close(self) -> None:
self._executorService.shutdown()
class Channel:
_channelManager: ChannelManager
chunkId: int
_q: queue.Queue = queue.Queue()
_file: AudioFile
_chunkIndex: int
_buffer: BytesOutputStream = BytesOutputStream()
_header: bool = True
2021-05-22 03:27:30 +02:00
def __init__(self, channel_manager: ChannelManager, file: AudioFile,
chunk_index: int):
2021-02-24 00:46:59 +01:00
self._channelManager = channel_manager
self._file = file
self._chunkIndex = chunk_index
with self._channelManager._seqHolderLock:
self.chunkId = self._channelManager._seqHolder
self._channelManager._seqHolder += 1
self._channelManager._executorService.submit(
2021-05-22 03:27:30 +02:00
lambda: ChannelManager.Channel.Handler(self))
2021-02-24 00:46:59 +01:00
def _handle(self, payload: BytesInputStream) -> bool:
if len(payload.buffer) == 0:
if not self._header:
2021-05-22 03:27:30 +02:00
self._file.write_chunk(bytearray(payload.buffer),
self._chunkIndex, False)
2021-02-24 00:46:59 +01:00
return True
2021-05-22 03:27:30 +02:00
self._channelManager._LOGGER.debug(
"Received empty chunk, skipping.")
2021-02-24 00:46:59 +01:00
return False
if self._header:
length: int
while len(payload.buffer) > 0:
length = payload.read_short()
if not length > 0:
break
header_id = payload.read_byte()
header_data = payload.read(length - 1)
2021-05-22 03:27:30 +02:00
self._file.write_header(int.from_bytes(header_id, "big"),
bytearray(header_data), False)
2021-02-24 00:46:59 +01:00
self._header = False
else:
self._buffer.write(payload.read(len(payload.buffer)))
return False
def _add_to_queue(self, payload):
self._q.put(payload)
def stream_error(self, code: int) -> None:
self._file.stream_error(self._chunkIndex, code)
class Handler(Runnable):
_channel: ChannelManager.Channel = None
def __init__(self, channel: ChannelManager.Channel):
self._channel = channel
def run(self) -> None:
self._channel._channelManager._LOGGER.debug(
2021-05-22 03:27:30 +02:00
"ChannelManager.Handler is starting")
2021-02-24 00:46:59 +01:00
with self._channel._q.all_tasks_done:
2021-05-22 03:27:30 +02:00
self._channel._channelManager._channels.pop(
self._channel.chunkId)
2021-02-24 00:46:59 +01:00
self._channel._channelManager._LOGGER.debug(
2021-05-22 03:27:30 +02:00
"ChannelManager.Handler is shutting down")