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.
- package/CHANGES.md +41 -0
- package/README.md +6 -3
- package/dist/plugin/wavesurfer.cursor.js +9 -5
- package/dist/plugin/wavesurfer.cursor.js.map +1 -1
- package/dist/plugin/wavesurfer.cursor.min.js +2 -2
- package/dist/plugin/wavesurfer.cursor.min.js.map +1 -1
- package/dist/plugin/wavesurfer.elan.js +5 -4
- package/dist/plugin/wavesurfer.elan.js.map +1 -1
- package/dist/plugin/wavesurfer.elan.min.js +2 -2
- package/dist/plugin/wavesurfer.elan.min.js.map +1 -1
- package/dist/plugin/wavesurfer.markers.js +31 -12
- package/dist/plugin/wavesurfer.markers.js.map +1 -1
- package/dist/plugin/wavesurfer.markers.min.js +2 -2
- package/dist/plugin/wavesurfer.markers.min.js.map +1 -1
- package/dist/plugin/wavesurfer.mediasession.js +5 -4
- package/dist/plugin/wavesurfer.mediasession.js.map +1 -1
- package/dist/plugin/wavesurfer.mediasession.min.js +2 -2
- package/dist/plugin/wavesurfer.mediasession.min.js.map +1 -1
- package/dist/plugin/wavesurfer.microphone.js +5 -4
- package/dist/plugin/wavesurfer.microphone.js.map +1 -1
- package/dist/plugin/wavesurfer.microphone.min.js +2 -2
- package/dist/plugin/wavesurfer.microphone.min.js.map +1 -1
- package/dist/plugin/wavesurfer.minimap.js +5 -4
- package/dist/plugin/wavesurfer.minimap.js.map +1 -1
- package/dist/plugin/wavesurfer.minimap.min.js +2 -2
- package/dist/plugin/wavesurfer.minimap.min.js.map +1 -1
- package/dist/plugin/wavesurfer.playhead.js +324 -0
- package/dist/plugin/wavesurfer.playhead.js.map +1 -0
- package/dist/plugin/wavesurfer.playhead.min.js +7 -0
- package/dist/plugin/wavesurfer.playhead.min.js.map +1 -0
- package/dist/plugin/wavesurfer.regions.js +141 -86
- package/dist/plugin/wavesurfer.regions.js.map +1 -1
- package/dist/plugin/wavesurfer.regions.min.js +2 -2
- package/dist/plugin/wavesurfer.regions.min.js.map +1 -1
- package/dist/plugin/wavesurfer.spectrogram.js +70 -63
- package/dist/plugin/wavesurfer.spectrogram.js.map +1 -1
- package/dist/plugin/wavesurfer.spectrogram.min.js +2 -2
- package/dist/plugin/wavesurfer.spectrogram.min.js.map +1 -1
- package/dist/plugin/wavesurfer.timeline.js +32 -16
- package/dist/plugin/wavesurfer.timeline.js.map +1 -1
- package/dist/plugin/wavesurfer.timeline.min.js +2 -2
- package/dist/plugin/wavesurfer.timeline.min.js.map +1 -1
- package/dist/wavesurfer-html-init.js +5 -4
- package/dist/wavesurfer-html-init.js.map +1 -1
- package/dist/wavesurfer-html-init.min.js +2 -2
- package/dist/wavesurfer-html-init.min.js.map +1 -1
- package/dist/wavesurfer.js +326 -60
- package/dist/wavesurfer.js.map +1 -1
- package/dist/wavesurfer.min.js +2 -2
- package/dist/wavesurfer.min.js.map +1 -1
- package/package.json +19 -16
- package/src/drawer.canvasentry.js +16 -0
- package/src/drawer.js +30 -21
- package/src/drawer.multicanvas.js +63 -39
- package/src/plugin/cursor/index.js +3 -1
- package/src/plugin/markers/index.js +22 -8
- package/src/plugin/playhead/index.js +226 -0
- package/src/plugin/regions/index.js +28 -11
- package/src/plugin/regions/region.js +116 -75
- package/src/plugin/spectrogram/index.js +65 -61
- package/src/plugin/timeline/index.js +30 -19
- package/src/util/index.js +2 -0
- package/src/util/orientation.js +98 -0
- package/src/util/silence-mode.js +35 -0
- package/src/wavesurfer.js +41 -22
- 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
|
-
|
|
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.
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
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
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|