Видео в вебе, Browser Policy и палки в колёсах

Раньше, когда деревья были большими, а веб был с градиентными кнопочками, на сайтах любили впилить рекламные видео, которые автоматически запускались с громким звуком. И вместо того чтобы приятно провести время или найти нужную информацию, приходилось судорожно искать источник этого звука. 

Это негативно сказывалось на пользовательском опыте, и в какой-то момент разработчики браузеров решили, что хватит это терпеть.

8180d3552a4866cc685be1b1a31a3216.jpg

В результате появилась Autoplay Policy 

Как понятно из названия, это политика автовоспроизведения видео в браузере. Инициатором её введения стал Chrome (с v. 66), и на текущий момент эту политику поддержали все современные браузеры.

Итак, давайте поговорим, что это такое и какие палки и колёса мы можем встретить.

4a240ab7ff86fef403d758887eaa78b5.png

По сути, Autoplay Policy ограничивает автовоспроизведение видео со звуком, но не всегда. Есть ряд условий, при выполнении одного из которых браузер всё же разрешает воспроизвести видео со звуком, а именно:

  1. Пользователь взаимодействует с доменом. Это может быть клик, например по кнопке «Play».

  2. Индекс вовлечённости домена, на котором располагается приложение, превышает пороговое значение.

  3. Пользователь добавляет веб-приложение на главный экран (в случае с PWA).

Итак, воспроизводить видео со звуком всё же можно, но если пользователь совершил какое-то действие в приложении. Или решение о воспроизведении будет принято на основании какого-то индекса вовлечённости. Что это такое?

Превышение порога индекса вовлечённости означает, что:

  1. Пользователь ранее смотрел видео на этом домене со звуком (чем больше, тем лучше).

  2. Продолжительность просмотра пользователем видео превышает n секунд (для Chrome n = 7).

  3. Вкладка открыта в момент попытки воспроизведения.

  4. Видео достаточно большое по размеру (для Chrome — от 200×140 px).

Это понятно, но «Что, если?…». И когда я говорю «Что, если?…», я имею ввиду не эту особу:

fc2cfe8f0683a52f8c2ed69da9c2c6f1.png

и даже не этого парня с громом и молниями:

5cfb0a05a2269379f69b9cf763659252.png

А что, если нам нужно реализовать веб-приложение с лентой видео? И лента бесконечная, видео в ней воспроизводятся автоматически при свайпе пользователя. Такое поведение очень похоже на поведение привычных нам приложений социальных сетей. И вот тут как раз появляются те самые палки и колёса, на которых работает наша лента.

8bb0b32a2ac47c6bbae5fffd70a72de1.jpg

Обычно базовой реализацией является что-то типа:

tryPlay() {
  // метод play возвращает promise
  this.video.play()
    .then(() => {
    	// скрываем кнопку плей, видео удалось воспроизвести
    })
    .catch(() => {
      // не удалось воспроизвести видео

      const retry = !this.video.muted;

      // выключаем звук
      this.video.muted = true;
      this.video.volume = 0;

      // пробуем воспроизвести еще раз
      retry && this.tryPlay();
    });
}

Если внимательно посмотреть на пример реализации, можно заметить, что даже без звука видео может не запуститься. Да, такой сценарий возможен, но об этом позже.

Автоплей не пройдёт

Итак, давайте подумаем, как можно реализовать ленту в условиях Autoplay Policy. Что у нас есть?

  1. n карточек с видео (дальше будем называть их постами).

  2. Видео в каждом посте нужно воспроизводить со звуком при отображении поста на экране.

Одно из отличий от реализации, представленной выше, — отсутствие необходимости запускать видео без звука. Поэтому можно сократить базовую реализацию до такой:

tryPlay() {
  // метод play возвращает promise
  this.video.play()
    .then(() => {
    	// скрываем кнопку плей, видео удалось воспроизвести
    })
    .catch(() => {});
}

Обратите внимание, что лучше не опускать вызов catch, потому что Firefox может выбросить исключение при попытке воспроизведения видео без переданного обработчика ошибок.

С этим вроде бы разобрались. Первым ограничением кажется, что видео нужно воспроизводить при действии пользователя. Это просто: просим пользователя при открытии ленты один раз совершить это действие. Например, показываем ему большую кнопку «Play».

05bf3dc873aa20418f738f264ecf278b.png

После того как пользователь нажмёт на эту кнопку… Внимание!

60201306f508e8ee4883330f3819d9c7.png

Видео воспроизвелось со звуком. И мы радостно реализовываем всю остальную логику, делаем красивый UI, Pixel Perfect по макету, любезно предоставленному талантливыми дизайнерами, прикручиваем свайп к следующим постам. И вроде бы всё классно, но… Решаем проверить свою реализацию на разных устройствах и браузерах. Берём среднестатистический смартфон, воспроизводим первое видео, свайпаем к следующему посту — и… Тадам!

1a0ca4f2c664c9a13b357dbed472979c.png

Видео не воспроизвелось. 

Подключаем шнур к устройству, открываем отладчик — и видим, что браузер такой:

«Слушай, а ты же совершил действие с другим , так не пойдёт, давай ты на этом его повторишь».

Выхода нет?

Что же с этим можно сделать? Ведь постов у нас много, и хотелось бы сохранить привычное для пользователя поведение. Выходов на самом деле несколько. 

Пользовательский опыт? Не, не слышал

Можно пожертвовать пользовательским опытом (что не хорошо) — и на каждом неуспешно воспроизведённом посте рисовать кнопку «Play» и надеяться, что пользователь не забьёт и посмотрит больше двух-трёх видео в ленте. 

Спасти UX полностью (ну почти)

На мой взгляд, есть лучшее решение, которое позволит частично сохранить пользовательский опыт.

Итак, мы знаем: чтобы автоматически воспроизвести видео со звуком, нам требуются как минимум одно действие пользователя и один тег . Но нам никто не запрещал воспроизводить в рамках этого разные ролики, меняя src как нам вздумается.

c205a9546577c8a28db77f7a57818a7b.gif

Всё, что нам нужно, — это вынести из поста, сделать его уникальным на странице и при изменении видимости поста делать что-то типа этого:

changeVideoSrc(src: string) {
  this.video.src = '';
  this.video.load();

  setTimeout(() => {
  	this.video.src = src;
  }, 50);
}

Перед тем как изменить src у video, для некоторых браузеров (например Safari и браузеры под iOS), сначала нужно:

  1. Сбросить текущее видео.

  2. Вызвать метод load, чтобы остановить загрузку текущего видео и сбросить то, что уже загружено.

  3. С небольшой задержкой заменить видео на новое (задержка нужна, чтобы браузер успел сбросить метаинформацию).

В результате всех этих манипуляций мы увидим, что видео стали меняться при свайпе пользователя, не теряя состояние автоплея со звуком.

ff0c21f2ebbd0a4d4eeca3bd7b8bf2a9.png

Но это позволит лишь частично сохранить пользовательский опыт. В чём же нюанс? Так как тег у нас теперь один на странице, то при свайпе постов нам нужно дать пользователю понять, что видео свайпается за его действием. Это позволит дать пользователю ощущение реального перемещения поста. Создать ещё один тег мы не можем, потому что в этом случае потеряем автоплей. Но так ли нам нужно свайпать в посте именно видео? Наверное, если пользователь решил смахнуть текущий пост, то этот контент ему уже неинтересен. Поэтому в базовом варианте можно сделать такое допущение — и в ответ на касание пользователя рисовать в посте обложку, например с первым кадром из видео.

7538b776b7d6450afc383c024ef1444c.gif

Можно прийти к выводу, что задача решена — с неплохо сохранённым пользовательским опытом.

И да, я в самом начале упоминал сценарий, когда браузер не разрешит автоплей видео даже без звука. Такое поведение свойственно iOS в режиме энергосбережения. В этом режиме браузер обязательно потребует действия пользователя — без него воспроизвести видео не получится.

Преисполниться в автоплей можно с помощью этой доки.

© Habrahabr.ru