HTML5 видео-плеер Acorn Media Player (jQuery плагин)



Собственные элементы управления видео в браузере Opera 10.63, с фокусом на кнопке громкости


Наш плагин jQuery создан с учетом прогрессивного улучшения — именно поэтому он создан поверх стандартного элемента видео. В этом случае, если JavaScript отключен, пользователь получит стандартные элементы управления, предоставляемые браузером.

Разметка для элементов управления выглядит следующим образом:

<div class="ghinda-video-controls">
  <a class="ghinda-video-play" title="Play/Pause"></a>
  <div class="ghinda-video-seek"></div>
  <div class="ghinda-video-timer">00:00</div>
  <div class="ghinda-volume-box">
    <div class="ghinda-volume-slider"></div>
    <a class="ghinda-volume-button" title="Mute/Unmute"></a>
  </div>
</div>


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

<div class="acorn-video-controls">
  <button class="acorn-play-button" title="Start the playback" aria-controls="video1">Play</button>
  
  <input type="range" class="acorn-seek-slider" title="Video seek control" value="0" min="0" 
   max="150" step="0.1" aria-controls="video1"/>
  
  <span class="acorn-timer">00:00</span>
  
  <div class="acorn-volume-box">
    <button class="acorn-volume-button" title="Mute volume" aria-controls="video1">Mute</button>
    <input type="range" class="acorn-volume-slider" title="" value="1" min="0" 
      max="1" step="0.05" aria-controls="video1"/>
  </div>
</div>


Кнопки

Прежде всего, мы заменили элементы <a>, которые ведут себя как кнопки, на реальные элементы <button>, так что разметка показывает их смысл, а считыватели экрана интерпретируют их правильно.

Все наши кнопки имеют содержательные метки и атрибуты title. Атрибуты title предоставляют также всплывающие подсказки, которые будут особенно полезны, если разработчик решит показывать только кнопки, без меток. Они также полезны для пользователей считывателей экрана, предоставляя им более длинное объяснение, что реально делает элемент управления.

Ползунки

Вместо бессодержательных <div> для ползунков, мы используем собственные ползунки HTML5: <input type="range">.

Мы будем по-прежнему использовать jQuery UI для создания ползунков, но, тем не менее, предоставляем собственные ползунки в качестве варианта для плагина, на тот случай, когда они будут правильно реализованы во всех браузерах.

В предыдущей версии плагина мы вызывали плагин ползунка UI на пустом <div>, но теперь мы собираемся вызывать его на элементе <input>. У нас теперь имеется проблема, так как плагин добавляет классы к ползунку, и присоединяет к нашему элементу другие элементы, такие как регулятор ползунка. Это не будет работать, так как <input> является линейным (inline), поэтому не может включать другие элементы. Поэтому перед вызовом плагина ползунка мы должны удалить <input>, заменить его элементом <div> (или <p> или любым другим обычным элементом HTML) и вызвать на нем плагин.

WAI-ARIA

Можно также видеть новый атрибут, кроме обычно используемых. Атрибут aria-controls является частью спецификации WAI-ARIA, и определяет, какой элемент контролируется. Введение в WAI ARIA.

При переписывании исходной разметки можно было бы сохранить исходные элементы и добавить к ним роли ARIA, например, добавить role="button" к элементам <a>. Для пользователей вспомогательных технологий это могло бы заставить их вести себя почти как элементы <button>. Однако при таком подходе возникает ряд проблем.

Проблемой могут быть семантические конфликты. Элемент <div>, например, является семантически нейтральным и может получить любую роль, но другие элементы имеют свою собственную внутреннюю семантику, и добавление к ним случайных ролей может приводить к конфликту между ролью ARIA, пытающейся описать элемент, и его врожденной семантикой.

В связи с этими конфликтами была создана концепция "неявной семантики ARIA". Таким способом спецификация описывает примененную семантику, и ограничение внутренней семантики для большинства элементов. Например, элементы <article>, <aside> и <section> имеют следующую примененную семантику: "article role", "note role" и "region role".

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

