Microsoft — мировой лидер в области ПО и ИТ-услуг
135,59
рейтинг
29 августа 2011 в 18:51

Разработка → Как сделать собственный видео-плеер на HTML5 Video tutorial

Ранее мы уже рассмотрели общие вопросы использования HTML5 Audio и Video и начали погружаться в детали, начав с задачи определения поддержки браузером нужного кодека. Сегодня мы рассмотрим задачу создания собственного видео-плеера на HTML5 Video.


Напомню, что video-элемент сам по себе уже обеспечивает необходимый набор контролов для управления проигрыванием. Чтобы была видна панель управления воспроизведением, достаточно указать атрибут controls.

<video src="trailer_480p.mp4" width="480" height="270" poster="poster.gif" controls />

Однако, как я отмечал в вводной статье, со стандартными контролами есть проблема, которая заключается как раз в том, что выглядят они нестандартно. Другими словами, в каждом браузере они выглядят по-своему (проверить, как выглядят контролы в разных браузерах, можно на примере Video Format Support на ietestdrive.com — просто откройте его в двух-трех различных браузерах).

API для управления воспроизведением


Стандарт HTML5 для работы с видео вводит в DOM новый интерфейс — HTMLVideoElement, наследующий в свою очередь интерфейс HTMLMediaElement.

Интерфейс HTMLMediaElement


Это общий интерфейс для обоих медиа-элементов (аудио и видео), описывающий доступ к базовым возможностями работы с медиа-контентом: контроль источника контента, управление воспроизведением, изменение уровня звука и обработка ошибок. Основные свойства и методы, которые нам понадобятся:

Состояние сети и готовность к работе
src — ссылка (url) на воспроизводимый контент
buffered — буферизованные куски видео

Воспроизведение и контролы
currentTime — текущий момент проигрывания (с.)
duration — длительность медиа-контента (с.)
paused — находится ли воспроизведение на паузе
ended — закончилось ли проигрывание
muted — включение/выключение звука
volume — уровень звука [0, 1]
play() — начать проигрывание
pause() — поставить на паузу

События
oncanplay — можно начать проигрывание
ontimeupdate — изменена позиция проигрывания
onplay — запущено проигрыв
onpause — нажата пауза
onended — воспроизведение закончилось

Важно: это далеко не все методы и свойства, выставляемые через интерфейс HTMLMediaElement.

Интерфейс HTMLVideoElement


Видео отличается от аудио несколькими дополнительными свойствами:
width и height — ширина и высота контейнера для проигрывания видео;
videoWidth и videoHeight — внутреннее значение ширины и высоты видео, если размеры не известны, равны 0;
poster — ссылка на картинку, которую можно показывать, пока видео недоступно (обычно это один
из первых непустых кадров).

Разница между width/height и videoWidth/videoHeight в том, что последние — это собственные характеристики видео, в частности, с учетом соотношения сторон и других характеристик, в то время как контейнер для видео может быть любых размеров (больше, меньше, с другой пропорцией).

Play & Pause


Создание нашего собственного видео-плеера мы начнем с простой задачи: научимся запускать видео на проигрывание и останавливать воспроизведение. Для этого нам понадобятся методы play() и pause() и несколько свойств, описывающих текущее состояние видео-потока (мы также будем использовать библиотеку jQuery, не забудьте ее подключить).

Первым делом нам необходим video-элемент, которым мы хотим управлять, и элемент на который можно нажимать для управления текущим состоянием:
<div>
    <video id="myvideo" width="480" height="270" poster="poster.gif" >
        <source src="trailer_480p.mp4" type='video/mp4;codecs="avc1.42E01E, mp4a.40.2"' />
        <source src="trailer_480p.webm" type='video/webm; codecs="vorbis,vp8"'/> 
    </video>
</div>
<div id="controls">
    <span id="playpause" class="paused" >Play</span>
</div>


