Added a waitingroom
This commit is contained in:
parent
9b9fcd6b02
commit
2f860ac3c4
4 changed files with 179 additions and 48 deletions
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
167
syng/server.py
167
syng/server.py
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Reference in a new issue