Added simple queue view in player

This commit is contained in:
Christoph Stahl 2025-02-18 09:53:40 +01:00
parent 8eb484abc2
commit 7fd54527c8
2 changed files with 73 additions and 3 deletions

View file

@ -13,6 +13,7 @@ be one of:
""" """
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable
import logging import logging
import os import os
import asyncio import asyncio
@ -134,6 +135,7 @@ class Client:
config["config"] = default_config() | config["config"] config["config"] = default_config() | config["config"]
self.is_running = False self.is_running = False
self.is_quitting = False
self.set_log_level(config["config"]["log_level"]) self.set_log_level(config["config"]["log_level"])
self.sio = socketio.AsyncClient(json=jsonencoder) self.sio = socketio.AsyncClient(json=jsonencoder)
self.loop: Optional[asyncio.AbstractEventLoop] = None self.loop: Optional[asyncio.AbstractEventLoop] = None
@ -149,6 +151,10 @@ class Client:
self.quit_callback, self.quit_callback,
) )
self.register_handlers() self.register_handlers()
self.queue_callbacks: list[Callable[[list[Entry]], None]] = []
def add_queue_callback(self, callback: Callable[[list[Entry]], None]) -> None:
self.queue_callbacks.append(callback)
def set_log_level(self, level: str) -> None: def set_log_level(self, level: str) -> None:
match level: match level:
@ -254,7 +260,9 @@ class Client:
:type data: dict[str, Any] :type data: dict[str, Any]
:rtype: None :rtype: None
""" """
self.state.queue = [Entry(**entry) for entry in data["queue"]] self.state.queue.clear()
self.state.queue.extend([Entry(**entry) for entry in data["queue"]])
# self.state.queue = [Entry(**entry) for entry in data["queue"]]
self.state.waiting_room = [Entry(**entry) for entry in data["waiting_room"]] self.state.waiting_room = [Entry(**entry) for entry in data["waiting_room"]]
self.state.recent = [Entry(**entry) for entry in data["recent"]] self.state.recent = [Entry(**entry) for entry in data["recent"]]
@ -273,6 +281,8 @@ class Client:
except ValueError as e: except ValueError as e:
logger.error("Error buffering: %s", e) logger.error("Error buffering: %s", e)
await self.sio.emit("skip", {"uuid": entry.uuid}) await self.sio.emit("skip", {"uuid": entry.uuid})
for callback in self.queue_callbacks:
callback(self.state.queue)
async def handle_connect(self) -> None: async def handle_connect(self) -> None:
""" """

View file