#controls span {
    display:inline-block;
}
        
#playpause {
    background:#eee;
    color:#333;
    padding:0 5px;
    font-size:12pt;
    text-transform:uppercase;
    width:50px;
}


Обратите внимание на инвертирование состояния кнопки (paused) и действия (play).

Теперь надо добавить немного js-кода, чтобы нажатие на кнопку play переключало ее состояние и соответственно запускало видео-ролик или ставило его на паузу:
$(document).ready(function(){
    var controls = {
        video: $("#myvideo"),
        playpause: $("#playpause")                 
    };
                
    var video = controls.video[0];
               
    controls.playpause.click(function(){
        if (video.paused) {
            video.play();
            $(this).text("Pause");    
        } else {
            video.pause();
            $(this).text("Play");
        }
                
        $(this).toggleClass("paused"); 
    });
}); 


При желании можно сразу добавить несколько css-стилей для кнопок управления и их различных состояний и...

… казалось бы, все уже замечательно работает, но не тут-то было! Есть несколько мелочей, которые нам также нужно учесть.

Проигрывание сначала


Во-первых, нам нужно правильно обработать окончание проигрывания видео-ролика (если, конечно, оно не зациклено), и в этот момент нужно переключить кнопки управления так, чтобы вместо состояния «pause» было состояние «play»:

video.addEventListener("ended", function() {
    video.pause();
    controls.playpause.text("Play");
    controls.playpause.toggleClass("paused");
});


Контекстное меню


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

video.addEventListener("play", function() {
    controls.playpause.text("Pause");
    controls.playpause.toggleClass("paused");
});
                
video.addEventListener("pause", function() {
    controls.playpause.text("Play");
    controls.playpause.toggleClass("paused");
});


Так как у нас становится многовато мест, где меняется внешний вид, самое время попутно произвести небольшой рефакторинг, убрав из изначального переключения режимов теперь уже дублирующую смену внешнего состояния:

var controls = {
    ...  
    togglePlayback: function() {
        (video.paused) ? video.play() : video.pause();
    }
    ...
};
                
controls.playpause.click(function(){
    controls.togglePlayback();
});


Кликабельное видео


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

controls.video.click(function() {
    controls.togglePlayback();
});


Текущий результат:


Прогресс


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

<span id="progress">
    <span id="total">
        <span id="buffered"><span id="current">​</span></span>
    </span>
</span>
<span id="time">
    <span id="currenttime">00:00</span> / 
    <span id="duration">00:00</span>
</span>


И соответствующие стили:

#progress {
    width:290px;
}
            
#total {
    width:100%;                
    background:#999;
}
            
#buffered {
    background:#ccc;
}
            
#current {
    background:#eee;
    line-height:0;
    height:10px;
}
            
#time {
    color:#999;
    font-size:12pt;
}

И несколько ссылок на соответствующие элементы для быстрого доступа в объект controls:
var controls = {
    ...
    total: $("#total"),
    buffered: $("#buffered"),
    progress: $("#current"),
    duration: $("#duration"),
    currentTime: $("#currenttime"),
    hasHours: false,
    ...
};


Первым делом, нам нужно понять, какова длительность ролика — для этого у video-элемента есть свойство duration. Отследить это значение можно, например, в момент готовности ролика к проигрыванию — по событию oncanplay:

video.addEventListener("canplay", function() {
    controls.hasHours = (video.duration / 3600) >= 1.0;                    
    controls.duration.text(formatTime(video.duration, controls.hasHours));
    controls.currentTime.text(formatTime(0),controls.hasHours);
}, false);


В данном случае, мы попутно определяем, нужно ли отображать количество часов в видео-плеере (кстати, вообще говоря, спецификация предполагает, что длительность ролика может изменяться — в этот момент срабатывает событие ondurationchange, и к тому же быть бесконечной — например, при стриминге радио).


Также мы используем специальную функцию formatTime для перевода секунд в формат HH:mm:ss или mm:ss:

function formatTime(time, hours) {
    if (hours) {
        var h = Math.floor(time / 3600);
        time = time - h * 3600;
                    
        var m = Math.floor(time / 60);
        var s = Math.floor(time % 60);
                    
        return h.lead0(2)  + ":" + m.lead0(2) + ":" + s.lead0(2);
    } else {
        var m = Math.floor(time / 60);
        var s = Math.floor(time % 60);
                    
        return m.lead0(2) + ":" + s.lead0(2);
    }
}
            
Number.prototype.lead0 = function(n) {
    var nz = "" + this;
    while (nz.length < n) {
        nz = "0" + nz;
    }
    return nz;
};


Для отображения процесса проигрывания нам понадобится событие ontimeupdate, срабатывающее при изменении текущего момента:

video.addEventListener("timeupdate", function() {
    controls.currentTime.text(formatTime(video.currentTime, controls.hasHours));
                    
    var progress = Math.floor(video.currentTime) / Math.floor(video.duration);
    controls.progress[0].style.width = Math.floor(progress * controls.total.width()) + "px";
}, false);


Свойство currentTime выдает в секундах текущее время. Его же можно использовать, чтобы изменить время проигрывания:

controls.total.click(function(e) {
    var x = (e.pageX - this.offsetLeft)/$(this).width();
    video.currentTime = x * video.duration;
});


Также будет полезным показывать буферизацию видео, для этого можно отталкиваться от события onprogress, срабатывающего при загрузке новых порций видео:

video.addEventListener("progress", function() {
    var buffered = Math.floor(video.buffered.end(0)) / Math.floor(video.duration);
    controls.buffered[0].style.width =  Math.floor(buffered * controls.total.width()) + "px";
}, false);


Важный нюанс относительно свойства buffered, который нужно иметь в виду, заключается в том, что он предоставляет не просто время в секундах, а промежутки времени в виде объекта TimaRanges. В большинстве случаев это будет только один промежуток с индексом 0, и начинающийся с отметки 0c. Однако, если браузер использует HTTP range запросы к серверу, например, в ответ на попытки перейти к другим фрагментам видео-потока, промежутков может быть несколько. Также надо учитывать, что в зависимости от реализации браузер может удалять из буфера памяти уже проигранные куски видео.

Промежуточный результат:


Звук


Наконец, давайте добавим еще небольшой штрих к нашем видео-плееру — возможность включать и выключать звук. Для этого добавим небольшой контрол с динамиком (SVG-иконка взята с сайта The Noun Project):

<span id="volume">
    <svg id="dynamic" version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 width="16px" height="16px" viewBox="0 0 95.465 95.465">
        <g >
            <polygon points="39.323,20.517 22.705,37.134 0,37.134 0,62.865 22.705,62.865 39.323,79.486 "/>
            <path d="M52.287,77.218c14.751-15.316,14.751-39.116,0-54.436c-2.909-3.02-7.493,1.577-4.59,4.59
                        c12.285,12.757,12.285,32.498,0,45.254C44.794,75.645,49.378,80.241,52.287,77.218L52.287,77.218z"/>
            <path d="M62.619,89.682c21.551-22.103,21.551-57.258,0-79.36c-2.927-3.001-7.515,1.592-4.592,4.59
                        c19.08,19.57,19.08,50.608,0,70.179C55.104,88.089,59.692,92.683,62.619,89.682L62.619,89.682z"/>
            <path d="M75.48,99.025c26.646-27.192,26.646-70.855,0-98.051c-2.936-2.996-7.524,1.601-4.592,4.59
                        c24.174,24.674,24.174,64.2,0,88.871C67.956,97.428,72.545,102.021,75.48,99.025L75.48,99.025z"/>
        </g>
        </svg>
</span>


С соответствующими стилями для включенного и выключенного состояний:
#dynamic {
    fill:#333;
    padding:0 5px;
}
            
#dynamic.off {
    fill:#ccc;
}

Для переключения состояния динамика нам понадобится свойство mute:

controls.dynamic.click(function() {
    var classes = this.getAttribute("class");

    if (new RegExp('\\boff\\b').test(classes)) {
        classes = classes.replace(" off", "");
    } else {
        classes = classes + " off";
    }

    this.setAttribute("class", classes);
                    
    video.muted = !video.muted;
});

(Стандартные методы jQuery для переключения css-классов не работают с SVG-элементами.)
Если вы хотите также менять уровень громкости, то вам поможет свойство volume, принимающее значения в диапазоне [0, 1].

Финальный результат:


Что еще...


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

Также не забудьте, что привязку событий к элементам управления нужно делать после того, как стало понятно, что видео доступно для проигрывания (oncanplay):

video.addEventListener("canplay", function() {
    ...
}, false);


Либо нужно делать соответствующие проверки или отлавливать возможные исключения. Исключения вообще надо отлавливать, например, событие onerror, возникающее при ошибке загрузки видео-потока :)

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


Готовые плееры


Думаю, вам не составит труда найти готовые решения для использования HTML5 Video со всеми полагающимися плюшками, вплоть до удобной кастомизации внешнего вида через CSS. Вот несколько полезных ссылок:

Наконец, HTML5 Video в спецификации.
Автор: @kichik
Microsoft
рейтинг 135,59
Microsoft — мировой лидер в области ПО и ИТ-услуг

