wavesurfer.js 7.0.0-alpha.4 → 7.0.0-alpha.41

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 (46) hide show
  1. package/README.md +64 -17
  2. package/dist/base-plugin.d.ts +14 -5
  3. package/dist/base-plugin.js +5 -1
  4. package/dist/decoder.d.ts +8 -10
  5. package/dist/decoder.js +37 -44
  6. package/dist/event-emitter.d.ts +16 -10
  7. package/dist/event-emitter.js +38 -16
  8. package/dist/fetcher.js +2 -13
  9. package/dist/legacy-adapter.d.ts +7 -0
  10. package/dist/legacy-adapter.js +15 -0
  11. package/dist/player.d.ts +32 -11
  12. package/dist/player.js +55 -39
  13. package/dist/plugins/envelope.d.ts +57 -0
  14. package/dist/plugins/envelope.js +305 -0
  15. package/dist/plugins/envelope.min.js +1 -0
  16. package/dist/plugins/minimap.d.ts +34 -0
  17. package/dist/plugins/minimap.js +99 -0
  18. package/dist/plugins/minimap.min.js +1 -0
  19. package/dist/plugins/multitrack.d.ts +117 -0
  20. package/dist/plugins/multitrack.js +506 -0
  21. package/dist/plugins/multitrack.min.js +1 -0
  22. package/dist/plugins/record.d.ts +26 -0
  23. package/dist/plugins/record.js +125 -0
  24. package/dist/plugins/record.min.js +1 -0
  25. package/dist/plugins/regions.d.ts +88 -41
  26. package/dist/plugins/regions.js +365 -170
  27. package/dist/plugins/regions.min.js +1 -0
  28. package/dist/plugins/spectrogram.d.ts +24 -0
  29. package/dist/plugins/spectrogram.js +165 -0
  30. package/dist/plugins/timeline.d.ts +41 -0
  31. package/dist/plugins/timeline.js +135 -0
  32. package/dist/plugins/timeline.min.js +1 -0
  33. package/dist/renderer.d.ts +26 -12
  34. package/dist/renderer.js +212 -157
  35. package/dist/timer.d.ts +1 -1
  36. package/dist/wavesurfer.d.ts +136 -0
  37. package/dist/wavesurfer.js +213 -0
  38. package/dist/wavesurfer.min.js +1 -1
  39. package/package.json +49 -24
  40. package/dist/index.d.ts +0 -104
  41. package/dist/index.js +0 -188
  42. package/dist/player-webaudio.d.ts +0 -8
  43. package/dist/player-webaudio.js +0 -32
  44. package/dist/react/useWavesurfer.d.ts +0 -5
  45. package/dist/react/useWavesurfer.js +0 -20
  46. package/dist/wavesurfer.Regions.min.js +0 -1
@@ -0,0 +1,165 @@
1
+ import BasePlugin from '../base-plugin.js';
2
+ class SpectrogramPlugin extends BasePlugin {
3
+ mediaSpectrogramer = null;
4
+ recordedUrl = '';
5
+ static create(options) {
6
+ return new SpectrogramPlugin(options || {});
7
+ }
8
+ loadBlob(data) {
9
+ const blob = new Blob(data, { type: 'audio/webm' });
10
+ this.recordedUrl = URL.createObjectURL(blob);
11
+ this.wavesurfer?.load(this.recordedUrl);
12
+ }
13
+ // Function to process frequency data
14
+ processFrequencyData(dataArray, sampleRate) {
15
+ // Smooth the dataArray using a moving average
16
+ const windowSize = 5; // Adjust the window size as needed
17
+ const smoothedDataArray = new Float32Array(dataArray.length);
18
+ for (let i = 0; i < dataArray.length; i++) {
19
+ let sum = 0;
20
+ let count = 0;
21
+ for (let j = i - Math.floor(windowSize / 2); j <= i + Math.floor(windowSize / 2); j++) {
22
+ if (j >= 0 && j < dataArray.length) {
23
+ sum += dataArray[j];
24
+ count++;
25
+ }
26
+ }
27
+ smoothedDataArray[i] = sum / count;
28
+ }
29
+ // Find the first two peaks (formants) above a certain threshold
30
+ const threshold = -70; // Adjust the threshold as needed
31
+ const formants = [];
32
+ let prevValue = smoothedDataArray[0];
33
+ let prevSlope = 0;
34
+ for (let i = 1; i < smoothedDataArray.length - 1; i++) {
35
+ const slope = smoothedDataArray[i] - prevValue;
36
+ if (slope > 0 && prevSlope < 0 && smoothedDataArray[i] > threshold) {
37
+ formants.push(i);
38
+ if (formants.length >= 2) {
39
+ break;
40
+ }
41
+ }
42
+ prevValue = smoothedDataArray[i];
43
+ prevSlope = slope;
44
+ }
45
+ // Convert the formant indices to frequencies
46
+ const nyquist = 0.5 * sampleRate;
47
+ const freqStep = nyquist / dataArray.length;
48
+ const formantFrequencies = formants.map(index => index * freqStep);
49
+ // Print the formant frequencies
50
+ if (formantFrequencies.length >= 2) {
51
+ return formantFrequencies;
52
+ }
53
+ }
54
+ render(stream) {
55
+ if (!this.container || !this.wavesurfer)
56
+ return () => undefined;
57
+ const canvas = document.createElement('canvas');
58
+ canvas.width = this.container.clientWidth;
59
+ canvas.height = this.container.clientHeight;
60
+ canvas.style.zIndex = '10';
61
+ this.container.appendChild(canvas);
62
+ const canvasCtx = canvas.getContext('2d');
63
+ const audioContext = new AudioContext();
64
+ const source = audioContext.createMediaStreamSource(stream);
65
+ const analyser = audioContext.createAnalyser();
66
+ source.connect(analyser);
67
+ // An array that will store real-time waveform data
68
+ const bufferLength = analyser.frequencyBinCount;
69
+ const dataArray = new Uint8Array(bufferLength);
70
+ // An array that will store FFT data
71
+ const freqArray = new Float32Array(bufferLength);
72
+ let animationId;
73
+ const drawWaveform = () => {
74
+ if (!canvasCtx)
75
+ return;
76
+ canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
77
+ analyser.getByteTimeDomainData(dataArray);
78
+ analyser.getFloatFrequencyData(freqArray);
79
+ this.processFrequencyData(freqArray, audioContext.sampleRate);
80
+ canvasCtx.lineWidth = this.options.lineWidth || 2;
81
+ canvasCtx.strokeStyle = this.options.waveColor || this.wavesurfer?.options.waveColor || '';
82
+ canvasCtx.beginPath();
83
+ const sliceWidth = (canvas.width * 1.0) / bufferLength;
84
+ let x = 0;
85
+ for (let i = 0; i < bufferLength; i++) {
86
+ const v = dataArray[i] / 128.0;
87
+ const y = (v * canvas.height) / 2;
88
+ if (i === 0) {
89
+ canvasCtx.moveTo(x, y);
90
+ }
91
+ else {
92
+ canvasCtx.lineTo(x, y);
93
+ }
94
+ x += sliceWidth;
95
+ }
96
+ canvasCtx.lineTo(canvas.width, canvas.height / 2);
97
+ canvasCtx.stroke();
98
+ animationId = requestAnimationFrame(drawWaveform);
99
+ };
100
+ drawWaveform();
101
+ return () => {
102
+ if (animationId) {
103
+ cancelAnimationFrame(animationId);
104
+ }
105
+ if (source) {
106
+ source.disconnect();
107
+ source.mediaStream.getTracks().forEach((track) => track.stop());
108
+ }
109
+ if (audioContext) {
110
+ audioContext.close();
111
+ }
112
+ canvas?.remove();
113
+ };
114
+ }
115
+ cleanUp() {
116
+ this.stopSpectrograming();
117
+ this.wavesurfer?.empty();
118
+ if (this.recordedUrl) {
119
+ URL.revokeObjectURL(this.recordedUrl);
120
+ this.recordedUrl = '';
121
+ }
122
+ }
123
+ async startSpectrograming() {
124
+ this.cleanUp();
125
+ let stream;
126
+ try {
127
+ stream = await navigator.mediaDevices.getUserMedia({ audio: true });
128
+ }
129
+ catch (err) {
130
+ throw new Error('Error accessing the microphone: ' + err.message);
131
+ }
132
+ const onStop = this.render(stream);
133
+ const mediaSpectrogramer = new MediaSpectrogramer(stream);
134
+ const recordedChunks = [];
135
+ mediaSpectrogramer.addEventListener('dataavailable', (event) => {
136
+ if (event.data.size > 0) {
137
+ recordedChunks.push(event.data);
138
+ }
139
+ });
140
+ mediaSpectrogramer.addEventListener('stop', () => {
141
+ onStop();
142
+ this.loadBlob(recordedChunks);
143
+ this.emit('stopSpectrograming');
144
+ });
145
+ mediaSpectrogramer.start();
146
+ this.emit('startSpectrograming');
147
+ this.mediaSpectrogramer = mediaSpectrogramer;
148
+ }
149
+ isSpectrograming() {
150
+ return this.mediaSpectrogramer?.state === 'recording';
151
+ }
152
+ stopSpectrograming() {
153
+ if (this.isSpectrograming()) {
154
+ this.mediaSpectrogramer?.stop();
155
+ }
156
+ }
157
+ getSpectrogramedUrl() {
158
+ return this.recordedUrl;
159
+ }
160
+ destroy() {
161
+ super.destroy();
162
+ this.cleanUp();
163
+ }
164
+ }
165
+ export default SpectrogramPlugin;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The Timeline plugin adds timestamps and notches under the waveform.
3
+ */
4
+ import BasePlugin, { type WaveSurferPluginParams } from '../base-plugin.js';
5
+ export type TimelinePluginOptions = {
6
+ /** The height of the timeline in pixels, defaults to 20 */
7
+ height?: number;
8
+ /** HTML container for the timeline, defaults to wavesufer's container */
9
+ container?: HTMLElement;
10
+ /** The duration of the timeline in seconds, defaults to wavesurfer's duration */
11
+ duration?: number;
12
+ /** Interval between ticks in seconds */
13
+ timeInterval?: number;
14
+ /** Interval between numeric labels */
15
+ primaryLabelInterval?: number;
16
+ /** Interval between secondary numeric labels */
17
+ secondaryLabelInterval?: number;
18
+ };
19
+ declare const defaultOptions: {
20
+ height: number;
21
+ };
22
+ export type TimelinePluginEvents = {
23
+ ready: [];
24
+ };
25
+ declare class TimelinePlugin extends BasePlugin<TimelinePluginEvents, TimelinePluginOptions> {
26
+ private timelineWrapper;
27
+ protected options: TimelinePluginOptions & typeof defaultOptions;
28
+ constructor(options: TimelinePluginOptions);
29
+ static create(options: TimelinePluginOptions): TimelinePlugin;
30
+ /** Called by wavesurfer, don't call manually */
31
+ init(params: WaveSurferPluginParams): void;
32
+ /** Unmount */
33
+ destroy(): void;
34
+ private initTimelineWrapper;
35
+ private formatTime;
36
+ private defaultTimeInterval;
37
+ private defaultPrimaryLabelInterval;
38
+ private defaultSecondaryLabelInterval;
39
+ private initTimeline;
40
+ }
41
+ export default TimelinePlugin;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The Timeline plugin adds timestamps and notches under the waveform.
3
+ */
4
+ import BasePlugin from '../base-plugin.js';
5
+ const defaultOptions = {
6
+ height: 20,
7
+ };
8
+ class TimelinePlugin extends BasePlugin {
9
+ constructor(options) {
10
+ super(options);
11
+ this.options = Object.assign({}, defaultOptions, options);
12
+ this.timelineWrapper = this.initTimelineWrapper();
13
+ }
14
+ static create(options) {
15
+ return new TimelinePlugin(options);
16
+ }
17
+ /** Called by wavesurfer, don't call manually */
18
+ init(params) {
19
+ super.init(params);
20
+ if (!this.wavesurfer || !this.wrapper) {
21
+ throw Error('WaveSurfer is not initialized');
22
+ }
23
+ const container = this.options.container ?? this.wrapper;
24
+ container.appendChild(this.timelineWrapper);
25
+ if (this.options.duration) {
26
+ this.initTimeline(this.options.duration);
27
+ }
28
+ else {
29
+ this.subscriptions.push(this.wavesurfer.on('decode', (duration) => {
30
+ this.initTimeline(duration);
31
+ }));
32
+ }
33
+ }
34
+ /** Unmount */
35
+ destroy() {
36
+ this.timelineWrapper.remove();
37
+ super.destroy();
38
+ }
39
+ initTimelineWrapper() {
40
+ return document.createElement('div');
41
+ }
42
+ formatTime(seconds) {
43
+ if (seconds / 60 > 1) {
44
+ // calculate minutes and seconds from seconds count
45
+ const minutes = Math.round(seconds / 60);
46
+ seconds = Math.round(seconds % 60);
47
+ const paddedSeconds = `${seconds < 10 ? '0' : ''}${seconds}`;
48
+ return `${minutes}:${paddedSeconds}`;
49
+ }
50
+ const rounded = Math.round(seconds * 1000) / 1000;
51
+ return `${rounded}`;
52
+ }
53
+ // Return how many seconds should be between each notch
54
+ defaultTimeInterval(pxPerSec) {
55
+ if (pxPerSec >= 25) {
56
+ return 1;
57
+ }
58
+ else if (pxPerSec * 5 >= 25) {
59
+ return 5;
60
+ }
61
+ else if (pxPerSec * 15 >= 25) {
62
+ return 15;
63
+ }
64
+ return Math.ceil(0.5 / pxPerSec) * 60;
65
+ }
66
+ // Return the cadence of notches that get labels in the primary color.
67
+ defaultPrimaryLabelInterval(pxPerSec) {
68
+ if (pxPerSec >= 25) {
69
+ return 10;
70
+ }
71
+ else if (pxPerSec * 5 >= 25) {
72
+ return 6;
73
+ }
74
+ else if (pxPerSec * 15 >= 25) {
75
+ return 4;
76
+ }
77
+ return 4;
78
+ }
79
+ // Return the cadence of notches that get labels in the secondary color.
80
+ defaultSecondaryLabelInterval(pxPerSec) {
81
+ if (pxPerSec >= 25) {
82
+ return 5;
83
+ }
84
+ else if (pxPerSec * 5 >= 25) {
85
+ return 2;
86
+ }
87
+ else if (pxPerSec * 15 >= 25) {
88
+ return 2;
89
+ }
90
+ return 2;
91
+ }
92
+ initTimeline(duration) {
93
+ const pxPerSec = this.timelineWrapper.scrollWidth / duration;
94
+ const timeInterval = this.options.timeInterval ?? this.defaultTimeInterval(pxPerSec);
95
+ const primaryLabelInterval = this.options.primaryLabelInterval ?? this.defaultPrimaryLabelInterval(pxPerSec);
96
+ const secondaryLabelInterval = this.options.secondaryLabelInterval ?? this.defaultSecondaryLabelInterval(pxPerSec);
97
+ const timeline = document.createElement('div');
98
+ timeline.setAttribute('style', `
99
+ height: ${this.options.height}px;
100
+ overflow: hidden;
101
+ display: flex;
102
+ justify-content: space-between;
103
+ align-items: flex-end;
104
+ font-size: ${this.options.height / 2}px;
105
+ white-space: nowrap;
106
+ `);
107
+ const notchEl = document.createElement('div');
108
+ notchEl.setAttribute('style', `
109
+ width: 1px;
110
+ height: 50%;
111
+ display: flex;
112
+ flex-direction: column;
113
+ justify-content: flex-end;
114
+ overflow: visible;
115
+ border-left: 1px solid currentColor;
116
+ opacity: 0.25;
117
+ `);
118
+ for (let i = 0; i < duration; i += timeInterval) {
119
+ const notch = notchEl.cloneNode();
120
+ const isPrimary = i % primaryLabelInterval === 0;
121
+ const isSecondary = i % secondaryLabelInterval === 0;
122
+ if (isPrimary || isSecondary) {
123
+ notch.style.height = '100%';
124
+ notch.style.textIndent = '3px';
125
+ notch.textContent = this.formatTime(i);
126
+ if (isPrimary)
127
+ notch.style.opacity = '1';
128
+ }
129
+ timeline.appendChild(notch);
130
+ }
131
+ this.timelineWrapper.appendChild(timeline);
132
+ this.emit('ready');
133
+ }
134
+ }
135
+ export default TimelinePlugin;
@@ -0,0 +1 @@
1
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Timeline=t():e.Timeline=t()}(WaveSurfer,(()=>(()=>{"use strict";var e={284:(e,t,i)=>{i.d(t,{Z:()=>s});var n=i(139);class r extends n.Z{constructor(e){super(),this.subscriptions=[],this.options=e}init(e){this.wavesurfer=e.wavesurfer,this.container=e.container,this.wrapper=e.wrapper}destroy(){this.subscriptions.forEach((e=>e()))}}const s=r},139:(e,t,i)=>{i.d(t,{Z:()=>n});const n=class{constructor(){this.listeners={}}on(e,t){return this.listeners[e]||(this.listeners[e]=new Set),this.listeners[e].add(t),()=>this.un(e,t)}once(e,t){const i=this.on(e,t),n=this.on(e,(()=>{i(),n()}));return i}un(e,t){this.listeners[e]&&(t?this.listeners[e].delete(t):delete this.listeners[e])}unAll(){this.listeners={}}emit(e,...t){this.listeners[e]&&this.listeners[e].forEach((e=>e(...t)))}}}},t={};function i(n){var r=t[n];if(void 0!==r)return r.exports;var s=t[n]={exports:{}};return e[n](s,s.exports,i),s.exports}i.d=(e,t)=>{for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var n={};return(()=>{i.d(n,{default:()=>s});var e=i(284);const t={height:20};class r extends e.Z{constructor(e){super(e),this.options=Object.assign({},t,e),this.timelineWrapper=this.initTimelineWrapper()}static create(e){return new r(e)}init(e){if(super.init(e),!this.wavesurfer||!this.wrapper)throw Error("WaveSurfer is not initialized");(this.options.container??this.wrapper).appendChild(this.timelineWrapper),this.options.duration?this.initTimeline(this.options.duration):this.subscriptions.push(this.wavesurfer.on("decode",(e=>{this.initTimeline(e)})))}destroy(){this.timelineWrapper.remove(),super.destroy()}initTimelineWrapper(){return document.createElement("div")}formatTime(e){return e/60>1?`${Math.round(e/60)}:${(e=Math.round(e%60))<10?"0":""}${e}`:""+Math.round(1e3*e)/1e3}defaultTimeInterval(e){return e>=25?1:5*e>=25?5:15*e>=25?15:60*Math.ceil(.5/e)}defaultPrimaryLabelInterval(e){return e>=25?10:5*e>=25?6:4}defaultSecondaryLabelInterval(e){return e>=25?5:2}initTimeline(e){const t=this.timelineWrapper.scrollWidth/e,i=this.options.timeInterval??this.defaultTimeInterval(t),n=this.options.primaryLabelInterval??this.defaultPrimaryLabelInterval(t),r=this.options.secondaryLabelInterval??this.defaultSecondaryLabelInterval(t),s=document.createElement("div");s.setAttribute("style",`\n height: ${this.options.height}px;\n overflow: hidden;\n display: flex;\n justify-content: space-between;\n align-items: flex-end;\n font-size: ${this.options.height/2}px;\n white-space: nowrap;\n `);const o=document.createElement("div");o.setAttribute("style","\n width: 1px;\n height: 50%;\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n overflow: visible;\n border-left: 1px solid currentColor;\n opacity: 0.25;\n ");for(let t=0;t<e;t+=i){const e=o.cloneNode(),i=t%n==0;(i||t%r==0)&&(e.style.height="100%",e.style.textIndent="3px",e.textContent=this.formatTime(t),i&&(e.style.opacity="1")),s.appendChild(e)}this.timelineWrapper.appendChild(s),this.emit("ready")}}const s=r})(),n.default})()));
@@ -1,36 +1,50 @@
1
1
  import EventEmitter from './event-emitter.js';
2
- type RendererOptions = {
2
+ type RendererRequiredParams = {
3
3
  container: HTMLElement | string | null;
4
+ };
5
+ export type RendererStyleOptions = {
4
6
  height: number;
5
7
  waveColor: string;
6
8
  progressColor: string;
9
+ cursorColor?: string;
10
+ cursorWidth: number;
7
11
  minPxPerSec: number;
12
+ fillParent: boolean;
8
13
  barWidth?: number;
9
14
  barGap?: number;
10
15
  barRadius?: number;
16
+ barHeight?: number;
17
+ hideScrollbar?: boolean;
18
+ autoCenter?: boolean;
11
19
  };
12
20
  type RendererEvents = {
13
- click: {
14
- relativeX: number;
15
- };
21
+ click: [relativeX: number];
16
22
  };
23
+ type ChannelData = Float32Array[] | Array<number[]>;
17
24
  declare class Renderer extends EventEmitter<RendererEvents> {
25
+ private static MAX_CANVAS_WIDTH;
18
26
  private options;
19
27
  private container;
20
28
  private scrollContainer;
21
- private mainCanvas;
22
- private progressCanvas;
23
- private cursor;
24
- private ctx;
29
+ private wrapper;
30
+ private canvasWrapper;
31
+ private progressWrapper;
25
32
  private timeout;
26
- constructor(options: RendererOptions);
33
+ private isScrolling;
34
+ private channelData;
35
+ private duration;
36
+ private resizeObserver;
37
+ constructor(params: RendererRequiredParams, options: RendererStyleOptions);
38
+ private initHtml;
39
+ setOptions(options: RendererStyleOptions): void;
27
40
  getContainer(): HTMLElement;
41
+ getWrapper(): HTMLElement;
28
42
  destroy(): void;
29
43
  private delay;
30
44
  private renderPeaks;
31
- private createProgressMask;
32
- render(channelData: Float32Array[], duration: number): void;
33
- zoom(channelData: Float32Array[], duration: number, minPxPerSec: number): void;
45
+ render(channelData: ChannelData, duration: number): void;
46
+ reRender(): void;
47
+ zoom(minPxPerSec: number): void;
34
48
  renderProgress(progress: number, autoCenter?: boolean): void;
35
49
  }
36
50
  export default Renderer;