wavesurfer.js 4.6.0 → 5.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 (66) hide show
  1. package/CHANGES.md +41 -0
  2. package/README.md +6 -3
  3. package/dist/plugin/wavesurfer.cursor.js +9 -5
  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 +5 -4
  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 +31 -12
  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 +5 -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 +5 -4
  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 +5 -4
  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 +324 -0
  28. package/dist/plugin/wavesurfer.playhead.js.map +1 -0
  29. package/dist/plugin/wavesurfer.playhead.min.js +7 -0
  30. package/dist/plugin/wavesurfer.playhead.min.js.map +1 -0
  31. package/dist/plugin/wavesurfer.regions.js +141 -86
  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 +70 -63
  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 +32 -16
  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 +5 -4
  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 +326 -60
  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 +19 -16
  52. package/src/drawer.canvasentry.js +16 -0
  53. package/src/drawer.js +30 -21
  54. package/src/drawer.multicanvas.js +63 -39
  55. package/src/plugin/cursor/index.js +3 -1
  56. package/src/plugin/markers/index.js +22 -8
  57. package/src/plugin/playhead/index.js +226 -0
  58. package/src/plugin/regions/index.js +28 -11
  59. package/src/plugin/regions/region.js +116 -75
  60. package/src/plugin/spectrogram/index.js +65 -61
  61. package/src/plugin/timeline/index.js +30 -19
  62. package/src/util/index.js +2 -0
  63. package/src/util/orientation.js +98 -0
  64. package/src/util/silence-mode.js +35 -0
  65. package/src/wavesurfer.js +41 -22
  66. package/src/webaudio.js +11 -2
@@ -0,0 +1,98 @@
1
+ const verticalPropMap = {
2
+ width: 'height',
3
+ height: 'width',
4
+
5
+ overflowX: 'overflowY',
6
+ overflowY: 'overflowX',
7
+
8
+ clientWidth: 'clientHeight',
9
+ clientHeight: 'clientWidth',
10
+
11
+ clientX: 'clientY',
12
+ clientY: 'clientX',
13
+
14
+ scrollWidth: 'scrollHeight',
15
+ scrollLeft: 'scrollTop',
16
+
17
+ offsetLeft: 'offsetTop',
18
+ offsetTop: 'offsetLeft',
19
+ offsetHeight: 'offsetWidth',
20
+ offsetWidth: 'offsetHeight',
21
+
22
+ left: 'top',
23
+ right: 'bottom',
24
+ top: 'left',
25
+ bottom: 'right',
26
+
27
+ borderRightStyle: 'borderBottomStyle',
28
+ borderRightWidth: 'borderBottomWidth',
29
+ borderRightColor: 'borderBottomColor'
30
+ };
31
+
32
+ /**
33
+ * Convert a horizontally-oriented property name to a vertical one.
34
+ *
35
+ * @param {string} prop A property name
36
+ * @param {bool} vertical Whether the element is oriented vertically
37
+ * @returns {string} prop, converted appropriately
38
+ */
39
+ function mapProp(prop, vertical) {
40
+ if (Object.prototype.hasOwnProperty.call(verticalPropMap, prop)) {
41
+ return vertical ? verticalPropMap[prop] : prop;
42
+ } else {
43
+ return prop;
44
+ }
45
+ }
46
+
47
+ const isProxy = Symbol("isProxy");
48
+
49
+ /**
50
+ * Returns an appropriately oriented object based on vertical.
51
+ * If vertical is true, attribute getting and setting will be mapped through
52
+ * verticalPropMap, so that e.g. getting the object's .width will give its
53
+ * .height instead.
54
+ * Certain methods of an oriented object will return oriented objects as well.
55
+ * Oriented objects can't be added to the DOM directly since they are Proxy objects
56
+ * and thus fail typechecks. Use domElement to get the actual element for this.
57
+ *
58
+ * @param {object} target The object to be wrapped and oriented
59
+ * @param {bool} vertical Whether the element is oriented vertically
60
+ * @returns {Proxy} An oriented object with attr translation via verticalAttrMap
61
+ * @since 5.0.0
62
+ */
63
+ export default function withOrientation(target, vertical) {
64
+ if (target[isProxy]) {
65
+ return target;
66
+ } else {
67
+ return new Proxy(
68
+ target, {
69
+ get: function(obj, prop, receiver) {
70
+ if (prop === isProxy) {
71
+ return true;
72
+ } else if (prop === 'domElement') {
73
+ return obj;
74
+ } else if (prop === 'style') {
75
+ return withOrientation(obj.style, vertical);
76
+ } else if (prop === 'canvas') {
77
+ return withOrientation(obj.canvas, vertical);
78
+ } else if (prop === 'getBoundingClientRect') {
79
+ return function(...args) {
80
+ return withOrientation(obj.getBoundingClientRect(...args), vertical);
81
+ };
82
+ } else if (prop === 'getContext') {
83
+ return function(...args) {
84
+ return withOrientation(obj.getContext(...args), vertical);
85
+ };
86
+ } else {
87
+ let value = obj[mapProp(prop, vertical)];
88
+ return typeof value == 'function' ? value.bind(obj) : value;
89
+ }
90
+ },
91
+ set: function(obj, prop, value) {
92
+ obj[mapProp(prop, vertical)] = value;
93
+ return true;
94
+ }
95
+ }
96
+ );
97
+ }
98
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Ignores device silence mode when using the `WebAudio` backend.
3
+ *
4
+ * Many mobile devices contain a hardware button to mute the ringtone for incoming
5
+ * calls and messages. Unfortunately, on some platforms like iOS, this also mutes
6
+ * wavesurfer's audio when using the `WebAudio` backend. This function creates a
7
+ * temporary `<audio>` element that makes sure the WebAudio backend keeps playing
8
+ * when muting the device ringer.
9
+ *
10
+ * @since 5.2.0
11
+ */
12
+ export default function ignoreSilenceMode() {
13
+ // Set the src to a short bit of url encoded as a silent mp3
14
+ // NOTE The silence MP3 must be high quality, when web audio sounds are played
15
+ // in parallel the web audio sound is mixed to match the bitrate of the html sound
16
+ // 0.01 seconds of silence VBR220-260 Joint Stereo 859B
17
+ const audioData = "data:audio/mpeg;base64,//uQxAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAACcQCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA//////////////////////////////////////////////////////////////////8AAABhTEFNRTMuMTAwA8MAAAAAAAAAABQgJAUHQQAB9AAAAnGMHkkIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//sQxAADgnABGiAAQBCqgCRMAAgEAH///////////////7+n/9FTuQsQH//////2NG0jWUGlio5gLQTOtIoeR2WX////X4s9Atb/JRVCbBUpeRUq//////////////////9RUi0f2jn/+xDECgPCjAEQAABN4AAANIAAAAQVTEFNRTMuMTAwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQ==";
18
+
19
+ // disable iOS Airplay (setting the attribute in js doesn't work)
20
+ let tmp = document.createElement("div");
21
+ tmp.innerHTML = '<audio x-webkit-airplay="deny"></audio>';
22
+
23
+ let audioSilentMode = tmp.children.item(0);
24
+ audioSilentMode.src = audioData;
25
+ audioSilentMode.preload = "auto";
26
+ audioSilentMode.type = "audio/mpeg";
27
+ audioSilentMode.disableRemotePlayback = true;
28
+
29
+ // play
30
+ audioSilentMode.play();
31
+
32
+ // cleanup
33
+ audioSilentMode.remove();
34
+ tmp.remove();
35
+ }
package/src/wavesurfer.js CHANGED
@@ -69,6 +69,8 @@ import MediaElementWebAudio from './mediaelement-webaudio';
69
69
  * pixels.
70
70
  * @property {boolean} hideScrollbar=false Whether to hide the horizontal
71
71
  * scrollbar when one would normally be shown.
72
+ * @property {boolean} ignoreSilenceMode=false If true, ignores device silence mode
73
+ * when using the `WebAudio` backend.
72
74
  * @property {boolean} interact=true Whether the mouse interaction will be
73
75
  * enabled at initialization. You can switch this parameter at any time later
74
76
  * on.
@@ -115,6 +117,7 @@ import MediaElementWebAudio from './mediaelement-webaudio';
115
117
  * @property {boolean} splitChannels=false Render with separate waveforms for
116
118
  * the channels of the audio
117
119
  * @property {SplitChannelOptions} splitChannelsOptions={} Options for splitChannel rendering
120
+ * @property {boolean} vertical=false Render the waveform vertically instead of horizontally.
118
121
  * @property {string} waveColor='#999' The fill color of the waveform after the
119
122
  * cursor.
120
123
  * @property {object} xhr={} XHR options. For example:
@@ -267,6 +270,7 @@ export default class WaveSurfer extends util.Observer {
267
270
  forceDecode: false,
268
271
  height: 128,
269
272
  hideScrollbar: false,
273
+ ignoreSilenceMode: false,
270
274
  interact: true,
271
275
  loopSelection: true,
272
276
  maxCanvasWidth: 4000,
@@ -293,6 +297,7 @@ export default class WaveSurfer extends util.Observer {
293
297
  filterChannels: [],
294
298
  relativeNormalization: false
295
299
  },
300
+ vertical: false,
296
301
  waveColor: '#999',
297
302
  xhr: {}
298
303
  };
@@ -401,7 +406,11 @@ export default class WaveSurfer extends util.Observer {
401
406
  }
402
407
 
403
408
  if (this.params.rtl === true) {
404
- util.style(this.container, { transform: 'rotateY(180deg)' });
409
+ if (this.params.vertical === true) {
410
+ util.style(this.container, { transform: 'rotateX(180deg)' });
411
+ } else {
412
+ util.style(this.container, { transform: 'rotateY(180deg)' });
413
+ }
405
414
  }
406
415
 
407
416
  if (this.params.backgroundColor) {
@@ -807,6 +816,11 @@ export default class WaveSurfer extends util.Observer {
807
816
  * wavesurfer.play(1, 5);
808
817
  */
809
818
  play(start, end) {
819
+ if (this.params.ignoreSilenceMode) {
820
+ // ignores device hardware silence mode
821
+ util.ignoreSilenceMode();
822
+ }
823
+
810
824
  this.fireEvent('interaction', () => this.play(start, end));
811
825
  return this.backend.play(start, end);
812
826
  }
@@ -1532,19 +1546,21 @@ export default class WaveSurfer extends util.Observer {
1532
1546
  * @param {function} callback The function to call on complete
1533
1547
  */
1534
1548
  decodeArrayBuffer(arraybuffer, callback) {
1535
- this.arraybuffer = arraybuffer;
1536
- this.backend.decodeArrayBuffer(
1537
- arraybuffer,
1538
- data => {
1539
- // Only use the decoded data if we haven't been destroyed or
1540
- // another decode started in the meantime
1541
- if (!this.isDestroyed && this.arraybuffer == arraybuffer) {
1542
- callback(data);
1543
- this.arraybuffer = null;
1544
- }
1545
- },
1546
- () => this.fireEvent('error', 'Error decoding audiobuffer')
1547
- );
1549
+ if (!this.isDestroyed) {
1550
+ this.arraybuffer = arraybuffer;
1551
+ this.backend.decodeArrayBuffer(
1552
+ arraybuffer,
1553
+ data => {
1554
+ // Only use the decoded data if we haven't been destroyed or
1555
+ // another decode started in the meantime
1556
+ if (!this.isDestroyed && this.arraybuffer == arraybuffer) {
1557
+ callback(data);
1558
+ this.arraybuffer = null;
1559
+ }
1560
+ },
1561
+ () => this.fireEvent('error', 'Error decoding audiobuffer')
1562
+ );
1563
+ }
1548
1564
  }
1549
1565
 
1550
1566
  /**
@@ -1604,7 +1620,8 @@ export default class WaveSurfer extends util.Observer {
1604
1620
  }
1605
1621
 
1606
1622
  /**
1607
- * Exports PCM data into a JSON array and opens in a new window.
1623
+ * Exports PCM data into a JSON array and optionally opens in a new window
1624
+ * as valid JSON Blob instance.
1608
1625
  *
1609
1626
  * @param {number} length=1024 The scale in which to export the peaks
1610
1627
  * @param {number} accuracy=10000
@@ -1624,16 +1641,18 @@ export default class WaveSurfer extends util.Observer {
1624
1641
  peaks,
1625
1642
  val => Math.round(val * accuracy) / accuracy
1626
1643
  );
1627
- return new Promise((resolve, reject) => {
1628
- const json = JSON.stringify(arr);
1629
1644
 
1630
- if (!noWindow) {
1631
- window.open(
1632
- 'data:application/json;charset=utf-8,' +
1633
- encodeURIComponent(json)
1645
+ return new Promise((resolve, reject) => {
1646
+ if (!noWindow){
1647
+ const blobJSON = new Blob(
1648
+ [JSON.stringify(arr)],
1649
+ {type: 'application/json;charset=utf-8'}
1634
1650
  );
1651
+ const objURL = URL.createObjectURL(blobJSON);
1652
+ window.open(objURL);
1653
+ URL.revokeObjectURL(objURL);
1635
1654
  }
1636
- resolve(json);
1655
+ resolve(arr);
1637
1656
  });
1638
1657
  }
1639
1658
 
package/src/webaudio.js CHANGED
@@ -261,7 +261,7 @@ export default class WebAudio extends util.Observer {
261
261
 
262
262
  /** @private */
263
263
  removeOnAudioProcess() {
264
- this.scriptNode.onaudioprocess = () => {};
264
+ this.scriptNode.onaudioprocess = null;
265
265
  }
266
266
  /** Create analyser node to perform audio analysis */
267
267
  createAnalyserNode() {
@@ -702,7 +702,16 @@ export default class WebAudio extends util.Observer {
702
702
  this.scheduledPause = null;
703
703
 
704
704
  this.startPosition += this.getPlayedTime();
705
- this.source && this.source.stop(0);
705
+ try {
706
+ this.source && this.source.stop(0);
707
+ } catch (err) {
708
+ // Calling stop can throw the following 2 errors:
709
+ // - RangeError (The value specified for when is negative.)
710
+ // - InvalidStateNode (The node has not been started by calling start().)
711
+ // We can safely ignore both errors, because:
712
+ // - The range is surely correct
713
+ // - The node might not have been started yet, in which case we just want to carry on without causing any trouble.
714
+ }
706
715
 
707
716
  this.setState(PAUSED);
708
717