- 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.
17 KiB
Субтитры выключены по умолчанию в обоих плеерах (кнопка CC остаётся)
Статус (2026-09-25): решение ОТМЕНЕНО, правки откатываются. Пользователь отказался от обходных путей ради субтитров: force-off у YouTube без потери кнопки CC не существует, поэтому субтитры пользователь отключит сам в настройках YouTube-аккаунта (Настройки → Воспроизведение и производительность → Субтитры и CC → снять «Всегда показывать субтитры»). Актуальное состояние — в журнале ниже.
Задача
На странице видео субтитры не должны показываться сами по себе: по умолчанию они ВЫКЛЮЧЕНЫ и в локальном <video>, и в YouTube-embed. Кнопка/меню субтитров (CC) остаётся доступной — пользователь может включить дорожку вручную, как и раньше.
Три точки изменения в frontend/src/components/Player.tsx:
- Локальная ветка
<video>: на событииloadedmetadataпройтись поvideo.textTracksи выставитьmode = 'disabled'всем дорожкам (стандартный HTML5 API). Нативная кнопка CC в контролах плеера останется и сможет включить дорожку вручную. - YouTube-ветка (IFrame Player API): добавить
cc_load_policy: 0вplayerVars(существующиеplaysinline: 1, autoplay: 1сохранить). - Фолбэк 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_urlMeTube-прокси.- 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" … />.
- Локальная ветка (строки 251–261):
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 — без изменений.
Критерии приёмки
- Локальный плеер: субтитры не показываются по умолчанию — после
loadedmetadataу всех дорожекvideo.textTracks[*].mode === 'disabled'; проверено на файле со встроенной дорожкой (in-band MP4). - YouTube-embed: субтитры не включаются принудительно —
cc_load_policy: 0присутствует и вplayerVars, и вsrcфолбэк-iframe. - Кнопка CC доступна и работает: в локальном плеере при наличии дорожек нативная кнопка/меню CC в контролах видна и ручной выбор дорожки включает её показ; у YouTube-embed CC-кнопка в UI плеера работает как раньше.
- Существующая логика не сломана: autoplay (обе ветки, включая фолбэк), двойной тап локального плеера с индикатором, кнопки «−10 сек»/«+10 сек» (disabled до
onReadyв YouTube-ветке),onError→переключение на YouTube,onReady-логика (сброс таймаута), фолбэк plain-iframe по таймауту,destroy()в cleanup — работают как раньше. npm run lintиnpm run buildвfrontend/чистые.- Backend не тронут: изменения только в
frontend/src/components/Player.tsxиanalytics/;pytestне требуется. - Деплой по правилу:
docker compose up -d --build,curl http://localhost:8080/api/health→ OK.
План
Player.tsx, локальная ветка (строки 251–261): к<video>добавить React-обработчикonLoadedMetadata, который поe.currentTarget.textTracksвыставляетmode = 'disabled'всем дорожкам, например:
Существующие атрибуты (onLoadedMetadata={(e) => { Array.from(e.currentTarget.textTracks).forEach((track) => { track.mode = 'disabled' }) }}controls,autoPlay,playsInline,src,onError) не трогать.Array.fromпоTextTrackList(iterable) корректен и по TS DOM lib.Player.tsx, YouTube-ветка (строка 181):playerVars: { playsinline: 1, autoplay: 1, cc_load_policy: 0 }(типRecord<string, string | number>— число допустимо).onReady, таймеры,host: youtube-nocookie.comне трогать.Player.tsx, фолбэк (строка 281):src→`https://www.youtube-nocookie.com/embed/${video.youtube_video_id}?autoplay=1&cc_load_policy=0`.title/allow/allowFullScreenне менять.- Проверки:
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); фолбэк-iframesrc=?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.