wavesurfer.js 7.11.0 → 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.d.ts +1 -0
- package/dist/__tests__/renderer-utils.test.js +373 -0
- package/dist/decoder.js +12 -4
- package/dist/draggable.d.ts +4 -0
- package/dist/draggable.js +20 -5
- package/dist/fetcher.js +14 -23
- 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.d.ts +10 -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 +126 -0
- package/dist/renderer-utils.js +252 -0
- package/dist/renderer.d.ts +4 -4
- package/dist/renderer.js +133 -197
- 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 +167 -1
- package/dist/wavesurfer.cjs +1 -1
- package/dist/wavesurfer.d.ts +18 -0
- package/dist/wavesurfer.esm.js +1 -1
- package/dist/wavesurfer.js +46 -8
- package/dist/wavesurfer.min.js +1 -1
- package/dist/webaudio.js +4 -0
- package/package.json +6 -6
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { MAX_CANVAS_WIDTH, MAX_NODES, calculateBarHeights, calculateBarRenderConfig, calculateBarSegments, calculateLinePaths, calculateScrollPercentages, calculateSingleCanvasWidth, calculateVerticalScale, calculateWaveformLayout, clampToUnit, clampWidthToBarGrid, getLazyRenderRange, getPixelRatio, getRelativePointerPosition, resolveBarYPosition, resolveChannelHeight, resolveColorValue, shouldClearCanvases, shouldRenderBars, sliceChannelData, } from '../renderer-utils.js';
|
|
2
|
+
describe('renderer-utils', () => {
|
|
3
|
+
describe('clampToUnit', () => {
|
|
4
|
+
it('clamps numbers to the [0, 1] range', () => {
|
|
5
|
+
expect(clampToUnit(-0.5)).toBe(0);
|
|
6
|
+
expect(clampToUnit(0.3)).toBe(0.3);
|
|
7
|
+
expect(clampToUnit(1.8)).toBe(1);
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
describe('calculateBarRenderConfig', () => {
|
|
11
|
+
const options = {
|
|
12
|
+
container: document.createElement('div'),
|
|
13
|
+
barWidth: 2,
|
|
14
|
+
barGap: 1,
|
|
15
|
+
barRadius: 3,
|
|
16
|
+
};
|
|
17
|
+
it('derives spacing values and scaling information', () => {
|
|
18
|
+
const config = calculateBarRenderConfig({
|
|
19
|
+
width: 100,
|
|
20
|
+
height: 50,
|
|
21
|
+
length: 10,
|
|
22
|
+
options,
|
|
23
|
+
pixelRatio: 2,
|
|
24
|
+
});
|
|
25
|
+
expect(config).toEqual({
|
|
26
|
+
halfHeight: 25,
|
|
27
|
+
barWidth: 4,
|
|
28
|
+
barGap: 2,
|
|
29
|
+
barRadius: 3,
|
|
30
|
+
barIndexScale: 100 / ((4 + 2) * 10),
|
|
31
|
+
barSpacing: 6,
|
|
32
|
+
barMinHeight: 0,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
describe('calculateBarHeights', () => {
|
|
37
|
+
it('returns rounded heights and ensures total height is at least 1', () => {
|
|
38
|
+
expect(calculateBarHeights({
|
|
39
|
+
maxTop: 0.5,
|
|
40
|
+
maxBottom: 0.25,
|
|
41
|
+
halfHeight: 20,
|
|
42
|
+
vScale: 1,
|
|
43
|
+
})).toEqual({ topHeight: 10, totalHeight: 15 });
|
|
44
|
+
expect(calculateBarHeights({
|
|
45
|
+
maxTop: 0,
|
|
46
|
+
maxBottom: 0,
|
|
47
|
+
halfHeight: 20,
|
|
48
|
+
vScale: 1,
|
|
49
|
+
})).toEqual({ topHeight: 0, totalHeight: 1 });
|
|
50
|
+
});
|
|
51
|
+
it('ensures total height is at least barMinHeight', () => {
|
|
52
|
+
expect(calculateBarHeights({
|
|
53
|
+
maxTop: 0,
|
|
54
|
+
maxBottom: 0,
|
|
55
|
+
halfHeight: 20,
|
|
56
|
+
vScale: 1,
|
|
57
|
+
barMinHeight: 10,
|
|
58
|
+
})).toEqual({ topHeight: 5, totalHeight: 10 });
|
|
59
|
+
expect(calculateBarHeights({
|
|
60
|
+
maxTop: 0,
|
|
61
|
+
maxBottom: 0,
|
|
62
|
+
halfHeight: 20,
|
|
63
|
+
vScale: 1,
|
|
64
|
+
barMinHeight: 10,
|
|
65
|
+
barAlign: 'top',
|
|
66
|
+
})).toEqual({ topHeight: 0, totalHeight: 10 });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('resolveBarYPosition', () => {
|
|
70
|
+
const baseArgs = {
|
|
71
|
+
halfHeight: 20,
|
|
72
|
+
topHeight: 10,
|
|
73
|
+
totalHeight: 20,
|
|
74
|
+
canvasHeight: 40,
|
|
75
|
+
};
|
|
76
|
+
it('positions bars relative to alignment', () => {
|
|
77
|
+
expect(resolveBarYPosition(Object.assign({ barAlign: 'top' }, baseArgs))).toBe(0);
|
|
78
|
+
expect(resolveBarYPosition(Object.assign({ barAlign: 'bottom' }, baseArgs))).toBe(20);
|
|
79
|
+
expect(resolveBarYPosition(Object.assign({ barAlign: undefined }, baseArgs))).toBe(10);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('calculateBarSegments', () => {
|
|
83
|
+
const options = {
|
|
84
|
+
container: document.createElement('div'),
|
|
85
|
+
};
|
|
86
|
+
it('aggregates bar segments across the channel data', () => {
|
|
87
|
+
const { barIndexScale, barSpacing, barWidth, halfHeight } = calculateBarRenderConfig({
|
|
88
|
+
width: 6,
|
|
89
|
+
height: 20,
|
|
90
|
+
length: 6,
|
|
91
|
+
options,
|
|
92
|
+
pixelRatio: 1,
|
|
93
|
+
});
|
|
94
|
+
const segments = calculateBarSegments({
|
|
95
|
+
channelData: [
|
|
96
|
+
new Float32Array([0.2, -0.4, 0.6, -0.8, 1, -1]),
|
|
97
|
+
new Float32Array([0.1, -0.2, 0.3, -0.4, 0.5, -0.6]),
|
|
98
|
+
],
|
|
99
|
+
barIndexScale,
|
|
100
|
+
barSpacing,
|
|
101
|
+
barWidth,
|
|
102
|
+
halfHeight,
|
|
103
|
+
vScale: 1,
|
|
104
|
+
canvasHeight: 40,
|
|
105
|
+
barAlign: undefined,
|
|
106
|
+
barMinHeight: 0,
|
|
107
|
+
});
|
|
108
|
+
expect(segments).toEqual([
|
|
109
|
+
{ x: 0, y: 8, width: 1, height: 3 },
|
|
110
|
+
{ x: 1, y: 6, width: 1, height: 6 },
|
|
111
|
+
{ x: 2, y: 4, width: 1, height: 9 },
|
|
112
|
+
{ x: 3, y: 2, width: 1, height: 12 },
|
|
113
|
+
{ x: 4, y: 0, width: 1, height: 15 },
|
|
114
|
+
{ x: 5, y: 0, width: 1, height: 16 },
|
|
115
|
+
]);
|
|
116
|
+
});
|
|
117
|
+
it('ensures bars are at least barMinHeight tall', () => {
|
|
118
|
+
const height = 40;
|
|
119
|
+
const length = 10;
|
|
120
|
+
const { barIndexScale, barSpacing, barWidth, halfHeight } = calculateBarRenderConfig({
|
|
121
|
+
width: 100,
|
|
122
|
+
height,
|
|
123
|
+
length,
|
|
124
|
+
options,
|
|
125
|
+
pixelRatio: 1,
|
|
126
|
+
});
|
|
127
|
+
const segments = calculateBarSegments({
|
|
128
|
+
channelData: [
|
|
129
|
+
new Float32Array(length).fill(0.001), // Very small values
|
|
130
|
+
],
|
|
131
|
+
barIndexScale,
|
|
132
|
+
barSpacing,
|
|
133
|
+
barWidth,
|
|
134
|
+
halfHeight,
|
|
135
|
+
vScale: 1,
|
|
136
|
+
canvasHeight: height / 2,
|
|
137
|
+
barAlign: undefined,
|
|
138
|
+
barMinHeight: 10,
|
|
139
|
+
});
|
|
140
|
+
expect(segments.length).toBeGreaterThan(0);
|
|
141
|
+
expect(segments[0].height).toBe(10);
|
|
142
|
+
expect(segments[0].y).toBe(15); // Centered: 20 - 10/2
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe('getRelativePointerPosition', () => {
|
|
146
|
+
it('returns pointer coordinates as relative offsets', () => {
|
|
147
|
+
const rect = {
|
|
148
|
+
left: 10,
|
|
149
|
+
top: 20,
|
|
150
|
+
width: 200,
|
|
151
|
+
height: 100,
|
|
152
|
+
};
|
|
153
|
+
expect(getRelativePointerPosition(rect, 110, 70)).toEqual([0.5, 0.5]);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
describe('resolveChannelHeight', () => {
|
|
157
|
+
it('returns numeric height when provided', () => {
|
|
158
|
+
expect(resolveChannelHeight({
|
|
159
|
+
optionsHeight: 150,
|
|
160
|
+
parentHeight: 0,
|
|
161
|
+
numberOfChannels: 2,
|
|
162
|
+
})).toBe(150);
|
|
163
|
+
});
|
|
164
|
+
it('splits height across channels when auto with overlays disabled', () => {
|
|
165
|
+
const splitChannels = [{ overlay: false }, { overlay: false }];
|
|
166
|
+
expect(resolveChannelHeight({
|
|
167
|
+
optionsHeight: 'auto',
|
|
168
|
+
optionsSplitChannels: splitChannels,
|
|
169
|
+
parentHeight: 200,
|
|
170
|
+
numberOfChannels: 2,
|
|
171
|
+
})).toBe(100);
|
|
172
|
+
});
|
|
173
|
+
it('falls back to default height when invalid', () => {
|
|
174
|
+
expect(resolveChannelHeight({
|
|
175
|
+
optionsHeight: 'invalid',
|
|
176
|
+
parentHeight: 0,
|
|
177
|
+
numberOfChannels: 2,
|
|
178
|
+
defaultHeight: 75,
|
|
179
|
+
})).toBe(75);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
describe('getPixelRatio', () => {
|
|
183
|
+
it('never returns less than 1', () => {
|
|
184
|
+
expect(getPixelRatio(undefined)).toBe(1);
|
|
185
|
+
expect(getPixelRatio(0.5)).toBe(1);
|
|
186
|
+
expect(getPixelRatio(2)).toBe(2);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe('shouldRenderBars', () => {
|
|
190
|
+
const options = { container: document.createElement('div') };
|
|
191
|
+
it('returns true when any bar option is configured', () => {
|
|
192
|
+
expect(shouldRenderBars(Object.assign(Object.assign({}, options), { barWidth: 1 }))).toBe(true);
|
|
193
|
+
expect(shouldRenderBars(Object.assign(Object.assign({}, options), { barGap: 2 }))).toBe(true);
|
|
194
|
+
expect(shouldRenderBars(Object.assign(Object.assign({}, options), { barAlign: 'top' }))).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
it('returns false when bars are not configured', () => {
|
|
197
|
+
expect(shouldRenderBars(options)).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
describe('resolveColorValue', () => {
|
|
201
|
+
const canvas = document.createElement('canvas');
|
|
202
|
+
let createLinearGradient;
|
|
203
|
+
let addColorStop;
|
|
204
|
+
beforeEach(() => {
|
|
205
|
+
createLinearGradient = jest.fn(() => ({ addColorStop }));
|
|
206
|
+
addColorStop = jest.fn();
|
|
207
|
+
jest.spyOn(document, 'createElement').mockImplementation(() => canvas);
|
|
208
|
+
jest
|
|
209
|
+
.spyOn(canvas, 'getContext')
|
|
210
|
+
.mockImplementation(() => ({ createLinearGradient }));
|
|
211
|
+
});
|
|
212
|
+
afterEach(() => {
|
|
213
|
+
jest.restoreAllMocks();
|
|
214
|
+
});
|
|
215
|
+
it('returns string values unchanged', () => {
|
|
216
|
+
expect(resolveColorValue('#000', 2)).toBe('#000');
|
|
217
|
+
});
|
|
218
|
+
it('falls back to default gray when gradient list is empty', () => {
|
|
219
|
+
expect(resolveColorValue([], 2)).toBe('#999');
|
|
220
|
+
});
|
|
221
|
+
it('uses the single color when gradient list has one item', () => {
|
|
222
|
+
expect(resolveColorValue(['#111'], 2)).toBe('#111');
|
|
223
|
+
});
|
|
224
|
+
it('creates a canvas gradient for multiple colors', () => {
|
|
225
|
+
const gradient = resolveColorValue(['#000', '#fff'], 2);
|
|
226
|
+
expect(createLinearGradient).toHaveBeenCalledWith(0, 0, 0, 300);
|
|
227
|
+
expect(addColorStop).toHaveBeenCalledTimes(2);
|
|
228
|
+
expect(addColorStop).toHaveBeenNthCalledWith(1, 0, '#000');
|
|
229
|
+
expect(addColorStop).toHaveBeenNthCalledWith(2, 1, '#fff');
|
|
230
|
+
expect(gradient.addColorStop).toBe(addColorStop);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
describe('calculateWaveformLayout', () => {
|
|
234
|
+
const baseArgs = {
|
|
235
|
+
duration: 2,
|
|
236
|
+
parentWidth: 300,
|
|
237
|
+
pixelRatio: 1,
|
|
238
|
+
};
|
|
239
|
+
it('uses parent width when not scrollable and fillParent is true', () => {
|
|
240
|
+
expect(calculateWaveformLayout(Object.assign(Object.assign({}, baseArgs), { minPxPerSec: 10, fillParent: true }))).toEqual({
|
|
241
|
+
scrollWidth: 20,
|
|
242
|
+
isScrollable: false,
|
|
243
|
+
useParentWidth: true,
|
|
244
|
+
width: 300,
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
it('uses scroll width when waveform exceeds parent width', () => {
|
|
248
|
+
expect(calculateWaveformLayout(Object.assign(Object.assign({}, baseArgs), { minPxPerSec: 500, fillParent: true }))).toEqual({
|
|
249
|
+
scrollWidth: 1000,
|
|
250
|
+
isScrollable: true,
|
|
251
|
+
useParentWidth: false,
|
|
252
|
+
width: 1000,
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
describe('clampWidthToBarGrid', () => {
|
|
257
|
+
const options = { container: document.createElement('div'), barWidth: 2, barGap: 1 };
|
|
258
|
+
it('returns original width when bars are disabled', () => {
|
|
259
|
+
expect(clampWidthToBarGrid(123, { container: document.createElement('div') })).toBe(123);
|
|
260
|
+
});
|
|
261
|
+
it('clamps width down to align with bar grid spacing', () => {
|
|
262
|
+
expect(clampWidthToBarGrid(10, options)).toBe(9);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
describe('calculateSingleCanvasWidth', () => {
|
|
266
|
+
const options = { container: document.createElement('div'), barWidth: 2, barGap: 1 };
|
|
267
|
+
it('limits width by canvas cap, client size, and total width', () => {
|
|
268
|
+
expect(calculateSingleCanvasWidth({
|
|
269
|
+
clientWidth: 9000,
|
|
270
|
+
totalWidth: 5000,
|
|
271
|
+
options,
|
|
272
|
+
})).toBe(clampWidthToBarGrid(Math.min(MAX_CANVAS_WIDTH, 5000), options));
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
describe('sliceChannelData', () => {
|
|
276
|
+
it('returns proportional slices based on offset and width', () => {
|
|
277
|
+
const channel = new Float32Array([1, 2, 3, 4, 5, 6, 7, 8]);
|
|
278
|
+
const slices = sliceChannelData({
|
|
279
|
+
channelData: [channel, channel],
|
|
280
|
+
offset: 100,
|
|
281
|
+
clampedWidth: 50,
|
|
282
|
+
totalWidth: 200,
|
|
283
|
+
});
|
|
284
|
+
expect(slices[0]).toEqual(new Float32Array([5, 6]));
|
|
285
|
+
expect(slices[1]).toEqual(new Float32Array([5, 6]));
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
describe('shouldClearCanvases', () => {
|
|
289
|
+
it('clears when exceeding maximum nodes', () => {
|
|
290
|
+
expect(shouldClearCanvases(MAX_NODES)).toBe(false);
|
|
291
|
+
expect(shouldClearCanvases(MAX_NODES + 1)).toBe(true);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
describe('getLazyRenderRange', () => {
|
|
295
|
+
it('returns surrounding canvas indices', () => {
|
|
296
|
+
expect(getLazyRenderRange({
|
|
297
|
+
scrollLeft: 50,
|
|
298
|
+
totalWidth: 200,
|
|
299
|
+
numCanvases: 5,
|
|
300
|
+
})).toEqual([0, 1, 2]);
|
|
301
|
+
});
|
|
302
|
+
it('defaults to the first canvas when width is zero', () => {
|
|
303
|
+
expect(getLazyRenderRange({ scrollLeft: 0, totalWidth: 0, numCanvases: 3 })).toEqual([0]);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
describe('calculateVerticalScale', () => {
|
|
307
|
+
it('returns base scale when not normalizing', () => {
|
|
308
|
+
expect(calculateVerticalScale({
|
|
309
|
+
channelData: [new Float32Array([0.5])],
|
|
310
|
+
barHeight: 2,
|
|
311
|
+
normalize: false,
|
|
312
|
+
})).toBe(2);
|
|
313
|
+
});
|
|
314
|
+
it('normalizes against the maximum magnitude when requested', () => {
|
|
315
|
+
expect(calculateVerticalScale({
|
|
316
|
+
channelData: [new Float32Array([0.25, -0.5])],
|
|
317
|
+
barHeight: 2,
|
|
318
|
+
normalize: true,
|
|
319
|
+
})).toBe(4);
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
describe('calculateLinePaths', () => {
|
|
323
|
+
it('produces symmetrical paths for mirrored channel data', () => {
|
|
324
|
+
const [topPath, bottomPath] = calculateLinePaths({
|
|
325
|
+
channelData: [new Float32Array([0, 0.5, 1]), new Float32Array([0, 0.25, 0.75])],
|
|
326
|
+
width: 6,
|
|
327
|
+
height: 8,
|
|
328
|
+
vScale: 1,
|
|
329
|
+
});
|
|
330
|
+
expect(topPath[0]).toEqual({ x: 0, y: 4 });
|
|
331
|
+
expect(topPath[topPath.length - 1]).toEqual({ x: 6, y: 4 });
|
|
332
|
+
expect(bottomPath[0]).toEqual({ x: 0, y: 4 });
|
|
333
|
+
expect(bottomPath[bottomPath.length - 1]).toEqual({ x: 6, y: 4 });
|
|
334
|
+
expect(topPath).toEqual([
|
|
335
|
+
{ x: 0, y: 4 },
|
|
336
|
+
{ x: 0, y: 3 },
|
|
337
|
+
{ x: 2, y: 2 },
|
|
338
|
+
{ x: 4, y: 0 },
|
|
339
|
+
{ x: 6, y: 4 },
|
|
340
|
+
]);
|
|
341
|
+
expect(bottomPath).toEqual([
|
|
342
|
+
{ x: 0, y: 4 },
|
|
343
|
+
{ x: 0, y: 5 },
|
|
344
|
+
{ x: 2, y: 5 },
|
|
345
|
+
{ x: 4, y: 7 },
|
|
346
|
+
{ x: 6, y: 4 },
|
|
347
|
+
]);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
describe('calculateScrollPercentages', () => {
|
|
351
|
+
it('returns full range when scroll width is zero', () => {
|
|
352
|
+
expect(calculateScrollPercentages({
|
|
353
|
+
scrollLeft: 0,
|
|
354
|
+
clientWidth: 100,
|
|
355
|
+
scrollWidth: 0,
|
|
356
|
+
})).toEqual({ startX: 0, endX: 1 });
|
|
357
|
+
});
|
|
358
|
+
it('returns start and end ratios relative to scroll width', () => {
|
|
359
|
+
expect(calculateScrollPercentages({
|
|
360
|
+
scrollLeft: 50,
|
|
361
|
+
clientWidth: 100,
|
|
362
|
+
scrollWidth: 400,
|
|
363
|
+
})).toEqual({ startX: 0.125, endX: 0.375 });
|
|
364
|
+
});
|
|
365
|
+
it('clamps values to 0-1 range', () => {
|
|
366
|
+
expect(calculateScrollPercentages({
|
|
367
|
+
scrollLeft: -10,
|
|
368
|
+
clientWidth: 100,
|
|
369
|
+
scrollWidth: 400,
|
|
370
|
+
})).toEqual({ startX: 0, endX: 0.225 });
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
});
|
package/dist/decoder.js
CHANGED
|
@@ -57,12 +57,20 @@ function createBuffer(channelData, duration) {
|
|
|
57
57
|
}
|
|
58
58
|
// Normalize to -1..1
|
|
59
59
|
normalize(channelData);
|
|
60
|
+
// Convert to Float32Array for consistency
|
|
61
|
+
const float32Channels = channelData.map((channel) => channel instanceof Float32Array ? channel : Float32Array.from(channel));
|
|
60
62
|
return {
|
|
61
63
|
duration,
|
|
62
|
-
length:
|
|
63
|
-
sampleRate:
|
|
64
|
-
numberOfChannels:
|
|
65
|
-
getChannelData: (i) =>
|
|
64
|
+
length: float32Channels[0].length,
|
|
65
|
+
sampleRate: float32Channels[0].length / duration,
|
|
66
|
+
numberOfChannels: float32Channels.length,
|
|
67
|
+
getChannelData: (i) => {
|
|
68
|
+
const channel = float32Channels[i];
|
|
69
|
+
if (!channel) {
|
|
70
|
+
throw new Error(`Channel ${i} not found`);
|
|
71
|
+
}
|
|
72
|
+
return channel;
|
|
73
|
+
},
|
|
66
74
|
copyFromChannel: AudioBuffer.prototype.copyFromChannel,
|
|
67
75
|
copyToChannel: AudioBuffer.prototype.copyToChannel,
|
|
68
76
|
};
|
package/dist/draggable.d.ts
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated Use createDragStream from './reactive/drag-stream.js' instead.
|
|
3
|
+
* This function is maintained for backward compatibility but will be removed in a future version.
|
|
4
|
+
*/
|
|
1
5
|
export declare function makeDraggable(element: HTMLElement | null, onDrag: (dx: number, dy: number, x: number, y: number) => void, onStart?: (x: number, y: number) => void, onEnd?: (x: number, y: number) => void, threshold?: number, mouseButton?: number, touchDelay?: number): () => void;
|
package/dist/draggable.js
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated Use createDragStream from './reactive/drag-stream.js' instead.
|
|
3
|
+
* This function is maintained for backward compatibility but will be removed in a future version.
|
|
4
|
+
*/
|
|
1
5
|
export function makeDraggable(element, onDrag, onStart, onEnd, threshold = 3, mouseButton = 0, touchDelay = 100) {
|
|
2
6
|
if (!element)
|
|
3
7
|
return () => void 0;
|
|
8
|
+
const activePointers = new Map();
|
|
4
9
|
const isTouchDevice = matchMedia('(pointer: coarse)').matches;
|
|
5
10
|
let unsubscribeDocument = () => void 0;
|
|
6
11
|
const onPointerDown = (event) => {
|
|
7
12
|
if (event.button !== mouseButton)
|
|
8
13
|
return;
|
|
9
|
-
event.
|
|
10
|
-
|
|
14
|
+
activePointers.set(event.pointerId, event);
|
|
15
|
+
if (activePointers.size > 1) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
11
18
|
let startX = event.clientX;
|
|
12
19
|
let startY = event.clientY;
|
|
13
20
|
let isDragging = false;
|
|
14
21
|
const touchStartTime = Date.now();
|
|
15
22
|
const onPointerMove = (event) => {
|
|
16
|
-
event.
|
|
17
|
-
|
|
23
|
+
if (event.defaultPrevented || activePointers.size > 1) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
18
26
|
if (isTouchDevice && Date.now() - touchStartTime < touchDelay)
|
|
19
27
|
return;
|
|
20
28
|
const x = event.clientX;
|
|
@@ -22,6 +30,8 @@ export function makeDraggable(element, onDrag, onStart, onEnd, threshold = 3, mo
|
|
|
22
30
|
const dx = x - startX;
|
|
23
31
|
const dy = y - startY;
|
|
24
32
|
if (isDragging || Math.abs(dx) > threshold || Math.abs(dy) > threshold) {
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
event.stopPropagation();
|
|
25
35
|
const rect = element.getBoundingClientRect();
|
|
26
36
|
const { left, top } = rect;
|
|
27
37
|
if (!isDragging) {
|
|
@@ -34,6 +44,7 @@ export function makeDraggable(element, onDrag, onStart, onEnd, threshold = 3, mo
|
|
|
34
44
|
}
|
|
35
45
|
};
|
|
36
46
|
const onPointerUp = (event) => {
|
|
47
|
+
activePointers.delete(event.pointerId);
|
|
37
48
|
if (isDragging) {
|
|
38
49
|
const x = event.clientX;
|
|
39
50
|
const y = event.clientY;
|
|
@@ -44,7 +55,7 @@ export function makeDraggable(element, onDrag, onStart, onEnd, threshold = 3, mo
|
|
|
44
55
|
unsubscribeDocument();
|
|
45
56
|
};
|
|
46
57
|
const onPointerLeave = (e) => {
|
|
47
|
-
|
|
58
|
+
activePointers.delete(e.pointerId);
|
|
48
59
|
if (!e.relatedTarget || e.relatedTarget === document.documentElement) {
|
|
49
60
|
onPointerUp(e);
|
|
50
61
|
}
|
|
@@ -56,6 +67,9 @@ export function makeDraggable(element, onDrag, onStart, onEnd, threshold = 3, mo
|
|
|
56
67
|
}
|
|
57
68
|
};
|
|
58
69
|
const onTouchMove = (event) => {
|
|
70
|
+
if (event.defaultPrevented || activePointers.size > 1) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
59
73
|
if (isDragging) {
|
|
60
74
|
event.preventDefault();
|
|
61
75
|
}
|
|
@@ -81,5 +95,6 @@ export function makeDraggable(element, onDrag, onStart, onEnd, threshold = 3, mo
|
|
|
81
95
|
return () => {
|
|
82
96
|
unsubscribeDocument();
|
|
83
97
|
element.removeEventListener('pointerdown', onPointerDown);
|
|
98
|
+
activePointers.clear();
|
|
84
99
|
};
|
|
85
100
|
}
|
package/dist/fetcher.js
CHANGED
|
@@ -14,36 +14,27 @@ function watchProgress(response, progressCallback) {
|
|
|
14
14
|
const reader = response.body.getReader();
|
|
15
15
|
const contentLength = Number(response.headers.get('Content-Length')) || 0;
|
|
16
16
|
let receivedLength = 0;
|
|
17
|
-
const maxIterations = 100000; // Safety limit to prevent infinite loops
|
|
18
|
-
let iterations = 0;
|
|
19
17
|
// Process the data
|
|
20
|
-
const processChunk = (value) =>
|
|
18
|
+
const processChunk = (value) => {
|
|
21
19
|
// Add to the received length
|
|
22
20
|
receivedLength += (value === null || value === void 0 ? void 0 : value.length) || 0;
|
|
23
21
|
const percentage = Math.round((receivedLength / contentLength) * 100);
|
|
24
22
|
progressCallback(percentage);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
data = yield reader.read();
|
|
35
|
-
}
|
|
36
|
-
catch (_a) {
|
|
37
|
-
// Ignore errors because we can only handle the main response
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
// Continue reading data until done
|
|
41
|
-
if (!data.done) {
|
|
23
|
+
};
|
|
24
|
+
// Use iteration instead of recursion to avoid stack issues
|
|
25
|
+
try {
|
|
26
|
+
while (true) {
|
|
27
|
+
const data = yield reader.read();
|
|
28
|
+
if (data.done) {
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
42
31
|
processChunk(data.value);
|
|
43
|
-
yield read();
|
|
44
32
|
}
|
|
45
|
-
}
|
|
46
|
-
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
// Ignore errors because we can only handle the main response
|
|
36
|
+
console.warn('Progress tracking error:', err);
|
|
37
|
+
}
|
|
47
38
|
});
|
|
48
39
|
}
|
|
49
40
|
function fetchBlob(url, progressCallback, requestInit) {
|
package/dist/player.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import EventEmitter, { type GeneralEventTypes } from './event-emitter.js';
|
|
2
|
+
import { type WritableSignal } from './reactive/store.js';
|
|
2
3
|
type PlayerOptions = {
|
|
3
4
|
media?: HTMLMediaElement;
|
|
4
5
|
mediaControls?: boolean;
|
|
@@ -8,7 +9,27 @@ type PlayerOptions = {
|
|
|
8
9
|
declare class Player<T extends GeneralEventTypes> extends EventEmitter<T> {
|
|
9
10
|
protected media: HTMLMediaElement;
|
|
10
11
|
private isExternalMedia;
|
|
12
|
+
private _isPlaying;
|
|
13
|
+
private _currentTime;
|
|
14
|
+
private _duration;
|
|
15
|
+
private _volume;
|
|
16
|
+
private _muted;
|
|
17
|
+
private _playbackRate;
|
|
18
|
+
private _seeking;
|
|
19
|
+
private reactiveMediaEventCleanups;
|
|
20
|
+
get isPlayingSignal(): WritableSignal<boolean>;
|
|
21
|
+
get currentTimeSignal(): WritableSignal<number>;
|
|
22
|
+
get durationSignal(): WritableSignal<number>;
|
|
23
|
+
get volumeSignal(): WritableSignal<number>;
|
|
24
|
+
get mutedSignal(): WritableSignal<boolean>;
|
|
25
|
+
get playbackRateSignal(): WritableSignal<number>;
|
|
26
|
+
get seekingSignal(): WritableSignal<boolean>;
|
|
11
27
|
constructor(options: PlayerOptions);
|
|
28
|
+
/**
|
|
29
|
+
* Setup reactive media event handlers that update signals
|
|
30
|
+
* This bridges the imperative HTMLMediaElement API to reactive state
|
|
31
|
+
*/
|
|
32
|
+
private setupReactiveMediaEvents;
|
|
12
33
|
protected onMediaEvent<K extends keyof HTMLElementEventMap>(event: K, callback: (ev: HTMLElementEventMap[K]) => void, options?: boolean | AddEventListenerOptions): () => void;
|
|
13
34
|
protected getSrc(): string;
|
|
14
35
|
private revokeSrc;
|