Ползунки ARIA

Возвращаясь к плееру, мы теперь разрешили проблемы с кнопками, но, принимая во внимание, что мы не всегда используем собственные ползунки браузера, нам по-прежнему требуется кое-что сделать с ползунками jQuery UI.

Ползунки UI предоставляют отличную клавиатурную доступность, но не имеют никакой поддержки ARIA (в версии 1.8.4). И, считая, что ползунок является элементом <a>, указывающим на href="#" в том же документе, считыватели экрана будут интерпретировать это как посещенную ссылку, а не как управляющий ползунок. Чтобы обойти эту проблему, мы создадим небольшую функцию, чтобы добавить в ползунок ARIA и возможность получать фокус.

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

С учетом того, что только один элемент (для ползунка) должен получить фокус, и UI jQuery предоставляет клавиатурное управление для ползунка, когда фокус находится на регуляторе, то это будет лучшим выбором.

Все элементы формы должны опознаваться пользователями, поэтому нужно пометить ползунок. Для этого можно использовать различные подходы. Можно было бы использовать атрибут aria-labelledby, использовать элемент <label> и заставить его указывать на ползунок, или использовать атрибут title. Для нашего случая атрибут title является, вероятно, лучшим решением.

Кроме атрибута role, ползунок ARIA требует следующие атрибуты:
  • aria-valuenow - Текущее значение ползунка.
  • aria-value-min - Минимальное значение.
  • aria-value-max - Максимальное значение, которое может иметь ползунок.

По умолчанию вспомогательные технологии используют атрибут aria-valuenow, но значение должно быть числом – если бы мы хотели предоставить пользователю более удобное для восприятия значение, мы могли бы использовать вместо этого другое свойство ARIA, aria-valuetext. Это значение является строкой, поэтому, например, когда пользователь использует ползунок поиска, то вместо вывода значения "23", можно было бы выводить "23 секунды".

С учетом всего здесь рассмотренного, наша функция выглядит следующим образом:

var initSliderAccess = function (elem, opts) {
  var accessDefaults = {
   'role': 'slider',
   'aria-valuenow': parseInt(opts.value),
   'aria-valuemin': parseInt(opts.min),
   'aria-valuemax': parseInt(opts.max),
   'aria-valuetext': opts.valuetext,
   'tabindex': '0'
  };
  elem.attr(accessDefaults);       
};

Эта функция адаптирована для тех средств, которые используются при вызове ползунка UI jQuery. Мы имеем в функции два объекта в качестве параметров, первый является элементом, с которым выполняется манипуляция, а второй является объектом свойств, которые используются для значений.

Можно видеть, что кроме атрибутов role и aria, мы добавляем атрибут tabindex . Это делается, чтобы гарантировать, что регулятор будет доступен с клавиатуры. Мы используем также функцию parseInt() на некоторых значениях, чтобы не передавать в систему вспомогательных технологий такие значения, как "1.54321".

В предыдущей статье, чтобы фактически заставить ползунки управлять видеоплеером, мы присоединили функции к порождаемым jQuery событиям slide и stop.

И так как мы всегда хотим информировать пользователя о текущем значении ползунка, мы должны изменить значение "aria-valuenow" и "aria-valuetext" на slide().

Мы добавим небольшой код в функции. Для ползунка поиска мы будем использовать следующий код:

sliderUI.attr("aria-valuenow", parseInt(ui.value));
sliderUI.attr("aria-valuetext", ariaTimeFormat(ui.value));

Объект sliderUI является элементом регулятора ползунка, а ui.value является текущим значением ползунка, возвращаемым плагином. Можно видеть, что мы используем для атрибута aria-valuetext другую функцию, функцию ariaTimeFormat().

Вот как выглядит функция ariaTimeFormat():

