wavesurfer.js 6.5.0 → 7.0.0-alpha.0
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.
- package/.eslintrc.json +24 -0
- package/.prettierrc +8 -0
- package/README.md +26 -117
- package/dist/base-plugin.d.ts +10 -0
- package/dist/base-plugin.js +13 -0
- package/dist/decoder.d.ts +10 -0
- package/dist/decoder.js +43 -0
- package/dist/event-emitter.d.ts +11 -0
- package/dist/event-emitter.js +21 -0
- package/dist/fetcher.d.ts +4 -0
- package/dist/fetcher.js +17 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.js +166 -0
- package/dist/player-webaudio.d.ts +8 -0
- package/dist/player-webaudio.js +31 -0
- package/dist/player.d.ts +16 -0
- package/dist/player.js +40 -0
- package/dist/plugins/regions.d.ts +48 -0
- package/dist/plugins/regions.js +183 -0
- package/dist/renderer.d.ts +34 -0
- package/dist/renderer.js +193 -0
- package/dist/timer.d.ts +10 -0
- package/dist/timer.js +17 -0
- package/examples/audio.ogg +0 -0
- package/examples/bars.js +19 -0
- package/examples/basic.js +8 -0
- package/examples/gradient.js +28 -0
- package/examples/regions.js +63 -0
- package/examples/video.js +19 -0
- package/examples/webaudio.js +14 -0
- package/package.json +19 -99
- package/src/base-plugin.ts +20 -0
- package/src/decoder.ts +41 -0
- package/src/event-emitter.ts +35 -0
- package/src/fetcher.ts +7 -0
- package/src/index.ts +252 -0
- package/src/player-webaudio.ts +34 -0
- package/src/player.ts +50 -0
- package/src/plugins/regions.ts +240 -0
- package/src/renderer.ts +250 -0
- package/src/timer.ts +27 -0
- package/tsconfig.json +105 -0
- package/tutorial/index.html +47 -0
- package/tutorial/src/editor.js +70 -0
- package/tutorial/src/init.js +66 -0
- package/tutorial/src/url.js +25 -0
- package/tutorial/style.css +211 -0
- package/yarn-error.log +1049 -0
- package/LICENSE +0 -29
- package/dist/plugin/wavesurfer.cursor.js +0 -418
- package/dist/plugin/wavesurfer.cursor.js.map +0 -1
- package/dist/plugin/wavesurfer.cursor.min.js +0 -7
- package/dist/plugin/wavesurfer.cursor.min.js.map +0 -1
- package/dist/plugin/wavesurfer.elan.js +0 -381
- package/dist/plugin/wavesurfer.elan.js.map +0 -1
- package/dist/plugin/wavesurfer.elan.min.js +0 -7
- package/dist/plugin/wavesurfer.elan.min.js.map +0 -1
- package/dist/plugin/wavesurfer.markers.js +0 -520
- package/dist/plugin/wavesurfer.markers.js.map +0 -1
- package/dist/plugin/wavesurfer.markers.min.js +0 -7
- package/dist/plugin/wavesurfer.markers.min.js.map +0 -1
- package/dist/plugin/wavesurfer.mediasession.js +0 -181
- package/dist/plugin/wavesurfer.mediasession.js.map +0 -1
- package/dist/plugin/wavesurfer.mediasession.min.js +0 -7
- package/dist/plugin/wavesurfer.mediasession.min.js.map +0 -1
- package/dist/plugin/wavesurfer.microphone.js +0 -466
- package/dist/plugin/wavesurfer.microphone.js.map +0 -1
- package/dist/plugin/wavesurfer.microphone.min.js +0 -7
- package/dist/plugin/wavesurfer.microphone.min.js.map +0 -1
- package/dist/plugin/wavesurfer.minimap.js +0 -440
- package/dist/plugin/wavesurfer.minimap.js.map +0 -1
- package/dist/plugin/wavesurfer.minimap.min.js +0 -7
- package/dist/plugin/wavesurfer.minimap.min.js.map +0 -1
- package/dist/plugin/wavesurfer.playhead.js +0 -300
- package/dist/plugin/wavesurfer.playhead.js.map +0 -1
- package/dist/plugin/wavesurfer.playhead.min.js +0 -7
- package/dist/plugin/wavesurfer.playhead.min.js.map +0 -1
- package/dist/plugin/wavesurfer.regions.js +0 -1342
- package/dist/plugin/wavesurfer.regions.js.map +0 -1
- package/dist/plugin/wavesurfer.regions.min.js +0 -7
- package/dist/plugin/wavesurfer.regions.min.js.map +0 -1
- package/dist/plugin/wavesurfer.spectrogram.js +0 -736
- package/dist/plugin/wavesurfer.spectrogram.js.map +0 -1
- package/dist/plugin/wavesurfer.spectrogram.min.js +0 -7
- package/dist/plugin/wavesurfer.spectrogram.min.js.map +0 -1
- package/dist/plugin/wavesurfer.timeline.js +0 -649
- package/dist/plugin/wavesurfer.timeline.js.map +0 -1
- package/dist/plugin/wavesurfer.timeline.min.js +0 -7
- package/dist/plugin/wavesurfer.timeline.min.js.map +0 -1
- package/dist/wavesurfer-html-init.js +0 -381
- package/dist/wavesurfer-html-init.js.map +0 -1
- package/dist/wavesurfer-html-init.min.js +0 -7
- package/dist/wavesurfer-html-init.min.js.map +0 -1
- package/dist/wavesurfer.js +0 -6145
- package/dist/wavesurfer.js.map +0 -1
- package/dist/wavesurfer.min.js +0 -7
- package/dist/wavesurfer.min.js.map +0 -1
- package/src/drawer.canvasentry.js +0 -427
- package/src/drawer.js +0 -413
- package/src/drawer.multicanvas.js +0 -617
- package/src/html-init.js +0 -241
- package/src/mediaelement-webaudio.js +0 -77
- package/src/mediaelement.js +0 -443
- package/src/peakcache.js +0 -127
- package/src/plugin/cursor/index.js +0 -350
- package/src/plugin/elan/index.js +0 -307
- package/src/plugin/markers/index.js +0 -436
- package/src/plugin/mediasession/index.js +0 -94
- package/src/plugin/microphone/index.js +0 -415
- package/src/plugin/minimap/index.js +0 -403
- package/src/plugin/playhead/index.js +0 -226
- package/src/plugin/regions/index.js +0 -478
- package/src/plugin/regions/region.js +0 -883
- package/src/plugin/spectrogram/fft.js +0 -222
- package/src/plugin/spectrogram/index.js +0 -522
- package/src/plugin/timeline/index.js +0 -600
- package/src/util/absMax.js +0 -16
- package/src/util/clamp.js +0 -11
- package/src/util/fetch.js +0 -222
- package/src/util/frame.js +0 -13
- package/src/util/get-id.js +0 -22
- package/src/util/index.js +0 -14
- package/src/util/max.js +0 -16
- package/src/util/min.js +0 -16
- package/src/util/observer.js +0 -144
- package/src/util/orientation.js +0 -98
- package/src/util/prevent-click.js +0 -19
- package/src/util/request-animation-frame.js +0 -15
- package/src/util/silence-mode.js +0 -42
- package/src/util/style.js +0 -16
- package/src/wavesurfer.js +0 -1797
- package/src/webaudio.js +0 -779
package/src/webaudio.js
DELETED
|
@@ -1,779 +0,0 @@
|
|
|
1
|
-
import * as util from './util';
|
|
2
|
-
|
|
3
|
-
// using constants to prevent someone writing the string wrong
|
|
4
|
-
const PLAYING = 'playing';
|
|
5
|
-
const PAUSED = 'paused';
|
|
6
|
-
const FINISHED = 'finished';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* WebAudio backend
|
|
10
|
-
*
|
|
11
|
-
* @extends {Observer}
|
|
12
|
-
*/
|
|
13
|
-
export default class WebAudio extends util.Observer {
|
|
14
|
-
/** scriptBufferSize: size of the processing buffer */
|
|
15
|
-
static scriptBufferSize = 256;
|
|
16
|
-
/** audioContext: allows to process audio with WebAudio API */
|
|
17
|
-
audioContext = null;
|
|
18
|
-
/** @private */
|
|
19
|
-
offlineAudioContext = null;
|
|
20
|
-
/** @private */
|
|
21
|
-
stateBehaviors = {
|
|
22
|
-
[PLAYING]: {
|
|
23
|
-
init() {
|
|
24
|
-
this.addOnAudioProcess();
|
|
25
|
-
},
|
|
26
|
-
getPlayedPercents() {
|
|
27
|
-
const duration = this.getDuration();
|
|
28
|
-
return this.getCurrentTime() / duration || 0;
|
|
29
|
-
},
|
|
30
|
-
getCurrentTime() {
|
|
31
|
-
return this.startPosition + this.getPlayedTime();
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
[PAUSED]: {
|
|
35
|
-
init() {
|
|
36
|
-
this.removeOnAudioProcess();
|
|
37
|
-
},
|
|
38
|
-
getPlayedPercents() {
|
|
39
|
-
const duration = this.getDuration();
|
|
40
|
-
return this.getCurrentTime() / duration || 0;
|
|
41
|
-
},
|
|
42
|
-
getCurrentTime() {
|
|
43
|
-
return this.startPosition;
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
[FINISHED]: {
|
|
47
|
-
init() {
|
|
48
|
-
this.removeOnAudioProcess();
|
|
49
|
-
this.fireEvent('finish');
|
|
50
|
-
},
|
|
51
|
-
getPlayedPercents() {
|
|
52
|
-
return 1;
|
|
53
|
-
},
|
|
54
|
-
getCurrentTime() {
|
|
55
|
-
return this.getDuration();
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Does the browser support this backend
|
|
62
|
-
*
|
|
63
|
-
* @return {boolean} Whether or not this browser supports this backend
|
|
64
|
-
*/
|
|
65
|
-
supportsWebAudio() {
|
|
66
|
-
return !!(window.AudioContext || window.webkitAudioContext);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Get the audio context used by this backend or create one
|
|
71
|
-
*
|
|
72
|
-
* @return {AudioContext} Existing audio context, or creates a new one
|
|
73
|
-
*/
|
|
74
|
-
getAudioContext() {
|
|
75
|
-
if (!window.WaveSurferAudioContext) {
|
|
76
|
-
window.WaveSurferAudioContext = new (window.AudioContext ||
|
|
77
|
-
window.webkitAudioContext)();
|
|
78
|
-
}
|
|
79
|
-
return window.WaveSurferAudioContext;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Get the offline audio context used by this backend or create one
|
|
84
|
-
*
|
|
85
|
-
* @param {number} sampleRate The sample rate to use
|
|
86
|
-
* @return {OfflineAudioContext} Existing offline audio context, or creates
|
|
87
|
-
* a new one
|
|
88
|
-
*/
|
|
89
|
-
getOfflineAudioContext(sampleRate) {
|
|
90
|
-
if (!window.WaveSurferOfflineAudioContext) {
|
|
91
|
-
window.WaveSurferOfflineAudioContext = new (window.OfflineAudioContext ||
|
|
92
|
-
window.webkitOfflineAudioContext)(1, 2, sampleRate);
|
|
93
|
-
}
|
|
94
|
-
return window.WaveSurferOfflineAudioContext;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Construct the backend
|
|
99
|
-
*
|
|
100
|
-
* @param {WavesurferParams} params Wavesurfer parameters
|
|
101
|
-
*/
|
|
102
|
-
constructor(params) {
|
|
103
|
-
super();
|
|
104
|
-
/** @private */
|
|
105
|
-
this.params = params;
|
|
106
|
-
/** ac: Audio Context instance */
|
|
107
|
-
this.ac =
|
|
108
|
-
params.audioContext ||
|
|
109
|
-
(this.supportsWebAudio() ? this.getAudioContext() : {});
|
|
110
|
-
/**@private */
|
|
111
|
-
this.lastPlay = this.ac.currentTime;
|
|
112
|
-
/** @private */
|
|
113
|
-
this.startPosition = 0;
|
|
114
|
-
/** @private */
|
|
115
|
-
this.scheduledPause = null;
|
|
116
|
-
/** @private */
|
|
117
|
-
this.states = {
|
|
118
|
-
[PLAYING]: Object.create(this.stateBehaviors[PLAYING]),
|
|
119
|
-
[PAUSED]: Object.create(this.stateBehaviors[PAUSED]),
|
|
120
|
-
[FINISHED]: Object.create(this.stateBehaviors[FINISHED])
|
|
121
|
-
};
|
|
122
|
-
/** @private */
|
|
123
|
-
this.buffer = null;
|
|
124
|
-
/** @private */
|
|
125
|
-
this.filters = [];
|
|
126
|
-
/** gainNode: allows to control audio volume */
|
|
127
|
-
this.gainNode = null;
|
|
128
|
-
/** @private */
|
|
129
|
-
this.mergedPeaks = null;
|
|
130
|
-
/** @private */
|
|
131
|
-
this.offlineAc = null;
|
|
132
|
-
/** @private */
|
|
133
|
-
this.peaks = null;
|
|
134
|
-
/** @private */
|
|
135
|
-
this.playbackRate = 1;
|
|
136
|
-
/** analyser: provides audio analysis information */
|
|
137
|
-
this.analyser = null;
|
|
138
|
-
/** scriptNode: allows processing audio */
|
|
139
|
-
this.scriptNode = null;
|
|
140
|
-
/** @private */
|
|
141
|
-
this.source = null;
|
|
142
|
-
/** @private */
|
|
143
|
-
this.splitPeaks = [];
|
|
144
|
-
/** @private */
|
|
145
|
-
this.state = null;
|
|
146
|
-
/** @private */
|
|
147
|
-
this.explicitDuration = params.duration;
|
|
148
|
-
/** @private */
|
|
149
|
-
this.sinkStreamDestination = null;
|
|
150
|
-
/** @private */
|
|
151
|
-
this.sinkAudioElement = null;
|
|
152
|
-
/**
|
|
153
|
-
* Boolean indicating if the backend was destroyed.
|
|
154
|
-
*/
|
|
155
|
-
this.destroyed = false;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Initialise the backend, called in `wavesurfer.createBackend()`
|
|
160
|
-
*/
|
|
161
|
-
init() {
|
|
162
|
-
this.createVolumeNode();
|
|
163
|
-
this.createScriptNode();
|
|
164
|
-
this.createAnalyserNode();
|
|
165
|
-
|
|
166
|
-
this.setState(PAUSED);
|
|
167
|
-
this.setPlaybackRate(this.params.audioRate);
|
|
168
|
-
this.setLength(0);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** @private */
|
|
172
|
-
disconnectFilters() {
|
|
173
|
-
if (this.filters) {
|
|
174
|
-
this.filters.forEach(filter => {
|
|
175
|
-
filter && filter.disconnect();
|
|
176
|
-
});
|
|
177
|
-
this.filters = null;
|
|
178
|
-
// Reconnect direct path
|
|
179
|
-
this.analyser.connect(this.gainNode);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* @private
|
|
185
|
-
*
|
|
186
|
-
* @param {string} state The new state
|
|
187
|
-
*/
|
|
188
|
-
setState(state) {
|
|
189
|
-
if (this.state !== this.states[state]) {
|
|
190
|
-
this.state = this.states[state];
|
|
191
|
-
this.state.init.call(this);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Unpacked `setFilters()`
|
|
197
|
-
*
|
|
198
|
-
* @param {...AudioNode} filters One or more filters to set
|
|
199
|
-
*/
|
|
200
|
-
setFilter(...filters) {
|
|
201
|
-
this.setFilters(filters);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Insert custom Web Audio nodes into the graph
|
|
206
|
-
*
|
|
207
|
-
* @param {AudioNode[]} filters Packed filters array
|
|
208
|
-
* @example
|
|
209
|
-
* const lowpass = wavesurfer.backend.ac.createBiquadFilter();
|
|
210
|
-
* wavesurfer.backend.setFilter(lowpass);
|
|
211
|
-
*/
|
|
212
|
-
setFilters(filters) {
|
|
213
|
-
// Remove existing filters
|
|
214
|
-
this.disconnectFilters();
|
|
215
|
-
|
|
216
|
-
// Insert filters if filter array not empty
|
|
217
|
-
if (filters && filters.length) {
|
|
218
|
-
this.filters = filters;
|
|
219
|
-
|
|
220
|
-
// Disconnect direct path before inserting filters
|
|
221
|
-
this.analyser.disconnect();
|
|
222
|
-
|
|
223
|
-
// Connect each filter in turn
|
|
224
|
-
filters
|
|
225
|
-
.reduce((prev, curr) => {
|
|
226
|
-
prev.connect(curr);
|
|
227
|
-
return curr;
|
|
228
|
-
}, this.analyser)
|
|
229
|
-
.connect(this.gainNode);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
/** Create ScriptProcessorNode to process audio */
|
|
233
|
-
createScriptNode() {
|
|
234
|
-
if (this.params.audioScriptProcessor) {
|
|
235
|
-
this.scriptNode = this.params.audioScriptProcessor;
|
|
236
|
-
} else {
|
|
237
|
-
if (this.ac.createScriptProcessor) {
|
|
238
|
-
this.scriptNode = this.ac.createScriptProcessor(
|
|
239
|
-
WebAudio.scriptBufferSize
|
|
240
|
-
);
|
|
241
|
-
} else {
|
|
242
|
-
this.scriptNode = this.ac.createJavaScriptNode(
|
|
243
|
-
WebAudio.scriptBufferSize
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
this.scriptNode.connect(this.ac.destination);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/** @private */
|
|
251
|
-
addOnAudioProcess() {
|
|
252
|
-
this.scriptNode.onaudioprocess = () => {
|
|
253
|
-
const time = this.getCurrentTime();
|
|
254
|
-
|
|
255
|
-
if (time >= this.getDuration()) {
|
|
256
|
-
this.setState(FINISHED);
|
|
257
|
-
this.fireEvent('pause');
|
|
258
|
-
} else if (time >= this.scheduledPause) {
|
|
259
|
-
this.pause();
|
|
260
|
-
} else if (this.state === this.states[PLAYING]) {
|
|
261
|
-
this.fireEvent('audioprocess', time);
|
|
262
|
-
}
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/** @private */
|
|
267
|
-
removeOnAudioProcess() {
|
|
268
|
-
this.scriptNode.onaudioprocess = null;
|
|
269
|
-
}
|
|
270
|
-
/** Create analyser node to perform audio analysis */
|
|
271
|
-
createAnalyserNode() {
|
|
272
|
-
this.analyser = this.ac.createAnalyser();
|
|
273
|
-
this.analyser.connect(this.gainNode);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Create the gain node needed to control the playback volume.
|
|
278
|
-
*
|
|
279
|
-
*/
|
|
280
|
-
createVolumeNode() {
|
|
281
|
-
// Create gain node using the AudioContext
|
|
282
|
-
if (this.ac.createGain) {
|
|
283
|
-
this.gainNode = this.ac.createGain();
|
|
284
|
-
} else {
|
|
285
|
-
this.gainNode = this.ac.createGainNode();
|
|
286
|
-
}
|
|
287
|
-
// Add the gain node to the graph
|
|
288
|
-
this.gainNode.connect(this.ac.destination);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Set the sink id for the media player
|
|
293
|
-
*
|
|
294
|
-
* @param {string} deviceId String value representing audio device id.
|
|
295
|
-
* @returns {Promise} A Promise that resolves to `undefined` when there
|
|
296
|
-
* are no errors.
|
|
297
|
-
*/
|
|
298
|
-
setSinkId(deviceId) {
|
|
299
|
-
if (deviceId) {
|
|
300
|
-
/**
|
|
301
|
-
* The webaudio API doesn't currently support setting the device
|
|
302
|
-
* output. Here we create an HTMLAudioElement, connect the
|
|
303
|
-
* webaudio stream to that element and setSinkId there.
|
|
304
|
-
*/
|
|
305
|
-
if (!this.sinkAudioElement) {
|
|
306
|
-
this.sinkAudioElement = new window.Audio();
|
|
307
|
-
// autoplay is necessary since we're not invoking .play()
|
|
308
|
-
this.sinkAudioElement.autoplay = true;
|
|
309
|
-
}
|
|
310
|
-
if (!this.sinkAudioElement.setSinkId) {
|
|
311
|
-
return Promise.reject(
|
|
312
|
-
new Error('setSinkId is not supported in your browser')
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
if (!this.sinkStreamDestination) {
|
|
316
|
-
this.sinkStreamDestination = this.ac.createMediaStreamDestination();
|
|
317
|
-
}
|
|
318
|
-
this.gainNode.disconnect();
|
|
319
|
-
this.gainNode.connect(this.sinkStreamDestination);
|
|
320
|
-
this.sinkAudioElement.srcObject = this.sinkStreamDestination.stream;
|
|
321
|
-
|
|
322
|
-
return this.sinkAudioElement.setSinkId(deviceId);
|
|
323
|
-
} else {
|
|
324
|
-
return Promise.reject(new Error('Invalid deviceId: ' + deviceId));
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Set the audio volume
|
|
330
|
-
*
|
|
331
|
-
* @param {number} value A floating point value between 0 and 1.
|
|
332
|
-
*/
|
|
333
|
-
setVolume(value) {
|
|
334
|
-
this.gainNode.gain.setValueAtTime(value, this.ac.currentTime);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Get the current volume
|
|
339
|
-
*
|
|
340
|
-
* @return {number} value A floating point value between 0 and 1.
|
|
341
|
-
*/
|
|
342
|
-
getVolume() {
|
|
343
|
-
return this.gainNode.gain.value;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Decode an array buffer and pass data to a callback
|
|
348
|
-
*
|
|
349
|
-
* @private
|
|
350
|
-
* @param {ArrayBuffer} arraybuffer The array buffer to decode
|
|
351
|
-
* @param {function} callback The function to call on complete.
|
|
352
|
-
* @param {function} errback The function to call on error.
|
|
353
|
-
*/
|
|
354
|
-
decodeArrayBuffer(arraybuffer, callback, errback) {
|
|
355
|
-
if (!this.offlineAc) {
|
|
356
|
-
this.offlineAc = this.getOfflineAudioContext(
|
|
357
|
-
this.ac && this.ac.sampleRate ? this.ac.sampleRate : 44100
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
if ('webkitAudioContext' in window) {
|
|
361
|
-
// Safari: no support for Promise-based decodeAudioData enabled
|
|
362
|
-
// Enable it in Safari using the Experimental Features > Modern WebAudio API option
|
|
363
|
-
this.offlineAc.decodeAudioData(
|
|
364
|
-
arraybuffer,
|
|
365
|
-
data => callback(data),
|
|
366
|
-
errback
|
|
367
|
-
);
|
|
368
|
-
} else {
|
|
369
|
-
this.offlineAc.decodeAudioData(arraybuffer).then(
|
|
370
|
-
(data) => callback(data)
|
|
371
|
-
).catch(
|
|
372
|
-
(err) => errback(err)
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Set pre-decoded peaks
|
|
379
|
-
*
|
|
380
|
-
* @param {number[]|Number.<Array[]>} peaks Peaks data
|
|
381
|
-
* @param {?number} duration Explicit duration
|
|
382
|
-
*/
|
|
383
|
-
setPeaks(peaks, duration) {
|
|
384
|
-
if (duration != null) {
|
|
385
|
-
this.explicitDuration = duration;
|
|
386
|
-
}
|
|
387
|
-
this.peaks = peaks;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Set the rendered length (different from the length of the audio)
|
|
392
|
-
*
|
|
393
|
-
* @param {number} length The rendered length
|
|
394
|
-
*/
|
|
395
|
-
setLength(length) {
|
|
396
|
-
// No resize, we can preserve the cached peaks.
|
|
397
|
-
if (this.mergedPeaks && length == 2 * this.mergedPeaks.length - 1 + 2) {
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
this.splitPeaks = [];
|
|
402
|
-
this.mergedPeaks = [];
|
|
403
|
-
// Set the last element of the sparse array so the peak arrays are
|
|
404
|
-
// appropriately sized for other calculations.
|
|
405
|
-
const channels = this.buffer ? this.buffer.numberOfChannels : 1;
|
|
406
|
-
let c;
|
|
407
|
-
for (c = 0; c < channels; c++) {
|
|
408
|
-
this.splitPeaks[c] = [];
|
|
409
|
-
this.splitPeaks[c][2 * (length - 1)] = 0;
|
|
410
|
-
this.splitPeaks[c][2 * (length - 1) + 1] = 0;
|
|
411
|
-
}
|
|
412
|
-
this.mergedPeaks[2 * (length - 1)] = 0;
|
|
413
|
-
this.mergedPeaks[2 * (length - 1) + 1] = 0;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Compute the max and min value of the waveform when broken into <length> subranges.
|
|
418
|
-
*
|
|
419
|
-
* @param {number} length How many subranges to break the waveform into.
|
|
420
|
-
* @param {number} first First sample in the required range.
|
|
421
|
-
* @param {number} last Last sample in the required range.
|
|
422
|
-
* @return {number[]|Number.<Array[]>} Array of 2*<length> peaks or array of arrays of
|
|
423
|
-
* peaks consisting of (max, min) values for each subrange.
|
|
424
|
-
*/
|
|
425
|
-
getPeaks(length, first, last) {
|
|
426
|
-
if (this.peaks) {
|
|
427
|
-
return this.peaks;
|
|
428
|
-
}
|
|
429
|
-
if (!this.buffer) {
|
|
430
|
-
return [];
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
first = first || 0;
|
|
434
|
-
last = last || length - 1;
|
|
435
|
-
|
|
436
|
-
this.setLength(length);
|
|
437
|
-
|
|
438
|
-
if (!this.buffer) {
|
|
439
|
-
return this.params.splitChannels
|
|
440
|
-
? this.splitPeaks
|
|
441
|
-
: this.mergedPeaks;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* The following snippet fixes a buffering data issue on the Safari
|
|
446
|
-
* browser which returned undefined It creates the missing buffer based
|
|
447
|
-
* on 1 channel, 4096 samples and the sampleRate from the current
|
|
448
|
-
* webaudio context 4096 samples seemed to be the best fit for rendering
|
|
449
|
-
* will review this code once a stable version of Safari TP is out
|
|
450
|
-
*/
|
|
451
|
-
if (!this.buffer.length) {
|
|
452
|
-
const newBuffer = this.createBuffer(1, 4096, this.sampleRate);
|
|
453
|
-
this.buffer = newBuffer.buffer;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const sampleSize = this.buffer.length / length;
|
|
457
|
-
const sampleStep = ~~(sampleSize / 10) || 1;
|
|
458
|
-
const channels = this.buffer.numberOfChannels;
|
|
459
|
-
let c;
|
|
460
|
-
|
|
461
|
-
for (c = 0; c < channels; c++) {
|
|
462
|
-
const peaks = this.splitPeaks[c];
|
|
463
|
-
const chan = this.buffer.getChannelData(c);
|
|
464
|
-
let i;
|
|
465
|
-
|
|
466
|
-
for (i = first; i <= last; i++) {
|
|
467
|
-
const start = ~~(i * sampleSize);
|
|
468
|
-
const end = ~~(start + sampleSize);
|
|
469
|
-
/**
|
|
470
|
-
* Initialize the max and min to the first sample of this
|
|
471
|
-
* subrange, so that even if the samples are entirely
|
|
472
|
-
* on one side of zero, we still return the true max and
|
|
473
|
-
* min values in the subrange.
|
|
474
|
-
*/
|
|
475
|
-
let min = chan[start];
|
|
476
|
-
let max = min;
|
|
477
|
-
let j;
|
|
478
|
-
|
|
479
|
-
for (j = start; j < end; j += sampleStep) {
|
|
480
|
-
const value = chan[j];
|
|
481
|
-
|
|
482
|
-
if (value > max) {
|
|
483
|
-
max = value;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
if (value < min) {
|
|
487
|
-
min = value;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
peaks[2 * i] = max;
|
|
492
|
-
peaks[2 * i + 1] = min;
|
|
493
|
-
|
|
494
|
-
if (c == 0 || max > this.mergedPeaks[2 * i]) {
|
|
495
|
-
this.mergedPeaks[2 * i] = max;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
if (c == 0 || min < this.mergedPeaks[2 * i + 1]) {
|
|
499
|
-
this.mergedPeaks[2 * i + 1] = min;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
return this.params.splitChannels ? this.splitPeaks : this.mergedPeaks;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Get the position from 0 to 1
|
|
509
|
-
*
|
|
510
|
-
* @return {number} Position
|
|
511
|
-
*/
|
|
512
|
-
getPlayedPercents() {
|
|
513
|
-
return this.state.getPlayedPercents.call(this);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/** @private */
|
|
517
|
-
disconnectSource() {
|
|
518
|
-
if (this.source) {
|
|
519
|
-
this.source.disconnect();
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* Destroy all references with WebAudio, disconnecting audio nodes and closing Audio Context
|
|
524
|
-
*/
|
|
525
|
-
destroyWebAudio() {
|
|
526
|
-
this.disconnectFilters();
|
|
527
|
-
this.disconnectSource();
|
|
528
|
-
this.gainNode.disconnect();
|
|
529
|
-
this.scriptNode.disconnect();
|
|
530
|
-
this.analyser.disconnect();
|
|
531
|
-
|
|
532
|
-
// close the audioContext if closeAudioContext option is set to true
|
|
533
|
-
if (this.params.closeAudioContext) {
|
|
534
|
-
// check if browser supports AudioContext.close()
|
|
535
|
-
if (
|
|
536
|
-
typeof this.ac.close === 'function' &&
|
|
537
|
-
this.ac.state != 'closed'
|
|
538
|
-
) {
|
|
539
|
-
this.ac.close();
|
|
540
|
-
}
|
|
541
|
-
// clear the reference to the audiocontext
|
|
542
|
-
this.ac = null;
|
|
543
|
-
// clear the actual audiocontext, either passed as param or the
|
|
544
|
-
// global singleton
|
|
545
|
-
if (!this.params.audioContext) {
|
|
546
|
-
window.WaveSurferAudioContext = null;
|
|
547
|
-
} else {
|
|
548
|
-
this.params.audioContext = null;
|
|
549
|
-
}
|
|
550
|
-
// clear the offlineAudioContext
|
|
551
|
-
window.WaveSurferOfflineAudioContext = null;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// disconnect resources used by setSinkId
|
|
555
|
-
if (this.sinkStreamDestination) {
|
|
556
|
-
this.sinkAudioElement.pause();
|
|
557
|
-
this.sinkAudioElement.srcObject = null;
|
|
558
|
-
this.sinkStreamDestination.disconnect();
|
|
559
|
-
this.sinkStreamDestination = null;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* This is called when wavesurfer is destroyed
|
|
564
|
-
*/
|
|
565
|
-
destroy() {
|
|
566
|
-
if (!this.isPaused()) {
|
|
567
|
-
this.pause();
|
|
568
|
-
}
|
|
569
|
-
this.unAll();
|
|
570
|
-
this.buffer = null;
|
|
571
|
-
this.destroyed = true;
|
|
572
|
-
|
|
573
|
-
this.destroyWebAudio();
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Loaded a decoded audio buffer
|
|
578
|
-
*
|
|
579
|
-
* @param {Object} buffer Decoded audio buffer to load
|
|
580
|
-
*/
|
|
581
|
-
load(buffer) {
|
|
582
|
-
this.startPosition = 0;
|
|
583
|
-
this.lastPlay = this.ac.currentTime;
|
|
584
|
-
this.buffer = buffer;
|
|
585
|
-
this.createSource();
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
/** @private */
|
|
589
|
-
createSource() {
|
|
590
|
-
this.disconnectSource();
|
|
591
|
-
this.source = this.ac.createBufferSource();
|
|
592
|
-
|
|
593
|
-
// adjust for old browsers
|
|
594
|
-
this.source.start = this.source.start || this.source.noteGrainOn;
|
|
595
|
-
this.source.stop = this.source.stop || this.source.noteOff;
|
|
596
|
-
|
|
597
|
-
this.setPlaybackRate(this.playbackRate);
|
|
598
|
-
this.source.buffer = this.buffer;
|
|
599
|
-
this.source.connect(this.analyser);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/**
|
|
603
|
-
* @private
|
|
604
|
-
*
|
|
605
|
-
* some browsers require an explicit call to #resume before they will play back audio
|
|
606
|
-
*/
|
|
607
|
-
resumeAudioContext() {
|
|
608
|
-
if (this.ac.state == 'suspended') {
|
|
609
|
-
this.ac.resume && this.ac.resume();
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
/**
|
|
614
|
-
* Used by `wavesurfer.isPlaying()` and `wavesurfer.playPause()`
|
|
615
|
-
*
|
|
616
|
-
* @return {boolean} Whether or not this backend is currently paused
|
|
617
|
-
*/
|
|
618
|
-
isPaused() {
|
|
619
|
-
return this.state !== this.states[PLAYING];
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
/**
|
|
623
|
-
* Used by `wavesurfer.getDuration()`
|
|
624
|
-
*
|
|
625
|
-
* @return {number} Duration of loaded buffer
|
|
626
|
-
*/
|
|
627
|
-
getDuration() {
|
|
628
|
-
if (this.explicitDuration) {
|
|
629
|
-
return this.explicitDuration;
|
|
630
|
-
}
|
|
631
|
-
if (!this.buffer) {
|
|
632
|
-
return 0;
|
|
633
|
-
}
|
|
634
|
-
return this.buffer.duration;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
/**
|
|
638
|
-
* Used by `wavesurfer.seekTo()`
|
|
639
|
-
*
|
|
640
|
-
* @param {number} start Position to start at in seconds
|
|
641
|
-
* @param {number} end Position to end at in seconds
|
|
642
|
-
* @return {{start: number, end: number}} Object containing start and end
|
|
643
|
-
* positions
|
|
644
|
-
*/
|
|
645
|
-
seekTo(start, end) {
|
|
646
|
-
if (!this.buffer) {
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
this.scheduledPause = null;
|
|
651
|
-
|
|
652
|
-
if (start == null) {
|
|
653
|
-
start = this.getCurrentTime();
|
|
654
|
-
if (start >= this.getDuration()) {
|
|
655
|
-
start = 0;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
if (end == null) {
|
|
659
|
-
end = this.getDuration();
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
this.startPosition = start;
|
|
663
|
-
this.lastPlay = this.ac.currentTime;
|
|
664
|
-
|
|
665
|
-
if (this.state === this.states[FINISHED]) {
|
|
666
|
-
this.setState(PAUSED);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
return {
|
|
670
|
-
start: start,
|
|
671
|
-
end: end
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
/**
|
|
676
|
-
* Get the playback position in seconds
|
|
677
|
-
*
|
|
678
|
-
* @return {number} The playback position in seconds
|
|
679
|
-
*/
|
|
680
|
-
getPlayedTime() {
|
|
681
|
-
return (this.ac.currentTime - this.lastPlay) * this.playbackRate;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Plays the loaded audio region.
|
|
686
|
-
*
|
|
687
|
-
* @param {number} start Start offset in seconds, relative to the beginning
|
|
688
|
-
* of a clip.
|
|
689
|
-
* @param {number} end When to stop relative to the beginning of a clip.
|
|
690
|
-
*/
|
|
691
|
-
play(start, end) {
|
|
692
|
-
if (!this.buffer) {
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// need to re-create source on each playback
|
|
697
|
-
this.createSource();
|
|
698
|
-
|
|
699
|
-
const adjustedTime = this.seekTo(start, end);
|
|
700
|
-
|
|
701
|
-
start = adjustedTime.start;
|
|
702
|
-
end = adjustedTime.end;
|
|
703
|
-
|
|
704
|
-
this.scheduledPause = end;
|
|
705
|
-
|
|
706
|
-
this.source.start(0, start);
|
|
707
|
-
|
|
708
|
-
this.resumeAudioContext();
|
|
709
|
-
|
|
710
|
-
this.setState(PLAYING);
|
|
711
|
-
|
|
712
|
-
this.fireEvent('play');
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
/**
|
|
716
|
-
* Pauses the loaded audio.
|
|
717
|
-
*/
|
|
718
|
-
pause() {
|
|
719
|
-
this.scheduledPause = null;
|
|
720
|
-
|
|
721
|
-
this.startPosition += this.getPlayedTime();
|
|
722
|
-
try {
|
|
723
|
-
this.source && this.source.stop(0);
|
|
724
|
-
} catch (err) {
|
|
725
|
-
// Calling stop can throw the following 2 errors:
|
|
726
|
-
// - RangeError (The value specified for when is negative.)
|
|
727
|
-
// - InvalidStateNode (The node has not been started by calling start().)
|
|
728
|
-
// We can safely ignore both errors, because:
|
|
729
|
-
// - The range is surely correct
|
|
730
|
-
// - The node might not have been started yet, in which case we just want to carry on without causing any trouble.
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
this.setState(PAUSED);
|
|
734
|
-
|
|
735
|
-
this.fireEvent('pause');
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
/**
|
|
739
|
-
* Returns the current time in seconds relative to the audio-clip's
|
|
740
|
-
* duration.
|
|
741
|
-
*
|
|
742
|
-
* @return {number} The current time in seconds
|
|
743
|
-
*/
|
|
744
|
-
getCurrentTime() {
|
|
745
|
-
return this.state.getCurrentTime.call(this);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
/**
|
|
749
|
-
* Returns the current playback rate. (0=no playback, 1=normal playback)
|
|
750
|
-
*
|
|
751
|
-
* @return {number} The current playback rate
|
|
752
|
-
*/
|
|
753
|
-
getPlaybackRate() {
|
|
754
|
-
return this.playbackRate;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
/**
|
|
758
|
-
* Set the audio source playback rate.
|
|
759
|
-
*
|
|
760
|
-
* @param {number} value The playback rate to use
|
|
761
|
-
*/
|
|
762
|
-
setPlaybackRate(value) {
|
|
763
|
-
this.playbackRate = value || 1;
|
|
764
|
-
this.source && this.source.playbackRate.setValueAtTime(
|
|
765
|
-
this.playbackRate,
|
|
766
|
-
this.ac.currentTime
|
|
767
|
-
);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
/**
|
|
771
|
-
* Set a point in seconds for playback to stop at.
|
|
772
|
-
*
|
|
773
|
-
* @param {number} end Position to end at
|
|
774
|
-
* @version 3.3.0
|
|
775
|
-
*/
|
|
776
|
-
setPlayEnd(end) {
|
|
777
|
-
this.scheduledPause = end;
|
|
778
|
-
}
|
|
779
|
-
}
|