Files
evening_detective_screen/README.md
T
2026-10-17 19:29:49 +07:00

184 lines
18 KiB
Markdown
Raw 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.
# Вечерний детектив — приложение площадки
Статус: программная часть SCREEN-004 реализована; SCREEN-001 и SCREEN-004
остаются IN_PROGRESS до аппаратной приёмки.
SCREEN-002 и SCREEN-003 завершены: описание пакета через существующий API,
загрузка, проверка, восстановление и локальная выдача файлов.
Управление с телефона идёт через существующий сервер сайта; desktop получает
обновления с сервера. Прямое LAN-подключение не используется.
Цель: ведущий управляет показом на компьютере с macOS, Windows или Linux, а подключённый монитор или
проектор показывает материалы игрокам. Все материалы показа заранее находятся
на диске; локальная команда не ждёт сеть или ответ сервера.
Стек: Electron + Vue + TypeScript + Vite. Первая версия включает шесть сборок:
macOS Intel/Apple Silicon, Windows x64/ARM64 и Linux x64/ARM64.
Поддерживаемые версии ОС и условия проверки описаны в PLAN.md.
Использовать текущие паттерны фронтенда и переиспользовать подходящие компоненты,
не переносить весь сайт в приложение и не выделять общий пакет заранее.
План: [PLAN.md](PLAN.md). Очередь реализации: [TASKS.md](TASKS.md).
Эти документы находятся здесь по прямому указанию владельца — исключение из
общего правила хранения задач в `../docs/agent`. Не дублировать их там.
Идентификаторы `SCREEN-*` относятся только к этому приложению.
Текущий этап: SCREEN-004; программная часть готова, аппаратная проверка открыта.
Новые задачи: `Новая задача: <описание>`.
Commit и push выполняет только пользователь. Вложенные `.git` не изменять.
## Запуск проигрывателя
Нужны Node.js 24.15+ и ffmpeg с libx264 в PATH.
В Linux приложение использует X11; для сессии Wayland нужен XWayland, чтобы
размещать показ на выбранном мониторе.
```sh
npm ci
npx --no-install install-electron --no
npm run media:generate
npm run dev
```
До запуска подключите второй дисплей в режиме расширенного рабочего стола
и назначьте экран компьютера основным. В пульте выберите
его и откройте показ. Аудио и видео — синтетические локальные файлы длительностью
60 секунд; бинарники находятся в игнорируемом fixtures/ и не входят в Git.
Тестовый режим сохраняет отдельный выбор WAV/видео; игровые пакеты выбираются
в разделе «Материалы показа». Полные игровые
режимы, интерфейс загрузки игр и авторизация пока не реализованы.
Управление телефоном через сервер, подключение игры и авторизация относятся к
SCREEN-006 и в техническом стенде ещё не реализованы. Интернет нужен для получения
команд телефона; локальное воспроизведение и пульт компьютера работают без него.
Проверки: npm run type-check, npm run lint, npm run lint:css, npm test, npm run build.
[Сборки и аппаратная приёмка](docs/SCREEN-001_ACCEPTANCE.md).
## Описание пакета SCREEN-002
`electron/screen-package.ts` экспортирует `prepareScreenPackage({ server, gameId,
token, signal? })` для вызова из основного процесса. Метод делает три GET-запроса:
`/api/games/{ID}`, `/api/games/{ID}/screen-plan`, `/api/games/{ID}/music`.
Авторизация — существующий Bearer JWT; токен используется только в запросах API,
не входит в результат. Редиректы API запрещены, ожидание ограничено 30 секундами;
поддерживается отмена через AbortSignal. Ответы с ошибками не выдают частичный пакет.
Интерфейс входа и подключение к пульту остаются в SCREEN-006; сейчас это программный
контракт, новых кнопок в стенде нет. Бэкенд, frontend и protobuf не изменены.
Результат `ScreenPackage` содержит `formatVersion: 1`, `metadataVersion`, адрес
сервера, ID/название игры, ссылки на материалы, шаги плана и связанные плейлисты.
`assets` содержит уникальные URL и их идентификаторы (SHA-256 адреса, не файла).
В `media` и треках используются эти идентификаторы. Фон по умолчанию — обложка.
Плейлисты сценария, игры и текущий выбранный включаются со всеми треками; прочая
библиотека, данные участников и живые команды не сохраняются. Ссылки с секретами
в параметрах и адреса с логином/паролем отклоняются.
`metadataVersion` — SHA-256 нормализованных метаданных. Он не замечает замену
байтов по прежнему URL. Само описание не означает готовность к показу. В SCREEN-003 размер и SHA-256
вычисляются при скачивании, после чего формируется итоговая версия и пакет
активируется атомарно. Внешним медиа нельзя передавать JWT сервера.
Существующий API не предоставляет атомарного снимка или серверных хешей файлов;
права доступа к играм и публичная выдача файлов остаются прежними.
## Локальное хранилище SCREEN-003
Основной процесс открывает `PackageStore` из `electron/package-store.ts` в
`app.getPath('userData')/packages`. Приложение допускает только один экземпляр,
чтобы две копии не изменяли хранилище одновременно. При запуске проверяются
манифесты, размеры и SHA-256 всех файлов; повреждённые пакеты не становятся готовыми.
Программный интерфейс (для основного процесса; подключение к UI — SCREEN-006):
```ts
const store = new PackageStore(packagesDirectory)
await store.initialize()
const description = await prepareScreenPackage({ server, gameId, token })
const ready = await store.download(description, { signal, onProgress })
// Другие операции: list(), cancel(), verify(key), remove(key), localFile(key, assetId).
```
Скачивание последовательно и потоково. Файл синхронизируется с диском и повторно
читается для проверки хеша. При обрыве текущий файл повторяется целиком: максимум
три попытки с паузами 1 и 3 секунды; завершённые файлы текущей попытки не повторяются.
Таймаут бездействия — 30 секунд. Ошибки 408/429/5xx повторяются; ошибки доступа,
неизвестные форматы и отсутствующие файлы останавливают подготовку. Незнакомый
общий размер в прогрессе обозначается `totalBytes: null`; итоговый размер известен
после загрузки. Поддерживаются MIME WAV/OGG, используемые текущим сервером.
Медиа запрашиваются без JWT и cookies. Разрешено до пяти проверенных HTTP(S)
редиректов; запрещены учётные данные в URL, секретные параметры и понижение
HTTPS до HTTP. Контроль места использует statfs перед загрузкой и перед записью
очередного блока, оставляя резерв 64 МиБ; фактическая ошибка ENOSPC также обрабатывается.
Каталог игры определяется хешем адреса сервера и ID игры; пути ресурсов — хешами
URL, независимо от исходного имени файла. Локальный манифест содержит исходное
описание, MIME, размеры и SHA-256 байтов; итоговая версия зависит от метаданных
и проверенных файлов. Секреты в манифест не записываются.
Новая версия готовится в `staging-*`, затем помещается в `v-<version>`. Указатель
`current.json` заменяется через временный файл и rename только после проверки.
Отмена и ошибки до переключения не затрагивают прежний готовый пакет. После
успеха прежняя версия удаляется; заблокированные ОС остатки повторно очищаются
при следующем запуске. Сбой после переключения не отменяет новую готовую версию.
Повреждённую текущую копию можно восстановить повторной загрузкой или удалить.
Симлинки при чтении не обходятся; удаление ограничено каталогом выбранной игры.
`screen-app://app/packages/<key>/<assetId>` выдаёт только ресурсы готового пакета,
с правильным MIME, запретом кеширования и поддержкой Range. Произвольные пути
и сетевые URL в протокол не передаются. Воспроизведение игровых пакетов подключено
в SCREEN-004; fixture-файлы доступны отдельно в тестовом режиме.
`npm test` включает настоящий временный HTTP-сервер: после скачивания сервер
отключается, новое хранилище проверяет пакет, локальная выдача читает все ресурсы.
`npm run smoke -- --no-sandbox --mute-audio --disable-gpu` дополнительно готовит
пакет с WAV, H.264 и двумя музыкальными треками в отдельном временном userData,
открывает его повторно через основной процесс и проверяет пульт, Range, команды
и переходы плейлиста. Эти флаги используются только в тесте.
Серверных эталонных хешей нет: локальная проверка подтверждает целостность
скачанной копии, но не неизменность источников во время подготовки.
## Локальный проигрыватель SCREEN-004
На пульте выберите готовый пакет или «Тестовый режим SCREEN-001», внешний дисплей
и откройте показ. В основном канале доступны вступление и заключительное видео;
их выбор останавливает прежнее основное медиа. Музыка воспроизводится отдельным
каналом без автоматического приглушения. Звук выводится только окном показа.
Начальная громкость каждого канала — 35%; общий mute не меняет сохранённые уровни.
«Пуск с начала» и «Повторить с начала» запускают выбранный ресурс с нуля,
«Пауза» сохраняет фактическую позицию, «Продолжить» возобновляет её, «Стоп»
сбрасывает позицию. Перемотка сохраняет паузу/воспроизведение и ограничена реальной
длительностью файла. Громкость не вызывает перемотку или повторный пуск.
Следующий трек сохраняет состояние воспроизведения или паузы; по окончании трека
переход выполняется локально. Повтор относится ко всему плейлисту; без повтора
последний трек завершает воспроизведение. Смена плейлиста останавливает музыку.
Основной процесс хранит каталог разрешённых локальных ресурсов и состояния
`main`/`music`; renderer получает только адреса screen-app. Команды IPC содержат
сессию и возрастающий sequence; повторные/устаревшие номера игнорируются.
Подтверждения содержат сессию, канал и operation, чтобы запоздавшие события
старого ресурса не меняли новый. Позиция обновляется по событиям медиа; подготовка,
успешный play и ошибки подтверждаются проигрывателем. Подготовлены выбранное
основное медиа и один следующий трек, без чтения всех видео в JavaScript.
`screenBridge.packages()` и `selectPackage(key)` доступны только пульту;
`selectPackage(null)` включает тестовый режим. Выбор повторно проверяет пакет.
Закрытие или пересоздание окна и смена пакета создают новую сессию и останавливают
каналы; повторное открытие не возобновляет звук. После перезапуска всего приложения
выбор начинается с тестового режима; сохранение выбора относится к SCREEN-007.
Для ручной проверки с настоящим внешним дисплеем выполните `npm run acceptance`.
Команда использует синтетические fixtures и отдельный временный userData, создаёт
локальный пакет с двумя тональными музыкальными треками и оставляет пульт открытым
без автоматических команд. Подготовка не требует сети после установки зависимостей.
При отсутствии внешнего дисплея команда завершится с сообщением, синтетический
монитор в ручном режиме не подставляется. Встроенная аппаратная инструкция:
[SCREEN-004_ACCEPTANCE.md](docs/SCREEN-004_ACCEPTANCE.md).
Проверки test и smoke запускайте последовательно: обе команды пересобирают
`dist-electron`, из которого Electron загружает preload при открытии окна.
Программные замеры в пульте разделены по каналу и текущему виду медиа; они
не измеряют физический звук/первый кадр. Аппаратная приёмка, остальные ОС,
реальные игровые материалы и p95 на M1 пока не подтверждены. Полные визуальные
режимы остаются в SCREEN-005, авторизация и получение серверных команд — в SCREEN-006.