Сьогодні ми розглянемо завдання створення власного відеоплеєра на HTML5 Video.


Нагадаю, що сам відеоелемент вже надає необхідний набір елементів управління відтворенням. Щоб побачити панель управління відтворенням, потрібно просто вказати атрибут управління.

Однак, як я вже зазначав у вступній статті, існує проблема зі стандартними елементами управління, яка саме в тому, що вони виглядають нестандартно. Іншими словами, в кожному браузері вони виглядають по-різному (можна перевірити, як виглядають елементи управління в різних браузерах, можна скористатися прикладом підтримки відеоформату на ietestdrive.com – просто відкрийте його в двох-трьох різних браузерах).

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

API керування відтворенням

Стандарт HTML5 для роботи з відео вводить в DOM – HTMLVideoElement новий інтерфейс, який в свою чергу успадковує інтерфейс HTMLMediaElement.

Інтерфейс HTMLMediaElement

Це поширений інтерфейс як для медіаелементів (аудіо, так і для відео), що описує доступ до основних можливостей роботи з медіаконтентом: управління джерелом вмісту, управління відтворенням, зміни рівня звуку та обробка помилок. Основні властивості та методи, які нам знадобляться:

Стан мережі таготовність src – Посилання (URL) на
буферизований вміст
, що відтворюється – Буферизовані фрагменти
відтворення відео

та елементи керування CurrentTime – поточний час відтворення (p)
тривалість – тривалість медіаконтенту (p)
призупинено — чи завершено відтворення на паузі
— чи закінчилося відтворення
вимкнено — увімкнено/вимкнено звук гучності
— рівень звуку [0, 1]
play() — початок відтворення
паузи() — пауза

подій
oncanplay — ви можете почати відтворювати
ontimeupdate – змінено положення відтворення
onplay – запускається відтворення
onpause – натиснута пауза
onended – відтворення закінчено

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

HTMLVideoElement interface

Відео відрізняється від аудіо декількома додатковими властивостями:
шириною і висотою – шириною і висотою контейнера для відтворення відео;
відеоВідкриття і відеоHeight — внутрішнє значення ширини і висоти відео, якщо розміри невідомі, дорівнює 0;
плакат – це посилання на зображення, яке можна показати, поки відео недоступне (зазвичай один з
перших непустих кадрів).

Різниця між шириною / висотою і відеоWidth / videoHeight полягає в тому, що останні є фірмовими характеристиками відео, особливо з урахуванням співвідношення сторін та інших характеристик, в той час як відеокомент може бути будь-якого розміру (більше, менше, з різним співвідношенням сторін).

Відтворення та пауза

Створюючи власний відеоплеєр, ми почнемо з простого завдання: навчитися запускати відео для відтворення і припиняти відтворення. Для цього нам знадобляться методи play() і pause() і кілька властивостей, які описують поточний стан відеопотоку (ми також будемо використовувати бібліотеку jQuery, не забудьте підключити її).

Перш за все, нам потрібен відеоелемент, яким ми хочемо керувати, і елемент, на який можна натиснути, щоб контролювати поточний стан:

Зверніть увагу на інвертування стану кнопки (паузи) і дії (відтворення).

Тепер потрібно додати трохи js-коду, щоб натискання кнопки відтворення переключило її стан і відповідно запускає відеокліп або призупиняє його:

Якщо хочете, ви можете відразу додати кілька css-стилів для кнопок управління та їх різних станів і …

… здавалося б, все вже працює нормально, але не тут було! Є кілька маленьких речей, які ми також повинні розглянути.

<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;
}
$(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"); 
    });
}); 

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

Во-первых, нам нужно правильно обработать окончание проигрывания видео-ролика (если, конечно, оно не зациклено), и в этот момент нужно переключить кнопки управления так, чтобы вместо состояния «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();
});

Прогресс

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

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

И несколько ссылок на соответствующие элементы для быстрого доступа в объект controls:

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

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

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

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

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

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

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

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

<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;
}
var controls = {
    ...
    total: $("#total"),
    buffered: $("#buffered"),
    progress: $("#current"),
    duration: $("#duration"),
    currentTime: $("#currenttime"),
    hasHours: false,
    ...
};
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);
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;
};
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);
controls.total.click(function(e) {
    var x = (e.pageX - this.offsetLeft)/$(this).width();
    video.currentTime = x * video.duration;
});
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);

Звук

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

С соответствующими стилями для включенного и выключенного состояний:

‎Щоб переключити стан динаміка, нам потрібна властивість ‎‎вимкнення звуку‎‎:‎

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

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

<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;
}
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;
});

Що ще…

Крім того, що ви можете легко налаштувати стилі управління, як ви вважаєте за потрібне, є кілька інших важливих моментів, які залишаються за межами цієї статті, але які корисно мати на увазі в реальному проекті:

Крім того, не забувайте, що вам потрібно прив’язати події до елементів керування після того, як стане зрозуміло, що відео доступне для відтворення (oncanplay):

Або вам потрібно зробити відповідні перевірки, або зловити можливі винятки. Винятки взагалі потрібно зловити, наприклад, подію onerror, яка виникає, коли відеопотік не завантажується 🙂

З додаткових опцій, які вам можуть знадобитися: змінити швидкість відтворення. Для цього є властивість playbackRate і відповідна подія onratechange.

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