Preparing Release
This commit is contained in:
parent
3760793ed9
commit
593ee0caa6
10 changed files with 1046 additions and 87 deletions
|
@ -21,7 +21,7 @@ minio = "^7.1.12"
|
|||
mutagen = "^1.46.0"
|
||||
aiocmd = "^0.1.5"
|
||||
pyqrcode = "^1.2.1"
|
||||
|
||||
pillow = "^9.3.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
@ -38,6 +38,8 @@ module = [
|
|||
"minio",
|
||||
"aiocmd",
|
||||
"pyqrcode",
|
||||
"socketio"
|
||||
"socketio",
|
||||
"pillow",
|
||||
"PIL"
|
||||
]
|
||||
ignore_missing_imports = true
|
||||
|
|
|
@ -7,9 +7,12 @@ import logging
|
|||
from argparse import ArgumentParser
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Any
|
||||
import tempfile
|
||||
import datetime
|
||||
|
||||
import socketio
|
||||
import pyqrcode
|
||||
from PIL import Image
|
||||
|
||||
from .sources import Source, configure_sources
|
||||
from .entry import Entry
|
||||
|
@ -31,13 +34,21 @@ class State:
|
|||
room: str = ""
|
||||
server: str = ""
|
||||
secret: str = ""
|
||||
preview_duration: int = 3
|
||||
last_song: Optional[datetime.datetime] = None
|
||||
|
||||
def get_config(self) -> dict[str, Any]:
|
||||
return {
|
||||
"preview_duration": self.preview_duration,
|
||||
"last_song": self.last_song.timestamp() if self.last_song else None,
|
||||
}
|
||||
|
||||
|
||||
state: State = State()
|
||||
|
||||
|
||||
@sio.on("skip")
|
||||
async def handle_skip(_: dict[str, Any]) -> None:
|
||||
@sio.on("skip-current")
|
||||
async def handle_skip_current(_: dict[str, Any] = {}) -> None:
|
||||
logger.info("Skipping current")
|
||||
if state.current_source is not None:
|
||||
await state.current_source.skip_current(state.queue[0])
|
||||
|
@ -49,20 +60,21 @@ async def handle_state(data: dict[str, Any]) -> None:
|
|||
state.recent = [Entry(**entry) for entry in data["recent"]]
|
||||
|
||||
for entry in state.queue[:2]:
|
||||
logger.warning(f"Buffering: %s", entry.title)
|
||||
logger.info("Buffering: %s", entry.title)
|
||||
await sources[entry.source].buffer(entry)
|
||||
|
||||
|
||||
@sio.on("connect")
|
||||
async def handle_connect(_: dict[str, Any]) -> None:
|
||||
async def handle_connect(_: dict[str, Any] = {}) -> None:
|
||||
logging.info("Connected to server")
|
||||
await sio.emit(
|
||||
"register-client",
|
||||
{
|
||||
"secret": state.secret,
|
||||
"queue": [entry.to_dict() for entry in state.queue],
|
||||
"recent": [entry.to_dict() for entry in state.recent],
|
||||
"room": state.room,
|
||||
"secret": state.secret,
|
||||
"config": state.get_config(),
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -74,6 +86,26 @@ async def handle_buffer(data: dict[str, Any]) -> None:
|
|||
await sio.emit("meta-info", {"uuid": data["uuid"], "meta": meta_info})
|
||||
|
||||
|
||||
async def preview(entry: Entry) -> None:
|
||||
background = Image.new("RGB", (1280, 720))
|
||||
subtitle: str = f"""1
|
||||
00:00:00,00 --> 00:05:00,00
|
||||
{entry.artist} - {entry.title}
|
||||
{entry.performer}"""
|
||||
with tempfile.NamedTemporaryFile() as tmpfile:
|
||||
background.save(tmpfile, "png")
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
"mpv",
|
||||
tmpfile.name,
|
||||
"--image-display-duration=3",
|
||||
"--sub-pos=50",
|
||||
"--sub-file=-",
|
||||
"--fullscreen",
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
)
|
||||
await process.communicate(subtitle.encode())
|
||||
|
||||
|
||||
@sio.on("play")
|
||||
async def handle_play(data: dict[str, Any]) -> None:
|
||||
entry: Entry = Entry(**data)
|
||||
|
@ -82,10 +114,10 @@ async def handle_play(data: dict[str, Any]) -> None:
|
|||
)
|
||||
try:
|
||||
state.current_source = sources[entry.source]
|
||||
await preview(entry)
|
||||
await sources[entry.source].play(entry)
|
||||
except Exception:
|
||||
print_exc()
|
||||
logging.info("Finished, waiting for next")
|
||||
await sio.emit("pop-then-get-next")
|
||||
|
||||
|
||||
|
@ -137,8 +169,17 @@ async def aiomain() -> None:
|
|||
args = parser.parse_args()
|
||||
|
||||
with open(args.config_file, encoding="utf8") as file:
|
||||
source_config = load(file)
|
||||
sources.update(configure_sources(source_config))
|
||||
config = load(file)
|
||||
sources.update(configure_sources(config["sources"]))
|
||||
|
||||
if "config" in config:
|
||||
if "last_song" in config["config"]:
|
||||
state.last_song = datetime.datetime.fromisoformat(
|
||||
config["config"]["last_song"]
|
||||
)
|
||||
if "preview_duration" in config["config"]:
|
||||
state.preview_duration = config["config"]["preview_duration"]
|
||||
|
||||
if args.room:
|
||||
state.room = args.room
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from __future__ import annotations
|
||||
from dataclasses import dataclass, field
|
||||
from uuid import uuid4, UUID
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
from datetime import datetime
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .sources import Source
|
||||
|
@ -17,7 +18,9 @@ class Entry:
|
|||
album: str
|
||||
performer: str
|
||||
failed: bool = False
|
||||
skip: bool = False
|
||||
uuid: UUID = field(default_factory=uuid4)
|
||||
started_at: Optional[float] = None
|
||||
|
||||
@staticmethod
|
||||
async def from_source(performer: str, ident: str, source: Source) -> Entry:
|
||||
|
@ -33,6 +36,8 @@ class Entry:
|
|||
"artist": self.artist,
|
||||
"album": self.album,
|
||||
"performer": self.performer,
|
||||
"skip": self.skip,
|
||||
"started_at": self.started_at,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
|
213
syng/server.py
213
syng/server.py
|
@ -1,13 +1,14 @@
|
|||
from __future__ import annotations
|
||||
from collections import deque
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, Optional
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
import string
|
||||
import random
|
||||
import logging
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from uuid import UUID
|
||||
import datetime
|
||||
|
||||
from aiohttp import web
|
||||
import socketio
|
||||
|
@ -21,13 +22,20 @@ sio.attach(app)
|
|||
|
||||
|
||||
async def root_handler(request: Any) -> Any:
|
||||
if request.path.endswith("/favicon.ico"):
|
||||
return web.FileResponse("syng/static/favicon.ico")
|
||||
return web.FileResponse("syng/static/index.html")
|
||||
|
||||
|
||||
async def favico_handler(_: Any) -> Any:
|
||||
return web.FileResponse("syng/static/favicon.ico")
|
||||
|
||||
|
||||
app.add_routes([web.static("/assets/", "syng/static/assets/")])
|
||||
app.router.add_route("*", "/", root_handler)
|
||||
app.router.add_route("*", "/{room}", root_handler)
|
||||
app.router.add_route("*", "/{room}/", root_handler)
|
||||
app.router.add_route("*", "/favicon.ico", favico_handler)
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -67,26 +75,51 @@ class Queue:
|
|||
if locator(item):
|
||||
updater(item)
|
||||
|
||||
def find_by_uuid(self, uuid: UUID | str) -> Optional[Entry]:
|
||||
for item in self._queue:
|
||||
if item.uuid == uuid or str(item.uuid) == uuid:
|
||||
return item
|
||||
return None
|
||||
|
||||
async def remove(self, entry: Entry) -> None:
|
||||
async with self.readlock:
|
||||
await self.num_of_entries_sem.acquire()
|
||||
self._queue.remove(entry)
|
||||
|
||||
async def moveUp(self, uuid: str) -> None:
|
||||
async with self.readlock:
|
||||
uuid_idx = 0
|
||||
for idx, item in enumerate(self._queue):
|
||||
if item.uuid == uuid or str(item.uuid) == uuid:
|
||||
uuid_idx = idx
|
||||
|
||||
if uuid_idx > 1:
|
||||
tmp = self._queue[uuid_idx]
|
||||
self._queue[uuid_idx] = self._queue[uuid_idx - 1]
|
||||
self._queue[uuid_idx - 1] = tmp
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
sources: dict[str, Source]
|
||||
sources_prio: list[str]
|
||||
preview_duration: int
|
||||
last_song: Optional[float]
|
||||
|
||||
|
||||
@dataclass
|
||||
class State:
|
||||
secret: str | None
|
||||
sources: dict[str, Source]
|
||||
sources_prio: list[str]
|
||||
queue: Queue
|
||||
recent: list[Entry]
|
||||
sid: str
|
||||
config: Config
|
||||
|
||||
|
||||
clients: dict[str, State] = {}
|
||||
|
||||
|
||||
@sio.on("get-state")
|
||||
async def handle_state(sid: str, data: dict[str, Any] = {}) -> None:
|
||||
async with sio.session(sid) as session:
|
||||
room = session["room"]
|
||||
state = clients[room]
|
||||
|
||||
async def send_state(state: State, sid: str) -> None:
|
||||
await sio.emit(
|
||||
"state",
|
||||
{
|
||||
|
@ -97,23 +130,50 @@ async def handle_state(sid: str, data: dict[str, Any] = {}) -> None:
|
|||
)
|
||||
|
||||
|
||||
@sio.on("get-state")
|
||||
async def handle_state(sid: str, data: dict[str, Any] = {}) -> None:
|
||||
async with sio.session(sid) as session:
|
||||
room = session["room"]
|
||||
state = clients[room]
|
||||
|
||||
await send_state(state, sid)
|
||||
|
||||
|
||||
@sio.on("append")
|
||||
async def handle_append(sid: str, data: dict[str, Any]) -> None:
|
||||
async with sio.session(sid) as session:
|
||||
room = session["room"]
|
||||
state = clients[room]
|
||||
|
||||
source_obj = state.sources[data["source"]]
|
||||
source_obj = state.config.sources[data["source"]]
|
||||
entry = await Entry.from_source(data["performer"], data["id"], source_obj)
|
||||
|
||||
first_song = state.queue._queue[0] if len(state.queue._queue) > 0 else None
|
||||
if first_song is None or first_song.started_at is None:
|
||||
start_time = datetime.datetime.now().timestamp()
|
||||
else:
|
||||
start_time = first_song.started_at
|
||||
|
||||
for item in state.queue._queue:
|
||||
start_time += item.duration + state.config.preview_duration + 1
|
||||
|
||||
print(state.config.last_song)
|
||||
print(start_time)
|
||||
|
||||
if state.config.last_song:
|
||||
if state.config.last_song < start_time:
|
||||
end_time = datetime.datetime.fromtimestamp(state.config.last_song)
|
||||
await sio.emit(
|
||||
"msg",
|
||||
{
|
||||
"msg": f"The song queue ends at {end_time.hour:02d}:{end_time.minute:02d}."
|
||||
},
|
||||
room=sid,
|
||||
)
|
||||
return
|
||||
|
||||
state.queue.append(entry)
|
||||
await sio.emit(
|
||||
"state",
|
||||
{
|
||||
"queue": state.queue.to_dict(),
|
||||
"recent": [entry.to_dict() for entry in state.recent],
|
||||
},
|
||||
room=room,
|
||||
)
|
||||
await send_state(state, room)
|
||||
|
||||
await sio.emit(
|
||||
"buffer",
|
||||
|
@ -133,14 +193,7 @@ async def handle_meta_info(sid: str, data: dict[str, Any]) -> None:
|
|||
lambda item: item.update(**data["meta"]),
|
||||
)
|
||||
|
||||
await sio.emit(
|
||||
"state",
|
||||
{
|
||||
"queue": state.queue.to_dict(),
|
||||
"recent": [entry.to_dict() for entry in state.recent],
|
||||
},
|
||||
room=room,
|
||||
)
|
||||
await send_state(state, room)
|
||||
|
||||
|
||||
@sio.on("get-first")
|
||||
|
@ -150,6 +203,7 @@ async def handle_get_first(sid: str, data: dict[str, Any] = {}) -> None:
|
|||
state = clients[room]
|
||||
|
||||
current = await state.queue.peek()
|
||||
current.started_at = datetime.datetime.now().timestamp()
|
||||
|
||||
await sio.emit("play", current.to_dict(), room=sid)
|
||||
|
||||
|
@ -162,15 +216,11 @@ async def handle_pop_then_get_next(sid: str, data: dict[str, Any] = {}) -> None:
|
|||
|
||||
old_entry = await state.queue.popleft()
|
||||
state.recent.append(old_entry)
|
||||
await sio.emit(
|
||||
"state",
|
||||
{
|
||||
"queue": state.queue.to_dict(),
|
||||
"recent": [entry.to_dict() for entry in state.recent],
|
||||
},
|
||||
room=room,
|
||||
)
|
||||
|
||||
await send_state(state, room)
|
||||
current = await state.queue.peek()
|
||||
current.started_at = datetime.datetime.now().timestamp()
|
||||
await send_state(state, room)
|
||||
|
||||
await sio.emit("play", current.to_dict(), room=sid)
|
||||
|
||||
|
@ -188,15 +238,22 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None:
|
|||
async with sio.session(sid) as session:
|
||||
session["room"] = room
|
||||
|
||||
print(data["config"])
|
||||
if room in clients:
|
||||
old_state: State = clients[room]
|
||||
if data["secret"] == old_state.secret:
|
||||
logger.info("Got new client connection for %s", room)
|
||||
old_state.sid = sid
|
||||
old_state.config = Config(
|
||||
sources=old_state.config.sources,
|
||||
sources_prio=old_state.config.sources_prio,
|
||||
**data["config"],
|
||||
)
|
||||
sio.enter_room(sid, room)
|
||||
await sio.emit(
|
||||
"client-registered", {"success": True, "room": room}, room=sid
|
||||
)
|
||||
await send_state(clients[room], sid)
|
||||
else:
|
||||
logger.warning("Got wrong secret for %s", room)
|
||||
await sio.emit(
|
||||
|
@ -206,19 +263,17 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None:
|
|||
logger.info("Registerd new client %s", room)
|
||||
initial_entries = [Entry(**entry) for entry in data["queue"]]
|
||||
initial_recent = [Entry(**entry) for entry in data["recent"]]
|
||||
|
||||
clients[room] = State(
|
||||
data["secret"], {}, [], Queue(initial_entries), initial_recent, sid
|
||||
secret=data["secret"],
|
||||
queue=Queue(initial_entries),
|
||||
recent=initial_recent,
|
||||
sid=sid,
|
||||
config=Config(sources={}, sources_prio=[], **data["config"]),
|
||||
)
|
||||
sio.enter_room(sid, room)
|
||||
await sio.emit(
|
||||
"state",
|
||||
{
|
||||
"queue": clients[room].queue.to_dict(),
|
||||
"recent": [entry.to_dict() for entry in clients[room].recent],
|
||||
},
|
||||
room=sid,
|
||||
)
|
||||
await sio.emit("client-registered", {"success": True, "room": room}, room=sid)
|
||||
await send_state(clients[room], sid)
|
||||
|
||||
|
||||
@sio.on("sources")
|
||||
|
@ -232,13 +287,13 @@ async def handle_sources(sid: str, data: dict[str, Any]) -> None:
|
|||
room = session["room"]
|
||||
state = clients[room]
|
||||
|
||||
unused_sources = state.sources.keys() - data["sources"]
|
||||
new_sources = data["sources"] - state.sources.keys()
|
||||
unused_sources = state.config.sources.keys() - data["sources"]
|
||||
new_sources = data["sources"] - state.config.sources.keys()
|
||||
|
||||
for source in unused_sources:
|
||||
del state.sources[source]
|
||||
del state.config.sources[source]
|
||||
|
||||
state.sources_prio = data["sources"]
|
||||
state.config.sources_prio = data["sources"]
|
||||
|
||||
for name in new_sources:
|
||||
await sio.emit("request-config", {"source": name}, room=sid)
|
||||
|
@ -250,13 +305,12 @@ async def handle_config_chung(sid: str, data: dict[str, Any]) -> None:
|
|||
room = session["room"]
|
||||
state = clients[room]
|
||||
|
||||
if not data["source"] in state.sources:
|
||||
logger.info("Added source %s", data["source"])
|
||||
state.sources[data["source"]] = available_sources[data["source"]](
|
||||
if not data["source"] in state.config.sources:
|
||||
state.config.sources[data["source"]] = available_sources[data["source"]](
|
||||
data["config"]
|
||||
)
|
||||
else:
|
||||
state.sources[data["source"]].add_to_config(data["config"])
|
||||
state.config.sources[data["source"]].add_to_config(data["config"])
|
||||
|
||||
|
||||
@sio.on("config")
|
||||
|
@ -265,8 +319,9 @@ async def handle_config(sid: str, data: dict[str, Any]) -> None:
|
|||
room = session["room"]
|
||||
state = clients[room]
|
||||
|
||||
state.sources[data["source"]] = available_sources[data["source"]](data["config"])
|
||||
logger.info("Added source %s", data["source"])
|
||||
state.config.sources[data["source"]] = available_sources[data["source"]](
|
||||
data["config"]
|
||||
)
|
||||
|
||||
|
||||
@sio.on("register-web")
|
||||
|
@ -276,14 +331,7 @@ async def handle_register_web(sid: str, data: dict[str, Any]) -> bool:
|
|||
session["room"] = data["room"]
|
||||
sio.enter_room(sid, session["room"])
|
||||
state = clients[session["room"]]
|
||||
await sio.emit(
|
||||
"state",
|
||||
{
|
||||
"queue": state.queue.to_dict(),
|
||||
"recent": [entry.to_dict() for entry in state.recent],
|
||||
},
|
||||
room=sid,
|
||||
)
|
||||
await send_state(state, sid)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -310,18 +358,47 @@ async def handle_get_config(sid: str, data: dict[str, Any]) -> None:
|
|||
if is_admin:
|
||||
await sio.emit(
|
||||
"config",
|
||||
{name: source.get_config() for name, source in state.sources.items()},
|
||||
{
|
||||
name: source.get_config()
|
||||
for name, source in state.config.sources.items()
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@sio.on("skip")
|
||||
async def handle_skip(sid: str, data: dict[str, Any] = {}) -> None:
|
||||
@sio.on("skip-current")
|
||||
async def handle_skip_current(sid: str, data: dict[str, Any] = {}) -> None:
|
||||
async with sio.session(sid) as session:
|
||||
room = session["room"]
|
||||
is_admin = session["admin"]
|
||||
|
||||
if is_admin:
|
||||
await sio.emit("skip", room=clients[room].sid)
|
||||
await sio.emit("skip-current", room=clients[room].sid)
|
||||
|
||||
|
||||
@sio.on("moveUp")
|
||||
async def handle_moveUp(sid: str, data: dict[str, Any]) -> None:
|
||||
async with sio.session(sid) as session:
|
||||
room = session["room"]
|
||||
is_admin = session["admin"]
|
||||
state = clients[room]
|
||||
if is_admin:
|
||||
await state.queue.moveUp(data["uuid"])
|
||||
await send_state(state, room)
|
||||
|
||||
|
||||
@sio.on("skip")
|
||||
async def handle_skip(sid: str, data: dict[str, Any]) -> None:
|
||||
async with sio.session(sid) as session:
|
||||
room = session["room"]
|
||||
is_admin = session["admin"]
|
||||
state = clients[room]
|
||||
|
||||
if is_admin:
|
||||
entry = state.queue.find_by_uuid(data["uuid"])
|
||||
if entry is not None:
|
||||
logger.info("Skipping %s", entry)
|
||||
await state.queue.remove(entry)
|
||||
await send_state(state, room)
|
||||
|
||||
|
||||
@sio.on("disconnect")
|
||||
|
@ -338,10 +415,10 @@ async def handle_search(sid: str, data: dict[str, str]) -> None:
|
|||
|
||||
query = data["query"]
|
||||
result_futures = []
|
||||
for source in state.sources_prio:
|
||||
for source in state.config.sources_prio:
|
||||
loop = asyncio.get_running_loop()
|
||||
search_future = loop.create_future()
|
||||
loop.create_task(state.sources[source].search(search_future, query))
|
||||
loop.create_task(state.config.sources[source].search(search_future, query))
|
||||
result_futures.append(search_future)
|
||||
|
||||
results = [
|
||||
|
|
|
@ -5,10 +5,14 @@ from typing import Tuple, Optional, Type, Any
|
|||
import os.path
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
import logging
|
||||
from traceback import print_exc
|
||||
|
||||
from ..entry import Entry
|
||||
from ..result import Result
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DLFilesEntry:
|
||||
|
@ -17,7 +21,8 @@ class DLFilesEntry:
|
|||
audio: Optional[str] = None
|
||||
buffering: bool = False
|
||||
complete: bool = False
|
||||
skip: bool = False
|
||||
failed: bool = False
|
||||
buffer_task: Optional[asyncio.Task[Tuple[str, Optional[str]]]] = None
|
||||
|
||||
|
||||
class Source:
|
||||
|
@ -54,28 +59,54 @@ class Source:
|
|||
async def buffer(self, entry: Entry) -> None:
|
||||
async with self.masterlock:
|
||||
if self.downloaded_files[entry.id].buffering:
|
||||
print(f"already buffering {entry.title}")
|
||||
return
|
||||
self.downloaded_files[entry.id].buffering = True
|
||||
|
||||
video, audio = await self.doBuffer(entry)
|
||||
self.downloaded_files[entry.id].video = video
|
||||
self.downloaded_files[entry.id].audio = audio
|
||||
self.downloaded_files[entry.id].complete = True
|
||||
try:
|
||||
buffer_task = asyncio.create_task(self.doBuffer(entry))
|
||||
self.downloaded_files[entry.id].buffer_task = buffer_task
|
||||
video, audio = await buffer_task
|
||||
|
||||
self.downloaded_files[entry.id].video = video
|
||||
self.downloaded_files[entry.id].audio = audio
|
||||
self.downloaded_files[entry.id].complete = True
|
||||
except Exception:
|
||||
print_exc()
|
||||
logger.error("Buffering failed for %s", entry)
|
||||
self.downloaded_files[entry.id].failed = True
|
||||
|
||||
self.downloaded_files[entry.id].ready.set()
|
||||
print(f"Buffering done for {entry.title}")
|
||||
|
||||
async def play(self, entry: Entry) -> None:
|
||||
await self.ensure_playable(entry)
|
||||
|
||||
if self.downloaded_files[entry.id].failed:
|
||||
del self.downloaded_files[entry.id]
|
||||
return
|
||||
|
||||
if entry.skip:
|
||||
del self.downloaded_files[entry.id]
|
||||
return
|
||||
|
||||
self.player = await self.play_mpv(
|
||||
self.downloaded_files[entry.id].video,
|
||||
self.downloaded_files[entry.id].audio,
|
||||
*self.extra_mpv_arguments,
|
||||
)
|
||||
await self.player.wait()
|
||||
self.player = None
|
||||
|
||||
async def skip_current(self, entry: Entry) -> None:
|
||||
if self.player is not None:
|
||||
entry.skip = True
|
||||
self.downloaded_files[entry.id].buffering = False
|
||||
buffer_task = self.downloaded_files[entry.id].buffer_task
|
||||
if buffer_task is not None:
|
||||
buffer_task.cancel()
|
||||
self.downloaded_files[entry.id].ready.set()
|
||||
|
||||
if (
|
||||
self.player is not None
|
||||
): # A race condition can occur here. In that case, just press the skip button again
|
||||
self.player.kill()
|
||||
|
||||
async def ensure_playable(self, entry: Entry) -> None:
|
||||
|
|
1
syng/static/assets/index.1ff4ae2d.css
Normal file
1
syng/static/assets/index.1ff4ae2d.css
Normal file
File diff suppressed because one or more lines are too long
787
syng/static/assets/index.c3b37c18.js
Normal file
787
syng/static/assets/index.c3b37c18.js
Normal file
File diff suppressed because one or more lines are too long
BIN
syng/static/assets/syng.84d20818.png
Normal file
BIN
syng/static/assets/syng.84d20818.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
BIN
syng/static/favicon.ico
Normal file
BIN
syng/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
15
syng/static/index.html
Normal file
15
syng/static/index.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Syng Rocks!</title>
|
||||
<script type="module" crossorigin src="/assets/index.c3b37c18.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index.1ff4ae2d.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue