car_ui/controllers/media_controller.py
2026-04-01 04:14:23 +03:00

287 lines
9.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any
from PySide6.QtCore import QObject, Signal, QSettings
@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
self._music_mac: str | None = None
def set_music_mac(self, mac: str | None) -> None:
"""Установить приоритетный MAC для музыки."""
self._music_mac = mac
# Синхронизируем с сервисом
if self._bt_service:
self._bt_service.set_music_mac(mac)
def get_music_mac(self) -> str | None:
"""Получить приоритетный MAC для музыки."""
return self._music_mac
def connect_device(self, mac: str) -> bool:
"""Подключиться к устройству."""
if self._bt_service:
return self._bt_service.connect_device(mac)
return False
@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._settings = QSettings("car_ui", "ui")
self._controllers: dict[str, MediaSourceController] = {}
self._current_mode: str = "bluetooth"
# Регистрируем контроллеры
self._register_controllers()
def _register_controllers(self) -> None:
"""Зарегистрировать все доступные контроллеры."""
bt_controller = BluetoothController(self._bt_service)
# Читаем приоритетный MAC из настроек
music_mac = self._settings.value("bluetooth/music_mac", "")
if music_mac:
bt_controller.set_music_mac(music_mac)
self._controllers["bluetooth"] = bt_controller
self._controllers["carplay"] = CarPlayController()
# В будущем можно добавить:
# self._controllers["aux"] = AuxController()
def set_mode(self, mode: str) -> None:
"""Установить активный режим."""
if mode in self._controllers:
# Если переключаемся с Bluetooth на CarPlay - ставим паузу
if self._current_mode == "bluetooth" and mode == "carplay":
self._controllers["bluetooth"].pause()
# Если переключаемся на Bluetooth - подключаем приоритетное устройство
elif mode == "bluetooth":
bt_controller = self._controllers["bluetooth"]
music_mac = bt_controller.get_music_mac()
if music_mac:
# Пытаемся подключить приоритетное устройство
bt_controller.connect_device(music_mac)
bt_controller.play()
self._current_mode = mode
# Сигнал о смене режима для обновления UI
self.metadata_changed.emit(self.get_metadata())
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()