Статьи

Аудио спрайты (и исправления для iOS)

Недавно мне пришлось работать над проектом для iOS, который требовал, чтобы звук воспроизводился при выполнении определенных действий. Проблема в том, что iOS и HTML5 были серьезно перепроданы Apple, и устройства довольно плохие по сравнению с настольными компьютерами. Аудио и видео особенно плохие, поэтому для решения моей проблемы я использовал аудио-спрайт, метод, который был похож на CSS-спрайты, только для аудио.

Проблемы

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

  1. iPhone не любит проигрывать слишком много звука одновременно, он очень прерывистый.
  2. iPad не воспроизводит более одного аудиопотока одновременно. Есть немного хорошей информации о 24 путях, в State of Audio — Early Findings — к сожалению, это было опубликовано после того, как я обнаружил это для себя!
  3. iOS не загрузит аудио, если пользователь не инициирует действие.
  4. Перед тем, как iOS сможет воспроизвести аудио, будет задержка примерно на 1/2 секунды — это потому, что создается аудиообъект (в iOS, а не HTML5).

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

Аудио Спрайт

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

Так что, если ваш звук длится 1 секунду, и вы хотите воспроизвести 10 аудиоклипов, вам нужен спрайт, который длится 10 секунд. Не ракетостроение.

Я заметил одну вещь: используя Audacity для создания спрайта, сдвинул все аудио. Возможно, я все испортил, но это то, что я постоянно замечал, поэтому я исправил это в коде, используя время выполнения заказа (то, что включено в окончательный исходный код). Хотя это и не идеально, вы действительно хотите, чтобы каждый спрайт начинался с приращения модуля 10 с 1 с.

Аудио спрайт трек

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

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

function Track(src, spriteLength) {
var audio = document.createElement('audio');

audio.src = src;
audio.autobuffer = true;
audio.load(); // force the audio to start loading...doesn't work in iOS

this.audio = audio;
this.spriteLength = spriteLength;
}

Track.prototype.play = function (position) {
var track = this,
audio = this.audio, // the audio element with our sprite loaded
length = this.spriteLength, // the length of the individual audio clip
time = position * length,
nextTime = time + length;

audio.pause();
audio.currentTime = time;
audio.play();

// clear any stop monitoring that was in place already
clearInterval(track.timer);
track.timer = setInterval(function () {
if (audio.currentTime >= nextTime) {
audio.pause();
clearInterval(track.timer);
}
}, 10);
};

 

Исправление проблем с iOS

Самая большая проблема с iOS заключается в том, что аудио не загружается вообще. Поэтому, когда мы пытаемся установить звук . currentTime = time будет сгенерирована фатальная ошибка JavaScript, потому что аудио не имеет загруженной длины и весь код попадет в дерьмо.

Поэтому нам нужно обернуть его с помощью try / catch:

try {
audio.pause();
audio.currentTime = time;
audio.play();
} catch (e) {
// what now?
}

Но теперь мы завернули это в попытку / поймать, что мы делаем? Мы могли бы воспроизводить звук и продолжать пытаться / ловить, чтобы переместить currentTime, но проблема в том, что звук начнет воспроизводиться не в том месте.

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

В результате экспериментов с iOS я обнаружил, что событие progress означает, что у меня достаточно данных для быстрой перемотки вперед, прежде чем пользователь услышит какой-либо звук. Я бы ожидал, что это будет игра или игра, но, увы, это iOS, и не все, что вы ожидаете!

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

try {
audio.currentTime = time;
audio.play();
} catch (e) {
track.updateCallback = function () {
track.updateCallback = null;
audio.currentTime = time;
audio.play();
};
audio.play();
}

Теперь внутри функции объекта Track я также добавил следующий код:

var track = this;

var progress = function () {
audio.removeEventListener('progress', progress, false);
if (track.updateCallback !== null) track.updateCallback();
};

audio.addEventListener('progress', progress, false);
track.updateCallback = null;

Это означает, что мы слушаем событие progress , и когда оно запускается впервые, мы удаляем обработчик, и, если есть обратный вызов, мы вызываем его. Прогресс срабатывает при попытке воспроизвести. Так что теперь у нас есть прыжок в нужную точку, но у нас все еще есть задержка в 1/2 секунды.

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

Снова внутри нового объекта дорожки мы добавляем код, чтобы попытаться загрузить аудио:

var force = function () {
audio.pause();
audio.removeEventListener('play', force, false);
};
audio.addEventListener('play', force, false);

var click = document.ontouchstart === undefined ? 'click' : 'touchstart';
var kickoff = function () {
audio.play();
document.documentElement.removeEventListener(click, kickoff, true);
};
document.documentElement.addEventListener(click, kickoff, true);

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

Законченный код

Я также реализовал простой проигрыватель, который позволяет вам использовать несколько дорожек (что очень хорошо работает на рабочем столе), или вы можете использовать один объект дорожки напрямую. Законченный код, с еще несколькими комментариями и попытками отключить звук, был включен в суть: аудио спрайты HTML5