Как создать лучший опыт мобильных медиа в Интернете? Легко! Все зависит от вовлеченности пользователя и важности, которую вы придаете медиа на веб-странице. Я думаю, мы все согласимся, что если видео — это ГЛАВНАЯ причина посещения пользователя, то пользовательский опыт должен быть захватывающим и повторно вовлекающим.

В этой статье я покажу вам, как прогрессивно улучшить ваш медиа-опыт и сделать его более захватывающим благодаря множеству веб-API. Вот почему мы собираемся создать простой мобильный плеер с пользовательскими элементами управления, полноэкранным режимом и фоновым воспроизведением. Вы можете попробовать пример сейчас и найти код в нашем репозитории GitHub.
Пользовательские элементы управления

Как видите, HTML-макет, который мы собираемся использовать для нашего медиаплеера, довольно прост: корневой элемент <div>
содержит медиаэлемент <video>
и дочерний элемент <div>
, предназначенный для управления видео.
Элементы управления видео, которые мы рассмотрим позже, включают в себя: кнопку воспроизведения/паузы, кнопку полноэкранного режима, кнопки перемотки назад и вперед, а также некоторые элементы для текущего времени, продолжительности и отслеживания времени.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls"></div>
</div>
Прочитать метаданные видео
Сначала давайте дождемся загрузки метаданных видео, чтобы установить длительность видео, текущее время и инициализировать индикатор выполнения. Обратите внимание, что функция secondsToTimeCode()
— это написанная мной пользовательская служебная функция, которая преобразует количество секунд в строку в формате "чч:мм:сс", который лучше подходит в нашем случае.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</strong>
</div>
</div>
video.addEventListener('loadedmetadata', function () {
videoDuration.textContent = secondsToTimeCode(video.duration);
videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
videoProgressBar.style.transform = `scaleX(${
video.currentTime / video.duration
})`;
});

Воспроизвести/приостановить видео
Теперь, когда метаданные видео загружены, давайте добавим нашу первую кнопку, которая позволит пользователю воспроизводить и приостанавливать видео с помощью video.play()
и video.pause()
в зависимости от состояния воспроизведения.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<strong><button id="playPauseButton"></button></strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</div>
</div>
playPauseButton.addEventListener('click', function (event) {
event.stopPropagation();
if (video.paused) {
video.play();
} else {
video.pause();
}
});
Вместо того, чтобы настраивать элементы управления видео в прослушивателе событий click
, мы используем события play
и pause
видео. Создание элементов управления на основе событий помогает с гибкостью (как мы увидим позже с API сеанса мультимедиа) и позволит нам синхронизировать элементы управления, если браузер вмешивается в воспроизведение. Когда видео начинает воспроизводиться, мы меняем состояние кнопки на «пауза» и скрываем элементы управления видео. Когда видео приостанавливается, мы просто меняем состояние кнопки на «воспроизведение» и показываем элементы управления видео.
video.addEventListener('play', function () {
playPauseButton.classList.add('playing');
});
video.addEventListener('pause', function () {
playPauseButton.classList.remove('playing');
});
Когда время, указанное атрибутом video currentTime
изменяется с помощью события video timeupdate
, мы также обновляем наши пользовательские элементы управления, если они видны.
video.addEventListener('timeupdate', function () {
if (videoControls.classList.contains('visible')) {
videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
videoProgressBar.style.transform = `scaleX(${
video.currentTime / video.duration
})`;
}
});
Когда видео заканчивается, мы просто меняем состояние кнопки на «воспроизведение», устанавливаем currentTime
видео обратно на 0 и показываем элементы управления видео на данный момент. Обратите внимание, что мы также могли бы выбрать автоматическую загрузку другого видео, если пользователь включил какую-либо функцию «Автовоспроизведение».
video.addEventListener('ended', function () {
playPauseButton.classList.remove('playing');
video.currentTime = 0;
});
Поиск назад и вперед
Давайте продолжим и добавим кнопки «перемотка назад» и «перемотка вперед», чтобы пользователь мог легко пропускать некоторый контент.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<button id="playPauseButton"></button>
<strong
><button id="seekForwardButton"></button>
<button id="seekBackwardButton"></button
></strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</div>
</div>
var skipTime = 10; // Time to skip in seconds
seekForwardButton.addEventListener('click', function (event) {
event.stopPropagation();
video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
});
seekBackwardButton.addEventListener('click', function (event) {
event.stopPropagation();
video.currentTime = Math.max(video.currentTime - skipTime, 0);
});
Как и прежде, вместо настройки стиля видео в прослушивателях событий click
этих кнопок, мы будем использовать события fired seeking
и seeked
video для настройки яркости видео. Мой пользовательский CSS-класс seeking
так же прост, как filter: brightness(0);
.
video.addEventListener('seeking', function () {
video.classList.add('seeking');
});
video.addEventListener('seeked', function () {
video.classList.remove('seeking');
});
Вот что мы создали на данный момент. В следующем разделе мы реализуем кнопку полноэкранного режима.
Полноэкранный
Здесь мы воспользуемся несколькими веб-API для создания идеального и бесшовного полноэкранного опыта. Чтобы увидеть это в действии, посмотрите пример .
Очевидно, вам не обязательно использовать их все. Просто выберите те, которые имеют для вас смысл, и объедините их, чтобы создать свой собственный поток.
Запретить автоматический полноэкранный режим
В iOS элементы video
автоматически переходят в полноэкранный режим, когда начинается воспроизведение мультимедиа. Поскольку мы пытаемся максимально адаптировать и контролировать наш опыт работы с мультимедиа в мобильных браузерах, я рекомендую вам установить атрибут playsinline
элемента video
, чтобы заставить его воспроизводиться в строке на iPhone и не переходить в полноэкранный режим, когда начинается воспроизведение. Обратите внимание, что это не имеет побочных эффектов в других браузерах.
<div id="videoContainer"></div>
<video id="video" src="file.mp4"></video><strong>playsinline</strong></video>
<div id="videoControls">...</div>
</div>
Переключить на полный экран при нажатии кнопки
Теперь, когда мы предотвратили автоматический полноэкранный режим, нам нужно самим управлять полноэкранным режимом для видео с помощью Fullscreen API . Когда пользователь нажимает кнопку «полноэкранный режим», давайте выйдем из полноэкранного режима с помощью document.exitFullscreen()
, если в данный момент документ использует полноэкранный режим. В противном случае запросим полноэкранный режим для контейнера видео с помощью метода requestFullscreen()
если он доступен, или вернемся к webkitEnterFullscreen()
для элемента video только на iOS.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<button id="playPauseButton"></button>
<button id="seekForwardButton"></button>
<button id="seekBackwardButton"></button>
<strong><button id="fullscreenButton"></button></strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</div>
</div>
fullscreenButton.addEventListener('click', function (event) {
event.stopPropagation();
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
requestFullscreenVideo();
}
});
function requestFullscreenVideo() {
if (videoContainer.requestFullscreen) {
videoContainer.requestFullscreen();
} else {
video.webkitEnterFullscreen();
}
}
document.addEventListener('fullscreenchange', function () {
fullscreenButton.classList.toggle('active', document.fullscreenElement);
});
Переключить полноэкранный режим при смене ориентации экрана
Так как пользователь поворачивает устройство в ландшафтном режиме, давайте будем умными и автоматически запросим полноэкранный режим, чтобы создать захватывающий опыт. Для этого нам понадобится API ориентации экрана , который пока не поддерживается везде и все еще имеет префикс в некоторых браузерах на тот момент. Таким образом, это будет наше первое прогрессивное улучшение.
Как это работает? Как только мы обнаруживаем изменения ориентации экрана, давайте запросим полноэкранный режим, если окно браузера находится в ландшафтном режиме (то есть его ширина больше высоты). Если нет, давайте выйдем из полноэкранного режима. Вот и все.
if ('orientation' in screen) {
screen.orientation.addEventListener('change', function () {
// Let's request fullscreen if user switches device in landscape mode.
if (screen.orientation.type.startsWith('landscape')) {
requestFullscreenVideo();
} else if (document.fullscreenElement) {
document.exitFullscreen();
}
});
}
Блокировка экрана в альбомной ориентации при нажатии кнопки
Поскольку видео может лучше просматриваться в ландшафтном режиме, мы можем захотеть заблокировать экран в ландшафтном режиме, когда пользователь нажимает кнопку «полноэкранный режим». Мы собираемся объединить ранее использовавшийся API ориентации экрана и некоторые медиазапросы, чтобы убедиться, что этот опыт будет наилучшим.
Блокировка экрана в альбомной ориентации так же проста, как вызов screen.orientation.lock('landscape')
. Однако это следует делать только тогда, когда устройство находится в портретном режиме с помощью matchMedia('(orientation: portrait)')
и его можно держать одной рукой с помощью matchMedia('(max-device-width: 768px)')
, поскольку для пользователей планшетов это будет не очень удобно.
fullscreenButton.addEventListener('click', function (event) {
event.stopPropagation();
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
requestFullscreenVideo();
<strong>lockScreenInLandscape();</strong>;
}
});
function lockScreenInLandscape() {
if (!('orientation' in screen)) {
return;
}
// Let's force landscape mode only if device is in portrait mode and can be held in one hand.
if (
matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches
) {
screen.orientation.lock('landscape');
}
}
Разблокировка экрана при изменении ориентации устройства
Вы, возможно, заметили, что созданный нами экран блокировки не идеален, поскольку мы не получаем изменений ориентации экрана, когда он заблокирован.
Чтобы исправить это, давайте воспользуемся API ориентации устройства, если он доступен. Этот API предоставляет информацию от оборудования, измеряющего положение и движение устройства в пространстве: гироскоп и цифровой компас для его ориентации и акселерометр для его скорости. Когда мы обнаруживаем изменение ориентации устройства, давайте разблокируем экран с помощью screen.orientation.unlock()
, если пользователь держит устройство в портретном режиме, а экран заблокирован в ландшафтном режиме.
function lockScreenInLandscape() {
if (!('orientation' in screen)) {
return;
}
// Let's force landscape mode only if device is in portrait mode and can be held in one hand.
if (matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches) {
screen.orientation.lock('landscape')
<strong>.then(function() {
listenToDeviceOrientationChanges();
})</strong>;
}
}
function listenToDeviceOrientationChanges() {
if (!('DeviceOrientationEvent' in window)) {
return;
}
var previousDeviceOrientation, currentDeviceOrientation;
window.addEventListener(
'deviceorientation',
function onDeviceOrientationChange(event) {
// event.beta represents a front to back motion of the device and
// event.gamma a left to right motion.
if (Math.abs(event.gamma) > 10 || Math.abs(event.beta) < 10) {
previousDeviceOrientation = currentDeviceOrientation;
currentDeviceOrientation = 'landscape';
return;
}
if (Math.abs(event.gamma) < 10 || Math.abs(event.beta) > 10) {
previousDeviceOrientation = currentDeviceOrientation;
// When device is rotated back to portrait, let's unlock screen orientation.
if (previousDeviceOrientation == 'landscape') {
screen.orientation.unlock();
window.removeEventListener(
'deviceorientation',
onDeviceOrientationChange,
);
}
}
},
);
}
Как вы видите, это тот самый бесшовный полноэкранный опыт, который мы искали. Чтобы увидеть это в действии, посмотрите пример .
Фоновое воспроизведение
Когда вы обнаруживаете, что веб-страница или видео на веб-странице больше не видны, вы можете обновить свою аналитику, чтобы отразить это. Это также может повлиять на текущее воспроизведение, например, на выбор другого трека, его паузу или даже на показ пользовательских кнопок пользователю.
Пауза видео при изменении видимости страницы
С помощью Page Visibility API мы можем определить текущую видимость страницы и получать уведомления об изменениях видимости. Код ниже останавливает видео, когда страница скрыта. Это происходит, когда активна блокировка экрана или когда вы переключаете вкладки, например.
Поскольку большинство мобильных браузеров теперь предлагают элементы управления за пределами браузера, которые позволяют возобновлять приостановленное видео, я рекомендую вам устанавливать это поведение только в том случае, если пользователю разрешено воспроизведение в фоновом режиме.
document.addEventListener('visibilitychange', function () {
// Pause video when page is hidden.
if (document.hidden) {
video.pause();
}
});
Показать/скрыть кнопку отключения звука при изменении видимости видео
Если вы используете новый API Intersection Observer , вы можете быть еще более детализированными без каких-либо затрат. Этот API позволяет вам узнать, когда наблюдаемый элемент входит или выходит из области просмотра браузера.
Давайте покажем/скроем кнопку отключения звука на основе видимости видео на странице. Если видео воспроизводится, но в данный момент не отображается, в правом нижнем углу страницы будет показана мини-кнопка отключения звука, чтобы предоставить пользователю контроль над звуком видео. Событие volumechange
video используется для обновления стиля кнопки отключения звука.
<button id="muteButton"></button>
if ('IntersectionObserver' in window) {
// Show/hide mute button based on video visibility in the page.
function onIntersection(entries) {
entries.forEach(function (entry) {
muteButton.hidden = video.paused || entry.isIntersecting;
});
}
var observer = new IntersectionObserver(onIntersection);
observer.observe(video);
}
muteButton.addEventListener('click', function () {
// Mute/unmute video on button click.
video.muted = !video.muted;
});
video.addEventListener('volumechange', function () {
muteButton.classList.toggle('active', video.muted);
});
Воспроизводить только одно видео за раз
Если на странице размещено несколько видео, я бы посоветовал вам воспроизвести только одно из них, а остальные автоматически приостанавливать, чтобы пользователю не приходилось слушать одновременно воспроизводимые несколько аудиодорожек.
// This array should be initialized once all videos have been added.
var videos = Array.from(document.querySelectorAll('video'));
videos.forEach(function (video) {
video.addEventListener('play', pauseOtherVideosPlaying);
});
function pauseOtherVideosPlaying(event) {
var videosToPause = videos.filter(function (video) {
return !video.paused && video != event.target;
});
// Pause all other videos currently playing.
videosToPause.forEach(function (video) {
video.pause();
});
}
Настройте уведомления для СМИ
С помощью Media Session API вы также можете настраивать уведомления о медиа, предоставляя метаданные для текущего воспроизводимого видео. Он также позволяет обрабатывать события, связанные с медиа, такие как поиск или изменение трека, которые могут поступать из уведомлений или ключей медиа. Чтобы увидеть это в действии, посмотрите пример .
Когда ваше веб-приложение воспроизводит аудио или видео, вы уже можете видеть уведомление о медиа, сидящее в области уведомлений. На Android Chrome делает все возможное, чтобы показать соответствующую информацию, используя заголовок документа и самое большое изображение значка, которое он может найти.
Давайте посмотрим, как настроить это медиа-уведомление, установив некоторые метаданные медиа-сеанса, такие как название, исполнитель, название альбома и обложка, с помощью API медиа-сеанса .
playPauseButton.addEventListener('click', function(event) {
event.stopPropagation();
if (video.paused) {
video.play()
<strong>.then(function() {
setMediaSession();
});</strong>
} else {
video.pause();
}
});
function setMediaSession() {
if (!('mediaSession' in navigator)) {
return;
}
navigator.mediaSession.metadata = new MediaMetadata({
title: 'Never Gonna Give You Up',
artist: 'Rick Astley',
album: 'Whenever You Need Somebody',
artwork: [
{src: 'https://dummyimage.com/96x96', sizes: '96x96', type: 'image/png'},
{
src: 'https://dummyimage.com/128x128',
sizes: '128x128',
type: 'image/png',
},
{
src: 'https://dummyimage.com/192x192',
sizes: '192x192',
type: 'image/png',
},
{
src: 'https://dummyimage.com/256x256',
sizes: '256x256',
type: 'image/png',
},
{
src: 'https://dummyimage.com/384x384',
sizes: '384x384',
type: 'image/png',
},
{
src: 'https://dummyimage.com/512x512',
sizes: '512x512',
type: 'image/png',
},
],
});
}
После завершения воспроизведения вам не нужно «отпускать» сеанс мультимедиа, так как уведомление автоматически исчезнет. Помните, что текущий navigator.mediaSession.metadata
будет использоваться при запуске любого воспроизведения. Вот почему вам нужно обновить его, чтобы убедиться, что вы всегда показываете соответствующую информацию в уведомлении мультимедиа.
Если ваше веб-приложение предоставляет список воспроизведения, вы можете разрешить пользователю перемещаться по нему непосредственно из уведомления о медиафайлах с помощью значков «Предыдущий трек» и «Следующий трек».
if ('mediaSession' in navigator) {
navigator.mediaSession.setActionHandler('previoustrack', function () {
// User clicked "Previous Track" media notification icon.
playPreviousVideo(); // load and play previous video
});
navigator.mediaSession.setActionHandler('nexttrack', function () {
// User clicked "Next Track" media notification icon.
playNextVideo(); // load and play next video
});
}
Обратите внимание, что обработчики действий мультимедиа сохранятся. Это очень похоже на шаблон прослушивателя событий, за исключением того, что обработка события означает, что браузер прекращает выполнять любое поведение по умолчанию и использует это как сигнал о том, что ваше веб-приложение поддерживает действие мультимедиа. Следовательно, элементы управления действиями мультимедиа не будут отображаться, если вы не установите правильный обработчик действий.
Кстати, отключить обработчик медиа-действий так же просто, как присвоить ему значение null
.
API сеанса мультимедиа позволяет отображать значки уведомлений мультимедиа «Перемотка назад» и «Перемотка вперед», если вы хотите контролировать количество пропущенного времени.
if ('mediaSession' in navigator) {
let skipTime = 10; // Time to skip in seconds
navigator.mediaSession.setActionHandler('seekbackward', function () {
// User clicked "Seek Backward" media notification icon.
video.currentTime = Math.max(video.currentTime - skipTime, 0);
});
navigator.mediaSession.setActionHandler('seekforward', function () {
// User clicked "Seek Forward" media notification icon.
video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
});
}
Значок "Воспроизведение/Пауза" всегда отображается в уведомлении о медиа, а соответствующие события обрабатываются браузером автоматически. Если по какой-то причине поведение по умолчанию не работает, вы все равно можете обрабатывать события "Воспроизведение" и "Пауза" медиа .
Крутая вещь в Media Session API заключается в том, что панель уведомлений — не единственное место, где видны метаданные и элементы управления медиа. Уведомление о медиа автоматически синхронизируется с любым сопряженным носимым устройством. И оно также отображается на экранах блокировки.