myYouTube/analytics/2026-09-25-subtitles-off-by-default.md
vrubelroman 43adec5224 Fix download status handling and the blank page after download
- Never let a non-authoritative MeTube 'updated' event downgrade a
  terminal job (it refilled the progress bar after completion).
- Self-heal stale active jobs against MeTube history on status polls,
  so a missed event no longer leaves a job stuck in 'queued'.
- Build YT.Player on an imperatively created child div: React keeps
  owning the container, so switching to the local copy after a
  download no longer throws removeChild and blanks the page.
- Autoplay on open and seek controls; subtitle experiment reverted.
2026-09-27 23:36:09 +00:00

77 lines
17 KiB
Markdown
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.

# Субтитры выключены по умолчанию в обоих плеерах (кнопка 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`.