HTML5 INSIGHT


Ранее мы уже рассмотрели общие вопросы использования 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 в спецификации.



Гайдар написал про блочные ссылки в HTML5.



* Это четвертая статья (не считая практических отступлений) из серии погружения в HTML5, рассказывающей об основах HTML5. Рано или поздно они сложатся в единое изложение, которое можно будет удобно читать, сидя на диванчике и перелистывая странички поглядывая в свой Kindle.*

Canvas and SVG

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

История развития графики в интернете требует, конечно, отдельного повествования, но важно отметить, что приход HTML5 (и многих новых модулей CSS3) представляет собой значительный поворот в этой истории. HTML5 многое переворачивает с ног на голову и активно побуждает дизайнеров и разработчиков пересматривать свои взгляды на используемые технологии и подходы.

Canvas и SVG — это две независимые технологии, хорошо дополняющие друг друга, хотя и различные по своей природе и происхождению. Но, прежде чем мы перейдем к примерам, давайте сначала поговорим о контексте их появления, который сделал возможным столь большой интерес к Canvas и SVG и лишь подчеркивает значимость обеих технологий.

Контекст технологий и возможностей

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

Практики и традиции

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

В мире до HTML5 можно выделить три основных направления работы с графикой (со своими возможностями и ограничениями):

  • Статичная графика. Филигранное искусство работы с каждым пикселем, оптимизации до последнего байта. Веселые анимированные гифки и упорная борьба за прозрачность в старых браузерах. И, конечно, генерация графики на сервере.
  • Flash, flash, flash. Широко расправивший крылья мир богатых анимаций и программируемой графики в вебе, игрушек в браузере и неожиданных всплывающих баннеров. Целое поколение флеш-аниматоров и флеш-разработчиков!
  • JavaScript, HTML, CSS. В орбиту используемых инструментов включаются возможности CSS и JavaScript для динамичного изменения графики и создания различных эффектов. Спрайты и наложения изображений. И куда же без сотни хитрых способов сделать скругленные уголки?

Узнали себя?

Я повторюсь, годами (!) нарабатывались практики, набивались шишки, искались обходные пути и хитрые приемы — и все это, чтобы сделать хорошо конечному пользователю, то есть нам с вами. Не все было идеально, не все было гладко, многих возможностей хотелось, но не хватало.

В сущности, сильно не хватало двух вещей — векторной графики, широко поддерживаемой всеми браузерами, и возможности генерировать графические изображения на клиенте без необходимости использовать сервер либо Flash или Silverlight. И тут…

Бум!

Появляется HTML5 со встроенным API для генерации графики на клиенте и интеграцией с давно существующим форматом для векторной графики — SVG. Как только маркетинговая машина заработала на полную катушку, все производители браузеров включились в игру под названием “HTML5”. А разработчикам и дизайнерам теперь нужно учить новые технологии, придумывать новые возможности их применения и адаптировать и интегрировать наработанные ранее практики в новом контексте.

(Причины, по которым я акцентирую столь большое внимание на наработанном опыте банальны: во-первых, мы становимся свидетелями большой трансформации в вебе, в ходе которой менеяется и обновляется технологический стек, а значит, пора браться за учебники; и, во-вторых, накопленная практика не пропадает - но быстрыми темпами переносится на платформу HTML5 и адаптируется в новых реалиях. Не пропустите момент!)

Производительность

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

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

Рыбки. Рыбки произвели фурор! Когда Microsoft показала первую предварительную версию грядущего IE9 с аппаратным ускорением графики, это многих (в том числе производителей других браузеров) заставило хорошо задуматься. Конечно, всегда есть над чем еще работать, но это действительно был огромный рывок вперед в области динамичной графики в вебе.

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

Что такое Canvas и почему это круто?

Canvas — это растровый холст, на котором можно рисовать через специальный API для JavaScript, предоставляющий базовые возможности: отрисовку примитивов и текста, побитовый доступ к изображению, вывод изображений и афинные преобразования контекста отрисовки.

Спецификации

API для Canvas описывается в отдельной спецификации – HTML Canvas 2D Context, в то время как спецификация HTML5 задает непосредственно сам холст, вставляемый на страницу с помощью тега <canvas>.

Нюанс, который также является важным и активно обсуждается в W3C, — обеспечение Accesibility с Canvas, но это, определенно, не тема для отдельного обсуждения, выходящего за рамки данного введения.

Так в чем крутость Canvas?

Есть три вещи, которые мне особенно нравятся в Canvas:

  1. Понятность. Canvas прост. Если вы знаете JavaScript и имели опыт работы с графикой через примитивные 2D-API, разобраться с Canvas вам не составит труда. API Canvas действительно прост: есть около 40 методов и 20 атрибутов, которые вы легко можете разбить для себя на несколько групп (см. например, вот эту шпаргалку).
  2. Скорость. Canvas быстр. Примитивен и потому быстр. С поддержкой аппаратного ускорения и потому быстр. Canvas спроектирован так, чтобы быть быстрым, хотя какие-то вещи и могут на первый взгляд показаться непрактичными (например, нужно сначала нарисовать весь путь, прежде чем его прочертить).
  3. Интеграция. Canvas сочетаем — это обычный html(5) элемент и его можно вставить в любоем место страницы. Canvas можно комбинировать с видео, изображениями и другими canvas. Ему можно даже сделать круглые уголки.

Давайте смотреть примеры!

Как готовить Canvas?

Мы рассмотрим три примера, которые хорошо показывают характер взаимодействия с Canvas и некоторые базовые возможности.

Stop? It is just the beginning!

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

<canvas id="myCanvas" width="200" height="200" />

Зная id canvas-элемента, необходимо получить контекст для рисования:

var ctx = document.getElementById("myCanvas").getContext("2d");

Вся остальная работа осуществляется уже относительно этого контекста:

// Ширина и высота холста
var width = ctx.canvas.width;
var height = ctx.canvas.height;
            
// Радиус и центр круга
var radius = 0.4 * width;
var cx = width / 2;
var cy = height / 2;
            
// Делаем путь для круга от 0 до 2PI
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.closePath();
            
// Устанавливаем отрисовку с тенью
ctx.shadowBlur = 10;
ctx.shadowColor = "black";
            
// Устанавливаем ширину и цвет линии и отрисовываем
ctx.lineWidth = 25;
ctx.strokeStyle = "#f00";
ctx.stroke();
            
// Убираем тень
ctx.shadowColor = "transparent";
            
// Создаем радиальный градиент для заливки
var gradient = ctx.createRadialGradient(cx, cx, 0, cx, cy, radius);
gradient.addColorStop(0, "#ddd");
gradient.addColorStop(1, "#eee");
            
// Устанавливаем градиент и делаем заливку
ctx.fillStyle = gradient;
ctx.fill();
            
// Устанавливаем стили текста и центрирование
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "bold 55px 'Segoe UI', 'Tahoma', sans-serif";
ctx.fillStyle = "#333";
            
// Выводим надпись
ctx.fillText("STOP", cx, cy);

Результат:

В этом примере надо обратить на три момента:

  1. Сначала создается путь и только потом он отрисовывается или заливается с нужной закраской. Это важно с точки зрения оптимизации, так как, фактически, API для Canvas побуждает сначала целиком подготовить путь и только после этого с ним что-то делать.
  2. Практически все парамерты носят глобальный характер, например, если тень явно не отключить, все последующие отрисовки будут с тенью. Установленный стиль линии будет работать для всех послудующих отрисовок линий и т.д. (В Canvas также есть возможность запоминать и восстанавливать текущие настройки глобальных параметров.)
  3. Для установки стилей есть различные варианты: задание цвета в формате, аналогичном правилам CSS, создание градиента и использование шаблона.

Папоротник

Переходим к классике основ изучения компьютерной графики! Давайте посмотрим, как нарисовать фрактальный лист папоротника с помощью Canvas.

Как и в предыдущем примере мы работаем с небольшим canvas-элементом:

<canvas id="myCanvas" width="200" height="200" />

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

// Афинные преобразования для каждой из вероятностей
var barnsleyMatrix = [[    0,   0,     0, 0.16, 0,    0],
                      [ 0.85, 0.04,-0.04, 0.85, 0, 1.60],
                      [ 0.20,-0.26, 0.23, 0.22, 0, 1.60],
                      [-0.15, 0.28, 0.26, 0.24, 0, 0.44]];
            
// Суммы вероятностей (0.01, 0.85, 0.07, 0.07)
var probability = [0.01, 0.86, 0.93, 1.0];
            
// Контекст для работы
var ctx = document.getElementById("myCanvas").getContext("2d");
            
// Ширина и высота холста
var width = ctx.canvas.width;
var height = ctx.canvas.height;
            
// Пиксели для изображения
var pixels = ctx.createImageData(width, height);
            
var iterations = 50000;
var p, n;
var x = 0.0;            
var y = 0.0;
var xn, yn, xp, yp, j;
            
// Итерационный процесс
for (var i = 0; i < iterations; i++) {
    p = Math.random();                
    n = 0; while (p > probability[n]) n++;
                
    // Новые координаты
    xn = barnsleyMatrix[n][0] * x + barnsleyMatrix[n][1] * y + barnsleyMatrix[n][4];
    yn = barnsleyMatrix[n][2] * x + barnsleyMatrix[n][3] * y + barnsleyMatrix[n][5];
    x = xn;
    y = yn;
                
    // Переводим в экранные координаты
    xp = width/2 + Math.floor( x * 20);                
    yp = height - Math.floor(y * 20);
                
    // Находим место в одномерном массиве пикселей
    j = (yp * width + xp) * 4;
    // Устанавливаем значения для компонент RGBA
    pixels.data[j + 0] = 0;
    pixels.data[j + 1] = 180;
    pixels.data[j + 2] = 0;
    pixels.data[j + 3] = 255;
}

// Записываем итоговый массив пикселей 
ctx.putImageData(pixels, 0, 0);

Результат:

В этом примере надо обратить внимание на два момента:

  1. Доступ к пикселя осуществляется не в прямом виде, а через выгружаемый и записываемый массив пикселей (окно), причем операции чтения и записи разнесены в разные функции и во времени. Опять-таки, API побуждает разработчиков сначала произвести необходимые изменения в пикселях и уже после этого массово внести изменения вместо того, чтобы отдельно читать и записывать каждый из пикселей.
  2. Массив, с которым идет работа, одномерный (хотя у объекта типа ImageData и определены ширина и высота), причем в нем последовательно хранятся не просто пиксели, а значения по каждому из каналов для каждого из пикселей. Другими словами, на каждый пиксель приходится четыре элемента массива (RGBA) со значениями от 0 до 255.

Кстати, как я уже писал в начале, Canvas может легко сочетаться с обычными изображениями и даже видео, в частности, это означает, что на Canvas можно проектировать картинку или текущий кадр видео-потока. В сочетании с доступом к отдельным пикселям, это позволяет делать анализ изображений непосредственно на клиенте с помощью JavaScript.

Гипно-спираль

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

<canvas id="myCanvas" width="200" height="200" />

Прежде всего, нам понадобится функция для отрисовки спирали (тут вам уже практически все должно быть знакомо):

// Отрисовка спирали
function drawSpiral(context)
{
    var cx = 0;
    var cy = 0;

    // Количество звеньев
    var iterations = 500;

    context.moveTo(cx,cy);
    
    var x2 = cx, y2 = cy;

    for (var i = 0; i <= iterations; i++)
    {
        var angle = i / 10; // radians
        var radius = angle * 2;

        var x = cx + radius * Math.cos(angle);
        var y = cy + radius * Math.sin(angle);

        context.beginPath();
        
        var val = Math.floor(radius * 3);
        // Затухание спирали
        if (val > 255) val = 255;
        context.strokeStyle='rgb(' + val + ',' + val + ',' + (255) + ')';

        // Отрисовка звена
        context.moveTo(x2, y2);
        context.lineTo(x, y);        
        context.stroke(); 

        x2 = x;
        y2 = y;
    }
}

Теперь надо заставить ее вращаться – в принципе, для этого есть три решения:

  1. Можно просто поворачивать картинку (например, с помощью трансформаций CSS).
  2. Можно перерисовывать спираль, добавляя сдвиг угла поворота.
  3. Можно перерисовывать спираль как есть. Но виртуально поворачивать систему координат.

Последнее мы и будем делать.

// Контекст для работы
var ctx = document.getElementById("myCanvas").getContext("2d");
            
// Ширина и высота холста
var width = ctx.canvas.width;
var height = ctx.canvas.height;

// Стиль линии
ctx.lineCap = 'round';
ctx.lineWidth = 8; 
            
// Переносим начало координат в центр            
ctx.translate(width / 2, height / 2);
                    
// Ставим таймер
setInterval(function () {
    // Очищаем экран
    ctx.clearRect(-width / 2, -height / 2, width, height);
    // Поворачиваем систему координат
    ctx.rotate(-Math.PI / 180 );
    // Рисуем спираль
    drawSpiral(ctx);
}, 17);

Результат (в жизни она еще вращается):

В этом примере надо обрабить внимание на следующие моменты:

  1. Вместо того, чтобы поворачивать непосредственно спираль, мы виртуально поворачивали контекст отрисовки. Такой прием позволяет описать отрисовку спирали в удобных локальных координатах (заметили, что мы ее рисовали относительно центра cx = cy = 0?), а трансформацию вынести на внешний уровень. Также с помощью метода translate мы сместили начало координат в центр экрана.
  2. Canvas — примитивен. Canvas растровый и помнит только свое последнее состояние в виде массива пикселей. Мы не можем получить доступ к нарисованным линиям как отдельным объектам, которые можно было бы повернуть. Мы видим только последний слепок и чтобы нарисовать что-то новое (обновление экрана), надо делать перерисовку (через очистку экрана или поверх).

Canvas — это весело! Двигаемся дальше — к SVG.

Что такое SVG и почему это тоже круто?

SVG = Scalable Vector Graphics, или по-русски масштабируемая векторная графика, — стандарт (и язык) описания векторной и смешанной графики в формате XML. В спецификации HTML5 SVG присутствует как нативный тег <svg> с отсылкой уже на соответствующий стандарт.

Надо сказать, что SVG появился не сегодня, и не вчера, и не вместе с HTML5, а в общем-то давально давно – еще в 2001 году, а работы над SVG велись с 1999 года. Сегодня, SVG поддерживается большинством популярных векторных графических редакторов, из бесплатных рекомендую Inkscape.

