UNPKG

6.07 kBJavaScriptView Raw
1"use strict";
2
3window.AudioContext = window.AudioContext || window.webkitAudioContext;
4
5/**
6 * Loads sound files and lets you know when they're all available. An instance of SoundLoader is available as {@link Splat.Game#sounds}.
7 * This implementation uses the Web Audio API, and if that is not available it automatically falls back to the HTML5 <audio> tag.
8 * @constructor
9 */
10function SoundLoader() {
11 /**
12 * The key-value object that stores named sounds.
13 * @member {object}
14 * @private
15 */
16 this.sounds = {};
17 /**
18 * The total number of sounds to be loaded.
19 * @member {number}
20 * @private
21 */
22 this.totalSounds = 0;
23 /**
24 * The number of sounds that have loaded completely.
25 * @member {number}
26 * @private
27 */
28 this.loadedSounds = 0;
29 /**
30 * A flag signifying if sounds are muted and should not be played.
31 * This only stops new sounds from being played, and doesn't actually mute the volume.
32 * Patches appreciated.
33 * @member {boolean}
34 */
35 this.muted = false;
36 /**
37 * A key-value object that stores named looping sounds.
38 * @member {object}
39 * @private
40 */
41 this.looping = {};
42
43 /**
44 * The Web Audio API AudioContext
45 * @member {external:AudioContext}
46 * @private
47 */
48 this.context = new window.AudioContext();
49}
50/**
51 * Load an audio file.
52 * @param {string} name The name you want to use when you {@link SoundLoader#play} the sound.
53 * @param {string} path The path of the sound file.
54 */
55SoundLoader.prototype.load = function(name, path) {
56 var self = this;
57
58 if (this.totalSounds === 0) {
59 // safari on iOS mutes sounds until they're played in response to user input
60 // play a dummy sound on first touch
61 var firstTouchHandler = function() {
62 window.removeEventListener("click", firstTouchHandler);
63 window.removeEventListener("keydown", firstTouchHandler);
64 window.removeEventListener("touchstart", firstTouchHandler);
65
66 var source = self.context.createOscillator();
67 source.connect(self.context.destination);
68 source.start(0);
69 source.stop(0);
70
71 if (self.firstPlay) {
72 self.play(self.firstPlay, self.firstPlayLoop);
73 } else {
74 self.firstPlay = "workaround";
75 }
76
77 };
78 window.addEventListener("click", firstTouchHandler);
79 window.addEventListener("keydown", firstTouchHandler);
80 window.addEventListener("touchstart", firstTouchHandler);
81 }
82
83 this.totalSounds++;
84
85 var request = new XMLHttpRequest();
86 request.open("GET", path, true);
87 request.responseType = "arraybuffer";
88 request.addEventListener("readystatechange", function() {
89 if (request.readyState !== 4) {
90 return;
91 }
92 if (request.status !== 200 && request.status !== 0) {
93 console.error("Error loading sound " + path);
94 return;
95 }
96 self.context.decodeAudioData(request.response, function(buffer) {
97 self.sounds[name] = buffer;
98 self.loadedSounds++;
99 }, function(err) {
100 console.error("Error decoding audio data for " + path + ": " + err);
101 });
102 });
103 request.addEventListener("error", function() {
104 console.error("Error loading sound " + path);
105 });
106 try {
107 request.send();
108 } catch (e) {
109 console.error("Error loading sound", path, e);
110 }
111};
112/**
113 * Test if all sounds have loaded.
114 * @returns {boolean}
115 */
116SoundLoader.prototype.allLoaded = function() {
117 return this.totalSounds === this.loadedSounds;
118};
119/**
120 * Play a sound.
121 * @param {string} name The name given to the sound during {@link SoundLoader#load}
122 * @param {boolean} [loop=false] A flag denoting whether the sound should be looped. To stop a looped sound use {@link SoundLoader#stop}.
123 */
124SoundLoader.prototype.play = function(name, loop) {
125 if (loop && this.looping[name]) {
126 return;
127 }
128 if (!this.firstPlay) {
129 // let the iOS user input workaround handle it
130 this.firstPlay = name;
131 this.firstPlayLoop = loop;
132 return;
133 }
134 if (this.muted) {
135 return;
136 }
137 var snd = this.sounds[name];
138 if (snd === undefined) {
139 console.error("Unknown sound: " + name);
140 }
141 var source = this.context.createBufferSource();
142 source.buffer = snd;
143 source.connect(this.context.destination);
144 if (loop) {
145 source.loop = true;
146 this.looping[name] = source;
147 }
148 source.start(0);
149};
150/**
151 * Stop playing a sound. This currently only stops playing a sound that was looped earlier, and doesn't stop a sound mid-play. Patches welcome.
152 * @param {string} name The name given to the sound during {@link SoundLoader#load}
153 */
154SoundLoader.prototype.stop = function(name) {
155 if (!this.looping[name]) {
156 return;
157 }
158 this.looping[name].stop();
159 delete this.looping[name];
160};
161
162function AudioTagSoundLoader() {
163 this.sounds = {};
164 this.totalSounds = 0;
165 this.loadedSounds = 0;
166 this.muted = false;
167 this.looping = {};
168}
169AudioTagSoundLoader.prototype.load = function(name, path) {
170 this.totalSounds++;
171
172 var audio = new Audio();
173 var self = this;
174 audio.addEventListener("error", function() {
175 console.error("Error loading sound " + path);
176 });
177 audio.addEventListener("canplaythrough", function() {
178 self.sounds[name] = audio;
179 self.loadedSounds++;
180 });
181 audio.src = path;
182 audio.load();
183};
184AudioTagSoundLoader.prototype.allLoaded = function() {
185 return this.totalSounds === this.loadedSounds;
186};
187AudioTagSoundLoader.prototype.play = function(name, loop) {
188 if (loop && this.looping[name]) {
189 return;
190 }
191 if (this.muted) {
192 return;
193 }
194 var snd = this.sounds[name];
195 if (snd === undefined) {
196 console.error("Unknown sound: " + name);
197 }
198 if (loop) {
199 snd.loop = true;
200 this.looping[name] = snd;
201 }
202 snd.play();
203};
204AudioTagSoundLoader.prototype.stop = function(name) {
205 var snd = this.looping[name];
206 if (!snd) {
207 return;
208 }
209 snd.loop = false;
210 snd.pause();
211 snd.currentTime = 0;
212 delete this.looping[name];
213};
214
215function FakeSoundLoader() {}
216FakeSoundLoader.prototype.load = function() {};
217FakeSoundLoader.prototype.allLoaded = function() { return true; };
218FakeSoundLoader.prototype.play = function() {};
219
220if (window.AudioContext) {
221 module.exports = SoundLoader;
222} else if (window.Audio) {
223 module.exports = AudioTagSoundLoader;
224} else {
225 console.log("This browser doesn't support the Web Audio API or the HTML5 audio tag.");
226 module.exports = FakeSoundLoader;
227}