Added a waitingroom

This commit is contained in:
Christoph Stahl 2023-04-03 09:03:01 +02:00
parent 9b9fcd6b02
commit 2f860ac3c4
4 changed files with 179 additions and 48 deletions

View file

@ -75,6 +75,8 @@ class State:
:type current_source: Optional[Source]
:param queue: A copy of the current playlist on the server.
:type queue: list[Entry]
:param waiting_room: A copy of the waiting room on the server.
:type waiting_room: list[Entry]
:param recent: A copy of all played songs this session.
:type recent: list[Entry]
:param room: The room on the server this playback client is connected to.
@ -97,6 +99,7 @@ class State:
current_source: Optional[Source] = None
queue: list[Entry] = field(default_factory=list)
waiting_room: list[Entry] = field(default_factory=list)
recent: list[Entry] = field(default_factory=list)
room: str = ""
server: str = ""
@ -164,6 +167,7 @@ async def handle_state(data: dict[str, Any]) -> None:
:rtype: None
"""
state.queue = [Entry(**entry) for entry in data["queue"]]
state.waiting_room = [Entry(**entry) for entry in data["waiting_room"]]
state.recent = [Entry(**entry) for entry in data["recent"]]
for entry in state.queue[:2]:
@ -193,6 +197,7 @@ async def handle_connect() -> None:
logging.info("Connected to server")
data = {
"queue": state.queue,
"waiting_room": state.waiting_room,
"recent": state.recent,
"room": state.room,
"secret": state.secret,
@ -200,6 +205,7 @@ async def handle_connect() -> None:
}
if state.key:
data["registration-key"] = state.key
print(data)
await sio.emit("register-client", data)

View file

@ -1,8 +1,8 @@
"""A async queue with synchronization."""
import asyncio
from collections import deque
from collections.abc import Callable, Iterable
from typing import Any
from typing import Callable
from typing import Optional
from uuid import UUID
@ -95,6 +95,8 @@ class Queue:
"""
Update entries in the queue, identified by their uuid.
If an entry with that uuid is not in the queue, nothing happens.
:param uuid: The uuid of the entry to update
:type uuid: UUID | str
:param updater: A function, that updates the entry
@ -119,6 +121,15 @@ class Queue:
return item
return None
def find_by_uid(self, uid: str) -> Iterable[Entry]:
"""
Find all entries for a given user id
"""
for item in self._queue:
if item.uid == uid:
yield item
def fold(self, func: Callable[[Entry, Any], Any], start_value: Any) -> Any:
"""Call ``func`` on each entry and accumulate the result."""
for item in self._queue:

View file

@ -104,6 +104,9 @@ class State:
are appended to this, and if a playback client requests a song, it is
taken from the top.
:type queue: Queue
:param waiting_room: Contains the Entries, that are hold back, until a
specific song is finished.
:type waiting_room: list[Entry]
:param recent: A list of already played songs in order.
:type recent: list[Entry]
:param sid: The socket.io session id of the (unique) playback client. Once
@ -116,6 +119,7 @@ class State:
secret: str
queue: Queue
waiting_room: list[Entry]
recent: list[Entry]
sid: str
config: Config
@ -145,7 +149,11 @@ async def send_state(state: State, sid: str) -> None:
"""
await sio.emit(
"state",
{"queue": state.queue, "recent": state.recent},
{
"queue": state.queue,
"recent": state.recent,
"waiting_room": state.waiting_room,
},
room=sid,
)
@ -170,6 +178,85 @@ async def handle_state(sid: str) -> None:
await send_state(state, sid)
@sio.on("waiting-room-append")
async def handle_waiting_room_append(sid: str, data: dict[str, Any]) -> None:
async with sio.session(sid) as session:
room = session["room"]
state = clients[room]
print(data)
source_obj = state.config.sources[data["source"]]
entry = await source_obj.get_entry(data["performer"], data["ident"])
if entry is None:
await sio.emit(
"msg",
{"msg": f"Unable to add to the waiting room: {data['ident']}"},
)
return
if (
"uid" not in data
or len(list(state.queue.find_by_uid(data["uid"]))) == 0
):
await append_to_queue(room, entry, sid)
return
entry.uid = data["uid"]
state.waiting_room.append(entry)
print(state.waiting_room)
await send_state(state, room)
await sio.emit(
"get-meta-info",
entry,
room=clients[room].sid,
)
# Und jetzt iwie hinzufügen, oder direkt queuen :/
async def append_to_queue(room, entry, report_to=None):
state = clients[room]
first_song = state.queue.try_peek()
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
start_time = state.queue.fold(
lambda item, time: time
+ item.duration
+ state.config.preview_duration
+ 1,
start_time,
)
if state.config.last_song:
if state.config.last_song < start_time:
end_time = datetime.datetime.fromtimestamp(state.config.last_song)
if report_to is not None:
await sio.emit(
"msg",
{
"msg": f"The song queue ends at {end_time.hour:02d}:"
f"{end_time.minute:02d}."
},
room=report_to,
)
return
state.queue.append(entry)
await send_state(state, room)
await sio.emit(
"get-meta-info",
entry,
room=clients[room].sid,
)
@sio.on("append")
async def handle_append(sid: str, data: dict[str, Any]) -> None:
"""
@ -209,46 +296,12 @@ async def handle_append(sid: str, data: dict[str, Any]) -> None:
source_obj = state.config.sources[data["source"]]
entry = await source_obj.get_entry(data["performer"], data["ident"])
if entry is None:
await sio.emit("mst", {"msg": f"Unable to append {data['ident']}"})
await sio.emit("msg", {"msg": f"Unable to append {data['ident']}"})
return
entry.uid = data["uid"] if "uid" in data else None
first_song = state.queue.try_peek()
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
start_time = state.queue.fold(
lambda item, time: time
+ item.duration
+ state.config.preview_duration
+ 1,
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}:"
f"{end_time.minute:02d}."
},
room=sid,
)
return
state.queue.append(entry)
await send_state(state, room)
await sio.emit(
"get-meta-info",
entry,
room=clients[room].sid,
)
await append_to_queue(room, entry, sid)
@sio.on("meta-info")
@ -278,6 +331,10 @@ async def handle_meta_info(sid: str, data: dict[str, Any]) -> None:
lambda item: item.update(**data["meta"]),
)
for entry in state.waiting_room:
if entry.uuid == data["uuid"] or str(entry.uuid) == data["uuid"]:
entry.update(**data["meta"])
await send_state(state, room)
@ -310,6 +367,28 @@ async def handle_get_first(sid: str) -> None:
await sio.emit("play", current, room=sid)
async def discard_first(room) -> Entry:
state = clients[room]
old_entry = await state.queue.popleft()
# append items from the waiting room
first_entry_for_uid = None
for wr_entry in state.waiting_room:
if wr_entry.uid == old_entry.uid:
first_entry_for_uid = wr_entry
break
if first_entry_for_uid is not None:
await append_to_queue(room, first_entry_for_uid)
state.waiting_room.remove(first_entry_for_uid)
state.recent.append(old_entry)
state.last_seen = datetime.datetime.now()
return old_entry
@sio.on("pop-then-get-next")
async def handle_pop_then_get_next(sid: str) -> None:
"""
@ -336,11 +415,9 @@ async def handle_pop_then_get_next(sid: str) -> None:
if sid != state.sid:
return
old_entry = await state.queue.popleft()
state.recent.append(old_entry)
state.last_seen = datetime.datetime.now()
await discard_first(room)
await send_state(state, room)
current = await state.queue.peek()
current.started_at = datetime.datetime.now().timestamp()
await send_state(state, room)
@ -448,12 +525,17 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None:
)
else:
logger.info("Registerd new client %s", room)
print(data)
initial_entries = [Entry(**entry) for entry in data["queue"]]
initial_waiting_room = [
Entry(**entry) for entry in data["waiting_room"]
]
initial_recent = [Entry(**entry) for entry in data["recent"]]
clients[room] = State(
secret=data["secret"],
queue=Queue(initial_entries),
waiting_room=initial_waiting_room,
recent=initial_recent,
sid=sid,
config=Config(sources={}, sources_prio=[], **data["config"]),
@ -637,8 +719,7 @@ async def handle_skip_current(sid: str) -> None:
state = clients[room]
if is_admin:
old_entry = await state.queue.popleft()
state.recent.append(old_entry)
old_entry = await discard_first(room)
await sio.emit("skip-current", old_entry, room=clients[room].sid)
await send_state(state, room)

View file

@ -2,7 +2,7 @@
# pylint: disable=missing-module-docstring
# pylint: disable=missing-class-docstring
import asyncio
from typing import Any
from typing import Any, Optional
from aiocmd import aiocmd
import socketio
@ -30,6 +30,12 @@ async def handle_state(data: dict[str, Any]) -> None:
print(
f"\t{item.performer}: {item.artist} - {item.title} ({item.duration})"
)
print("Waiting Room")
for raw_item in data["shadow_queue"]:
item = Entry(**raw_item)
print(
f"\t{item.performer}: {item.artist} - {item.title} ({item.duration})"
)
print("Recent")
for raw_item in data["recent"]:
item = Entry(**raw_item)
@ -38,8 +44,13 @@ async def handle_state(data: dict[str, Any]) -> None:
)
@sio.on("msg")
async def handle_msg(data: dict[str, Any]) -> None:
print(data["msg"])
@sio.on("connect")
async def handle_connect(_: dict[str, Any]) -> None:
async def handle_connect() -> None:
print("Connected")
await sio.emit("register-web", {"room": state["room"]})
@ -64,6 +75,7 @@ class SyngShell(aiocmd.PromptToolkitCmd):
{
"performer": "Hammy",
"source": "youtube",
"uid": "mockup",
# https://youtube.com/watch?v=x5bM5Bdizi4",
"ident": "https://www.youtube.com/watch?v=rqZqHXJm-UA",
},
@ -72,9 +84,30 @@ class SyngShell(aiocmd.PromptToolkitCmd):
async def do_search(self, query: str) -> None:
await sio.emit("search", {"query": query})
async def do_append(self, source: str, ident: str) -> None:
async def do_append(
self, source: str, ident: str, uid: Optional[str] = None
) -> None:
await sio.emit(
"append", {"performer": "Hammy", "source": source, "ident": ident}
"append",
{
"performer": "Mockup",
"source": source,
"ident": ident,
"uid": uid if uid is not None else "mockup",
},
)
async def do_waiting_room(
self, source: str, ident: str, uid: Optional[str] = None
) -> None:
await sio.emit(
"shadow-append",
{
"performer": "Mockup",
"source": source,
"ident": ident,
"uid": uid if uid is not None else "mockup",
},
)
async def do_admin(self, data: str) -> None: