myYouTube/analytics/2026-09-25-subtitles-off-by-default.md

78 lines
17 KiB
Markdown
Raw Normal View History

# Субтитры выключены по умолчанию в обоих плеерах (кнопка CC остаётся)
> **Статус (2026-09-25): решение ОТМЕНЕНО, правки откатываются.** Пользователь отказался от обходных путей ради субтитров: force-off у YouTube без потери кнопки CC не существует, поэтому субтитры пользователь отключит сам в настройках YouTube-аккаунта (Настройки → Воспроизведение и производительность → Субтитры и CC → снять «Всегда показывать субтитры»). Актуальное состояние — в журнале ниже.
## Задача
На странице видео субтитры не должны показываться сами по себе: по умолчанию они ВЫКЛЮЧЕНЫ и в локальном `<video>`, и в YouTube-embed. Кнопка/меню субтитров (CC) остаётся доступной — пользователь может включить дорожку вручную, как и раньше.
Три точки изменения в `frontend/src/components/Player.tsx`:
1. Локальная ветка `<video>`: на событии `loadedmetadata` пройтись по `video.textTracks` и выставить `mode = 'disabled'` всем дорожкам (стандартный HTML5 API). Нативная кнопка CC в контролах плеера останется и сможет включить дорожку вручную.
2. YouTube-ветка (IFrame Player API): добавить `cc_load_policy: 0` в `playerVars` (существующие `playsinline: 1, autoplay: 1` сохранить).
3. Фолбэк plain-iframe: параметр `cc_load_policy=0` в `src` (аналог).
## Контекст
- Текущее состояние `Player.tsx` (300 строк, последний коммит `b1faeb3` «Autoplay video when opening the video page»; рабочая ветка `master`, дерево чистое):
- Локальная ветка (строки 251–261): `<video ref={videoRef} controls autoPlay playsInline src={video.local.media_url!} onError={…} />` — обработчика `loadedmetadata` и какой-либо работы с `textTracks` **нет** (grep по `frontend/src`: `textTracks`, `loadedmetadata`, `cc_load_policy` не встречаются).
- `<track>`-элементов в JSX нет и backend `.vtt`/субтитры не отдаёт (grep по `*.py` пуст): в локальном плеере дорожки возможны только **встроенные в медиаконтейнер** (in-band, например MP4), отданные через `media_url` MeTube-прокси.
- YouTube-ветка (строки 178–193): `new YT.Player(host, { videoId, host: 'https://www.youtube-nocookie.com', playerVars: { playsinline: 1, autoplay: 1 }, events: { onReady } })` — `cc_load_policy` отсутствует.
- Фолбэк при таймауте API (строки 279–285): `<iframe src="https://www.youtube-nocookie.com/embed/{id}?autoplay=1" … />`.
- `Player` монтируется только в `VideoPage.tsx:24` с `key={video.youtube_video_id}`: смена видео — ремоунт, обработчик `loadedmetadata` будет срабатывать на каждый свежий монтаж, отдельной навигационной логики не требуется.
- **HTML5 API (факт для приёмки):** у `TextTrack.mode` три значения — `'disabled'` / `'hidden'` / `'showing'`. По WHATWG HTML дорожка `<track>` без атрибута `default` стартует с `mode='disabled'`, однако у in-band дорожек браузеры могут включить показ по флагам контейнера (forced) или по пользовательским настройкам («always show captions» в Chrome/системных настройках субтитров) — поэтому явный проход с `mode='disabled'` на `loadedmetadata` является защитной мерой, а не пустым действием. Присваивание `mode` у in-band дорожки легально (свойство writable); дорожки `kind='metadata'` не отображаются в любом случае, установка им `mode='disabled'` безвредна.
- **Нативная CC-кнопка (факт для приёмки):** в нативных контролах браузера кнопка/меню субтитров отображается, когда `video.textTracks.length > 0`. Установка `mode='disabled'` не удаляет дорожки из `TextTrackList` и не прячет кнопку: выбор дорожки из меню ставит `mode='showing'`. Требуется ручная проверка в реальном браузере (Chrome/Firefox) — зафиксирована в критериях.
- **YouTube `cc_load_policy` (факт для рисков):** по документации IFrame API значение `1` принудительно включает субтитры; `0`/отсутствие параметра — «по предпочтению пользователя». **Force-off у YouTube не существует**: `0` означает «не включать принудительно», но если у пользователя субтитры включены в настройках YouTube (embed хранит предпочтение в cookie), они могут показаться и с `0`.
- Frontend без тестовой инфраструктуры; проверки — `npm run lint` (oxlint) и `npm run build` (`tsc -b && vite build`). Backend, миграции — не трогаем.
## Затронутые подсистемы и файлы
Только frontend, один файл:
- `frontend/src/components/Player.tsx`:
- локальная ветка: на `<video>` добавить `onLoadedMetadata` — обойти `textTracks`, всем `mode = 'disabled'`;
- YouTube-ветка: `playerVars: { playsinline: 1, autoplay: 1 }` → `{ playsinline: 1, autoplay: 1, cc_load_policy: 0 }`;
- фолбэк-iframe: `src` → `https://www.youtube-nocookie.com/embed/{id}?autoplay=1&cc_load_policy=0`.
`App.css`, `VideoPage.tsx`, backend, тесты, миграции, README — без изменений.
## Критерии приёмки
1. Локальный плеер: субтитры не показываются по умолчанию — после `loadedmetadata` у всех дорожек `video.textTracks[*].mode === 'disabled'`; проверено на файле со **встроенной** дорожкой (in-band MP4).
2. YouTube-embed: субтитры не включаются принудительно — `cc_load_policy: 0` присутствует и в `playerVars`, и в `src` фолбэк-iframe.
3. Кнопка CC доступна и работает: в локальном плеере при наличии дорожек нативная кнопка/меню CC в контролах видна и ручной выбор дорожки включает её показ; у YouTube-embed CC-кнопка в UI плеера работает как раньше.
4. Существующая логика не сломана: autoplay (обе ветки, включая фолбэк), двойной тап локального плеера с индикатором, кнопки «−10 сек»/«+10 сек» (disabled до `onReady` в YouTube-ветке), `onError`→переключение на YouTube, `onReady`-логика (сброс таймаута), фолбэк plain-iframe по таймауту, `destroy()` в cleanup — работают как раньше.
5. `npm run lint` и `npm run build` в `frontend/` чистые.
6. Backend не тронут: изменения только в `frontend/src/components/Player.tsx` и `analytics/`; `pytest` не требуется.
7. Деплой по правилу: `docker compose up -d --build`, `curl http://localhost:8080/api/health` → OK.
## План
1. `Player.tsx`, локальная ветка (строки 251–261): к `<video>` добавить React-обработчик `onLoadedMetadata`, который по `e.currentTarget.textTracks` выставляет `mode = 'disabled'` всем дорожкам, например:
```tsx
onLoadedMetadata={(e) => {
Array.from(e.currentTarget.textTracks).forEach((track) => {
track.mode = 'disabled'
})
}}
```
Существующие атрибуты (`controls`, `autoPlay`, `playsInline`, `src`, `onError`) не трогать. `Array.from` по `TextTrackList` (iterable) корректен и по TS DOM lib.
2. `Player.tsx`, YouTube-ветка (строка 181): `playerVars: { playsinline: 1, autoplay: 1, cc_load_policy: 0 }` (тип `Record<string, string | number>` — число допустимо). `onReady`, таймеры, `host: youtube-nocookie.com` не трогать.
3. `Player.tsx`, фолбэк (строка 281): `src` → `` `https://www.youtube-nocookie.com/embed/${video.youtube_video_id}?autoplay=1&cc_load_policy=0` ``. `title`/`allow`/`allowFullScreen` не менять.
4. Проверки: `npm run lint`, `npm run build`; ручная — локальное видео со встроенной дорожкой (дорожки не показываются, CC-кнопка есть и включает), локальное видео без дорожек (поведение не изменилось), YouTube-видео (субтитры не включаются сами; автозапуск, кнопки ±10 сек, фолбэк при блокировке youtube.com в DevTools); деплой + health.
## Риски и ограничения
- **У YouTube нет force-off (главный риск, принят осознанно):** `cc_load_policy: 0` — это «не включать принудительно», поведение равно дефолту и учитывает предпочтение пользователя. Если у пользователя субтитры включены в настройках YouTube (embed-контекст хранит выбор в cookie), они могут показаться несмотря на `0`. Это ограничение YouTube, а не баг; явное `0` фиксирует намерение и защищает от случайного перехода на `1` в будущем.
- **In-band дорожки и момент появления:** обычно `textTracks` заполнены уже к `loadedmetadata`; в редких случаях браузер добавляет дорожки позже (событие `addtrack`). Приёмка включает проверку на реальном файле с дорожкой; если окажется, что дорожки появляются после `loadedmetadata`, усилить обработчиком `addtrack` на `video.textTracks` (превентивно не делать — держим минимальный дифф).
- **CC-кнопка появляется только при наличии дорожек:** если в медиафайле дорожек нет, нативной кнопки CC нет — это ожидаемо (нечего показывать), а не регресс. При наличии дорожек `mode='disabled'` кнопку не прячет.
- **Браузерные/системные настройки пользователя:** Chrome/Edge «always show captions», системные субтитры Windows/macOS могут заставлять браузер показывать дорожки даже после нашей установки `'disabled'`; это вне контроля приложения и относится к пользовательским предпочтениям, а не к дефолту плеера.
- **Не трогать существующую логику:** изменение аддитивное — один обработчик на `<video>`, один ключ в `playerVars`, один query-параметр в `src`; `onError`/recheck, таймауты, cleanup, рендер фолбэка не затрагиваются.
- **Ремоунт по `key`:** `onLoadedMetadata` срабатывает при каждом монтировании `Player` (переход на страницу, смена видео) — это ожидаемо и идемпотентно.
## Журнал изменений
- 2026-09-25: документ создан перед реализацией. Зафиксированы факты по коду: `<video controls autoPlay playsInline>` без `onLoadedMetadata` и без работы с `textTracks` (Player.tsx:251–261); `<track>` в JSX нет, backend `.vtt` не отдаёт — локальные дорожки только in-band через `media_url`; `playerVars: { playsinline: 1, autoplay: 1 }` без `cc_load_policy` (строка 181); фолбэк-iframe `src` = `?autoplay=1` (строки 279–285); `Player` монтируется только в `VideoPage.tsx:24` с `key`. Решения: `onLoadedMetadata` с `mode='disabled'` по всем `textTracks` (нативная CC-кнопка остаётся, ручное включение работает — проверяется в приёмке); `cc_load_policy: 0` в `playerVars` и `&cc_load_policy=0` в `src` фолбэка; отсутствие force-off у YouTube зафиксировано в «Рисках». README не обновляется (описывает плеер на уровне «YouTube-плеер», README.md:10, без деталей опций); backend не трогаем.
- 2026-09-25 (follow-up): подтвердились оба риска из «Рисков» — `cc_load_policy: 0` не отключает субтитры при включённом пользовательском предпочтении (force-off не существует), и in-band дорожки могут появляться после `loadedmetadata`. Продолжение зафиксировано как Баг 3 в `analytics/2026-09-25-download-status-fixes.md`: сброс `unloadModule('captions')` + `loadModule('captions')` через IFrame API после `onReady` (YouTube) и обработчик `addtrack` на `textTracks`, отключающий новые дорожки (локальное видео). Реализация выполняется в рамках той задачи; этот документ не переписывается.
- 2026-09-25 (ОТКАТ): **решение отменено пользователем.** Обходные пути ради субтитров больше не делаем — все правки этой задачи откатываются в `frontend/src/components/Player.tsx`: убраны `cc_load_policy: 0` из `playerVars` и `cc_load_policy=0` из `src` фолбэк-iframe, вызовы `unloadModule('captions')`/`loadModule('captions')` в `onReady` и соответствующие методы `loadModule`/`unloadModule` из интерфейса `YoutubePlayerApi`, `onLoadedMetadata` с переводом `textTracks` в `mode='disabled'`, отдельный `useEffect` с обработчиком `addtrack`. Причина: у YouTube нет force-off субтитров без потери кнопки CC (подтверждено практикой реализации) — пользователь сам отключит субтитры в настройках YouTube-аккаунта (Настройки → Воспроизведение и производительность → Субтитры и CC → снять «Всегда показывать субтитры»). Незатронутые правки сохраняются: autoplay-атрибуты обеих веток, императивный дочерний div (фикс чёрной страницы), кнопки ±10 сек, двойной тап с индикатором, таймаут-фолбэк, cleanup. Баг 3 в `analytics/2026-09-25-download-status-fixes.md` откатывается вместе с этой задачей; Баги 1 и 2 там остаются в силе. Критерий отката: в `Player.tsx` не остаётся ни одного упоминания `captions`/`cc_load_policy`/`textTracks`/`addtrack`.