Описание
При отключении монитора или окна захвата во время записи происходит потеря захвата. Нет возможности переключиться на другое устройство без остановки записи. Пользователь вынужден прерывать запись.
Текущее поведение
Запись идёт → Окно закрыто → Capture lost → Захват прерван → ???
Result: Потеря захвата без альтернативы
Ожидаемое поведение
Запись идёт → Окно закрыто → Детекция → Буфер → Пользователь выбирает источник → Продолжение
Result: Непрерывная запись с переключением источника
Технические требования
1. Архитектура горячего переключения
from abc import ABC, abstractmethod
from typing import Protocol
class CaptureSource(ABC):
"""Абстракция источника захвата."""
@abstractmethod
def is_available(self) -> bool:
"""Проверяет доступность источника."""
...
@abstractmethod
def get_info(self) -> CaptureSourceInfo:
"""Информация об источнике."""
...
@abstractmethod
def start_capture(self, on_frame: Callable[[np.ndarray], None]) -> None:
"""Начинает захват с источника."""
...
@abstractmethod
def stop_capture(self) -> None:
"""Останавливает захват."""
...
@dataclass
class CaptureSourceInfo:
type: str # "monitor", "window", "rect"
id: str
name: str
width: int
height: int
2. Hot-swap менеджер
class CaptureHotSwapManager:
"""Управляет горячим переключением источников захвата."""
def __init__(self, event_bus: EventBus):
self._event_bus = event_bus
self._current_source: CaptureSource | None = None
self._backup_sources: list[CaptureSource] = []
self._frame_buffer: deque[np.ndarray] = deque(maxlen=900) # 30 сек
def register_backup_source(self, source: CaptureSource) -> None:
"""Регистрирует резервный источник."""
self._backup_sources.append(source)
def on_capture_lost(self, reason: str) -> None:
"""Обработчик потери захвата."""
# 1. Уведомить UI
self._event_bus.publish(RecordingEvent(
event_type=RecordingEventType.CAPTURE_SOURCE_LOST,
payload={"reason": reason, "available_sources": self._get_available_sources()}
))
# 2. Попытаться автоматическое переключение
if self._try_auto_switch():
return
# 3. Перейти в режим ожидания выбора пользователя
self._enter_await_mode()
def switch_to_source(self, source_id: str) -> bool:
"""Переключиться на указанный источник."""
source = self._find_source_by_id(source_id)
if source is None or not source.is_available():
return False
# Сохраняем текущий контекст
current_source = self._current_source
try:
# Создаём новую сессию
self._current_source = source
self._current_source.start_capture(self._on_frame)
# Останавливаем старую
if current_source:
current_source.stop_capture()
# Уведомляем о переключении
self._event_bus.publish(RecordingEvent(
event_type=RecordingEventType.CAPTURE_SOURCE_SWITCHED,
payload={"new_source": source.get_info()}
))
return True
except Exception as e:
# Rollback
self._current_source = current_source
if current_source:
current_source.start_capture(self._on_frame)
return False
3. API endpoints
# Получение доступных источников
GET /api/v1/resources/capture-sources
Response: {
"sources": [
{
"id": "monitor-1",
"type": "monitor",
"name": "Primary Monitor",
"width": 1920,
"height": 1080,
"available": true
},
{
"id": "window-Notepad",
"type": "window",
"name": "Notepad",
"width": 800,
"height": 600,
"available": true
}
],
"current": "monitor-1"
}
# Переключение источника
POST /api/v1/recording/switch-source
Body: {"source_id": "monitor-2"}
Response: {"success": true, "new_source": {...}}
4. UI интеграция
class CaptureSourceSelector(QWidget):
"""Виджет выбора источника захвата."""
source_changed = pyqtSignal(str) # source_id
def update_sources(self, sources: list[CaptureSourceInfo], current: str) -> None:
"""Обновляет список доступных источников."""
self._sources = sources
self._current = current
# Обновить combobox
self.ui.source_combo.clear()
for source in sources:
icon = self._get_icon(source.type)
text = f"{source.name} ({source.width}x{source.height})"
self.ui.source_combo.addItem(icon, text, source.id)
if source.id == current:
self.ui.source_combo.setCurrentIndex(
self.ui.source_combo.count() - 1
)
# Показать предупреждение о переключении
if current not in [s.id for s in sources if s.available]:
self._show_switch_warning()
5. Обработка событий
# Event Bus
class RecordingEventType(StrEnum):
CAPTURE_SOURCE_LOST = "capture_source_lost"
CAPTURE_SOURCE_SWITCHED = "capture_source_switched"
CAPTURE_SOURCE_AWAITING = "capture_source_awaiting"
Критерии приёмки
Файлы для изменения
recorder/video_recorder.py — поддержка hot-swap
recorder/capture_source.py — абстракция источника
core/event_bus.py — новые события
api/routes_recording.py — endpoints переключения
gui/views/capture_view.py — UI выбора источника
tests/unit/ — тесты hot-swap
Приоритет: P2
Тэги: reliability, enhancement
Описание
При отключении монитора или окна захвата во время записи происходит потеря захвата. Нет возможности переключиться на другое устройство без остановки записи. Пользователь вынужден прерывать запись.
Текущее поведение
Ожидаемое поведение
Технические требования
1. Архитектура горячего переключения
2. Hot-swap менеджер
3. API endpoints
4. UI интеграция
5. Обработка событий
Критерии приёмки
CaptureHotSwapManagerФайлы для изменения
recorder/video_recorder.py— поддержка hot-swaprecorder/capture_source.py— абстракция источникаcore/event_bus.py— новые событияapi/routes_recording.py— endpoints переключенияgui/views/capture_view.py— UI выбора источникаtests/unit/— тесты hot-swapПриоритет: P2
Тэги:
reliability,enhancement