wavesurfer.js 7.0.0-alpha.18 → 7.0.0-alpha.19
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/dist/base-plugin.d.ts +2 -2
- package/dist/base-plugin.js +5 -1
- package/dist/decoder.js +7 -19
- package/dist/event-emitter.js +1 -0
- package/dist/fetcher.js +2 -13
- package/dist/index.d.ts +4 -2
- package/dist/index.js +38 -42
- package/dist/player.d.ts +1 -0
- package/dist/player.js +16 -15
- package/dist/plugins/minimap.js +17 -6
- package/dist/plugins/multitrack.d.ts +10 -2
- package/dist/plugins/multitrack.js +70 -35
- package/dist/plugins/regions.js +61 -66
- package/dist/plugins/timeline.js +6 -6
- package/dist/renderer.d.ts +4 -6
- package/dist/renderer.js +134 -146
- package/dist/timer.js +1 -4
- package/dist/wavesurfer.Minimap.min.js +1 -1
- package/dist/wavesurfer.Multitrack.min.js +1 -1
- package/dist/wavesurfer.Regions.min.js +1 -1
- package/dist/wavesurfer.Timeline.min.js +1 -1
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +29 -25
- package/dist/player-webaudio.d.ts +0 -8
- package/dist/player-webaudio.js +0 -32
- package/dist/plugins/xmultitrack.d.ts +0 -44
- package/dist/plugins/xmultitrack.js +0 -260
- package/dist/react/useWavesurfer.d.ts +0 -5
- package/dist/react/useWavesurfer.js +0 -20
package/dist/base-plugin.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
|
|
2
|
-
import
|
|
2
|
+
import WaveSurfer, { type WaveSurferPluginParams } from './index.js';
|
|
3
3
|
export declare class BasePlugin<EventTypes extends GeneralEventTypes, Options> extends EventEmitter<EventTypes> {
|
|
4
4
|
protected wavesurfer: WaveSurfer;
|
|
5
|
-
protected container:
|
|
5
|
+
protected container: HTMLElement;
|
|
6
6
|
protected wrapper: HTMLElement;
|
|
7
7
|
protected subscriptions: (() => void)[];
|
|
8
8
|
protected options: Options;
|
package/dist/base-plugin.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import EventEmitter from './event-emitter.js';
|
|
2
2
|
export class BasePlugin extends EventEmitter {
|
|
3
|
+
wavesurfer;
|
|
4
|
+
container;
|
|
5
|
+
wrapper;
|
|
6
|
+
subscriptions = [];
|
|
7
|
+
options;
|
|
3
8
|
constructor(params, options) {
|
|
4
9
|
super();
|
|
5
|
-
this.subscriptions = [];
|
|
6
10
|
this.wavesurfer = params.wavesurfer;
|
|
7
11
|
this.container = params.container;
|
|
8
12
|
this.wrapper = params.wrapper;
|
package/dist/decoder.js
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
class Decoder {
|
|
2
|
+
audioCtx = null;
|
|
11
3
|
initAudioContext(sampleRate) {
|
|
12
4
|
this.audioCtx = new AudioContext({
|
|
13
5
|
latencyHint: 'playback',
|
|
@@ -15,7 +7,6 @@ class Decoder {
|
|
|
15
7
|
});
|
|
16
8
|
}
|
|
17
9
|
constructor() {
|
|
18
|
-
this.audioCtx = null;
|
|
19
10
|
// Minimum sample rate supported by Web Audio API
|
|
20
11
|
const DEFAULT_SAMPLE_RATE = 3000; // Chrome, Safari
|
|
21
12
|
const FALLBACK_SAMPLE_RATE = 8000; // Firefox
|
|
@@ -26,17 +17,14 @@ class Decoder {
|
|
|
26
17
|
this.initAudioContext(FALLBACK_SAMPLE_RATE);
|
|
27
18
|
}
|
|
28
19
|
}
|
|
29
|
-
decode(audioData) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return this.audioCtx.decodeAudioData(audioData);
|
|
35
|
-
});
|
|
20
|
+
async decode(audioData) {
|
|
21
|
+
if (!this.audioCtx) {
|
|
22
|
+
throw new Error('AudioContext is not initialized');
|
|
23
|
+
}
|
|
24
|
+
return this.audioCtx.decodeAudioData(audioData);
|
|
36
25
|
}
|
|
37
26
|
destroy() {
|
|
38
|
-
|
|
39
|
-
(_a = this.audioCtx) === null || _a === void 0 ? void 0 : _a.close();
|
|
27
|
+
this.audioCtx?.close();
|
|
40
28
|
this.audioCtx = null;
|
|
41
29
|
}
|
|
42
30
|
}
|
package/dist/event-emitter.js
CHANGED
package/dist/fetcher.js
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
class Fetcher {
|
|
11
|
-
load(url) {
|
|
12
|
-
return
|
|
13
|
-
return fetch(url).then((response) => response.arrayBuffer());
|
|
14
|
-
});
|
|
2
|
+
async load(url) {
|
|
3
|
+
return fetch(url).then((response) => response.arrayBuffer());
|
|
15
4
|
}
|
|
16
5
|
}
|
|
17
6
|
export default Fetcher;
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export type WaveSurferOptions = {
|
|
|
35
35
|
autoplay?: boolean;
|
|
36
36
|
/** Is the waveform clickable? */
|
|
37
37
|
interactive?: boolean;
|
|
38
|
+
/** Hide scrollbar **/
|
|
39
|
+
noScrollbar?: boolean;
|
|
38
40
|
};
|
|
39
41
|
export type WaveSurferEvents = {
|
|
40
42
|
decode: {
|
|
@@ -61,10 +63,10 @@ export type WaveSurferEvents = {
|
|
|
61
63
|
};
|
|
62
64
|
export type WaveSurferPluginParams = {
|
|
63
65
|
wavesurfer: WaveSurfer;
|
|
64
|
-
container:
|
|
66
|
+
container: HTMLElement;
|
|
65
67
|
wrapper: HTMLElement;
|
|
66
68
|
};
|
|
67
|
-
|
|
69
|
+
declare class WaveSurfer extends EventEmitter<WaveSurferEvents> {
|
|
68
70
|
private options;
|
|
69
71
|
private fetcher;
|
|
70
72
|
private decoder;
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import Fetcher from './fetcher.js';
|
|
11
2
|
import Decoder from './decoder.js';
|
|
12
3
|
import Renderer from './renderer.js';
|
|
@@ -22,18 +13,23 @@ const defaultOptions = {
|
|
|
22
13
|
fillParent: true,
|
|
23
14
|
interactive: true,
|
|
24
15
|
};
|
|
25
|
-
|
|
16
|
+
class WaveSurfer extends EventEmitter {
|
|
17
|
+
options;
|
|
18
|
+
fetcher;
|
|
19
|
+
decoder;
|
|
20
|
+
renderer;
|
|
21
|
+
player;
|
|
22
|
+
timer;
|
|
23
|
+
plugins = [];
|
|
24
|
+
subscriptions = [];
|
|
25
|
+
decodedData = null;
|
|
26
26
|
/** Create a new WaveSurfer instance */
|
|
27
27
|
static create(options) {
|
|
28
28
|
return new WaveSurfer(options);
|
|
29
29
|
}
|
|
30
30
|
/** Create a new WaveSurfer instance */
|
|
31
31
|
constructor(options) {
|
|
32
|
-
var _a;
|
|
33
32
|
super();
|
|
34
|
-
this.plugins = [];
|
|
35
|
-
this.subscriptions = [];
|
|
36
|
-
this.decodedData = null;
|
|
37
33
|
this.options = Object.assign({}, defaultOptions, options);
|
|
38
34
|
this.fetcher = new Fetcher();
|
|
39
35
|
this.decoder = new Decoder();
|
|
@@ -54,12 +50,13 @@ export class WaveSurfer extends EventEmitter {
|
|
|
54
50
|
barWidth: this.options.barWidth,
|
|
55
51
|
barGap: this.options.barGap,
|
|
56
52
|
barRadius: this.options.barRadius,
|
|
53
|
+
noScrollbar: this.options.noScrollbar,
|
|
57
54
|
});
|
|
58
55
|
this.initPlayerEvents();
|
|
59
56
|
this.initRendererEvents();
|
|
60
57
|
this.initTimerEvents();
|
|
61
58
|
this.initReadyEvent();
|
|
62
|
-
const url = this.options.url ||
|
|
59
|
+
const url = this.options.url || this.options.media?.src;
|
|
63
60
|
if (url) {
|
|
64
61
|
this.load(url, this.options.peaks, this.options.duration);
|
|
65
62
|
}
|
|
@@ -119,32 +116,32 @@ export class WaveSurfer extends EventEmitter {
|
|
|
119
116
|
}));
|
|
120
117
|
}
|
|
121
118
|
/** Load an audio file by URL, with optional pre-decoded audio data */
|
|
122
|
-
load(url, channelData, duration) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
(
|
|
135
|
-
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
this.decodedData = {
|
|
139
|
-
duration,
|
|
140
|
-
numberOfChannels: channelData.length,
|
|
141
|
-
sampleRate: channelData[0].length / duration,
|
|
142
|
-
getChannelData: (i) => channelData[i],
|
|
143
|
-
};
|
|
119
|
+
async load(url, channelData, duration) {
|
|
120
|
+
this.player.loadUrl(url);
|
|
121
|
+
// Fetch and decode the audio of no pre-computed audio data is provided
|
|
122
|
+
if (channelData == null) {
|
|
123
|
+
const audio = await this.fetcher.load(url);
|
|
124
|
+
const data = await this.decoder.decode(audio);
|
|
125
|
+
this.decodedData = data;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
if (!duration) {
|
|
129
|
+
duration =
|
|
130
|
+
(await new Promise((resolve) => {
|
|
131
|
+
this.player.on('canplay', () => resolve(this.getDuration()), {
|
|
132
|
+
once: true,
|
|
133
|
+
});
|
|
134
|
+
})) || 0;
|
|
144
135
|
}
|
|
145
|
-
this.
|
|
146
|
-
|
|
147
|
-
|
|
136
|
+
this.decodedData = {
|
|
137
|
+
duration,
|
|
138
|
+
numberOfChannels: channelData.length,
|
|
139
|
+
sampleRate: channelData[0].length / duration,
|
|
140
|
+
getChannelData: (i) => channelData[i],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
this.renderer.render(this.decodedData);
|
|
144
|
+
this.emit('decode', { duration: this.decodedData.duration });
|
|
148
145
|
}
|
|
149
146
|
/** Zoom in or out */
|
|
150
147
|
zoom(minPxPerSec) {
|
|
@@ -171,8 +168,7 @@ export class WaveSurfer extends EventEmitter {
|
|
|
171
168
|
}
|
|
172
169
|
/** Get the duration of the audio in seconds */
|
|
173
170
|
getDuration() {
|
|
174
|
-
|
|
175
|
-
return this.player.getDuration() || ((_a = this.decodedData) === null || _a === void 0 ? void 0 : _a.duration) || 0;
|
|
171
|
+
return this.player.getDuration() || this.decodedData?.duration || 0;
|
|
176
172
|
}
|
|
177
173
|
/** Get the current audio position in seconds */
|
|
178
174
|
getCurrentTime() {
|
package/dist/player.d.ts
CHANGED
package/dist/player.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
class Player {
|
|
2
|
+
media;
|
|
3
|
+
isExternalMedia = false;
|
|
4
|
+
hasPlayedOnce = false;
|
|
5
|
+
subscriptions = [];
|
|
2
6
|
constructor({ media, autoplay }) {
|
|
3
|
-
this.isExternalMedia = false;
|
|
4
|
-
this.hasPlayedOnce = false;
|
|
5
7
|
if (media) {
|
|
6
8
|
this.media = media;
|
|
7
9
|
this.isExternalMedia = true;
|
|
@@ -9,11 +11,11 @@ class Player {
|
|
|
9
11
|
else {
|
|
10
12
|
this.media = document.createElement('audio');
|
|
11
13
|
}
|
|
14
|
+
this.subscriptions.push(
|
|
12
15
|
// Track the first play() call
|
|
13
|
-
|
|
16
|
+
this.on('play', () => {
|
|
14
17
|
this.hasPlayedOnce = true;
|
|
15
|
-
|
|
16
|
-
});
|
|
18
|
+
}, { once: true }));
|
|
17
19
|
// Autoplay
|
|
18
20
|
if (autoplay) {
|
|
19
21
|
this.media.autoplay = true;
|
|
@@ -24,6 +26,9 @@ class Player {
|
|
|
24
26
|
return () => this.media.removeEventListener(event, callback);
|
|
25
27
|
}
|
|
26
28
|
destroy() {
|
|
29
|
+
this.subscriptions.forEach((unsubscribe) => {
|
|
30
|
+
unsubscribe();
|
|
31
|
+
});
|
|
27
32
|
this.media.pause();
|
|
28
33
|
if (!this.isExternalMedia) {
|
|
29
34
|
this.media.remove();
|
|
@@ -45,17 +50,13 @@ class Player {
|
|
|
45
50
|
return this.media.currentTime > 0 && !this.media.paused && !this.media.ended;
|
|
46
51
|
}
|
|
47
52
|
seekTo(time) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
this.media.currentTime = time;
|
|
55
|
-
if (!hasPlayedOnce) {
|
|
56
|
-
this.media.pause();
|
|
57
|
-
}
|
|
53
|
+
// iOS Safari requires a play() call before seeking
|
|
54
|
+
if (!this.hasPlayedOnce && navigator.userAgent.match(/(iPad|iPhone|iPod)/g)) {
|
|
55
|
+
this.media.play()?.then?.(() => {
|
|
56
|
+
setTimeout(() => this.media.pause(), 10);
|
|
57
|
+
});
|
|
58
58
|
}
|
|
59
|
+
this.media.currentTime = time;
|
|
59
60
|
}
|
|
60
61
|
getDuration() {
|
|
61
62
|
return this.media.duration;
|
package/dist/plugins/minimap.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import BasePlugin from '../base-plugin.js';
|
|
2
|
-
import
|
|
2
|
+
import WaveSurfer from '../index.js';
|
|
3
3
|
const defaultOptions = {
|
|
4
4
|
height: 50,
|
|
5
5
|
overlayColor: 'rgba(100, 100, 100, 0.1)',
|
|
6
6
|
};
|
|
7
7
|
class TimelinePlugin extends BasePlugin {
|
|
8
|
+
options;
|
|
9
|
+
minimapWrapper;
|
|
10
|
+
miniWavesurfer = null;
|
|
11
|
+
overlay;
|
|
8
12
|
constructor(params, options) {
|
|
9
13
|
super(params, options);
|
|
10
|
-
this.miniWavesurfer = null;
|
|
11
14
|
this.options = Object.assign({}, defaultOptions, options);
|
|
12
15
|
this.minimapWrapper = this.initMinimapWrapper();
|
|
13
16
|
this.overlay = this.initOverlay();
|
|
@@ -18,7 +21,7 @@ class TimelinePlugin extends BasePlugin {
|
|
|
18
21
|
initMinimapWrapper() {
|
|
19
22
|
const div = document.createElement('div');
|
|
20
23
|
div.style.position = 'relative';
|
|
21
|
-
this.container.
|
|
24
|
+
this.container.insertAdjacentElement('afterend', div);
|
|
22
25
|
return div;
|
|
23
26
|
}
|
|
24
27
|
initOverlay() {
|
|
@@ -33,7 +36,16 @@ class TimelinePlugin extends BasePlugin {
|
|
|
33
36
|
const media = this.wavesurfer.getMediaElement();
|
|
34
37
|
if (!data || !media)
|
|
35
38
|
return;
|
|
36
|
-
this.miniWavesurfer = WaveSurfer.create(
|
|
39
|
+
this.miniWavesurfer = WaveSurfer.create({
|
|
40
|
+
...this.options,
|
|
41
|
+
container: this.minimapWrapper,
|
|
42
|
+
minPxPerSec: 1,
|
|
43
|
+
fillParent: true,
|
|
44
|
+
media,
|
|
45
|
+
url: media.src,
|
|
46
|
+
peaks: [data.getChannelData(0)],
|
|
47
|
+
duration: data.duration,
|
|
48
|
+
});
|
|
37
49
|
const overlayWidth = Math.round((this.minimapWrapper.clientWidth / this.wrapper.clientWidth) * 100);
|
|
38
50
|
this.overlay.style.width = `${overlayWidth}%`;
|
|
39
51
|
this.wavesurfer.on('timeupdate', ({ currentTime }) => {
|
|
@@ -43,8 +55,7 @@ class TimelinePlugin extends BasePlugin {
|
|
|
43
55
|
}
|
|
44
56
|
/** Unmount */
|
|
45
57
|
destroy() {
|
|
46
|
-
|
|
47
|
-
(_a = this.miniWavesurfer) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
58
|
+
this.miniWavesurfer?.destroy();
|
|
48
59
|
this.minimapWrapper.remove();
|
|
49
60
|
super.destroy();
|
|
50
61
|
}
|
|
@@ -8,19 +8,26 @@ type MultitrackTracks = Array<{
|
|
|
8
8
|
startCue?: number;
|
|
9
9
|
endCue?: number;
|
|
10
10
|
markers?: Array<{
|
|
11
|
-
id: string | number;
|
|
12
11
|
time: number;
|
|
13
12
|
label?: string;
|
|
14
13
|
color?: string;
|
|
15
14
|
}>;
|
|
15
|
+
regions?: Array<{
|
|
16
|
+
startTime: number;
|
|
17
|
+
endTime: number;
|
|
18
|
+
label?: string;
|
|
19
|
+
color?: string;
|
|
20
|
+
}>;
|
|
21
|
+
options?: WaveSurferOptions;
|
|
16
22
|
}>;
|
|
17
23
|
type MultitrackOptions = {
|
|
18
24
|
container: HTMLElement;
|
|
19
25
|
minPxPerSec?: number;
|
|
20
26
|
cursorColor?: string;
|
|
27
|
+
cursorWidth?: number;
|
|
21
28
|
trackBackground?: string;
|
|
22
29
|
trackBorderColor?: string;
|
|
23
|
-
|
|
30
|
+
rightButtonDrag?: boolean;
|
|
24
31
|
onTrackPositionUpdate?: (id: string | number, startPosition: number) => void;
|
|
25
32
|
};
|
|
26
33
|
declare class MultiTrack {
|
|
@@ -41,6 +48,7 @@ declare class MultiTrack {
|
|
|
41
48
|
private initTimeline;
|
|
42
49
|
private updatePosition;
|
|
43
50
|
private onDrag;
|
|
51
|
+
private onMove;
|
|
44
52
|
private findCurrentTracks;
|
|
45
53
|
private startSync;
|
|
46
54
|
play(): void;
|
|
@@ -2,18 +2,25 @@ import WaveSurfer from '../index.js';
|
|
|
2
2
|
import RegionsPlugin from './regions.js';
|
|
3
3
|
import TimelinePlugin from './timeline.js';
|
|
4
4
|
class MultiTrack {
|
|
5
|
+
tracks;
|
|
6
|
+
options;
|
|
7
|
+
audios = [];
|
|
8
|
+
wavesurfers = [];
|
|
9
|
+
durations = [];
|
|
10
|
+
currentTime = 0;
|
|
11
|
+
maxDuration = 0;
|
|
12
|
+
rendering;
|
|
13
|
+
isDragging = false;
|
|
14
|
+
frameRequest = null;
|
|
5
15
|
static create(tracks, options) {
|
|
6
16
|
return new MultiTrack(tracks, options);
|
|
7
17
|
}
|
|
8
18
|
constructor(tracks, options) {
|
|
9
|
-
this.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
this.isDragging = false;
|
|
15
|
-
this.frameRequest = null;
|
|
16
|
-
this.tracks = tracks.map((track) => (Object.assign(Object.assign({}, track), { startPosition: track.startPosition || 0, peaks: track.peaks || (track.url ? undefined : [new Float32Array()]) })));
|
|
19
|
+
this.tracks = tracks.map((track) => ({
|
|
20
|
+
...track,
|
|
21
|
+
startPosition: track.startPosition || 0,
|
|
22
|
+
peaks: track.peaks || (track.url ? undefined : [new Float32Array()]),
|
|
23
|
+
}));
|
|
17
24
|
this.options = options;
|
|
18
25
|
this.rendering = initRendering(this.tracks, this.options);
|
|
19
26
|
this.initAudios().then((durations) => {
|
|
@@ -31,9 +38,9 @@ class MultiTrack {
|
|
|
31
38
|
this.initWavesurfers();
|
|
32
39
|
this.initTimeline();
|
|
33
40
|
this.rendering.containers.forEach((container, index) => {
|
|
34
|
-
const drag = initDragging(container, (delta) => this.onDrag(index, delta));
|
|
41
|
+
const drag = initDragging(container, (delta) => this.onDrag(index, delta), options.rightButtonDrag);
|
|
35
42
|
this.wavesurfers[index].once('destroy', () => {
|
|
36
|
-
drag
|
|
43
|
+
drag?.destroy();
|
|
37
44
|
});
|
|
38
45
|
});
|
|
39
46
|
});
|
|
@@ -53,25 +60,46 @@ class MultiTrack {
|
|
|
53
60
|
initWavesurfers() {
|
|
54
61
|
const wavesurfers = this.tracks.map((track, index) => {
|
|
55
62
|
// Create a wavesurfer instance
|
|
56
|
-
const ws = WaveSurfer.create(
|
|
63
|
+
const ws = WaveSurfer.create({
|
|
64
|
+
...track.options,
|
|
65
|
+
container: this.rendering.containers[index],
|
|
66
|
+
minPxPerSec: 0,
|
|
67
|
+
media: this.audios[index],
|
|
68
|
+
peaks: track.peaks,
|
|
69
|
+
cursorColor: 'transparent',
|
|
70
|
+
cursorWidth: 0,
|
|
71
|
+
interactive: false,
|
|
72
|
+
});
|
|
57
73
|
const wsRegions = ws.registerPlugin(RegionsPlugin, {
|
|
58
74
|
draggable: false,
|
|
59
|
-
resizable:
|
|
75
|
+
resizable: true,
|
|
60
76
|
dragSelection: false,
|
|
61
77
|
});
|
|
62
78
|
ws.once('decode', () => {
|
|
63
79
|
// Render start and end cues
|
|
64
80
|
if (track.startCue) {
|
|
65
|
-
wsRegions.add(
|
|
81
|
+
const region = wsRegions.add(0, track.startCue, '', 'rgba(0, 0, 0, 0.7)');
|
|
82
|
+
region.element.firstElementChild?.remove();
|
|
66
83
|
}
|
|
67
84
|
if (track.endCue) {
|
|
68
|
-
wsRegions.add(track.endCue, track.endCue, '
|
|
85
|
+
const region = wsRegions.add(track.endCue, track.endCue + this.durations[index], '', 'rgba(0, 0, 0, 0.7)');
|
|
86
|
+
region.element.lastChild?.remove();
|
|
87
|
+
}
|
|
88
|
+
// Render regions
|
|
89
|
+
if (track.regions) {
|
|
90
|
+
track.regions.forEach((params) => {
|
|
91
|
+
const region = wsRegions.add(params.startTime || 0, params.endTime, params.label, params.color);
|
|
92
|
+
if (!params.startTime) {
|
|
93
|
+
region.element.firstElementChild?.remove();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
69
96
|
}
|
|
70
97
|
// Render markers
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
98
|
+
if (track.markers) {
|
|
99
|
+
track.markers.forEach((marker) => {
|
|
100
|
+
wsRegions.add(marker.time, marker.time, marker.label, marker.color);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
75
103
|
});
|
|
76
104
|
return ws;
|
|
77
105
|
});
|
|
@@ -111,23 +139,25 @@ class MultiTrack {
|
|
|
111
139
|
});
|
|
112
140
|
}
|
|
113
141
|
onDrag(index, delta) {
|
|
114
|
-
|
|
142
|
+
// Prevent click events when dragging
|
|
143
|
+
this.isDragging = true;
|
|
144
|
+
setTimeout(() => (this.isDragging = false), 600);
|
|
145
|
+
const newTime = this.tracks[index].startPosition + delta * this.maxDuration;
|
|
146
|
+
this.onMove(index, newTime);
|
|
147
|
+
}
|
|
148
|
+
onMove(index, newStartPosition) {
|
|
115
149
|
const track = this.tracks[index];
|
|
116
150
|
if (!track.draggable)
|
|
117
151
|
return;
|
|
118
|
-
this.
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return Math.min(min, track.startPosition);
|
|
125
|
-
}, Infinity);
|
|
126
|
-
if (newTime + this.durations[index] >= minStart) {
|
|
127
|
-
track.startPosition = newTime;
|
|
152
|
+
const mainIndex = this.tracks.findIndex((item) => item.url && !item.draggable);
|
|
153
|
+
const mainTrack = this.tracks[mainIndex];
|
|
154
|
+
const minStart = (mainTrack ? mainTrack.startPosition : 0) - this.durations[index];
|
|
155
|
+
const maxStart = mainTrack ? mainTrack.startPosition + this.durations[mainIndex] : this.maxDuration;
|
|
156
|
+
if (newStartPosition >= minStart && newStartPosition <= maxStart) {
|
|
157
|
+
track.startPosition = newStartPosition;
|
|
128
158
|
this.rendering.setContainerOffsets();
|
|
129
159
|
this.updatePosition(this.currentTime);
|
|
130
|
-
|
|
160
|
+
this.options.onTrackPositionUpdate?.(track.id, track.startPosition);
|
|
131
161
|
}
|
|
132
162
|
}
|
|
133
163
|
findCurrentTracks() {
|
|
@@ -165,8 +195,7 @@ class MultiTrack {
|
|
|
165
195
|
this.startSync();
|
|
166
196
|
const indexes = this.findCurrentTracks();
|
|
167
197
|
indexes.forEach((index) => {
|
|
168
|
-
|
|
169
|
-
(_a = this.audios[index]) === null || _a === void 0 ? void 0 : _a.play();
|
|
198
|
+
this.audios[index]?.play();
|
|
170
199
|
});
|
|
171
200
|
}
|
|
172
201
|
pause() {
|
|
@@ -212,8 +241,9 @@ function initRendering(tracks, options) {
|
|
|
212
241
|
options.container.appendChild(scroll);
|
|
213
242
|
// Create a common cursor
|
|
214
243
|
const cursor = document.createElement('div');
|
|
215
|
-
cursor.setAttribute('style', '
|
|
244
|
+
cursor.setAttribute('style', 'height: 100%; position: absolute; z-index: 10; top: 0; left: 0');
|
|
216
245
|
cursor.style.backgroundColor = options.cursorColor || '#000';
|
|
246
|
+
cursor.style.width = `${options.cursorWidth ?? 1}px`;
|
|
217
247
|
wrapper.appendChild(cursor);
|
|
218
248
|
const { clientWidth } = wrapper;
|
|
219
249
|
// Create containers for each track
|
|
@@ -237,7 +267,7 @@ function initRendering(tracks, options) {
|
|
|
237
267
|
container.style.width = `${durations[i] * pxPerSec}px`;
|
|
238
268
|
container.style.transform = `translateX(${offset}px)`;
|
|
239
269
|
if (tracks[i].draggable)
|
|
240
|
-
container.style.cursor = '
|
|
270
|
+
container.style.cursor = 'move';
|
|
241
271
|
});
|
|
242
272
|
};
|
|
243
273
|
return {
|
|
@@ -271,14 +301,19 @@ function initRendering(tracks, options) {
|
|
|
271
301
|
},
|
|
272
302
|
};
|
|
273
303
|
}
|
|
274
|
-
function initDragging(container, onDrag) {
|
|
304
|
+
function initDragging(container, onDrag, rightButtonDrag = false) {
|
|
275
305
|
const wrapper = container.parentElement;
|
|
276
306
|
if (!wrapper)
|
|
277
307
|
return;
|
|
278
308
|
// Dragging tracks to set position
|
|
279
309
|
let dragStart = null;
|
|
310
|
+
container.addEventListener('contextmenu', (e) => {
|
|
311
|
+
rightButtonDrag && e.preventDefault();
|
|
312
|
+
});
|
|
280
313
|
// Drag start
|
|
281
314
|
container.addEventListener('mousedown', (e) => {
|
|
315
|
+
if (rightButtonDrag && e.button !== 2)
|
|
316
|
+
return;
|
|
282
317
|
const rect = wrapper.getBoundingClientRect();
|
|
283
318
|
dragStart = e.clientX - rect.left;
|
|
284
319
|
});
|