var ariaTimeFormat = function(sec) {
  var m = Math.floor(sec/60)<10?"" + Math.floor(sec/60):Math.floor(sec/60);
  var s = Math.floor(sec-(m*60))<10?"" + Math.floor(sec-(m*60)):Math.floor(sec-(m*60));
  var formatedTime;
           
  var min = 'minutes';
  var sec = 'seconds';
 
  if(m==1) min = 'minute';
  if(s==1) sec = 'second';
 
  if (m!=0) {      
    formatedTime = m + ' ' + min + ' ' + s + ' ' + sec;
  } else {          
    formatedTime = s + ' ' + sec;
  };        
 
  return formatedTime;
};

Эта функция получает текущее значение ползунка (число секунд, где расположен регулятор) и преобразует его в понятную человеку строку. Если ползунок расположен на 72 (секунде), например, функция вернет "1 minute 12 seconds".

Это особенно полезно для длинных видеофильмов, где без такой функции смещение указателя поиска на 25 минут будет сообщаться как 1500 секунд, делая это достаточно неудобным, особенно для пользователей считывателей экрана.

Мы будем использовать ту же самую функцию ARIAfication на ползунке громкости, чтобы сделать его доступным, и модифицируем функцию изменения громкости, чтобы изменять значение aria-valuenow и aria-valuetext, когда ползунок перемещается:

video.$volume.$handle.attr("aria-valuenow", Math.round(volume*100));
video.$volume.$handle.attr("aria-valuetext", Math.round(volume*100) + ' percent');

Можно видеть, что мы умножаем наше значение на 100. Это связано с тем, что значение громкости находится между 0 и 1, а не между 0 и 100. Также больше смысла имеет предоставление пользователю "процентного" значения для громкости, а не однозначное число, именно поэтому мы добавляем символ процентов к aria-valuetext, а также умножаем и округляем значение.

Титры и стенограммы

Как крайне важное свойство доступности, каждый плеер видео/мультимедиа должен предоставлять поддержку для титров и/или стенограмм. К сожалению, текущая спецификация W3C HTML5 не содержит ничего с этим связанного. С другой стороны спецификация WHATWG HTML5 (да, существует две спецификации HTML5) недавно добавила элемент <track> для титров. Это "позволяет авторам определить явные внешние размеченные по времени дорожки для элементов медиа ". Поэтому, по существу, вы можете определить внешний файл, содержащий титры, подзаголовки, описания или другие размеченные по времени дорожки. Можно определить несколько элементов <track> для различных дорожек, таких как различные языки.

Текущий формат файла называется WebSRT, и является, по сути, улучшенной версией формата SRT для SubRip.

Можно прочитать больше об этом в спецификации WHATWG HTML5.

Этот элемент отсутствует в спецификации W3C, в связи с "политическими" проблемами, в основном, потому что текущий предложенный формат (WebSRT) конфликтует с примерно 50 другими форматами, включая форматы W3C, такие как smilText и TTML.

Одной из основных проблем является то, что он не реализован сейчас ни в одном браузере, но не бойтесь – мы собираемся реализовать элемент самостоятельно, используя JavaScript. Это лучший защищенный от будущих изменений способ использовать титры сейчас. Когда однажды проблемы разрешатся, и браузеры реализуют элемент, нам не понадобиться изменять существующую разметку для использования собственных титров.

Техника, которую мы собираемся использовать, является в какой-то степени обратной версией техники создания титров Брюса Лоусона. Если вы не знакомы с ней, прочтите статью Улучшение доступности видео в HTML5 с помощью титров на основе JavaScript на сайте Dev Opera.

Описанная в этой статье техника описывает использование разметки HTML для определения титров, использование специальных атрибутов data для задания смещения времени для каждого титра. Затем выполняется синтаксический анализ элементов и создается объект JavaScript, который используется для отображения каждого титра в правильное время. Она использует также генерируемый с помощью CSS контент для вставки отметок времени в контент.

Мы собираемся изменить технику на прямо противоположную, и интерпретировать элементы <track> таким же образом, как может браузер.

Прежде всего, нам потребуется интерпретатор для файлов, так как мы собираемся использовать анализатор SRT Сильвии Пфайффер – посмотрите его обсуждение в следующей статье, и демонстрационный пример анализатора SRT.

