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]
|
:type current_source: Optional[Source]
|
||||||
:param queue: A copy of the current playlist on the server.
|
:param queue: A copy of the current playlist on the server.
|
||||||
:type queue: list[Entry]
|
: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.
|
:param recent: A copy of all played songs this session.
|
||||||
:type recent: list[Entry]
|
:type recent: list[Entry]
|
||||||
:param room: The room on the server this playback client is connected to.
|
:param room: The room on the server this playback client is connected to.
|
||||||
|
@ -97,6 +99,7 @@ class State:
|
||||||
|
|
||||||
current_source: Optional[Source] = None
|
current_source: Optional[Source] = None
|
||||||
queue: list[Entry] = field(default_factory=list)
|
queue: list[Entry] = field(default_factory=list)
|
||||||
|
waiting_room: list[Entry] = field(default_factory=list)
|
||||||
recent: list[Entry] = field(default_factory=list)
|
recent: list[Entry] = field(default_factory=list)
|
||||||
room: str = ""
|
room: str = ""
|
||||||
server: str = ""
|
server: str = ""
|
||||||
|
@ -164,6 +167,7 @@ async def handle_state(data: dict[str, Any]) -> None:
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
state.queue = [Entry(**entry) for entry in data["queue"]]
|
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"]]
|
state.recent = [Entry(**entry) for entry in data["recent"]]
|
||||||
|
|
||||||
for entry in state.queue[:2]:
|
for entry in state.queue[:2]:
|
||||||
|
@ -193,6 +197,7 @@ async def handle_connect() -> None:
|
||||||
logging.info("Connected to server")
|
logging.info("Connected to server")
|
||||||
data = {
|
data = {
|
||||||
"queue": state.queue,
|
"queue": state.queue,
|
||||||
|
"waiting_room": state.waiting_room,
|
||||||
"recent": state.recent,
|
"recent": state.recent,
|
||||||
"room": state.room,
|
"room": state.room,
|
||||||
"secret": state.secret,
|
"secret": state.secret,
|
||||||
|
@ -200,6 +205,7 @@ async def handle_connect() -> None:
|
||||||
}
|
}
|
||||||
if state.key:
|
if state.key:
|
||||||
data["registration-key"] = state.key
|
data["registration-key"] = state.key
|
||||||
|
print(data)
|
||||||
await sio.emit("register-client", data)
|
await sio.emit("register-client", data)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
"""A async queue with synchronization."""
|
"""A async queue with synchronization."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
from collections.abc import Callable, Iterable
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
@ -95,6 +95,8 @@ class Queue:
|
||||||
"""
|
"""
|
||||||
Update entries in the queue, identified by their uuid.
|
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
|
:param uuid: The uuid of the entry to update
|
||||||
:type uuid: UUID | str
|
:type uuid: UUID | str
|
||||||
:param updater: A function, that updates the entry
|
:param updater: A function, that updates the entry
|
||||||
|
@ -119,6 +121,15 @@ class Queue:
|
||||||
return item
|
return item
|
||||||
return None
|
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:
|
def fold(self, func: Callable[[Entry, Any], Any], start_value: Any) -> Any:
|
||||||
"""Call ``func`` on each entry and accumulate the result."""
|
"""Call ``func`` on each entry and accumulate the result."""
|
||||||
for item in self._queue:
|
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
|
are appended to this, and if a playback client requests a song, it is
|
||||||
taken from the top.
|
taken from the top.
|
||||||
:type queue: Queue
|
: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.
|
:param recent: A list of already played songs in order.
|
||||||
:type recent: list[Entry]
|
:type recent: list[Entry]
|
||||||
:param sid: The socket.io session id of the (unique) playback client. Once
|
:param sid: The socket.io session id of the (unique) playback client. Once
|
||||||
|
@ -116,6 +119,7 @@ class State:
|
||||||
|
|
||||||
secret: str
|
secret: str
|
||||||
queue: Queue
|
queue: Queue
|
||||||
|
waiting_room: list[Entry]
|
||||||
recent: list[Entry]
|
recent: list[Entry]
|
||||||
sid: str
|
sid: str
|
||||||
config: Config
|
config: Config
|
||||||
|
@ -145,7 +149,11 @@ async def send_state(state: State, sid: str) -> None:
|
||||||
"""
|
"""
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"state",
|
"state",
|
||||||
{"queue": state.queue, "recent": state.recent},
|
{
|
||||||
|
"queue": state.queue,
|
||||||
|
"recent": state.recent,
|
||||||
|
"waiting_room": state.waiting_room,
|
||||||
|
},
|
||||||
room=sid,
|
room=sid,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -170,6 +178,85 @@ async def handle_state(sid: str) -> None:
|
||||||
await send_state(state, sid)
|
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")
|
@sio.on("append")
|
||||||
async def handle_append(sid: str, data: dict[str, Any]) -> None:
|
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"]]
|
source_obj = state.config.sources[data["source"]]
|
||||||
entry = await source_obj.get_entry(data["performer"], data["ident"])
|
entry = await source_obj.get_entry(data["performer"], data["ident"])
|
||||||
if entry is None:
|
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
|
return
|
||||||
|
|
||||||
entry.uid = data["uid"] if "uid" in data else None
|
entry.uid = data["uid"] if "uid" in data else None
|
||||||
|
|
||||||
first_song = state.queue.try_peek()
|
await append_to_queue(room, entry, sid)
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@sio.on("meta-info")
|
@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"]),
|
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)
|
await send_state(state, room)
|
||||||
|
|
||||||
|
|
||||||
|
@ -310,6 +367,28 @@ async def handle_get_first(sid: str) -> None:
|
||||||
await sio.emit("play", current, room=sid)
|
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")
|
@sio.on("pop-then-get-next")
|
||||||
async def handle_pop_then_get_next(sid: str) -> None:
|
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:
|
if sid != state.sid:
|
||||||
return
|
return
|
||||||
|
|
||||||
old_entry = await state.queue.popleft()
|
await discard_first(room)
|
||||||
state.recent.append(old_entry)
|
|
||||||
state.last_seen = datetime.datetime.now()
|
|
||||||
|
|
||||||
await send_state(state, room)
|
await send_state(state, room)
|
||||||
|
|
||||||
current = await state.queue.peek()
|
current = await state.queue.peek()
|
||||||
current.started_at = datetime.datetime.now().timestamp()
|
current.started_at = datetime.datetime.now().timestamp()
|
||||||
await send_state(state, room)
|
await send_state(state, room)
|
||||||
|
@ -448,12 +525,17 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None:
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.info("Registerd new client %s", room)
|
logger.info("Registerd new client %s", room)
|
||||||
|
print(data)
|
||||||
initial_entries = [Entry(**entry) for entry in data["queue"]]
|
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"]]
|
initial_recent = [Entry(**entry) for entry in data["recent"]]
|
||||||
|
|
||||||
clients[room] = State(
|
clients[room] = State(
|
||||||
secret=data["secret"],
|
secret=data["secret"],
|
||||||
queue=Queue(initial_entries),
|
queue=Queue(initial_entries),
|
||||||
|
waiting_room=initial_waiting_room,
|
||||||
recent=initial_recent,
|
recent=initial_recent,
|
||||||
sid=sid,
|
sid=sid,
|
||||||
config=Config(sources={}, sources_prio=[], **data["config"]),
|
config=Config(sources={}, sources_prio=[], **data["config"]),
|
||||||
|
@ -637,8 +719,7 @@ async def handle_skip_current(sid: str) -> None:
|
||||||
state = clients[room]
|
state = clients[room]
|
||||||
|
|
||||||
if is_admin:
|
if is_admin:
|
||||||
old_entry = await state.queue.popleft()
|
old_entry = await discard_first(room)
|
||||||
state.recent.append(old_entry)
|
|
||||||
await sio.emit("skip-current", old_entry, room=clients[room].sid)
|
await sio.emit("skip-current", old_entry, room=clients[room].sid)
|
||||||
await send_state(state, room)
|
await send_state(state, room)
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# pylint: disable=missing-module-docstring
|
# pylint: disable=missing-module-docstring
|
||||||
# pylint: disable=missing-class-docstring
|
# pylint: disable=missing-class-docstring
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
|
|
||||||
from aiocmd import aiocmd
|
from aiocmd import aiocmd
|
||||||
import socketio
|
import socketio
|
||||||
|
@ -30,6 +30,12 @@ async def handle_state(data: dict[str, Any]) -> None:
|
||||||
print(
|
print(
|
||||||
f"\t{item.performer}: {item.artist} - {item.title} ({item.duration})"
|
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")
|
print("Recent")
|
||||||
for raw_item in data["recent"]:
|
for raw_item in data["recent"]:
|
||||||
item = Entry(**raw_item)
|
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")
|
@sio.on("connect")
|
||||||
async def handle_connect(_: dict[str, Any]) -> None:
|
async def handle_connect() -> None:
|
||||||
print("Connected")
|
print("Connected")
|
||||||
await sio.emit("register-web", {"room": state["room"]})
|
await sio.emit("register-web", {"room": state["room"]})
|
||||||
|
|
||||||
|
@ -64,6 +75,7 @@ class SyngShell(aiocmd.PromptToolkitCmd):
|
||||||
{
|
{
|
||||||
"performer": "Hammy",
|
"performer": "Hammy",
|
||||||
"source": "youtube",
|
"source": "youtube",
|
||||||
|
"uid": "mockup",
|
||||||
# https://youtube.com/watch?v=x5bM5Bdizi4",
|
# https://youtube.com/watch?v=x5bM5Bdizi4",
|
||||||
"ident": "https://www.youtube.com/watch?v=rqZqHXJm-UA",
|
"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:
|
async def do_search(self, query: str) -> None:
|
||||||
await sio.emit("search", {"query": query})
|
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(
|
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:
|
async def do_admin(self, data: str) -> None:
|
||||||
|
|
Loading…
Add table
Reference in a new issue