SVG, как же я тебе рад!

Не вдаваясь в историю развития SVG (кстати, вторая редакция текущего стандарта, SVG 1.1, была окончательно одобрена 16 августа 2011 года), не могу не сказать пару слов о том, почему этот стандарт стал столь актуальным сегодня, хотя по факту использовался и заметно раньше. На мой взгляд, все дело в трех ключевых вещах:

  1. Маркетинг вокруг HTML5. SVG, формально включенный в состав HTML5, стал хорошей красной тряпочкой для демонстрации преимуществ новой версии стандарта. HTML5 позволяет работать с векторной графикой — это звучит слишком хорошо, чтобы умалчивать сей замечательный факт. Маркетинг, в свою очередь приводит ко второму фактору.
  2. Поддержка в браузерах. Производители браузеров не могут игнорировать тот факт, что им нужно реализовывать HTML5, если они хотят оставаться на плаву в браузерном рынке. Поэтому сегодня мы видим базовую поддержку SVG во всех основных браузерах, причем местами и с поддержкой аппаратного ускорения при отрисовке. Но маркетинга, конечно, было бы мало для того, чтобы браться за SVG, если бы не третий фактор.
  3. SVG — это круто! На самом деле, если еще несколько лет назад SVG рассматривался преимущество как стандарт для описания графики, то сегодня на него смотрят уже значительно шире. Потенциально, SVG — это не просто квадратики и кружочки с градиентными заливками, звездочки и фрагменты текста, но и возможность описания интерфейса для интерактивного взаимодействия, платформа для анимаций и визуализации данных. В контексте роста интереса к разработке веб-приложений роль SVG будет тоже расти.

К замечательным особенностям SVG также нужно отнести то, что базируясь на XML, вставленная в страницу SVG-графика имеет DOM-представление, которое можно изменять через JavaScript и настраивать отдельные свойства через CSS. Я уже говорил, что это круто?

Кухня SVG

Давайте начнем с простой задачи — посмотрим на примере, что из себя представляет SVG-графика и как ее вставить на страницу.

Show just begins!

Для начала нам понадобится SVG-картинка. Вы ее можете нарисовать в графическом редакторе или ручками в блокноте из известных тегов:

<?xml version="1.0" encoding="utf-8" ?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200px" height="200px" viewBox="0 0 200 200">
    <circle cx="102" cy="102" r="80" style="fill:#333" />
    <circle cx="100" cy="100" r="80" style="fill:#00aad4" />
    <circle cx="100" cy="100" r="65" style="fill:#eee" />
    <text x="40" y="110" style="font-size:22px;fill:#000000;font-family:Segoe UI;" >Don`t STOP!</text>
</svg>

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

<img src="dont-stop.svg" />

В результате вы должны увидеть такую картинку:

Изображения в формате SVG можно также вставлять через теги object или embed, однако, что в этом случае, что в случае с img важно помнить, что вставленная картинка не будет предоставлять наружу возможность взаимодействовать через JavaScript или SVG.

Обратите также внимание на то, что описание стилей для различных элементов очень сильно похоже на привычную вам работу с CSS для обычных элементов HTML-разметки.

Также важным моментом является то, что описание SVG принципиально текстовое, а значит может индексироваться поисковиками, которые умеют вытаскивать из картинок текстовую информацию (например, внутренние подписи).

Давайте сделаем что-нибудь с анимацией?

Покачаемся?

На сайте “The Noun Project” собрана большая коллекция различных SVG иконок. Среди них должны быть качели. В качелях спрятано пасхальное яйцо — при наведении мыши качели начинают качаться! Как это сделано?

Начнем с того, как выглядят качели изнутри (так выгляди SVG-файл, экспортированный из Adobe Illustrator):

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 width="100px" height="54.805px" viewBox="0 0 100 54.805" enable-background="new 0 0 100 54.805" xml:space="preserve">
<g id="rotator" transform="rotate(0)">
	<circle cx="14.466" cy="17.737" r="4.264" />
	<circle cx="77.027" cy="4.167" r="4.264" />
	<path d="M 77.207 27.417 l -1.003 4.827 l 4.959 1.267 l 4.274 -7.878 L 100 22.474 l -1.143 -5.267 l -5.006 1.085 c 1.573 -3.747 -1.045 -5.289 -1.045 -5.289 L 82.312 6.087 l -4.217 3.005 l -4.084 6.25 c 0 0 -1.974 3.023 -0.126 4.636 l 0.192 2.604 L 24.779 33.273 l -0.903 -2.447 c 1.013 -2.233 -2.036 -4.167 -2.036 -4.167 l -6.306 -3.996 l -5.082 -0.989 L 3.764 32.316 c 0 0 -1.747 2.488 1.239 5.246 L 0 38.648 l 1.142 5.269 l 14.562 -3.158 l 7.155 5.398 l 3.989 -3.207 l -2.913 -3.978 L 77.207 27.417 Z M 20.043 34.301 l -5.588 -2.679 l 1.379 -4.267 l 5.61 2.643 l 1.367 3.702 L 20.043 34.301 Z M 78.814 21.554 l -2.771 0.602 l -0.29 -3.938 l 4.014 -4.728 l 3.022 3.31 L 78.814 21.554 Z" />
</g>
	<polygon points="49.999,33.319 44.807,37.859 44.807,54.804 50,54.804 55.193,54.804 55.193,37.859" />
</svg>
</svg>

(Конечно, если быть до конца честным, это не от файл, который вы можете скачать напрямую с сайта. И если вы скачали файл, то легко увидите разницу. Содержимое этого примера можно вытащить из текста страницы ;) Я также добавил в этом примере id для группирующего элемента g.)

В этом примере верхняя часть качелей отделена от нижней и объединена в общую группу. Именно она нас и интересует. Вы легко можете заметить атрибут transform со свойством “rotate(0)” и, наверняка, уже догадались, что будет дальше.

Внутри SVG-документа можно писать код на JavaScript! Вставьте сразу после тега svg вставьте следующий код:

<script type="text/javascript">
    window.onload = function() {    
        var g = document.getElementById("rotator");
        
        var i = 0;
        var timer = setInterval(function() {
            var rad = i++ / 360 * Math.PI * 2;
            var rotateVal = 14 * Math.sin(10 * rad - Math.PI / 2) + 12;
            g.setAttribute("transform", "rotate(" + rotateVal + " 50 27)");      
        }, 40);    
    };</script>

Осталось запустить документ. Правда, если вы его вставить картинкой (img), качели вращаться не будут, так как браузер не разрешит исполнение JavaScript-кода внутри картинки. Другое дело вставленный объект!

<img src="seesaw.svg" />        
<embed src="seesaw.svg" type="image/svg+xml" width="100" height="55" />                
            

Результат:

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

Давайте теперь вставим SVG непосредственно внутрь страницы?

Играем в Громова и Собянина!

Первым делом, вам понадобится карта административного деления Московской области.

Я не буду приводить ее внутренее содержимое тут, но из содержимого файла нам надо извлечь все, кроме заголовочного тега xml-файла и подчистить содержание внутри тега svg. Также можно удалить defs-блок вместе с CSS-правилами. В таком виде его можно спокойно вставить внутрь обычной html-страницы:

<svg width="400px" height="400px" viewBox="0 0  25220 23850">
    <g id="Layer_x0020_1">
        <metadata id="CorelCorpID_0Corel-Layer"/>
        <g id="_143023512">
    ... 
</svg>

В начале страницы давайте добавим несколько стилей, которые нам понадобятся дальше:

<style>
    .moscow, .fil7 { fill: red; }
    .fil1 {fill:none}
</style>
            

Теперь самое время перейти к тому, чтобы добавить немного логики. Чтобы упростить себе задачу, давайте добавим на страницу ссылку на jQuery:

<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.5.1.min.js" type="text/javascript"></script>

Теперь нужно написать небольшую логику, которая по нажатию на тот или иной регион будет перекрашивать его в другой цвет, включая или не включая в состав Москвы:

$(function(){
    $("#_143023512 polygon").each(function (i) {
        $(this).click(function(){
            var classes = this.getAttribute("class");

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

            this.setAttribute("class", classes);	                    
        });
    }); 
});

Теперь вы можете сами решить, какими будут новые границы Москвы! (А если найдете детальную карту мира, сможете вообще перекроить все страны!)

Из этого примера нужно сделать два вывода:

  1. SVG отлично встраивается в HMTL5-документы и интегрируется с CSS и JavaScript через DOM.
  2. Сегодня не все так гладко, как хотелось бы. В частности, т.к. между интерфейсами для доступа к css-классам элементов SVG и HTML есть различия, полноценно натравить на SVG популярные библиотеки оказывается проблематичным.

Подходим к финишной черте! Несколько мыслей о том, когда лучше использовать Canvas, когда SVG, и в чем между ними принципиальная разница.

Canvas vs. SVG

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

  • Canvas — растровый холст с доступом к отдельным пикселям, хранящий только последнее свое состояние, которое можно вытащить в виде массива значений цвета и прозрачности (RGBA) для разных пикселей. SVG — векторное масштабируемое изображение с доступом к отдельным объектам, легко поддающееся индексации.
  • Для работы с Canvas есть специальный API с набором примитивных функций. Для работы с внутренностями SVG можно использовать DOM и CSS.
  • JS-код для Canvas пишется отдельно от элемента <canvas>. JS-код для SVG можно как писать отдельно, так и вставлять прямо внутрь SVG-документа.
  • И то, что мы не обсуждали подробно: на Canvas можно спроектировать изображение или видео, но не элемент SVG.

Что и когда следует использовать?

Выбирая между Canvas и SVG, следуют отталкиваться от двух вещей: характера решаемых задач и производительности.

Canvas безальтернативно следует использовать для обработки изображений и видео, хорошо подохдит для сложных сцен с множеством объектов и можно хитро использовать в веб-рекламе.

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

Обе технологии, можно спокойно применять для отрисовки графиков и диграмм и для двумерных игрушек.

Что касается производительности, то тут все достаточно просто: до определенного уровня зависимости скорости отрисовки Canvas и SVG от размера экрана и количества объектов двигаются рядом, но далее оказывается, что SVG лучше подходит для изображений большого размера, а Canvas показывает себя проворнее при большом количестве объектов.

В остальном — надо смотреть на то, с помощью чего конкретную вашу задачу будет решить проще всего.

p.s. Пожалуйста! Пожалуйста, никогда не делайте баннеры на HTML5.

p.p.s. Как бы там ни было, злая сторона меня подсказывает, что на рекламе средствами HTML5 можно сделать неплохой бизнес ;)



*Мысль этого топика тривиальная и должна быть известна каждому: используйте асинхронные запросы. Но статистика – штука суровая, и, видимо, это знают не все.*

8.4% всех зависаний IE9 за прошедший месяц являются следствием того, что XMLHttpRequest объекты блокируют поток UI синхронным запросом. Это огромное число! С помощью нескольких доступных изменений в коде эти зависания можно легко избежать – и разработчики могут обеспечить своим пользователям намного лучшие впечатления от работы с их сайтами. Мы рассмотрим, что происходит при зависании, что вы можете с этим сделать, и мы также попробуем сделать небольшую демонстрацию, чтобы воочую посмотреть, что происходит, когда синхронный запрос подвешивает браузер.

Синхронный XMLHttpRequest блокирует поток UI

Мы уже знаем, что запуск блокирующих операций в потоке UI может приводить к проблемам. Мы уже писали об этом раньше. Это может быть неочевидным, но это именно то, что происходит, когда метод XMLHttpRequest.send() вызывается синхронно (например, путем передачи значения false для атрибута bAsync при вызове метода open()). Поток UI должен дождаться, пока получить ответ от сервера или пока не произойдет таймаут для запроса. Пока идет ожидание, сообщения не проходят.

Зачем вообще использовать синхронне XMLHttpRequest?

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

Как исправить…

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

  1. Пишите свой код так, чтобы запросы были асинхронными

    Вам нужно передать значение true для bAsync параметра в методе open(), и вам также нужно будет написать обработчик для события onreadystatechange. В интернете есть множество примеров, как это можно сделать, например, вот этот.
  2. Установите свойство timeout.

    Так как Desktop Window Manager обнаруживает зависание через 5 секунд без ответа, а восстановление после зависания в IE срабатывает через 8, я рекомендую устанавливать timeout-свойство в не более, чем 5 секунд. Также, возможно, вы захотите поставить обработчик для реакции на событие ontimeout. (Откуда я знаю эти числа? Читайте подробнее здесь и здесь)

Как насчет примера?

Давайте посмотрим, как это все работает, детальнее, на простом сценарии:

  1. Мы напишем код, который отсылает синхронный запрос и этим подвешивает браузер, и далее
  2. заменим код на асинхронный и посмотрим на изменения в поведении браузера.

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

ПРЕДУПРЕЖДЕНИЕ: Этот пример подвесит ваш браузер и может привести к непредсказуемым и даже нежелаемым результатам как на стороне клиента, так и на стороне сервера. Другими словами, не запускаете его на продакшн-сервере или любой другой машине, которая не может корректно отработать нестабильный и потенциально неправильный код.

Установка

Вам понадобится сервер, способный запускать ASP.NET страницы, если вы хотите повторить непосредственно пример, приведенный ниже. Я понимаю, что, возможно, не все читатели используют ASP.NET, но код должен быть достаточно прозрачным, чтобы вы могли без проблем воспроизвести его в другом окружении.

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

Скопируйте этот кода в ваш любимый текстовый редактор и сохраните его как hangme.aspx где-нибудь на вашем сервере.

<!-- code starts after this line --> 

<%@ Page Language="C#" %>

<html>

<head>

    <title>XmlHttpRequest open hang test</title>

    <script runat="server">

        protected void Page_Load(object sender, EventArgs e) {

            if(Request.QueryString["hang"] == "1") {

                int seconds = 0;

                Int32.TryParse(Request.QueryString["seconds"], out seconds);

                System.Threading.Thread.Sleep(seconds * 1000);

            }

        }

    </script>

    <script type="text/javascript">

        function call_hangme() {

            var oReq;

            if (window.XMLHttpRequest) {

                oReq = new XMLHttpRequest();

            }

            if (oReq != null) {

                var sUrl = "http://localhost/hangme.aspx?hang=1&seconds=360";

                // change localhost to your server name if applicable

                document.getElementById("txt1").innerHTML = "Request Sent...";

                // pass false for the bAsync parameter for a synchronous request

                oReq.open("GET", sUrl, false);

                oReq.send();

                document.getElementById("txt1").innerHTML = "Response Received!";

            }

            else {

                window.alert("Error creating XmlHttpRequest object.");

            }

        } 

    </script>

</head>

<body>

    <p>Click this button to hang the browser</p>

    <form name="form1" action="" method="get">

        <input type="button" name="btn1" value="hang me" onClick="call_hangme()">

    </form>

    <p id="txt1"/>  

</body>

</html> 

<!-- code ends on the line before this one --> 

Давайте его подвесим!

Теперь,

  1. Откройте Internet Explorer и перейдите на страницу hangme.aspx.
  2. Нажмите на кнопку “hang me”.

Ух-ох! Мы зависли! Так как мы сказали потоку поспать 6 минут, нам придется тут немного поторчать. Если вы используете IE9, вы наверняка, заметите, золотистую плашку снизу, предлагающую “Восстановить страницу”. Если вы используете другой браузер, скорее всего, вы получите замороженное окно.

Используем асинхронное открытие запроса

Скопируйте этот код в новый файл и сохраните его как wont_hangme.aspx.

<!-- code starts after this line --> 

<%@ Page Language="C#" %>

<html>

<head>

    <title>XmlHttpRequest open hang test</title>

    <script runat="server">

        protected void Page_Load(object sender, EventArgs e) {

            if(Request.QueryString["hang"] == "1") {

                int seconds = 0;

                Int32.TryParse(Request.QueryString["seconds"], out seconds);

                System.Threading.Thread.Sleep(seconds * 1000);

            }

        }

    </script>

    <script type="text/javascript">

        function call_hangme() {

            var oReq;

            if (window.XMLHttpRequest) {

                oReq = new XMLHttpRequest();

            }

            if (oReq != null) {

                var sUrl = "http://localhost/wont_hangme.aspx?hang=1&seconds=360";

                // change localhost to your server name if applicable

                document.getElementById("txt1").innerHTML = "Request Sent...";

                // pass true for the bAsync parameter 

                oReq.open("GET", sUrl, true);

                

                /* Here we define an anonymous function for the

                onreadystatechange event.  We check if we received all the data,  

                and the request was successful before changing the text */

                oReq.onreadystatechange = function() {

                    if (oReq.readyState == 4 && oReq.status == 200) {

                        document.getElementById("txt1").innerHTML = "Response Received!";

                    }

                }

                

                oReq.send();

            }

            else {

                window.alert("Error creating XmlHttpRequest object.");

            }

        } 

    </script>

</head>

<body>

    <p>Click this button to hang the browser</p>

    <form name="form1" action="" method="get">

        <input type="button" name="btn1" value="hang me" onClick="call_hangme()">

    </form>

    <p id="txt1"/>  

</body>

</html> 

<!-- code ends on the line before this one --> 

Новый код подсвечен. Здесь мы теперь вызываем метод open() с параметром bAsync = trur, что означает, что мы делаем асинхронный вызов. Это освободит поток UI для других задач вместо того, чтобы ждать ответа или таймаута. Если мы не заблокированы, мы можем пересылать сообщением, а если так, то мы не зависли!

Теперь, когда ответ от сервера рано или поздно приходит к нам назад, нам понадобится функция, чтобы его обработать. Именно это и делает подсвеченный код. Это наш обработчик для события onreadystatechange. Как вы можете видеть, я всего лишь проверяю, что запрос успешно прошел (status == 200) и что все данные были получены (readyState == 4).

Можем мы подвесить еще раз?

Снова,

  1. Откройте Internet Explorer и перейдите на страницу wont_hangme.aspx.
  2. Нажмите кнопку “hang me”.

Voilà! Как вы видите, запрос отправлен, но браузер не завис. Когда ответ все-таки придет назад к нам, тект должен поменяться; но как бы там ни было, вы по-прежнему можете взаимодействовать со страницей.

В заключение

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

Source: Why You Should Use XMLHttpRequest Asynchronously



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

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

Определение поддержки аудио и видео

Начнем с простой задачи: как понять, поддерживает ли браузер пользователя вставку аудио- и видео-элементов? 

Для решения этой задачи достаточно попытаться создать новый элемент с соответствующим тегом и посмотреть на результат:

var audio = document.createElement("audio");
// audio
/* [object HTMLAudioElement] {
autobuffer : false,
autoplay : false,
buffered : [object TimeRanges],
controls : false, currentSrc : "",
currentTime : 0,
defaultPlaybackRate : 1,
duration : NaN,
ended : false,
error : null ... }
*/

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

audio instanceof HTMLAudioElement;
// true

(Если браузер поддерживает создание новых элементов, в том числе с произвольным названием, но не умеет поддерживать конкретный тип элемента, то результатом будет объект HTMLUnknownElement. Для старых браузеров это может быть не верно – и результатом будет объект какого-нибудь другого типа или просто Object с некоторыми свойствами, характерными для HTML-элементов.)

На этом этапе должно быть очевидно, что в принципе, достаточно через блок try-catch проверять поддержку браузером объекта HTMLAudioElement или HTMLVideoElement соответственно.

var videoSupported = false;
try {
HTMLVideoElement;
videoSupported = true;
} catch (e) {};
videoSupported;
// IE9 - true
// IE8 - false

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

Определение поддержки формата/кодека

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

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

Чтобы решить эту задачу программно, в медиа-элементах есть специальный метод, общий для аудио и видео (формально, HTMLAudioElement и HTMLVideoElement оба реализуют интерфейс HTMLMediaElement), – canPlayType(), принимающий в качестве параметра строку описания типа медиа-файла.

var support = videoElement.canPlayType('video/x-new-fictional-format;codecs="kittens,bunnies"');

Если браузер знает, что он не поддерживает данный формат, например, из-за того, что он не умеет работать с указанным типом контейнера или нужного кодека нет в его списке поддерживаемых кодеков, – он должен вернуть пустую строку.

Пустая строка также возвращается, если в качестве типа указан “application/octet-stream” – общий mime-тип для бинарных файлов.

Если браузер уверен, что он сможет проиграть медиа-файл в заданном формате, он возвращает “probably”. В остальных случаях возвращается “maybe”.

Разница между “probably” и “maybe” может быть неочевидной, особенно с учетом близости значений двух слов (по крайней мере, в русском языке).

На языке спецификации, надо понимать два ключевых момента:

  1. И “probably” и “maybe” – это вероятностные вещи. Конечно, вы не получите числового выражения от 0 до 1 для каждого из этих значений, но важно понимать, что ни одно из них не равно 1.
  2. Ключевой момент различия между “probably” и “maybe” заключается в том, что если указан формат и известно, что для этого формата могут быть разные кодеки, но кодек не указан, браузер никогда не должен возвращать “probably” – он вернет “maybe”, так как могут быть комбинации кодеков, которые он не поддерживает.

Например, вот так это выглядит для IE9:

video.canPlayType('video/x-new-fictional-format;codecs="kittens,bunnies"'); 
// ""
video.canPlayType('video/mp4');
// "maybe"
video.canPlayType('video/mp4;codecs="avc1.42E01E, mp4a.40.2"');
// "probably"
video.canPlayType('video/mp4; codecs="avc1.58A01E, mp4a.40.2"');
// "probably"
video.canPlayType('video/mp4; codecs="mp4v.20.8, mp4a.40.2"');
// ""

Важная деталь, о которой важно помнить, заключается также в том, что сервер, который отдает вам медиа-файлы, должен быть корректно настроен, то есть отдавать видео и аудио с правильным mime-type. В противном случае, из соображений безопасности, браузер может блокировать работу с этими файлами.

Например, для IIS это можно проверить в панели управления и добавить недостающие форматы там же или через web.config:

<system.webServer>
<staticContent>
<mimeMap fileExtension=".mp4" mimeType="video/mp4" />
<mimeMap fileExtension=".m4v" mimeType="video/m4v" />
<mimeMap fileExtension=".webm" mimeType="video/webm" />
</staticContent>
</system.webServer>

Теперь, возвращаемся к обещанному трюку. Учитывая, что метод canPlayType есть только у медиа-элементов (в отличие от остальных типов элементов), его наличие является хорошим способом определить, что созданный элемент является именно медиа-элементом.

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

var audio = document.createElement("audio");
if (audio != null && audio.canPlayType && audio.canPlayType("audio/mpeg")){
audio.src = "audio/sample.mp3";
audio.play();
}

Наконец, давайте посмотрим, как проверка поддержка аудио и видео осуществляется через популярную библиотеку Modernizr. (Для любителей ASP.NET MVC напомню, что она также включена в состав  ASP.NET MVC 3.)

Использование Modernizr

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

Проверка поддержки аудио-элемента и популярных кодеков:

tests['audio'] = function() {
var elem = document.createElement('audio'), bool = false;
try {
if ( bool = !!elem.canPlayType ) {
bool = new Boolean(bool);
bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"');
bool.mp3 = elem.canPlayType('audio/mpeg;');
bool.wav = elem.canPlayType('audio/wav; codecs="1"');
bool.m4a = elem.canPlayType('audio/x-m4a;') || elem.canPlayType('audio/aac;');
}
} catch(e) { }
return bool;
};

 Проверка поддержки видео-элемента и популярных кодеков:

tests['video'] = function() {
var elem = document.createElement('video'), bool = false;
try {
if ( bool = !!elem.canPlayType ) {
bool = new Boolean(bool);
bool.ogg = elem.canPlayType('video/ogg; codecs="theora"');
// Workaround required for IE9, which doesn't report video support without audio codec specified.
// bug 599718 @ msft connect
var h264 = 'video/mp4; codecs="avc1.42E01E';
bool.h264 = elem.canPlayType(h264 + '"') || elem.canPlayType(h264 + ', mp4a.40.2"');
bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"');
}
} catch(e) { }
return bool;
};

(Про упоминаемый баг IE9 – надеюсь, в Modernizr это обновят, т.к. в финальной версии IE9 это давно поправлено.;)

Из приведенных примеров должен быть очевиден способ использования библиотеки для определения поддержки аудио/видео и соответствующих кодеков:

if (Modernizr.audio) {
var audio = new Audio();
audio.src = Modernizr.audio.mp3 ? 'background.mp3' :
             Modernizr.audio.ogg ? 'background.ogg' :            
                                   'background.m4a';
audio.play();
} else {
// Ooops.
}

Другие тонкости использования медиа-элементов HTML5 мы рассмотрим как-нибудь в другой раз ;)



HTML5 ★ Boilerplate обновился до версии 2.0 – со множеством больших и маленьких изменений.



Не про HTML5, но просто не могу не поделиться, особенно после того, как встретил ссылку уже в нескольких дизайнерских местах :)

Тренируйтесь узнавать шрифты по начертанию символов.



The Noun Project

– sharing, celebrating and enhancing the world’s visual language.

Большая и растущая коллекция простых и понятных SVG-иконок на самые разные темы.



*Продолжаем погружаться в тему HTML5 – и самое время перейти к практическим примерам.*

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

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

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

Что может и чего не может <audio>

<audio>-элемент HTML5, как вы, наверняка, уже догадались, сам по себе никакого низкоуровневого API не предоставляет. Он позволяет только управлять воспроизведением аудио-потока: запускать, ставить на паузу, останавливать проигрывание, узнавать текущую позицию и общую длительность композиции.

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

Причем это будет зависеть только от возможностей, предоставляемых спецификацией, но и (в большей степени) от реализации в конкретном браузере – не случайно Rovio и Google, делая Angry Birds на HTML5, оптимизированную для Chrome, отказались от идеи использовать для звуков audio-элементы HTML5. Вместо этого “Angry Birds на HTML5” использует Flash. (См. также обсуждение в блоге разработчиков.)

Для более глубокого погружения в тему <audio>-элемента также рекомендую статью Unlocking the power of HTML5 <audio>, описывающую основные приемы работы аудио в HTML5. 

Стандарты на извлечение звука

Как я уже упоминал в конце статьи про введение в аудио и видео, в настоящее времянад созданием низкоуровневого API для доступа к аудиопотоку уже активно ведется в рамках Audio-группы W3C.

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

The audio API will provide methods to read audio samples, write audio data, create sounds, and perform client-side audio processing and synthesis with minimal latency. It will also add programmatic access to the PCM audio stream for low-level manipulation directly in script.

На сегодня Mozilla и Google уже успели предоставить собственные версии API для доступа к аудио-информации.

Audio Data API от Mozilla предоставляет простой доступ к аудио-потоку на чтение и запись, задача реализации алгоритмов обработки аудио в реальном времени должна при этом решаться на стороне скрипта (на JavaScript). Спецификация для Webkit – Web Audio API от Google – предоставляет высокоуровневый API, при котором основные задачи обработки могут выполняться нативно браузером.

Рабочая группа W3C работает над выработкой общего подхода, в котором будет предоставлен двуслойный API для обеспечения более широких возможностей.

К слову, область деятельности группы помимо клиентского API для работы с Audio включает также задачи доступа к аудио-устройствам, включая микрофоны и другие источники звука, и работы с колонками, в том числе в многоканальном режиме.

Следить за новостями группы можно в твиттере @w3caudio.

Но это все лирика, давайте к практике!

Практический подход: что работает сегодня?

Практический подход, работающий сегодня – это предобработка.

Да-да! Вот так банально. Предобработка аудио-информации с последующей генерацией визуализации, синхронизированной с воспроизводимым аудио-потоком.

На самом деле, если речь идет о смысловом извлечении информации (например, текста песни), то предобработка – единственный практичный выход, причем как правило, эта обработка делается ручками.

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

Давайте смотреть, как это работает. 

Пример из жизни:  Chell in the Rain

Chell in the Rain – это красивая аудио-текстовая визуализация песни Exile Vilify. Синхронно с  аудио-потоком на экране возникают слова из текста песни.

Что внутри

  • jQuery + Sizzle.js (для селекторов)
  • jPlayer (для проигрывания Audio и Video)
  • собственный код, который нам, собственно и интересен ;)

Как все работает  

Пропуская инициализацию аудио и обработчики событий для управления воспроизведением.

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

var timings = newArray();
timings[0] = 11.5;
timings[1] = 17;
timings[2] = 24;
timings[3] = 29;
timings[4] = 35.5;

Отдельно хранится массив фраз из текста песни:

var lyrics = newArray();
lyrics[0] = 'Exile';
lyrics[1] = 'It takes your mind... again';
lyrics[2] = "You've got sucker's luck";
lyrics[3] ='Have you given up?';

С привязкой к таймингу и на основании текущего момента в аудио-композиции срабатывает триггер на переход к новой фразе:

if(event.jPlayer.status.currentTime >= timings[currentTrigger] && nolyrics != true) {
    fireTrigger(currentTrigger);
    currentTrigger++;
}

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

function fireTrigger(trigger) {
   switch (trigger) {
      case 0:
         $('#lyrics1 p').addClass('vilify').html(lyrics[0]).fadeIn(1500);
         break;
      case 1:
         $('#lyrics2 p').html(lyrics[1]).fadeIn(1000).delay(5000).fadeOut(1000);
         $('#lyrics1 p').delay(6000).fadeOut(1000);
         break;
      case 2:
         $('#lyrics1 p').fadeIn(1000);
         break;
      case 3:
         $('#lyrics2 p').fadeIn(1000).delay(4000).fadeOut(1000);
         $('#lyrics1 p').delay(5000).fadeOut(1000);
         break;
      case 4:
         $('#lyrics1 p').removeClass('vilify').html(lyrics[2]).fadeIn(1000);
         break;
      case 5:
         $('#lyrics2 p').html(lyrics[3]).fadeIn(1000).delay(3000).fadeOut(1000);
         $('#lyrics1 p').delay(4000).fadeOut(1000);
         break;
...

Довольно просто и эффектно, согласитесь! Самое главное во всей этой истории – это легкость совмещения аудио-потока и возможностей HTML, CSS и JavaScript.

Пример из жизни: Music Can Be Fun

Music Can Be Fun – мини-игра на стыке искусства и музыки. Предлагаю сначала немного поиграться, чтобы было понятно, о чем пойдет речь ;)

Пример посложнее – и здесь уже активно используются возможности Canvas, но так как нас интересует только музыкальная составляющая, то все не так страшно!

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

var _lyrics = [
   ["00:17.94", "00:22.39", "When I have once or twice"],
   ["00:23.93", "00:30.52", "Thought I lived my life .. for"],
   ["00:40.74", "00:47.38", "Oh oh I'll wake up in a thousand years"],
   ["00:48.40", "00:52.06", "With every ghost I'm looking through"],
   ["00:53.33", "00:57.80", "I was a cold, cold boy"],
   ["00:59.52", "01:03.00", "Hey! Oh when I lie with you"],
...

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

var _effects = [
   ["00:06.00", 1],
   ["00:30.50", 1],
   ["00:42.50", 1],
   ["00:54.50", 2],
   ["00:57.00", 1],
...

(На самом деле, ко времени привязаны даже частоты появления синих и красных шариков ;)

При обновлении момента проигрывания (событие onTimeUpdate) происходит применение тех или иных визуализаций:

var _onTimeUpdate = function() {
   var t = MusicManager.currentTime = _song.currentTime;
   ...

   for (var i = _lyricsId; i < _lyrics.length; i++) {
      if (MusicManager.currentTime < _lyrics[i][0]) break;
      if (MusicManager.currentTime < _lyrics[i][1]) {
         SubtitleManager.changeSubtitle(_lyrics[i][2]);
      } else {
         SubtitleManager.changeSubtitle("");
      _lyricsId++;
      }
   }

   for (var i = _effectsId; i < _effects.length; i++) {
      if (MusicManager.currentTime < _effects[i][0]) break;
      MusicManager.isEffect1Used = false;
      MusicManager.isEffect2Used = !_effects[i][1] == 2;
      _effectsId++;
   }
...
}

По-прежнему просто и эффективно. Один и тот же прием легко применим не только к текстовой информации, но и к различным визуальным эффектам.

Остается понять, можно ли второе как-то автоматизировать, чтобы уж совсем все не делать ручками. Очевидно, можно – и Grant Skinner в своем блоге подсказывает, как это сделать ;)

Пример из жизни: извлечение данных

В своем блоге в посте Music Visualizer in HTML5 / JS with Source Code Грант делится своим опытом в визуализации аудио с помощью HTML5.

Столкнувшись с тем, что HTML5 Audio не предоставляет API для экстракции низкоуровневых данных о проигрываемой композиции, Грант написал небольшое AIR-приложение (архив также содержит примеры), позволяющее вытащить из mp3-файла информацию об уровнях звука в текстовом виде или в виде изображения.

В увеличенном масштабе информация о музыкальной композиции выглядит так:

Имея данные в таком виде, их можно легко извлечь, например, средствами Canvas. В текстовом виде все еще проще (пример не привожу, т.к. данные в текстовом файле упакованы).

Чтобы работать с такими предобработанным данными, Грант написал специальную библиотеку на JavaScript (VolumeData.js в архиве).

Работа с библиотекой осуществляется довольно просто. Начинается все с загрузки информации о композиции:

loadMusic("music.jpg");

где, внутри функции loadMusic, как вы уже догадались, загружается обычная картинка:

function loadMusic(dataImageURL) {
   image = new Image();
   image.src = dataImageURL;
   playing = false;
   Ticker.addListener(window);
}

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

volumeData = newVolumeData(image);

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

var t = audio.currentTime;
var vol = volumeData.getVolume(t);
var avgVol = volumeData.getAverageVolume(t-0.1,t);
var volDelta = volumeData.getVolume(t-0.05);
volDelta.left = vol.left-volDelta.left;
volDelta.right = vol.right-volDelta.right;

Визуальные эффекты привязываются к этим данным. Для визуализации используется библиотека EaselJS.

Посмотреть, как это работает на практике, можно в примерах Star Field и Atomic.

Заключение

Подводя итог, остается только сказать, что смотря на все это, меня не покидает ощущение, что с HTML5 индустрия движется в правильном направлении. Да, еще не все возможно, и далеко не все вещи делаются так же легко (и вообще возможны), как их сегодня можно делать во Flash или Silverlight. Но многое уже на горизонте!



* Это третья статья из серии погружения в HTML5 — их можно рассматривать как заметки к более объемному труду, который также постепенно готовится, но вместе с тем, они могут служить и просто кратким в введением в тематику HTML5*

Потребность воспроизводить аудио и видео информацию в браузере без использования плагинов и расширений сегодня кажется все более и более естественной. (Ок, это фраза из ближайшего будущего.)

На практике, большинство пользователей не отдает себе отчета в том, что большинство аудио и видео информации в интернете сегодня воспроизводится через Flash или Silverlight. Если оглянуться назад в историю, мы еще можем вспомнить воспроизведение видео через Media Player в браузере (мы к этой истории еще вернемся).

Мастодонты веб-разработки, наверняка, должны помнить про элемент bgsound (Пожалуйста, никогда… Слышите? Никогда его больше не используйте.)

И все-таки…

Зачем в браузере нужна нативная поддержка аудио и видео

Хотя я уже касался этого вопроса в первой статье (см. раздел “Почему HTML5 — это мега-мега-круто”) и, в основном, остается только повторить доводы из нее, я начну с другого.

Если посмотреть на эволюционный контекст развития HTML5, то на заднем фоне сильно начнет мелькать не только заметно обострившаяся конкуренция браузеров, но и целый форпост нового поколения мобильных устройств: от непосредственно телефонов и сматрфонов до энергоэффективных нетбуков и планшетных устройств на всяких армах (ARM) и атомах (Intel Atom).

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

Если говорить простым словами, то ситуация следующая. Мобильные устройства должны быть эффективными и кушать мало энергии в ходе своей работы, чтобы долго-долго радовать своих владельцев. Простая мысль сделать более энергоёмкой батарею, в тех же размерах или даже более компактную, сталкивается с непреодолимыми на сегодня физико-химическими барьерами, которые еще только предстоит решить (и на решение которых по всему миру тратятся миллиарды долларов ежегодно).

С другой стороны, на мобильных устройствах хочется все большего и большего – и кино посмотреть, и музыку в фоне послушать, и в гонки поиграть, и документы в Word/Excel поправить.

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

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

На фоне эффективности прекрасные возможности Flash и Silverlight по работе с мультимедиа (к чему мы еще вернемся) оказываются не у дел в простых сценариях, когда нужно просто проиграть аудио- или видеопоток.

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

(И, как известно, не на всех мобильных устройства поддерживаются Flash или Silverlight в браузере.)

И, возвращаясь к тому, о чем я уже писал: реализация поддержки аудио и видео через стандарт, а не плагин, позволяет легко интегрировать мультимедийные компоненты в общую экосистему клиентских технологий, основанных на веб-стандартах (html, css, javascript и др.). Тут вам и доступ и управление через DOM и JavaScript, и наложение стилей через CSS, и прозрачная по реализации вставка “куда хочу” без перекрытия слоев.

Теперь давайте ближе к делу!

Как это выглядит на практике

Как ни странно, работа с аудио и видео выглядит не сильно отлично от того, как выглядит работа с обычными картинками.

Например, чтобы вставить аудио-файл, достаточно написать что-то в таком виде :

<audio id="myAudio" controls loop>
   <source src="elvis.mp3" />
   <source src="elvis.ogg" />
</audio>

Обратите внимание на две вещи:

  1. Наличие атрибутов без значений – это так называемые бинарные атрибуты, наличие которых эквивалентно значению true или тому, которое определяется стандартом. То есть controls равносильно тому, что вы бы написали controls =
    “controls”
    .
  2. В данном случае указано два источника для воспоизведения, чтобы браузер мог выбрать, какой из них он может проиграть в зависимости от того, какие кодеки он поддерживает (к этому вопросу мы вернемся в следующем разделе). Если источник только один, то достаточно указать ссылку на файл в родительском теге audio через атрибут src.

Вставка видео производится абсолютно аналогично, но с некоторыми дополнительными возможностями (размер и постер):

<video id="myVideo" width="640" height="480"   
       poster="images/elvis.jpg" />
<script>
function loadVideo() {
 var player = document.getElementById("myVideo");
 player.src = "media/elvis.mp4";
 player.setAttribute("autoload", "autoload");
 
 player.play();
}
</script>

Постер – это картинка, которая показывается до начала воспроизведения. В этом примере мы инициализируем проигрывание видео через код на JavaScript, начиная с установки ссылки на файл и заканчивая непосредственно запуском на проигрывание.

Когда вы вставите аудио или видео на страницу, браузер предложит вам готовые средства для управления воспроизведением:

Важный нюанс, который в отдельных сценариях может иметь значение – эти элементы воспроизведения видео или аудио – разные в разных браузерах и не подлежат стилизации через CSS или какие-либо другие механизмы.

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

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

Что там с кодеками?

Кодеки – это один из самых острых вопросов применительных к аудио и видео в HTML5. Если коротко, но все не просто и простой жизни в ближайшем будущем не предвидится.

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

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

Для дополенительных деталей рекомендую вот эти статьи в блоге Internet Explorer, довольно доступно и подробно освещающие суть проблемы:

На практике оказывается, что единственный кодек с широкой индустриальной поддержкой, включая аппаратное кодирование и декодирования, для видео – это .h264 (и mp3 для аудио), лицензируемый MPEG LA.

Бесплатная альтернатива, продвигаемая Google – VP8 (WebM), к сожалению, на сегодня не имеет ни широкой аппаратной поддержки, ни гарантий патентной защиты со стороны Google.

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

Аудио-кодеки

Видео-кодеки

Для Chrome и Firefox существуют специальные плагины, подменяющие воспроизведение HTML5 видео в неподдерживаемом .h264 на проигрывание через Windows Media Player. Да, Chrome все еще поддерживает .h264, хотя Google и обещал отключить поддержку уже более полугода назад.

IE9 воспроизводит нативно видео в .h264 и в WebM, если в системе установлен соответствующий кодек.

(К слову, популярные мобильные платформы на iOS, Android и WP7 аппаратно поддерживают .h264 и mp3.)

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

Фолбэк! Я хочу это в IE6!

Важный вопрос заключается в том, что делать с новыми мультимедийными возможностями HTML5 применительно к старым браузерам, в которых остается уповать только на Flash или Silverlight.

Ответ, как ни странно, просто – надо использовать Flash или Silverlight. Такой подход называется обеспечением обратной совместимости или fallback.

Реализация fallback для аудио или видео становится возможным благодаря тому, что браузер должен просто игнорировать не известные ему теги, поэтому вот этот код

<video width="854" height="480" controls preload> 
    <source src="video/trailer_480p.mp4" type="video/mp4;" /> 
    <source src="video/trailer_480p.webm" type='video/webm; codecs="vorbis,vp8"'/> 
     <object width="854" height="480" type="application/x-shockwave-flash" data="video/player.swf"> 
         <param name="movie" value="video/player.swf" /> 
         <param name="allowfullscreen" value="true" /> 
         <param name="flashvars" value='file=trailer_480p.mp4' /> 

         <p>Download video as <a href="video/trailer_480p.mp4">mp4</a>.</p> 
     </object>
</video>

будет предлагать пользователю именно ту альтернативу, которую его браузер способен воспроизвести, начиная с HTML5 Video, продолжая Flash и заканчивая просто ссылой на видео-файл, которую можно скачать штатным загрузчиком.

И да – это будет работать даже в IE6.

Что я не могу сделать с аудио и видео, но, возможно, смогу в будущем?

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

Это и защита контента (DRM), и возможности адаптации качества видео-потока под текущие ограничения канала или устройства (наподобии Smooth Streaming в Silverlight или Live Streaming во Flash), и даже возможность воспроизведения видео в полноэкранном режиме.

Последнее, кстати, вы можете почувствовать на себе, если включите поддержку HTML5 Video на Youtube и попробуйте раскрыть какое-нибудь видео на полный экран – это будет видео на полную страницу внутри браузера.

Ключевая причина, по которой сегодня в стандарте HTML5 нет полноэкранного проигрывания видео – это вопрос безопасности, то есть исключения того, что любой скрипт на странице может перевести браузер в полноэкранный режим (это как раз обратная стороная интеграции в экосистему).

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

Работа над этими и другими возможностями сегодня активно ведется внутри W3C, так, относительно недавно (весной этого года) была создана новая группа, занимающаяся именно вопросами работы с аудио.

Так что самое интересное еще впереди.

p.s. Совсем забыл самое важное: чтобы сделать видео со скругленными уголками, достаточно просто применить к видео CSS-правило border-raduis.