@ -11,7 +11,7 @@ from datetime import datetime
import os import os
from functools import partial from functools import partial
import random import random
from typing import TYPE_CHECKING, Any, Optional from typing import TYPE_CHECKING, Any, Optional, cast
import secrets import secrets
import string import string
import signal import signal
@ -28,7 +28,15 @@ except ImportError:
os.environ["QT_API"] = "pyqt6" os.environ["QT_API"] = "pyqt6"
from qasync import QEventLoop, QApplication from qasync import QEventLoop, QApplication
from PyQt6.QtCore import QObject, QTimer, Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import (
QAbstractListModel,
QModelIndex,
QObject,
QTimer,
Qt,
pyqtSignal,
pyqtSlot,
)
from PyQt6.QtGui import QCloseEvent, QIcon, QPixmap from PyQt6.QtGui import QCloseEvent, QIcon, QPixmap
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QCheckBox, QCheckBox,
@ -40,6 +48,7 @@ from PyQt6.QtWidgets import (
QLabel, QLabel,
QLayout, QLayout,
QLineEdit, QLineEdit,
QListView,
QMainWindow, QMainWindow,
QMessageBox, QMessageBox,
QPushButton, QPushButton,
@ -59,6 +68,7 @@ import platformdirs
from . import resources # noqa from . import resources # noqa
from .client import Client, default_config from .client import Client, default_config
from .log import logger from .log import logger
from .entry import Entry
from .sources import available_sources from .sources import available_sources
from .config import ( from .config import (
@ -73,6 +83,28 @@ from .config import (
) )
class QueueModel(QAbstractListModel):
def __init__(self, queue: list[Entry]) -> None:
super().__init__()
self.queue = queue
def update(self, queue: list[Entry]) -> None:
self.queue = queue
self.dataChanged.emit(self.index(0, 0), self.index(self.rowCount() - 1, 0))
def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> Any:
if role == Qt.ItemDataRole.DisplayRole:
entry = self.queue[index.row()]
return f"{entry.title} - {entry.artist} [{entry.album}]\n{entry.performer}"
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
return len(self.queue)
class QueueView(QListView):
pass
class OptionFrame(QWidget): class OptionFrame(QWidget):
def add_bool_option(self, name: str, description: str, value: bool = False) -> None: def add_bool_option(self, name: str, description: str, value: bool = False) -> None:
label = QLabel(description, self) label = QLabel(description, self)
@ -497,6 +529,8 @@ class GeneralConfig(OptionFrame):
class SyngGui(QMainWindow): class SyngGui(QMainWindow):
def closeEvent(self, a0: Optional[QCloseEvent]) -> None: def closeEvent(self, a0: Optional[QCloseEvent]) -> None:
if self.client is not None: if self.client is not None:
if self.client.player is not None and self.client.player.mpv is not None:
self.client.player.mpv.terminate()
self.client.quit_callback() self.client.quit_callback()
self.log_label_handler.cleanup() self.log_label_handler.cleanup()
@ -529,11 +563,23 @@ class SyngGui(QMainWindow):
spacer_item = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) spacer_item = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
self.buttons_layout.addItem(spacer_item) self.buttons_layout.addItem(spacer_item)
if os.getenv("SYNG_DEBUG", "0") == "1":
self.print_queue_button = QPushButton("Print Queue")
self.print_queue_button.clicked.connect(self.debug_print_queue)
self.buttons_layout.addWidget(self.print_queue_button)
self.startbutton = QPushButton("Connect") self.startbutton = QPushButton("Connect")
self.startbutton.clicked.connect(self.start_syng_client) self.startbutton.clicked.connect(self.start_syng_client)
self.buttons_layout.addWidget(self.startbutton) self.buttons_layout.addWidget(self.startbutton)
def debug_print_queue(self) -> None:
if self.client is not None:
print([entry.title for entry in self.client.state.queue])
model = cast(Optional[QueueModel], self.queue_list_view.model())
if model is not None:
print(model.queue)
def toggle_advanced(self, state: bool) -> None: def toggle_advanced(self, state: bool) -> None:
self.resetbutton.setVisible(state) self.resetbutton.setVisible(state)
self.exportbutton.setVisible(state) self.exportbutton.setVisible(state)
@ -626,6 +672,16 @@ class SyngGui(QMainWindow):
self.tabview.addTab(self.log_tab, "Logs") self.tabview.addTab(self.log_tab, "Logs")
def add_queue_tab(self) -> None:
self.queue_tab = QWidget(parent=self.central_widget)
self.queue_layout = QVBoxLayout(self.queue_tab)
self.queue_tab.setLayout(self.queue_layout)
self.queue_list_view: QueueView = QueueView(self.queue_tab)
self.queue_layout.addWidget(self.queue_list_view)
self.tabview.addTab(self.queue_tab, "Queue")
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self.setWindowTitle("Syng") self.setWindowTitle("Syng")
@ -654,6 +710,7 @@ class SyngGui(QMainWindow):
for source_name in available_sources: for source_name in available_sources:
self.add_source_config(source_name, config["sources"][source_name]) self.add_source_config(source_name, config["sources"][source_name])
self.add_queue_tab()
self.add_log_tab() self.add_log_tab()
self.update_qr() self.update_qr()
@ -786,6 +843,9 @@ class SyngGui(QMainWindow):
config = self.gather_config() config = self.gather_config()
self.client = Client(config) self.client = Client(config)
asyncio.run_coroutine_threadsafe(self.client.start_client(config), self.loop) asyncio.run_coroutine_threadsafe(self.client.start_client(config), self.loop)
model = QueueModel(self.client.state.queue)
self.queue_list_view.setModel(model)
self.client.add_queue_callback(model.update)
self.timer.start(500) self.timer.start(500)
self.set_client_button_stop() self.set_client_button_stop()
else: else: