wavesurfer.js 7.11.1 → 7.12.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 +1 -1
- package/dist/__tests__/memory-leaks.test.d.ts +7 -0
- package/dist/__tests__/memory-leaks.test.js +184 -0
- package/dist/__tests__/player.test.js +107 -0
- package/dist/__tests__/renderer-utils.test.js +55 -2
- package/dist/draggable.d.ts +4 -0
- package/dist/draggable.js +4 -0
- package/dist/player.d.ts +21 -0
- package/dist/player.js +86 -0
- package/dist/plugins/envelope.cjs +1 -1
- package/dist/plugins/envelope.esm.js +1 -1
- package/dist/plugins/envelope.js +1 -1
- package/dist/plugins/envelope.min.js +1 -1
- package/dist/plugins/hover.cjs +1 -1
- package/dist/plugins/hover.d.ts +0 -3
- package/dist/plugins/hover.esm.js +1 -1
- package/dist/plugins/hover.js +1 -1
- package/dist/plugins/hover.min.js +1 -1
- package/dist/plugins/minimap.cjs +1 -1
- package/dist/plugins/minimap.esm.js +1 -1
- package/dist/plugins/minimap.js +1 -1
- package/dist/plugins/minimap.min.js +1 -1
- package/dist/plugins/record.cjs +1 -1
- package/dist/plugins/record.esm.js +1 -1
- package/dist/plugins/record.js +1 -1
- package/dist/plugins/record.min.js +1 -1
- package/dist/plugins/regions.cjs +1 -1
- package/dist/plugins/regions.esm.js +1 -1
- package/dist/plugins/regions.js +1 -1
- package/dist/plugins/regions.min.js +1 -1
- package/dist/plugins/timeline.cjs +1 -1
- package/dist/plugins/timeline.d.ts +1 -1
- package/dist/plugins/timeline.esm.js +1 -1
- package/dist/plugins/timeline.js +1 -1
- package/dist/plugins/timeline.min.js +1 -1
- package/dist/plugins/zoom.cjs +1 -1
- package/dist/plugins/zoom.esm.js +1 -1
- package/dist/plugins/zoom.js +1 -1
- package/dist/plugins/zoom.min.js +1 -1
- package/dist/reactive/__tests__/drag-stream.test.d.ts +1 -0
- package/dist/reactive/__tests__/drag-stream.test.js +199 -0
- package/dist/reactive/__tests__/event-stream-emitter.test.d.ts +1 -0
- package/dist/reactive/__tests__/event-stream-emitter.test.js +230 -0
- package/dist/reactive/__tests__/event-streams.test.d.ts +1 -0
- package/dist/reactive/__tests__/event-streams.test.js +278 -0
- package/dist/reactive/__tests__/media-event-bridge.test.d.ts +1 -0
- package/dist/reactive/__tests__/media-event-bridge.test.js +202 -0
- package/dist/reactive/__tests__/render-scheduler.test.d.ts +1 -0
- package/dist/reactive/__tests__/render-scheduler.test.js +216 -0
- package/dist/reactive/__tests__/scroll-stream.test.d.ts +1 -0
- package/dist/reactive/__tests__/scroll-stream.test.js +191 -0
- package/dist/reactive/__tests__/state-event-emitter.test.d.ts +1 -0
- package/dist/reactive/__tests__/state-event-emitter.test.js +234 -0
- package/dist/reactive/__tests__/store.test.d.ts +1 -0
- package/dist/reactive/__tests__/store.test.js +271 -0
- package/dist/reactive/drag-stream.d.ts +48 -0
- package/dist/reactive/drag-stream.js +146 -0
- package/dist/reactive/event-stream-emitter.d.ts +104 -0
- package/dist/reactive/event-stream-emitter.js +151 -0
- package/dist/reactive/event-streams.d.ts +65 -0
- package/dist/reactive/event-streams.js +146 -0
- package/dist/reactive/media-event-bridge.d.ts +51 -0
- package/dist/reactive/media-event-bridge.js +152 -0
- package/dist/reactive/render-scheduler.d.ts +43 -0
- package/dist/reactive/render-scheduler.js +75 -0
- package/dist/reactive/scroll-stream.d.ts +93 -0
- package/dist/reactive/scroll-stream.js +131 -0
- package/dist/reactive/state-event-emitter.d.ts +105 -0
- package/dist/reactive/state-event-emitter.js +213 -0
- package/dist/reactive/store.d.ts +66 -0
- package/dist/reactive/store.js +115 -0
- package/dist/renderer-utils.d.ts +12 -3
- package/dist/renderer-utils.js +33 -13
- package/dist/renderer.d.ts +2 -1
- package/dist/renderer.js +44 -42
- package/dist/state/__tests__/wavesurfer-state.test.d.ts +1 -0
- package/dist/state/__tests__/wavesurfer-state.test.js +268 -0
- package/dist/state/wavesurfer-state.d.ts +90 -0
- package/dist/state/wavesurfer-state.js +124 -0
- package/dist/types.d.ts +165 -1
- package/dist/wavesurfer.cjs +1 -1
- package/dist/wavesurfer.d.ts +16 -0
- package/dist/wavesurfer.esm.js +1 -1
- package/dist/wavesurfer.js +32 -0
- package/dist/wavesurfer.min.js +1 -1
- package/package.json +2 -3
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { createWaveSurferState } from '../wavesurfer-state';
|
|
2
|
+
describe('WaveSurferState', () => {
|
|
3
|
+
it('should create state with default values', () => {
|
|
4
|
+
const { state } = createWaveSurferState();
|
|
5
|
+
expect(state.currentTime.value).toBe(0);
|
|
6
|
+
expect(state.duration.value).toBe(0);
|
|
7
|
+
expect(state.isPlaying.value).toBe(false);
|
|
8
|
+
expect(state.isPaused.value).toBe(true);
|
|
9
|
+
expect(state.isSeeking.value).toBe(false);
|
|
10
|
+
expect(state.volume.value).toBe(1);
|
|
11
|
+
expect(state.playbackRate.value).toBe(1);
|
|
12
|
+
expect(state.audioBuffer.value).toBeNull();
|
|
13
|
+
expect(state.peaks.value).toBeNull();
|
|
14
|
+
expect(state.url.value).toBe('');
|
|
15
|
+
expect(state.zoom.value).toBe(0);
|
|
16
|
+
expect(state.scrollPosition.value).toBe(0);
|
|
17
|
+
});
|
|
18
|
+
describe('actions', () => {
|
|
19
|
+
it('should update currentTime', () => {
|
|
20
|
+
const { state, actions } = createWaveSurferState();
|
|
21
|
+
actions.setCurrentTime(10);
|
|
22
|
+
expect(state.currentTime.value).toBe(10);
|
|
23
|
+
});
|
|
24
|
+
it('should clamp currentTime to duration', () => {
|
|
25
|
+
const { state, actions } = createWaveSurferState();
|
|
26
|
+
actions.setDuration(100);
|
|
27
|
+
actions.setCurrentTime(150);
|
|
28
|
+
expect(state.currentTime.value).toBe(100);
|
|
29
|
+
});
|
|
30
|
+
it('should not allow negative currentTime', () => {
|
|
31
|
+
const { state, actions } = createWaveSurferState();
|
|
32
|
+
actions.setCurrentTime(-10);
|
|
33
|
+
expect(state.currentTime.value).toBe(0);
|
|
34
|
+
});
|
|
35
|
+
it('should update duration', () => {
|
|
36
|
+
const { state, actions } = createWaveSurferState();
|
|
37
|
+
actions.setDuration(120);
|
|
38
|
+
expect(state.duration.value).toBe(120);
|
|
39
|
+
});
|
|
40
|
+
it('should not allow negative duration', () => {
|
|
41
|
+
const { state, actions } = createWaveSurferState();
|
|
42
|
+
actions.setDuration(-10);
|
|
43
|
+
expect(state.duration.value).toBe(0);
|
|
44
|
+
});
|
|
45
|
+
it('should update isPlaying', () => {
|
|
46
|
+
const { state, actions } = createWaveSurferState();
|
|
47
|
+
actions.setPlaying(true);
|
|
48
|
+
expect(state.isPlaying.value).toBe(true);
|
|
49
|
+
actions.setPlaying(false);
|
|
50
|
+
expect(state.isPlaying.value).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
it('should update isSeeking', () => {
|
|
53
|
+
const { state, actions } = createWaveSurferState();
|
|
54
|
+
actions.setSeeking(true);
|
|
55
|
+
expect(state.isSeeking.value).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
it('should update volume', () => {
|
|
58
|
+
const { state, actions } = createWaveSurferState();
|
|
59
|
+
actions.setVolume(0.5);
|
|
60
|
+
expect(state.volume.value).toBe(0.5);
|
|
61
|
+
});
|
|
62
|
+
it('should clamp volume between 0 and 1', () => {
|
|
63
|
+
const { state, actions } = createWaveSurferState();
|
|
64
|
+
actions.setVolume(-0.5);
|
|
65
|
+
expect(state.volume.value).toBe(0);
|
|
66
|
+
actions.setVolume(1.5);
|
|
67
|
+
expect(state.volume.value).toBe(1);
|
|
68
|
+
});
|
|
69
|
+
it('should update playbackRate', () => {
|
|
70
|
+
const { state, actions } = createWaveSurferState();
|
|
71
|
+
actions.setPlaybackRate(2);
|
|
72
|
+
expect(state.playbackRate.value).toBe(2);
|
|
73
|
+
});
|
|
74
|
+
it('should clamp playbackRate between 0.1 and 16', () => {
|
|
75
|
+
const { state, actions } = createWaveSurferState();
|
|
76
|
+
actions.setPlaybackRate(0.05);
|
|
77
|
+
expect(state.playbackRate.value).toBe(0.1);
|
|
78
|
+
actions.setPlaybackRate(20);
|
|
79
|
+
expect(state.playbackRate.value).toBe(16);
|
|
80
|
+
});
|
|
81
|
+
it('should update audioBuffer', () => {
|
|
82
|
+
const { state, actions } = createWaveSurferState();
|
|
83
|
+
const buffer = { duration: 120 };
|
|
84
|
+
actions.setAudioBuffer(buffer);
|
|
85
|
+
expect(state.audioBuffer.value).toBe(buffer);
|
|
86
|
+
});
|
|
87
|
+
it('should update duration when audioBuffer is set', () => {
|
|
88
|
+
const { state, actions } = createWaveSurferState();
|
|
89
|
+
const buffer = { duration: 120 };
|
|
90
|
+
actions.setAudioBuffer(buffer);
|
|
91
|
+
expect(state.duration.value).toBe(120);
|
|
92
|
+
});
|
|
93
|
+
it('should update peaks', () => {
|
|
94
|
+
const { state, actions } = createWaveSurferState();
|
|
95
|
+
const peaks = [new Float32Array([1, 2, 3])];
|
|
96
|
+
actions.setPeaks(peaks);
|
|
97
|
+
expect(state.peaks.value).toBe(peaks);
|
|
98
|
+
});
|
|
99
|
+
it('should update url', () => {
|
|
100
|
+
const { state, actions } = createWaveSurferState();
|
|
101
|
+
actions.setUrl('/audio.mp3');
|
|
102
|
+
expect(state.url.value).toBe('/audio.mp3');
|
|
103
|
+
});
|
|
104
|
+
it('should update zoom', () => {
|
|
105
|
+
const { state, actions } = createWaveSurferState();
|
|
106
|
+
actions.setZoom(100);
|
|
107
|
+
expect(state.zoom.value).toBe(100);
|
|
108
|
+
});
|
|
109
|
+
it('should not allow negative zoom', () => {
|
|
110
|
+
const { state, actions } = createWaveSurferState();
|
|
111
|
+
actions.setZoom(-10);
|
|
112
|
+
expect(state.zoom.value).toBe(0);
|
|
113
|
+
});
|
|
114
|
+
it('should update scrollPosition', () => {
|
|
115
|
+
const { state, actions } = createWaveSurferState();
|
|
116
|
+
actions.setScrollPosition(50);
|
|
117
|
+
expect(state.scrollPosition.value).toBe(50);
|
|
118
|
+
});
|
|
119
|
+
it('should not allow negative scrollPosition', () => {
|
|
120
|
+
const { state, actions } = createWaveSurferState();
|
|
121
|
+
actions.setScrollPosition(-10);
|
|
122
|
+
expect(state.scrollPosition.value).toBe(0);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe('computed values', () => {
|
|
126
|
+
it('should compute isPaused from isPlaying', () => {
|
|
127
|
+
const { state, actions } = createWaveSurferState();
|
|
128
|
+
expect(state.isPaused.value).toBe(true);
|
|
129
|
+
actions.setPlaying(true);
|
|
130
|
+
expect(state.isPaused.value).toBe(false);
|
|
131
|
+
actions.setPlaying(false);
|
|
132
|
+
expect(state.isPaused.value).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
it('should compute canPlay from audioBuffer', () => {
|
|
135
|
+
const { state, actions } = createWaveSurferState();
|
|
136
|
+
expect(state.canPlay.value).toBe(false);
|
|
137
|
+
const buffer = { duration: 120 };
|
|
138
|
+
actions.setAudioBuffer(buffer);
|
|
139
|
+
expect(state.canPlay.value).toBe(true);
|
|
140
|
+
actions.setAudioBuffer(null);
|
|
141
|
+
expect(state.canPlay.value).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
it('should compute isReady from canPlay and duration', () => {
|
|
144
|
+
const { state, actions } = createWaveSurferState();
|
|
145
|
+
expect(state.isReady.value).toBe(false);
|
|
146
|
+
// Only buffer, no duration
|
|
147
|
+
const buffer = { duration: 0 };
|
|
148
|
+
actions.setAudioBuffer(buffer);
|
|
149
|
+
expect(state.isReady.value).toBe(false);
|
|
150
|
+
// Both buffer and duration
|
|
151
|
+
actions.setDuration(120);
|
|
152
|
+
expect(state.isReady.value).toBe(true);
|
|
153
|
+
// Remove buffer
|
|
154
|
+
actions.setAudioBuffer(null);
|
|
155
|
+
expect(state.isReady.value).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
it('should compute progress from currentTime', () => {
|
|
158
|
+
const { state, actions } = createWaveSurferState();
|
|
159
|
+
expect(state.progress.value).toBe(0);
|
|
160
|
+
actions.setCurrentTime(50);
|
|
161
|
+
expect(state.progress.value).toBe(50);
|
|
162
|
+
});
|
|
163
|
+
it('should compute progressPercent from currentTime and duration', () => {
|
|
164
|
+
const { state, actions } = createWaveSurferState();
|
|
165
|
+
expect(state.progressPercent.value).toBe(0);
|
|
166
|
+
actions.setDuration(100);
|
|
167
|
+
actions.setCurrentTime(25);
|
|
168
|
+
expect(state.progressPercent.value).toBe(0.25);
|
|
169
|
+
actions.setCurrentTime(50);
|
|
170
|
+
expect(state.progressPercent.value).toBe(0.5);
|
|
171
|
+
actions.setCurrentTime(100);
|
|
172
|
+
expect(state.progressPercent.value).toBe(1);
|
|
173
|
+
});
|
|
174
|
+
it('should handle progressPercent with zero duration', () => {
|
|
175
|
+
const { state, actions } = createWaveSurferState();
|
|
176
|
+
actions.setDuration(0);
|
|
177
|
+
actions.setCurrentTime(10);
|
|
178
|
+
expect(state.progressPercent.value).toBe(0);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
describe('subscriptions', () => {
|
|
182
|
+
it('should notify subscribers on state changes', () => {
|
|
183
|
+
const { state, actions } = createWaveSurferState();
|
|
184
|
+
const callback = jest.fn();
|
|
185
|
+
state.isPlaying.subscribe(callback);
|
|
186
|
+
actions.setPlaying(true);
|
|
187
|
+
expect(callback).toHaveBeenCalledWith(true);
|
|
188
|
+
});
|
|
189
|
+
it('should notify computed value subscribers', () => {
|
|
190
|
+
const { state, actions } = createWaveSurferState();
|
|
191
|
+
const callback = jest.fn();
|
|
192
|
+
state.progressPercent.subscribe(callback);
|
|
193
|
+
actions.setDuration(100);
|
|
194
|
+
actions.setCurrentTime(50);
|
|
195
|
+
expect(callback).toHaveBeenCalledWith(0.5);
|
|
196
|
+
});
|
|
197
|
+
it('should work with multiple subscribers', () => {
|
|
198
|
+
const { state, actions } = createWaveSurferState();
|
|
199
|
+
const callback1 = jest.fn();
|
|
200
|
+
const callback2 = jest.fn();
|
|
201
|
+
state.isPlaying.subscribe(callback1);
|
|
202
|
+
state.isPlaying.subscribe(callback2);
|
|
203
|
+
actions.setPlaying(true);
|
|
204
|
+
expect(callback1).toHaveBeenCalledWith(true);
|
|
205
|
+
expect(callback2).toHaveBeenCalledWith(true);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
describe('state isolation', () => {
|
|
209
|
+
it('should create independent state instances', () => {
|
|
210
|
+
const instance1 = createWaveSurferState();
|
|
211
|
+
const instance2 = createWaveSurferState();
|
|
212
|
+
instance1.actions.setCurrentTime(10);
|
|
213
|
+
instance2.actions.setCurrentTime(20);
|
|
214
|
+
expect(instance1.state.currentTime.value).toBe(10);
|
|
215
|
+
expect(instance2.state.currentTime.value).toBe(20);
|
|
216
|
+
});
|
|
217
|
+
it('should not share state between instances', () => {
|
|
218
|
+
const instance1 = createWaveSurferState();
|
|
219
|
+
const instance2 = createWaveSurferState();
|
|
220
|
+
instance1.actions.setPlaying(true);
|
|
221
|
+
expect(instance1.state.isPlaying.value).toBe(true);
|
|
222
|
+
expect(instance2.state.isPlaying.value).toBe(false);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
describe('complex state updates', () => {
|
|
226
|
+
it('should handle multiple rapid updates', () => {
|
|
227
|
+
const { state, actions } = createWaveSurferState();
|
|
228
|
+
const values = [];
|
|
229
|
+
state.currentTime.subscribe((time) => values.push(time));
|
|
230
|
+
// Start from 1 since 0 is the initial value (no change from initial)
|
|
231
|
+
for (let i = 1; i < 100; i++) {
|
|
232
|
+
actions.setCurrentTime(i);
|
|
233
|
+
}
|
|
234
|
+
expect(values).toHaveLength(99);
|
|
235
|
+
expect(state.currentTime.value).toBe(99);
|
|
236
|
+
});
|
|
237
|
+
it('should maintain consistency across dependent computed values', () => {
|
|
238
|
+
const { state, actions } = createWaveSurferState();
|
|
239
|
+
actions.setDuration(100);
|
|
240
|
+
actions.setCurrentTime(50);
|
|
241
|
+
const buffer = { duration: 100 };
|
|
242
|
+
actions.setAudioBuffer(buffer);
|
|
243
|
+
expect(state.progressPercent.value).toBe(0.5);
|
|
244
|
+
expect(state.canPlay.value).toBe(true);
|
|
245
|
+
expect(state.isReady.value).toBe(true);
|
|
246
|
+
actions.setCurrentTime(75);
|
|
247
|
+
expect(state.progressPercent.value).toBe(0.75);
|
|
248
|
+
expect(state.progress.value).toBe(75);
|
|
249
|
+
});
|
|
250
|
+
it('should handle state reset correctly', () => {
|
|
251
|
+
const { state, actions } = createWaveSurferState();
|
|
252
|
+
// Set some state
|
|
253
|
+
actions.setDuration(100);
|
|
254
|
+
actions.setCurrentTime(50);
|
|
255
|
+
actions.setPlaying(true);
|
|
256
|
+
actions.setVolume(0.5);
|
|
257
|
+
// Reset
|
|
258
|
+
actions.setCurrentTime(0);
|
|
259
|
+
actions.setDuration(0);
|
|
260
|
+
actions.setPlaying(false);
|
|
261
|
+
actions.setVolume(1);
|
|
262
|
+
expect(state.currentTime.value).toBe(0);
|
|
263
|
+
expect(state.duration.value).toBe(0);
|
|
264
|
+
expect(state.isPlaying.value).toBe(false);
|
|
265
|
+
expect(state.volume.value).toBe(1);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized reactive state for WaveSurfer
|
|
3
|
+
*
|
|
4
|
+
* This module provides a single source of truth for all WaveSurfer state.
|
|
5
|
+
* State is managed using reactive signals that automatically notify subscribers.
|
|
6
|
+
*/
|
|
7
|
+
import { type Signal, type WritableSignal } from '../reactive/store.js';
|
|
8
|
+
/**
|
|
9
|
+
* Read-only reactive state for WaveSurfer
|
|
10
|
+
*/
|
|
11
|
+
export interface WaveSurferState {
|
|
12
|
+
readonly currentTime: Signal<number>;
|
|
13
|
+
readonly duration: Signal<number>;
|
|
14
|
+
readonly isPlaying: Signal<boolean>;
|
|
15
|
+
readonly isPaused: Signal<boolean>;
|
|
16
|
+
readonly isSeeking: Signal<boolean>;
|
|
17
|
+
readonly volume: Signal<number>;
|
|
18
|
+
readonly playbackRate: Signal<number>;
|
|
19
|
+
readonly audioBuffer: Signal<AudioBuffer | null>;
|
|
20
|
+
readonly peaks: Signal<Array<Float32Array | number[]> | null>;
|
|
21
|
+
readonly url: Signal<string>;
|
|
22
|
+
readonly zoom: Signal<number>;
|
|
23
|
+
readonly scrollPosition: Signal<number>;
|
|
24
|
+
readonly canPlay: Signal<boolean>;
|
|
25
|
+
readonly isReady: Signal<boolean>;
|
|
26
|
+
readonly progress: Signal<number>;
|
|
27
|
+
readonly progressPercent: Signal<number>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Actions for updating WaveSurfer state
|
|
31
|
+
*/
|
|
32
|
+
export interface WaveSurferActions {
|
|
33
|
+
setCurrentTime: (time: number) => void;
|
|
34
|
+
setDuration: (duration: number) => void;
|
|
35
|
+
setPlaying: (playing: boolean) => void;
|
|
36
|
+
setSeeking: (seeking: boolean) => void;
|
|
37
|
+
setVolume: (volume: number) => void;
|
|
38
|
+
setPlaybackRate: (rate: number) => void;
|
|
39
|
+
setAudioBuffer: (buffer: AudioBuffer | null) => void;
|
|
40
|
+
setPeaks: (peaks: Array<Float32Array | number[]> | null) => void;
|
|
41
|
+
setUrl: (url: string) => void;
|
|
42
|
+
setZoom: (zoom: number) => void;
|
|
43
|
+
setScrollPosition: (position: number) => void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Optional Player signals to compose into WaveSurferState
|
|
47
|
+
* When provided, these signals from Player are used directly instead of creating new ones
|
|
48
|
+
* Note: Signals must be WritableSignal to allow state actions to update them
|
|
49
|
+
*/
|
|
50
|
+
export interface PlayerSignals {
|
|
51
|
+
isPlaying?: WritableSignal<boolean>;
|
|
52
|
+
currentTime?: WritableSignal<number>;
|
|
53
|
+
duration?: WritableSignal<number>;
|
|
54
|
+
volume?: WritableSignal<number>;
|
|
55
|
+
playbackRate?: WritableSignal<number>;
|
|
56
|
+
isSeeking?: WritableSignal<boolean>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Create a new WaveSurfer state instance
|
|
60
|
+
*
|
|
61
|
+
* @param playerSignals - Optional signals from Player to compose with WaveSurfer state
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // Without Player signals (standalone)
|
|
66
|
+
* const { state, actions } = createWaveSurferState()
|
|
67
|
+
*
|
|
68
|
+
* // With Player signals (composed)
|
|
69
|
+
* const { state, actions } = createWaveSurferState({
|
|
70
|
+
* isPlaying: player.isPlayingSignal,
|
|
71
|
+
* currentTime: player.currentTimeSignal,
|
|
72
|
+
* // ...
|
|
73
|
+
* })
|
|
74
|
+
*
|
|
75
|
+
* // Read state
|
|
76
|
+
* console.log(state.isPlaying.value)
|
|
77
|
+
*
|
|
78
|
+
* // Update state
|
|
79
|
+
* actions.setPlaying(true)
|
|
80
|
+
*
|
|
81
|
+
* // Subscribe to changes
|
|
82
|
+
* state.isPlaying.subscribe(playing => {
|
|
83
|
+
* console.log('Playing:', playing)
|
|
84
|
+
* })
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export declare function createWaveSurferState(playerSignals?: PlayerSignals): {
|
|
88
|
+
state: WaveSurferState;
|
|
89
|
+
actions: WaveSurferActions;
|
|
90
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized reactive state for WaveSurfer
|
|
3
|
+
*
|
|
4
|
+
* This module provides a single source of truth for all WaveSurfer state.
|
|
5
|
+
* State is managed using reactive signals that automatically notify subscribers.
|
|
6
|
+
*/
|
|
7
|
+
import { signal, computed } from '../reactive/store.js';
|
|
8
|
+
/**
|
|
9
|
+
* Create a new WaveSurfer state instance
|
|
10
|
+
*
|
|
11
|
+
* @param playerSignals - Optional signals from Player to compose with WaveSurfer state
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Without Player signals (standalone)
|
|
16
|
+
* const { state, actions } = createWaveSurferState()
|
|
17
|
+
*
|
|
18
|
+
* // With Player signals (composed)
|
|
19
|
+
* const { state, actions } = createWaveSurferState({
|
|
20
|
+
* isPlaying: player.isPlayingSignal,
|
|
21
|
+
* currentTime: player.currentTimeSignal,
|
|
22
|
+
* // ...
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* // Read state
|
|
26
|
+
* console.log(state.isPlaying.value)
|
|
27
|
+
*
|
|
28
|
+
* // Update state
|
|
29
|
+
* actions.setPlaying(true)
|
|
30
|
+
*
|
|
31
|
+
* // Subscribe to changes
|
|
32
|
+
* state.isPlaying.subscribe(playing => {
|
|
33
|
+
* console.log('Playing:', playing)
|
|
34
|
+
* })
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function createWaveSurferState(playerSignals) {
|
|
38
|
+
var _a, _b, _c, _d, _e, _f;
|
|
39
|
+
// Use Player signals if provided, otherwise create new ones
|
|
40
|
+
const currentTime = (_a = playerSignals === null || playerSignals === void 0 ? void 0 : playerSignals.currentTime) !== null && _a !== void 0 ? _a : signal(0);
|
|
41
|
+
const duration = (_b = playerSignals === null || playerSignals === void 0 ? void 0 : playerSignals.duration) !== null && _b !== void 0 ? _b : signal(0);
|
|
42
|
+
const isPlaying = (_c = playerSignals === null || playerSignals === void 0 ? void 0 : playerSignals.isPlaying) !== null && _c !== void 0 ? _c : signal(false);
|
|
43
|
+
const isSeeking = (_d = playerSignals === null || playerSignals === void 0 ? void 0 : playerSignals.isSeeking) !== null && _d !== void 0 ? _d : signal(false);
|
|
44
|
+
const volume = (_e = playerSignals === null || playerSignals === void 0 ? void 0 : playerSignals.volume) !== null && _e !== void 0 ? _e : signal(1);
|
|
45
|
+
const playbackRate = (_f = playerSignals === null || playerSignals === void 0 ? void 0 : playerSignals.playbackRate) !== null && _f !== void 0 ? _f : signal(1);
|
|
46
|
+
// WaveSurfer-specific signals (not in Player)
|
|
47
|
+
const audioBuffer = signal(null);
|
|
48
|
+
const peaks = signal(null);
|
|
49
|
+
const url = signal('');
|
|
50
|
+
const zoom = signal(0);
|
|
51
|
+
const scrollPosition = signal(0);
|
|
52
|
+
// Computed values (derived state)
|
|
53
|
+
const isPaused = computed(() => !isPlaying.value, [isPlaying]);
|
|
54
|
+
const canPlay = computed(() => audioBuffer.value !== null, [audioBuffer]);
|
|
55
|
+
const isReady = computed(() => {
|
|
56
|
+
return canPlay.value && duration.value > 0;
|
|
57
|
+
}, [canPlay, duration]);
|
|
58
|
+
const progress = computed(() => currentTime.value, [currentTime]);
|
|
59
|
+
const progressPercent = computed(() => {
|
|
60
|
+
return duration.value > 0 ? currentTime.value / duration.value : 0;
|
|
61
|
+
}, [currentTime, duration]);
|
|
62
|
+
// Public read-only state
|
|
63
|
+
const state = {
|
|
64
|
+
currentTime,
|
|
65
|
+
duration,
|
|
66
|
+
isPlaying,
|
|
67
|
+
isPaused,
|
|
68
|
+
isSeeking,
|
|
69
|
+
volume,
|
|
70
|
+
playbackRate,
|
|
71
|
+
audioBuffer,
|
|
72
|
+
peaks,
|
|
73
|
+
url,
|
|
74
|
+
zoom,
|
|
75
|
+
scrollPosition,
|
|
76
|
+
canPlay,
|
|
77
|
+
isReady,
|
|
78
|
+
progress,
|
|
79
|
+
progressPercent,
|
|
80
|
+
};
|
|
81
|
+
// Actions that modify state
|
|
82
|
+
const actions = {
|
|
83
|
+
setCurrentTime: (time) => {
|
|
84
|
+
const clampedTime = Math.max(0, Math.min(duration.value || Infinity, time));
|
|
85
|
+
currentTime.set(clampedTime);
|
|
86
|
+
},
|
|
87
|
+
setDuration: (d) => {
|
|
88
|
+
duration.set(Math.max(0, d));
|
|
89
|
+
},
|
|
90
|
+
setPlaying: (playing) => {
|
|
91
|
+
isPlaying.set(playing);
|
|
92
|
+
},
|
|
93
|
+
setSeeking: (seeking) => {
|
|
94
|
+
isSeeking.set(seeking);
|
|
95
|
+
},
|
|
96
|
+
setVolume: (v) => {
|
|
97
|
+
const clampedVolume = Math.max(0, Math.min(1, v));
|
|
98
|
+
volume.set(clampedVolume);
|
|
99
|
+
},
|
|
100
|
+
setPlaybackRate: (rate) => {
|
|
101
|
+
const clampedRate = Math.max(0.1, Math.min(16, rate));
|
|
102
|
+
playbackRate.set(clampedRate);
|
|
103
|
+
},
|
|
104
|
+
setAudioBuffer: (buffer) => {
|
|
105
|
+
audioBuffer.set(buffer);
|
|
106
|
+
if (buffer) {
|
|
107
|
+
duration.set(buffer.duration);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
setPeaks: (p) => {
|
|
111
|
+
peaks.set(p);
|
|
112
|
+
},
|
|
113
|
+
setUrl: (u) => {
|
|
114
|
+
url.set(u);
|
|
115
|
+
},
|
|
116
|
+
setZoom: (z) => {
|
|
117
|
+
zoom.set(Math.max(0, z));
|
|
118
|
+
},
|
|
119
|
+
setScrollPosition: (pos) => {
|
|
120
|
+
scrollPosition.set(Math.max(0, pos));
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
return { state, actions };
|
|
124
|
+
}
|