HTML5 INSIGHT


*Продолжаем погружаться в тему 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. Но многое уже на горизонте!