wavesurfer.js 6.5.1 → 7.0.0-alpha.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.
Files changed (132) hide show
  1. package/.eslintrc.json +24 -0
  2. package/.prettierrc +8 -0
  3. package/README.md +26 -117
  4. package/dist/base-plugin.d.ts +10 -0
  5. package/dist/base-plugin.js +13 -0
  6. package/dist/decoder.d.ts +10 -0
  7. package/dist/decoder.js +43 -0
  8. package/dist/event-emitter.d.ts +11 -0
  9. package/dist/event-emitter.js +21 -0
  10. package/dist/fetcher.d.ts +4 -0
  11. package/dist/fetcher.js +17 -0
  12. package/dist/index.d.ts +92 -0
  13. package/dist/index.js +166 -0
  14. package/dist/player-webaudio.d.ts +8 -0
  15. package/dist/player-webaudio.js +31 -0
  16. package/dist/player.d.ts +16 -0
  17. package/dist/player.js +40 -0
  18. package/dist/plugins/regions.d.ts +48 -0
  19. package/dist/plugins/regions.js +183 -0
  20. package/dist/renderer.d.ts +34 -0
  21. package/dist/renderer.js +193 -0
  22. package/dist/timer.d.ts +10 -0
  23. package/dist/timer.js +17 -0
  24. package/examples/audio.ogg +0 -0
  25. package/examples/bars.js +19 -0
  26. package/examples/basic.js +8 -0
  27. package/examples/gradient.js +28 -0
  28. package/examples/regions.js +63 -0
  29. package/examples/video.js +19 -0
  30. package/examples/webaudio.js +14 -0
  31. package/package.json +19 -99
  32. package/src/base-plugin.ts +20 -0
  33. package/src/decoder.ts +41 -0
  34. package/src/event-emitter.ts +35 -0
  35. package/src/fetcher.ts +7 -0
  36. package/src/index.ts +252 -0
  37. package/src/player-webaudio.ts +34 -0
  38. package/src/player.ts +50 -0
  39. package/src/plugins/regions.ts +240 -0
  40. package/src/renderer.ts +250 -0
  41. package/src/timer.ts +27 -0
  42. package/tsconfig.json +105 -0
  43. package/tutorial/index.html +47 -0
  44. package/tutorial/src/editor.js +70 -0
  45. package/tutorial/src/init.js +66 -0
  46. package/tutorial/src/url.js +25 -0
  47. package/tutorial/style.css +211 -0
  48. package/yarn-error.log +1049 -0
  49. package/LICENSE +0 -29
  50. package/dist/plugin/wavesurfer.cursor.js +0 -418
  51. package/dist/plugin/wavesurfer.cursor.js.map +0 -1
  52. package/dist/plugin/wavesurfer.cursor.min.js +0 -7
  53. package/dist/plugin/wavesurfer.cursor.min.js.map +0 -1
  54. package/dist/plugin/wavesurfer.elan.js +0 -381
  55. package/dist/plugin/wavesurfer.elan.js.map +0 -1
  56. package/dist/plugin/wavesurfer.elan.min.js +0 -7
  57. package/dist/plugin/wavesurfer.elan.min.js.map +0 -1
  58. package/dist/plugin/wavesurfer.markers.js +0 -520
  59. package/dist/plugin/wavesurfer.markers.js.map +0 -1
  60. package/dist/plugin/wavesurfer.markers.min.js +0 -7
  61. package/dist/plugin/wavesurfer.markers.min.js.map +0 -1
  62. package/dist/plugin/wavesurfer.mediasession.js +0 -181
  63. package/dist/plugin/wavesurfer.mediasession.js.map +0 -1
  64. package/dist/plugin/wavesurfer.mediasession.min.js +0 -7
  65. package/dist/plugin/wavesurfer.mediasession.min.js.map +0 -1
  66. package/dist/plugin/wavesurfer.microphone.js +0 -466
  67. package/dist/plugin/wavesurfer.microphone.js.map +0 -1
  68. package/dist/plugin/wavesurfer.microphone.min.js +0 -7
  69. package/dist/plugin/wavesurfer.microphone.min.js.map +0 -1
  70. package/dist/plugin/wavesurfer.minimap.js +0 -440
  71. package/dist/plugin/wavesurfer.minimap.js.map +0 -1
  72. package/dist/plugin/wavesurfer.minimap.min.js +0 -7
  73. package/dist/plugin/wavesurfer.minimap.min.js.map +0 -1
  74. package/dist/plugin/wavesurfer.playhead.js +0 -300
  75. package/dist/plugin/wavesurfer.playhead.js.map +0 -1
  76. package/dist/plugin/wavesurfer.playhead.min.js +0 -7
  77. package/dist/plugin/wavesurfer.playhead.min.js.map +0 -1
  78. package/dist/plugin/wavesurfer.regions.js +0 -1335
  79. package/dist/plugin/wavesurfer.regions.js.map +0 -1
  80. package/dist/plugin/wavesurfer.regions.min.js +0 -7
  81. package/dist/plugin/wavesurfer.regions.min.js.map +0 -1
  82. package/dist/plugin/wavesurfer.spectrogram.js +0 -736
  83. package/dist/plugin/wavesurfer.spectrogram.js.map +0 -1
  84. package/dist/plugin/wavesurfer.spectrogram.min.js +0 -7
  85. package/dist/plugin/wavesurfer.spectrogram.min.js.map +0 -1
  86. package/dist/plugin/wavesurfer.timeline.js +0 -649
  87. package/dist/plugin/wavesurfer.timeline.js.map +0 -1
  88. package/dist/plugin/wavesurfer.timeline.min.js +0 -7
  89. package/dist/plugin/wavesurfer.timeline.min.js.map +0 -1
  90. package/dist/wavesurfer-html-init.js +0 -381
  91. package/dist/wavesurfer-html-init.js.map +0 -1
  92. package/dist/wavesurfer-html-init.min.js +0 -7
  93. package/dist/wavesurfer-html-init.min.js.map +0 -1
  94. package/dist/wavesurfer.js +0 -6145
  95. package/dist/wavesurfer.js.map +0 -1
  96. package/dist/wavesurfer.min.js +0 -7
  97. package/dist/wavesurfer.min.js.map +0 -1
  98. package/src/drawer.canvasentry.js +0 -427
  99. package/src/drawer.js +0 -413
  100. package/src/drawer.multicanvas.js +0 -617
  101. package/src/html-init.js +0 -241
  102. package/src/mediaelement-webaudio.js +0 -77
  103. package/src/mediaelement.js +0 -443
  104. package/src/peakcache.js +0 -127
  105. package/src/plugin/cursor/index.js +0 -350
  106. package/src/plugin/elan/index.js +0 -307
  107. package/src/plugin/markers/index.js +0 -436
  108. package/src/plugin/mediasession/index.js +0 -94
  109. package/src/plugin/microphone/index.js +0 -415
  110. package/src/plugin/minimap/index.js +0 -403
  111. package/src/plugin/playhead/index.js +0 -226
  112. package/src/plugin/regions/index.js +0 -478
  113. package/src/plugin/regions/region.js +0 -875
  114. package/src/plugin/spectrogram/fft.js +0 -222
  115. package/src/plugin/spectrogram/index.js +0 -522
  116. package/src/plugin/timeline/index.js +0 -600
  117. package/src/util/absMax.js +0 -16
  118. package/src/util/clamp.js +0 -11
  119. package/src/util/fetch.js +0 -222
  120. package/src/util/frame.js +0 -13
  121. package/src/util/get-id.js +0 -22
  122. package/src/util/index.js +0 -14
  123. package/src/util/max.js +0 -16
  124. package/src/util/min.js +0 -16
  125. package/src/util/observer.js +0 -144
  126. package/src/util/orientation.js +0 -98
  127. package/src/util/prevent-click.js +0 -19
  128. package/src/util/request-animation-frame.js +0 -15
  129. package/src/util/silence-mode.js +0 -42
  130. package/src/util/style.js +0 -16
  131. package/src/wavesurfer.js +0 -1797
  132. package/src/webaudio.js +0 -779
package/src/wavesurfer.js DELETED
@@ -1,1797 +0,0 @@
1
- import * as util from './util';
2
- import MultiCanvas from './drawer.multicanvas';
3
- import WebAudio from './webaudio';
4
- import MediaElement from './mediaelement';
5
- import PeakCache from './peakcache';
6
- import MediaElementWebAudio from './mediaelement-webaudio';
7
-
8
- /*
9
- * This work is licensed under a BSD-3-Clause License.
10
- */
11
-
12
- /** @external {HTMLElement} https://developer.mozilla.org/en/docs/Web/API/HTMLElement */
13
- /** @external {OfflineAudioContext} https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext */
14
- /** @external {File} https://developer.mozilla.org/en-US/docs/Web/API/File */
15
- /** @external {Blob} https://developer.mozilla.org/en-US/docs/Web/API/Blob */
16
- /** @external {CanvasRenderingContext2D} https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D */
17
- /** @external {MediaStreamConstraints} https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints */
18
- /** @external {AudioNode} https://developer.mozilla.org/de/docs/Web/API/AudioNode */
19
-
20
- /**
21
- * @typedef {Object} WavesurferParams
22
- * @property {AudioContext} audioContext=null Use your own previously
23
- * initialized AudioContext or leave blank.
24
- * @property {number} audioRate=1 Speed at which to play audio. Lower number is
25
- * slower.
26
- * @property {ScriptProcessorNode} audioScriptProcessor=null Use your own previously
27
- * initialized ScriptProcessorNode or leave blank.
28
- * @property {boolean} autoCenter=true If a scrollbar is present, center the
29
- * waveform on current progress
30
- * @property {number} autoCenterRate=5 If autoCenter is active, rate at which the
31
- * waveform is centered
32
- * @property {boolean} autoCenterImmediately=false If autoCenter is active, immediately
33
- * center waveform on current progress
34
- * @property {string} backend='WebAudio' `'WebAudio'|'MediaElement'|'MediaElementWebAudio'` In most cases
35
- * you don't have to set this manually. MediaElement is a fallback for unsupported browsers.
36
- * MediaElementWebAudio allows to use WebAudio API also with big audio files, loading audio like with
37
- * MediaElement backend (HTML5 audio tag). You have to use the same methods of MediaElement backend for loading and
38
- * playback, giving also peaks, so the audio data are not decoded. In this way you can use WebAudio features, like filters,
39
- * also with audio with big duration. For example:
40
- * ` wavesurfer.load(url | HTMLMediaElement, peaks, preload, duration);
41
- * wavesurfer.play();
42
- * wavesurfer.setFilter(customFilter);
43
- * `
44
- * @property {string} backgroundColor=null Change background color of the
45
- * waveform container.
46
- * @property {number} barHeight=1 The height of the wave bars.
47
- * @property {number} barRadius=0 The radius of the wave bars. Makes bars rounded
48
- * @property {number} barGap=null The optional spacing between bars of the wave,
49
- * if not provided will be calculated in legacy format.
50
- * @property {number} barWidth=null Draw the waveform using bars.
51
- * @property {number} barMinHeight=null If specified, draw at least a bar of this height,
52
- * eliminating waveform gaps
53
- * @property {boolean} closeAudioContext=false Close and nullify all audio
54
- * contexts when the destroy method is called.
55
- * @property {!string|HTMLElement} container CSS selector or HTML element where
56
- * the waveform should be drawn. This is the only required parameter.
57
- * @property {string} cursorColor='#333' The fill color of the cursor indicating
58
- * the playhead position.
59
- * @property {number} cursorWidth=1 Measured in pixels.
60
- * @property {object} drawingContextAttributes={desynchronized: false} Drawing context
61
- * attributes.
62
- * @property {number} duration=null Optional audio length so pre-rendered peaks
63
- * can be display immediately for example.
64
- * @property {boolean} fillParent=true Whether to fill the entire container or
65
- * draw only according to `minPxPerSec`.
66
- * @property {boolean} forceDecode=false Force decoding of audio using web audio
67
- * when zooming to get a more detailed waveform.
68
- * @property {number} height=128 The height of the waveform. Measured in
69
- * pixels.
70
- * @property {boolean} hideScrollbar=false Whether to hide the horizontal
71
- * scrollbar when one would normally be shown.
72
- * @property {boolean} hideCursor=false Whether to hide the mouse cursor
73
- * when one would normally be shown by default.
74
- * @property {boolean} ignoreSilenceMode=false If true, ignores device silence mode
75
- * when using the `WebAudio` backend.
76
- * @property {boolean} interact=true Whether the mouse interaction will be
77
- * enabled at initialization. You can switch this parameter at any time later
78
- * on.
79
- * @property {boolean} loopSelection=true (Use with regions plugin) Enable
80
- * looping of selected regions
81
- * @property {number} maxCanvasWidth=4000 Maximum width of a single canvas in
82
- * pixels, excluding a small overlap (2 * `pixelRatio`, rounded up to the next
83
- * even integer). If the waveform is longer than this value, additional canvases
84
- * will be used to render the waveform, which is useful for very large waveforms
85
- * that may be too wide for browsers to draw on a single canvas.
86
- * @property {boolean} mediaControls=false (Use with backend `MediaElement` or `MediaElementWebAudio`)
87
- * this enables the native controls for the media element
88
- * @property {string} mediaType='audio' (Use with backend `MediaElement` or `MediaElementWebAudio`)
89
- * `'audio'|'video'` ('video' only for `MediaElement`)
90
- * @property {number} minPxPerSec=20 Minimum number of pixels per second of
91
- * audio.
92
- * @property {boolean} normalize=false If true, normalize by the maximum peak
93
- * instead of 1.0.
94
- * @property {boolean} partialRender=false Use the PeakCache to improve
95
- * rendering speed of large waveforms
96
- * @property {number} pixelRatio=window.devicePixelRatio The pixel ratio used to
97
- * calculate display
98
- * @property {PluginDefinition[]} plugins=[] An array of plugin definitions to
99
- * register during instantiation, they will be directly initialised unless they
100
- * are added with the `deferInit` property set to true.
101
- * @property {string} progressColor='#555' The fill color of the part of the
102
- * waveform behind the cursor. When `progressColor` and `waveColor` are the same
103
- * the progress wave is not rendered at all.
104
- * @property {boolean} removeMediaElementOnDestroy=true Set to false to keep the
105
- * media element in the DOM when the player is destroyed. This is useful when
106
- * reusing an existing media element via the `loadMediaElement` method.
107
- * @property {Object} renderer=MultiCanvas Can be used to inject a custom
108
- * renderer.
109
- * @property {boolean|number} responsive=false If set to `true` resize the
110
- * waveform, when the window is resized. This is debounced with a `100ms`
111
- * timeout by default. If this parameter is a number it represents that timeout.
112
- * @property {boolean} rtl=false If set to `true`, renders waveform from
113
- * right-to-left.
114
- * @property {boolean} scrollParent=false Whether to scroll the container with a
115
- * lengthy waveform. Otherwise the waveform is shrunk to the container width
116
- * (see fillParent).
117
- * @property {number} skipLength=2 Number of seconds to skip with the
118
- * skipForward() and skipBackward() methods.
119
- * @property {boolean} splitChannels=false Render with separate waveforms for
120
- * the channels of the audio
121
- * @property {SplitChannelOptions} splitChannelsOptions={} Options for splitChannel rendering
122
- * @property {boolean} vertical=false Render the waveform vertically instead of horizontally.
123
- * @property {string} waveColor='#999' The fill color of the waveform after the
124
- * cursor.
125
- * @property {object} xhr={} XHR options. For example:
126
- * `let xhr = {
127
- * cache: 'default',
128
- * mode: 'cors',
129
- * method: 'GET',
130
- * credentials: 'same-origin',
131
- * redirect: 'follow',
132
- * referrer: 'client',
133
- * requestHeaders: [
134
- * {
135
- * key: 'Authorization',
136
- * value: 'my-token'
137
- * }
138
- * ]
139
- * };`
140
- */
141
-
142
- /**
143
- * @typedef {Object} PluginDefinition
144
- * @desc The Object used to describe a plugin
145
- * @example wavesurfer.addPlugin(pluginDefinition);
146
- * @property {string} name The name of the plugin, the plugin instance will be
147
- * added as a property to the wavesurfer instance under this name
148
- * @property {?Object} staticProps The properties that should be added to the
149
- * wavesurfer instance as static properties
150
- * @property {?boolean} deferInit Don't initialise plugin
151
- * automatically
152
- * @property {Object} params={} The plugin parameters, they are the first parameter
153
- * passed to the plugin class constructor function
154
- * @property {PluginClass} instance The plugin instance factory, is called with
155
- * the dependency specified in extends. Returns the plugin class.
156
- */
157
-
158
- /**
159
- * @typedef {Object} SplitChannelOptions
160
- * @desc parameters applied when splitChannels option is true
161
- * @property {boolean} overlay=false determines whether channels are rendered on top of each other or on separate tracks
162
- * @property {object} channelColors={} object describing color for each channel. Example:
163
- * {
164
- * 0: {
165
- * progressColor: 'green',
166
- * waveColor: 'pink'
167
- * },
168
- * 1: {
169
- * progressColor: 'orange',
170
- * waveColor: 'purple'
171
- * }
172
- * }
173
- * @property {number[]} filterChannels=[] indexes of channels to be hidden from rendering
174
- * @property {boolean} relativeNormalization=false determines whether
175
- * normalization is done per channel or maintains proportionality between
176
- * channels. Only applied when normalize and splitChannels are both true.
177
- * @property {boolean} splitDragSelection=false determines if drag selection in regions
178
- * plugin works separately on each channel or only one selection for all channels
179
- * @since 4.3.0
180
- */
181
-
182
- /**
183
- * @interface PluginClass
184
- *
185
- * @desc This is the interface which is implemented by all plugin classes. Note
186
- * that this only turns into an observer after being passed through
187
- * `wavesurfer.addPlugin`.
188
- *
189
- * @extends {Observer}
190
- */
191
- class PluginClass {
192
- /**
193
- * Plugin definition factory
194
- *
195
- * This function must be used to create a plugin definition which can be
196
- * used by wavesurfer to correctly instantiate the plugin.
197
- *
198
- * It returns a `PluginDefinition` object representing the plugin.
199
- *
200
- * @param {Object} params={} The plugin params (specific to the plugin)
201
- */
202
- create(params) {}
203
- /**
204
- * Construct the plugin
205
- *
206
- * @param {Object} params={} The plugin params (specific to the plugin)
207
- * @param {Object} ws The wavesurfer instance
208
- */
209
- constructor(params, ws) {}
210
- /**
211
- * Initialise the plugin
212
- *
213
- * Start doing something. This is called by
214
- * `wavesurfer.initPlugin(pluginName)`
215
- */
216
- init() {}
217
- /**
218
- * Destroy the plugin instance
219
- *
220
- * Stop doing something. This is called by
221
- * `wavesurfer.destroyPlugin(pluginName)`
222
- */
223
- destroy() {}
224
- }
225
-
226
- /**
227
- * WaveSurfer core library class
228
- *
229
- * @extends {Observer}
230
- * @example
231
- * const params = {
232
- * container: '#waveform',
233
- * waveColor: 'violet',
234
- * progressColor: 'purple'
235
- * };
236
- *
237
- * // initialise like this
238
- * const wavesurfer = WaveSurfer.create(params);
239
- *
240
- * // or like this ...
241
- * const wavesurfer = new WaveSurfer(params);
242
- * wavesurfer.init();
243
- *
244
- * // load audio file
245
- * wavesurfer.load('example/media/demo.wav');
246
- */
247
- export default class WaveSurfer extends util.Observer {
248
- /** @private */
249
- defaultParams = {
250
- audioContext: null,
251
- audioScriptProcessor: null,
252
- audioRate: 1,
253
- autoCenter: true,
254
- autoCenterRate: 5,
255
- autoCenterImmediately: false,
256
- backend: 'WebAudio',
257
- backgroundColor: null,
258
- barHeight: 1,
259
- barRadius: 0,
260
- barGap: null,
261
- barMinHeight: null,
262
- container: null,
263
- cursorColor: '#333',
264
- cursorWidth: 1,
265
- dragSelection: true,
266
- drawingContextAttributes: {
267
- // Boolean that hints the user agent to reduce the latency
268
- // by desynchronizing the canvas paint cycle from the event
269
- // loop
270
- desynchronized: false
271
- },
272
- duration: null,
273
- fillParent: true,
274
- forceDecode: false,
275
- height: 128,
276
- hideScrollbar: false,
277
- hideCursor: false,
278
- ignoreSilenceMode: false,
279
- interact: true,
280
- loopSelection: true,
281
- maxCanvasWidth: 4000,
282
- mediaContainer: null,
283
- mediaControls: false,
284
- mediaType: 'audio',
285
- minPxPerSec: 20,
286
- normalize: false,
287
- partialRender: false,
288
- pixelRatio:
289
- window.devicePixelRatio || screen.deviceXDPI / screen.logicalXDPI,
290
- plugins: [],
291
- progressColor: '#555',
292
- removeMediaElementOnDestroy: true,
293
- renderer: MultiCanvas,
294
- responsive: false,
295
- rtl: false,
296
- scrollParent: false,
297
- skipLength: 2,
298
- splitChannels: false,
299
- splitChannelsOptions: {
300
- overlay: false,
301
- channelColors: {},
302
- filterChannels: [],
303
- relativeNormalization: false,
304
- splitDragSelection: false
305
- },
306
- vertical: false,
307
- waveColor: '#999',
308
- xhr: {}
309
- };
310
-
311
- /** @private */
312
- backends = {
313
- MediaElement,
314
- WebAudio,
315
- MediaElementWebAudio
316
- };
317
-
318
- /**
319
- * Instantiate this class, call its `init` function and returns it
320
- *
321
- * @param {WavesurferParams} params The wavesurfer parameters
322
- * @return {Object} WaveSurfer instance
323
- * @example const wavesurfer = WaveSurfer.create(params);
324
- */
325
- static create(params) {
326
- const wavesurfer = new WaveSurfer(params);
327
- return wavesurfer.init();
328
- }
329
-
330
- /**
331
- * The library version number is available as a static property of the
332
- * WaveSurfer class
333
- *
334
- * @type {String}
335
- * @example
336
- * console.log('Using wavesurfer.js ' + WaveSurfer.VERSION);
337
- */
338
- static VERSION = __VERSION__;
339
-
340
- /**
341
- * Functions in the `util` property are available as a prototype property to
342
- * all instances
343
- *
344
- * @type {Object}
345
- * @example
346
- * const wavesurfer = WaveSurfer.create(params);
347
- * wavesurfer.util.style(myElement, { background: 'blue' });
348
- */
349
- util = util;
350
-
351
- /**
352
- * Functions in the `util` property are available as a static property of the
353
- * WaveSurfer class
354
- *
355
- * @type {Object}
356
- * @example
357
- * WaveSurfer.util.style(myElement, { background: 'blue' });
358
- */
359
- static util = util;
360
-
361
- /**
362
- * Initialise wavesurfer instance
363
- *
364
- * @param {WavesurferParams} params Instantiation options for wavesurfer
365
- * @example
366
- * const wavesurfer = new WaveSurfer(params);
367
- * @returns {this} Wavesurfer instance
368
- */
369
- constructor(params) {
370
- super();
371
- /**
372
- * Extract relevant parameters (or defaults)
373
- * @private
374
- */
375
- this.params = Object.assign({}, this.defaultParams, params);
376
- this.params.splitChannelsOptions = Object.assign(
377
- {},
378
- this.defaultParams.splitChannelsOptions,
379
- params.splitChannelsOptions
380
- );
381
- /** @private */
382
- this.container =
383
- 'string' == typeof params.container
384
- ? document.querySelector(this.params.container)
385
- : this.params.container;
386
-
387
- if (!this.container) {
388
- throw new Error('Container element not found');
389
- }
390
-
391
- if (this.params.mediaContainer == null) {
392
- /** @private */
393
- this.mediaContainer = this.container;
394
- } else if (typeof this.params.mediaContainer == 'string') {
395
- /** @private */
396
- this.mediaContainer = document.querySelector(
397
- this.params.mediaContainer
398
- );
399
- } else {
400
- /** @private */
401
- this.mediaContainer = this.params.mediaContainer;
402
- }
403
-
404
- if (!this.mediaContainer) {
405
- throw new Error('Media Container element not found');
406
- }
407
-
408
- if (this.params.maxCanvasWidth <= 1) {
409
- throw new Error('maxCanvasWidth must be greater than 1');
410
- } else if (this.params.maxCanvasWidth % 2 == 1) {
411
- throw new Error('maxCanvasWidth must be an even number');
412
- }
413
-
414
- if (this.params.rtl === true) {
415
- if (this.params.vertical === true) {
416
- util.style(this.container, { transform: 'rotateX(180deg)' });
417
- } else {
418
- util.style(this.container, { transform: 'rotateY(180deg)' });
419
- }
420
- }
421
-
422
- if (this.params.backgroundColor) {
423
- this.setBackgroundColor(this.params.backgroundColor);
424
- }
425
-
426
- /**
427
- * @private Used to save the current volume when muting so we can
428
- * restore once unmuted
429
- * @type {number}
430
- */
431
- this.savedVolume = 0;
432
-
433
- /**
434
- * @private The current muted state
435
- * @type {boolean}
436
- */
437
- this.isMuted = false;
438
-
439
- /**
440
- * @private Will hold a list of event descriptors that need to be
441
- * canceled on subsequent loads of audio
442
- * @type {Object[]}
443
- */
444
- this.tmpEvents = [];
445
-
446
- /**
447
- * @private Holds any running audio downloads
448
- * @type {Observer}
449
- */
450
- this.currentRequest = null;
451
- /** @private */
452
- this.arraybuffer = null;
453
- /** @private */
454
- this.drawer = null;
455
- /** @private */
456
- this.backend = null;
457
- /** @private */
458
- this.peakCache = null;
459
-
460
- // cache constructor objects
461
- if (typeof this.params.renderer !== 'function') {
462
- throw new Error('Renderer parameter is invalid');
463
- }
464
- /**
465
- * @private The uninitialised Drawer class
466
- */
467
- this.Drawer = this.params.renderer;
468
- /**
469
- * @private The uninitialised Backend class
470
- */
471
- // Back compat
472
- if (this.params.backend == 'AudioElement') {
473
- this.params.backend = 'MediaElement';
474
- }
475
-
476
- if (
477
- (this.params.backend == 'WebAudio' ||
478
- this.params.backend === 'MediaElementWebAudio') &&
479
- !WebAudio.prototype.supportsWebAudio.call(null)
480
- ) {
481
- this.params.backend = 'MediaElement';
482
- }
483
- this.Backend = this.backends[this.params.backend];
484
-
485
- /**
486
- * @private map of plugin names that are currently initialised
487
- */
488
- this.initialisedPluginList = {};
489
- /** @private */
490
- this.isDestroyed = false;
491
-
492
- /**
493
- * Get the current ready status.
494
- *
495
- * @example const isReady = wavesurfer.isReady;
496
- * @return {boolean}
497
- */
498
- this.isReady = false;
499
-
500
- // responsive debounced event listener. If this.params.responsive is not
501
- // set, this is never called. Use 100ms or this.params.responsive as
502
- // timeout for the debounce function.
503
- let prevWidth = 0;
504
- this._onResize = util.debounce(
505
- () => {
506
- if (
507
- this.drawer.wrapper &&
508
- prevWidth != this.drawer.wrapper.clientWidth &&
509
- !this.params.scrollParent
510
- ) {
511
- prevWidth = this.drawer.wrapper.clientWidth;
512
- if (prevWidth) {
513
- // redraw only if waveform container is rendered and has a width
514
- this.drawer.fireEvent('redraw');
515
- }
516
- }
517
- },
518
- typeof this.params.responsive === 'number'
519
- ? this.params.responsive
520
- : 100
521
- );
522
-
523
- return this;
524
- }
525
-
526
- /**
527
- * Initialise the wave
528
- *
529
- * @example
530
- * var wavesurfer = new WaveSurfer(params);
531
- * wavesurfer.init();
532
- * @return {this} The wavesurfer instance
533
- */
534
- init() {
535
- this.registerPlugins(this.params.plugins);
536
- this.createDrawer();
537
- this.createBackend();
538
- this.createPeakCache();
539
- return this;
540
- }
541
-
542
- /**
543
- * Add and initialise array of plugins (if `plugin.deferInit` is falsey),
544
- * this function is called in the init function of wavesurfer
545
- *
546
- * @param {PluginDefinition[]} plugins An array of plugin definitions
547
- * @emits {WaveSurfer#plugins-registered} Called with the array of plugin definitions
548
- * @return {this} The wavesurfer instance
549
- */
550
- registerPlugins(plugins) {
551
- // first instantiate all the plugins
552
- plugins.forEach(plugin => this.addPlugin(plugin));
553
-
554
- // now run the init functions
555
- plugins.forEach(plugin => {
556
- // call init function of the plugin if deferInit is falsey
557
- // in that case you would manually use initPlugins()
558
- if (!plugin.deferInit) {
559
- this.initPlugin(plugin.name);
560
- }
561
- });
562
- this.fireEvent('plugins-registered', plugins);
563
- return this;
564
- }
565
-
566
- /**
567
- * Get a map of plugin names that are currently initialised
568
- *
569
- * @example wavesurfer.getPlugins();
570
- * @return {Object} Object with plugin names
571
- */
572
- getActivePlugins() {
573
- return this.initialisedPluginList;
574
- }
575
-
576
- /**
577
- * Add a plugin object to wavesurfer
578
- *
579
- * @param {PluginDefinition} plugin A plugin definition
580
- * @emits {WaveSurfer#plugin-added} Called with the name of the plugin that was added
581
- * @example wavesurfer.addPlugin(WaveSurfer.minimap());
582
- * @return {this} The wavesurfer instance
583
- */
584
- addPlugin(plugin) {
585
- if (!plugin.name) {
586
- throw new Error('Plugin does not have a name!');
587
- }
588
- if (!plugin.instance) {
589
- throw new Error(
590
- `Plugin ${plugin.name} does not have an instance property!`
591
- );
592
- }
593
-
594
- // staticProps properties are applied to wavesurfer instance
595
- if (plugin.staticProps) {
596
- Object.keys(plugin.staticProps).forEach(pluginStaticProp => {
597
- /**
598
- * Properties defined in a plugin definition's `staticProps` property are added as
599
- * staticProps properties of the WaveSurfer instance
600
- */
601
- this[pluginStaticProp] = plugin.staticProps[pluginStaticProp];
602
- });
603
- }
604
-
605
- const Instance = plugin.instance;
606
-
607
- // turn the plugin instance into an observer
608
- const observerPrototypeKeys = Object.getOwnPropertyNames(
609
- util.Observer.prototype
610
- );
611
- observerPrototypeKeys.forEach(key => {
612
- Instance.prototype[key] = util.Observer.prototype[key];
613
- });
614
-
615
- /**
616
- * Instantiated plugin classes are added as a property of the wavesurfer
617
- * instance
618
- * @type {Object}
619
- */
620
- this[plugin.name] = new Instance(plugin.params || {}, this);
621
- this.fireEvent('plugin-added', plugin.name);
622
- return this;
623
- }
624
-
625
- /**
626
- * Initialise a plugin
627
- *
628
- * @param {string} name A plugin name
629
- * @emits WaveSurfer#plugin-initialised
630
- * @example wavesurfer.initPlugin('minimap');
631
- * @return {this} The wavesurfer instance
632
- */
633
- initPlugin(name) {
634
- if (!this[name]) {
635
- throw new Error(`Plugin ${name} has not been added yet!`);
636
- }
637
- if (this.initialisedPluginList[name]) {
638
- // destroy any already initialised plugins
639
- this.destroyPlugin(name);
640
- }
641
- this[name].init();
642
- this.initialisedPluginList[name] = true;
643
- this.fireEvent('plugin-initialised', name);
644
- return this;
645
- }
646
-
647
- /**
648
- * Destroy a plugin
649
- *
650
- * @param {string} name A plugin name
651
- * @emits WaveSurfer#plugin-destroyed
652
- * @example wavesurfer.destroyPlugin('minimap');
653
- * @returns {this} The wavesurfer instance
654
- */
655
- destroyPlugin(name) {
656
- if (!this[name]) {
657
- throw new Error(
658
- `Plugin ${name} has not been added yet and cannot be destroyed!`
659
- );
660
- }
661
- if (!this.initialisedPluginList[name]) {
662
- throw new Error(
663
- `Plugin ${name} is not active and cannot be destroyed!`
664
- );
665
- }
666
- if (typeof this[name].destroy !== 'function') {
667
- throw new Error(`Plugin ${name} does not have a destroy function!`);
668
- }
669
-
670
- this[name].destroy();
671
- delete this.initialisedPluginList[name];
672
- this.fireEvent('plugin-destroyed', name);
673
- return this;
674
- }
675
-
676
- /**
677
- * Destroy all initialised plugins. Convenience function to use when
678
- * wavesurfer is removed
679
- *
680
- * @private
681
- */
682
- destroyAllPlugins() {
683
- Object.keys(this.initialisedPluginList).forEach(name =>
684
- this.destroyPlugin(name)
685
- );
686
- }
687
-
688
- /**
689
- * Create the drawer and draw the waveform
690
- *
691
- * @private
692
- * @emits WaveSurfer#drawer-created
693
- */
694
- createDrawer() {
695
- this.drawer = new this.Drawer(this.container, this.params);
696
- this.drawer.init();
697
- this.fireEvent('drawer-created', this.drawer);
698
-
699
- if (this.params.responsive !== false) {
700
- window.addEventListener('resize', this._onResize, true);
701
- window.addEventListener('orientationchange', this._onResize, true);
702
- }
703
-
704
- this.drawer.on('redraw', () => {
705
- this.drawBuffer();
706
- this.drawer.progress(this.backend.getPlayedPercents());
707
- });
708
-
709
- // Click-to-seek
710
- this.drawer.on('click', (e, progress) => {
711
- setTimeout(() => this.seekTo(progress), 0);
712
- });
713
-
714
- // Relay the scroll event from the drawer
715
- this.drawer.on('scroll', e => {
716
- if (this.params.partialRender) {
717
- this.drawBuffer();
718
- }
719
- this.fireEvent('scroll', e);
720
- });
721
- }
722
-
723
- /**
724
- * Create the backend
725
- *
726
- * @private
727
- * @emits WaveSurfer#backend-created
728
- */
729
- createBackend() {
730
- if (this.backend) {
731
- this.backend.destroy();
732
- }
733
-
734
- this.backend = new this.Backend(this.params);
735
- this.backend.init();
736
- this.fireEvent('backend-created', this.backend);
737
-
738
- this.backend.on('finish', () => {
739
- this.drawer.progress(this.backend.getPlayedPercents());
740
- this.fireEvent('finish');
741
- });
742
- this.backend.on('play', () => this.fireEvent('play'));
743
- this.backend.on('pause', () => this.fireEvent('pause'));
744
-
745
- this.backend.on('audioprocess', time => {
746
- this.drawer.progress(this.backend.getPlayedPercents());
747
- this.fireEvent('audioprocess', time);
748
- });
749
-
750
- // only needed for MediaElement and MediaElementWebAudio backend
751
- if (
752
- this.params.backend === 'MediaElement' ||
753
- this.params.backend === 'MediaElementWebAudio'
754
- ) {
755
- this.backend.on('seek', () => {
756
- this.drawer.progress(this.backend.getPlayedPercents());
757
- });
758
-
759
- this.backend.on('volume', () => {
760
- let newVolume = this.getVolume();
761
- this.fireEvent('volume', newVolume);
762
-
763
- if (this.backend.isMuted !== this.isMuted) {
764
- this.isMuted = this.backend.isMuted;
765
- this.fireEvent('mute', this.isMuted);
766
- }
767
- });
768
- }
769
- }
770
-
771
- /**
772
- * Create the peak cache
773
- *
774
- * @private
775
- */
776
- createPeakCache() {
777
- if (this.params.partialRender) {
778
- this.peakCache = new PeakCache();
779
- }
780
- }
781
-
782
- /**
783
- * Get the duration of the audio clip
784
- *
785
- * @example const duration = wavesurfer.getDuration();
786
- * @return {number} Duration in seconds
787
- */
788
- getDuration() {
789
- return this.backend.getDuration();
790
- }
791
-
792
- /**
793
- * Get the current playback position
794
- *
795
- * @example const currentTime = wavesurfer.getCurrentTime();
796
- * @return {number} Playback position in seconds
797
- */
798
- getCurrentTime() {
799
- return this.backend.getCurrentTime();
800
- }
801
-
802
- /**
803
- * Set the current play time in seconds.
804
- *
805
- * @param {number} seconds A positive number in seconds. E.g. 10 means 10
806
- * seconds, 60 means 1 minute
807
- */
808
- setCurrentTime(seconds) {
809
- if (seconds >= this.getDuration()) {
810
- this.seekTo(1);
811
- } else {
812
- this.seekTo(seconds / this.getDuration());
813
- }
814
- }
815
-
816
- /**
817
- * Starts playback from the current position. Optional start and end
818
- * measured in seconds can be used to set the range of audio to play.
819
- *
820
- * @param {?number} start Position to start at
821
- * @param {?number} end Position to end at
822
- * @emits WaveSurfer#interaction
823
- * @return {Promise} Result of the backend play method
824
- * @example
825
- * // play from second 1 to 5
826
- * wavesurfer.play(1, 5);
827
- */
828
- play(start, end) {
829
- if (this.params.ignoreSilenceMode) {
830
- // ignores device hardware silence mode
831
- util.ignoreSilenceMode();
832
- }
833
-
834
- this.fireEvent('interaction', () => this.play(start, end));
835
- return this.backend.play(start, end);
836
- }
837
-
838
- /**
839
- * Set a point in seconds for playback to stop at.
840
- *
841
- * @param {number} position Position (in seconds) to stop at
842
- * @version 3.3.0
843
- */
844
- setPlayEnd(position) {
845
- this.backend.setPlayEnd(position);
846
- }
847
-
848
- /**
849
- * Stops and pauses playback
850
- *
851
- * @example wavesurfer.pause();
852
- * @return {Promise} Result of the backend pause method
853
- */
854
- pause() {
855
- if (!this.backend.isPaused()) {
856
- return this.backend.pause();
857
- }
858
- }
859
-
860
- /**
861
- * Toggle playback
862
- *
863
- * @example wavesurfer.playPause();
864
- * @return {Promise} Result of the backend play or pause method
865
- */
866
- playPause() {
867
- return this.backend.isPaused() ? this.play() : this.pause();
868
- }
869
-
870
- /**
871
- * Get the current playback state
872
- *
873
- * @example const isPlaying = wavesurfer.isPlaying();
874
- * @return {boolean} False if paused, true if playing
875
- */
876
- isPlaying() {
877
- return !this.backend.isPaused();
878
- }
879
-
880
- /**
881
- * Skip backward
882
- *
883
- * @param {?number} seconds Amount to skip back, if not specified `skipLength`
884
- * is used
885
- * @example wavesurfer.skipBackward();
886
- */
887
- skipBackward(seconds) {
888
- this.skip(-seconds || -this.params.skipLength);
889
- }
890
-
891
- /**
892
- * Skip forward
893
- *
894
- * @param {?number} seconds Amount to skip back, if not specified `skipLength`
895
- * is used
896
- * @example wavesurfer.skipForward();
897
- */
898
- skipForward(seconds) {
899
- this.skip(seconds || this.params.skipLength);
900
- }
901
-
902
- /**
903
- * Skip a number of seconds from the current position (use a negative value
904
- * to go backwards).
905
- *
906
- * @param {number} offset Amount to skip back or forwards
907
- * @example
908
- * // go back 2 seconds
909
- * wavesurfer.skip(-2);
910
- */
911
- skip(offset) {
912
- const duration = this.getDuration() || 1;
913
- let position = this.getCurrentTime() || 0;
914
- position = Math.max(0, Math.min(duration, position + (offset || 0)));
915
- this.seekAndCenter(position / duration);
916
- }
917
-
918
- /**
919
- * Seeks to a position and centers the view
920
- *
921
- * @param {number} progress Between 0 (=beginning) and 1 (=end)
922
- * @example
923
- * // seek and go to the middle of the audio
924
- * wavesurfer.seekTo(0.5);
925
- */
926
- seekAndCenter(progress) {
927
- this.seekTo(progress);
928
- this.drawer.recenter(progress);
929
- }
930
-
931
- /**
932
- * Seeks to a position
933
- *
934
- * @param {number} progress Between 0 (=beginning) and 1 (=end)
935
- * @emits WaveSurfer#interaction
936
- * @emits WaveSurfer#seek
937
- * @example
938
- * // seek to the middle of the audio
939
- * wavesurfer.seekTo(0.5);
940
- */
941
- seekTo(progress) {
942
- // return an error if progress is not a number between 0 and 1
943
- if (
944
- typeof progress !== 'number' ||
945
- !isFinite(progress) ||
946
- progress < 0 ||
947
- progress > 1
948
- ) {
949
- throw new Error(
950
- 'Error calling wavesurfer.seekTo, parameter must be a number between 0 and 1!'
951
- );
952
- }
953
- this.fireEvent('interaction', () => this.seekTo(progress));
954
-
955
- const isWebAudioBackend = this.params.backend === 'WebAudio';
956
- const paused = this.backend.isPaused();
957
-
958
- if (isWebAudioBackend && !paused) {
959
- this.backend.pause();
960
- }
961
-
962
- // avoid small scrolls while paused seeking
963
- const oldScrollParent = this.params.scrollParent;
964
- this.params.scrollParent = false;
965
- this.backend.seekTo(progress * this.getDuration());
966
- this.drawer.progress(progress);
967
-
968
- if (isWebAudioBackend && !paused) {
969
- this.backend.play();
970
- }
971
-
972
- this.params.scrollParent = oldScrollParent;
973
- this.fireEvent('seek', progress);
974
- }
975
-
976
- /**
977
- * Stops and goes to the beginning.
978
- *
979
- * @example wavesurfer.stop();
980
- */
981
- stop() {
982
- this.pause();
983
- this.seekTo(0);
984
- this.drawer.progress(0);
985
- }
986
-
987
- /**
988
- * Sets the ID of the audio device to use for output and returns a Promise.
989
- *
990
- * @param {string} deviceId String value representing underlying output
991
- * device
992
- * @returns {Promise} `Promise` that resolves to `undefined` when there are
993
- * no errors detected.
994
- */
995
- setSinkId(deviceId) {
996
- return this.backend.setSinkId(deviceId);
997
- }
998
-
999
- /**
1000
- * Set the playback volume.
1001
- *
1002
- * @param {number} newVolume A value between 0 and 1, 0 being no
1003
- * volume and 1 being full volume.
1004
- * @emits WaveSurfer#volume
1005
- */
1006
- setVolume(newVolume) {
1007
- if (this.isMuted === true) {
1008
- this.savedVolume = newVolume;
1009
- return;
1010
- }
1011
- this.backend.setVolume(newVolume);
1012
- this.fireEvent('volume', newVolume);
1013
- }
1014
-
1015
- /**
1016
- * Get the playback volume.
1017
- *
1018
- * @return {number} A value between 0 and 1, 0 being no
1019
- * volume and 1 being full volume.
1020
- */
1021
- getVolume() {
1022
- return this.backend.getVolume();
1023
- }
1024
-
1025
- /**
1026
- * Set the playback rate.
1027
- *
1028
- * @param {number} rate A positive number. E.g. 0.5 means half the normal
1029
- * speed, 2 means double speed and so on.
1030
- * @example wavesurfer.setPlaybackRate(2);
1031
- */
1032
- setPlaybackRate(rate) {
1033
- this.backend.setPlaybackRate(rate);
1034
- }
1035
-
1036
- /**
1037
- * Get the playback rate.
1038
- *
1039
- * @return {number} The current playback rate.
1040
- */
1041
- getPlaybackRate() {
1042
- return this.backend.getPlaybackRate();
1043
- }
1044
-
1045
- /**
1046
- * Toggle the volume on and off. If not currently muted it will save the
1047
- * current volume value and turn the volume off. If currently muted then it
1048
- * will restore the volume to the saved value, and then rest the saved
1049
- * value.
1050
- *
1051
- * @example wavesurfer.toggleMute();
1052
- */
1053
- toggleMute() {
1054
- this.setMute(!this.isMuted);
1055
- }
1056
-
1057
- /**
1058
- * Enable or disable muted audio
1059
- *
1060
- * @param {boolean} mute Specify `true` to mute audio.
1061
- * @emits WaveSurfer#volume
1062
- * @emits WaveSurfer#mute
1063
- * @example
1064
- * // unmute
1065
- * wavesurfer.setMute(false);
1066
- * console.log(wavesurfer.getMute()) // logs false
1067
- */
1068
- setMute(mute) {
1069
- // ignore all muting requests if the audio is already in that state
1070
- if (mute === this.isMuted) {
1071
- this.fireEvent('mute', this.isMuted);
1072
- return;
1073
- }
1074
-
1075
- if (this.backend.setMute) {
1076
- // Backends such as the MediaElement backend have their own handling
1077
- // of mute, let them handle it.
1078
- this.backend.setMute(mute);
1079
- this.isMuted = mute;
1080
- } else {
1081
- if (mute) {
1082
- // If currently not muted then save current volume,
1083
- // turn off the volume and update the mute properties
1084
- this.savedVolume = this.backend.getVolume();
1085
- this.backend.setVolume(0);
1086
- this.isMuted = true;
1087
- this.fireEvent('volume', 0);
1088
- } else {
1089
- // If currently muted then restore to the saved volume
1090
- // and update the mute properties
1091
- this.backend.setVolume(this.savedVolume);
1092
- this.isMuted = false;
1093
- this.fireEvent('volume', this.savedVolume);
1094
- }
1095
- }
1096
- this.fireEvent('mute', this.isMuted);
1097
- }
1098
-
1099
- /**
1100
- * Get the current mute status.
1101
- *
1102
- * @example const isMuted = wavesurfer.getMute();
1103
- * @return {boolean} Current mute status
1104
- */
1105
- getMute() {
1106
- return this.isMuted;
1107
- }
1108
-
1109
- /**
1110
- * Get the list of current set filters as an array.
1111
- *
1112
- * Filters must be set with setFilters method first
1113
- *
1114
- * @return {array} List of enabled filters
1115
- */
1116
- getFilters() {
1117
- return this.backend.filters || [];
1118
- }
1119
-
1120
- /**
1121
- * Toggles `scrollParent` and redraws
1122
- *
1123
- * @example wavesurfer.toggleScroll();
1124
- */
1125
- toggleScroll() {
1126
- this.params.scrollParent = !this.params.scrollParent;
1127
- this.drawBuffer();
1128
- }
1129
-
1130
- /**
1131
- * Toggle mouse interaction
1132
- *
1133
- * @example wavesurfer.toggleInteraction();
1134
- */
1135
- toggleInteraction() {
1136
- this.params.interact = !this.params.interact;
1137
- }
1138
-
1139
- /**
1140
- * Get the fill color of the waveform after the cursor.
1141
- *
1142
- * @param {?number} channelIdx Optional index of the channel to get its wave color if splitChannels is true
1143
- * @return {string|object} A CSS color string, or an array of CSS color strings.
1144
- */
1145
- getWaveColor(channelIdx = null) {
1146
- if (this.params.splitChannelsOptions.channelColors[channelIdx]) {
1147
- return this.params.splitChannelsOptions.channelColors[channelIdx].waveColor;
1148
- }
1149
- return this.params.waveColor;
1150
- }
1151
-
1152
- /**
1153
- * Set the fill color of the waveform after the cursor.
1154
- *
1155
- * @param {string|object} color A CSS color string, or an array of CSS color strings.
1156
- * @param {?number} channelIdx Optional index of the channel to set its wave color if splitChannels is true
1157
- * @example wavesurfer.setWaveColor('#ddd');
1158
- */
1159
- setWaveColor(color, channelIdx = null) {
1160
- if (this.params.splitChannelsOptions.channelColors[channelIdx]) {
1161
- this.params.splitChannelsOptions.channelColors[channelIdx].waveColor = color;
1162
- } else {
1163
- this.params.waveColor = color;
1164
- }
1165
- this.drawBuffer();
1166
- }
1167
-
1168
- /**
1169
- * Get the fill color of the waveform behind the cursor.
1170
- *
1171
- * @param {?number} channelIdx Optional index of the channel to get its progress color if splitChannels is true
1172
- * @return {string|object} A CSS color string, or an array of CSS color strings.
1173
- */
1174
- getProgressColor(channelIdx = null) {
1175
- if (this.params.splitChannelsOptions.channelColors[channelIdx]) {
1176
- return this.params.splitChannelsOptions.channelColors[channelIdx].progressColor;
1177
- }
1178
- return this.params.progressColor;
1179
- }
1180
-
1181
- /**
1182
- * Set the fill color of the waveform behind the cursor.
1183
- *
1184
- * @param {string|object} color A CSS color string, or an array of CSS color strings.
1185
- * @param {?number} channelIdx Optional index of the channel to set its progress color if splitChannels is true
1186
- * @example wavesurfer.setProgressColor('#400');
1187
- */
1188
- setProgressColor(color, channelIdx) {
1189
- if (this.params.splitChannelsOptions.channelColors[channelIdx]) {
1190
- this.params.splitChannelsOptions.channelColors[channelIdx].progressColor = color;
1191
- } else {
1192
- this.params.progressColor = color;
1193
- }
1194
- this.drawBuffer();
1195
- }
1196
-
1197
- /**
1198
- * Get the background color of the waveform container.
1199
- *
1200
- * @return {string} A CSS color string.
1201
- */
1202
- getBackgroundColor() {
1203
- return this.params.backgroundColor;
1204
- }
1205
-
1206
- /**
1207
- * Set the background color of the waveform container.
1208
- *
1209
- * @param {string} color A CSS color string.
1210
- * @example wavesurfer.setBackgroundColor('#FF00FF');
1211
- */
1212
- setBackgroundColor(color) {
1213
- this.params.backgroundColor = color;
1214
- util.style(this.container, { background: this.params.backgroundColor });
1215
- }
1216
-
1217
- /**
1218
- * Get the fill color of the cursor indicating the playhead
1219
- * position.
1220
- *
1221
- * @return {string} A CSS color string.
1222
- */
1223
- getCursorColor() {
1224
- return this.params.cursorColor;
1225
- }
1226
-
1227
- /**
1228
- * Set the fill color of the cursor indicating the playhead
1229
- * position.
1230
- *
1231
- * @param {string} color A CSS color string.
1232
- * @example wavesurfer.setCursorColor('#222');
1233
- */
1234
- setCursorColor(color) {
1235
- this.params.cursorColor = color;
1236
- this.drawer.updateCursor();
1237
- }
1238
-
1239
- /**
1240
- * Get the height of the waveform.
1241
- *
1242
- * @return {number} Height measured in pixels.
1243
- */
1244
- getHeight() {
1245
- return this.params.height;
1246
- }
1247
-
1248
- /**
1249
- * Set the height of the waveform.
1250
- *
1251
- * @param {number} height Height measured in pixels.
1252
- * @example wavesurfer.setHeight(200);
1253
- */
1254
- setHeight(height) {
1255
- this.params.height = height;
1256
- this.drawer.setHeight(height * this.params.pixelRatio);
1257
- this.drawBuffer();
1258
- }
1259
-
1260
- /**
1261
- * Hide channels from being drawn on the waveform if splitting channels.
1262
- *
1263
- * For example, if we want to draw only the peaks for the right stereo channel:
1264
- *
1265
- * const wavesurfer = new WaveSurfer.create({...splitChannels: true});
1266
- * wavesurfer.load('stereo_audio.mp3');
1267
- *
1268
- * wavesurfer.setFilteredChannel([0]); <-- hide left channel peaks.
1269
- *
1270
- * @param {array} channelIndices Channels to be filtered out from drawing.
1271
- * @version 4.0.0
1272
- */
1273
- setFilteredChannels(channelIndices) {
1274
- this.params.splitChannelsOptions.filterChannels = channelIndices;
1275
- this.drawBuffer();
1276
- }
1277
-
1278
- /**
1279
- * Get the correct peaks for current wave view-port and render wave
1280
- *
1281
- * @private
1282
- * @emits WaveSurfer#redraw
1283
- */
1284
- drawBuffer() {
1285
- const nominalWidth = Math.round(
1286
- this.getDuration() *
1287
- this.params.minPxPerSec *
1288
- this.params.pixelRatio
1289
- );
1290
- const parentWidth = this.drawer.getWidth();
1291
- let width = nominalWidth;
1292
- // always start at 0 after zooming for scrolling : issue redraw left part
1293
- let start = 0;
1294
- let end = Math.max(start + parentWidth, width);
1295
- // Fill container
1296
- if (
1297
- this.params.fillParent &&
1298
- (!this.params.scrollParent || nominalWidth < parentWidth)
1299
- ) {
1300
- width = parentWidth;
1301
- start = 0;
1302
- end = width;
1303
- }
1304
-
1305
- let peaks;
1306
- if (this.params.partialRender) {
1307
- const newRanges = this.peakCache.addRangeToPeakCache(
1308
- width,
1309
- start,
1310
- end
1311
- );
1312
- let i;
1313
- for (i = 0; i < newRanges.length; i++) {
1314
- peaks = this.backend.getPeaks(
1315
- width,
1316
- newRanges[i][0],
1317
- newRanges[i][1]
1318
- );
1319
- this.drawer.drawPeaks(
1320
- peaks,
1321
- width,
1322
- newRanges[i][0],
1323
- newRanges[i][1]
1324
- );
1325
- }
1326
- } else {
1327
- peaks = this.backend.getPeaks(width, start, end);
1328
- this.drawer.drawPeaks(peaks, width, start, end);
1329
- }
1330
- this.fireEvent('redraw', peaks, width);
1331
- }
1332
-
1333
- /**
1334
- * Horizontally zooms the waveform in and out. It also changes the parameter
1335
- * `minPxPerSec` and enables the `scrollParent` option. Calling the function
1336
- * with a falsey parameter will reset the zoom state.
1337
- *
1338
- * @param {?number} pxPerSec Number of horizontal pixels per second of
1339
- * audio, if none is set the waveform returns to unzoomed state
1340
- * @emits WaveSurfer#zoom
1341
- * @example wavesurfer.zoom(20);
1342
- */
1343
- zoom(pxPerSec) {
1344
- if (!pxPerSec) {
1345
- this.params.minPxPerSec = this.defaultParams.minPxPerSec;
1346
- this.params.scrollParent = false;
1347
- } else {
1348
- this.params.minPxPerSec = pxPerSec;
1349
- this.params.scrollParent = true;
1350
- }
1351
-
1352
- this.drawBuffer();
1353
- this.drawer.progress(this.backend.getPlayedPercents());
1354
-
1355
- this.drawer.recenter(this.getCurrentTime() / this.getDuration());
1356
- this.fireEvent('zoom', pxPerSec);
1357
- }
1358
-
1359
- /**
1360
- * Decode buffer and load
1361
- *
1362
- * @private
1363
- * @param {ArrayBuffer} arraybuffer Buffer to process
1364
- */
1365
- loadArrayBuffer(arraybuffer) {
1366
- this.decodeArrayBuffer(arraybuffer, data => {
1367
- if (!this.isDestroyed) {
1368
- this.loadDecodedBuffer(data);
1369
- }
1370
- });
1371
- }
1372
-
1373
- /**
1374
- * Directly load an externally decoded AudioBuffer
1375
- *
1376
- * @private
1377
- * @param {AudioBuffer} buffer Buffer to process
1378
- * @emits WaveSurfer#ready
1379
- */
1380
- loadDecodedBuffer(buffer) {
1381
- this.backend.load(buffer);
1382
- this.drawBuffer();
1383
- this.isReady = true;
1384
- this.fireEvent('ready');
1385
- }
1386
-
1387
- /**
1388
- * Loads audio data from a Blob or File object
1389
- *
1390
- * @param {Blob|File} blob Audio data
1391
- * @example
1392
- */
1393
- loadBlob(blob) {
1394
- // Create file reader
1395
- const reader = new FileReader();
1396
- reader.addEventListener('progress', e => this.onProgress(e));
1397
- reader.addEventListener('load', e =>
1398
- this.loadArrayBuffer(e.target.result)
1399
- );
1400
- reader.addEventListener('error', () =>
1401
- this.fireEvent('error', 'Error reading file')
1402
- );
1403
- reader.readAsArrayBuffer(blob);
1404
- this.empty();
1405
- }
1406
-
1407
- /**
1408
- * Loads audio and re-renders the waveform.
1409
- *
1410
- * @param {string|HTMLMediaElement} url The url of the audio file or the
1411
- * audio element with the audio
1412
- * @param {number[]|Number.<Array[]>} peaks Wavesurfer does not have to decode
1413
- * the audio to render the waveform if this is specified
1414
- * @param {?string} preload (Use with backend `MediaElement` and `MediaElementWebAudio`)
1415
- * `'none'|'metadata'|'auto'` Preload attribute for the media element
1416
- * @param {?number} duration The duration of the audio. This is used to
1417
- * render the peaks data in the correct size for the audio duration (as
1418
- * befits the current `minPxPerSec` and zoom value) without having to decode
1419
- * the audio.
1420
- * @returns {void}
1421
- * @throws Will throw an error if the `url` argument is empty.
1422
- * @example
1423
- * // uses fetch or media element to load file (depending on backend)
1424
- * wavesurfer.load('http://example.com/demo.wav');
1425
- *
1426
- * // setting preload attribute with media element backend and supplying
1427
- * // peaks
1428
- * wavesurfer.load(
1429
- * 'http://example.com/demo.wav',
1430
- * [0.0218, 0.0183, 0.0165, 0.0198, 0.2137, 0.2888],
1431
- * true
1432
- * );
1433
- */
1434
- load(url, peaks, preload, duration) {
1435
- if (!url) {
1436
- throw new Error('url parameter cannot be empty');
1437
- }
1438
- this.empty();
1439
- if (preload) {
1440
- // check whether the preload attribute will be usable and if not log
1441
- // a warning listing the reasons why not and nullify the variable
1442
- const preloadIgnoreReasons = {
1443
- "Preload is not 'auto', 'none' or 'metadata'":
1444
- ['auto', 'metadata', 'none'].indexOf(preload) === -1,
1445
- 'Peaks are not provided': !peaks,
1446
- "Backend is not of type 'MediaElement' or 'MediaElementWebAudio'":
1447
- ['MediaElement', 'MediaElementWebAudio'].indexOf(
1448
- this.params.backend
1449
- ) === -1,
1450
- 'Url is not of type string': typeof url !== 'string'
1451
- };
1452
- const activeReasons = Object.keys(preloadIgnoreReasons).filter(
1453
- reason => preloadIgnoreReasons[reason]
1454
- );
1455
- if (activeReasons.length) {
1456
- // eslint-disable-next-line no-console
1457
- console.warn(
1458
- 'Preload parameter of wavesurfer.load will be ignored because:\n\t- ' +
1459
- activeReasons.join('\n\t- ')
1460
- );
1461
- // stop invalid values from being used
1462
- preload = null;
1463
- }
1464
- }
1465
-
1466
- // loadBuffer(url, peaks, duration) requires that url is a string
1467
- // but users can pass in a HTMLMediaElement to WaveSurfer
1468
- if (this.params.backend === 'WebAudio' && url instanceof HTMLMediaElement) {
1469
- url = url.src;
1470
- }
1471
-
1472
- switch (this.params.backend) {
1473
- case 'WebAudio':
1474
- return this.loadBuffer(url, peaks, duration);
1475
- case 'MediaElement':
1476
- case 'MediaElementWebAudio':
1477
- return this.loadMediaElement(url, peaks, preload, duration);
1478
- }
1479
- }
1480
-
1481
- /**
1482
- * Loads audio using Web Audio buffer backend.
1483
- *
1484
- * @private
1485
- * @emits WaveSurfer#waveform-ready
1486
- * @param {string} url URL of audio file
1487
- * @param {number[]|Number.<Array[]>} peaks Peaks data
1488
- * @param {?number} duration Optional duration of audio file
1489
- * @returns {void}
1490
- */
1491
- loadBuffer(url, peaks, duration) {
1492
- const load = action => {
1493
- if (action) {
1494
- this.tmpEvents.push(this.once('ready', action));
1495
- }
1496
- return this.getArrayBuffer(url, data => this.loadArrayBuffer(data));
1497
- };
1498
-
1499
- if (peaks) {
1500
- this.backend.setPeaks(peaks, duration);
1501
- this.drawBuffer();
1502
- this.fireEvent('waveform-ready');
1503
- this.tmpEvents.push(this.once('interaction', load));
1504
- } else {
1505
- return load();
1506
- }
1507
- }
1508
-
1509
- /**
1510
- * Either create a media element, or load an existing media element.
1511
- *
1512
- * @private
1513
- * @emits WaveSurfer#waveform-ready
1514
- * @param {string|HTMLMediaElement} urlOrElt Either a path to a media file, or an
1515
- * existing HTML5 Audio/Video Element
1516
- * @param {number[]|Number.<Array[]>} peaks Array of peaks. Required to bypass web audio
1517
- * dependency
1518
- * @param {?boolean} preload Set to true if the preload attribute of the
1519
- * audio element should be enabled
1520
- * @param {?number} duration Optional duration of audio file
1521
- */
1522
- loadMediaElement(urlOrElt, peaks, preload, duration) {
1523
- let url = urlOrElt;
1524
-
1525
- if (typeof urlOrElt === 'string') {
1526
- this.backend.load(url, this.mediaContainer, peaks, preload);
1527
- } else {
1528
- const elt = urlOrElt;
1529
- this.backend.loadElt(elt, peaks);
1530
-
1531
- // If peaks are not provided,
1532
- // url = element.src so we can get peaks with web audio
1533
- url = elt.src;
1534
- }
1535
-
1536
- this.tmpEvents.push(
1537
- this.backend.once('canplay', () => {
1538
- // ignore when backend was already destroyed
1539
- if (!this.backend.destroyed) {
1540
- this.drawBuffer();
1541
- this.isReady = true;
1542
- this.fireEvent('ready');
1543
- }
1544
- }),
1545
- this.backend.once('error', err => this.fireEvent('error', err))
1546
- );
1547
-
1548
- // If peaks are provided, render them and fire the `waveform-ready` event.
1549
- if (peaks) {
1550
- this.backend.setPeaks(peaks, duration);
1551
- this.drawBuffer();
1552
- this.fireEvent('waveform-ready');
1553
- }
1554
-
1555
- // If no pre-decoded peaks are provided, or are provided with
1556
- // forceDecode flag, attempt to download the audio file and decode it
1557
- // with Web Audio.
1558
- if (
1559
- (!peaks || this.params.forceDecode) &&
1560
- this.backend.supportsWebAudio()
1561
- ) {
1562
- this.getArrayBuffer(url, arraybuffer => {
1563
- this.decodeArrayBuffer(arraybuffer, buffer => {
1564
- this.backend.buffer = buffer;
1565
- this.backend.setPeaks(null);
1566
- this.drawBuffer();
1567
- this.fireEvent('waveform-ready');
1568
- });
1569
- });
1570
- }
1571
- }
1572
-
1573
- /**
1574
- * Decode an array buffer and pass data to a callback
1575
- *
1576
- * @private
1577
- * @param {Object} arraybuffer The array buffer to decode
1578
- * @param {function} callback The function to call on complete
1579
- */
1580
- decodeArrayBuffer(arraybuffer, callback) {
1581
- if (!this.isDestroyed) {
1582
- this.arraybuffer = arraybuffer;
1583
- this.backend.decodeArrayBuffer(
1584
- arraybuffer,
1585
- data => {
1586
- // Only use the decoded data if we haven't been destroyed or
1587
- // another decode started in the meantime
1588
- if (!this.isDestroyed && this.arraybuffer == arraybuffer) {
1589
- callback(data);
1590
- this.arraybuffer = null;
1591
- }
1592
- },
1593
- () => this.fireEvent('error', 'Error decoding audiobuffer')
1594
- );
1595
- }
1596
- }
1597
-
1598
- /**
1599
- * Load an array buffer using fetch and pass the result to a callback
1600
- *
1601
- * @param {string} url The URL of the file object
1602
- * @param {function} callback The function to call on complete
1603
- * @returns {util.fetchFile} fetch call
1604
- * @private
1605
- */
1606
- getArrayBuffer(url, callback) {
1607
- let options = Object.assign(
1608
- {
1609
- url: url,
1610
- responseType: 'arraybuffer'
1611
- },
1612
- this.params.xhr
1613
- );
1614
- const request = util.fetchFile(options);
1615
-
1616
- this.currentRequest = request;
1617
-
1618
- this.tmpEvents.push(
1619
- request.on('progress', e => {
1620
- this.onProgress(e);
1621
- }),
1622
- request.on('success', data => {
1623
- callback(data);
1624
- this.currentRequest = null;
1625
- }),
1626
- request.on('error', e => {
1627
- this.fireEvent('error', e);
1628
- this.currentRequest = null;
1629
- })
1630
- );
1631
-
1632
- return request;
1633
- }
1634
-
1635
- /**
1636
- * Called while the audio file is loading
1637
- *
1638
- * @private
1639
- * @param {Event} e Progress event
1640
- * @emits WaveSurfer#loading
1641
- */
1642
- onProgress(e) {
1643
- let percentComplete;
1644
- if (e.lengthComputable) {
1645
- percentComplete = e.loaded / e.total;
1646
- } else {
1647
- // Approximate progress with an asymptotic
1648
- // function, and assume downloads in the 1-3 MB range.
1649
- percentComplete = e.loaded / (e.loaded + 1000000);
1650
- }
1651
- this.fireEvent('loading', Math.round(percentComplete * 100), e.target);
1652
- }
1653
-
1654
- /**
1655
- * Exports PCM data into a JSON array and optionally opens in a new window
1656
- * as valid JSON Blob instance.
1657
- *
1658
- * @param {number} length=1024 The scale in which to export the peaks
1659
- * @param {number} accuracy=10000
1660
- * @param {?boolean} noWindow Set to true to disable opening a new
1661
- * window with the JSON
1662
- * @param {number} start Start index
1663
- * @param {number} end End index
1664
- * @return {Promise} Promise that resolves with array of peaks
1665
- */
1666
- exportPCM(length, accuracy, noWindow, start, end) {
1667
- length = length || 1024;
1668
- start = start || 0;
1669
- accuracy = accuracy || 10000;
1670
- noWindow = noWindow || false;
1671
- const peaks = this.backend.getPeaks(length, start, end);
1672
- const arr = [].map.call(
1673
- peaks,
1674
- val => Math.round(val * accuracy) / accuracy
1675
- );
1676
-
1677
- return new Promise((resolve, reject) => {
1678
- if (!noWindow){
1679
- const blobJSON = new Blob(
1680
- [JSON.stringify(arr)],
1681
- {type: 'application/json;charset=utf-8'}
1682
- );
1683
- const objURL = URL.createObjectURL(blobJSON);
1684
- window.open(objURL);
1685
- URL.revokeObjectURL(objURL);
1686
- }
1687
- resolve(arr);
1688
- });
1689
- }
1690
-
1691
- /**
1692
- * Save waveform image as data URI.
1693
- *
1694
- * The default format is `image/png`. Other supported types are
1695
- * `image/jpeg` and `image/webp`.
1696
- *
1697
- * @param {string} format='image/png' A string indicating the image format.
1698
- * The default format type is `image/png`.
1699
- * @param {number} quality=1 A number between 0 and 1 indicating the image
1700
- * quality to use for image formats that use lossy compression such as
1701
- * `image/jpeg` and `image/webp`.
1702
- * @param {string} type Image data type to return. Either `dataURL` (default)
1703
- * or `blob`.
1704
- * @return {string|string[]|Promise} When using `dataURL` type this returns
1705
- * a single data URL or an array of data URLs, one for each canvas. When using
1706
- * `blob` type this returns a `Promise` resolving with an array of `Blob`
1707
- * instances, one for each canvas.
1708
- */
1709
- exportImage(format, quality, type) {
1710
- if (!format) {
1711
- format = 'image/png';
1712
- }
1713
- if (!quality) {
1714
- quality = 1;
1715
- }
1716
- if (!type) {
1717
- type = 'dataURL';
1718
- }
1719
-
1720
- return this.drawer.getImage(format, quality, type);
1721
- }
1722
-
1723
- /**
1724
- * Cancel any fetch request currently in progress
1725
- */
1726
- cancelAjax() {
1727
- if (this.currentRequest && this.currentRequest.controller) {
1728
- // If the current request has a ProgressHandler, then its ReadableStream might need to be cancelled too
1729
- // See: Wavesurfer issue #2042
1730
- // See Firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1583815
1731
- if (this.currentRequest._reader) {
1732
- // Ignoring exceptions thrown by call to cancel()
1733
- this.currentRequest._reader.cancel().catch(err => {});
1734
- }
1735
-
1736
- this.currentRequest.controller.abort();
1737
- this.currentRequest = null;
1738
- }
1739
- }
1740
-
1741
- /**
1742
- * @private
1743
- */
1744
- clearTmpEvents() {
1745
- this.tmpEvents.forEach(e => e.un());
1746
- }
1747
-
1748
- /**
1749
- * Display empty waveform.
1750
- */
1751
- empty() {
1752
- if (!this.backend.isPaused()) {
1753
- this.stop();
1754
- this.backend.disconnectSource();
1755
- }
1756
- this.isReady = false;
1757
- this.cancelAjax();
1758
- this.clearTmpEvents();
1759
-
1760
- // empty drawer
1761
- this.drawer.progress(0);
1762
- this.drawer.setWidth(0);
1763
- this.drawer.drawPeaks({ length: this.drawer.getWidth() }, 0);
1764
- }
1765
-
1766
- /**
1767
- * Remove events, elements and disconnect WebAudio nodes.
1768
- *
1769
- * @emits WaveSurfer#destroy
1770
- */
1771
- destroy() {
1772
- this.destroyAllPlugins();
1773
- this.fireEvent('destroy');
1774
- this.cancelAjax();
1775
- this.clearTmpEvents();
1776
- this.unAll();
1777
- if (this.params.responsive !== false) {
1778
- window.removeEventListener('resize', this._onResize, true);
1779
- window.removeEventListener(
1780
- 'orientationchange',
1781
- this._onResize,
1782
- true
1783
- );
1784
- }
1785
- if (this.backend) {
1786
- this.backend.destroy();
1787
- // clears memory usage
1788
- this.backend = null;
1789
- }
1790
- if (this.drawer) {
1791
- this.drawer.destroy();
1792
- }
1793
- this.isDestroyed = true;
1794
- this.isReady = false;
1795
- this.arraybuffer = null;
1796
- }
1797
- }