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.
Files changed (91) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/memory-leaks.test.d.ts +7 -0
  3. package/dist/__tests__/memory-leaks.test.js +184 -0
  4. package/dist/__tests__/player.test.js +107 -0
  5. package/dist/__tests__/renderer-utils.test.d.ts +1 -0
  6. package/dist/__tests__/renderer-utils.test.js +373 -0
  7. package/dist/decoder.js +12 -4
  8. package/dist/draggable.d.ts +4 -0
  9. package/dist/draggable.js +20 -5
  10. package/dist/fetcher.js +14 -23
  11. package/dist/player.d.ts +21 -0
  12. package/dist/player.js +86 -0
  13. package/dist/plugins/envelope.cjs +1 -1
  14. package/dist/plugins/envelope.esm.js +1 -1
  15. package/dist/plugins/envelope.js +1 -1
  16. package/dist/plugins/envelope.min.js +1 -1
  17. package/dist/plugins/hover.cjs +1 -1
  18. package/dist/plugins/hover.d.ts +0 -3
  19. package/dist/plugins/hover.esm.js +1 -1
  20. package/dist/plugins/hover.js +1 -1
  21. package/dist/plugins/hover.min.js +1 -1
  22. package/dist/plugins/minimap.cjs +1 -1
  23. package/dist/plugins/minimap.esm.js +1 -1
  24. package/dist/plugins/minimap.js +1 -1
  25. package/dist/plugins/minimap.min.js +1 -1
  26. package/dist/plugins/record.cjs +1 -1
  27. package/dist/plugins/record.esm.js +1 -1
  28. package/dist/plugins/record.js +1 -1
  29. package/dist/plugins/record.min.js +1 -1
  30. package/dist/plugins/regions.cjs +1 -1
  31. package/dist/plugins/regions.esm.js +1 -1
  32. package/dist/plugins/regions.js +1 -1
  33. package/dist/plugins/regions.min.js +1 -1
  34. package/dist/plugins/timeline.cjs +1 -1
  35. package/dist/plugins/timeline.d.ts +1 -1
  36. package/dist/plugins/timeline.esm.js +1 -1
  37. package/dist/plugins/timeline.js +1 -1
  38. package/dist/plugins/timeline.min.js +1 -1
  39. package/dist/plugins/zoom.cjs +1 -1
  40. package/dist/plugins/zoom.d.ts +10 -1
  41. package/dist/plugins/zoom.esm.js +1 -1
  42. package/dist/plugins/zoom.js +1 -1
  43. package/dist/plugins/zoom.min.js +1 -1
  44. package/dist/reactive/__tests__/drag-stream.test.d.ts +1 -0
  45. package/dist/reactive/__tests__/drag-stream.test.js +199 -0
  46. package/dist/reactive/__tests__/event-stream-emitter.test.d.ts +1 -0
  47. package/dist/reactive/__tests__/event-stream-emitter.test.js +230 -0
  48. package/dist/reactive/__tests__/event-streams.test.d.ts +1 -0
  49. package/dist/reactive/__tests__/event-streams.test.js +278 -0
  50. package/dist/reactive/__tests__/media-event-bridge.test.d.ts +1 -0
  51. package/dist/reactive/__tests__/media-event-bridge.test.js +202 -0
  52. package/dist/reactive/__tests__/render-scheduler.test.d.ts +1 -0
  53. package/dist/reactive/__tests__/render-scheduler.test.js +216 -0
  54. package/dist/reactive/__tests__/scroll-stream.test.d.ts +1 -0
  55. package/dist/reactive/__tests__/scroll-stream.test.js +191 -0
  56. package/dist/reactive/__tests__/state-event-emitter.test.d.ts +1 -0
  57. package/dist/reactive/__tests__/state-event-emitter.test.js +234 -0
  58. package/dist/reactive/__tests__/store.test.d.ts +1 -0
  59. package/dist/reactive/__tests__/store.test.js +271 -0
  60. package/dist/reactive/drag-stream.d.ts +48 -0
  61. package/dist/reactive/drag-stream.js +146 -0
  62. package/dist/reactive/event-stream-emitter.d.ts +104 -0
  63. package/dist/reactive/event-stream-emitter.js +151 -0
  64. package/dist/reactive/event-streams.d.ts +65 -0
  65. package/dist/reactive/event-streams.js +146 -0
  66. package/dist/reactive/media-event-bridge.d.ts +51 -0
  67. package/dist/reactive/media-event-bridge.js +152 -0
  68. package/dist/reactive/render-scheduler.d.ts +43 -0
  69. package/dist/reactive/render-scheduler.js +75 -0
  70. package/dist/reactive/scroll-stream.d.ts +93 -0
  71. package/dist/reactive/scroll-stream.js +131 -0
  72. package/dist/reactive/state-event-emitter.d.ts +105 -0
  73. package/dist/reactive/state-event-emitter.js +213 -0
  74. package/dist/reactive/store.d.ts +66 -0
  75. package/dist/reactive/store.js +115 -0
  76. package/dist/renderer-utils.d.ts +126 -0
  77. package/dist/renderer-utils.js +252 -0
  78. package/dist/renderer.d.ts +4 -4
  79. package/dist/renderer.js +133 -197
  80. package/dist/state/__tests__/wavesurfer-state.test.d.ts +1 -0
  81. package/dist/state/__tests__/wavesurfer-state.test.js +268 -0
  82. package/dist/state/wavesurfer-state.d.ts +90 -0
  83. package/dist/state/wavesurfer-state.js +124 -0
  84. package/dist/types.d.ts +167 -1
  85. package/dist/wavesurfer.cjs +1 -1
  86. package/dist/wavesurfer.d.ts +18 -0
  87. package/dist/wavesurfer.esm.js +1 -1
  88. package/dist/wavesurfer.js +46 -8
  89. package/dist/wavesurfer.min.js +1 -1
  90. package/dist/webaudio.js +4 -0
  91. 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: channelData[0].length,
63
- sampleRate: channelData[0].length / duration,
64
- numberOfChannels: channelData.length,
65
- getChannelData: (i) => channelData === null || channelData === void 0 ? void 0 : channelData[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
  };
@@ -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.preventDefault();
10
- event.stopPropagation();
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.preventDefault();
17
- event.stopPropagation();
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
- // Listen to events only on the document and not on inner elements
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) => __awaiter(this, void 0, void 0, function* () {
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
- const read = () => __awaiter(this, void 0, void 0, function* () {
27
- // Safety check to prevent infinite recursion
28
- if (iterations++ > maxIterations) {
29
- console.error('Fetcher: Maximum iterations reached, stopping read loop');
30
- return;
31
- }
32
- let data;
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
- read();
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;