wavesurfer.js 7.0.0-beta.9 → 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 +56 -24
- 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 +10 -7
- package/dist/fetcher.d.ts +1 -3
- package/dist/fetcher.js +13 -6
- package/dist/player.d.ts +2 -2
- package/dist/player.js +10 -8
- 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 -0
- 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 -0
- 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 +7 -4
- package/dist/plugins/record.esm.js +1 -0
- package/dist/plugins/record.js +73 -66
- package/dist/plugins/record.min.js +1 -0
- 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 -0
- 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 -0
- 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 -0
- package/dist/plugins/timer.d.ts +11 -0
- package/dist/plugins/wavesurfer.d.ts +156 -0
- package/dist/renderer.js +11 -7
- package/dist/wavesurfer.cjs +1 -0
- package/dist/wavesurfer.d.ts +5 -3
- package/dist/wavesurfer.esm.js +1 -0
- package/dist/wavesurfer.js +62 -41
- package/dist/wavesurfer.min.js +1 -0
- package/package.json +21 -22
- package/dist/plugins/envelope.min.cjs +0 -1
- package/dist/plugins/minimap.min.cjs +0 -1
- package/dist/plugins/record.min.cjs +0 -1
- package/dist/plugins/regions.min.cjs +0 -1
- package/dist/plugins/spectrogram-fft.d.ts +0 -9
- package/dist/plugins/spectrogram-fft.js +0 -150
- package/dist/plugins/spectrogram.min.cjs +0 -1
- package/dist/plugins/timeline.min.cjs +0 -1
- package/dist/wavesurfer.min.cjs +0 -1
|
@@ -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,21 +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
|
-
realtimeWaveColor?: string;
|
|
7
|
-
lineWidth?: number;
|
|
8
6
|
mimeType?: MediaRecorderOptions['mimeType'];
|
|
9
7
|
audioBitsPerSecond?: MediaRecorderOptions['audioBitsPerSecond'];
|
|
10
8
|
};
|
|
11
|
-
export type RecordPluginEvents = {
|
|
9
|
+
export type RecordPluginEvents = BasePluginEvents & {
|
|
12
10
|
startRecording: [];
|
|
13
11
|
stopRecording: [];
|
|
14
12
|
};
|
|
15
13
|
declare class RecordPlugin extends BasePlugin<RecordPluginEvents, RecordPluginOptions> {
|
|
16
14
|
private mediaRecorder;
|
|
17
15
|
private recordedUrl;
|
|
16
|
+
private savedCursorWidth;
|
|
17
|
+
private savedInteractive;
|
|
18
18
|
static create(options?: RecordPluginOptions): RecordPlugin;
|
|
19
|
+
private preventInteraction;
|
|
20
|
+
private restoreInteraction;
|
|
21
|
+
onInit(): void;
|
|
19
22
|
private loadBlob;
|
|
20
23
|
render(stream: MediaStream): () => void;
|
|
21
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,6 +1,15 @@
|
|
|
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';
|
|
5
14
|
const MIME_TYPES = ['audio/webm', 'audio/wav', 'audio/mpeg', 'audio/mp4', 'audio/mp3'];
|
|
6
15
|
const findSupportedMimeType = () => MIME_TYPES.find((mimeType) => MediaRecorder.isTypeSupported(mimeType));
|
|
@@ -9,56 +18,49 @@ class RecordPlugin extends BasePlugin {
|
|
|
9
18
|
super(...arguments);
|
|
10
19
|
this.mediaRecorder = null;
|
|
11
20
|
this.recordedUrl = '';
|
|
21
|
+
this.savedCursorWidth = 1;
|
|
22
|
+
this.savedInteractive = true;
|
|
12
23
|
}
|
|
13
24
|
static create(options) {
|
|
14
25
|
return new RecordPlugin(options || {});
|
|
15
26
|
}
|
|
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
|
+
}
|
|
16
44
|
loadBlob(data, type) {
|
|
45
|
+
var _a;
|
|
17
46
|
const blob = new Blob(data, { type });
|
|
18
47
|
this.recordedUrl = URL.createObjectURL(blob);
|
|
19
|
-
this.
|
|
48
|
+
this.restoreInteraction();
|
|
49
|
+
(_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.load(this.recordedUrl);
|
|
20
50
|
}
|
|
21
51
|
render(stream) {
|
|
22
|
-
|
|
23
|
-
return () => undefined;
|
|
24
|
-
const container = this.wavesurfer.getWrapper();
|
|
25
|
-
const canvas = document.createElement('canvas');
|
|
26
|
-
canvas.width = container.clientWidth;
|
|
27
|
-
canvas.height = container.clientHeight;
|
|
28
|
-
canvas.style.zIndex = '10';
|
|
29
|
-
container.appendChild(canvas);
|
|
30
|
-
const canvasCtx = canvas.getContext('2d');
|
|
31
|
-
const audioContext = new AudioContext();
|
|
52
|
+
const audioContext = new AudioContext({ sampleRate: 8000 });
|
|
32
53
|
const source = audioContext.createMediaStreamSource(stream);
|
|
33
54
|
const analyser = audioContext.createAnalyser();
|
|
34
55
|
source.connect(analyser);
|
|
56
|
+
const bufferLength = analyser.frequencyBinCount;
|
|
57
|
+
const dataArray = new Float32Array(bufferLength);
|
|
58
|
+
const sampleDuration = bufferLength / audioContext.sampleRate;
|
|
35
59
|
let animationId;
|
|
36
60
|
const drawWaveform = () => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const bufferLength = analyser.frequencyBinCount;
|
|
41
|
-
const dataArray = new Uint8Array(bufferLength);
|
|
42
|
-
analyser.getByteTimeDomainData(dataArray);
|
|
43
|
-
canvasCtx.lineWidth = this.options.lineWidth || 2;
|
|
44
|
-
const color = this.options.realtimeWaveColor || this.wavesurfer?.options.waveColor || '';
|
|
45
|
-
canvasCtx.strokeStyle = Array.isArray(color) ? color[0] : color;
|
|
46
|
-
canvasCtx.beginPath();
|
|
47
|
-
const sliceWidth = (canvas.width * 1.0) / bufferLength;
|
|
48
|
-
let x = 0;
|
|
49
|
-
for (let i = 0; i < bufferLength; i++) {
|
|
50
|
-
const v = dataArray[i] / 128.0;
|
|
51
|
-
const y = (v * canvas.height) / 2;
|
|
52
|
-
if (i === 0) {
|
|
53
|
-
canvasCtx.moveTo(x, y);
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
canvasCtx.lineTo(x, y);
|
|
57
|
-
}
|
|
58
|
-
x += sliceWidth;
|
|
59
|
-
}
|
|
60
|
-
canvasCtx.lineTo(canvas.width, canvas.height / 2);
|
|
61
|
-
canvasCtx.stroke();
|
|
61
|
+
var _a;
|
|
62
|
+
analyser.getFloatTimeDomainData(dataArray);
|
|
63
|
+
(_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.load('', [dataArray], sampleDuration);
|
|
62
64
|
animationId = requestAnimationFrame(drawWaveform);
|
|
63
65
|
};
|
|
64
66
|
drawWaveform();
|
|
@@ -73,52 +75,57 @@ class RecordPlugin extends BasePlugin {
|
|
|
73
75
|
if (audioContext) {
|
|
74
76
|
audioContext.close();
|
|
75
77
|
}
|
|
76
|
-
canvas?.remove();
|
|
77
78
|
};
|
|
78
79
|
}
|
|
79
80
|
cleanUp() {
|
|
81
|
+
var _a;
|
|
80
82
|
this.stopRecording();
|
|
81
|
-
this.wavesurfer
|
|
83
|
+
(_a = this.wavesurfer) === null || _a === void 0 ? void 0 : _a.empty();
|
|
82
84
|
if (this.recordedUrl) {
|
|
83
85
|
URL.revokeObjectURL(this.recordedUrl);
|
|
84
86
|
this.recordedUrl = '';
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
|
-
|
|
88
|
-
this
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
stream
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
throw new Error('Error accessing the microphone: ' + err.message);
|
|
95
|
-
}
|
|
96
|
-
const onStop = this.render(stream);
|
|
97
|
-
const mediaRecorder = new MediaRecorder(stream, {
|
|
98
|
-
mimeType: this.options.mimeType || findSupportedMimeType(),
|
|
99
|
-
audioBitsPerSecond: this.options.audioBitsPerSecond,
|
|
100
|
-
});
|
|
101
|
-
const recordedChunks = [];
|
|
102
|
-
mediaRecorder.addEventListener('dataavailable', (event) => {
|
|
103
|
-
if (event.data.size > 0) {
|
|
104
|
-
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 });
|
|
105
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;
|
|
106
119
|
});
|
|
107
|
-
mediaRecorder.addEventListener('stop', () => {
|
|
108
|
-
onStop();
|
|
109
|
-
this.loadBlob(recordedChunks, mediaRecorder.mimeType);
|
|
110
|
-
this.emit('stopRecording');
|
|
111
|
-
});
|
|
112
|
-
mediaRecorder.start();
|
|
113
|
-
this.emit('startRecording');
|
|
114
|
-
this.mediaRecorder = mediaRecorder;
|
|
115
120
|
}
|
|
116
121
|
isRecording() {
|
|
117
|
-
|
|
122
|
+
var _a;
|
|
123
|
+
return ((_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.state) === 'recording';
|
|
118
124
|
}
|
|
119
125
|
stopRecording() {
|
|
126
|
+
var _a;
|
|
120
127
|
if (this.isRecording()) {
|
|
121
|
-
this.mediaRecorder
|
|
128
|
+
(_a = this.mediaRecorder) === null || _a === void 0 ? void 0 : _a.stop();
|
|
122
129
|
}
|
|
123
130
|
}
|
|
124
131
|
getRecordedUrl() {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):((e="undefined"!=typeof globalThis?globalThis:e||self).WaveSurfer=e.WaveSurfer||{},e.WaveSurfer.Record=t())}(this,(function(){"use strict";function e(e,t,r,i){return new(r||(r=Promise))((function(s,n){function o(e){try{d(i.next(e))}catch(e){n(e)}}function a(e){try{d(i.throw(e))}catch(e){n(e)}}function d(e){var t;e.done?s(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(o,a)}d((i=i.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),i=this.on(e,(()=>{r(),i()}));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 i=["audio/webm","audio/wav","audio/mpeg","audio/mp4","audio/mp3"];class s extends r{constructor(){super(...arguments),this.mediaRecorder=null,this.recordedUrl="",this.savedCursorWidth=1,this.savedInteractive=!0}static create(e){return new s(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 i=new Blob(e,{type:t});this.recordedUrl=URL.createObjectURL(i),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),i=t.createAnalyser();r.connect(i);const s=i.frequencyBinCount,n=new Float32Array(s),o=s/t.sampleRate;let a;const d=()=>{var e;i.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||i.find((e=>MediaRecorder.isTypeSupported(e))),audioBitsPerSecond:this.options.audioBitsPerSecond}),s=[];r.addEventListener("dataavailable",(e=>{e.data.size>0&&s.push(e.data)})),r.addEventListener("stop",(()=>{t(),this.loadBlob(s,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()}}return s}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";class t{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const i=this.on(t,e),n=this.on(t,(()=>{i(),n()}));return i}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}}class e extends t{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t()))}}function i(t,e,i,n,s=5){let r=()=>{};if(!t)return r;const o=o=>{if(2===o.button)return;o.preventDefault(),o.stopPropagation();let l=o.clientX,a=o.clientY,h=!1;const d=n=>{n.preventDefault(),n.stopPropagation();const r=n.clientX,o=n.clientY;if(h||Math.abs(r-l)>=s||Math.abs(o-a)>=s){const{left:n,top:s}=t.getBoundingClientRect();h||(h=!0,null==i||i(l-n,a-s)),e(r-l,o-a,r-n,o-s),l=r,a=o}},u=t=>{h&&(t.preventDefault(),t.stopPropagation())},c=()=>{h&&(null==n||n()),r()};document.addEventListener("pointermove",d),document.addEventListener("pointerup",c),document.addEventListener("pointerleave",c),document.addEventListener("click",u,!0),r=()=>{document.removeEventListener("pointermove",d),document.removeEventListener("pointerup",c),document.removeEventListener("pointerleave",c),setTimeout((()=>{document.removeEventListener("click",u,!0)}),10)}};return t.addEventListener("pointerdown",o),()=>{r(),t.removeEventListener("pointerdown",o)}}class n extends t{constructor(t,e){var i,n,s,r,o,l;super(),this.totalDuration=e,this.minLength=0,this.maxLength=1/0,this.id=t.id||`region-${Math.random().toString(32).slice(2)}`,this.start=t.start,this.end=null!==(i=t.end)&&void 0!==i?i:t.start,this.drag=null===(n=t.drag)||void 0===n||n,this.resize=null===(s=t.resize)||void 0===s||s,this.color=null!==(r=t.color)&&void 0!==r?r:"rgba(0, 0, 0, 0.1)",this.minLength=null!==(o=t.minLength)&&void 0!==o?o:this.minLength,this.maxLength=null!==(l=t.maxLength)&&void 0!==l?l:this.maxLength,this.element=this.initElement(t.content),this.renderPosition(),this.initMouseEvents()}initElement(t){const e=document.createElement("div"),i=this.start===this.end;if(e.setAttribute("part",`${i?"marker":"region"} ${this.id}`),e.setAttribute("style",`\n position: absolute;\n height: 100%;\n background-color: ${i?"none":this.color};\n border-left: ${i?"2px solid "+this.color:"none"};\n border-radius: 2px;\n box-sizing: border-box;\n transition: background-color 0.2s ease;\n cursor: ${this.drag?"grab":"default"};\n pointer-events: all;\n `),t&&("string"==typeof t?(this.content=document.createElement("div"),this.content.style.padding=`0.2em ${i?.2:.4}em`,this.content.textContent=t):this.content=t,this.content.setAttribute("part","region-content"),e.appendChild(this.content)),!i){const t=document.createElement("div");t.setAttribute("data-resize","left"),t.setAttribute("style",`\n position: absolute;\n z-index: 2;\n width: 6px;\n height: 100%;\n top: 0;\n left: 0;\n border-left: 2px solid rgba(0, 0, 0, 0.5);\n border-radius: 2px 0 0 2px;\n cursor: ${this.resize?"ew-resize":"default"};\n word-break: keep-all;\n `),t.setAttribute("part","region-handle region-handle-left");const i=t.cloneNode();i.setAttribute("data-resize","right"),i.style.left="",i.style.right="0",i.style.borderRight=i.style.borderLeft,i.style.borderLeft="",i.style.borderRadius="0 2px 2px 0",i.setAttribute("part","region-handle region-handle-right"),e.appendChild(t),e.appendChild(i)}return e}renderPosition(){const t=this.start/this.totalDuration,e=(this.totalDuration-this.end)/this.totalDuration;this.element.style.left=100*t+"%",this.element.style.right=100*e+"%"}initMouseEvents(){const{element:t}=this;if(!t)return;t.addEventListener("click",(t=>this.emit("click",t))),t.addEventListener("mouseenter",(t=>this.emit("over",t))),t.addEventListener("mouseleave",(t=>this.emit("leave",t))),t.addEventListener("dblclick",(t=>this.emit("dblclick",t))),i(t,(t=>this.onMove(t)),(()=>this.onStartMoving()),(()=>this.onEndMoving()));i(t.querySelector('[data-resize="left"]'),(t=>this.onResize(t,"start")),(()=>null),(()=>this.onEndResizing()),1),i(t.querySelector('[data-resize="right"]'),(t=>this.onResize(t,"end")),(()=>null),(()=>this.onEndResizing()),1)}onStartMoving(){this.drag&&(this.element.style.cursor="grabbing")}onEndMoving(){this.drag&&(this.element.style.cursor="grab",this.emit("update-end"))}_onUpdate(t,e){if(!this.element.parentElement)return;const i=t/this.element.parentElement.clientWidth*this.totalDuration,n=e&&"start"!==e?this.start:this.start+i,s=e&&"end"!==e?this.end:this.end+i,r=s-n;n>0&&s<this.totalDuration&&n<=s&&r>=this.minLength&&r<=this.maxLength&&(this.start=n,this.end=s,this.renderPosition(),this.emit("update"))}onMove(t){this.drag&&this._onUpdate(t)}onResize(t,e){this.resize&&this._onUpdate(t,e)}onEndResizing(){this.resize&&this.emit("update-end")}_setTotalDuration(t){this.totalDuration=t,this.renderPosition()}play(){this.emit("play")}setOptions(t){var e,i;t.color&&(this.color=t.color,this.element.style.backgroundColor=this.color),void 0!==t.drag&&(this.drag=t.drag,this.element.style.cursor=this.drag?"grab":"default"),void 0!==t.resize&&(this.resize=t.resize,this.element.querySelectorAll("[data-resize]").forEach((t=>{t.style.cursor=this.resize?"ew-resize":"default"}))),void 0===t.start&&void 0===t.end||(this.start=null!==(e=t.start)&&void 0!==e?e:this.start,this.end=null!==(i=t.end)&&void 0!==i?i:this.end,this.renderPosition())}remove(){this.emit("remove"),this.element.remove(),this.element=null}}class s extends e{constructor(t){super(t),this.regions=[],this.regionsContainer=this.initRegionsContainer()}static create(t){return new s(t)}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");this.wavesurfer.getWrapper().appendChild(this.regionsContainer)}initRegionsContainer(){const t=document.createElement("div");return t.setAttribute("style","\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 3;\n pointer-events: none;\n "),t}getRegions(){return this.regions}avoidOverlapping(t){if(!t.content)return;const e=t.content,i=e.getBoundingClientRect().left,n=t.element.scrollWidth,s=this.regions.filter((e=>{if(e===t||!e.content)return!1;const s=e.content.getBoundingClientRect().left,r=e.element.scrollWidth;return i<s+r&&s<i+n})).map((t=>{var e;return(null===(e=t.content)||void 0===e?void 0:e.getBoundingClientRect().height)||0})).reduce(((t,e)=>t+e),0);e.style.marginTop=`${s}px`}saveRegion(t){this.regionsContainer.appendChild(t.element),this.avoidOverlapping(t),this.regions.push(t),this.emit("region-created",t);const e=[t.on("update-end",(()=>{this.avoidOverlapping(t),this.emit("region-updated",t)})),t.on("play",(()=>{var e,i;null===(e=this.wavesurfer)||void 0===e||e.play(),null===(i=this.wavesurfer)||void 0===i||i.setTime(t.start)})),t.on("click",(e=>{this.emit("region-clicked",t,e)})),t.on("dblclick",(e=>{this.emit("region-double-clicked",t,e)})),t.once("remove",(()=>{e.forEach((t=>t())),this.regions=this.regions.filter((e=>e!==t))}))];this.subscriptions.push(...e)}addRegion(t){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");const e=this.wavesurfer.getDuration(),i=new n(t,e);return e?this.saveRegion(i):this.subscriptions.push(this.wavesurfer.once("ready",(t=>{i._setTotalDuration(t),this.saveRegion(i)}))),i}enableDragSelection(t){var e,s;const r=null===(s=null===(e=this.wavesurfer)||void 0===e?void 0:e.getWrapper())||void 0===s?void 0:s.querySelector("div");if(!r)return()=>{};let o=null,l=0;return i(r,((t,e,i)=>{o&&o._onUpdate(t,i>l?"end":"start")}),(e=>{if(l=e,!this.wavesurfer)return;const i=this.wavesurfer.getDuration(),s=this.wavesurfer.getWrapper().clientWidth,r=e/s*i,a=(e+5)/s*i;o=new n(Object.assign(Object.assign({},t),{start:r,end:a}),i),this.regionsContainer.appendChild(o.element)}),(()=>{o&&(this.saveRegion(o),o=null)}))}clearRegions(){this.regions.forEach((t=>t.remove()))}destroy(){this.clearRegions(),super.destroy()}}module.exports=s;
|
|
@@ -3,35 +3,54 @@
|
|
|
3
3
|
* Regions can be clicked on, dragged and resized.
|
|
4
4
|
* You can set the color and content of each region, as well as their HTML content.
|
|
5
5
|
*/
|
|
6
|
-
import BasePlugin from '../base-plugin.js';
|
|
6
|
+
import BasePlugin, { type BasePluginEvents } from '../base-plugin.js';
|
|
7
7
|
import EventEmitter from '../event-emitter.js';
|
|
8
8
|
export type RegionsPluginOptions = undefined;
|
|
9
|
-
export type RegionsPluginEvents = {
|
|
9
|
+
export type RegionsPluginEvents = BasePluginEvents & {
|
|
10
10
|
'region-created': [region: Region];
|
|
11
11
|
'region-updated': [region: Region];
|
|
12
12
|
'region-clicked': [region: Region, e: MouseEvent];
|
|
13
13
|
'region-double-clicked': [region: Region, e: MouseEvent];
|
|
14
14
|
};
|
|
15
15
|
export type RegionEvents = {
|
|
16
|
+
/** Before the region is removed */
|
|
16
17
|
remove: [];
|
|
18
|
+
/** When the region's parameters are being updated */
|
|
17
19
|
update: [];
|
|
20
|
+
/** When dragging or resizing is finished */
|
|
18
21
|
'update-end': [];
|
|
22
|
+
/** On play */
|
|
19
23
|
play: [];
|
|
24
|
+
/** On mouse click */
|
|
20
25
|
click: [event: MouseEvent];
|
|
26
|
+
/** Double click */
|
|
21
27
|
dblclick: [event: MouseEvent];
|
|
28
|
+
/** Mouse over */
|
|
22
29
|
over: [event: MouseEvent];
|
|
30
|
+
/** Mouse leave */
|
|
23
31
|
leave: [event: MouseEvent];
|
|
24
32
|
};
|
|
25
33
|
export type RegionParams = {
|
|
34
|
+
/** The id of the region, any string */
|
|
26
35
|
id?: string;
|
|
36
|
+
/** The start position of the region (in seconds) */
|
|
27
37
|
start: number;
|
|
38
|
+
/** The end position of the region (in seconds) */
|
|
28
39
|
end?: number;
|
|
40
|
+
/** Allow/dissallow dragging the region */
|
|
29
41
|
drag?: boolean;
|
|
42
|
+
/** Allow/dissallow resizing the region */
|
|
30
43
|
resize?: boolean;
|
|
44
|
+
/** The color of the region (CSS color) */
|
|
31
45
|
color?: string;
|
|
46
|
+
/** Content string or HTML element */
|
|
32
47
|
content?: string | HTMLElement;
|
|
48
|
+
/** Min length when resizing (in seconds) */
|
|
49
|
+
minLength?: number;
|
|
50
|
+
/** Max length when resizing (in seconds) */
|
|
51
|
+
maxLength?: number;
|
|
33
52
|
};
|
|
34
|
-
|
|
53
|
+
declare class Region extends EventEmitter<RegionEvents> {
|
|
35
54
|
private totalDuration;
|
|
36
55
|
element: HTMLElement;
|
|
37
56
|
id: string;
|
|
@@ -41,13 +60,15 @@ export declare class Region extends EventEmitter<RegionEvents> {
|
|
|
41
60
|
resize: boolean;
|
|
42
61
|
color: string;
|
|
43
62
|
content?: HTMLElement;
|
|
63
|
+
minLength: number;
|
|
64
|
+
maxLength: number;
|
|
44
65
|
constructor(params: RegionParams, totalDuration: number);
|
|
45
66
|
private initElement;
|
|
46
67
|
private renderPosition;
|
|
47
68
|
private initMouseEvents;
|
|
48
69
|
private onStartMoving;
|
|
49
70
|
private onEndMoving;
|
|
50
|
-
|
|
71
|
+
_onUpdate(dx: number, side?: 'start' | 'end'): void;
|
|
51
72
|
private onMove;
|
|
52
73
|
private onResize;
|
|
53
74
|
private onEndResizing;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class t{constructor(){this.listeners={}}on(t,e){return this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),()=>this.un(t,e)}once(t,e){const i=this.on(t,e),n=this.on(t,(()=>{i(),n()}));return i}un(t,e){this.listeners[t]&&(e?this.listeners[t].delete(e):delete this.listeners[t])}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}}class e extends t{constructor(t){super(),this.subscriptions=[],this.options=t}onInit(){}init(t){this.wavesurfer=t,this.onInit()}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t()))}}function i(t,e,i,n,s=5){let r=()=>{};if(!t)return r;const o=o=>{if(2===o.button)return;o.preventDefault(),o.stopPropagation();let l=o.clientX,a=o.clientY,h=!1;const d=n=>{n.preventDefault(),n.stopPropagation();const r=n.clientX,o=n.clientY;if(h||Math.abs(r-l)>=s||Math.abs(o-a)>=s){const{left:n,top:s}=t.getBoundingClientRect();h||(h=!0,null==i||i(l-n,a-s)),e(r-l,o-a,r-n,o-s),l=r,a=o}},u=t=>{h&&(t.preventDefault(),t.stopPropagation())},c=()=>{h&&(null==n||n()),r()};document.addEventListener("pointermove",d),document.addEventListener("pointerup",c),document.addEventListener("pointerleave",c),document.addEventListener("click",u,!0),r=()=>{document.removeEventListener("pointermove",d),document.removeEventListener("pointerup",c),document.removeEventListener("pointerleave",c),setTimeout((()=>{document.removeEventListener("click",u,!0)}),10)}};return t.addEventListener("pointerdown",o),()=>{r(),t.removeEventListener("pointerdown",o)}}class n extends t{constructor(t,e){var i,n,s,r,o,l;super(),this.totalDuration=e,this.minLength=0,this.maxLength=1/0,this.id=t.id||`region-${Math.random().toString(32).slice(2)}`,this.start=t.start,this.end=null!==(i=t.end)&&void 0!==i?i:t.start,this.drag=null===(n=t.drag)||void 0===n||n,this.resize=null===(s=t.resize)||void 0===s||s,this.color=null!==(r=t.color)&&void 0!==r?r:"rgba(0, 0, 0, 0.1)",this.minLength=null!==(o=t.minLength)&&void 0!==o?o:this.minLength,this.maxLength=null!==(l=t.maxLength)&&void 0!==l?l:this.maxLength,this.element=this.initElement(t.content),this.renderPosition(),this.initMouseEvents()}initElement(t){const e=document.createElement("div"),i=this.start===this.end;if(e.setAttribute("part",`${i?"marker":"region"} ${this.id}`),e.setAttribute("style",`\n position: absolute;\n height: 100%;\n background-color: ${i?"none":this.color};\n border-left: ${i?"2px solid "+this.color:"none"};\n border-radius: 2px;\n box-sizing: border-box;\n transition: background-color 0.2s ease;\n cursor: ${this.drag?"grab":"default"};\n pointer-events: all;\n `),t&&("string"==typeof t?(this.content=document.createElement("div"),this.content.style.padding=`0.2em ${i?.2:.4}em`,this.content.textContent=t):this.content=t,this.content.setAttribute("part","region-content"),e.appendChild(this.content)),!i){const t=document.createElement("div");t.setAttribute("data-resize","left"),t.setAttribute("style",`\n position: absolute;\n z-index: 2;\n width: 6px;\n height: 100%;\n top: 0;\n left: 0;\n border-left: 2px solid rgba(0, 0, 0, 0.5);\n border-radius: 2px 0 0 2px;\n cursor: ${this.resize?"ew-resize":"default"};\n word-break: keep-all;\n `),t.setAttribute("part","region-handle region-handle-left");const i=t.cloneNode();i.setAttribute("data-resize","right"),i.style.left="",i.style.right="0",i.style.borderRight=i.style.borderLeft,i.style.borderLeft="",i.style.borderRadius="0 2px 2px 0",i.setAttribute("part","region-handle region-handle-right"),e.appendChild(t),e.appendChild(i)}return e}renderPosition(){const t=this.start/this.totalDuration,e=(this.totalDuration-this.end)/this.totalDuration;this.element.style.left=100*t+"%",this.element.style.right=100*e+"%"}initMouseEvents(){const{element:t}=this;if(!t)return;t.addEventListener("click",(t=>this.emit("click",t))),t.addEventListener("mouseenter",(t=>this.emit("over",t))),t.addEventListener("mouseleave",(t=>this.emit("leave",t))),t.addEventListener("dblclick",(t=>this.emit("dblclick",t))),i(t,(t=>this.onMove(t)),(()=>this.onStartMoving()),(()=>this.onEndMoving()));i(t.querySelector('[data-resize="left"]'),(t=>this.onResize(t,"start")),(()=>null),(()=>this.onEndResizing()),1),i(t.querySelector('[data-resize="right"]'),(t=>this.onResize(t,"end")),(()=>null),(()=>this.onEndResizing()),1)}onStartMoving(){this.drag&&(this.element.style.cursor="grabbing")}onEndMoving(){this.drag&&(this.element.style.cursor="grab",this.emit("update-end"))}_onUpdate(t,e){if(!this.element.parentElement)return;const i=t/this.element.parentElement.clientWidth*this.totalDuration,n=e&&"start"!==e?this.start:this.start+i,s=e&&"end"!==e?this.end:this.end+i,r=s-n;n>0&&s<this.totalDuration&&n<=s&&r>=this.minLength&&r<=this.maxLength&&(this.start=n,this.end=s,this.renderPosition(),this.emit("update"))}onMove(t){this.drag&&this._onUpdate(t)}onResize(t,e){this.resize&&this._onUpdate(t,e)}onEndResizing(){this.resize&&this.emit("update-end")}_setTotalDuration(t){this.totalDuration=t,this.renderPosition()}play(){this.emit("play")}setOptions(t){var e,i;t.color&&(this.color=t.color,this.element.style.backgroundColor=this.color),void 0!==t.drag&&(this.drag=t.drag,this.element.style.cursor=this.drag?"grab":"default"),void 0!==t.resize&&(this.resize=t.resize,this.element.querySelectorAll("[data-resize]").forEach((t=>{t.style.cursor=this.resize?"ew-resize":"default"}))),void 0===t.start&&void 0===t.end||(this.start=null!==(e=t.start)&&void 0!==e?e:this.start,this.end=null!==(i=t.end)&&void 0!==i?i:this.end,this.renderPosition())}remove(){this.emit("remove"),this.element.remove(),this.element=null}}class s extends e{constructor(t){super(t),this.regions=[],this.regionsContainer=this.initRegionsContainer()}static create(t){return new s(t)}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");this.wavesurfer.getWrapper().appendChild(this.regionsContainer)}initRegionsContainer(){const t=document.createElement("div");return t.setAttribute("style","\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 3;\n pointer-events: none;\n "),t}getRegions(){return this.regions}avoidOverlapping(t){if(!t.content)return;const e=t.content,i=e.getBoundingClientRect().left,n=t.element.scrollWidth,s=this.regions.filter((e=>{if(e===t||!e.content)return!1;const s=e.content.getBoundingClientRect().left,r=e.element.scrollWidth;return i<s+r&&s<i+n})).map((t=>{var e;return(null===(e=t.content)||void 0===e?void 0:e.getBoundingClientRect().height)||0})).reduce(((t,e)=>t+e),0);e.style.marginTop=`${s}px`}saveRegion(t){this.regionsContainer.appendChild(t.element),this.avoidOverlapping(t),this.regions.push(t),this.emit("region-created",t);const e=[t.on("update-end",(()=>{this.avoidOverlapping(t),this.emit("region-updated",t)})),t.on("play",(()=>{var e,i;null===(e=this.wavesurfer)||void 0===e||e.play(),null===(i=this.wavesurfer)||void 0===i||i.setTime(t.start)})),t.on("click",(e=>{this.emit("region-clicked",t,e)})),t.on("dblclick",(e=>{this.emit("region-double-clicked",t,e)})),t.once("remove",(()=>{e.forEach((t=>t())),this.regions=this.regions.filter((e=>e!==t))}))];this.subscriptions.push(...e)}addRegion(t){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");const e=this.wavesurfer.getDuration(),i=new n(t,e);return e?this.saveRegion(i):this.subscriptions.push(this.wavesurfer.once("ready",(t=>{i._setTotalDuration(t),this.saveRegion(i)}))),i}enableDragSelection(t){var e,s;const r=null===(s=null===(e=this.wavesurfer)||void 0===e?void 0:e.getWrapper())||void 0===s?void 0:s.querySelector("div");if(!r)return()=>{};let o=null,l=0;return i(r,((t,e,i)=>{o&&o._onUpdate(t,i>l?"end":"start")}),(e=>{if(l=e,!this.wavesurfer)return;const i=this.wavesurfer.getDuration(),s=this.wavesurfer.getWrapper().clientWidth,r=e/s*i,a=(e+5)/s*i;o=new n(Object.assign(Object.assign({},t),{start:r,end:a}),i),this.regionsContainer.appendChild(o.element)}),(()=>{o&&(this.saveRegion(o),o=null)}))}clearRegions(){this.regions.forEach((t=>t.remove()))}destroy(){this.clearRegions(),super.destroy()}}export{s as default};
|