wavesurfer.js 6.0.3 → 6.2.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.
Files changed (58) hide show
  1. package/CHANGES.md +28 -0
  2. package/README.md +3 -3
  3. package/dist/plugin/wavesurfer.cursor.js +4 -4
  4. package/dist/plugin/wavesurfer.cursor.js.map +1 -1
  5. package/dist/plugin/wavesurfer.cursor.min.js +2 -2
  6. package/dist/plugin/wavesurfer.cursor.min.js.map +1 -1
  7. package/dist/plugin/wavesurfer.elan.js +2 -2
  8. package/dist/plugin/wavesurfer.elan.js.map +1 -1
  9. package/dist/plugin/wavesurfer.elan.min.js +2 -2
  10. package/dist/plugin/wavesurfer.elan.min.js.map +1 -1
  11. package/dist/plugin/wavesurfer.markers.js +2 -2
  12. package/dist/plugin/wavesurfer.markers.js.map +1 -1
  13. package/dist/plugin/wavesurfer.markers.min.js +2 -2
  14. package/dist/plugin/wavesurfer.markers.min.js.map +1 -1
  15. package/dist/plugin/wavesurfer.mediasession.js +2 -4
  16. package/dist/plugin/wavesurfer.mediasession.js.map +1 -1
  17. package/dist/plugin/wavesurfer.mediasession.min.js +2 -2
  18. package/dist/plugin/wavesurfer.mediasession.min.js.map +1 -1
  19. package/dist/plugin/wavesurfer.microphone.js +2 -2
  20. package/dist/plugin/wavesurfer.microphone.js.map +1 -1
  21. package/dist/plugin/wavesurfer.microphone.min.js +2 -2
  22. package/dist/plugin/wavesurfer.microphone.min.js.map +1 -1
  23. package/dist/plugin/wavesurfer.minimap.js +25 -5
  24. package/dist/plugin/wavesurfer.minimap.js.map +1 -1
  25. package/dist/plugin/wavesurfer.minimap.min.js +2 -2
  26. package/dist/plugin/wavesurfer.minimap.min.js.map +1 -1
  27. package/dist/plugin/wavesurfer.playhead.js +2 -2
  28. package/dist/plugin/wavesurfer.playhead.js.map +1 -1
  29. package/dist/plugin/wavesurfer.playhead.min.js +2 -2
  30. package/dist/plugin/wavesurfer.playhead.min.js.map +1 -1
  31. package/dist/plugin/wavesurfer.regions.js +2 -2
  32. package/dist/plugin/wavesurfer.regions.js.map +1 -1
  33. package/dist/plugin/wavesurfer.regions.min.js +2 -2
  34. package/dist/plugin/wavesurfer.regions.min.js.map +1 -1
  35. package/dist/plugin/wavesurfer.spectrogram.js +43 -20
  36. package/dist/plugin/wavesurfer.spectrogram.js.map +1 -1
  37. package/dist/plugin/wavesurfer.spectrogram.min.js +2 -2
  38. package/dist/plugin/wavesurfer.spectrogram.min.js.map +1 -1
  39. package/dist/plugin/wavesurfer.timeline.js +6 -4
  40. package/dist/plugin/wavesurfer.timeline.js.map +1 -1
  41. package/dist/plugin/wavesurfer.timeline.min.js +2 -2
  42. package/dist/plugin/wavesurfer.timeline.min.js.map +1 -1
  43. package/dist/wavesurfer-html-init.js +2 -2
  44. package/dist/wavesurfer-html-init.js.map +1 -1
  45. package/dist/wavesurfer-html-init.min.js +2 -2
  46. package/dist/wavesurfer-html-init.min.js.map +1 -1
  47. package/dist/wavesurfer.js +35 -12
  48. package/dist/wavesurfer.js.map +1 -1
  49. package/dist/wavesurfer.min.js +2 -2
  50. package/dist/wavesurfer.min.js.map +1 -1
  51. package/package.json +8 -8
  52. package/src/plugin/cursor/index.js +2 -1
  53. package/src/plugin/mediasession/index.js +0 -2
  54. package/src/plugin/minimap/index.js +17 -2
  55. package/src/plugin/spectrogram/index.js +37 -20
  56. package/src/plugin/timeline/index.js +7 -1
  57. package/src/wavesurfer.js +5 -1
  58. package/src/webaudio.js +24 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wavesurfer.js",
3
- "version": "6.0.3",
3
+ "version": "6.2.0",
4
4
  "description": "Interactive navigable audio visualization using Web Audio and Canvas",
5
5
  "main": "dist/wavesurfer.js",
6
6
  "directories": {
@@ -55,13 +55,13 @@
55
55
  "visualization"
56
56
  ],
57
57
  "devDependencies": {
58
- "@babel/core": "^7.17.5",
58
+ "@babel/core": "^7.17.8",
59
59
  "@babel/eslint-parser": "^7.17.0",
60
60
  "@babel/plugin-proposal-class-properties": "^7.16.7",
61
61
  "@babel/preset-env": "^7.16.11",
62
- "@babel/register": "^7.17.0",
62
+ "@babel/register": "^7.17.7",
63
63
  "@chiragrupani/karma-chromium-edge-launcher": "^2.2.1",
64
- "babel-loader": "^8.2.3",
64
+ "babel-loader": "^8.2.4",
65
65
  "babel-plugin-add-module-exports": "^1.0.4",
66
66
  "babel-plugin-istanbul": "^6.1.1",
67
67
  "babel-plugin-proxy": "^1.1.0",
@@ -74,22 +74,22 @@
74
74
  "esdoc-ecmascript-proposal-plugin": "^1.0.0",
75
75
  "esdoc-integrate-test-plugin": "^1.0.0",
76
76
  "esdoc-standard-plugin": "^1.0.0",
77
- "eslint": "^8.10.0",
77
+ "eslint": "^8.11.0",
78
78
  "htmlhint": "^1.1.2",
79
79
  "in-publish": "^2.0.1",
80
80
  "jasmine-core": "^4.0.1",
81
81
  "karma": "^6.3.17",
82
- "karma-chrome-launcher": "3.1.0",
82
+ "karma-chrome-launcher": "3.1.1",
83
83
  "karma-coverage": "^2.2.0",
84
84
  "karma-firefox-launcher": "^2.1.2",
85
85
  "karma-jasmine": "4.0.1",
86
86
  "karma-jasmine-matchers": "5.0.0",
87
87
  "karma-verbose-reporter": "0.0.8",
88
88
  "karma-webpack": "^5.0.0",
89
- "lint-staged": "^12.3.4",
89
+ "lint-staged": "^12.3.7",
90
90
  "load-script": "^2.0.0",
91
91
  "pre-commit": "^1.2.2",
92
- "webpack": "^5.69.1",
92
+ "webpack": "^5.70.0",
93
93
  "webpack-cli": "^4.9.2",
94
94
  "webpack-dev-server": "^4.7.4",
95
95
  "webpack-merge": "^5.8.0"
@@ -90,7 +90,8 @@ export default class CursorPlugin {
90
90
  const bbox = this.wrapper.getBoundingClientRect();
91
91
  let y = 0;
92
92
  let x = this.wrapper.scrollLeft + event.clientX - bbox.left;
93
- let flip = bbox.right < event.clientX + this.displayTime.getBoundingClientRect().width;
93
+ const displayTimeWidth = this.displayTime ? this.displayTime.getBoundingClientRect().width : 0;
94
+ let flip = bbox.right < event.clientX + displayTimeWidth;
94
95
 
95
96
  if (this.params.showTime && this.params.followCursorY) {
96
97
  // follow y-position of the mouse
@@ -1,5 +1,3 @@
1
- /*global MediaMetadata*/
2
-
3
1
  /**
4
2
  * @typedef {Object} MediaSessionPluginParams
5
3
  * @property {MediaMetadata} metadata A MediaMetadata object: a representation
@@ -96,6 +96,7 @@ export default class MinimapPlugin {
96
96
  this.drawer = new ws.Drawer(this.params.container, this.params);
97
97
  this.wavesurfer = ws;
98
98
  this.util = ws.util;
99
+ this.cleared = false;
99
100
  this.renderEvent = 'redraw';
100
101
  this.overviewRegion = null;
101
102
  this.regionsPlugin = this.wavesurfer[this.params.regionsPluginName];
@@ -119,7 +120,7 @@ export default class MinimapPlugin {
119
120
  }
120
121
 
121
122
  if (this.regionsPlugin && this.params.showRegions) {
122
- this.regions();
123
+ this.drawRegions();
123
124
  }
124
125
  this.render();
125
126
  };
@@ -154,6 +155,18 @@ export default class MinimapPlugin {
154
155
  );
155
156
  }
156
157
  });
158
+ this._onLoading = percent => {
159
+ if (percent >= 100) {
160
+ this.cleared = false;
161
+ return;
162
+ }
163
+ if (this.cleared === true) {
164
+ return;
165
+ }
166
+ const len = this.drawer.getWidth();
167
+ this.drawer.drawPeaks([0], len, 0, len);
168
+ this.cleared = true;
169
+ };
157
170
  this._onZoom = e => {
158
171
  this.render();
159
172
  };
@@ -179,12 +192,13 @@ export default class MinimapPlugin {
179
192
  this.wavesurfer.un('scroll', this._onScroll);
180
193
  this.wavesurfer.un('audioprocess', this._onAudioprocess);
181
194
  this.wavesurfer.un('zoom', this._onZoom);
195
+ this.wavesurfer.un('loading', this._onLoading);
182
196
  this.drawer.destroy();
183
197
  this.overviewRegion = null;
184
198
  this.unAll();
185
199
  }
186
200
 
187
- regions() {
201
+ drawRegions() {
188
202
  this.regions = {};
189
203
 
190
204
  this.wavesurfer.on('region-created', region => {
@@ -267,6 +281,7 @@ export default class MinimapPlugin {
267
281
  window.addEventListener('orientationchange', this._onResize, true);
268
282
  this.wavesurfer.on('audioprocess', this._onAudioprocess);
269
283
  this.wavesurfer.on('seek', this._onSeek);
284
+ this.wavesurfer.on('loading', this._onLoading);
270
285
  if (this.params.showOverview) {
271
286
  this.wavesurfer.on('scroll', this._onScroll);
272
287
  this.wavesurfer.drawer.wrapper.addEventListener(
@@ -10,6 +10,8 @@ import FFT from './fft';
10
10
  * a power of 2.
11
11
  * @property {boolean} splitChannels=false Render with separate spectrograms for
12
12
  * the channels of the audio
13
+ * @property {number} height=fftSamples/2 Height of the spectrogram view in CSS
14
+ * pixels
13
15
  * @property {boolean} labels Set to true to display frequency labels.
14
16
  * @property {number} noverlap Size of the overlapping window. Must be <
15
17
  * fftSamples. Auto deduced from canvas size by default.
@@ -21,6 +23,9 @@ import FFT from './fft';
21
23
  * @property {number} pixelRatio=wavesurfer.params.pixelRatio to control the
22
24
  * size of the spectrogram in relation with its canvas. 1 = Draw on the whole
23
25
  * canvas. 2 = Draw on a quarter (1/2 the length and 1/2 the width)
26
+ * @property {number} frequencyMin=0 Min frequency to scale spectrogram.
27
+ * @property {number} frequencyMax=12000 Max frequency to scale spectrogram.
28
+ * Set this to samplerate/2 to draw whole range of spectrogram.
24
29
  * @property {?boolean} deferInit Set to true to manually call
25
30
  * `initPlugin('spectrogram')`
26
31
  * @property {?number[][]} colorMap A 256 long array of 4-element arrays.
@@ -125,13 +130,18 @@ export default class SpectrogramPlugin {
125
130
  this.pixelRatio = this.params.pixelRatio || ws.params.pixelRatio;
126
131
  this.fftSamples =
127
132
  this.params.fftSamples || ws.params.fftSamples || 512;
128
- this.height = this.fftSamples / 2;
133
+ this.height = this.params.height || this.fftSamples / 2;
129
134
  this.noverlap = params.noverlap;
130
135
  this.windowFunc = params.windowFunc;
131
136
  this.alpha = params.alpha;
132
137
  this.splitChannels = params.splitChannels;
133
138
  this.channels = this.splitChannels ? ws.backend.buffer.numberOfChannels : 1;
134
139
 
140
+ // Getting file's original samplerate is difficult(#1248).
141
+ // So set 12kHz default to render like wavesurfer.js 5.x.
142
+ this.frequencyMin = params.frequencyMin || 0;
143
+ this.frequencyMax = params.frequencyMax || 12000;
144
+
135
145
  this.createWrapper();
136
146
  this.createCanvas();
137
147
  this.render();
@@ -177,11 +187,10 @@ export default class SpectrogramPlugin {
177
187
  const labelsEl = (this.labelsEl = document.createElement('canvas'));
178
188
  labelsEl.classList.add('spec-labels');
179
189
  this.drawer.style(labelsEl, {
180
- left: 0,
181
- position: 'absolute',
190
+ position: 'fixed',
182
191
  zIndex: 9,
183
- height: `${this.height * this.channels / this.pixelRatio}px`,
184
- width: `${55 / this.pixelRatio}px`
192
+ height: `${this.height * this.channels}px`,
193
+ width: `55px`
185
194
  });
186
195
  this.wrapper.appendChild(labelsEl);
187
196
  this.loadLabels(
@@ -201,7 +210,7 @@ export default class SpectrogramPlugin {
201
210
  position: 'relative',
202
211
  userSelect: 'none',
203
212
  webkitUserSelect: 'none',
204
- height: `${this.height * this.channels / this.pixelRatio}px`
213
+ height: `${this.height * this.channels}px`
205
214
  });
206
215
 
207
216
  if (wsParams.fillParent || wsParams.scrollParent) {
@@ -248,8 +257,9 @@ export default class SpectrogramPlugin {
248
257
  updateCanvasStyle() {
249
258
  const width = Math.round(this.width / this.pixelRatio) + 'px';
250
259
  this.canvas.width = this.width;
251
- this.canvas.height = this.height * this.channels;
260
+ this.canvas.height = this.fftSamples / 2 * this.channels;
252
261
  this.canvas.style.width = width;
262
+ this.canvas.style.height = this.height + 'px';
253
263
  }
254
264
 
255
265
  drawSpectrogram(frequenciesData, my) {
@@ -259,8 +269,11 @@ export default class SpectrogramPlugin {
259
269
  }
260
270
 
261
271
  const spectrCc = my.spectrCc;
262
- const height = my.height;
272
+ const height = my.fftSamples / 2;
263
273
  const width = my.width;
274
+ const freqFrom = my.buffer.sampleRate / 2;
275
+ const freqMin = my.frequencyMin;
276
+ const freqMax = my.frequencyMax;
264
277
 
265
278
  if (!spectrCc) {
266
279
  return;
@@ -268,7 +281,7 @@ export default class SpectrogramPlugin {
268
281
 
269
282
  for (let c = 0; c < frequenciesData.length; c++) { // for each channel
270
283
  const pixels = my.resample(frequenciesData[c]);
271
- const imageData = spectrCc.createImageData(width, height);
284
+ const imageData = new ImageData(width, height);
272
285
 
273
286
  for (let i = 0; i < pixels.length; i++) {
274
287
  for (let j = 0; j < pixels[i].length; j++) {
@@ -281,8 +294,15 @@ export default class SpectrogramPlugin {
281
294
  }
282
295
  }
283
296
 
284
- // stack spectrograms
285
- spectrCc.putImageData(imageData, 0, height * c);
297
+ // scale and stack spectrograms
298
+ createImageBitmap(imageData).then(renderer =>
299
+ spectrCc.drawImage(renderer,
300
+ 0, height * (1 - freqMax / freqFrom), // source x, y
301
+ width, height * (freqMax - freqMin) / freqFrom, // source width, height
302
+ 0, height * c, // destination x, y
303
+ width, height // destination width, height
304
+ )
305
+ );
286
306
  }
287
307
  }
288
308
 
@@ -381,15 +401,15 @@ export default class SpectrogramPlugin {
381
401
  const bgWidth = 55;
382
402
  const getMaxY = frequenciesHeight || 512;
383
403
  const labelIndex = 5 * (getMaxY / 256);
384
- const freqStart = 0;
385
- const step =
386
- (this.wavesurfer.backend.ac.sampleRate / 2 - freqStart) /
387
- labelIndex;
404
+ const freqStart = this.frequencyMin;
405
+ const step = (this.frequencyMax - freqStart) / labelIndex;
388
406
 
389
407
  // prepare canvas element for labels
390
408
  const ctx = this.labelsEl.getContext('2d');
391
- this.labelsEl.height = this.height * this.channels;
392
- this.labelsEl.width = bgWidth;
409
+ const dispScale = window.devicePixelRatio;
410
+ this.labelsEl.height = this.height * this.channels * dispScale;
411
+ this.labelsEl.width = bgWidth * dispScale;
412
+ ctx.scale(dispScale, dispScale);
393
413
 
394
414
  if (!ctx) {
395
415
  return;
@@ -408,9 +428,6 @@ export default class SpectrogramPlugin {
408
428
  ctx.textBaseline = 'middle';
409
429
 
410
430
  const freq = freqStart + step * i;
411
- const index = Math.round(
412
- (freq / (this.sampleRate / 2)) * this.fftSamples
413
- );
414
431
  const label = this.freqType(freq);
415
432
  const units = this.unitType(freq);
416
433
  const yLabelOffset = 2;
@@ -371,7 +371,13 @@ export default class TimelinePlugin {
371
371
  // build an array of position data with index, second and pixel data,
372
372
  // this is then used multiple times below
373
373
  const positioning = [];
374
- for (i = 0; i < totalSeconds / timeInterval; i++) {
374
+
375
+ // render until end in case we have a negative offset
376
+ const renderSeconds = (this.params.offset < 0)
377
+ ? totalSeconds - this.params.offset
378
+ : totalSeconds;
379
+
380
+ for (i = 0; i < renderSeconds / timeInterval; i++) {
375
381
  positioning.push([i, curSeconds, curPixel]);
376
382
  curSeconds += timeInterval;
377
383
  curPixel += pixelsPerSecond * timeInterval;
package/src/wavesurfer.js CHANGED
@@ -501,11 +501,15 @@ export default class WaveSurfer extends util.Observer {
501
501
  this._onResize = util.debounce(
502
502
  () => {
503
503
  if (
504
+ this.drawer.wrapper &&
504
505
  prevWidth != this.drawer.wrapper.clientWidth &&
505
506
  !this.params.scrollParent
506
507
  ) {
507
508
  prevWidth = this.drawer.wrapper.clientWidth;
508
- this.drawer.fireEvent('redraw');
509
+ if (prevWidth) {
510
+ // redraw only if waveform container is rendered and has a width
511
+ this.drawer.fireEvent('redraw');
512
+ }
509
513
  }
510
514
  },
511
515
  typeof this.params.responsive === 'number'
package/src/webaudio.js CHANGED
@@ -145,6 +145,10 @@ export default class WebAudio extends util.Observer {
145
145
  this.state = null;
146
146
  /** @private */
147
147
  this.explicitDuration = params.duration;
148
+ /** @private */
149
+ this.sinkStreamDestination = null;
150
+ /** @private */
151
+ this.sinkAudioElement = null;
148
152
  /**
149
153
  * Boolean indicating if the backend was destroyed.
150
154
  */
@@ -298,19 +302,24 @@ export default class WebAudio extends util.Observer {
298
302
  * output. Here we create an HTMLAudioElement, connect the
299
303
  * webaudio stream to that element and setSinkId there.
300
304
  */
301
- let audio = new window.Audio();
302
- if (!audio.setSinkId) {
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) {
303
311
  return Promise.reject(
304
312
  new Error('setSinkId is not supported in your browser')
305
313
  );
306
314
  }
307
- audio.autoplay = true;
308
- const dest = this.ac.createMediaStreamDestination();
315
+ if (!this.sinkStreamDestination) {
316
+ this.sinkStreamDestination = this.ac.createMediaStreamDestination();
317
+ }
309
318
  this.gainNode.disconnect();
310
- this.gainNode.connect(dest);
311
- audio.srcObject = dest.stream;
319
+ this.gainNode.connect(this.sinkStreamDestination);
320
+ this.sinkAudioElement.srcObject = this.sinkStreamDestination.stream;
312
321
 
313
- return audio.setSinkId(deviceId);
322
+ return this.sinkAudioElement.setSinkId(deviceId);
314
323
  } else {
315
324
  return Promise.reject(new Error('Invalid deviceId: ' + deviceId));
316
325
  }
@@ -541,6 +550,14 @@ export default class WebAudio extends util.Observer {
541
550
  // clear the offlineAudioContext
542
551
  window.WaveSurferOfflineAudioContext = null;
543
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
+ }
544
561
  }
545
562
  /**
546
563
  * This is called when wavesurfer is destroyed