Теперь в функции инициирования титров мы собираемся найти элементы <track>. Если мы найдем более одного элемента, мы сгенерируем разметку и позволим пользователю выбирать титры из списка. Спецификация включает атрибут label для элемента <track>, определяя его как "читаемый пользователем заголовок дорожки", который "используется агентами пользователя при перечислении дорожек подзаголовков, титров, и аудио описаний в своем пользовательском интерфейсе". Поэтому мы собираемся использовать этот атрибут в своем UI.

<ul>
  <li>
    <label>
      <input type="radio" name="acornCaptions" checked="checked" />
      None
    </label>
  </li>
  <li>
    <label>
      <input type="radio" name="acornCaptions" />
      English <!-- это атрибут "label" элемента <track> -->
    </label>
  </li>
  <li>
    <label>
      <input type="radio" name="acornCaptions" />
      Romani <!-- это атрибут "label" элемента <track> -->
    </label>
  </li>
</ul>

Мы используем неупорядоченный список с кнопками <input type="radio">.

Мы помещаем элементы <input> и текст в элементы <label>, чтобы вспомогательные технологии ассоциировали метку с кнопкой, не определяя <label> отдельно и присваивая ему уникальный ID и кнопку.

Реальный текст метки является контентом атрибута label элемента <track>.

Когда пользователь выбирает титры, мы загружаем их с помощью вызова Ajax, анализируем, создаем с каждым титром привязанный к времени объект, и генерируем также стенограмму.

Это звучит сложнее, чем является в действительности.

$.ajax({
  url: url,
  success: function(data) {
    // используем анализатор SRT на загруженных данных
    captions = parseSrt(data);
 
  // находим управляющую кнопку стенограммы и отображаем ее

    video.$transcript = video.$container.next('.acorn-transcript');
    video.$transcriptBtn.show();
 
    // генерируем разметку для стенограммы и добавляем ее

    var transcriptText = '';
    $(captions).each(function() {
      transcriptText += '' + this.content.replace("'","") + '';
    });
    video.$transcript.html(transcriptText);
 
    captionsActive = true;
    video.$captions.show();
 
    // в случае паузы видео и
// при отключенном timeUpdate,
// мы будем updateCaption (обновлять титры) вручную
 
if(video.$self.attr('paused')) updateCaption();
     
    video.$captionsBtn.addClass('acorn-captions-active').removeClass('acorn-captions-loading');
  },
  error: function() {

    // в случае ошибки при загрузке титров
// не отображайте ничего и покажите сообщение
// об ошибке, если присутствует консоль
 
    captionsActive = false;
    captions = '';
    video.$transcriptBtn.hide();
    video.$captionsBtn.removeClass('acorn-captions-active').removeClass('acorn-captions-loading');
 
    if(console) console.log('Error loading captions');
  }
});

Код достаточно просто понять, поэтому я собираюсь сосредоточиться больше на стенограммах. Можно видеть, что мы генерируем разметку и используем функцию jQuery html(), чтобы добавить их в контейнер стенограмм. Мы используем такую же разметку, как и техника Брюса Лоусона, но теперь для стенограммы, а не для титров.

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

Клавиши доступа?

Большинство доступных плееров мультимедиа и RIA реализуют некоторую форму клавиш доступа, либо с помощью стандартного атрибута accesskey, либо присваивая сложные комбинации клавиш с помощью JavaScript. Хотя это может показаться отличным решением для большинства разработчиков, обследования и примеры использования говорят об обратном.

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

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

Окончательная отделка

Наш плагин берет стандартный элемент <video> из HTML5 и создает для него доступные элементы управления, поддержку титров и стенограммы, и другие средства, но он не предоставляет никакой поддержки для описания видео. Это необходимо сделать без использования плагина.

Я предлагаю использовать элемент <figure> из HTML5 вместе с <figcaption>, и использовать атрибут aria-describedby для соединения элемента <video> с описанием. В этом случае, когда, например, считыватель экрана достигнет видео, он получит также его описание.

