Пример из FlippyTime

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

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

Однако, когда я связал функцию воспроизведения звука JavaScript со звуковым эффектом при прикосновении к плитке (например, на экране могла быть сетка из плиток 4x4), он не воспроизводил следующий звук до тех пор, пока закончилось воспроизведение первого фрагмента звука.

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

Есть много библиотек, посвященных решению этой проблемы, но я писал FlippyTime на VanillaJS в качестве эксперимента с чистым JavaScript и искал простой и эффективный способ сделать это с минимумом кода.

Вспоминая свою давнюю любовь к MIDI, я подумал: «Почему бы мне не попробовать создать виртуальные каналы и предварительно загрузить в каждый один и тот же звуковой эффект, тогда я могу быстро переключаться между ними, когда мне нужно воспроизвести один и тот же звуковой эффект несколько раз».

Вы можете найти полный код здесь, в файле main.js FlippyTime, но разбивка проста:

function Channel(audio_uri) {
	this.audio_uri = audio_uri;
	this.resource = new Audio(audio_uri);
}

Channel.prototype.play = function() {
	// Try refreshing the resource altogether
	this.resource.play();
}

Объект Channel отвечает за загрузку данного аудиоресурса. Вот и все: тупая обертка.

function Switcher(audio_uri, num) {
	this.channels = [];
	this.num = num;
	this.index = 0;

	for (var i = 0; i < num; i++) {
		this.channels.push(new Channel(audio_uri));
	}
}

Switcher.prototype.play = function() {
	this.channels[this.index++].play();
	this.index = this.index < this.num ? this.index : 0;
}

Switcher, с другой стороны, загружает num экземпляра каналов и загружает новый звук в каждый из них.

Когда play() вызывается в экземпляре Switcher, он просто перебирает доступные каналы и воспроизводит этот конкретный аудиопоток. Это позволяет использовать один и тот же аудиофайл несколько раз и воспроизводить одновременно.

Реализация объекта GAME.Sound на основе модуля приведена ниже:

GAME.Sound = (function() {
	var self = {};

	self.playFront = function() {
		if (GAME.isReady()) { sfx_switcher_front.play(); }
	}

	self.playBack = function() {
		if (GAME.isReady()) { sfx_switcher_back.play(); }
	}

	self.playSuccess = function() {
		if (GAME.isReady()) { sfx_switcher_success.play(); }
	}

	self.playStart = function() {
		if (GAME.isReady()) { sfx_switcher_start.play(); }
	}

	self.init = function() {
		sfx_switcher_front   = new Switcher('sfx/flip-front.mp3', 10);
		sfx_switcher_back    = new Switcher('sfx/flip-back.mp3', 10);
		sfx_switcher_success = new Switcher('sfx/success.mp3', 2);
		sfx_switcher_start   = new Switcher('sfx/start.mp3', 1);
	}

	return self;
}());

Обратите внимание, как init загружает все необходимые ресурсы заранее?

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

Резюме

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

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