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.QtCore import Qt, QSize, QTimer, Signal
|
||||||
from PySide6.QtGui import QFont
|
from PySide6.QtGui import QFont
|
||||||
|
|
||||||
from services.bluetooth_service import BluetoothService
|
from controllers.media_controller import MediaController, TrackMetadata
|
||||||
|
|
||||||
|
|
||||||
class MediaScreen(QWidget):
|
class MediaScreen(QWidget):
|
||||||
source_changed = Signal(str)
|
source_changed = Signal(str)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, media_controller: MediaController = None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._bt_service = BluetoothService(self)
|
self._controller = media_controller
|
||||||
|
|
||||||
root = QVBoxLayout(self)
|
root = QVBoxLayout(self)
|
||||||
root.setContentsMargins(18, 16, 18, 16)
|
root.setContentsMargins(18, 16, 18, 16)
|
||||||
@ -155,43 +155,54 @@ class MediaScreen(QWidget):
|
|||||||
self._poll_timer.start(2000)
|
self._poll_timer.start(2000)
|
||||||
self._refresh_metadata()
|
self._refresh_metadata()
|
||||||
|
|
||||||
|
def set_controller(self, controller: MediaController) -> None:
|
||||||
|
"""Установить контроллер."""
|
||||||
|
self._controller = controller
|
||||||
|
self._refresh_metadata()
|
||||||
|
|
||||||
def _toggle_play(self):
|
def _toggle_play(self):
|
||||||
"""Переключить воспроизведение/пауза."""
|
"""Переключить воспроизведение/пауза."""
|
||||||
self._bt_service.player_toggle()
|
if self._controller:
|
||||||
|
self._controller.toggle_play()
|
||||||
QTimer.singleShot(300, self._refresh_metadata)
|
QTimer.singleShot(300, self._refresh_metadata)
|
||||||
|
|
||||||
def _prev(self):
|
def _prev(self):
|
||||||
"""Предыдущий трек."""
|
"""Предыдущий трек."""
|
||||||
self._bt_service.player_previous()
|
if self._controller:
|
||||||
|
self._controller.previous_track()
|
||||||
QTimer.singleShot(300, self._refresh_metadata)
|
QTimer.singleShot(300, self._refresh_metadata)
|
||||||
|
|
||||||
def _next(self):
|
def _next(self):
|
||||||
"""Следующий трек."""
|
"""Следующий трек."""
|
||||||
self._bt_service.player_next()
|
if self._controller:
|
||||||
|
self._controller.next_track()
|
||||||
QTimer.singleShot(300, self._refresh_metadata)
|
QTimer.singleShot(300, self._refresh_metadata)
|
||||||
|
|
||||||
def _refresh_metadata(self):
|
def _refresh_metadata(self):
|
||||||
"""Обновить метаданные трека."""
|
"""Обновить метаданные трека."""
|
||||||
metadata = self._bt_service.get_player_metadata()
|
if not self._controller:
|
||||||
|
return
|
||||||
|
|
||||||
|
metadata = self._controller.get_metadata()
|
||||||
|
|
||||||
if metadata["title"]:
|
if metadata.title:
|
||||||
self.title.setText(metadata["title"])
|
self.title.setText(metadata.title)
|
||||||
if metadata["artist"]:
|
if metadata.artist:
|
||||||
self.artist.setText(metadata["artist"])
|
self.artist.setText(metadata.artist)
|
||||||
if metadata["album"]:
|
if metadata.album:
|
||||||
self.album.setText(metadata["album"])
|
self.album.setText(metadata.album)
|
||||||
if metadata["source"]:
|
if metadata.source:
|
||||||
text = f"Источник: {metadata["source"]}"
|
text = f"Источник: {metadata.source}"
|
||||||
self.source.setText(text)
|
self.source.setText(text)
|
||||||
self.source_changed.emit(text)
|
self.source_changed.emit(text)
|
||||||
if metadata["duration"] is not None and metadata["duration"] > 0:
|
if metadata.duration is not None and metadata.duration > 0:
|
||||||
self.progress.setRange(0, metadata["duration"])
|
self.progress.setRange(0, metadata.duration)
|
||||||
self.time_total.setText(self._format_time(metadata["duration"]))
|
self.time_total.setText(self._format_time(metadata.duration))
|
||||||
if metadata["position"] is not None:
|
if metadata.position is not None:
|
||||||
self.progress.setValue(metadata["position"])
|
self.progress.setValue(metadata.position)
|
||||||
self.time_pos.setText(self._format_time(metadata["position"]))
|
self.time_pos.setText(self._format_time(metadata.position))
|
||||||
if metadata["status"]:
|
if metadata.status:
|
||||||
self.btn_play.setText("⏸" if metadata["status"] == "playing" else "▶")
|
self.btn_play.setText("⏸" if metadata.status == "playing" else "▶")
|
||||||
|
|
||||||
def _format_time(self, ms: int) -> str:
|
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.media import MediaScreen
|
||||||
from screens.stub import StubScreen
|
from screens.stub import StubScreen
|
||||||
from screens.settings import SettingsScreen
|
from screens.settings import SettingsScreen
|
||||||
|
from services.bluetooth_service import BluetoothService
|
||||||
|
from controllers.media_controller import MediaController
|
||||||
|
|
||||||
|
|
||||||
class MainWindowNew(QMainWindow):
|
class MainWindowNew(QMainWindow):
|
||||||
@ -98,7 +100,11 @@ class MainWindowNew(QMainWindow):
|
|||||||
top.addWidget(self.btn_settings)
|
top.addWidget(self.btn_settings)
|
||||||
|
|
||||||
self.stack = QStackedWidget()
|
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(self.media_screen) # 0
|
||||||
self.stack.addWidget(StubScreen("Car")) # 1
|
self.stack.addWidget(StubScreen("Car")) # 1
|
||||||
self.stack.addWidget(StubScreen("Maps")) # 2
|
self.stack.addWidget(StubScreen("Maps")) # 2
|
||||||
@ -112,6 +118,7 @@ class MainWindowNew(QMainWindow):
|
|||||||
|
|
||||||
# Читаем режим источника
|
# Читаем режим источника
|
||||||
self._source_mode = self._settings.value("media/source_mode", "bluetooth")
|
self._source_mode = self._settings.value("media/source_mode", "bluetooth")
|
||||||
|
self._media_controller.set_mode(self._source_mode)
|
||||||
self._update_source_menu()
|
self._update_source_menu()
|
||||||
|
|
||||||
self.act_media.triggered.connect(lambda: self._open_media())
|
self.act_media.triggered.connect(lambda: self._open_media())
|
||||||
@ -191,6 +198,7 @@ class MainWindowNew(QMainWindow):
|
|||||||
"""Установить режим источника (Bluetooth/CarPlay)."""
|
"""Установить режим источника (Bluetooth/CarPlay)."""
|
||||||
self._settings.setValue("media/source_mode", mode)
|
self._settings.setValue("media/source_mode", mode)
|
||||||
self._source_mode = mode
|
self._source_mode = mode
|
||||||
|
self._media_controller.set_mode(mode)
|
||||||
self._update_source_menu()
|
self._update_source_menu()
|
||||||
# Обновляем заголовок
|
# Обновляем заголовок
|
||||||
if self.stack.currentIndex() == 0:
|
if self.stack.currentIndex() == 0:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user