wavesurfer.js 7.0.0-beta.8 → 7.0.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/README.md +65 -33
- package/dist/base-plugin.d.ts +5 -2
- package/dist/base-plugin.js +1 -0
- package/dist/decoder.js +17 -6
- package/dist/draggable.js +15 -10
- package/dist/fetcher.d.ts +2 -2
- package/dist/fetcher.js +14 -3
- package/dist/player.d.ts +3 -3
- package/dist/player.js +12 -10
- package/dist/plugins/base-plugin.d.ts +16 -0
- package/dist/plugins/decoder.d.ts +9 -0
- package/dist/plugins/draggable.d.ts +1 -0
- package/dist/plugins/envelope.cjs +1 -0
- package/dist/plugins/envelope.d.ts +12 -4
- package/dist/plugins/envelope.esm.js +1 -0
- package/dist/plugins/envelope.js +29 -9
- package/dist/plugins/envelope.min.js +1 -1
- package/dist/plugins/event-emitter.d.ts +19 -0
- package/dist/plugins/fetcher.d.ts +5 -0
- package/dist/plugins/hover.cjs +1 -0
- package/dist/plugins/hover.d.ts +35 -0
- package/dist/plugins/hover.esm.js +1 -0
- package/dist/plugins/hover.js +101 -0
- package/dist/plugins/hover.min.js +1 -0
- package/dist/plugins/minimap.cjs +1 -0
- package/dist/plugins/minimap.d.ts +2 -2
- package/dist/plugins/minimap.esm.js +1 -0
- package/dist/plugins/minimap.js +8 -13
- package/dist/plugins/minimap.min.js +1 -1
- package/dist/plugins/player.d.ts +45 -0
- package/dist/plugins/plugins/envelope.d.ts +79 -0
- package/dist/plugins/plugins/hover.d.ts +35 -0
- package/dist/plugins/plugins/minimap.d.ts +39 -0
- package/dist/plugins/plugins/record.d.ts +31 -0
- package/dist/plugins/plugins/regions.d.ts +115 -0
- package/dist/plugins/plugins/spectrogram.d.ts +76 -0
- package/dist/plugins/plugins/timeline.d.ts +47 -0
- package/dist/plugins/record.cjs +1 -0
- package/dist/plugins/record.d.ts +9 -4
- package/dist/plugins/record.esm.js +1 -0
- package/dist/plugins/record.js +77 -65
- package/dist/plugins/record.min.js +1 -1
- package/dist/plugins/regions.cjs +1 -0
- package/dist/plugins/regions.d.ts +25 -4
- package/dist/plugins/regions.esm.js +1 -0
- package/dist/plugins/regions.js +60 -60
- package/dist/plugins/regions.min.js +1 -1
- package/dist/plugins/renderer.d.ts +44 -0
- package/dist/plugins/spectrogram.cjs +1 -0
- package/dist/plugins/spectrogram.d.ts +10 -3
- package/dist/plugins/spectrogram.esm.js +1 -0
- package/dist/plugins/spectrogram.js +166 -20
- package/dist/plugins/spectrogram.min.js +1 -1
- package/dist/plugins/timeline.cjs +1 -0
- package/dist/plugins/timeline.d.ts +5 -3
- package/dist/plugins/timeline.esm.js +1 -0
- package/dist/plugins/timeline.js +27 -21
- package/dist/plugins/timeline.min.js +1 -1
- package/dist/plugins/timer.d.ts +11 -0
- package/dist/plugins/wavesurfer.d.ts +156 -0
- package/dist/renderer.js +25 -11
- package/dist/wavesurfer.cjs +1 -0
- package/dist/wavesurfer.d.ts +7 -4
- package/dist/wavesurfer.esm.js +1 -0
- package/dist/wavesurfer.js +74 -42
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +21 -22
- package/dist/plugins/spectrogram-fft.d.ts +0 -9
- package/dist/plugins/spectrogram-fft.js +0 -150
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Envelope is a visual UI for controlling the audio volume and add fade-in and fade-out effects.
|
|
3
|
+
*/
|
|
4
|
+
import BasePlugin, { type BasePluginEvents } from '../base-plugin.js';
|
|
5
|
+
export type EnvelopePluginOptions = {
|
|
6
|
+
fadeInStart?: number;
|
|
7
|
+
fadeInEnd?: number;
|
|
8
|
+
fadeOutStart?: number;
|
|
9
|
+
fadeOutEnd?: number;
|
|
10
|
+
volume?: number;
|
|
11
|
+
lineWidth?: string;
|
|
12
|
+
lineColor?: string;
|
|
13
|
+
dragPointSize?: number;
|
|
14
|
+
dragPointFill?: string;
|
|
15
|
+
dragPointStroke?: string;
|
|
16
|
+
};
|
|
17
|
+
declare const defaultOptions: {
|
|
18
|
+
fadeInStart: number;
|
|
19
|
+
fadeOutEnd: number;
|
|
20
|
+
fadeInEnd: number;
|
|
21
|
+
fadeOutStart: number;
|
|
22
|
+
lineWidth: number;
|
|
23
|
+
lineColor: string;
|
|
24
|
+
dragPointSize: number;
|
|
25
|
+
dragPointFill: string;
|
|
26
|
+
dragPointStroke: string;
|
|
27
|
+
};
|
|
28
|
+
type Options = EnvelopePluginOptions & typeof defaultOptions;
|
|
29
|
+
export type EnvelopePluginEvents = BasePluginEvents & {
|
|
30
|
+
'fade-in-change': [time: number];
|
|
31
|
+
'fade-out-change': [time: number];
|
|
32
|
+
'volume-change': [volume: number];
|
|
33
|
+
};
|
|
34
|
+
declare class EnvelopePlugin extends BasePlugin<EnvelopePluginEvents, EnvelopePluginOptions> {
|
|
35
|
+
protected options: Options;
|
|
36
|
+
private polyline;
|
|
37
|
+
private audioContext;
|
|
38
|
+
private gainNode;
|
|
39
|
+
private volume;
|
|
40
|
+
private isFadingIn;
|
|
41
|
+
private isFadingOut;
|
|
42
|
+
private readonly naturalVolumeExponent;
|
|
43
|
+
constructor(options: EnvelopePluginOptions);
|
|
44
|
+
static create(options: EnvelopePluginOptions): EnvelopePlugin;
|
|
45
|
+
destroy(): void;
|
|
46
|
+
/** Called by wavesurfer, don't call manually */
|
|
47
|
+
onInit(): void;
|
|
48
|
+
private initSvg;
|
|
49
|
+
private renderPolyline;
|
|
50
|
+
private initWebAudio;
|
|
51
|
+
private invertNaturalVolume;
|
|
52
|
+
private naturalVolume;
|
|
53
|
+
private setGainValue;
|
|
54
|
+
private initFadeEffects;
|
|
55
|
+
/** Get the current audio volume */
|
|
56
|
+
getCurrentVolume(): number;
|
|
57
|
+
/**
|
|
58
|
+
* Set the fade-in start time.
|
|
59
|
+
* @param time The time (in seconds) to set the fade-in start time to
|
|
60
|
+
* @param moveFadeInEnd Whether to move the drag point to the new time (default: false)
|
|
61
|
+
*/
|
|
62
|
+
setStartTime(time: number, moveFadeInEnd?: boolean): void;
|
|
63
|
+
/** Set the fade-out end time.
|
|
64
|
+
* @param time The time (in seconds) to set the fade-out end time to
|
|
65
|
+
* @param moveFadeOutStart Whether to move the drag point to the new time (default: false)
|
|
66
|
+
*/
|
|
67
|
+
setEndTime(time: number, moveFadeOutStart?: boolean): void;
|
|
68
|
+
/** Set the fade-in end time.
|
|
69
|
+
* @param time The time (in seconds) to set the fade-in end time to
|
|
70
|
+
*/
|
|
71
|
+
setFadeInEnd(time: number): void;
|
|
72
|
+
/** Set the fade-out start time.
|
|
73
|
+
* @param time The time (in seconds) to set the fade-out start time to
|
|
74
|
+
*/
|
|
75
|
+
setFadeOutStart(time: number): void;
|
|
76
|
+
/** Set the volume of the audio */
|
|
77
|
+
setVolume(volume: number): void;
|
|
78
|
+
}
|
|
79
|
+
export default EnvelopePlugin;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Hover plugin follows the mouse and shows a timestamp
|
|
3
|
+
*/
|
|
4
|
+
import BasePlugin, { type BasePluginEvents } from '../base-plugin.js';
|
|
5
|
+
export type HoverPluginOptions = {
|
|
6
|
+
lineColor?: string;
|
|
7
|
+
lineWidth?: string | number;
|
|
8
|
+
labelColor?: string;
|
|
9
|
+
labelSize?: string | number;
|
|
10
|
+
labelBackground?: string;
|
|
11
|
+
};
|
|
12
|
+
declare const defaultOptions: {
|
|
13
|
+
lineWidth: number;
|
|
14
|
+
labelSize: number;
|
|
15
|
+
};
|
|
16
|
+
export type HoverPluginEvents = BasePluginEvents & {
|
|
17
|
+
hover: [relX: number];
|
|
18
|
+
};
|
|
19
|
+
declare class HoverPlugin extends BasePlugin<HoverPluginEvents, HoverPluginOptions> {
|
|
20
|
+
protected options: HoverPluginOptions & typeof defaultOptions;
|
|
21
|
+
private wrapper;
|
|
22
|
+
private label;
|
|
23
|
+
private unsubscribe;
|
|
24
|
+
constructor(options?: HoverPluginOptions);
|
|
25
|
+
static create(options?: HoverPluginOptions): HoverPlugin;
|
|
26
|
+
private addUnits;
|
|
27
|
+
/** Called by wavesurfer, don't call manually */
|
|
28
|
+
onInit(): void;
|
|
29
|
+
private formatTime;
|
|
30
|
+
private onPointerMove;
|
|
31
|
+
private onPointerLeave;
|
|
32
|
+
/** Unmount */
|
|
33
|
+
destroy(): void;
|
|
34
|
+
}
|
|
35
|
+
export default HoverPlugin;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimap is a tiny copy of the main waveform serving as a navigation tool.
|
|
3
|
+
*/
|
|
4
|
+
import BasePlugin, { type BasePluginEvents } from '../base-plugin.js';
|
|
5
|
+
import { type WaveSurferOptions } from '../wavesurfer.js';
|
|
6
|
+
export type MinimapPluginOptions = {
|
|
7
|
+
overlayColor?: string;
|
|
8
|
+
insertPosition?: InsertPosition;
|
|
9
|
+
} & WaveSurferOptions;
|
|
10
|
+
declare const defaultOptions: {
|
|
11
|
+
height: number;
|
|
12
|
+
overlayColor: string;
|
|
13
|
+
insertPosition: string;
|
|
14
|
+
};
|
|
15
|
+
export type MinimapPluginEvents = BasePluginEvents & {
|
|
16
|
+
ready: [];
|
|
17
|
+
interaction: [];
|
|
18
|
+
};
|
|
19
|
+
declare class MinimapPlugin extends BasePlugin<MinimapPluginEvents, MinimapPluginOptions> {
|
|
20
|
+
protected options: MinimapPluginOptions & typeof defaultOptions;
|
|
21
|
+
private minimapWrapper;
|
|
22
|
+
private miniWavesurfer;
|
|
23
|
+
private overlay;
|
|
24
|
+
private container;
|
|
25
|
+
constructor(options: MinimapPluginOptions);
|
|
26
|
+
static create(options: MinimapPluginOptions): MinimapPlugin;
|
|
27
|
+
/** Called by wavesurfer, don't call manually */
|
|
28
|
+
onInit(): void;
|
|
29
|
+
private initMinimapWrapper;
|
|
30
|
+
private initOverlay;
|
|
31
|
+
private initMinimap;
|
|
32
|
+
private getOverlayWidth;
|
|
33
|
+
private onRedraw;
|
|
34
|
+
private onScroll;
|
|
35
|
+
private initWaveSurferEvents;
|
|
36
|
+
/** Unmount */
|
|
37
|
+
destroy(): void;
|
|
38
|
+
}
|
|
39
|
+
export default MinimapPlugin;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record audio from the microphone, render a waveform and download the audio.
|
|
3
|
+
*/
|
|
4
|
+
import BasePlugin, { type BasePluginEvents } from '../base-plugin.js';
|
|
5
|
+
export type RecordPluginOptions = {
|
|
6
|
+
mimeType?: MediaRecorderOptions['mimeType'];
|
|
7
|
+
audioBitsPerSecond?: MediaRecorderOptions['audioBitsPerSecond'];
|
|
8
|
+
};
|
|
9
|
+
export type RecordPluginEvents = BasePluginEvents & {
|
|
10
|
+
startRecording: [];
|
|
11
|
+
stopRecording: [];
|
|
12
|
+
};
|
|
13
|
+
declare class RecordPlugin extends BasePlugin<RecordPluginEvents, RecordPluginOptions> {
|
|
14
|
+
private mediaRecorder;
|
|
15
|
+
private recordedUrl;
|
|
16
|
+
private savedCursorWidth;
|
|
17
|
+
private savedInteractive;
|
|
18
|
+
static create(options?: RecordPluginOptions): RecordPlugin;
|
|
19
|
+
private preventInteraction;
|
|
20
|
+
private restoreInteraction;
|
|
21
|
+
onInit(): void;
|
|
22
|
+
private loadBlob;
|
|
23
|
+
render(stream: MediaStream): () => void;
|
|
24
|
+
private cleanUp;
|
|
25
|
+
startRecording(): Promise<void>;
|
|
26
|
+
isRecording(): boolean;
|
|
27
|
+
stopRecording(): void;
|
|
28
|
+
getRecordedUrl(): string;
|
|
29
|
+
destroy(): void;
|
|
30
|
+
}
|
|
31
|
+
export default RecordPlugin;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regions are visual overlays on the waveform that can be used to mark segments of audio.
|
|
3
|
+
* Regions can be clicked on, dragged and resized.
|
|
4
|
+
* You can set the color and content of each region, as well as their HTML content.
|
|
5
|
+
*/
|
|
6
|
+
import BasePlugin, { type BasePluginEvents } from '../base-plugin.js';
|
|
7
|
+
import EventEmitter from '../event-emitter.js';
|
|
8
|
+
export type RegionsPluginOptions = undefined;
|
|
9
|
+
export type RegionsPluginEvents = BasePluginEvents & {
|
|
10
|
+
'region-created': [region: Region];
|
|
11
|
+
'region-updated': [region: Region];
|
|
12
|
+
'region-clicked': [region: Region, e: MouseEvent];
|
|
13
|
+
'region-double-clicked': [region: Region, e: MouseEvent];
|
|
14
|
+
};
|
|
15
|
+
export type RegionEvents = {
|
|
16
|
+
/** Before the region is removed */
|
|
17
|
+
remove: [];
|
|
18
|
+
/** When the region's parameters are being updated */
|
|
19
|
+
update: [];
|
|
20
|
+
/** When dragging or resizing is finished */
|
|
21
|
+
'update-end': [];
|
|
22
|
+
/** On play */
|
|
23
|
+
play: [];
|
|
24
|
+
/** On mouse click */
|
|
25
|
+
click: [event: MouseEvent];
|
|
26
|
+
/** Double click */
|
|
27
|
+
dblclick: [event: MouseEvent];
|
|
28
|
+
/** Mouse over */
|
|
29
|
+
over: [event: MouseEvent];
|
|
30
|
+
/** Mouse leave */
|
|
31
|
+
leave: [event: MouseEvent];
|
|
32
|
+
};
|
|
33
|
+
export type RegionParams = {
|
|
34
|
+
/** The id of the region, any string */
|
|
35
|
+
id?: string;
|
|
36
|
+
/** The start position of the region (in seconds) */
|
|
37
|
+
start: number;
|
|
38
|
+
/** The end position of the region (in seconds) */
|
|
39
|
+
end?: number;
|
|
40
|
+
/** Allow/dissallow dragging the region */
|
|
41
|
+
drag?: boolean;
|
|
42
|
+
/** Allow/dissallow resizing the region */
|
|
43
|
+
resize?: boolean;
|
|
44
|
+
/** The color of the region (CSS color) */
|
|
45
|
+
color?: string;
|
|
46
|
+
/** Content string or HTML element */
|
|
47
|
+
content?: string | HTMLElement;
|
|
48
|
+
/** Min length when resizing (in seconds) */
|
|
49
|
+
minLength?: number;
|
|
50
|
+
/** Max length when resizing (in seconds) */
|
|
51
|
+
maxLength?: number;
|
|
52
|
+
};
|
|
53
|
+
declare class Region extends EventEmitter<RegionEvents> {
|
|
54
|
+
private totalDuration;
|
|
55
|
+
element: HTMLElement;
|
|
56
|
+
id: string;
|
|
57
|
+
start: number;
|
|
58
|
+
end: number;
|
|
59
|
+
drag: boolean;
|
|
60
|
+
resize: boolean;
|
|
61
|
+
color: string;
|
|
62
|
+
content?: HTMLElement;
|
|
63
|
+
minLength: number;
|
|
64
|
+
maxLength: number;
|
|
65
|
+
constructor(params: RegionParams, totalDuration: number);
|
|
66
|
+
private initElement;
|
|
67
|
+
private renderPosition;
|
|
68
|
+
private initMouseEvents;
|
|
69
|
+
private onStartMoving;
|
|
70
|
+
private onEndMoving;
|
|
71
|
+
_onUpdate(dx: number, side?: 'start' | 'end'): void;
|
|
72
|
+
private onMove;
|
|
73
|
+
private onResize;
|
|
74
|
+
private onEndResizing;
|
|
75
|
+
_setTotalDuration(totalDuration: number): void;
|
|
76
|
+
/** Play the region from start to end */
|
|
77
|
+
play(): void;
|
|
78
|
+
/** Update the region's options */
|
|
79
|
+
setOptions(options: {
|
|
80
|
+
color?: string;
|
|
81
|
+
drag?: boolean;
|
|
82
|
+
resize?: boolean;
|
|
83
|
+
start?: number;
|
|
84
|
+
end?: number;
|
|
85
|
+
}): void;
|
|
86
|
+
/** Remove the region */
|
|
87
|
+
remove(): void;
|
|
88
|
+
}
|
|
89
|
+
declare class RegionsPlugin extends BasePlugin<RegionsPluginEvents, RegionsPluginOptions> {
|
|
90
|
+
private regions;
|
|
91
|
+
private regionsContainer;
|
|
92
|
+
/** Create an instance of RegionsPlugin */
|
|
93
|
+
constructor(options?: RegionsPluginOptions);
|
|
94
|
+
/** Create an instance of RegionsPlugin */
|
|
95
|
+
static create(options?: RegionsPluginOptions): RegionsPlugin;
|
|
96
|
+
/** Called by wavesurfer, don't call manually */
|
|
97
|
+
onInit(): void;
|
|
98
|
+
private initRegionsContainer;
|
|
99
|
+
/** Get all created regions */
|
|
100
|
+
getRegions(): Region[];
|
|
101
|
+
private avoidOverlapping;
|
|
102
|
+
private saveRegion;
|
|
103
|
+
/** Create a region with given parameters */
|
|
104
|
+
addRegion(options: RegionParams): Region;
|
|
105
|
+
/**
|
|
106
|
+
* Enable creation of regions by dragging on an empty space on the waveform.
|
|
107
|
+
* Returns a function to disable the drag selection.
|
|
108
|
+
*/
|
|
109
|
+
enableDragSelection(options: Omit<RegionParams, 'start' | 'end'>): () => void;
|
|
110
|
+
/** Remove all regions */
|
|
111
|
+
clearRegions(): void;
|
|
112
|
+
/** Destroy the plugin and clean up */
|
|
113
|
+
destroy(): void;
|
|
114
|
+
}
|
|
115
|
+
export default RegionsPlugin;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spectrogram plugin
|
|
3
|
+
*
|
|
4
|
+
* Render a spectrogram visualisation of the audio.
|
|
5
|
+
*
|
|
6
|
+
* @author Pavel Denisov (https://github.com/akreal)
|
|
7
|
+
* @see https://github.com/wavesurfer-js/wavesurfer.js/pull/337
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // ... initialising wavesurfer with the plugin
|
|
11
|
+
* var wavesurfer = WaveSurfer.create({
|
|
12
|
+
* // wavesurfer options ...
|
|
13
|
+
* plugins: [
|
|
14
|
+
* SpectrogramPlugin.create({
|
|
15
|
+
* // plugin options ...
|
|
16
|
+
* })
|
|
17
|
+
* ]
|
|
18
|
+
* });
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Spectrogram plugin for wavesurfer.
|
|
22
|
+
*/
|
|
23
|
+
import BasePlugin, { type BasePluginEvents } from '../base-plugin.js';
|
|
24
|
+
export type SpectrogramPluginOptions = {
|
|
25
|
+
/** Selector of element or element in which to render */
|
|
26
|
+
container: string | HTMLElement;
|
|
27
|
+
/** Number of samples to fetch to FFT. Must be a power of 2. */
|
|
28
|
+
fftSamples?: number;
|
|
29
|
+
/** Height of the spectrogram view in CSS pixels */
|
|
30
|
+
height?: number;
|
|
31
|
+
/** Set to true to display frequency labels. */
|
|
32
|
+
labels?: boolean;
|
|
33
|
+
labelsBackground?: string;
|
|
34
|
+
labelsColor?: string;
|
|
35
|
+
labelsHzColor?: string;
|
|
36
|
+
/** Size of the overlapping window. Must be < fftSamples. Auto deduced from canvas size by default. */
|
|
37
|
+
noverlap?: number;
|
|
38
|
+
/** The window function to be used. */
|
|
39
|
+
windowFunc?: 'bartlett' | 'bartlettHann' | 'blackman' | 'cosine' | 'gauss' | 'hamming' | 'hann' | 'lanczoz' | 'rectangular' | 'triangular';
|
|
40
|
+
/** Some window functions have this extra value. (Between 0 and 1) */
|
|
41
|
+
alpha?: number;
|
|
42
|
+
/** Min frequency to scale spectrogram. */
|
|
43
|
+
frequencyMin?: number;
|
|
44
|
+
/** Max frequency to scale spectrogram. Set this to samplerate/2 to draw whole range of spectrogram. */
|
|
45
|
+
frequencyMax?: number;
|
|
46
|
+
/**
|
|
47
|
+
* A 256 long array of 4-element arrays. Each entry should contain a float between 0 and 1 and specify r, g, b, and alpha.
|
|
48
|
+
* Each entry should contain a float between 0 and 1 and specify r, g, b, and alpha.
|
|
49
|
+
*/
|
|
50
|
+
colorMap?: number[][];
|
|
51
|
+
};
|
|
52
|
+
export type SpectrogramPluginEvents = BasePluginEvents & {
|
|
53
|
+
ready: [];
|
|
54
|
+
click: [relativeX: number];
|
|
55
|
+
};
|
|
56
|
+
declare class SpectrogramPlugin extends BasePlugin<SpectrogramPluginEvents, SpectrogramPluginOptions> {
|
|
57
|
+
static create(options?: SpectrogramPluginOptions): SpectrogramPlugin;
|
|
58
|
+
utils: {
|
|
59
|
+
style: (el: HTMLElement, styles: Record<string, string>) => CSSStyleDeclaration & Record<string, string>;
|
|
60
|
+
};
|
|
61
|
+
constructor(options: SpectrogramPluginOptions);
|
|
62
|
+
onInit(): void;
|
|
63
|
+
destroy(): void;
|
|
64
|
+
createWrapper(): void;
|
|
65
|
+
_wrapperClickHandler(event: any): void;
|
|
66
|
+
createCanvas(): void;
|
|
67
|
+
render(): void;
|
|
68
|
+
drawSpectrogram: (frequenciesData: any) => void;
|
|
69
|
+
getFrequencies(callback: any): void;
|
|
70
|
+
loadFrequenciesData(url: any): Promise<void>;
|
|
71
|
+
freqType(freq: any): string | number;
|
|
72
|
+
unitType(freq: any): "KHz" | "Hz";
|
|
73
|
+
loadLabels(bgFill: any, fontSizeFreq: any, fontSizeUnit: any, fontType: any, textColorFreq: any, textColorUnit: any, textAlign: any, container: any): void;
|
|
74
|
+
resample(oldMatrix: any): Uint8Array[];
|
|
75
|
+
}
|
|
76
|
+
export default SpectrogramPlugin;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Timeline plugin adds timestamps and notches under the waveform.
|
|
3
|
+
*/
|
|
4
|
+
import BasePlugin, { type BasePluginEvents } 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
|
+
/** Pass 'beforebegin' to insert the timeline on top of the waveform */
|
|
11
|
+
insertPosition?: InsertPosition;
|
|
12
|
+
/** The duration of the timeline in seconds, defaults to wavesurfer's duration */
|
|
13
|
+
duration?: number;
|
|
14
|
+
/** Interval between ticks in seconds */
|
|
15
|
+
timeInterval?: number;
|
|
16
|
+
/** Interval between numeric labels */
|
|
17
|
+
primaryLabelInterval?: number;
|
|
18
|
+
/** Interval between secondary numeric labels */
|
|
19
|
+
secondaryLabelInterval?: number;
|
|
20
|
+
/** Custom inline style to apply to the container */
|
|
21
|
+
style?: Partial<CSSStyleDeclaration> | string;
|
|
22
|
+
/** Turn the time into a suitable label for the time. */
|
|
23
|
+
formatTimeCallback?: (seconds: number) => string;
|
|
24
|
+
};
|
|
25
|
+
declare const defaultOptions: {
|
|
26
|
+
height: number;
|
|
27
|
+
formatTimeCallback: (seconds: number) => string;
|
|
28
|
+
};
|
|
29
|
+
export type TimelinePluginEvents = BasePluginEvents & {
|
|
30
|
+
ready: [];
|
|
31
|
+
};
|
|
32
|
+
declare class TimelinePlugin extends BasePlugin<TimelinePluginEvents, TimelinePluginOptions> {
|
|
33
|
+
private timelineWrapper;
|
|
34
|
+
protected options: TimelinePluginOptions & typeof defaultOptions;
|
|
35
|
+
constructor(options?: TimelinePluginOptions);
|
|
36
|
+
static create(options?: TimelinePluginOptions): TimelinePlugin;
|
|
37
|
+
/** Called by wavesurfer, don't call manually */
|
|
38
|
+
onInit(): void;
|
|
39
|
+
/** Unmount */
|
|
40
|
+
destroy(): void;
|
|
41
|
+
private initTimelineWrapper;
|
|
42
|
+
private defaultTimeInterval;
|
|
43
|
+
private defaultPrimaryLabelInterval;
|
|
44
|
+
private defaultSecondaryLabelInterval;
|
|
45
|
+
private initTimeline;
|
|
46
|
+
}
|
|
47
|
+
export default TimelinePlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";function e(e,t,r,s){return new(r||(r=Promise))((function(i,o){function n(e){try{d(s.next(e))}catch(e){o(e)}}function a(e){try{d(s.throw(e))}catch(e){o(e)}}function d(e){var t;e.done?i(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(n,a)}d((s=s.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;class t{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 r=this.on(e,t),s=this.on(e,(()=>{r(),s()}));return r}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)))}}class r extends t{constructor(e){super(),this.subscriptions=[],this.options=e}onInit(){}init(e){this.wavesurfer=e,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((e=>e()))}}const s=["audio/webm","audio/wav","audio/mpeg","audio/mp4","audio/mp3"];class i extends r{constructor(){super(...arguments),this.mediaRecorder=null,this.recordedUrl="",this.savedCursorWidth=1,this.savedInteractive=!0}static create(e){return new i(e||{})}preventInteraction(){this.wavesurfer&&(this.savedCursorWidth=this.wavesurfer.options.cursorWidth||1,this.savedInteractive=this.wavesurfer.options.interact||!0,this.wavesurfer.options.cursorWidth=0,this.wavesurfer.options.interact=!1)}restoreInteraction(){this.wavesurfer&&(this.wavesurfer.options.cursorWidth=this.savedCursorWidth,this.wavesurfer.options.interact=this.savedInteractive)}onInit(){this.preventInteraction()}loadBlob(e,t){var r;const s=new Blob(e,{type:t});this.recordedUrl=URL.createObjectURL(s),this.restoreInteraction(),null===(r=this.wavesurfer)||void 0===r||r.load(this.recordedUrl)}render(e){const t=new AudioContext({sampleRate:8e3}),r=t.createMediaStreamSource(e),s=t.createAnalyser();r.connect(s);const i=s.frequencyBinCount,o=new Float32Array(i),n=i/t.sampleRate;let a;const d=()=>{var e;s.getFloatTimeDomainData(o),null===(e=this.wavesurfer)||void 0===e||e.load("",[o],n),a=requestAnimationFrame(d)};return d(),()=>{a&&cancelAnimationFrame(a),r&&(r.disconnect(),r.mediaStream.getTracks().forEach((e=>e.stop()))),t&&t.close()}}cleanUp(){var e;this.stopRecording(),null===(e=this.wavesurfer)||void 0===e||e.empty(),this.recordedUrl&&(URL.revokeObjectURL(this.recordedUrl),this.recordedUrl="")}startRecording(){return e(this,void 0,void 0,(function*(){let e;this.preventInteraction(),this.cleanUp();try{e=yield navigator.mediaDevices.getUserMedia({audio:!0})}catch(e){throw new Error("Error accessing the microphone: "+e.message)}const t=this.render(e),r=new MediaRecorder(e,{mimeType:this.options.mimeType||s.find((e=>MediaRecorder.isTypeSupported(e))),audioBitsPerSecond:this.options.audioBitsPerSecond}),i=[];r.addEventListener("dataavailable",(e=>{e.data.size>0&&i.push(e.data)})),r.addEventListener("stop",(()=>{t(),this.loadBlob(i,r.mimeType),this.emit("stopRecording")})),r.start(),this.emit("startRecording"),this.mediaRecorder=r}))}isRecording(){var e;return"recording"===(null===(e=this.mediaRecorder)||void 0===e?void 0:e.state)}stopRecording(){var e;this.isRecording()&&(null===(e=this.mediaRecorder)||void 0===e||e.stop())}getRecordedUrl(){return this.recordedUrl}destroy(){super.destroy(),this.cleanUp()}}module.exports=i;
|
package/dist/plugins/record.d.ts
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Record audio from the microphone, render a waveform and download the audio.
|
|
3
3
|
*/
|
|
4
|
-
import BasePlugin from '../base-plugin.js';
|
|
4
|
+
import BasePlugin, { type BasePluginEvents } from '../base-plugin.js';
|
|
5
5
|
export type RecordPluginOptions = {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
mimeType?: MediaRecorderOptions['mimeType'];
|
|
7
|
+
audioBitsPerSecond?: MediaRecorderOptions['audioBitsPerSecond'];
|
|
8
8
|
};
|
|
9
|
-
export type RecordPluginEvents = {
|
|
9
|
+
export type RecordPluginEvents = BasePluginEvents & {
|
|
10
10
|
startRecording: [];
|
|
11
11
|
stopRecording: [];
|
|
12
12
|
};
|
|
13
13
|
declare class RecordPlugin extends BasePlugin<RecordPluginEvents, RecordPluginOptions> {
|
|
14
14
|
private mediaRecorder;
|
|
15
15
|
private recordedUrl;
|
|
16
|
+
private savedCursorWidth;
|
|
17
|
+
private savedInteractive;
|
|
16
18
|
static create(options?: RecordPluginOptions): RecordPlugin;
|
|
19
|
+
private preventInteraction;
|
|
20
|
+
private restoreInteraction;
|
|
21
|
+
onInit(): void;
|
|
17
22
|
private loadBlob;
|
|
18
23
|
render(stream: MediaStream): () => void;
|
|
19
24
|
private cleanUp;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e,t,r,s){return new(r||(r=Promise))((function(i,n){function o(e){try{d(s.next(e))}catch(e){n(e)}}function a(e){try{d(s.throw(e))}catch(e){n(e)}}function d(e){var t;e.done?i(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(o,a)}d((s=s.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;class t{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 r=this.on(e,t),s=this.on(e,(()=>{r(),s()}));return r}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)))}}class r extends t{constructor(e){super(),this.subscriptions=[],this.options=e}onInit(){}init(e){this.wavesurfer=e,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((e=>e()))}}const s=["audio/webm","audio/wav","audio/mpeg","audio/mp4","audio/mp3"];class i extends r{constructor(){super(...arguments),this.mediaRecorder=null,this.recordedUrl="",this.savedCursorWidth=1,this.savedInteractive=!0}static create(e){return new i(e||{})}preventInteraction(){this.wavesurfer&&(this.savedCursorWidth=this.wavesurfer.options.cursorWidth||1,this.savedInteractive=this.wavesurfer.options.interact||!0,this.wavesurfer.options.cursorWidth=0,this.wavesurfer.options.interact=!1)}restoreInteraction(){this.wavesurfer&&(this.wavesurfer.options.cursorWidth=this.savedCursorWidth,this.wavesurfer.options.interact=this.savedInteractive)}onInit(){this.preventInteraction()}loadBlob(e,t){var r;const s=new Blob(e,{type:t});this.recordedUrl=URL.createObjectURL(s),this.restoreInteraction(),null===(r=this.wavesurfer)||void 0===r||r.load(this.recordedUrl)}render(e){const t=new AudioContext({sampleRate:8e3}),r=t.createMediaStreamSource(e),s=t.createAnalyser();r.connect(s);const i=s.frequencyBinCount,n=new Float32Array(i),o=i/t.sampleRate;let a;const d=()=>{var e;s.getFloatTimeDomainData(n),null===(e=this.wavesurfer)||void 0===e||e.load("",[n],o),a=requestAnimationFrame(d)};return d(),()=>{a&&cancelAnimationFrame(a),r&&(r.disconnect(),r.mediaStream.getTracks().forEach((e=>e.stop()))),t&&t.close()}}cleanUp(){var e;this.stopRecording(),null===(e=this.wavesurfer)||void 0===e||e.empty(),this.recordedUrl&&(URL.revokeObjectURL(this.recordedUrl),this.recordedUrl="")}startRecording(){return e(this,void 0,void 0,(function*(){let e;this.preventInteraction(),this.cleanUp();try{e=yield navigator.mediaDevices.getUserMedia({audio:!0})}catch(e){throw new Error("Error accessing the microphone: "+e.message)}const t=this.render(e),r=new MediaRecorder(e,{mimeType:this.options.mimeType||s.find((e=>MediaRecorder.isTypeSupported(e))),audioBitsPerSecond:this.options.audioBitsPerSecond}),i=[];r.addEventListener("dataavailable",(e=>{e.data.size>0&&i.push(e.data)})),r.addEventListener("stop",(()=>{t(),this.loadBlob(i,r.mimeType),this.emit("stopRecording")})),r.start(),this.emit("startRecording"),this.mediaRecorder=r}))}isRecording(){var e;return"recording"===(null===(e=this.mediaRecorder)||void 0===e?void 0:e.state)}stopRecording(){var e;this.isRecording()&&(null===(e=this.mediaRecorder)||void 0===e||e.stop())}getRecordedUrl(){return this.recordedUrl}destroy(){super.destroy(),this.cleanUp()}}export{i as default};
|
package/dist/plugins/record.js
CHANGED
|
@@ -1,62 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Record audio from the microphone, render a waveform and download the audio.
|
|
3
3
|
*/
|
|
4
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
5
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
6
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
7
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
8
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
9
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
10
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
11
|
+
});
|
|
12
|
+
};
|
|
4
13
|
import BasePlugin from '../base-plugin.js';
|
|
14
|
+
const MIME_TYPES = ['audio/webm', 'audio/wav', 'audio/mpeg', 'audio/mp4', 'audio/mp3'];
|
|
15
|
+
const findSupportedMimeType = () => MIME_TYPES.find((mimeType) => MediaRecorder.isTypeSupported(mimeType));
|
|
5
16
|
class RecordPlugin extends BasePlugin {
|
|
6
17
|
constructor() {
|
|
7
18
|
super(...arguments);
|
|
8
19
|
this.mediaRecorder = null;
|
|
9
20
|
this.recordedUrl = '';
|
|
21
|
+
this.savedCursorWidth = 1;
|
|
22
|
+
this.savedInteractive = true;
|
|
10
23
|
}
|
|
11
24
|
static create(options) {
|
|
12
25
|
return new RecordPlugin(options || {});
|
|
13
26
|
}
|
|
14
|
-
|
|
15
|
-
|
|
27
|
+
preventInteraction() {
|
|
28
|
+
if (this.wavesurfer) {
|
|
29
|
+
this.savedCursorWidth = this.wavesurfer.options.cursorWidth || 1;
|
|
30
|
+
this.savedInteractive = this.wavesurfer.options.interact || true;
|
|
31
|
+
this.wavesurfer.options.cursorWidth = 0;
|
|
32
|
+
this.wavesurfer.options.interact = false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
restoreInteraction() {
|
|
36
|
+
if (this.wavesurfer) {
|
|
37
|
+
this.wavesurfer.options.cursorWidth = this.savedCursorWidth;
|
|
38
|
+
this.wavesurfer.options.interact = this.savedInteractive;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
onInit() {
|
|
42
|
+
this.preventInteraction();
|
|
43
|
+
}
|
|
44
|
+
loadBlob(data, type) {
|
|
45
|
+
var _a;
|
|
46
|
+
const blob = new Blob(data, { type });
|
|
16
47
|
this.recordedUrl = URL.createObjectURL(blob);
|
|
17
|
-
this.
|
|
48
|
+
this.restoreInteraction();
|
|
49
|
+
(_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.load(this.recordedUrl);
|
|
18
50
|
}
|
|
19
51
|
render(stream) {
|
|
20
|
-
|
|
21
|
-
return () => undefined;
|
|
22
|
-
const container = this.wavesurfer.getWrapper();
|
|
23
|
-
const canvas = document.createElement('canvas');
|
|
24
|
-
canvas.width = container.clientWidth;
|
|
25
|
-
canvas.height = container.clientHeight;
|
|
26
|
-
canvas.style.zIndex = '10';
|
|
27
|
-
container.appendChild(canvas);
|
|
28
|
-
const canvasCtx = canvas.getContext('2d');
|
|
29
|
-
const audioContext = new AudioContext();
|
|
52
|
+
const audioContext = new AudioContext({ sampleRate: 8000 });
|
|
30
53
|
const source = audioContext.createMediaStreamSource(stream);
|
|
31
54
|
const analyser = audioContext.createAnalyser();
|
|
32
55
|
source.connect(analyser);
|
|
56
|
+
const bufferLength = analyser.frequencyBinCount;
|
|
57
|
+
const dataArray = new Float32Array(bufferLength);
|
|
58
|
+
const sampleDuration = bufferLength / audioContext.sampleRate;
|
|
33
59
|
let animationId;
|
|
34
60
|
const drawWaveform = () => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const bufferLength = analyser.frequencyBinCount;
|
|
39
|
-
const dataArray = new Uint8Array(bufferLength);
|
|
40
|
-
analyser.getByteTimeDomainData(dataArray);
|
|
41
|
-
canvasCtx.lineWidth = this.options.lineWidth || 2;
|
|
42
|
-
const color = this.options.realtimeWaveColor || this.wavesurfer?.options.waveColor || '';
|
|
43
|
-
canvasCtx.strokeStyle = Array.isArray(color) ? color[0] : color;
|
|
44
|
-
canvasCtx.beginPath();
|
|
45
|
-
const sliceWidth = (canvas.width * 1.0) / bufferLength;
|
|
46
|
-
let x = 0;
|
|
47
|
-
for (let i = 0; i < bufferLength; i++) {
|
|
48
|
-
const v = dataArray[i] / 128.0;
|
|
49
|
-
const y = (v * canvas.height) / 2;
|
|
50
|
-
if (i === 0) {
|
|
51
|
-
canvasCtx.moveTo(x, y);
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
canvasCtx.lineTo(x, y);
|
|
55
|
-
}
|
|
56
|
-
x += sliceWidth;
|
|
57
|
-
}
|
|
58
|
-
canvasCtx.lineTo(canvas.width, canvas.height / 2);
|
|
59
|
-
canvasCtx.stroke();
|
|
61
|
+
var _a;
|
|
62
|
+
analyser.getFloatTimeDomainData(dataArray);
|
|
63
|
+
(_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.load('', [dataArray], sampleDuration);
|
|
60
64
|
animationId = requestAnimationFrame(drawWaveform);
|
|
61
65
|
};
|
|
62
66
|
drawWaveform();
|
|
@@ -71,49 +75,57 @@ class RecordPlugin extends BasePlugin {
|
|
|
71
75
|
if (audioContext) {
|
|
72
76
|
audioContext.close();
|
|
73
77
|
}
|
|
74
|
-
canvas?.remove();
|
|
75
78
|
};
|
|
76
79
|
}
|
|
77
80
|
cleanUp() {
|
|
81
|
+
var _a;
|
|
78
82
|
this.stopRecording();
|
|
79
|
-
this.wavesurfer
|
|
83
|
+
(_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.empty();
|
|
80
84
|
if (this.recordedUrl) {
|
|
81
85
|
URL.revokeObjectURL(this.recordedUrl);
|
|
82
86
|
this.recordedUrl = '';
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
|
-
|
|
86
|
-
this
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
stream
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
throw new Error('Error accessing the microphone: ' + err.message);
|
|
93
|
-
}
|
|
94
|
-
const onStop = this.render(stream);
|
|
95
|
-
const mediaRecorder = new MediaRecorder(stream);
|
|
96
|
-
const recordedChunks = [];
|
|
97
|
-
mediaRecorder.addEventListener('dataavailable', (event) => {
|
|
98
|
-
if (event.data.size > 0) {
|
|
99
|
-
recordedChunks.push(event.data);
|
|
89
|
+
startRecording() {
|
|
90
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
+
this.preventInteraction();
|
|
92
|
+
this.cleanUp();
|
|
93
|
+
let stream;
|
|
94
|
+
try {
|
|
95
|
+
stream = yield navigator.mediaDevices.getUserMedia({ audio: true });
|
|
100
96
|
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
throw new Error('Error accessing the microphone: ' + err.message);
|
|
99
|
+
}
|
|
100
|
+
const onStop = this.render(stream);
|
|
101
|
+
const mediaRecorder = new MediaRecorder(stream, {
|
|
102
|
+
mimeType: this.options.mimeType || findSupportedMimeType(),
|
|
103
|
+
audioBitsPerSecond: this.options.audioBitsPerSecond,
|
|
104
|
+
});
|
|
105
|
+
const recordedChunks = [];
|
|
106
|
+
mediaRecorder.addEventListener('dataavailable', (event) => {
|
|
107
|
+
if (event.data.size > 0) {
|
|
108
|
+
recordedChunks.push(event.data);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
mediaRecorder.addEventListener('stop', () => {
|
|
112
|
+
onStop();
|
|
113
|
+
this.loadBlob(recordedChunks, mediaRecorder.mimeType);
|
|
114
|
+
this.emit('stopRecording');
|
|
115
|
+
});
|
|
116
|
+
mediaRecorder.start();
|
|
117
|
+
this.emit('startRecording');
|
|
118
|
+
this.mediaRecorder = mediaRecorder;
|
|
101
119
|
});
|
|
102
|
-
mediaRecorder.addEventListener('stop', () => {
|
|
103
|
-
onStop();
|
|
104
|
-
this.loadBlob(recordedChunks);
|
|
105
|
-
this.emit('stopRecording');
|
|
106
|
-
});
|
|
107
|
-
mediaRecorder.start();
|
|
108
|
-
this.emit('startRecording');
|
|
109
|
-
this.mediaRecorder = mediaRecorder;
|
|
110
120
|
}
|
|
111
121
|
isRecording() {
|
|
112
|
-
|
|
122
|
+
var _a;
|
|
123
|
+
return ((_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.state) === 'recording';
|
|
113
124
|
}
|
|
114
125
|
stopRecording() {
|
|
126
|
+
var _a;
|
|
115
127
|
if (this.isRecording()) {
|
|
116
|
-
this.mediaRecorder
|
|
128
|
+
(_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.stop();
|
|
117
129
|
}
|
|
118
130
|
}
|
|
119
131
|
getRecordedUrl() {
|