-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaudio.js
229 lines (212 loc) · 6.69 KB
/
audio.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
'use strict';
/**
* An object representing one audio sample. Uses Howler.js under the hood if it is available.
* @param {string} filename Name of the audio file without a file extension. Assumes that the audio file is located
* in Audio.audioPath.
* @param {Array.<string>} fileExtensions Array of extensions. Defaults to ogg and mp3, which should be enough for
* cross-browser compatibility. The default file extensions are configurable through Audio.defaultExtensions.
* @constructor
*/
var Audio = function(filename, fileExtensions) {
if (fileExtensions === undefined) {
fileExtensions = Audio.defaultExtensions;
}
var that = this;
Audio.allAudio.push(this);
this.loaded = false; // Used purely for purposes of marking the audio loaded.
var markLoaded = function() {
that._markLoaded();
}
this.filenames = [];
var canDetermineLoaded = false;
for (var i = 0; i < fileExtensions.length; ++i) {
this.filenames.push(Audio.audioPath + filename + '.' + fileExtensions[i]);
}
// Don't use howler when using the file protocol, since it requires CORS requests
if (typeof Howl !== 'undefined' && window.location.origin.substring(0, 4) != 'file') {
// Use howler.js to implement Audio
this._howl = new Howl({
src: this.filenames,
onload: markLoaded,
onloaderror: markLoaded
});
return;
} else {
this._howl = null;
}
this.audio = document.createElement('audio');
for (var i = 0; i < fileExtensions.length; ++i) {
if (fileExtensions[i] === 'ogg' && !canDetermineLoaded) {
canDetermineLoaded = this.audio.canPlayType('audio/ogg;codecs="vorbis"') == 'probably';
}
if (fileExtensions[i] === 'mp3' && !canDetermineLoaded) {
canDetermineLoaded = this.audio.canPlayType('audio/mpeg') == 'probably';
}
}
this.playWhenReady = null; // Event listener to start playing when audio is ready.
if (canDetermineLoaded) {
this.audio.addEventListener('canplay', markLoaded);
// Can never be sure that the audio will load. Fake loaded after 10 seconds to unblock loading bar.
setTimeout(markLoaded, 10000);
} else {
this._markLoaded();
}
this.addSourcesTo(this.audio);
this.clones = [];
this.ensureOneClone();
};
/**
* Path for audio files. Set this before creating any Audio objects.
*/
Audio.audioPath = 'assets/sounds/';
/**
* Default file extensions. Set this before creating any Audio objects. Ogg and mp3 are enough for cross-browser
* compatibility.
*/
Audio.defaultExtensions = ['ogg', 'mp3'];
/**
* True when all audio is muted. Set this by calling muteAll.
*/
Audio.allMuted = false;
/**
* @param {boolean} mute Set to true to mute all audio.
*/
Audio.muteAll = function(mute) {
Audio.allMuted = mute;
if (this._howl) {
if (mute) {
Howler.mute();
} else {
Howler.unmute();
}
} else {
for (var i = 0; i < Audio.allAudio.length; ++i) {
var audio = Audio.allAudio[i];
audio.audio.muted = mute;
for (var j = 0; j < audio.clones.length; ++j) {
audio.clones[j].muted = mute;
}
}
}
};
/**
* All audio objects that have been created.
*/
Audio.allAudio = [];
/**
* How many Audio objects have been fully loaded.
*/
Audio.loadedCount = 0;
/**
* @return {number} Amount of Audio objects that have been fully loaded per amount that has been created.
*/
Audio.loadedFraction = function() {
return Audio.loadedCount / Audio.allAudio.length;
};
/**
* @param {HTMLAudioElement} audioElement Element to add audio sources to.
* @protected
*/
Audio.prototype.addSourcesTo = function(audioElement) {
for (var i = 0; i < this.filenames.length; ++i) {
var source = document.createElement('source');
source.src = this.filenames[i];
audioElement.appendChild(source);
}
};
/**
* Play a clone of this sample. Will not affect other clones. Playback will not loop and playback can not be stopped.
*/
Audio.prototype.play = function () {
if (this._howl) {
this._howl.play();
return;
}
// If readyState was compared against 4, Firefox wouldn't play audio at all sometimes. That's why using 2 here.
if (this.audio.readyState < 2) {
return;
}
var clone = this.ensureOneClone();
clone.play();
this.ensureOneClone(); // Make another clone ready ahead of time.
};
/**
* Play this sample when it is ready. Use only if only one copy of this sample is going to play simultaneously.
* Playback can be stopped by calling stop().
* @param {boolean=} loop Whether the sample should loop when played. Defaults to false.
*/
Audio.prototype.playSingular = function (loop) {
if (loop === undefined) {
loop = false;
}
if (this._howl) {
if (this._howl.playing(0)) {
return;
}
this._howl.play();
this._howl.loop(loop);
return;
}
this.audio.loop = loop;
if (this.audio.readyState >= 2) {
if (this.playWhenReady !== null) {
this.audio.removeEventListener('canplay', this.playWhenReady);
this.playWhenReady = null;
}
this.audio.play();
this._markLoaded();
} else if (this.playWhenReady === null) {
var that = this;
this.playWhenReady = function() {
that.audio.play();
that._markLoaded();
}
this.audio.addEventListener('canplay', this.playWhenReady);
}
};
/**
* Stop playing this sample.
*/
Audio.prototype.stop = function () {
if (this._howl) {
this._howl.stop();
return;
}
if (this.playWhenReady !== null) {
this.audio.removeEventListener('canplay', this.playWhenReady);
this.playWhenReady = null;
}
this.audio.pause();
this.audio.currentTime = 0;
};
/**
* Ensure that there is one clone available for playback and return it.
* @protected
* @return {HTMLAudioElement} Clone that is ready for playback.
*/
Audio.prototype.ensureOneClone = function() {
for (var i = 0; i < this.clones.length; ++i) {
if (this.clones[i].ended || (this.clones[i].readyState == 4 && this.clones[i].paused)) {
this.clones[i].currentTime = 0;
return this.clones[i];
}
}
var clone = document.createElement('audio');
if (Audio.allMuted) {
clone.muted = true;
}
this.addSourcesTo(clone);
this.clones.push(clone);
return clone;
};
/**
* Mark this audio sample loaded.
* @protected
*/
Audio.prototype._markLoaded = function() {
if (this.loaded) {
return;
}
this.loaded = true;
Audio.loadedCount++;
};