wavesurfer 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
};
|