buffering in parallel, youtube now buffered -> streaming is fallback, currently broken for s3

This commit is contained in:
Christoph Stahl 2022-11-25 15:53:46 +01:00
parent fcb10f7d10
commit b234b7ea81
5 changed files with 69 additions and 18 deletions

View file

@ -25,7 +25,7 @@ state = {"current": None, "queue": [], "recent": [], "room": None, "server": "",
@sio.on("skip") @sio.on("skip")
async def handle_skip(): async def handle_skip():
logger.info("Skipping current") logger.info("Skipping current")
await state["current"].skip_current() await state["current"].skip_current(state["queue"][0])
@sio.on("state") @sio.on("state")
@ -55,6 +55,11 @@ async def handle_buffer(data):
await sio.emit("meta-info", {"uuid": data["uuid"], "meta": meta_info}) await sio.emit("meta-info", {"uuid": data["uuid"], "meta": meta_info})
async def buffer_and_report(entry):
meta_info = await sources[entry.source].buffer(entry)
await sio.emit("meta-info", {"uuid": entry.uuid, "meta": meta_info})
@sio.on("play") @sio.on("play")
async def handle_play(data): async def handle_play(data):
entry = Entry(**data) entry = Entry(**data)
@ -62,9 +67,8 @@ async def handle_play(data):
f"Playing: {entry.artist} - {entry.title} [{entry.album}] ({entry.source}) for {entry.performer}" f"Playing: {entry.artist} - {entry.title} [{entry.album}] ({entry.source}) for {entry.performer}"
) )
try: try:
meta_info = await sources[entry.source].buffer(entry)
await sio.emit("meta-info", {"uuid": data["uuid"], "meta": meta_info})
state["current"] = sources[entry.source] state["current"] = sources[entry.source]
asyncio.create_task(buffer_and_report(entry))
await sources[entry.source].play(entry) await sources[entry.source].play(entry)
except Exception: except Exception:
print_exc() print_exc()

View file

@ -3,9 +3,9 @@ import asyncio
async def play_mpv( async def play_mpv(
video: str, audio: str | None, options video: str, audio: str | None, options: list[str] = list()
) -> asyncio.subprocess.Process: ) -> asyncio.subprocess.Process:
args = [*options, video] + ([f"--audio-file={audio}"] if audio else []) args = ["--fullscreen", *options, video] + ([f"--audio-file={audio}"] if audio else [])
mpv_process = asyncio.create_subprocess_exec("mpv", *args) mpv_process = asyncio.create_subprocess_exec("mpv", *args)
return await mpv_process return await mpv_process

View file

@ -57,12 +57,12 @@ class S3Source(Source):
mp3_file = self.downloaded_files[entry.uuid]["mp3"] mp3_file = self.downloaded_files[entry.uuid]["mp3"]
self.player = await play_mpv( self.player = await play_mpv(
cdg_file, mp3_file, ["--scale=oversample", "--fullscreen"] cdg_file, mp3_file, ["--scale=oversample"]
) )
await self.player.wait() await self.player.wait()
async def skip_current(self) -> None: async def skip_current(self, entry) -> None:
await self.player.kill() await self.player.kill()
@async_in_thread @async_in_thread

View file

@ -29,7 +29,7 @@ class Source:
async def play(self, entry: Entry) -> None: async def play(self, entry: Entry) -> None:
raise NotImplementedError raise NotImplementedError
async def skip_current(self) -> None: async def skip_current(self, entry: Entry) -> None:
pass pass
async def init_server(self) -> None: async def init_server(self) -> None:

View file

@ -1,6 +1,7 @@
import asyncio import asyncio
import shlex import shlex
from functools import partial from functools import partial
from threading import Event, Lock
from pytube import YouTube, Search, Channel, innertube from pytube import YouTube, Search, Channel, innertube
@ -15,12 +16,23 @@ class YoutubeSource(Source):
super().__init__() super().__init__()
self.innertube_client = innertube.InnerTube(client="WEB") self.innertube_client = innertube.InnerTube(client="WEB")
self.channels = config["channels"] if "channels" in config else [] self.channels = config["channels"] if "channels" in config else []
self.tmp_dir = config["tmp_dir"] if "tmp_dir" in config else "/tmp/syng"
self.player: None | asyncio.subprocess.Process = None self.player: None | asyncio.subprocess.Process = None
self.downloaded_files = {}
self.masterlock = Lock()
async def get_config(self): async def get_config(self):
return {"channels": self.channels} return {"channels": self.channels}
async def play(self, entry: Entry) -> None: async def play(self, entry: Entry) -> None:
if entry.uuid in self.downloaded_files and "video" in self.downloaded_files[entry.uuid]:
print("playing locally")
video_file = self.downloaded_files[entry.uuid]["video"]
audio_file = self.downloaded_files[entry.uuid]["audio"]
self.player = await play_mpv(video_file, audio_file)
else:
print("streaming")
self.player = await play_mpv( self.player = await play_mpv(
entry.id, entry.id,
None, None,
@ -30,9 +42,10 @@ class YoutubeSource(Source):
"--fullscreen", "--fullscreen",
], ],
) )
await self.player.wait() await self.player.wait()
async def skip_current(self) -> None: async def skip_current(self, entry) -> None:
await self.player.kill() await self.player.kill()
@async_in_thread @async_in_thread
@ -121,5 +134,39 @@ class YoutubeSource(Source):
pass pass
return list_of_videos return list_of_videos
@async_in_thread
def buffer(self, entry: Entry) -> dict:
print(f"Buffering {entry}")
with self.masterlock:
if entry.uuid in self.downloaded_files:
print(f"Already buffering {entry}")
return {}
self.downloaded_files[entry.uuid] = {}
yt = YouTube(entry.id)
streams = yt.streams
video_streams = streams.filter(
type="video",
custom_filter_functions=[lambda s: int(s.resolution[:-1]) <= 1080]
)
audio_streams = streams.filter(only_audio=True)
best_720_stream = sorted(video_streams, key=lambda s: int(s.resolution[:-1]) + (1 if s.is_progressive else 0))[-1]
best_audio_stream = sorted(audio_streams, key=lambda s: int(s.abr[:-4]))[-1]
print(best_720_stream)
print(best_audio_stream)
if not best_720_stream.is_progressive:
self.downloaded_files[entry.uuid]["audio"] = best_audio_stream.download(output_path=self.tmp_dir, filename_prefix=f"{entry.uuid}-audio")
else:
self.downloaded_files[entry.uuid]["audio"] = None
self.downloaded_files[entry.uuid]["video"] = best_720_stream.download(output_path=self.tmp_dir, filename_prefix=entry.uuid)
return {}
available_sources["youtube"] = YoutubeSource available_sources["youtube"] = YoutubeSource