Pull to refresh

HTML5 Audio и Game Development: баги браузеров, проблемы и их решения, идеи

JavaScript *
В топике я расскажу о нюансах использования тега <audio> в разных браузерах при разработке игр, о проблемах, с которыми я столкнулся и о том, как их решить. Объяснение будет идти паралельно с написанием обертки для удобной работы.


Элемент Audio имеет очень красивый интерфейс, но нам его надо расширить, потому напишем обертку:
var LibCanvasAudio = function (file) {
    
this.audio = new Audio;
    
this.audio.src file;
};

LibCanvasAudio.prototype = {};


.ogg vs .mp3


Для начала — муть с поддерживаемыми кодеками. Большинство браузеров поддерживает ogg vorbis(для Оперы в Линуксе не забудьте установить gstreamer base и good плагины), но, например, Apple решил выебнуться. Давайте для вменяемых браузеров будем отдавать в .ogg, всем остальным — в .mp3. Все звездочки в имени файла будем заменять на ogg или mp3 соответственно:

var LibCanvasAudio = function (file) {
    
this.audio = new Audio;
    
this.src(file);
};

LibCanvasAudio.prototype = {
    
src : function (file) {
        var 
codec this.getSupport();
        if (!
codec) throw 'AudioNotSupported';
        
this.audio.src file.replace(/\*/gthis.getSupport());
        
this.audio.load();
        return 
this;
    },
    
getSupport : function () {
        return !
this.audio.canPlayType false :
            
this.audio.canPlayType('audio/ogg;')  ? 'ogg' :
            
this.audio.canPlayType('audio/mpeg;') ? 'mp3' false;
    }
}



Схема Гатлинга


Хорошо. Мы подошли к главной теме. Допустим, мы разрабатываем экшн. У нас огромное количество взрывов, выстрелов, етс. Допустим, один взрыв с эхо длится 5 секунд, а интервал между взрывами может быть 0.5 секунд. Если просто запускать файл сначала, то предыдущий взрыв резко оборвется. Мы могли бы клонировать элемент Audio каждый раз перед запуском, но так мы будем плодить кучу DOM-элементов. Прибилизительно через 5 минут игры все браузеры сходят с ума. Потому предлагаю воспользоваться схемой Гатлинга. Заносим определенное количество элементов в массив и вызываем их по-очереди. Пока сыграет последний элемент первый успеет закончиться. Главное — выставить достаточно количество «стволов» для каждого из звуков.

LibCanvasAudio.prototype = {
    
// ...
    
cloneAudio : function () {
        
audioClone this.audio.cloneNode(true);
        
audioClone.load();
        return 
audioClone;
    },
    
gatling : function (count) {
        
this.barrels = [];
        
this.gatIndex =  0;
        while (
count--) {
            
this.barrels.push(this.cloneAudio());
        }
        return 
this;
    },
    
getNext : function () {
        var 
elem this.barrels[this.gatIndex];
        ++
this.gatIndex >= this.barrels.length && (this.gatIndex 0);
        return 
elem;
    },
    
playNext : function () {
        var 
elem this.getNext();
        
elem.pause();
        
elem.currentTime 0;
        
elem.play();
        return 
this;
    }
};



Интерфейс у нас получается приблизительно такой:
var shotSound = new LibCanvasAudio('explosion.*').gatling(6);

window.addEventListener('keydown', function (e) {
    (
e.keyCode == keys.SPACE) && shotSound.playNext();
}, 
false);


Облом в Опере


В Опере нас ждёт облом. Детально изучив этот вопрос нашел баг, который я зарепортил с кодом DSK-309302. Клонированный элемент Аудио в Опере не работает:

// var audioOrig   = document.createElement('audio'); // аналогично с:
var audioOrig      = new Audio();
audioOrig.src      'shot.ogg';
audioOrig.controls 'controls';

var audioClone audioOrig.cloneNode(true);

function 
appendToBody (node) {
    
document.getElementsByTagName('body')[0].appendChild(node);
}

audioOrig.play(); // работает
audioClone.play(); // не работает в Опере

appendToBody(audioOrig);  // работает
appendToBody(audioClone); // не работает в Опере


Напишем небольшой фикс для Оперы:
LibCanvasAudio.prototype = {
    
// ..
    
cloneAudio : function () {
        if (
window.opera) { // Reported Opera bug DSK-309302
            
var audioClone = new Audio;
            
audioClone.src this.audio.src;
        } else {
            
audioClone this.audio.cloneNode(true);
        }
        
audioClone.load();
        return 
audioClone;
    },
    
// ..
};


Баг в Фоксе


Другой баг мы можем наблюдать в firefox 3.5 (в 3.6 и 4 уже нету) — при повторном воспроизведении аудиотрека первая секунда± — дублируется. Судя по всему, не только у меня: «the second time you hit the button it plays the “badumm” twice in Firefox». (видео с записью бага). Добавим небольшой фикс — будем отматывать аудио не в начало, а на 25 миллисекунду (минимальное значение установленно экспериментально и равно приблизительно 0.021-0.022 секунды). При желании можно добавить проверку версии и всех кроме 3.5 фокса возвращать в начало (но разница между 25 миллисекундой и нулевой не ощущается, в крайнем случае, зная про этот нюанс можно всё аудио отодвинуть в любимом аудиоредакторе на 25 миллисекунд влево):

LibCanvasAudio.prototype = {
    
// ..
    
playNext : function () {
        var 
elem this.getNext();
        
elem.pause();
        
elem.currentTime 0.025;
        
elem.play();
        return 
this;
    }
};


Бонус


В ie9 preview 4 не работает new Audio(), но это очень просто решить, заменив его на document.createElement('audio');

Что получилось в итоге (тыкать в пробел)


Исходник, получившийся в результате: pastebin.com/xG4mhX3w
Tags:
Hubs:
Total votes 74: ↑64 and ↓10 +54
Views 11K
Comments 85
Comments Comments 85