<figure>
  <video controls="controls" width="300" height="200" preload="metadata" aria-describedby="videodescription">
    <source src="/path/to/video.webm" />  
    <track src="/path/to/caption.srt" kind="captions" srclang="rom" label="Romani" />        
  </video>
  <figcaption id="videodescription">
    Трейлер короткого анимационного фильма "Sintel", проекта Durian Open Movie компании Blender Foundation.
    Дополнительная информация на сайте http://durian.blender.org. Это описание видео.
  </figcaption>
</figure>


--

Тест

Назовите новые элементы медиа, появившиеся в HTML5?
(Ответ считается верным, если отмечены все правильные варианты ответов.)
Вариант 1 <object>
Вариант 2 <embed>
Вариант 3 <audio>
Вариант 4 <video>

Какой атрибут тега <video> задает обложку видеофайла?
(Отметьте один правильный вариант ответа.)
Вариант 1 poster
Вариант 2 height
Вариант 3 width

Какой атрибут тега <video> приводит к циклическому повторению воспроизведения видео?
(Отметьте один правильный вариант ответа.)
Вариант 1 loop
Вариант 2 autoplay
Вариант 3 autobuffer

Укажите некорректные методы для создания на странице индивидуальных элементов управления медиа?
(Ответ считается верным, если отмечены все правильные варианты ответов.)
Вариант 1 play()
Вариант 2 stop()
Вариант 3 reverse()

Какие из приведенных ниже видеокодеков имеют поддержку в браузерах без установки дополнительных плагинов:
(Ответ считается верным, если отмечены все правильные варианты ответов.)
Вариант 1 WebM/VP8
Вариант 2 DivX
Вариант 3 ogg/theora
Вариант 4 On2 TrueMotion VP6
Вариант 5 H.264

Какой атрибут API видео HTML5 можно использовать для считывания или задания громкости аудио дорожки видео?
(Отметьте один правильный вариант ответа.)
Вариант 1 muted
Вариант 2 volume
Вариант 3 duration

Какое событие API видео HTML5 сообщает, что воспроизведение достигло конца видеофайла?
(Отметьте один правильный вариант ответа.)
Вариант 1 played
Вариант 2 paused
Вариант 3 ended
Вариант 4 loadeddata

С помощью какого элемента разметки HTML5 можно создавать на странице гибрид видео и другой графики?
(Отметьте один правильный вариант ответа.)
Вариант 1 <img>
Вариант 2 <object>
Вариант 3 <canvas>

Отметьте некорректные способы задания дополнительного видеофайла в альтернативном формате:
(Отметьте один правильный вариант ответа.)
Вариант 1
<video controls=controls poster=turkish.jpg>
<source src=turkish.ogv type=video/ogg>
<source src=turkish.mp4 type=video/mp4>
Download the <a href=video.ogg>Turkish dancing masterclass video</a>
</video>
Вариант 2
<video width=320 height=240 controls poster=turkish.jpg src=turkish.ogv >
<source src=turkish.mp4 type=video/mp4>
Download the <a href=video.ogg>Turkish dancing masterclass video</a>
</video>
Вариант 3
<video width=320 height=240 controls poster=turkish.jpg>
<source src=turkish.ogv type=video/ogg>
<source src=turkish.mp4 type=video/mp4>
Download the <a href=video.ogg>Turkish dancing masterclass video</a>
</video>

Какие элементы разметки предназначены для вставки титров в видео HTML5?
(Отметьте один правильный вариант ответа.)
Вариант 1 <sub>
Вариант 2 <subtitle>
Вариант 3 <track>

Какой элемент разметки устанавливает связь между текстовой меткой и элементом формы и используется при создании пользовательских элементов управления?
(Отметьте один правильный вариант ответа.)
Вариант 1 <label>
Вариант 2 <name>
Вариант 3 <title>

Какой из атрибутов элементов управления ARIA позволяет задавать минимальное значение, которое может иметь ползунок?
(Отметьте один правильный вариант ответа.)
Вариант 1 aria-value-min
Вариант 2 aria-valuenow
Вариант 3 aria-value-max