wavesurfer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,160 @@
1
+ 'use strict';
2
+
3
+ WaveSurfer.MediaElement = Object.create(WaveSurfer.WebAudio);
4
+
5
+ WaveSurfer.util.extend(WaveSurfer.MediaElement, {
6
+ init: function (params) {
7
+ this.params = params;
8
+
9
+ // Dummy media to catch errors
10
+ this.media = {
11
+ currentTime: 0,
12
+ duration: 0,
13
+ paused: true,
14
+ playbackRate: 1,
15
+ play: function () {},
16
+ pause: function () {}
17
+ };
18
+
19
+ this.mediaType = params.mediaType.toLowerCase();
20
+ this.elementPosition = params.elementPosition;
21
+ },
22
+
23
+ load: function (url, container, peaks) {
24
+ var my = this;
25
+
26
+ var media = document.createElement(this.mediaType);
27
+ media.controls = false;
28
+ media.autoplay = false;
29
+ media.preload = 'auto';
30
+ media.src = url;
31
+
32
+ media.addEventListener('error', function () {
33
+ my.fireEvent('error', 'Error loading media element');
34
+ });
35
+
36
+ media.addEventListener('canplay', function () {
37
+ my.fireEvent('canplay');
38
+ });
39
+
40
+ media.addEventListener('ended', function () {
41
+ my.fireEvent('finish');
42
+ });
43
+
44
+ media.addEventListener('timeupdate', function () {
45
+ my.fireEvent('audioprocess', my.getCurrentTime());
46
+ });
47
+
48
+ var prevMedia = container.querySelector(this.mediaType);
49
+ if (prevMedia) {
50
+ container.removeChild(prevMedia);
51
+ }
52
+ container.appendChild(media);
53
+
54
+ this.media = media;
55
+ this.peaks = peaks;
56
+ this.onPlayEnd = null;
57
+ this.buffer = null;
58
+ this.setPlaybackRate(this.playbackRate);
59
+ },
60
+
61
+ isPaused: function () {
62
+ return this.media.paused;
63
+ },
64
+
65
+ getDuration: function () {
66
+ var duration = this.media.duration;
67
+ if (duration >= Infinity) { // streaming audio
68
+ duration = this.media.seekable.end();
69
+ }
70
+ return duration;
71
+ },
72
+
73
+ getCurrentTime: function () {
74
+ return this.media.currentTime;
75
+ },
76
+
77
+ getPlayedPercents: function () {
78
+ return (this.getCurrentTime() / this.getDuration()) || 0;
79
+ },
80
+
81
+ /**
82
+ * Set the audio source playback rate.
83
+ */
84
+ setPlaybackRate: function (value) {
85
+ this.playbackRate = value || 1;
86
+ this.media.playbackRate = this.playbackRate;
87
+ },
88
+
89
+ seekTo: function (start) {
90
+ if (start != null) {
91
+ this.media.currentTime = start;
92
+ }
93
+ this.clearPlayEnd();
94
+ },
95
+
96
+ /**
97
+ * Plays the loaded audio region.
98
+ *
99
+ * @param {Number} start Start offset in seconds,
100
+ * relative to the beginning of a clip.
101
+ * @param {Number} end End offset in seconds,
102
+ * relative to the beginning of a clip.
103
+ */
104
+ play: function (start, end) {
105
+ this.seekTo(start);
106
+ this.media.play();
107
+ end && this.setPlayEnd(end);
108
+ },
109
+
110
+ /**
111
+ * Pauses the loaded audio.
112
+ */
113
+ pause: function () {
114
+ this.media.pause();
115
+ this.clearPlayEnd();
116
+ },
117
+
118
+ setPlayEnd: function (end) {
119
+ var my = this;
120
+ this.onPlayEnd = function (time) {
121
+ if (time >= end) {
122
+ my.pause();
123
+ my.seekTo(end);
124
+ }
125
+ };
126
+ this.on('audioprocess', this.onPlayEnd);
127
+ },
128
+
129
+ clearPlayEnd: function () {
130
+ if (this.onPlayEnd) {
131
+ this.un('audioprocess', this.onPlayEnd);
132
+ this.onPlayEnd = null;
133
+ }
134
+ },
135
+
136
+ getPeaks: function (length) {
137
+ if (this.buffer) {
138
+ return WaveSurfer.WebAudio.getPeaks.call(this, length);
139
+ }
140
+ return this.peaks || [];
141
+ },
142
+
143
+ getVolume: function () {
144
+ return this.media.volume;
145
+ },
146
+
147
+ setVolume: function (val) {
148
+ this.media.volume = val;
149
+ },
150
+
151
+ destroy: function () {
152
+ this.pause();
153
+ this.unAll();
154
+ this.media.parentNode && this.media.parentNode.removeChild(this.media);
155
+ this.media = null;
156
+ }
157
+ });
158
+
159
+ //For backwards compatibility
160
+ WaveSurfer.AudioElement = WaveSurfer.MediaElement;
@@ -0,0 +1,132 @@
1
+ /* Common utilities */
2
+ WaveSurfer.util = {
3
+ extend: function (dest) {
4
+ var sources = Array.prototype.slice.call(arguments, 1);
5
+ sources.forEach(function (source) {
6
+ Object.keys(source).forEach(function (key) {
7
+ dest[key] = source[key];
8
+ });
9
+ });
10
+ return dest;
11
+ },
12
+
13
+ getId: function () {
14
+ return 'wavesurfer_' + Math.random().toString(32).substring(2);
15
+ },
16
+
17
+ ajax: function (options) {
18
+ var ajax = Object.create(WaveSurfer.Observer);
19
+ var xhr = new XMLHttpRequest();
20
+ var fired100 = false;
21
+
22
+ xhr.open(options.method || 'GET', options.url, true);
23
+ xhr.responseType = options.responseType || 'json';
24
+
25
+ xhr.addEventListener('progress', function (e) {
26
+ ajax.fireEvent('progress', e);
27
+ if (e.lengthComputable && e.loaded == e.total) {
28
+ fired100 = true;
29
+ }
30
+ });
31
+
32
+ xhr.addEventListener('load', function (e) {
33
+ if (!fired100) {
34
+ ajax.fireEvent('progress', e);
35
+ }
36
+ ajax.fireEvent('load', e);
37
+
38
+ if (200 == xhr.status || 206 == xhr.status) {
39
+ ajax.fireEvent('success', xhr.response, e);
40
+ } else {
41
+ ajax.fireEvent('error', e);
42
+ }
43
+ });
44
+
45
+ xhr.addEventListener('error', function (e) {
46
+ ajax.fireEvent('error', e);
47
+ });
48
+
49
+ xhr.send();
50
+ ajax.xhr = xhr;
51
+ return ajax;
52
+ }
53
+ };
54
+
55
+
56
+ /* Observer */
57
+ WaveSurfer.Observer = {
58
+ /**
59
+ * Attach a handler function for an event.
60
+ */
61
+ on: function (event, fn) {
62
+ if (!this.handlers) { this.handlers = {}; }
63
+
64
+ var handlers = this.handlers[event];
65
+ if (!handlers) {
66
+ handlers = this.handlers[event] = [];
67
+ }
68
+ handlers.push(fn);
69
+
70
+ // Return an event descriptor
71
+ return {
72
+ name: event,
73
+ callback: fn,
74
+ un: this.un.bind(this, event, fn)
75
+ };
76
+ },
77
+
78
+ /**
79
+ * Remove an event handler.
80
+ */
81
+ un: function (event, fn) {
82
+ if (!this.handlers) { return; }
83
+
84
+ var handlers = this.handlers[event];
85
+ if (handlers) {
86
+ if (fn) {
87
+ for (var i = handlers.length - 1; i >= 0; i--) {
88
+ if (handlers[i] == fn) {
89
+ handlers.splice(i, 1);
90
+ }
91
+ }
92
+ } else {
93
+ handlers.length = 0;
94
+ }
95
+ }
96
+ },
97
+
98
+ /**
99
+ * Remove all event handlers.
100
+ */
101
+ unAll: function () {
102
+ this.handlers = null;
103
+ },
104
+
105
+ /**
106
+ * Attach a handler to an event. The handler is executed at most once per
107
+ * event type.
108
+ */
109
+ once: function (event, handler) {
110
+ var my = this;
111
+ var fn = function () {
112
+ handler.apply(this, arguments);
113
+ setTimeout(function () {
114
+ my.un(event, fn);
115
+ }, 0);
116
+ };
117
+ return this.on(event, fn);
118
+ },
119
+
120
+ fireEvent: function (event) {
121
+ if (!this.handlers) { return; }
122
+ var handlers = this.handlers[event];
123
+ var args = Array.prototype.slice.call(arguments, 1);
124
+ handlers && handlers.forEach(function (fn) {
125
+ fn.apply(null, args);
126
+ });
127
+ }
128
+ };
129
+
130
+
131
+ /* Make the main WaveSurfer object an observer */
132
+ WaveSurfer.util.extend(WaveSurfer, WaveSurfer.Observer);
@@ -0,0 +1,440 @@
1
+ /**
2
+ * wavesurfer.js
3
+ *
4
+ * https://github.com/katspaugh/wavesurfer.js
5
+ *
6
+ * This work is licensed under a Creative Commons Attribution 3.0 Unported License.
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ var WaveSurfer = {
12
+ defaultParams: {
13
+ height : 128,
14
+ waveColor : '#999',
15
+ progressColor : '#555',
16
+ cursorColor : '#333',
17
+ cursorWidth : 1,
18
+ skipLength : 2,
19
+ minPxPerSec : 20,
20
+ pixelRatio : window.devicePixelRatio,
21
+ fillParent : true,
22
+ scrollParent : false,
23
+ hideScrollbar : false,
24
+ normalize : false,
25
+ audioContext : null,
26
+ container : null,
27
+ dragSelection : true,
28
+ loopSelection : true,
29
+ audioRate : 1,
30
+ interact : true,
31
+ splitChannels : false,
32
+ renderer : 'Canvas',
33
+ backend : 'WebAudio',
34
+ mediaType : 'audio'
35
+ },
36
+
37
+ init: function (params) {
38
+ // Extract relevant parameters (or defaults)
39
+ this.params = WaveSurfer.util.extend({}, this.defaultParams, params);
40
+
41
+ this.container = 'string' == typeof params.container ?
42
+ document.querySelector(this.params.container) :
43
+ this.params.container;
44
+
45
+ if (!this.container) {
46
+ throw new Error('Container element not found');
47
+ }
48
+
49
+ if (typeof this.params.mediaContainer == 'undefined') {
50
+ this.mediaContainer = this.container;
51
+ } else if (typeof this.params.mediaContainer == 'string') {
52
+ this.mediaContainer = document.querySelector(this.params.mediaContainer);
53
+ } else {
54
+ this.mediaContainer = this.params.mediaContainer;
55
+ }
56
+
57
+ if (!this.mediaContainer) {
58
+ throw new Error('Media Container element not found');
59
+ }
60
+
61
+ // Used to save the current volume when muting so we can
62
+ // restore once unmuted
63
+ this.savedVolume = 0;
64
+ // The current muted state
65
+ this.isMuted = false;
66
+ // Will hold a list of event descriptors that need to be
67
+ // cancelled on subsequent loads of audio
68
+ this.tmpEvents = [];
69
+
70
+ this.createDrawer();
71
+ this.createBackend();
72
+ },
73
+
74
+ createDrawer: function () {
75
+ var my = this;
76
+
77
+ this.drawer = Object.create(WaveSurfer.Drawer[this.params.renderer]);
78
+ this.drawer.init(this.container, this.params);
79
+
80
+ this.drawer.on('redraw', function () {
81
+ my.drawBuffer();
82
+ my.drawer.progress(my.backend.getPlayedPercents());
83
+ });
84
+
85
+ // Click-to-seek
86
+ this.drawer.on('click', function (e, progress) {
87
+ setTimeout(function () {
88
+ my.seekTo(progress);
89
+ }, 0);
90
+ });
91
+
92
+ // Relay the scroll event from the drawer
93
+ this.drawer.on('scroll', function (e) {
94
+ my.fireEvent('scroll', e);
95
+ });
96
+ },
97
+
98
+ createBackend: function () {
99
+ var my = this;
100
+
101
+ if (this.backend) {
102
+ this.backend.destroy();
103
+ }
104
+
105
+ // Back compat
106
+ if (this.params.backend == 'AudioElement') {
107
+ this.params.backend = 'MediaElement';
108
+ }
109
+
110
+ if (this.params.backend == 'WebAudio' && !WaveSurfer.WebAudio.supportsWebAudio()) {
111
+ this.params.backend = 'MediaElement';
112
+ }
113
+
114
+ this.backend = Object.create(WaveSurfer[this.params.backend]);
115
+ this.backend.init(this.params);
116
+
117
+ this.backend.on('finish', function () {
118
+ my.fireEvent('finish');
119
+ });
120
+
121
+ this.backend.on('audioprocess', function (time) {
122
+ my.drawer.progress(my.backend.getPlayedPercents());
123
+ my.fireEvent('audioprocess', time);
124
+ });
125
+ },
126
+
127
+ getDuration: function () {
128
+ return this.backend.getDuration();
129
+ },
130
+
131
+ getCurrentTime: function () {
132
+ return this.backend.getCurrentTime();
133
+ },
134
+
135
+ play: function (start, end) {
136
+ this.backend.play(start, end);
137
+ this.fireEvent('play');
138
+ },
139
+
140
+ pause: function () {
141
+ this.backend.pause();
142
+ this.fireEvent('pause');
143
+ },
144
+
145
+ playPause: function () {
146
+ this.backend.isPaused() ? this.play() : this.pause();
147
+ },
148
+
149
+ skipBackward: function (seconds) {
150
+ this.skip(-seconds || -this.params.skipLength);
151
+ },
152
+
153
+ skipForward: function (seconds) {
154
+ this.skip(seconds || this.params.skipLength);
155
+ },
156
+
157
+ skip: function (offset) {
158
+ var position = this.getCurrentTime() || 0;
159
+ var duration = this.getDuration() || 1;
160
+ position = Math.max(0, Math.min(duration, position + (offset || 0)));
161
+ this.seekAndCenter(position / duration);
162
+ },
163
+
164
+ seekAndCenter: function (progress) {
165
+ this.seekTo(progress);
166
+ this.drawer.recenter(progress);
167
+ },
168
+
169
+ seekTo: function (progress) {
170
+ var paused = this.backend.isPaused();
171
+ // avoid small scrolls while paused seeking
172
+ var oldScrollParent = this.params.scrollParent;
173
+ if (paused) {
174
+ this.params.scrollParent = false;
175
+ }
176
+ this.backend.seekTo(progress * this.getDuration());
177
+ this.drawer.progress(this.backend.getPlayedPercents());
178
+
179
+ if (!paused) {
180
+ this.backend.pause();
181
+ this.backend.play();
182
+ }
183
+ this.params.scrollParent = oldScrollParent;
184
+ this.fireEvent('seek', progress);
185
+ },
186
+
187
+ stop: function () {
188
+ this.pause();
189
+ this.seekTo(0);
190
+ this.drawer.progress(0);
191
+ },
192
+
193
+ /**
194
+ * Set the playback volume.
195
+ *
196
+ * @param {Number} newVolume A value between 0 and 1, 0 being no
197
+ * volume and 1 being full volume.
198
+ */
199
+ setVolume: function (newVolume) {
200
+ this.backend.setVolume(newVolume);
201
+ },
202
+
203
+ /**
204
+ * Set the playback rate.
205
+ *
206
+ * @param {Number} rate A positive number. E.g. 0.5 means half the
207
+ * normal speed, 2 means double speed and so on.
208
+ */
209
+ setPlaybackRate: function (rate) {
210
+ this.backend.setPlaybackRate(rate);
211
+ },
212
+
213
+ /**
214
+ * Toggle the volume on and off. It not currenly muted it will
215
+ * save the current volume value and turn the volume off.
216
+ * If currently muted then it will restore the volume to the saved
217
+ * value, and then rest the saved value.
218
+ */
219
+ toggleMute: function () {
220
+ if (this.isMuted) {
221
+ // If currently muted then restore to the saved volume
222
+ // and update the mute properties
223
+ this.backend.setVolume(this.savedVolume);
224
+ this.isMuted = false;
225
+ } else {
226
+ // If currently not muted then save current volume,
227
+ // turn off the volume and update the mute properties
228
+ this.savedVolume = this.backend.getVolume();
229
+ this.backend.setVolume(0);
230
+ this.isMuted = true;
231
+ }
232
+ },
233
+
234
+ toggleScroll: function () {
235
+ this.params.scrollParent = !this.params.scrollParent;
236
+ this.drawBuffer();
237
+ },
238
+
239
+ toggleInteraction: function () {
240
+ this.params.interact = !this.params.interact;
241
+ },
242
+
243
+ drawBuffer: function () {
244
+ var nominalWidth = Math.round(
245
+ this.getDuration() * this.params.minPxPerSec * this.params.pixelRatio
246
+ );
247
+ var parentWidth = this.drawer.getWidth();
248
+ var width = nominalWidth;
249
+
250
+ // Fill container
251
+ if (this.params.fillParent && (!this.params.scrollParent || nominalWidth < parentWidth)) {
252
+ width = parentWidth;
253
+ }
254
+
255
+ var peaks = this.backend.getPeaks(width);
256
+ this.drawer.drawPeaks(peaks, width);
257
+ this.fireEvent('redraw', peaks, width);
258
+ },
259
+
260
+ /**
261
+ * Internal method.
262
+ */
263
+ loadArrayBuffer: function (arraybuffer) {
264
+ this.decodeArrayBuffer(arraybuffer, function (data) {
265
+ this.loadDecodedBuffer(data);
266
+ }.bind(this));
267
+ },
268
+
269
+ /**
270
+ * Directly load an externally decoded AudioBuffer.
271
+ */
272
+ loadDecodedBuffer: function (buffer) {
273
+ this.backend.load(buffer);
274
+ this.drawBuffer();
275
+ this.fireEvent('ready');
276
+ },
277
+
278
+ /**
279
+ * Loads audio data from a Blob or File object.
280
+ *
281
+ * @param {Blob|File} blob Audio data.
282
+ */
283
+ loadBlob: function (blob) {
284
+ var my = this;
285
+ // Create file reader
286
+ var reader = new FileReader();
287
+ reader.addEventListener('progress', function (e) {
288
+ my.onProgress(e);
289
+ });
290
+ reader.addEventListener('load', function (e) {
291
+ my.loadArrayBuffer(e.target.result);
292
+ });
293
+ reader.addEventListener('error', function () {
294
+ my.fireEvent('error', 'Error reading file');
295
+ });
296
+ reader.readAsArrayBuffer(blob);
297
+ this.empty();
298
+ },
299
+
300
+ /**
301
+ * Loads audio and rerenders the waveform.
302
+ */
303
+ load: function (url, peaks) {
304
+ switch (this.params.backend) {
305
+ case 'WebAudio': return this.loadBuffer(url);
306
+ case 'MediaElement': return this.loadMediaElement(url, peaks);
307
+ }
308
+ },
309
+
310
+ /**
311
+ * Loads audio using Web Audio buffer backend.
312
+ */
313
+ loadBuffer: function (url) {
314
+ this.empty();
315
+ // load via XHR and render all at once
316
+ return this.getArrayBuffer(url, this.loadArrayBuffer.bind(this));
317
+ },
318
+
319
+ loadMediaElement: function (url, peaks) {
320
+ this.empty();
321
+ this.backend.load(url, this.mediaContainer, peaks);
322
+
323
+ this.tmpEvents.push(
324
+ this.backend.once('canplay', (function () {
325
+ this.drawBuffer();
326
+ this.fireEvent('ready');
327
+ }).bind(this)),
328
+
329
+ this.backend.once('error', (function (err) {
330
+ this.fireEvent('error', err);
331
+ }).bind(this))
332
+ );
333
+
334
+
335
+ // If no pre-decoded peaks provided, attempt to download the
336
+ // audio file and decode it with Web Audio.
337
+ if (!peaks && this.backend.supportsWebAudio()) {
338
+ this.getArrayBuffer(url, (function (arraybuffer) {
339
+ this.decodeArrayBuffer(arraybuffer, (function (buffer) {
340
+ this.backend.buffer = buffer;
341
+ this.drawBuffer();
342
+ }).bind(this));
343
+ }).bind(this));
344
+ }
345
+ },
346
+
347
+ decodeArrayBuffer: function (arraybuffer, callback) {
348
+ this.backend.decodeArrayBuffer(
349
+ arraybuffer,
350
+ this.fireEvent.bind(this, 'decoded'),
351
+ this.fireEvent.bind(this, 'error', 'Error decoding audiobuffer')
352
+ );
353
+ this.tmpEvents.push(
354
+ this.once('decoded', callback)
355
+ );
356
+ },
357
+
358
+ getArrayBuffer: function (url, callback) {
359
+ var my = this;
360
+ var ajax = WaveSurfer.util.ajax({
361
+ url: url,
362
+ responseType: 'arraybuffer'
363
+ });
364
+ this.tmpEvents.push(
365
+ ajax.on('progress', function (e) {
366
+ my.onProgress(e);
367
+ }),
368
+ ajax.on('success', callback),
369
+ ajax.on('error', function (e) {
370
+ my.fireEvent('error', 'XHR error: ' + e.target.statusText);
371
+ })
372
+ );
373
+ return ajax;
374
+ },
375
+
376
+ onProgress: function (e) {
377
+ if (e.lengthComputable) {
378
+ var percentComplete = e.loaded / e.total;
379
+ } else {
380
+ // Approximate progress with an asymptotic
381
+ // function, and assume downloads in the 1-3 MB range.
382
+ percentComplete = e.loaded / (e.loaded + 1000000);
383
+ }
384
+ this.fireEvent('loading', Math.round(percentComplete * 100), e.target);
385
+ },
386
+
387
+ /**
388
+ * Exports PCM data into a JSON array and opens in a new window.
389
+ */
390
+ exportPCM: function (length, accuracy, noWindow) {
391
+ length = length || 1024;
392
+ accuracy = accuracy || 10000;
393
+ noWindow = noWindow || false;
394
+ var peaks = this.backend.getPeaks(length, accuracy);
395
+ var arr = [].map.call(peaks, function (val) {
396
+ return Math.round(val * accuracy) / accuracy;
397
+ });
398
+ var json = JSON.stringify(arr);
399
+ if (!noWindow) {
400
+ window.open('data:application/json;charset=utf-8,' +
401
+ encodeURIComponent(json));
402
+ }
403
+ return json;
404
+ },
405
+
406
+ clearTmpEvents: function () {
407
+ this.tmpEvents.forEach(function (e) { e.un(); });
408
+ },
409
+
410
+ /**
411
+ * Display empty waveform.
412
+ */
413
+ empty: function () {
414
+ if (!this.backend.isPaused()) {
415
+ this.stop();
416
+ this.backend.disconnectSource();
417
+ }
418
+ this.clearTmpEvents();
419
+ this.drawer.progress(0);
420
+ this.drawer.setWidth(0);
421
+ this.drawer.drawPeaks({ length: this.drawer.getWidth() }, 0);
422
+ },
423
+
424
+ /**
425
+ * Remove events, elements and disconnect WebAudio nodes.
426
+ */
427
+ destroy: function () {
428
+ this.fireEvent('destroy');
429
+ this.clearTmpEvents();
430
+ this.unAll();
431
+ this.backend.destroy();
432
+ this.drawer.destroy();
433
+ }
434
+ };
435
+
436
+ WaveSurfer.create = function (params) {
437
+ var wavesurfer = Object.create(WaveSurfer);
438
+ wavesurfer.init(params);
439
+ return wavesurfer;
440
+ };