myYouTube/analytics/2026-09-25-video-autoplay.md
vrubelroman b1faeb3729 Autoplay video when opening the video page
Add autoPlay/playsInline to the local video element, autoplay: 1 to
the YouTube IFrame API playerVars, and ?autoplay=1 to the plain-iframe
fallback. Best-effort: browsers may block unmuted autoplay, so the
play button stays as fallback.
2026-09-25 12:06:11 +00:00

10 KiB
Raw Permalink Blame History

Автозапуск видео при переходе на страницу видео (best-effort, без muted)

Задача

При переходе на страницу видео (YouTube-ветка или локальная копия) плеер должен пытаться начать воспроизведение сразу, без ручного нажатия play. Реализация — best-effort, без костылей: звук не мутируем, поэтому там, где браузер разрешает автозапуск со звуком, видео стартует само; где запрещает — остаётся обычная кнопка play, поведение не хуже текущего.

Три точки изменения в frontend/src/components/Player.tsx:

  1. Локальная ветка <video controls>: добавить атрибут autoPlay (+ playsInline для iOS — проверить, что его ещё нет).
  2. YouTube-ветка (IFrame Player API): добавить autoplay: 1 в playerVars (существующий playsinline: 1 сохранить).
  3. Фолбэк plain-iframe: добавить параметр ?autoplay=1 к src.

Контекст

  • Текущее состояние Player.tsx (298 строк, последний коммит «Add seek controls to the player»):
    • Локальная ветка (строки 247–272): <video ref={videoRef} controls src={video.local.media_url!} onError={…} /> — атрибутов autoPlay/playsInline/muted нет (grep по frontend/src: muted не встречается, playsinline — только в playerVars, строка 181).
    • YouTube-ветка (строки 178–192): new YT.Player(host, { videoId, host: 'https://www.youtube-nocookie.com', playerVars: { playsinline: 1 }, events: { onReady } }) — autoplay отсутствует.
    • Фолбэк при таймауте API (строки 277–283): <iframe src="https://www.youtube-nocookie.com/embed/{id}" title allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen /> — в src параметров нет; allow уже содержит autoplay (Permissions Policy для iframe, менять не нужно).
  • Player монтируется только в VideoPage.tsx:24 с key={video.youtube_video_id}: при переходе на страницу видео компонент монтируется заново, смена видео — ремоунт. Отдельной логики «перезапуска при навигации» не требуется: автозапуск срабатывает на каждый свежий монтаж.
  • Локальная ветка выбирается по video.local.available && !!video.local.media_url && !localFailed; при onError локального видео — переход в YouTube-ветку. Автозапуск добавляется в обе ветки независимо, существующая логика выбора ветки не меняется.
  • Frontend без тестовой инфраструктуры; проверки — npm run lint (oxlint) и npm run build (tsc -b && vite build). Backend, миграции, README — не трогаем.

Затронутые подсистемы и файлы

Только frontend, один файл:

  • frontend/src/components/Player.tsx:
    • локальная ветка: <video> — добавить autoPlay и playsInline;
    • YouTube-ветка: playerVars: { playsinline: 1 } → playerVars: { playsinline: 1, autoplay: 1 };
    • фолбэк-iframe: src → https://www.youtube-nocookie.com/embed/{id}?autoplay=1.

App.css, VideoPage.tsx, backend, тесты, миграции, README — без изменений.

Критерии приёмки

  1. Автозапуск настроен на обеих ветках и в фолбэке: autoPlay/playsInline на <video>, autoplay: 1 в playerVars, ?autoplay=1 в src фолбэка.
  2. Звук не мутируется: muted не добавляется нигде (<video>, playerVars, параметры iframe); best-effort — где браузер разрешит автозапуск со звуком, видео стартует, где нет — остаётся кнопка play.
  3. Существующая логика не сломана: кнопки «−10 сек»/«+10 сек» (обе ветки, disabled до onReady в YouTube-ветке), двойной тап локального плеера, onReady-логика (сброс таймаута, setYtApiStatus('ready')), фолбэк plain-iframe по таймауту, destroy() в cleanup — работают как раньше.
  4. npm run lint и npm run build в frontend/ чистые.
  5. Backend не тронут: изменения только в frontend/src/components/Player.tsx и analytics/; pytest не требуется.
  6. Деплой по правилу: docker compose up -d --build, curl http://localhost:8080/api/health → OK.

План

  1. Player.tsx, локальная ветка (строки 251–259): к <video> добавить атрибуты autoPlay и playsInline (React camelCase) — рядом с controls, до src. Больше ничего в этой ветке не менять.
  2. Player.tsx, YouTube-ветка (строка 181): playerVars: { playsinline: 1 } → playerVars: { playsinline: 1, autoplay: 1 } (тип Record<string, string | number> — число допустимо). onReady, таймеры, host: youtube-nocookie.com не трогать.
  3. Player.tsx, фолбэк (строка 279): src → `https://www.youtube-nocookie.com/embed/${video.youtube_video_id}?autoplay=1`. title/allow/allowFullScreen не менять (allow уже содержит autoplay).
  4. Проверки: npm run lint, npm run build; ручная — переход на страницу локального видео и YouTube-видео в браузерах с разной политикой автозапуска (Chrome с/без истории взаимодействия, Safari/iOS, Firefox): где разрешено — старт без нажатия, где запрещено — кнопка play; проверить кнопки ±10 сек, двойной тап, фолбэк (offline/блокировка youtube.com в DevTools); деплой + health.

Риски и ограничения

  • Политика автозапуска браузеров (главный риск, принят осознанно): автозапуск со звуком блокируется по умолчанию в Chrome (требует Media Engagement Index / недавнее взаимодействие пользователя с сайтом), Safari и практически всегда на iOS/iPadOS, где автозапуск возможен только с muted. Мы намеренно не мутим (пользователю нужен звук), поэтому результат — best-effort: где браузер разрешит — стартует, где нет — остаётся кнопка play. Это ожидаемое и принятое поведение, а не баг.
  • iOS: даже с playsInline автозапуск со звуком будет заблокирован; playsInline добавляем, чтобы не было попытки fullscreen и чтобы воспроизведение после нажатия play шло инлайн, как сейчас у YouTube-ветки (playsinline: 1).
  • YouTube embed: autoplay: 1 без mute: 1 часто блокируется без предшествующего взаимодействия с доменом YouTube; в фолбэке plain-iframe политика та же. mute: 1 сознательно не добавляем (критерий 2).
  • Консоль: при заблокированном автозапуске браузер может выводить предупреждение (для <video autoplay> — заметка о блокировке, иногда rejected play promise в DevTools) — это штатное поведение best-effort, не ошибка приложения; новых unhandled rejection в коде приложения нет (мы не вызываем play() вручную).
  • Не трогать существующую логику: playerVars меняется одной строкой; onReady/таймаут/cleanup и рендер фолбэка не затрагиваются. При рефакторинге не добавлять muted и не менять порядок/содержимое allow.
  • Ремоунт по key: автозапуск происходит при каждом монтировании Player (переход на страницу, смена видео) — это ожидаемо; при возврате со страницы видео и повторном заходе видео начнётся заново с начала, как и сейчас при обычном play.

Журнал изменений

  • 2026-09-25: документ создан перед реализацией. Зафиксированы факты по коду: <video controls> без autoPlay/playsInline/muted (Player.tsx:251–259); playerVars: { playsinline: 1 } без autoplay (строка 181); фолбэк-iframe src без параметров (строка 279), allow уже содержит autoplay; Player монтируется только в VideoPage.tsx:24 с key. Решения: autoPlay + playsInline на <video>; autoplay: 1 в playerVars (сохранить playsinline: 1); ?autoplay=1 в src фолбэка; muted не добавлять нигде — best-effort со звуком, блокировка браузерами (особенно iOS) принята и описана в «Рисках». README не трогаем; backend не трогаем.