wavesurfer.js 7.0.0-alpha.0 → 7.0.0-alpha.2
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 +1 -1
- package/dist/event-emitter.d.ts +2 -0
- package/dist/wavesurfer.Regions.min.js +1 -0
- package/dist/wavesurfer.min.js +1 -0
- package/package.json +14 -6
- package/.eslintrc.json +0 -24
- package/.prettierrc +0 -8
- package/dist/base-plugin.js +0 -13
- package/dist/decoder.js +0 -43
- package/dist/event-emitter.js +0 -21
- package/dist/fetcher.js +0 -17
- package/dist/index.js +0 -166
- package/dist/player-webaudio.js +0 -31
- package/dist/player.js +0 -40
- package/dist/plugins/regions.js +0 -183
- package/dist/renderer.js +0 -193
- package/dist/timer.js +0 -17
- package/examples/audio.ogg +0 -0
- package/examples/bars.js +0 -19
- package/examples/basic.js +0 -8
- package/examples/gradient.js +0 -28
- package/examples/regions.js +0 -63
- package/examples/video.js +0 -19
- package/examples/webaudio.js +0 -14
- package/src/base-plugin.ts +0 -20
- package/src/decoder.ts +0 -41
- package/src/event-emitter.ts +0 -35
- package/src/fetcher.ts +0 -7
- package/src/index.ts +0 -252
- package/src/player-webaudio.ts +0 -34
- package/src/player.ts +0 -50
- package/src/plugins/regions.ts +0 -240
- package/src/renderer.ts +0 -250
- package/src/timer.ts +0 -27
- package/tsconfig.json +0 -105
- package/tutorial/index.html +0 -47
- package/tutorial/src/editor.js +0 -70
- package/tutorial/src/init.js +0 -66
- package/tutorial/src/url.js +0 -25
- package/tutorial/style.css +0 -211
- package/yarn-error.log +0 -1049
package/dist/renderer.js
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import EventEmitter from './event-emitter.js';
|
|
2
|
-
class Renderer extends EventEmitter {
|
|
3
|
-
constructor(options) {
|
|
4
|
-
super();
|
|
5
|
-
this.options = options;
|
|
6
|
-
let container = null;
|
|
7
|
-
if (typeof this.options.container === 'string') {
|
|
8
|
-
container = document.querySelector(this.options.container);
|
|
9
|
-
}
|
|
10
|
-
else if (this.options.container instanceof HTMLElement) {
|
|
11
|
-
container = this.options.container;
|
|
12
|
-
}
|
|
13
|
-
if (!container) {
|
|
14
|
-
throw new Error('Container not found');
|
|
15
|
-
}
|
|
16
|
-
const div = document.createElement('div');
|
|
17
|
-
const shadow = div.attachShadow({ mode: 'open' });
|
|
18
|
-
shadow.innerHTML = `
|
|
19
|
-
<style>
|
|
20
|
-
:host .scroll {
|
|
21
|
-
overflow-x: auto;
|
|
22
|
-
overflow-y: visible;
|
|
23
|
-
user-select: none;
|
|
24
|
-
width: 100%;
|
|
25
|
-
height: ${this.options.height}px;
|
|
26
|
-
position: relative;
|
|
27
|
-
}
|
|
28
|
-
:host .wrapper {
|
|
29
|
-
position: relative;
|
|
30
|
-
width: fit-content;
|
|
31
|
-
min-width: 100%;
|
|
32
|
-
height: 100%;
|
|
33
|
-
z-index: 2;
|
|
34
|
-
}
|
|
35
|
-
:host canvas {
|
|
36
|
-
display: block;
|
|
37
|
-
height: 100%;
|
|
38
|
-
min-width: 100%;
|
|
39
|
-
image-rendering: pixelated;
|
|
40
|
-
}
|
|
41
|
-
:host .progress {
|
|
42
|
-
position: absolute;
|
|
43
|
-
z-index: 2;
|
|
44
|
-
top: 0;
|
|
45
|
-
left: 0;
|
|
46
|
-
height: 100%;
|
|
47
|
-
pointer-events: none;
|
|
48
|
-
clip-path: inset(100%);
|
|
49
|
-
}
|
|
50
|
-
:host .cursor {
|
|
51
|
-
position: absolute;
|
|
52
|
-
z-index: 3;
|
|
53
|
-
top: 0;
|
|
54
|
-
left: 0;
|
|
55
|
-
height: 100%;
|
|
56
|
-
border-right: 1px solid ${this.options.progressColor};
|
|
57
|
-
}
|
|
58
|
-
</style>
|
|
59
|
-
|
|
60
|
-
<div class="scroll">
|
|
61
|
-
<div class="wrapper">
|
|
62
|
-
<canvas></canvas>
|
|
63
|
-
<canvas class="progress"></canvas>
|
|
64
|
-
<div class="cursor"></div>
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
`;
|
|
68
|
-
this.container = div;
|
|
69
|
-
this.shadowRoot = shadow;
|
|
70
|
-
this.mainCanvas = shadow.querySelector('canvas');
|
|
71
|
-
this.ctx = this.mainCanvas.getContext('2d', { desynchronized: true });
|
|
72
|
-
this.progressCanvas = shadow.querySelector('.progress');
|
|
73
|
-
this.cursor = shadow.querySelector('.cursor');
|
|
74
|
-
container.appendChild(div);
|
|
75
|
-
this.mainCanvas.addEventListener('click', (e) => {
|
|
76
|
-
const rect = this.mainCanvas.getBoundingClientRect();
|
|
77
|
-
const x = e.clientX - rect.left;
|
|
78
|
-
const relativeX = x / rect.width;
|
|
79
|
-
this.emit('click', { relativeX });
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
destroy() {
|
|
83
|
-
this.container.remove();
|
|
84
|
-
}
|
|
85
|
-
renderLinePeaks(channelData, width, height) {
|
|
86
|
-
const { ctx } = this;
|
|
87
|
-
ctx.clearRect(0, 0, width, height);
|
|
88
|
-
ctx.beginPath();
|
|
89
|
-
ctx.moveTo(0, height / 2);
|
|
90
|
-
// Channel 0 is left, 1 is right
|
|
91
|
-
const leftChannel = channelData[0];
|
|
92
|
-
const isMono = channelData.length === 1;
|
|
93
|
-
const rightChannel = isMono ? leftChannel : channelData[1];
|
|
94
|
-
// Draw left channel in the top half of the canvas
|
|
95
|
-
let prevX = -1;
|
|
96
|
-
let prevY = 0;
|
|
97
|
-
for (let i = 0, len = leftChannel.length; i < len; i++) {
|
|
98
|
-
const x = Math.round((i / leftChannel.length) * width);
|
|
99
|
-
const y = Math.round(((1 - leftChannel[i]) * height) / 2);
|
|
100
|
-
if (x !== prevX || y > prevY) {
|
|
101
|
-
ctx.lineTo(x, y);
|
|
102
|
-
prevX = x;
|
|
103
|
-
prevY = y;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// Draw right channel in the bottom half of the canvas
|
|
107
|
-
prevX = -1;
|
|
108
|
-
prevY = 0;
|
|
109
|
-
for (let i = rightChannel.length - 1; i >= 0; i--) {
|
|
110
|
-
const x = Math.round((i / rightChannel.length) * width);
|
|
111
|
-
const y = Math.round(((1 + rightChannel[i]) * height) / 2);
|
|
112
|
-
if (x !== prevX || (isMono ? y < -prevY : y > prevY)) {
|
|
113
|
-
ctx.lineTo(x, y);
|
|
114
|
-
prevX = x;
|
|
115
|
-
prevY = y;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
ctx.strokeStyle = ctx.fillStyle = this.options.waveColor;
|
|
119
|
-
ctx.stroke();
|
|
120
|
-
ctx.fill();
|
|
121
|
-
}
|
|
122
|
-
renderBarPeaks(channelData, width, height) {
|
|
123
|
-
var _a, _b;
|
|
124
|
-
const { devicePixelRatio } = window;
|
|
125
|
-
const { ctx } = this;
|
|
126
|
-
ctx.clearRect(0, 0, width, height);
|
|
127
|
-
const barWidthOption = this.options.barWidth || 1;
|
|
128
|
-
const barWidth = barWidthOption * devicePixelRatio;
|
|
129
|
-
const barGap = ((_a = this.options.barGap) !== null && _a !== void 0 ? _a : barWidthOption / 2) * devicePixelRatio;
|
|
130
|
-
const barRadius = (_b = this.options.barRadius) !== null && _b !== void 0 ? _b : 0;
|
|
131
|
-
const leftChannel = channelData[0];
|
|
132
|
-
const isMono = channelData.length === 1;
|
|
133
|
-
const rightChannel = isMono ? leftChannel : channelData[1];
|
|
134
|
-
const barCount = Math.floor(width / (barWidth + barGap));
|
|
135
|
-
const leftChannelBars = new Float32Array(barCount);
|
|
136
|
-
const rightChannelBars = new Float32Array(barCount);
|
|
137
|
-
const barIndexScale = barCount / leftChannel.length;
|
|
138
|
-
const leftChannelLength = leftChannel.length;
|
|
139
|
-
for (let i = 0; i < leftChannelLength; i++) {
|
|
140
|
-
const barIndex = Math.round(i * barIndexScale);
|
|
141
|
-
leftChannelBars[barIndex] = Math.max(leftChannelBars[barIndex], leftChannel[i]);
|
|
142
|
-
rightChannelBars[barIndex] = (isMono ? Math.min : Math.max)(rightChannelBars[barIndex], rightChannel[i]);
|
|
143
|
-
}
|
|
144
|
-
ctx.beginPath();
|
|
145
|
-
for (let i = 0; i < barCount; i++) {
|
|
146
|
-
const leftBarHeight = Math.max(1, Math.round((leftChannelBars[i] * height) / 2));
|
|
147
|
-
const rightBarHeight = Math.max(1, Math.round(Math.abs(rightChannelBars[i] * height) / 2));
|
|
148
|
-
ctx.roundRect(i * (barWidth + barGap), height / 2 - leftBarHeight, barWidth, leftBarHeight + rightBarHeight, barRadius);
|
|
149
|
-
}
|
|
150
|
-
ctx.fillStyle = this.options.waveColor;
|
|
151
|
-
ctx.fill();
|
|
152
|
-
}
|
|
153
|
-
render(channelData, duration, minPxPerSec = this.options.minPxPerSec) {
|
|
154
|
-
const { devicePixelRatio } = window;
|
|
155
|
-
const width = Math.max(this.container.clientWidth * devicePixelRatio, duration * minPxPerSec);
|
|
156
|
-
const { height } = this.options;
|
|
157
|
-
this.mainCanvas.width = width;
|
|
158
|
-
this.mainCanvas.height = height;
|
|
159
|
-
this.mainCanvas.style.width = Math.round(width / devicePixelRatio) + 'px';
|
|
160
|
-
const renderingFn = this.options.barWidth ? this.renderBarPeaks : this.renderLinePeaks;
|
|
161
|
-
renderingFn.call(this, channelData, width, height);
|
|
162
|
-
this.createProgressMask();
|
|
163
|
-
}
|
|
164
|
-
createProgressMask() {
|
|
165
|
-
const progressCtx = this.progressCanvas.getContext('2d', { desynchronized: true });
|
|
166
|
-
if (!progressCtx) {
|
|
167
|
-
throw new Error('Failed to get canvas context');
|
|
168
|
-
}
|
|
169
|
-
this.progressCanvas.width = this.mainCanvas.width;
|
|
170
|
-
this.progressCanvas.height = this.mainCanvas.height;
|
|
171
|
-
this.progressCanvas.style.width = this.mainCanvas.style.width;
|
|
172
|
-
this.progressCanvas.style.height = this.mainCanvas.style.height;
|
|
173
|
-
progressCtx.drawImage(this.mainCanvas, 0, 0);
|
|
174
|
-
progressCtx.globalCompositeOperation = 'source-in';
|
|
175
|
-
progressCtx.fillStyle = this.options.progressColor;
|
|
176
|
-
progressCtx.fillRect(0, 0, this.progressCanvas.width, this.progressCanvas.height);
|
|
177
|
-
}
|
|
178
|
-
renderProgress(progress, autoCenter = false) {
|
|
179
|
-
this.progressCanvas.style.clipPath = `inset(0 ${100 - progress * 100}% 0 0)`;
|
|
180
|
-
this.cursor.style.left = `${progress * 100}%`;
|
|
181
|
-
if (autoCenter) {
|
|
182
|
-
const center = this.container.clientWidth / 2;
|
|
183
|
-
const fullWidth = this.mainCanvas.clientWidth;
|
|
184
|
-
if (fullWidth * progress >= center) {
|
|
185
|
-
this.container.scrollLeft = fullWidth * progress - center;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
getContainer() {
|
|
190
|
-
return this.shadowRoot.querySelector('.scroll');
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
export default Renderer;
|
package/dist/timer.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import EventEmitter from './event-emitter.js';
|
|
2
|
-
class Timer extends EventEmitter {
|
|
3
|
-
constructor() {
|
|
4
|
-
super();
|
|
5
|
-
this.unsubscribe = () => undefined;
|
|
6
|
-
this.unsubscribe = this.on('tick', () => {
|
|
7
|
-
requestAnimationFrame(() => {
|
|
8
|
-
this.emit('tick');
|
|
9
|
-
});
|
|
10
|
-
});
|
|
11
|
-
this.emit('tick');
|
|
12
|
-
}
|
|
13
|
-
destroy() {
|
|
14
|
-
this.unsubscribe();
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
export default Timer;
|
package/examples/audio.ogg
DELETED
|
Binary file
|
package/examples/bars.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import WaveSurfer from '/dist/index.js'
|
|
2
|
-
|
|
3
|
-
const wavesurfer = WaveSurfer.create({
|
|
4
|
-
container: document.body,
|
|
5
|
-
waveColor: 'rgb(200, 0, 200)',
|
|
6
|
-
progressColor: 'rgb(100, 0, 100)',
|
|
7
|
-
url: 'https://wavesurfer-js.org/example/media/demo.wav',
|
|
8
|
-
|
|
9
|
-
// Set a bar width
|
|
10
|
-
barWidth: 2,
|
|
11
|
-
// Optionally, specify the spacing between bars
|
|
12
|
-
barGap: 1,
|
|
13
|
-
// And the bar radius
|
|
14
|
-
barRadius: 2,
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
wavesurfer.on('seek', function () {
|
|
18
|
-
wavesurfer.play()
|
|
19
|
-
})
|
package/examples/basic.js
DELETED
package/examples/gradient.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import WaveSurfer from '/dist/index.js'
|
|
2
|
-
|
|
3
|
-
// Create a canvas gradient
|
|
4
|
-
const ctx = document.createElement('canvas').getContext('2d')
|
|
5
|
-
const gradient = ctx.createLinearGradient(0, 0, 0, 150)
|
|
6
|
-
gradient.addColorStop(0, 'rgb(200, 0, 200)')
|
|
7
|
-
gradient.addColorStop(0.7, 'rgb(100, 0, 100)')
|
|
8
|
-
gradient.addColorStop(1, 'rgb(0, 0, 0)')
|
|
9
|
-
|
|
10
|
-
// Default style with a gradient
|
|
11
|
-
WaveSurfer.create({
|
|
12
|
-
container: document.body,
|
|
13
|
-
waveColor: gradient,
|
|
14
|
-
height: 200,
|
|
15
|
-
progressColor: 'rgba(0, 0, 100, 0.5)',
|
|
16
|
-
url: 'https://wavesurfer-js.org/example/media/demo.wav'
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// SoundCloud-style bars
|
|
21
|
-
WaveSurfer.create({
|
|
22
|
-
container: document.body,
|
|
23
|
-
waveColor: gradient,
|
|
24
|
-
height: 200,
|
|
25
|
-
barWidth: 2,
|
|
26
|
-
progressColor: 'rgba(0, 0, 100, 0.5)',
|
|
27
|
-
url: 'https://wavesurfer-js.org/example/media/demo.wav'
|
|
28
|
-
})
|
package/examples/regions.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import WaveSurfer from '/dist/index.js'
|
|
2
|
-
import RegionsPlugin from '/dist/plugins/regions.js'
|
|
3
|
-
|
|
4
|
-
// Create an instance of WaveSurfer
|
|
5
|
-
const ws = WaveSurfer.create({
|
|
6
|
-
container: document.body,
|
|
7
|
-
waveColor: 'rgb(200, 0, 200)',
|
|
8
|
-
progressColor: 'rgb(100, 0, 100)',
|
|
9
|
-
url: 'https://wavesurfer-js.org/example/media/demo.wav'
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
// Initialize the Regions plugin
|
|
13
|
-
const wsRegions = ws.registerPlugin(RegionsPlugin)
|
|
14
|
-
|
|
15
|
-
// Give regions a random color when they are created
|
|
16
|
-
const random = (min, max) => Math.random() * (max - min) + min
|
|
17
|
-
wsRegions.on('region-created', ({ region }) => {
|
|
18
|
-
wsRegions.setRegionColor(region, `rgba(${random(0, 255)}, ${random(0, 255)}, ${random(0, 255)}, 0.5)`)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
// Create some regions at specific time ranges
|
|
22
|
-
ws.on('ready', () => {
|
|
23
|
-
wsRegions.addRegionAtTime(4, 7, 'First region')
|
|
24
|
-
wsRegions.addRegionAtTime(9, 10, 'Middle region')
|
|
25
|
-
wsRegions.addRegionAtTime(12, 17, 'Last region')
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
// Loop a region on click
|
|
29
|
-
let loop = true
|
|
30
|
-
let activeRegion = null
|
|
31
|
-
|
|
32
|
-
wsRegions.on('region-clicked', ({ region }) => {
|
|
33
|
-
ws.seekTo(region.startTime)
|
|
34
|
-
ws.play()
|
|
35
|
-
activeRegion = region
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
// Track the time
|
|
39
|
-
ws.on('audioprocess', ({ currentTime }) => {
|
|
40
|
-
// When the end of the region is reached
|
|
41
|
-
if (activeRegion && ws.isPlaying() && (currentTime >= activeRegion.endTime)) {
|
|
42
|
-
if (loop) {
|
|
43
|
-
// If looping, jump to the start of the region
|
|
44
|
-
ws.seekTo(activeRegion.startTime)
|
|
45
|
-
} else {
|
|
46
|
-
// Otherwise, stop playing
|
|
47
|
-
ws.pause()
|
|
48
|
-
ws.seekTo(activeRegion.endTime)
|
|
49
|
-
activeRegion = null
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
// Toggle looping with a checkbox
|
|
55
|
-
const p = document.createElement('p')
|
|
56
|
-
p.innerHTML = `
|
|
57
|
-
<label>
|
|
58
|
-
<input type="checkbox" checked="${loop}" style="vertical-align: text-top" />
|
|
59
|
-
Loop regions on click
|
|
60
|
-
</label>
|
|
61
|
-
`
|
|
62
|
-
document.body.appendChild(p)
|
|
63
|
-
document.querySelector('input').onclick = (e) => { loop = e.target.checked }
|
package/examples/video.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import WaveSurfer from '/dist/index.js'
|
|
2
|
-
|
|
3
|
-
// Create a video element and add it to the DOM.
|
|
4
|
-
// The video can also be defined in HTML, in which case we just get it by a selector.
|
|
5
|
-
const video = document.createElement('video')
|
|
6
|
-
video.crossOrigin = 'anonymous'
|
|
7
|
-
video.controls = true
|
|
8
|
-
video.style.width = '100%'
|
|
9
|
-
video.src = 'https://wavesurfer-js.org/example/media/nasa.mp4'
|
|
10
|
-
document.body.appendChild(video)
|
|
11
|
-
|
|
12
|
-
// Initialize wavesurfer.js
|
|
13
|
-
const ws = WaveSurfer.create({
|
|
14
|
-
container: document.body,
|
|
15
|
-
waveColor: 'rgb(200, 0, 200)',
|
|
16
|
-
progressColor: 'rgb(100, 0, 100)',
|
|
17
|
-
// Pass the video element in the `media` param
|
|
18
|
-
media: document.querySelector('video'),
|
|
19
|
-
})
|
package/examples/webaudio.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import WaveSurfer from '/dist/index.js'
|
|
2
|
-
|
|
3
|
-
const audio = new Audio()
|
|
4
|
-
audio.controls = true
|
|
5
|
-
audio.src = '/examples/audio.ogg'
|
|
6
|
-
document.body.appendChild(audio)
|
|
7
|
-
|
|
8
|
-
const wavesurfer = WaveSurfer.create({
|
|
9
|
-
container: document.body,
|
|
10
|
-
waveColor: 'rgb(200, 0, 200)',
|
|
11
|
-
progressColor: 'rgb(100, 0, 100)',
|
|
12
|
-
backend: 'WebAudio',
|
|
13
|
-
media: audio,
|
|
14
|
-
})
|
package/src/base-plugin.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js'
|
|
2
|
-
import type { WaveSurfer, WaveSurferPluginParams } from './index.js'
|
|
3
|
-
|
|
4
|
-
export class BasePlugin<EventTypes extends GeneralEventTypes> extends EventEmitter<EventTypes> {
|
|
5
|
-
protected wavesurfer: WaveSurfer
|
|
6
|
-
protected renderer: WaveSurfer['renderer']
|
|
7
|
-
protected subscriptions: (() => void)[] = []
|
|
8
|
-
|
|
9
|
-
constructor(params: WaveSurferPluginParams) {
|
|
10
|
-
super()
|
|
11
|
-
this.wavesurfer = params.wavesurfer
|
|
12
|
-
this.renderer = params.renderer
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
destroy() {
|
|
16
|
-
this.subscriptions.forEach((unsubscribe) => unsubscribe())
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export default BasePlugin
|
package/src/decoder.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// Web Audio decodeAudioData with a minimum allowed sample rate
|
|
2
|
-
const SAMPLE_RATE = 3000
|
|
3
|
-
|
|
4
|
-
class Decoder {
|
|
5
|
-
audioCtx: AudioContext | null = null
|
|
6
|
-
|
|
7
|
-
constructor() {
|
|
8
|
-
this.audioCtx = new (window.AudioContext ||
|
|
9
|
-
(window as unknown as { webkitAudioContext: AudioContext }).webkitAudioContext)({
|
|
10
|
-
latencyHint: 'playback',
|
|
11
|
-
sampleRate: SAMPLE_RATE,
|
|
12
|
-
})
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
public async decode(audioData: ArrayBuffer): Promise<{
|
|
16
|
-
duration: number
|
|
17
|
-
channelData: Float32Array[]
|
|
18
|
-
}> {
|
|
19
|
-
if (!this.audioCtx) {
|
|
20
|
-
throw new Error('AudioContext is not initialized')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const buffer = await this.audioCtx.decodeAudioData(audioData)
|
|
24
|
-
const channelData = [buffer.getChannelData(0)]
|
|
25
|
-
if (buffer.numberOfChannels > 1) {
|
|
26
|
-
channelData.push(buffer.getChannelData(1))
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
duration: buffer.duration,
|
|
31
|
-
channelData,
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
destroy() {
|
|
36
|
-
this.audioCtx?.close()
|
|
37
|
-
this.audioCtx = null
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default Decoder
|
package/src/event-emitter.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export interface GeneralEventTypes {
|
|
2
|
-
// the name of the event and the data it dispatches with
|
|
3
|
-
// e.g. 'entryCreated': { count: 1 }
|
|
4
|
-
[eventType: string]: unknown
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
class EventEmitter<EventTypes extends GeneralEventTypes> {
|
|
8
|
-
private eventTarget: EventTarget
|
|
9
|
-
|
|
10
|
-
constructor() {
|
|
11
|
-
this.eventTarget = new EventTarget()
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
protected emit<T extends keyof EventTypes>(eventType: T, detail?: EventTypes[T]): void {
|
|
15
|
-
const e = new CustomEvent(String(eventType), { detail })
|
|
16
|
-
this.eventTarget.dispatchEvent(e)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Subscribe to an event and return a function to unsubscribe */
|
|
20
|
-
on<T extends keyof EventTypes>(eventType: T, callback: (detail: EventTypes[T]) => void): () => void {
|
|
21
|
-
const handler = (e: Event) => {
|
|
22
|
-
if (e instanceof CustomEvent) {
|
|
23
|
-
callback(e.detail)
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const eventName = String(eventType)
|
|
28
|
-
|
|
29
|
-
this.eventTarget.addEventListener(eventName, handler)
|
|
30
|
-
|
|
31
|
-
return () => this.eventTarget.removeEventListener(eventName, handler)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export default EventEmitter
|