wavesurfer.js 6.5.2 → 6.6.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 (35) hide show
  1. package/README.md +19 -3
  2. package/dist/plugin/wavesurfer.cursor.js +1 -1
  3. package/dist/plugin/wavesurfer.cursor.min.js +1 -1
  4. package/dist/plugin/wavesurfer.elan.js +1 -1
  5. package/dist/plugin/wavesurfer.elan.min.js +1 -1
  6. package/dist/plugin/wavesurfer.markers.js +1 -1
  7. package/dist/plugin/wavesurfer.markers.min.js +1 -1
  8. package/dist/plugin/wavesurfer.mediasession.js +1 -1
  9. package/dist/plugin/wavesurfer.mediasession.min.js +1 -1
  10. package/dist/plugin/wavesurfer.microphone.js +1 -1
  11. package/dist/plugin/wavesurfer.microphone.min.js +1 -1
  12. package/dist/plugin/wavesurfer.minimap.js +1 -1
  13. package/dist/plugin/wavesurfer.minimap.min.js +1 -1
  14. package/dist/plugin/wavesurfer.playhead.js +1 -1
  15. package/dist/plugin/wavesurfer.playhead.min.js +1 -1
  16. package/dist/plugin/wavesurfer.regions.js +1 -1
  17. package/dist/plugin/wavesurfer.regions.min.js +1 -1
  18. package/dist/plugin/wavesurfer.spectrogram.js +128 -34
  19. package/dist/plugin/wavesurfer.spectrogram.js.map +1 -1
  20. package/dist/plugin/wavesurfer.spectrogram.min.js +2 -2
  21. package/dist/plugin/wavesurfer.spectrogram.min.js.map +1 -1
  22. package/dist/plugin/wavesurfer.timeline.js +1 -1
  23. package/dist/plugin/wavesurfer.timeline.min.js +1 -1
  24. package/dist/wavesurfer-html-init.js +1 -1
  25. package/dist/wavesurfer-html-init.min.js +1 -1
  26. package/dist/wavesurfer.js +160 -6
  27. package/dist/wavesurfer.js.map +1 -1
  28. package/dist/wavesurfer.min.js +2 -2
  29. package/dist/wavesurfer.min.js.map +1 -1
  30. package/package.json +4 -3
  31. package/src/drawer.canvasentry.js +43 -0
  32. package/src/drawer.js +0 -4
  33. package/src/drawer.multicanvas.js +71 -1
  34. package/src/plugin/spectrogram/index.js +121 -30
  35. package/src/wavesurfer.js +33 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wavesurfer.js",
3
- "version": "6.5.2",
3
+ "version": "6.6.1",
4
4
  "description": "Interactive navigable audio visualization using Web Audio and Canvas",
5
5
  "main": "dist/wavesurfer.js",
6
6
  "directories": {
@@ -31,11 +31,12 @@
31
31
  "test": "karma start karma.conf.js",
32
32
  "test:chrome": "karma start karma.conf.js --browsers=Chrome --single-run=false",
33
33
  "prepublishOnly": "not-in-install && npm run build || in-install",
34
- "prepare": "npm run build"
34
+ "prepare": "npm run build",
35
+ "release": "./scripts/release.sh"
35
36
  },
36
37
  "repository": {
37
38
  "type": "git",
38
- "url": "git://github.com/wavesurfer-js/wavesurfer.js.git"
39
+ "url": "git@github.com:wavesurfer-js/wavesurfer.js.git"
39
40
  },
40
41
  "publishConfig": {
41
42
  "registry": "https://registry.npmjs.org"
@@ -66,6 +66,11 @@ export default class CanvasEntry {
66
66
  * @type {object}
67
67
  */
68
68
  this.canvasContextAttributes = {};
69
+ /**
70
+ * The Timeout id used to track this canvas entry.
71
+ */
72
+ this.drawTimeout = null;
73
+
69
74
  }
70
75
 
71
76
  /**
@@ -125,21 +130,27 @@ export default class CanvasEntry {
125
130
  */
126
131
  clearWave() {
127
132
  // wave
133
+ this.waveCtx.save();
134
+ this.waveCtx.setTransform(1, 0, 0, 1, 0, 0);
128
135
  this.waveCtx.clearRect(
129
136
  0,
130
137
  0,
131
138
  this.waveCtx.canvas.width,
132
139
  this.waveCtx.canvas.height
133
140
  );
141
+ this.waveCtx.restore();
134
142
 
135
143
  // progress
136
144
  if (this.hasProgressCanvas) {
145
+ this.progressCtx.save();
146
+ this.progressCtx.setTransform(1, 0, 0, 1, 0, 0);
137
147
  this.progressCtx.clearRect(
138
148
  0,
139
149
  0,
140
150
  this.progressCtx.canvas.width,
141
151
  this.progressCtx.canvas.height
142
152
  );
153
+ this.progressCtx.restore();
143
154
  }
144
155
  }
145
156
 
@@ -424,4 +435,36 @@ export default class CanvasEntry {
424
435
  return this.wave.toDataURL(format, quality);
425
436
  }
426
437
  }
438
+
439
+ /**
440
+ * Stretches existing canvas
441
+ * @param {Number} newTotalWidth total width of wave in pixels
442
+ */
443
+ stretchCanvas(newTotalWidth) {
444
+ //Calculate the start and width of this canvas
445
+ let start = Math.round(this.start * newTotalWidth);
446
+ let width = Math.round(this.end * newTotalWidth - start);
447
+
448
+ //Stretch canvas
449
+ let elementSize = { width: width + 'px' };
450
+ let elementStart = {left: start + 'px'};
451
+ style(this.wave, elementSize);
452
+ style(this.wave, elementStart);
453
+ if (this.hasProgressCanvas) {
454
+ style(this.progress, elementSize);
455
+ style(this.progress, elementStart);
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Set the left offset of the canvas
461
+ * @param {Number} position in px for the canvas to start
462
+ */
463
+ setLeft(position) {
464
+ let elementStart = {left: position + 'px'};
465
+ style(this.wave, elementStart);
466
+ if (this.hasProgressCanvas) {
467
+ style(this.progress, elementStart);
468
+ }
469
+ }
427
470
  }
package/src/drawer.js CHANGED
@@ -272,10 +272,6 @@ export default class Drawer extends util.Observer {
272
272
  * @return {boolean} Whether the width of the container was updated or not
273
273
  */
274
274
  setWidth(width) {
275
- if (this.width == width) {
276
- return false;
277
- }
278
-
279
275
  this.width = width;
280
276
 
281
277
  if (this.params.fillParent || this.params.scrollParent) {
@@ -89,6 +89,14 @@ export default class MultiCanvas extends Drawer {
89
89
  * @type {boolean}
90
90
  */
91
91
  this.vertical = params.vertical;
92
+
93
+ /**
94
+ * Whether to use the optimsized zoom rendering
95
+ * Automatically toggles to true if stretchCanvases() function is called
96
+ *
97
+ * @type {boolean}
98
+ */
99
+ this.optimiseZoom = false;
92
100
  }
93
101
 
94
102
  /**
@@ -157,10 +165,16 @@ export default class MultiCanvas extends Drawer {
157
165
 
158
166
  let canvasWidth = this.maxCanvasWidth + this.overlap;
159
167
  const lastCanvas = this.canvases.length - 1;
168
+ let leftOffset = 0;
160
169
  this.canvases.forEach((entry, i) => {
161
170
  if (i == lastCanvas) {
162
171
  canvasWidth = this.width - this.maxCanvasWidth * lastCanvas;
163
172
  }
173
+
174
+ //Set left offset and add to next entry
175
+ entry.setLeft(leftOffset);
176
+ leftOffset += canvasWidth / this.params.pixelRatio;
177
+
164
178
  this.updateDimensions(entry, canvasWidth, this.height);
165
179
 
166
180
  entry.clearWave();
@@ -410,7 +424,39 @@ export default class MultiCanvas extends Drawer {
410
424
  this.canvases.forEach((entry, i) => {
411
425
  this.setFillStyles(entry, waveColor, progressColor);
412
426
  this.applyCanvasTransforms(entry, this.params.vertical);
413
- entry.drawLines(peaks, absmax, halfH, offsetY, start, end);
427
+
428
+ if (this.optimiseZoom) {
429
+ //Optimising zoom functionality
430
+ //If there's a wrapper, optimise for the view
431
+ let priority = 0;
432
+ if (this.wrapper) {
433
+ let canvasRect = entry.wave.getBoundingClientRect();
434
+ let wrapperRect = this.wrapper.getBoundingClientRect();
435
+
436
+ //Determine whether canvas is in viewframe or not and assign priority
437
+ if (Math.floor(canvasRect['left']) > Math.ceil(wrapperRect['right'])) {
438
+ //Canvas is to the right of view window
439
+ let distance = canvasRect['left'] - wrapperRect['right'];
440
+ priority = Math.ceil(distance / wrapperRect['width']);
441
+ } else if (Math.ceil(canvasRect['right']) < Math.floor(wrapperRect['left'])) {
442
+ //Canvas is to the left of the view window
443
+ let distance = wrapperRect['left'] - canvasRect['right'];
444
+ priority = Math.ceil(distance / wrapperRect['width']);
445
+ }
446
+ } else {
447
+ //Everything is equal priority
448
+ }
449
+
450
+ //This staggers the drawing of canvases so they don't all draw at once
451
+ entry.clearWave();
452
+ clearTimeout(entry.drawTimeout);
453
+ entry.drawTimeout = setTimeout(function(){
454
+ entry.drawLines(peaks, absmax, halfH, offsetY, start, end);
455
+ entry.drawTimeout = null;
456
+ }, 25 * priority);
457
+ } else {
458
+ entry.drawLines(peaks, absmax, halfH, offsetY, start, end);
459
+ }
414
460
  });
415
461
  }
416
462
 
@@ -606,6 +652,30 @@ export default class MultiCanvas extends Drawer {
606
652
  }
607
653
  }
608
654
 
655
+ /**
656
+ * Stretches the canvases to mimic zoom without recalculation
657
+ *
658
+ * @param {Number} desiredWidth new width of the wave display
659
+ * @param {Number} progress Value between 0 and 1 for wave progress
660
+ */
661
+ stretchCanvases(desiredWidth, progress) {
662
+ if (!this.optimiseZoom) {
663
+ //Enable optimsed zooming
664
+ this.optimiseZoom = true;
665
+ }
666
+ let totalCanvasWidth = Math.round(desiredWidth / this.params.pixelRatio);
667
+ this.width = desiredWidth;
668
+
669
+ for (let i = 0; i < this.canvases.length; i++) {
670
+ this.canvases[i].stretchCanvas(totalCanvasWidth);
671
+ }
672
+
673
+ //Update progress
674
+ let progressPos = progress * totalCanvasWidth;
675
+ this.updateProgress(progressPos);
676
+ this.recenterOnPosition(progressPos, true);
677
+ }
678
+
609
679
  /**
610
680
  * Render the new progress
611
681
  *
@@ -92,6 +92,9 @@ export default class SpectrogramPlugin {
92
92
  this._onRender = () => {
93
93
  this.render();
94
94
  };
95
+ this._onZoom = () => {
96
+ this.stretchCanvases();
97
+ };
95
98
  this._onWrapperClick = e => {
96
99
  this._wrapperClickHandler(e);
97
100
  };
@@ -136,6 +139,9 @@ export default class SpectrogramPlugin {
136
139
  this.alpha = params.alpha;
137
140
  this.splitChannels = params.splitChannels;
138
141
  this.channels = this.splitChannels ? ws.backend.buffer.numberOfChannels : 1;
142
+ this.canvases = [];
143
+ this.canvasesTimeouts = [];
144
+ this.scrollLeftTracker = 0; //Tracks the desired scrollLeft value
139
145
 
140
146
  // Getting file's original samplerate is difficult(#1248).
141
147
  // So set 12kHz default to render like wavesurfer.js 5.x.
@@ -143,10 +149,11 @@ export default class SpectrogramPlugin {
143
149
  this.frequencyMax = params.frequencyMax || 12000;
144
150
 
145
151
  this.createWrapper();
146
- this.createCanvas();
152
+ this.addCanvas();
147
153
  this.render();
148
154
 
149
155
  drawer.wrapper.addEventListener('scroll', this._onScroll);
156
+ ws.on('zoom', this._onZoom);
150
157
  ws.on('redraw', this._onRender);
151
158
  };
152
159
  }
@@ -231,17 +238,49 @@ export default class SpectrogramPlugin {
231
238
  this.fireEvent('click', relX / this.width || 0);
232
239
  }
233
240
 
234
- createCanvas() {
235
- const canvas = (this.canvas = this.wrapper.appendChild(
241
+ /**
242
+ * Add a canvas to this.canvases
243
+ */
244
+ addCanvas() {
245
+ const canvas = (this.wrapper.appendChild(
236
246
  document.createElement('canvas')
237
247
  ));
238
248
 
239
- this.spectrCc = canvas.getContext('2d');
240
-
241
249
  this.util.style(canvas, {
242
250
  position: 'absolute',
243
251
  zIndex: 4
244
252
  });
253
+
254
+ this.canvases.push(canvas);
255
+ this.canvasesTimeouts.push(null);
256
+ }
257
+
258
+ /**
259
+ * Remove a canvas from this.canvases
260
+ */
261
+ removeCanvas() {
262
+ //Stop drawing (if drawing)
263
+ clearTimeout(this.canvasesTimeouts[this.canvasesTimeouts.length - 1]);
264
+
265
+ let lastEntry = this.canvases[this.canvases.length - 1];
266
+ lastEntry.parentElement.removeChild(lastEntry);
267
+
268
+ this.canvases.pop();
269
+ this.canvasesTimeouts.pop();
270
+ }
271
+
272
+ /**
273
+ * Ensure the correct number of canvases for the size of the spectrogram
274
+ */
275
+ updateCanvases() {
276
+ let canvasesRequired = Math.ceil(this.width / 4000);
277
+
278
+ while (this.canvases.length < canvasesRequired) {
279
+ this.addCanvas();
280
+ }
281
+ while (this.canvases.length > canvasesRequired) {
282
+ this.removeCanvas();
283
+ }
245
284
  }
246
285
 
247
286
  render() {
@@ -255,11 +294,14 @@ export default class SpectrogramPlugin {
255
294
  }
256
295
 
257
296
  updateCanvasStyle() {
258
- const width = Math.round(this.width / this.pixelRatio) + 'px';
259
- this.canvas.width = this.width;
260
- this.canvas.height = this.fftSamples / 2 * this.channels;
261
- this.canvas.style.width = width;
262
- this.canvas.style.height = this.height + 'px';
297
+ this.updateCanvases();
298
+ //width per canvas
299
+ for (let i = 0; i < this.canvases.length; i++) {
300
+ this.canvases[i].width = Math.round(this.width / this.canvases.length);
301
+ this.canvases[i].height = this.fftSamples / 2 * this.channels;
302
+ this.canvases[i].style.width = Math.round(this.canvases[i].width / this.pixelRatio) + 'px';
303
+ this.canvases[i].style.height = this.height + 'px';
304
+ }
263
305
  }
264
306
 
265
307
  drawSpectrogram(frequenciesData, my) {
@@ -268,25 +310,61 @@ export default class SpectrogramPlugin {
268
310
  frequenciesData = [frequenciesData];
269
311
  }
270
312
 
271
- const spectrCc = my.spectrCc;
313
+ my.updateCanvasStyle();
314
+
315
+ //Stop canvases still being drawn
316
+ for (let i = 0; i < my.canvasesTimeouts.length; i++) {
317
+ clearTimeout(my.canvasesTimeouts[i]);
318
+ }
319
+
320
+ const view = [my.scrollLeftTracker, my.scrollLeftTracker + my.wrapper.clientWidth];
321
+
322
+ for (let canvasNum = 0; canvasNum < my.canvases.length; canvasNum++) {
323
+ const canvasLeft = canvasNum * Math.floor(my.width / my.canvases.length / my.pixelRatio);
324
+ const canvasRight = (canvasNum + 1) * Math.floor(my.width / my.canvases.length / my.pixelRatio);
325
+ const canvasBound = [canvasLeft, canvasRight];
326
+ my.canvases[canvasNum].style['left'] = canvasLeft + 'px';
327
+
328
+ //Optimise drawing for the view
329
+ let priority = 0;
330
+ if (canvasBound[0] > view[1]) {
331
+ //Canvas is to the right of view window
332
+ let distance = canvasBound[0] - view[1];
333
+ priority = Math.ceil(distance / (view[1] - view[0]));
334
+ } else if (canvasBound[1] < view[0]) {
335
+ //Canvas is to the left of the view window
336
+ let distance = view[0] - canvasBound[1];
337
+ priority = Math.ceil(distance / (view[1] - view[0]));
338
+ }
339
+
340
+ //delay = 25ms * number of viewport widths away the canvas is
341
+ my.canvasesTimeouts[canvasNum] = setTimeout(my.drawToCanvas, 25 * priority, frequenciesData, my, canvasNum);
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Draw spectrogram channel to a specific canvas
347
+ * @param {[Number, Number, Number]} frequenciesData spectrogram data in [channel, sample, freq] format
348
+ * @param {SpectrogramPlugin} my variable with 'this' in it
349
+ * @param {Number} canvasNum Canvas to draw to
350
+ */
351
+ drawToCanvas(frequenciesData, my, canvasNum) {
272
352
  const height = my.fftSamples / 2;
273
- const width = my.width;
274
353
  const freqFrom = my.buffer.sampleRate / 2;
275
354
  const freqMin = my.frequencyMin;
276
355
  const freqMax = my.frequencyMax;
277
356
 
278
- if (!spectrCc) {
279
- return;
280
- }
357
+ for (let channel = 0; channel < frequenciesData.length; channel++) {
281
358
 
282
- for (let c = 0; c < frequenciesData.length; c++) { // for each channel
283
- const pixels = my.resample(frequenciesData[c]);
284
- const imageData = new ImageData(width, height);
359
+ //Get pixels from frequency data and apply to image
360
+ const relevantFreqs = frequenciesData[channel].slice(canvasNum * Math.round(frequenciesData[channel].length / my.canvases.length), (canvasNum + 1) * Math.round(frequenciesData[channel].length / my.canvases.length));
361
+ const pixels = my.resample(relevantFreqs);
362
+ const imageData = new ImageData(pixels.length, height);
285
363
 
286
364
  for (let i = 0; i < pixels.length; i++) {
287
365
  for (let j = 0; j < pixels[i].length; j++) {
288
366
  const colorMap = my.colorMap[pixels[i][j]];
289
- const redIndex = ((height - j) * width + i) * 4;
367
+ const redIndex = ((height - j) * imageData.width + i) * 4;
290
368
  imageData.data[redIndex] = colorMap[0] * 255;
291
369
  imageData.data[redIndex + 1] = colorMap[1] * 255;
292
370
  imageData.data[redIndex + 2] = colorMap[2] * 255;
@@ -294,16 +372,20 @@ export default class SpectrogramPlugin {
294
372
  }
295
373
  }
296
374
 
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
- );
375
+ //Draw image to canvas
376
+ createImageBitmap(imageData).then(renderer => {
377
+ if (my.canvases[canvasNum]) { //Check canvas still exists after creating image
378
+ my.canvases[canvasNum].getContext('2d').drawImage(renderer,
379
+ 0, height * (1 - freqMax / freqFrom), // source x, y
380
+ imageData.width, height * (freqMax - freqMin) / freqFrom, // source width, height
381
+ 0, height * channel, // destination x, y
382
+ my.canvases[canvasNum].width, height // destination width, height
383
+ );
384
+ }
385
+ });
306
386
  }
387
+ //Drawing is finished
388
+ my.canvasesTimeouts[canvasNum] = null;
307
389
  }
308
390
 
309
391
  getFrequencies(callback) {
@@ -322,7 +404,7 @@ export default class SpectrogramPlugin {
322
404
 
323
405
  let noverlap = this.noverlap;
324
406
  if (!noverlap) {
325
- const uniqueSamplesPerPx = buffer.length / this.canvas.width;
407
+ const uniqueSamplesPerPx = buffer.length / this.width;
326
408
  noverlap = Math.max(0, Math.round(fftSamples - uniqueSamplesPerPx));
327
409
  }
328
410
 
@@ -461,12 +543,13 @@ export default class SpectrogramPlugin {
461
543
 
462
544
  updateScroll(e) {
463
545
  if (this.wrapper) {
546
+ this.scrollLeftTracker = e.target.scrollLeft;
464
547
  this.wrapper.scrollLeft = e.target.scrollLeft;
465
548
  }
466
549
  }
467
550
 
468
551
  resample(oldMatrix) {
469
- const columnsNumber = this.width;
552
+ const columnsNumber = oldMatrix.length;
470
553
  const newMatrix = [];
471
554
 
472
555
  const oldPiece = 1 / oldMatrix.length;
@@ -519,4 +602,12 @@ export default class SpectrogramPlugin {
519
602
 
520
603
  return newMatrix;
521
604
  }
605
+
606
+ stretchCanvases() {
607
+ for (let i = 0; i < this.canvases.length; i++) {
608
+ this.canvases[i].style.width = Math.round(this.drawer.width / this.canvases.length / this.pixelRatio) + 'px';
609
+ const canvasLeft = i * Math.floor(this.drawer.width / this.canvases.length / this.pixelRatio);
610
+ this.canvases[i].style['left'] = canvasLeft + 'px';
611
+ }
612
+ }
522
613
  }
package/src/wavesurfer.js CHANGED
@@ -1275,6 +1275,21 @@ export default class WaveSurfer extends util.Observer {
1275
1275
  this.drawBuffer();
1276
1276
  }
1277
1277
 
1278
+ /**
1279
+ * Calls getPeaks() and drawPeaks()
1280
+ * @param {WaveSurfer} wavesurfer the Wavesurfer to get/draw peaks from/to
1281
+ * @param {number} width The width of the area that should be drawn
1282
+ * @param {number} start The x-offset of the beginning of the area that
1283
+ * should be rendered
1284
+ * @param {number} end The x-offset of the end of the area that should be
1285
+ * rendered
1286
+ */
1287
+ getAndDrawPeaks(wavesurfer, width, start, end) {
1288
+ let peaks;
1289
+ peaks = wavesurfer.backend.getPeaks(width, start, end);
1290
+ wavesurfer.drawer.drawPeaks(peaks, width, start, end);
1291
+ }
1292
+
1278
1293
  /**
1279
1294
  * Get the correct peaks for current wave view-port and render wave
1280
1295
  *
@@ -1356,6 +1371,24 @@ export default class WaveSurfer extends util.Observer {
1356
1371
  this.fireEvent('zoom', pxPerSec);
1357
1372
  }
1358
1373
 
1374
+ /**
1375
+ * Call this function while moving the zoom slider to stretch the canvases
1376
+ * of the wave without recalculating
1377
+ *
1378
+ * @param {Number} pxPerSec value returned from the zoom slider
1379
+ */
1380
+ zooming(pxPerSec) {
1381
+ //Calculate the new width, this cannot be smaller than the parent container width
1382
+ let desiredWidth = Math.round(this.getDuration() * pxPerSec * this.params.pixelRatio);
1383
+ let parentWidth = this.drawer.getWidth();
1384
+ desiredWidth = Math.max(parentWidth, desiredWidth);
1385
+
1386
+ //Stretch canvases
1387
+ this.drawer.stretchCanvases(desiredWidth, this.backend.getPlayedPercents());
1388
+
1389
+ this.fireEvent('zoom', pxPerSec);
1390
+ }
1391
+
1359
1392
  /**
1360
1393
  * Decode buffer and load
1361
1394
  *