wavesurfer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/wavesurfer.rb +6 -0
- data/lib/wavesurfer/version.rb +3 -0
- data/vendor/assets/javascripts/plugin/wavesurfer.elan.js +252 -0
- data/vendor/assets/javascripts/plugin/wavesurfer.microphone.js +173 -0
- data/vendor/assets/javascripts/plugin/wavesurfer.minimap.js +200 -0
- data/vendor/assets/javascripts/plugin/wavesurfer.regions.js +390 -0
- data/vendor/assets/javascripts/plugin/wavesurfer.spectrogram.js +210 -0
- data/vendor/assets/javascripts/plugin/wavesurfer.timeline.js +196 -0
- data/vendor/assets/javascripts/src/drawer.canvas.js +134 -0
- data/vendor/assets/javascripts/src/drawer.js +193 -0
- data/vendor/assets/javascripts/src/mediaelement.js +160 -0
- data/vendor/assets/javascripts/src/util.js +132 -0
- data/vendor/assets/javascripts/src/wavesurfer.js +440 -0
- data/vendor/assets/javascripts/src/webaudio.js +385 -0
- data/vendor/assets/javascripts/wavesurfer-plugins.js +6 -0
- data/vendor/assets/javascripts/wavesurfer.js +6 -0
- metadata +88 -0
@@ -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
|
+
};
|