UNPKG

4.71 kBJavaScriptView Raw
1var AssetLoader = require("./assets/asset-loader");
2var loadSound = require("./assets/load-sound");
3
4window.AudioContext = window.AudioContext || window.webkitAudioContext;
5
6/**
7 * Plays audio, tracks looping sounds, and manages volume.
8 * This implementation uses the Web Audio API.
9 * @constructor
10 * @param {Object} manifest A hash where the key is the name of a sound, and the value is the URL of a sound file.
11 */
12function SoundManager(manifest) {
13 /**
14 * A flag signifying if sounds have been muted through {@link SoundManager#mute}.
15 * @member {boolean}
16 * @private
17 */
18 this.muted = false;
19 /**
20 * A key-value object that stores named looping sounds.
21 * @member {object}
22 * @private
23 */
24 this.looping = {};
25
26 /**
27 * The Web Audio API AudioContext
28 * @member {external:AudioContext}
29 * @private
30 */
31 this.context = new window.AudioContext();
32
33 this.gainNode = this.context.createGain();
34 this.gainNode.connect(this.context.destination);
35 this.volume = this.gainNode.gain.value;
36 this.installSafariWorkaround();
37 this.assets = new AssetLoader(manifest, loadSound.bind(undefined, this.context));
38}
39SoundManager.prototype.installSafariWorkaround = function() {
40 // safari on iOS mutes sounds until they're played in response to user input
41 // play a dummy sound on first touch
42 var firstTouchHandler = function() {
43 window.removeEventListener("click", firstTouchHandler);
44 window.removeEventListener("keydown", firstTouchHandler);
45 window.removeEventListener("touchstart", firstTouchHandler);
46
47 var source = this.context.createOscillator();
48 source.connect(this.gainNode);
49 source.start(0);
50 source.stop(0);
51
52 if (this.firstPlay) {
53 this.play(this.firstPlay, this.firstPlayLoop);
54 } else {
55 this.firstPlay = "workaround";
56 }
57 }.bind(this);
58 window.addEventListener("click", firstTouchHandler);
59 window.addEventListener("keydown", firstTouchHandler);
60 window.addEventListener("touchstart", firstTouchHandler);
61};
62/**
63 * Play a sound.
64 * @param {string} name The name of the sound to play.
65 * @param {Object} [loop=undefined] A hash containing loopStart and loopEnd options. To stop a looped sound use {@link SoundManager#stop}.
66 */
67SoundManager.prototype.play = function(name, loop) {
68 if (loop && this.looping[name]) {
69 return;
70 }
71 if (!this.firstPlay) {
72 // let the iOS user input workaround handle it
73 this.firstPlay = name;
74 this.firstPlayLoop = loop;
75 return;
76 }
77 var snd = this.assets.get(name);
78 if (snd === undefined) {
79 console.error("Unknown sound: " + name);
80 }
81 var source = this.context.createBufferSource();
82 source.buffer = snd;
83 source.connect(this.gainNode);
84 if (loop) {
85 source.loop = true;
86 source.loopStart = loop.loopStart || 0;
87 source.loopEnd = loop.loopEnd || 0;
88 this.looping[name] = source;
89 }
90 source.start(0);
91};
92/**
93 * 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.
94 * @param {string} name The name of the sound to stop looping.
95 */
96SoundManager.prototype.stop = function(name) {
97 if (!this.looping[name]) {
98 return;
99 }
100 this.looping[name].stop(0);
101 delete this.looping[name];
102};
103/**
104 * Silence all sounds. Sounds keep playing, but at zero volume. Call {@link SoundManager#unmute} to restore the previous volume level.
105 */
106SoundManager.prototype.mute = function() {
107 this.gainNode.gain.value = 0;
108 this.muted = true;
109};
110/**
111 * Restore volume to whatever value it was before {@link SoundManager#mute} was called.
112 */
113SoundManager.prototype.unmute = function() {
114 this.gainNode.gain.value = this.volume;
115 this.muted = false;
116};
117/**
118 * Set the volume of all sounds.
119 * @param {number} gain The desired volume level. A number between 0.0 and 1.0, with 0.0 being silent, and 1.0 being maximum volume.
120 */
121SoundManager.prototype.setVolume = function(gain) {
122 this.volume = gain;
123 this.gainNode.gain.value = gain;
124 this.muted = false;
125};
126/**
127 * Test if the volume is currently muted.
128 * @return {boolean} True if the volume is currently muted.
129 */
130SoundManager.prototype.isMuted = function() {
131 return this.muted;
132};
133
134
135function FakeSoundManager() {}
136FakeSoundManager.prototype.play = function() {};
137FakeSoundManager.prototype.stop = function() {};
138FakeSoundManager.prototype.mute = function() {};
139FakeSoundManager.prototype.unmute = function() {};
140FakeSoundManager.prototype.setVolume = function() {};
141FakeSoundManager.prototype.isMuted = function() {
142 return true;
143};
144
145if (window.AudioContext) {
146 module.exports = SoundManager;
147} else {
148 console.warn("This browser doesn't support the Web Audio API.");
149 module.exports = FakeSoundManager;
150}