add controller
This commit is contained in:
parent
8638c322a3
commit
04f5562e57
15
controllers/__init__.py
Normal file
15
controllers/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
from .media_controller import (
|
||||
MediaController,
|
||||
MediaSourceController,
|
||||
BluetoothController,
|
||||
CarPlayController,
|
||||
TrackMetadata,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"MediaController",
|
||||
"MediaSourceController",
|
||||
"BluetoothController",
|
||||
"CarPlayController",
|
||||
"TrackMetadata",
|
||||
]
|
||||
247
controllers/media_controller.py
Normal file
247
controllers/media_controller.py
Normal file
@ -0,0 +1,247 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrackMetadata:
|
||||
"""Метаданные трека."""
|
||||
title: str = ""
|
||||
artist: str = ""
|
||||
album: str = ""
|
||||
source: str = ""
|
||||
position: int = 0 # ms
|
||||
duration: int = 0 # ms
|
||||
status: str = "" # "playing", "paused", "stopped"
|
||||
|
||||
|
||||
class MediaSourceController(ABC):
|
||||
"""Базовый класс контроллера источника медиа."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
"""Название источника."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_volume(self) -> int:
|
||||
"""Получить текущую громкость (0-100)."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_volume(self, value: int) -> None:
|
||||
"""Установить громкость (0-100)."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_metadata(self) -> TrackMetadata:
|
||||
"""Получить метаданные текущего трека."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def play(self) -> None:
|
||||
"""Воспроизведение."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def pause(self) -> None:
|
||||
"""Пауза."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def toggle_play(self) -> None:
|
||||
"""Переключить воспроизведение/пауза."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def next_track(self) -> None:
|
||||
"""Следующий трек."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def previous_track(self) -> None:
|
||||
"""Предыдущий трек."""
|
||||
pass
|
||||
|
||||
|
||||
class BluetoothController(MediaSourceController):
|
||||
"""Контроллер для Bluetooth аудио."""
|
||||
|
||||
def __init__(self, bt_service: Any):
|
||||
self._bt_service = bt_service
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "Bluetooth"
|
||||
|
||||
def get_volume(self) -> int:
|
||||
# TODO: получить громкость из Bluetooth сервиса
|
||||
return 50
|
||||
|
||||
def set_volume(self, value: int) -> None:
|
||||
# TODO: установить громкость в Bluetooth сервис
|
||||
pass
|
||||
|
||||
def get_metadata(self) -> TrackMetadata:
|
||||
if self._bt_service is None:
|
||||
return TrackMetadata()
|
||||
|
||||
metadata = self._bt_service.get_player_metadata()
|
||||
return TrackMetadata(
|
||||
title=metadata.get("title", ""),
|
||||
artist=metadata.get("artist", ""),
|
||||
album=metadata.get("album", ""),
|
||||
source=metadata.get("source", ""),
|
||||
position=metadata.get("position", 0) or 0,
|
||||
duration=metadata.get("duration", 0) or 0,
|
||||
status=metadata.get("status", ""),
|
||||
)
|
||||
|
||||
def play(self) -> None:
|
||||
if self._bt_service:
|
||||
self._bt_service.player_play()
|
||||
|
||||
def pause(self) -> None:
|
||||
if self._bt_service:
|
||||
self._bt_service.player_pause()
|
||||
|
||||
def toggle_play(self) -> None:
|
||||
if self._bt_service:
|
||||
self._bt_service.player_toggle()
|
||||
|
||||
def next_track(self) -> None:
|
||||
if self._bt_service:
|
||||
self._bt_service.player_next()
|
||||
|
||||
def previous_track(self) -> None:
|
||||
if self._bt_service:
|
||||
self._bt_service.player_previous()
|
||||
|
||||
|
||||
class CarPlayController(MediaSourceController):
|
||||
"""Контроллер для CarPlay (заглушка)."""
|
||||
|
||||
def __init__(self):
|
||||
self._volume = 50
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "CarPlay"
|
||||
|
||||
def get_volume(self) -> int:
|
||||
return self._volume
|
||||
|
||||
def set_volume(self, value: int) -> None:
|
||||
self._volume = max(0, min(100, value))
|
||||
|
||||
def get_metadata(self) -> TrackMetadata:
|
||||
# Заглушка - нет данных
|
||||
return TrackMetadata(
|
||||
title="",
|
||||
artist="",
|
||||
album="",
|
||||
source="CarPlay",
|
||||
position=0,
|
||||
duration=0,
|
||||
status="stopped",
|
||||
)
|
||||
|
||||
def play(self) -> None:
|
||||
# TODO: реализовать через CarPlay API
|
||||
pass
|
||||
|
||||
def pause(self) -> None:
|
||||
# TODO: реализовать через CarPlay API
|
||||
pass
|
||||
|
||||
def toggle_play(self) -> None:
|
||||
# TODO: реализовать через CarPlay API
|
||||
pass
|
||||
|
||||
def next_track(self) -> None:
|
||||
# TODO: реализовать через CarPlay API
|
||||
pass
|
||||
|
||||
def previous_track(self) -> None:
|
||||
# TODO: реализовать через CarPlay API
|
||||
pass
|
||||
|
||||
|
||||
class MediaController(QObject):
|
||||
"""
|
||||
Главный контроллер медиа.
|
||||
Выбирает активный контроллер в зависимости от режима.
|
||||
"""
|
||||
|
||||
volume_changed = Signal(int)
|
||||
metadata_changed = Signal(object) # TrackMetadata
|
||||
|
||||
def __init__(self, bt_service: Any = None, parent: QObject = None):
|
||||
super().__init__(parent)
|
||||
self._bt_service = bt_service
|
||||
self._controllers: dict[str, MediaSourceController] = {}
|
||||
self._current_mode: str = "bluetooth"
|
||||
|
||||
# Регистрируем контроллеры
|
||||
self._register_controllers()
|
||||
|
||||
def _register_controllers(self) -> None:
|
||||
"""Зарегистрировать все доступные контроллеры."""
|
||||
self._controllers["bluetooth"] = BluetoothController(self._bt_service)
|
||||
self._controllers["carplay"] = CarPlayController()
|
||||
# В будущем можно добавить:
|
||||
# self._controllers["aux"] = AuxController()
|
||||
|
||||
def set_mode(self, mode: str) -> None:
|
||||
"""Установить активный режим."""
|
||||
if mode in self._controllers:
|
||||
self._current_mode = mode
|
||||
|
||||
def get_mode(self) -> str:
|
||||
"""Получить текущий режим."""
|
||||
return self._current_mode
|
||||
|
||||
@property
|
||||
def _current_controller(self) -> MediaSourceController:
|
||||
"""Получить активный контроллер."""
|
||||
return self._controllers.get(self._current_mode, self._controllers["bluetooth"])
|
||||
|
||||
# === Делегирующие методы ===
|
||||
|
||||
def get_volume(self) -> int:
|
||||
"""Получить текущую громкость."""
|
||||
return self._current_controller.get_volume()
|
||||
|
||||
def set_volume(self, value: int) -> None:
|
||||
"""Установить громкость."""
|
||||
self._current_controller.set_volume(value)
|
||||
self.volume_changed.emit(value)
|
||||
|
||||
def get_metadata(self) -> TrackMetadata:
|
||||
"""Получить метаданные текущего трека."""
|
||||
return self._current_controller.get_metadata()
|
||||
|
||||
def play(self) -> None:
|
||||
"""Воспроизведение."""
|
||||
self._current_controller.play()
|
||||
|
||||
def pause(self) -> None:
|
||||
"""Пауза."""
|
||||
self._current_controller.pause()
|
||||
|
||||
def toggle_play(self) -> None:
|
||||
"""Переключить воспроизведение/пауза."""
|
||||
self._current_controller.toggle_play()
|
||||
|
||||
def next_track(self) -> None:
|
||||
"""Следующий трек."""
|
||||
self._current_controller.next_track()
|
||||
|
||||
def previous_track(self) -> None:
|
||||
"""Предыдущий трек."""
|
||||
self._current_controller.previous_track()
|
||||
@ -10,15 +10,15 @@ from PySide6.QtWidgets import (
|
||||
from PySide6.QtCore import Qt, QSize, QTimer, Signal
|
||||
from PySide6.QtGui import QFont
|
||||
|
||||
from services.bluetooth_service import BluetoothService
|
||||
from controllers.media_controller import MediaController, TrackMetadata
|
||||
|
||||
|
||||
class MediaScreen(QWidget):
|
||||
source_changed = Signal(str)
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, media_controller: MediaController = None):
|
||||
super().__init__()
|
||||
self._bt_service = BluetoothService(self)
|
||||
self._controller = media_controller
|
||||
|
||||
root = QVBoxLayout(self)
|
||||
root.setContentsMargins(18, 16, 18, 16)
|
||||
@ -155,43 +155,54 @@ class MediaScreen(QWidget):
|
||||
self._poll_timer.start(2000)
|
||||
self._refresh_metadata()
|
||||
|
||||
def set_controller(self, controller: MediaController) -> None:
|
||||
"""Установить контроллер."""
|
||||
self._controller = controller
|
||||
self._refresh_metadata()
|
||||
|
||||
def _toggle_play(self):
|
||||
"""Переключить воспроизведение/пауза."""
|
||||
self._bt_service.player_toggle()
|
||||
if self._controller:
|
||||
self._controller.toggle_play()
|
||||
QTimer.singleShot(300, self._refresh_metadata)
|
||||
|
||||
def _prev(self):
|
||||
"""Предыдущий трек."""
|
||||
self._bt_service.player_previous()
|
||||
if self._controller:
|
||||
self._controller.previous_track()
|
||||
QTimer.singleShot(300, self._refresh_metadata)
|
||||
|
||||
def _next(self):
|
||||
"""Следующий трек."""
|
||||
self._bt_service.player_next()
|
||||
if self._controller:
|
||||
self._controller.next_track()
|
||||
QTimer.singleShot(300, self._refresh_metadata)
|
||||
|
||||
def _refresh_metadata(self):
|
||||
"""Обновить метаданные трека."""
|
||||
metadata = self._bt_service.get_player_metadata()
|
||||
if not self._controller:
|
||||
return
|
||||
|
||||
metadata = self._controller.get_metadata()
|
||||
|
||||
if metadata["title"]:
|
||||
self.title.setText(metadata["title"])
|
||||
if metadata["artist"]:
|
||||
self.artist.setText(metadata["artist"])
|
||||
if metadata["album"]:
|
||||
self.album.setText(metadata["album"])
|
||||
if metadata["source"]:
|
||||
text = f"Источник: {metadata["source"]}"
|
||||
if metadata.title:
|
||||
self.title.setText(metadata.title)
|
||||
if metadata.artist:
|
||||
self.artist.setText(metadata.artist)
|
||||
if metadata.album:
|
||||
self.album.setText(metadata.album)
|
||||
if metadata.source:
|
||||
text = f"Источник: {metadata.source}"
|
||||
self.source.setText(text)
|
||||
self.source_changed.emit(text)
|
||||
if metadata["duration"] is not None and metadata["duration"] > 0:
|
||||
self.progress.setRange(0, metadata["duration"])
|
||||
self.time_total.setText(self._format_time(metadata["duration"]))
|
||||
if metadata["position"] is not None:
|
||||
self.progress.setValue(metadata["position"])
|
||||
self.time_pos.setText(self._format_time(metadata["position"]))
|
||||
if metadata["status"]:
|
||||
self.btn_play.setText("⏸" if metadata["status"] == "playing" else "▶")
|
||||
if metadata.duration is not None and metadata.duration > 0:
|
||||
self.progress.setRange(0, metadata.duration)
|
||||
self.time_total.setText(self._format_time(metadata.duration))
|
||||
if metadata.position is not None:
|
||||
self.progress.setValue(metadata.position)
|
||||
self.time_pos.setText(self._format_time(metadata.position))
|
||||
if metadata.status:
|
||||
self.btn_play.setText("⏸" if metadata.status == "playing" else "▶")
|
||||
|
||||
def _format_time(self, ms: int) -> str:
|
||||
"""Форматировать время из миллисекунд."""
|
||||
|
||||
@ -16,6 +16,8 @@ from themes import THEME_DAY, THEME_NIGHT
|
||||
from screens.media import MediaScreen
|
||||
from screens.stub import StubScreen
|
||||
from screens.settings import SettingsScreen
|
||||
from services.bluetooth_service import BluetoothService
|
||||
from controllers.media_controller import MediaController
|
||||
|
||||
|
||||
class MainWindowNew(QMainWindow):
|
||||
@ -98,7 +100,11 @@ class MainWindowNew(QMainWindow):
|
||||
top.addWidget(self.btn_settings)
|
||||
|
||||
self.stack = QStackedWidget()
|
||||
self.media_screen = MediaScreen()
|
||||
# Создаём сервис и контроллер
|
||||
self._bt_service = BluetoothService(self)
|
||||
self._media_controller = MediaController(self._bt_service, self)
|
||||
|
||||
self.media_screen = MediaScreen(self._media_controller)
|
||||
self.stack.addWidget(self.media_screen) # 0
|
||||
self.stack.addWidget(StubScreen("Car")) # 1
|
||||
self.stack.addWidget(StubScreen("Maps")) # 2
|
||||
@ -112,6 +118,7 @@ class MainWindowNew(QMainWindow):
|
||||
|
||||
# Читаем режим источника
|
||||
self._source_mode = self._settings.value("media/source_mode", "bluetooth")
|
||||
self._media_controller.set_mode(self._source_mode)
|
||||
self._update_source_menu()
|
||||
|
||||
self.act_media.triggered.connect(lambda: self._open_media())
|
||||
@ -191,6 +198,7 @@ class MainWindowNew(QMainWindow):
|
||||
"""Установить режим источника (Bluetooth/CarPlay)."""
|
||||
self._settings.setValue("media/source_mode", mode)
|
||||
self._source_mode = mode
|
||||
self._media_controller.set_mode(mode)
|
||||
self._update_source_menu()
|
||||
# Обновляем заголовок
|
||||
if self.stack.currentIndex() == 0:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user