Комментарии (17)

  • +3
    Скорее бы появилась возможность полноценного fullscreen на не веб-китных браузерах.
    • +1
      А на веб-китовых разве есть полноценный фуллскрин? У меня в хроме только на окно браузера разворачиваеться… Потом меню > фуллскринн.
      • 0
        на маке есть полноценный фуллскрин. уже очень давно кстати.
        • 0
          В сафари да. В хроме на маке — нет. В файрфоксе на маке — тоже нет.
          • 0
            хром если я не ошибаюсь юзает свой плагин для видео.
            а сафари крутит видео при помощи квиктайма. соответственно сафари на винде тоже имеет нормальный фулскрин, если конечно установлен айтюнс/квиктайм.
            а воще нормальный фулскин будет на любом вебкитовем браузере, при условии про юзается квиктайм.
            • 0
              Ну вот… нет квиктайма, нет фуллскрина…
  • +1
    Не прочитав заголовка, подумал, что блэндер поселился на хабрахабре.
    За статью спасибо.
  • 0
    А можно автора попросить написать JS-код для такой фичи: после 30 секунд непрерывного проигрывания в появляется строчка complete. Хочу сделать счетчик просмотров ролика. Заранее спасибо.
    • 0
      Строчку вырезало:
      <div id="complete"></div>
    • +2
      Именно в такой постановке задачи, не уверен, что считать эффективным решением :)

      Есть такие варианты:
      1. По timeupdate проверять, что текущая позиция больше 30 секунд и увеличивать счетчик:
      video.addEventListener("timeupdate", function() {
          if (video.currentTime >= 30.0) console.log("complete");
      }, false);


      Это, пожалуй, самый очевидный вариант, но он не отлавливает, к примеру, бытрую смену позиции за 30 секунд и дальше просмотр в течение 5 сек.

      2. Попробовать смотреть на свойство played — там TimeRanges с промежутками, которые пользователь смотрел. По ним можно пройтись и проверить, есть ли длинее 30 секунд.

      for (var i = 0; i < video.played.length; i++) {
          if ((video.played.end(i) - video.played.start(i)) >= 30.0) console.log("complete");
      }
      • 0
        Для JWPlayer (в Flash) у меня сейчас сделано как-то так:

        function playerReady( obj )
        {
        JWplayer = document.getElementById( obj.id );
        ....
        JWplayer.addModelListener('TIME', 'timeMonitor');
        JWtotalPlayed = 0;
        JWskippedVideo = 0;
        };

        function timeMonitor(obj)
        {
        if (JWtotalPlayed > 1000000)
        {
        return;
        }
        if (JWtotalPlayed > 30)
        {
        loadHTML('/test.php?&id=' + JWcurrentVideoID, 'data');
        JWtotalPlayed = 2000000;
        }
        JWcurrentPosition = obj.position;
        if (JWpreviousPosition < JWcurrentPosition - .5)
        {
        JWskippedVideo += JWcurrentPosition - JWpreviousPosition;
        } else {
        JWtotalPlayed += JWcurrentPosition - JWpreviousPosition;
        }
        JWpreviousPosition = JWcurrentPosition;
        };


        Как и думал, что-то подобное есть в стандартной реализации для HTML5. Очень похоже что для моей задачи подойдет, предложенный Вами первый вариант, с сохранением предыдущей позиции проигрывателя и прибавлением дельты. Спасибо, попробую.
  • –1
    Кадры из ролика, который встроен в устройства Archos?
    • +3
      Ролик Big Buck Bunny часто используется в качестве тестового файла для видео кодеков и плееров вместе с другими свободными (в данном случае Creative Commons Attribution) мультфильмами. Хотя я тоже не понимаю, почему никто не снимет, не отредерит, не закапчурит, не нарисует новый, ведь эти уже всем надоели.
  • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Хорошая статья. Хотя, признаться, руководство, как сделать HTML5 плеер нужно только тем, кто не собирается использовать уже готовые решения, которых уже очень много.

    З.Ы. в HTML5 вообще много методов по работе с видео. И это радует: хоть какой-то прогресс.
  • +3
    А теперь добавлю-ка я ложку (или половничек) дёгтя в Ваш медок: а что Вы скажете о планшетах, мобильниках с из Android, iOS,…
    Сам сейчас занят HTML5 мультимедией для разных усройств (основной упор как раз на Андрооид 2.1 — уж не удивляйтесь, заказчик хочет купить мегаораву устройств именно на этой версии, и перепрошивать их на новую версию — да я умру, пока закончу). Поэтому могу добавить пару штрихов, прочитанных, например, здесь. Кстати, познавательная статейка, почитайте.
    По Андроидам. Если оный Андроид младше версии 2.3, то есть пара глюков:
    1) атрибут type не нужен, даже мешает. Поэтому его убрать, а расширение файла ЖЕСТКО должно быть mp4. У меня этот блок таков:

    video id="video"
    source src="video.mp4"
    /video


    (Все три строки открываются и закрываются угловыми скобками, но их не могу поставить — парсер Хабра глючит).

    Вся логика обеспечивается JavaScript. Почему Джавой? А вот почему:
    2) Атрибут controls НЕ поддерживается. Вот такая вот петрушка.

    По iOS 3.2 (iPhone, iPod Touche, iPad 1 — у второго айпада версия iOS 4, и там нижеописанные баги пофиксены):
    1) не распознаёт видео, если есть атрибут poster.
    2) если у Вас несколько атрибутов source, то видит только первый из них. Так что H.264+AAC (в контейнере MP4) вставляйте первым.

    А теперь насчет энкодинга. Все эти вышеописанные ОСи (даже, наверное, новейшие их версии) поддерживают дажеко не все варианты. Например, MP4 поддерживается только в следующей комбинации: видео H.264 (только профиль baseline), аудио AAC (профиль кодирования low complexity).
    С WebM и Ogg (Theora+Vorbis) тоже свои затыки. Вообще, эта тема весьма обширна, так что не буду повторяться, а отошлю к вышеназванной ссылке.
  • 0
    Спасибо за статью. Не хватает только у Вас в коде
    var controls = {
    ....
    dynamic: $("#dynamic"),
    ....

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка