import pychromecast from pychromecast.controllers.media import MediaController import time import threading from typing import List, Optional from .utils import get_logger logger = get_logger(__name__) class CastManager: def __init__(self): self.chromecast = None self.mc: Optional[MediaController] = None self.queue: List[str] = [] self.current_index = -1 self.is_playing = False self._lock = threading.Lock() self._discover_thread = None self.browser = None def discover(self, timeout=5): logger.info("Discovering Chromecasts...") chromecasts, browser = pychromecast.get_chromecasts(timeout=timeout) self.browser = browser if chromecasts: # For simplicity, pick the first one found or allow selection later # In a real scenario, we might want to target a specific name self.chromecast = chromecasts[0] logger.info(f"Found Chromecast: {self.chromecast.name}") self.chromecast.wait() self.mc = self.chromecast.media_controller self.mc.register_status_listener(self) else: logger.error("No Chromecasts found.") def play_queue(self, urls: List[str]): with self._lock: self.queue = urls self.current_index = 0 self._play_current() def _play_current(self): if 0 <= self.current_index < len(self.queue): url = self.queue[self.current_index] logger.info(f"Playing {self.current_index + 1}/{len(self.queue)}: {url}") if not self.chromecast: self.discover() if self.chromecast: self.chromecast.wait() # Use generic video type, or try to guess from URL self.mc.play_media(url, 'video/mp4') self.is_playing = True else: logger.error("Cannot play: No Chromecast connected.") else: logger.info("Queue finished.") self.is_playing = False self.current_index = -1 def stop(self): with self._lock: if self.mc: self.mc.stop() self.queue = [] self.current_index = -1 self.is_playing = False def new_media_status(self, status): """Callback from pychromecast when media status changes.""" logger.debug(f"Media status update: {status.player_state}") # Check if the current video finished if status.player_state == "IDLE" and status.idle_reason == "FINISHED": logger.info("Current track finished. Moving to next...") with self._lock: if self.current_index != -1: self.current_index += 1 # Small delay to ensure state transitions threading.Timer(1.0, self._play_current).start() def get_status(self): status = { "is_playing": self.is_playing, "queue_len": len(self.queue), "current_index": self.current_index, } if self.mc and self.mc.status: status.update({ "player_state": self.mc.status.player_state, "current_time": self.mc.status.current_time, "duration": self.mc.status.duration, }) return status # Singleton instance cast_manager = CastManager()