sceyt-chat-react-uikit 1.8.0-beta.5 → 1.8.0-beta.6
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/index.js +1169 -273
- package/index.modern.js +1169 -273
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -32188,15 +32188,16 @@ class EventEmitter {
|
|
|
32188
32188
|
if (!this.listeners[event]) {
|
|
32189
32189
|
this.listeners[event] = new Set();
|
|
32190
32190
|
}
|
|
32191
|
-
this.listeners[event].add(listener);
|
|
32192
32191
|
if (options === null || options === void 0 ? void 0 : options.once) {
|
|
32193
|
-
|
|
32194
|
-
|
|
32195
|
-
this.un(event,
|
|
32192
|
+
// Create a wrapper that removes itself after being called once
|
|
32193
|
+
const onceWrapper = (...args) => {
|
|
32194
|
+
this.un(event, onceWrapper);
|
|
32195
|
+
listener(...args);
|
|
32196
32196
|
};
|
|
32197
|
-
this.
|
|
32198
|
-
return
|
|
32197
|
+
this.listeners[event].add(onceWrapper);
|
|
32198
|
+
return () => this.un(event, onceWrapper);
|
|
32199
32199
|
}
|
|
32200
|
+
this.listeners[event].add(listener);
|
|
32200
32201
|
return () => this.un(event, listener);
|
|
32201
32202
|
}
|
|
32202
32203
|
/** Unsubscribe from an event */
|
|
@@ -32266,8 +32267,13 @@ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _argume
|
|
|
32266
32267
|
function decode(audioData, sampleRate) {
|
|
32267
32268
|
return __awaiter(this, void 0, void 0, function* () {
|
|
32268
32269
|
const audioCtx = new AudioContext({ sampleRate });
|
|
32269
|
-
|
|
32270
|
-
|
|
32270
|
+
try {
|
|
32271
|
+
return yield audioCtx.decodeAudioData(audioData);
|
|
32272
|
+
}
|
|
32273
|
+
finally {
|
|
32274
|
+
// Ensure AudioContext is always closed, even on synchronous errors
|
|
32275
|
+
audioCtx.close();
|
|
32276
|
+
}
|
|
32271
32277
|
});
|
|
32272
32278
|
}
|
|
32273
32279
|
/** Normalize peaks to -1..1 */
|
|
@@ -32291,17 +32297,36 @@ function normalize(channelData) {
|
|
|
32291
32297
|
}
|
|
32292
32298
|
/** Create an audio buffer from pre-decoded audio data */
|
|
32293
32299
|
function createBuffer(channelData, duration) {
|
|
32300
|
+
// Validate inputs
|
|
32301
|
+
if (!channelData || channelData.length === 0) {
|
|
32302
|
+
throw new Error('channelData must be a non-empty array');
|
|
32303
|
+
}
|
|
32304
|
+
if (duration <= 0) {
|
|
32305
|
+
throw new Error('duration must be greater than 0');
|
|
32306
|
+
}
|
|
32294
32307
|
// If a single array of numbers is passed, make it an array of arrays
|
|
32295
32308
|
if (typeof channelData[0] === 'number')
|
|
32296
32309
|
channelData = [channelData];
|
|
32310
|
+
// Validate channel data after conversion
|
|
32311
|
+
if (!channelData[0] || channelData[0].length === 0) {
|
|
32312
|
+
throw new Error('channelData must contain non-empty channel arrays');
|
|
32313
|
+
}
|
|
32297
32314
|
// Normalize to -1..1
|
|
32298
32315
|
normalize(channelData);
|
|
32316
|
+
// Convert to Float32Array for consistency
|
|
32317
|
+
const float32Channels = channelData.map((channel) => channel instanceof Float32Array ? channel : Float32Array.from(channel));
|
|
32299
32318
|
return {
|
|
32300
32319
|
duration,
|
|
32301
|
-
length:
|
|
32302
|
-
sampleRate:
|
|
32303
|
-
numberOfChannels:
|
|
32304
|
-
getChannelData: (i) =>
|
|
32320
|
+
length: float32Channels[0].length,
|
|
32321
|
+
sampleRate: float32Channels[0].length / duration,
|
|
32322
|
+
numberOfChannels: float32Channels.length,
|
|
32323
|
+
getChannelData: (i) => {
|
|
32324
|
+
const channel = float32Channels[i];
|
|
32325
|
+
if (!channel) {
|
|
32326
|
+
throw new Error(`Channel ${i} not found`);
|
|
32327
|
+
}
|
|
32328
|
+
return channel;
|
|
32329
|
+
},
|
|
32305
32330
|
copyFromChannel: AudioBuffer.prototype.copyFromChannel,
|
|
32306
32331
|
copyToChannel: AudioBuffer.prototype.copyToChannel,
|
|
32307
32332
|
};
|
|
@@ -32370,28 +32395,26 @@ function watchProgress(response, progressCallback) {
|
|
|
32370
32395
|
const contentLength = Number(response.headers.get('Content-Length')) || 0;
|
|
32371
32396
|
let receivedLength = 0;
|
|
32372
32397
|
// Process the data
|
|
32373
|
-
const processChunk = (value) =>
|
|
32398
|
+
const processChunk = (value) => {
|
|
32374
32399
|
// Add to the received length
|
|
32375
32400
|
receivedLength += (value === null || value === void 0 ? void 0 : value.length) || 0;
|
|
32376
32401
|
const percentage = Math.round((receivedLength / contentLength) * 100);
|
|
32377
32402
|
progressCallback(percentage);
|
|
32378
|
-
}
|
|
32379
|
-
|
|
32380
|
-
|
|
32381
|
-
|
|
32382
|
-
data = yield reader.read();
|
|
32383
|
-
|
|
32384
|
-
|
|
32385
|
-
|
|
32386
|
-
return;
|
|
32387
|
-
}
|
|
32388
|
-
// Continue reading data until done
|
|
32389
|
-
if (!data.done) {
|
|
32403
|
+
};
|
|
32404
|
+
// Use iteration instead of recursion to avoid stack issues
|
|
32405
|
+
try {
|
|
32406
|
+
while (true) {
|
|
32407
|
+
const data = yield reader.read();
|
|
32408
|
+
if (data.done) {
|
|
32409
|
+
break;
|
|
32410
|
+
}
|
|
32390
32411
|
processChunk(data.value);
|
|
32391
|
-
yield read();
|
|
32392
32412
|
}
|
|
32393
|
-
}
|
|
32394
|
-
|
|
32413
|
+
}
|
|
32414
|
+
catch (err) {
|
|
32415
|
+
// Ignore errors because we can only handle the main response
|
|
32416
|
+
console.warn('Progress tracking error:', err);
|
|
32417
|
+
}
|
|
32395
32418
|
});
|
|
32396
32419
|
}
|
|
32397
32420
|
function fetchBlob(url, progressCallback, requestInit) {
|
|
@@ -32410,6 +32433,121 @@ const Fetcher = {
|
|
|
32410
32433
|
fetchBlob,
|
|
32411
32434
|
};
|
|
32412
32435
|
|
|
32436
|
+
/**
|
|
32437
|
+
* Reactive primitives for managing state in WaveSurfer
|
|
32438
|
+
*
|
|
32439
|
+
* This module provides signal-based reactivity similar to SolidJS signals.
|
|
32440
|
+
* Signals are reactive values that notify subscribers when they change.
|
|
32441
|
+
*/
|
|
32442
|
+
/**
|
|
32443
|
+
* Create a reactive signal that notifies subscribers when its value changes
|
|
32444
|
+
*
|
|
32445
|
+
* @example
|
|
32446
|
+
* ```typescript
|
|
32447
|
+
* const count = signal(0)
|
|
32448
|
+
* count.subscribe(val => console.log('Count:', val))
|
|
32449
|
+
* count.set(5) // Logs: Count: 5
|
|
32450
|
+
* ```
|
|
32451
|
+
*/
|
|
32452
|
+
function signal(initialValue) {
|
|
32453
|
+
let _value = initialValue;
|
|
32454
|
+
const subscribers = new Set();
|
|
32455
|
+
return {
|
|
32456
|
+
get value() {
|
|
32457
|
+
return _value;
|
|
32458
|
+
},
|
|
32459
|
+
set(newValue) {
|
|
32460
|
+
// Only update and notify if value actually changed
|
|
32461
|
+
if (!Object.is(_value, newValue)) {
|
|
32462
|
+
_value = newValue;
|
|
32463
|
+
subscribers.forEach((fn) => fn(_value));
|
|
32464
|
+
}
|
|
32465
|
+
},
|
|
32466
|
+
update(fn) {
|
|
32467
|
+
this.set(fn(_value));
|
|
32468
|
+
},
|
|
32469
|
+
subscribe(callback) {
|
|
32470
|
+
subscribers.add(callback);
|
|
32471
|
+
return () => subscribers.delete(callback);
|
|
32472
|
+
},
|
|
32473
|
+
};
|
|
32474
|
+
}
|
|
32475
|
+
/**
|
|
32476
|
+
* Create a computed value that automatically updates when its dependencies change
|
|
32477
|
+
*
|
|
32478
|
+
* @example
|
|
32479
|
+
* ```typescript
|
|
32480
|
+
* const count = signal(0)
|
|
32481
|
+
* const doubled = computed(() => count.value * 2, [count])
|
|
32482
|
+
* console.log(doubled.value) // 0
|
|
32483
|
+
* count.set(5)
|
|
32484
|
+
* console.log(doubled.value) // 10
|
|
32485
|
+
* ```
|
|
32486
|
+
*/
|
|
32487
|
+
function computed(fn, dependencies) {
|
|
32488
|
+
const result = signal(fn());
|
|
32489
|
+
// Subscribe to all dependencies immediately
|
|
32490
|
+
// This ensures the computed value stays in sync even if no one is subscribed to it
|
|
32491
|
+
dependencies.forEach((dep) => dep.subscribe(() => {
|
|
32492
|
+
const newValue = fn();
|
|
32493
|
+
// Update the result signal, which will notify our subscribers if value changed
|
|
32494
|
+
if (!Object.is(result.value, newValue)) {
|
|
32495
|
+
result.set(newValue);
|
|
32496
|
+
}
|
|
32497
|
+
}));
|
|
32498
|
+
// Return a read-only signal that proxies the result
|
|
32499
|
+
return {
|
|
32500
|
+
get value() {
|
|
32501
|
+
return result.value;
|
|
32502
|
+
},
|
|
32503
|
+
subscribe(callback) {
|
|
32504
|
+
// Just subscribe to result changes
|
|
32505
|
+
return result.subscribe(callback);
|
|
32506
|
+
},
|
|
32507
|
+
};
|
|
32508
|
+
}
|
|
32509
|
+
/**
|
|
32510
|
+
* Run a side effect automatically when dependencies change
|
|
32511
|
+
*
|
|
32512
|
+
* @param fn - Effect function. Can return a cleanup function.
|
|
32513
|
+
* @param dependencies - Signals that trigger the effect when they change
|
|
32514
|
+
* @returns Unsubscribe function that stops the effect and runs cleanup
|
|
32515
|
+
*
|
|
32516
|
+
* @example
|
|
32517
|
+
* ```typescript
|
|
32518
|
+
* const count = signal(0)
|
|
32519
|
+
* effect(() => {
|
|
32520
|
+
* console.log('Count is:', count.value)
|
|
32521
|
+
* return () => console.log('Cleanup')
|
|
32522
|
+
* }, [count])
|
|
32523
|
+
* count.set(5) // Logs: Cleanup, Count is: 5
|
|
32524
|
+
* ```
|
|
32525
|
+
*/
|
|
32526
|
+
function effect(fn, dependencies) {
|
|
32527
|
+
let cleanup;
|
|
32528
|
+
const run = () => {
|
|
32529
|
+
// Run cleanup from previous execution
|
|
32530
|
+
if (cleanup) {
|
|
32531
|
+
cleanup();
|
|
32532
|
+
cleanup = undefined;
|
|
32533
|
+
}
|
|
32534
|
+
// Run effect and capture new cleanup
|
|
32535
|
+
cleanup = fn();
|
|
32536
|
+
};
|
|
32537
|
+
// Subscribe to all dependencies
|
|
32538
|
+
const unsubscribes = dependencies.map((dep) => dep.subscribe(run));
|
|
32539
|
+
// Run effect immediately
|
|
32540
|
+
run();
|
|
32541
|
+
// Return function that unsubscribes and runs cleanup
|
|
32542
|
+
return () => {
|
|
32543
|
+
if (cleanup) {
|
|
32544
|
+
cleanup();
|
|
32545
|
+
cleanup = undefined;
|
|
32546
|
+
}
|
|
32547
|
+
unsubscribes.forEach((unsub) => unsub());
|
|
32548
|
+
};
|
|
32549
|
+
}
|
|
32550
|
+
|
|
32413
32551
|
var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
32414
32552
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
32415
32553
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -32420,9 +32558,33 @@ var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _argu
|
|
|
32420
32558
|
});
|
|
32421
32559
|
};
|
|
32422
32560
|
class Player extends EventEmitter {
|
|
32561
|
+
// Expose reactive state as writable signals
|
|
32562
|
+
// These are writable to allow WaveSurfer to compose them into centralized state
|
|
32563
|
+
get isPlayingSignal() {
|
|
32564
|
+
return this._isPlaying;
|
|
32565
|
+
}
|
|
32566
|
+
get currentTimeSignal() {
|
|
32567
|
+
return this._currentTime;
|
|
32568
|
+
}
|
|
32569
|
+
get durationSignal() {
|
|
32570
|
+
return this._duration;
|
|
32571
|
+
}
|
|
32572
|
+
get volumeSignal() {
|
|
32573
|
+
return this._volume;
|
|
32574
|
+
}
|
|
32575
|
+
get mutedSignal() {
|
|
32576
|
+
return this._muted;
|
|
32577
|
+
}
|
|
32578
|
+
get playbackRateSignal() {
|
|
32579
|
+
return this._playbackRate;
|
|
32580
|
+
}
|
|
32581
|
+
get seekingSignal() {
|
|
32582
|
+
return this._seeking;
|
|
32583
|
+
}
|
|
32423
32584
|
constructor(options) {
|
|
32424
32585
|
super();
|
|
32425
32586
|
this.isExternalMedia = false;
|
|
32587
|
+
this.reactiveMediaEventCleanups = [];
|
|
32426
32588
|
if (options.media) {
|
|
32427
32589
|
this.media = options.media;
|
|
32428
32590
|
this.isExternalMedia = true;
|
|
@@ -32430,6 +32592,16 @@ class Player extends EventEmitter {
|
|
|
32430
32592
|
else {
|
|
32431
32593
|
this.media = document.createElement('audio');
|
|
32432
32594
|
}
|
|
32595
|
+
// Initialize reactive state
|
|
32596
|
+
this._isPlaying = signal(false);
|
|
32597
|
+
this._currentTime = signal(0);
|
|
32598
|
+
this._duration = signal(0);
|
|
32599
|
+
this._volume = signal(this.media.volume);
|
|
32600
|
+
this._muted = signal(this.media.muted);
|
|
32601
|
+
this._playbackRate = signal(this.media.playbackRate || 1);
|
|
32602
|
+
this._seeking = signal(false);
|
|
32603
|
+
// Setup reactive media event handlers
|
|
32604
|
+
this.setupReactiveMediaEvents();
|
|
32433
32605
|
// Controls
|
|
32434
32606
|
if (options.mediaControls) {
|
|
32435
32607
|
this.media.controls = true;
|
|
@@ -32447,6 +32619,48 @@ class Player extends EventEmitter {
|
|
|
32447
32619
|
}, { once: true });
|
|
32448
32620
|
}
|
|
32449
32621
|
}
|
|
32622
|
+
/**
|
|
32623
|
+
* Setup reactive media event handlers that update signals
|
|
32624
|
+
* This bridges the imperative HTMLMediaElement API to reactive state
|
|
32625
|
+
*/
|
|
32626
|
+
setupReactiveMediaEvents() {
|
|
32627
|
+
// Playing state
|
|
32628
|
+
this.reactiveMediaEventCleanups.push(this.onMediaEvent('play', () => {
|
|
32629
|
+
this._isPlaying.set(true);
|
|
32630
|
+
}));
|
|
32631
|
+
this.reactiveMediaEventCleanups.push(this.onMediaEvent('pause', () => {
|
|
32632
|
+
this._isPlaying.set(false);
|
|
32633
|
+
}));
|
|
32634
|
+
this.reactiveMediaEventCleanups.push(this.onMediaEvent('ended', () => {
|
|
32635
|
+
this._isPlaying.set(false);
|
|
32636
|
+
}));
|
|
32637
|
+
// Time tracking
|
|
32638
|
+
this.reactiveMediaEventCleanups.push(this.onMediaEvent('timeupdate', () => {
|
|
32639
|
+
this._currentTime.set(this.media.currentTime);
|
|
32640
|
+
}));
|
|
32641
|
+
this.reactiveMediaEventCleanups.push(this.onMediaEvent('durationchange', () => {
|
|
32642
|
+
this._duration.set(this.media.duration || 0);
|
|
32643
|
+
}));
|
|
32644
|
+
this.reactiveMediaEventCleanups.push(this.onMediaEvent('loadedmetadata', () => {
|
|
32645
|
+
this._duration.set(this.media.duration || 0);
|
|
32646
|
+
}));
|
|
32647
|
+
// Seeking state
|
|
32648
|
+
this.reactiveMediaEventCleanups.push(this.onMediaEvent('seeking', () => {
|
|
32649
|
+
this._seeking.set(true);
|
|
32650
|
+
}));
|
|
32651
|
+
this.reactiveMediaEventCleanups.push(this.onMediaEvent('seeked', () => {
|
|
32652
|
+
this._seeking.set(false);
|
|
32653
|
+
}));
|
|
32654
|
+
// Volume and muted
|
|
32655
|
+
this.reactiveMediaEventCleanups.push(this.onMediaEvent('volumechange', () => {
|
|
32656
|
+
this._volume.set(this.media.volume);
|
|
32657
|
+
this._muted.set(this.media.muted);
|
|
32658
|
+
}));
|
|
32659
|
+
// Playback rate
|
|
32660
|
+
this.reactiveMediaEventCleanups.push(this.onMediaEvent('ratechange', () => {
|
|
32661
|
+
this._playbackRate.set(this.media.playbackRate);
|
|
32662
|
+
}));
|
|
32663
|
+
}
|
|
32450
32664
|
onMediaEvent(event, callback, options) {
|
|
32451
32665
|
this.media.addEventListener(event, callback, options);
|
|
32452
32666
|
return () => this.media.removeEventListener(event, callback, options);
|
|
@@ -32483,17 +32697,27 @@ class Player extends EventEmitter {
|
|
|
32483
32697
|
}
|
|
32484
32698
|
}
|
|
32485
32699
|
destroy() {
|
|
32700
|
+
// Cleanup reactive media event listeners
|
|
32701
|
+
this.reactiveMediaEventCleanups.forEach((cleanup) => cleanup());
|
|
32702
|
+
this.reactiveMediaEventCleanups = [];
|
|
32486
32703
|
if (this.isExternalMedia)
|
|
32487
32704
|
return;
|
|
32488
32705
|
this.media.pause();
|
|
32489
|
-
this.media.remove();
|
|
32490
32706
|
this.revokeSrc();
|
|
32491
32707
|
this.media.removeAttribute('src');
|
|
32492
32708
|
// Load resets the media element to its initial state
|
|
32493
32709
|
this.media.load();
|
|
32710
|
+
// Remove from DOM after cleanup
|
|
32711
|
+
this.media.remove();
|
|
32494
32712
|
}
|
|
32495
32713
|
setMediaElement(element) {
|
|
32714
|
+
// Cleanup reactive event listeners from old media element
|
|
32715
|
+
this.reactiveMediaEventCleanups.forEach((cleanup) => cleanup());
|
|
32716
|
+
this.reactiveMediaEventCleanups = [];
|
|
32717
|
+
// Set new media element
|
|
32496
32718
|
this.media = element;
|
|
32719
|
+
// Reinitialize reactive event listeners on new media element
|
|
32720
|
+
this.setupReactiveMediaEvents();
|
|
32497
32721
|
}
|
|
32498
32722
|
/** Start playing the audio */
|
|
32499
32723
|
play() {
|
|
@@ -32573,23 +32797,312 @@ class Player extends EventEmitter {
|
|
|
32573
32797
|
}
|
|
32574
32798
|
}
|
|
32575
32799
|
|
|
32576
|
-
|
|
32577
|
-
|
|
32578
|
-
|
|
32800
|
+
const DEFAULT_HEIGHT = 128;
|
|
32801
|
+
const MAX_CANVAS_WIDTH = 8000;
|
|
32802
|
+
const MAX_NODES = 10;
|
|
32803
|
+
function clampToUnit(value) {
|
|
32804
|
+
if (value < 0)
|
|
32805
|
+
return 0;
|
|
32806
|
+
if (value > 1)
|
|
32807
|
+
return 1;
|
|
32808
|
+
return value;
|
|
32809
|
+
}
|
|
32810
|
+
function calculateBarRenderConfig({ width, height, length, options, pixelRatio, }) {
|
|
32811
|
+
const halfHeight = height / 2;
|
|
32812
|
+
const barWidth = options.barWidth ? options.barWidth * pixelRatio : 1;
|
|
32813
|
+
const barGap = options.barGap ? options.barGap * pixelRatio : options.barWidth ? barWidth / 2 : 0;
|
|
32814
|
+
const barRadius = options.barRadius || 0;
|
|
32815
|
+
const barMinHeight = options.barMinHeight ? options.barMinHeight * pixelRatio : 0;
|
|
32816
|
+
const spacing = barWidth + barGap || 1;
|
|
32817
|
+
const barIndexScale = length > 0 ? width / spacing / length : 0;
|
|
32818
|
+
return {
|
|
32819
|
+
halfHeight,
|
|
32820
|
+
barWidth,
|
|
32821
|
+
barGap,
|
|
32822
|
+
barRadius,
|
|
32823
|
+
barMinHeight,
|
|
32824
|
+
barIndexScale,
|
|
32825
|
+
barSpacing: spacing,
|
|
32826
|
+
};
|
|
32827
|
+
}
|
|
32828
|
+
function calculateBarHeights({ maxTop, maxBottom, halfHeight, vScale, barMinHeight = 0, barAlign, }) {
|
|
32829
|
+
let topHeight = Math.round(maxTop * halfHeight * vScale);
|
|
32830
|
+
const bottomHeight = Math.round(maxBottom * halfHeight * vScale);
|
|
32831
|
+
let totalHeight = topHeight + bottomHeight || 1;
|
|
32832
|
+
if (totalHeight < barMinHeight) {
|
|
32833
|
+
totalHeight = barMinHeight;
|
|
32834
|
+
if (!barAlign) {
|
|
32835
|
+
topHeight = totalHeight / 2;
|
|
32836
|
+
}
|
|
32837
|
+
}
|
|
32838
|
+
return { topHeight, totalHeight };
|
|
32839
|
+
}
|
|
32840
|
+
function resolveBarYPosition({ barAlign, halfHeight, topHeight, totalHeight, canvasHeight, }) {
|
|
32841
|
+
if (barAlign === 'top')
|
|
32842
|
+
return 0;
|
|
32843
|
+
if (barAlign === 'bottom')
|
|
32844
|
+
return canvasHeight - totalHeight;
|
|
32845
|
+
return halfHeight - topHeight;
|
|
32846
|
+
}
|
|
32847
|
+
function calculateBarSegments({ channelData, barIndexScale, barSpacing, barWidth, halfHeight, vScale, canvasHeight, barAlign, barMinHeight, }) {
|
|
32848
|
+
const topChannel = channelData[0] || [];
|
|
32849
|
+
const bottomChannel = channelData[1] || topChannel;
|
|
32850
|
+
const length = topChannel.length;
|
|
32851
|
+
const segments = [];
|
|
32852
|
+
let prevX = 0;
|
|
32853
|
+
let maxTop = 0;
|
|
32854
|
+
let maxBottom = 0;
|
|
32855
|
+
for (let i = 0; i <= length; i++) {
|
|
32856
|
+
const x = Math.round(i * barIndexScale);
|
|
32857
|
+
if (x > prevX) {
|
|
32858
|
+
const { topHeight, totalHeight } = calculateBarHeights({
|
|
32859
|
+
maxTop,
|
|
32860
|
+
maxBottom,
|
|
32861
|
+
halfHeight,
|
|
32862
|
+
vScale,
|
|
32863
|
+
barMinHeight,
|
|
32864
|
+
barAlign,
|
|
32865
|
+
});
|
|
32866
|
+
const y = resolveBarYPosition({
|
|
32867
|
+
barAlign,
|
|
32868
|
+
halfHeight,
|
|
32869
|
+
topHeight,
|
|
32870
|
+
totalHeight,
|
|
32871
|
+
canvasHeight,
|
|
32872
|
+
});
|
|
32873
|
+
segments.push({
|
|
32874
|
+
x: prevX * barSpacing,
|
|
32875
|
+
y,
|
|
32876
|
+
width: barWidth,
|
|
32877
|
+
height: totalHeight,
|
|
32878
|
+
});
|
|
32879
|
+
prevX = x;
|
|
32880
|
+
maxTop = 0;
|
|
32881
|
+
maxBottom = 0;
|
|
32882
|
+
}
|
|
32883
|
+
const magnitudeTop = Math.abs(topChannel[i] || 0);
|
|
32884
|
+
const magnitudeBottom = Math.abs(bottomChannel[i] || 0);
|
|
32885
|
+
if (magnitudeTop > maxTop)
|
|
32886
|
+
maxTop = magnitudeTop;
|
|
32887
|
+
if (magnitudeBottom > maxBottom)
|
|
32888
|
+
maxBottom = magnitudeBottom;
|
|
32889
|
+
}
|
|
32890
|
+
return segments;
|
|
32891
|
+
}
|
|
32892
|
+
function getRelativePointerPosition(rect, clientX, clientY) {
|
|
32893
|
+
const x = clientX - rect.left;
|
|
32894
|
+
const y = clientY - rect.top;
|
|
32895
|
+
const relativeX = x / rect.width;
|
|
32896
|
+
const relativeY = y / rect.height;
|
|
32897
|
+
return [relativeX, relativeY];
|
|
32898
|
+
}
|
|
32899
|
+
function resolveChannelHeight({ optionsHeight, optionsSplitChannels, parentHeight, numberOfChannels, defaultHeight = DEFAULT_HEIGHT, }) {
|
|
32900
|
+
if (optionsHeight == null)
|
|
32901
|
+
return defaultHeight;
|
|
32902
|
+
const numericHeight = Number(optionsHeight);
|
|
32903
|
+
if (!isNaN(numericHeight))
|
|
32904
|
+
return numericHeight;
|
|
32905
|
+
if (optionsHeight === 'auto') {
|
|
32906
|
+
const height = parentHeight || defaultHeight;
|
|
32907
|
+
if (optionsSplitChannels === null || optionsSplitChannels === void 0 ? void 0 : optionsSplitChannels.every((channel) => !channel.overlay)) {
|
|
32908
|
+
return height / numberOfChannels;
|
|
32909
|
+
}
|
|
32910
|
+
return height;
|
|
32911
|
+
}
|
|
32912
|
+
return defaultHeight;
|
|
32913
|
+
}
|
|
32914
|
+
function getPixelRatio(devicePixelRatio) {
|
|
32915
|
+
return Math.max(1, devicePixelRatio || 1);
|
|
32916
|
+
}
|
|
32917
|
+
function shouldRenderBars(options) {
|
|
32918
|
+
return Boolean(options.barWidth || options.barGap || options.barAlign);
|
|
32919
|
+
}
|
|
32920
|
+
function resolveColorValue(color, devicePixelRatio, canvasHeight) {
|
|
32921
|
+
if (!Array.isArray(color))
|
|
32922
|
+
return color || '';
|
|
32923
|
+
if (color.length === 0)
|
|
32924
|
+
return '#999';
|
|
32925
|
+
if (color.length < 2)
|
|
32926
|
+
return color[0] || '';
|
|
32927
|
+
const canvasElement = document.createElement('canvas');
|
|
32928
|
+
const ctx = canvasElement.getContext('2d');
|
|
32929
|
+
const gradientHeight = canvasHeight !== null && canvasHeight !== void 0 ? canvasHeight : canvasElement.height * devicePixelRatio;
|
|
32930
|
+
const gradient = ctx.createLinearGradient(0, 0, 0, gradientHeight || devicePixelRatio);
|
|
32931
|
+
const colorStopPercentage = 1 / (color.length - 1);
|
|
32932
|
+
color.forEach((value, index) => {
|
|
32933
|
+
gradient.addColorStop(index * colorStopPercentage, value);
|
|
32934
|
+
});
|
|
32935
|
+
return gradient;
|
|
32936
|
+
}
|
|
32937
|
+
function calculateWaveformLayout({ duration, minPxPerSec = 0, parentWidth, fillParent, pixelRatio, }) {
|
|
32938
|
+
const scrollWidth = Math.ceil(duration * minPxPerSec);
|
|
32939
|
+
const isScrollable = scrollWidth > parentWidth;
|
|
32940
|
+
const useParentWidth = Boolean(fillParent && !isScrollable);
|
|
32941
|
+
const width = (useParentWidth ? parentWidth : scrollWidth) * pixelRatio;
|
|
32942
|
+
return {
|
|
32943
|
+
scrollWidth,
|
|
32944
|
+
isScrollable,
|
|
32945
|
+
useParentWidth,
|
|
32946
|
+
width,
|
|
32947
|
+
};
|
|
32948
|
+
}
|
|
32949
|
+
function clampWidthToBarGrid(width, options) {
|
|
32950
|
+
if (!shouldRenderBars(options))
|
|
32951
|
+
return width;
|
|
32952
|
+
const barWidth = options.barWidth || 0.5;
|
|
32953
|
+
const barGap = options.barGap || barWidth / 2;
|
|
32954
|
+
const totalBarWidth = barWidth + barGap;
|
|
32955
|
+
if (totalBarWidth === 0)
|
|
32956
|
+
return width;
|
|
32957
|
+
return Math.floor(width / totalBarWidth) * totalBarWidth;
|
|
32958
|
+
}
|
|
32959
|
+
function calculateSingleCanvasWidth({ clientWidth, totalWidth, options, }) {
|
|
32960
|
+
const baseWidth = Math.min(MAX_CANVAS_WIDTH, clientWidth, totalWidth);
|
|
32961
|
+
return clampWidthToBarGrid(baseWidth, options);
|
|
32962
|
+
}
|
|
32963
|
+
function sliceChannelData({ channelData, offset, clampedWidth, totalWidth, }) {
|
|
32964
|
+
return channelData.map((channel) => {
|
|
32965
|
+
const start = Math.floor((offset / totalWidth) * channel.length);
|
|
32966
|
+
const end = Math.floor(((offset + clampedWidth) / totalWidth) * channel.length);
|
|
32967
|
+
return channel.slice(start, end);
|
|
32968
|
+
});
|
|
32969
|
+
}
|
|
32970
|
+
function shouldClearCanvases(currentNodeCount) {
|
|
32971
|
+
return currentNodeCount > MAX_NODES;
|
|
32972
|
+
}
|
|
32973
|
+
function getLazyRenderRange({ scrollLeft, totalWidth, numCanvases, }) {
|
|
32974
|
+
if (totalWidth === 0)
|
|
32975
|
+
return [0];
|
|
32976
|
+
const viewPosition = scrollLeft / totalWidth;
|
|
32977
|
+
const startCanvas = Math.floor(viewPosition * numCanvases);
|
|
32978
|
+
return [startCanvas - 1, startCanvas, startCanvas + 1];
|
|
32979
|
+
}
|
|
32980
|
+
function calculateVerticalScale({ channelData, barHeight, normalize, maxPeak, }) {
|
|
32981
|
+
var _a;
|
|
32982
|
+
const baseScale = barHeight || 1;
|
|
32983
|
+
if (!normalize)
|
|
32984
|
+
return baseScale;
|
|
32985
|
+
const firstChannel = channelData[0];
|
|
32986
|
+
if (!firstChannel || firstChannel.length === 0)
|
|
32987
|
+
return baseScale;
|
|
32988
|
+
// Use fixed max peak if provided, otherwise calculate from data
|
|
32989
|
+
let max = maxPeak !== null && maxPeak !== void 0 ? maxPeak : 0;
|
|
32990
|
+
if (!maxPeak) {
|
|
32991
|
+
for (let i = 0; i < firstChannel.length; i++) {
|
|
32992
|
+
const value = (_a = firstChannel[i]) !== null && _a !== void 0 ? _a : 0;
|
|
32993
|
+
const magnitude = Math.abs(value);
|
|
32994
|
+
if (magnitude > max)
|
|
32995
|
+
max = magnitude;
|
|
32996
|
+
}
|
|
32997
|
+
}
|
|
32998
|
+
if (!max)
|
|
32999
|
+
return baseScale;
|
|
33000
|
+
return baseScale / max;
|
|
33001
|
+
}
|
|
33002
|
+
function calculateLinePaths({ channelData, width, height, vScale, }) {
|
|
33003
|
+
const halfHeight = height / 2;
|
|
33004
|
+
const primaryChannel = channelData[0] || [];
|
|
33005
|
+
const secondaryChannel = channelData[1] || primaryChannel;
|
|
33006
|
+
const channels = [primaryChannel, secondaryChannel];
|
|
33007
|
+
return channels.map((channel, index) => {
|
|
33008
|
+
const length = channel.length;
|
|
33009
|
+
const hScale = length ? width / length : 0;
|
|
33010
|
+
const baseY = halfHeight;
|
|
33011
|
+
const direction = index === 0 ? -1 : 1;
|
|
33012
|
+
const path = [{ x: 0, y: baseY }];
|
|
33013
|
+
let prevX = 0;
|
|
33014
|
+
let max = 0;
|
|
33015
|
+
for (let i = 0; i <= length; i++) {
|
|
33016
|
+
const x = Math.round(i * hScale);
|
|
33017
|
+
if (x > prevX) {
|
|
33018
|
+
const heightDelta = Math.round(max * halfHeight * vScale) || 1;
|
|
33019
|
+
const y = baseY + heightDelta * direction;
|
|
33020
|
+
path.push({ x: prevX, y });
|
|
33021
|
+
prevX = x;
|
|
33022
|
+
max = 0;
|
|
33023
|
+
}
|
|
33024
|
+
const value = Math.abs(channel[i] || 0);
|
|
33025
|
+
if (value > max)
|
|
33026
|
+
max = value;
|
|
33027
|
+
}
|
|
33028
|
+
path.push({ x: prevX, y: baseY });
|
|
33029
|
+
return path;
|
|
33030
|
+
});
|
|
33031
|
+
}
|
|
33032
|
+
function roundToHalfAwayFromZero(value) {
|
|
33033
|
+
const scaled = value * 2;
|
|
33034
|
+
const rounded = scaled < 0 ? Math.floor(scaled) : Math.ceil(scaled);
|
|
33035
|
+
return rounded / 2;
|
|
33036
|
+
}
|
|
33037
|
+
|
|
33038
|
+
/**
|
|
33039
|
+
* Event stream utilities for converting DOM events to reactive signals
|
|
33040
|
+
*
|
|
33041
|
+
* These utilities allow composing event handling using reactive primitives.
|
|
33042
|
+
*/
|
|
33043
|
+
/**
|
|
33044
|
+
* Cleanup a stream created with event stream utilities
|
|
33045
|
+
*
|
|
33046
|
+
* This removes event listeners and unsubscribes from sources.
|
|
33047
|
+
*/
|
|
33048
|
+
function cleanup(stream) {
|
|
33049
|
+
const cleanupFn = stream._cleanup;
|
|
33050
|
+
if (typeof cleanupFn === 'function') {
|
|
33051
|
+
cleanupFn();
|
|
33052
|
+
}
|
|
33053
|
+
}
|
|
33054
|
+
|
|
33055
|
+
/**
|
|
33056
|
+
* Reactive drag stream utilities
|
|
33057
|
+
*
|
|
33058
|
+
* Provides declarative drag handling using reactive streams.
|
|
33059
|
+
* Automatically handles mouseup cleanup and supports constraints.
|
|
33060
|
+
*/
|
|
33061
|
+
/**
|
|
33062
|
+
* Create a reactive drag stream from an element
|
|
33063
|
+
*
|
|
33064
|
+
* Emits drag events (start, move, end) as the user drags the element.
|
|
33065
|
+
* Automatically handles pointer capture, multi-touch prevention, and cleanup.
|
|
33066
|
+
*
|
|
33067
|
+
* @example
|
|
33068
|
+
* ```typescript
|
|
33069
|
+
* const dragSignal = createDragStream(element)
|
|
33070
|
+
*
|
|
33071
|
+
* effect(() => {
|
|
33072
|
+
* const drag = dragSignal.value
|
|
33073
|
+
* if (drag?.type === 'move') {
|
|
33074
|
+
* console.log('Dragging:', drag.deltaX, drag.deltaY)
|
|
33075
|
+
* }
|
|
33076
|
+
* }, [dragSignal])
|
|
33077
|
+
* ```
|
|
33078
|
+
*
|
|
33079
|
+
* @param element - Element to make draggable
|
|
33080
|
+
* @param options - Drag configuration options
|
|
33081
|
+
* @returns Signal emitting drag events and cleanup function
|
|
33082
|
+
*/
|
|
33083
|
+
function createDragStream(element, options = {}) {
|
|
33084
|
+
const { threshold = 3, mouseButton = 0, touchDelay = 100 } = options;
|
|
33085
|
+
const dragSignal = signal(null);
|
|
33086
|
+
const activePointers = new Map();
|
|
32579
33087
|
const isTouchDevice = matchMedia('(pointer: coarse)').matches;
|
|
32580
33088
|
let unsubscribeDocument = () => void 0;
|
|
32581
33089
|
const onPointerDown = (event) => {
|
|
32582
33090
|
if (event.button !== mouseButton)
|
|
32583
33091
|
return;
|
|
32584
|
-
event.
|
|
32585
|
-
|
|
33092
|
+
activePointers.set(event.pointerId, event);
|
|
33093
|
+
if (activePointers.size > 1) {
|
|
33094
|
+
return;
|
|
33095
|
+
}
|
|
32586
33096
|
let startX = event.clientX;
|
|
32587
33097
|
let startY = event.clientY;
|
|
32588
33098
|
let isDragging = false;
|
|
32589
33099
|
const touchStartTime = Date.now();
|
|
33100
|
+
const rect = element.getBoundingClientRect();
|
|
33101
|
+
const { left, top } = rect;
|
|
32590
33102
|
const onPointerMove = (event) => {
|
|
32591
|
-
event.
|
|
32592
|
-
|
|
33103
|
+
if (event.defaultPrevented || activePointers.size > 1) {
|
|
33104
|
+
return;
|
|
33105
|
+
}
|
|
32593
33106
|
if (isTouchDevice && Date.now() - touchStartTime < touchDelay)
|
|
32594
33107
|
return;
|
|
32595
33108
|
const x = event.clientX;
|
|
@@ -32597,29 +33110,45 @@ function makeDraggable(element, onDrag, onStart, onEnd, threshold = 3, mouseButt
|
|
|
32597
33110
|
const dx = x - startX;
|
|
32598
33111
|
const dy = y - startY;
|
|
32599
33112
|
if (isDragging || Math.abs(dx) > threshold || Math.abs(dy) > threshold) {
|
|
32600
|
-
|
|
32601
|
-
|
|
33113
|
+
event.preventDefault();
|
|
33114
|
+
event.stopPropagation();
|
|
32602
33115
|
if (!isDragging) {
|
|
32603
|
-
|
|
33116
|
+
// Emit start event
|
|
33117
|
+
dragSignal.set({
|
|
33118
|
+
type: 'start',
|
|
33119
|
+
x: startX - left,
|
|
33120
|
+
y: startY - top,
|
|
33121
|
+
});
|
|
32604
33122
|
isDragging = true;
|
|
32605
33123
|
}
|
|
32606
|
-
|
|
33124
|
+
// Emit move event
|
|
33125
|
+
dragSignal.set({
|
|
33126
|
+
type: 'move',
|
|
33127
|
+
x: x - left,
|
|
33128
|
+
y: y - top,
|
|
33129
|
+
deltaX: dx,
|
|
33130
|
+
deltaY: dy,
|
|
33131
|
+
});
|
|
32607
33132
|
startX = x;
|
|
32608
33133
|
startY = y;
|
|
32609
33134
|
}
|
|
32610
33135
|
};
|
|
32611
33136
|
const onPointerUp = (event) => {
|
|
33137
|
+
activePointers.delete(event.pointerId);
|
|
32612
33138
|
if (isDragging) {
|
|
32613
33139
|
const x = event.clientX;
|
|
32614
33140
|
const y = event.clientY;
|
|
32615
|
-
|
|
32616
|
-
|
|
32617
|
-
|
|
33141
|
+
// Emit end event
|
|
33142
|
+
dragSignal.set({
|
|
33143
|
+
type: 'end',
|
|
33144
|
+
x: x - left,
|
|
33145
|
+
y: y - top,
|
|
33146
|
+
});
|
|
32618
33147
|
}
|
|
32619
33148
|
unsubscribeDocument();
|
|
32620
33149
|
};
|
|
32621
33150
|
const onPointerLeave = (e) => {
|
|
32622
|
-
|
|
33151
|
+
activePointers.delete(e.pointerId);
|
|
32623
33152
|
if (!e.relatedTarget || e.relatedTarget === document.documentElement) {
|
|
32624
33153
|
onPointerUp(e);
|
|
32625
33154
|
}
|
|
@@ -32631,6 +33160,9 @@ function makeDraggable(element, onDrag, onStart, onEnd, threshold = 3, mouseButt
|
|
|
32631
33160
|
}
|
|
32632
33161
|
};
|
|
32633
33162
|
const onTouchMove = (event) => {
|
|
33163
|
+
if (event.defaultPrevented || activePointers.size > 1) {
|
|
33164
|
+
return;
|
|
33165
|
+
}
|
|
32634
33166
|
if (isDragging) {
|
|
32635
33167
|
event.preventDefault();
|
|
32636
33168
|
}
|
|
@@ -32653,9 +33185,114 @@ function makeDraggable(element, onDrag, onStart, onEnd, threshold = 3, mouseButt
|
|
|
32653
33185
|
};
|
|
32654
33186
|
};
|
|
32655
33187
|
element.addEventListener('pointerdown', onPointerDown);
|
|
32656
|
-
|
|
33188
|
+
const cleanupFn = () => {
|
|
32657
33189
|
unsubscribeDocument();
|
|
32658
33190
|
element.removeEventListener('pointerdown', onPointerDown);
|
|
33191
|
+
activePointers.clear();
|
|
33192
|
+
cleanup(dragSignal);
|
|
33193
|
+
};
|
|
33194
|
+
return {
|
|
33195
|
+
signal: dragSignal,
|
|
33196
|
+
cleanup: cleanupFn,
|
|
33197
|
+
};
|
|
33198
|
+
}
|
|
33199
|
+
|
|
33200
|
+
/**
|
|
33201
|
+
* Reactive scroll stream utilities
|
|
33202
|
+
*
|
|
33203
|
+
* Provides declarative scroll handling using reactive streams.
|
|
33204
|
+
* Automatically handles scroll event optimization and cleanup.
|
|
33205
|
+
*/
|
|
33206
|
+
// ============================================================================
|
|
33207
|
+
// Pure Scroll Calculation Functions
|
|
33208
|
+
// ============================================================================
|
|
33209
|
+
/**
|
|
33210
|
+
* Calculate visible percentages from scroll data
|
|
33211
|
+
* Pure function - no side effects
|
|
33212
|
+
*
|
|
33213
|
+
* @param scrollData - Current scroll dimensions
|
|
33214
|
+
* @returns Start and end positions as percentages (0-1)
|
|
33215
|
+
*/
|
|
33216
|
+
function calculateScrollPercentages(scrollData) {
|
|
33217
|
+
const { scrollLeft, scrollWidth, clientWidth } = scrollData;
|
|
33218
|
+
if (scrollWidth === 0) {
|
|
33219
|
+
return { startX: 0, endX: 1 };
|
|
33220
|
+
}
|
|
33221
|
+
const startX = scrollLeft / scrollWidth;
|
|
33222
|
+
const endX = (scrollLeft + clientWidth) / scrollWidth;
|
|
33223
|
+
return {
|
|
33224
|
+
startX: Math.max(0, Math.min(1, startX)),
|
|
33225
|
+
endX: Math.max(0, Math.min(1, endX)),
|
|
33226
|
+
};
|
|
33227
|
+
}
|
|
33228
|
+
/**
|
|
33229
|
+
* Calculate scroll bounds in pixels
|
|
33230
|
+
* Pure function - no side effects
|
|
33231
|
+
*
|
|
33232
|
+
* @param scrollData - Current scroll dimensions
|
|
33233
|
+
* @returns Left and right scroll bounds in pixels
|
|
33234
|
+
*/
|
|
33235
|
+
function calculateScrollBounds(scrollData) {
|
|
33236
|
+
return {
|
|
33237
|
+
left: scrollData.scrollLeft,
|
|
33238
|
+
right: scrollData.scrollLeft + scrollData.clientWidth,
|
|
33239
|
+
};
|
|
33240
|
+
}
|
|
33241
|
+
/**
|
|
33242
|
+
* Create a reactive scroll stream from an element
|
|
33243
|
+
*
|
|
33244
|
+
* Emits scroll data as the user scrolls the element.
|
|
33245
|
+
* Automatically computes derived values (percentages, bounds).
|
|
33246
|
+
*
|
|
33247
|
+
* @example
|
|
33248
|
+
* ```typescript
|
|
33249
|
+
* const scrollStream = createScrollStream(container)
|
|
33250
|
+
*
|
|
33251
|
+
* effect(() => {
|
|
33252
|
+
* const { startX, endX } = scrollStream.percentages.value
|
|
33253
|
+
* console.log('Visible:', startX, 'to', endX)
|
|
33254
|
+
* }, [scrollStream.percentages])
|
|
33255
|
+
*
|
|
33256
|
+
* scrollStream.cleanup()
|
|
33257
|
+
* ```
|
|
33258
|
+
*
|
|
33259
|
+
* @param element - Scrollable element
|
|
33260
|
+
* @returns Scroll stream with signals and cleanup
|
|
33261
|
+
*/
|
|
33262
|
+
function createScrollStream(element) {
|
|
33263
|
+
// Create signals
|
|
33264
|
+
const scrollData = signal({
|
|
33265
|
+
scrollLeft: element.scrollLeft,
|
|
33266
|
+
scrollWidth: element.scrollWidth,
|
|
33267
|
+
clientWidth: element.clientWidth,
|
|
33268
|
+
});
|
|
33269
|
+
// Computed derived values
|
|
33270
|
+
const percentages = computed(() => {
|
|
33271
|
+
return calculateScrollPercentages(scrollData.value);
|
|
33272
|
+
}, [scrollData]);
|
|
33273
|
+
const bounds = computed(() => {
|
|
33274
|
+
return calculateScrollBounds(scrollData.value);
|
|
33275
|
+
}, [scrollData]);
|
|
33276
|
+
// Update scroll data on scroll event
|
|
33277
|
+
const onScroll = () => {
|
|
33278
|
+
scrollData.set({
|
|
33279
|
+
scrollLeft: element.scrollLeft,
|
|
33280
|
+
scrollWidth: element.scrollWidth,
|
|
33281
|
+
clientWidth: element.clientWidth,
|
|
33282
|
+
});
|
|
33283
|
+
};
|
|
33284
|
+
// Attach scroll listener
|
|
33285
|
+
element.addEventListener('scroll', onScroll, { passive: true });
|
|
33286
|
+
// Cleanup function
|
|
33287
|
+
const cleanupFn = () => {
|
|
33288
|
+
element.removeEventListener('scroll', onScroll);
|
|
33289
|
+
cleanup(scrollData);
|
|
33290
|
+
};
|
|
33291
|
+
return {
|
|
33292
|
+
scrollData,
|
|
33293
|
+
percentages,
|
|
33294
|
+
bounds,
|
|
33295
|
+
cleanup: cleanupFn,
|
|
32659
33296
|
};
|
|
32660
33297
|
}
|
|
32661
33298
|
|
|
@@ -32690,6 +33327,8 @@ class Renderer extends EventEmitter {
|
|
|
32690
33327
|
this.isDragging = false;
|
|
32691
33328
|
this.subscriptions = [];
|
|
32692
33329
|
this.unsubscribeOnScroll = [];
|
|
33330
|
+
this.dragStream = null;
|
|
33331
|
+
this.scrollStream = null;
|
|
32693
33332
|
this.subscriptions = [];
|
|
32694
33333
|
this.options = options;
|
|
32695
33334
|
const parent = this.parentFromOptionsContainer(options.container);
|
|
@@ -32721,35 +33360,30 @@ class Renderer extends EventEmitter {
|
|
|
32721
33360
|
return parent;
|
|
32722
33361
|
}
|
|
32723
33362
|
initEvents() {
|
|
32724
|
-
const getClickPosition = (e) => {
|
|
32725
|
-
const rect = this.wrapper.getBoundingClientRect();
|
|
32726
|
-
const x = e.clientX - rect.left;
|
|
32727
|
-
const y = e.clientY - rect.top;
|
|
32728
|
-
const relativeX = x / rect.width;
|
|
32729
|
-
const relativeY = y / rect.height;
|
|
32730
|
-
return [relativeX, relativeY];
|
|
32731
|
-
};
|
|
32732
33363
|
// Add a click listener
|
|
32733
33364
|
this.wrapper.addEventListener('click', (e) => {
|
|
32734
|
-
const
|
|
33365
|
+
const rect = this.wrapper.getBoundingClientRect();
|
|
33366
|
+
const [x, y] = getRelativePointerPosition(rect, e.clientX, e.clientY);
|
|
32735
33367
|
this.emit('click', x, y);
|
|
32736
33368
|
});
|
|
32737
33369
|
// Add a double click listener
|
|
32738
33370
|
this.wrapper.addEventListener('dblclick', (e) => {
|
|
32739
|
-
const
|
|
33371
|
+
const rect = this.wrapper.getBoundingClientRect();
|
|
33372
|
+
const [x, y] = getRelativePointerPosition(rect, e.clientX, e.clientY);
|
|
32740
33373
|
this.emit('dblclick', x, y);
|
|
32741
33374
|
});
|
|
32742
33375
|
// Drag
|
|
32743
33376
|
if (this.options.dragToSeek === true || typeof this.options.dragToSeek === 'object') {
|
|
32744
33377
|
this.initDrag();
|
|
32745
33378
|
}
|
|
32746
|
-
// Add a scroll listener
|
|
32747
|
-
this.
|
|
32748
|
-
|
|
32749
|
-
const startX
|
|
32750
|
-
const
|
|
32751
|
-
this.emit('scroll', startX, endX,
|
|
32752
|
-
});
|
|
33379
|
+
// Add a scroll listener using reactive stream
|
|
33380
|
+
this.scrollStream = createScrollStream(this.scrollContainer);
|
|
33381
|
+
const unsubscribeScroll = effect(() => {
|
|
33382
|
+
const { startX, endX } = this.scrollStream.percentages.value;
|
|
33383
|
+
const { left, right } = this.scrollStream.bounds.value;
|
|
33384
|
+
this.emit('scroll', startX, endX, left, right);
|
|
33385
|
+
}, [this.scrollStream.percentages, this.scrollStream.bounds]);
|
|
33386
|
+
this.subscriptions.push(unsubscribeScroll);
|
|
32753
33387
|
// Re-render the waveform on container resize
|
|
32754
33388
|
if (typeof ResizeObserver === 'function') {
|
|
32755
33389
|
const delay = this.createDelay(100);
|
|
@@ -32767,39 +33401,32 @@ class Renderer extends EventEmitter {
|
|
|
32767
33401
|
return;
|
|
32768
33402
|
this.lastContainerWidth = width;
|
|
32769
33403
|
this.reRender();
|
|
33404
|
+
this.emit('resize');
|
|
32770
33405
|
}
|
|
32771
33406
|
initDrag() {
|
|
32772
|
-
|
|
32773
|
-
|
|
32774
|
-
|
|
32775
|
-
|
|
32776
|
-
|
|
32777
|
-
|
|
32778
|
-
|
|
32779
|
-
|
|
32780
|
-
|
|
32781
|
-
|
|
32782
|
-
|
|
32783
|
-
|
|
32784
|
-
|
|
32785
|
-
|
|
32786
|
-
|
|
32787
|
-
|
|
32788
|
-
|
|
32789
|
-
|
|
32790
|
-
|
|
32791
|
-
|
|
32792
|
-
|
|
32793
|
-
|
|
32794
|
-
|
|
32795
|
-
return Number(optionsHeight);
|
|
32796
|
-
if (optionsHeight === 'auto') {
|
|
32797
|
-
const height = this.parent.clientHeight || defaultHeight;
|
|
32798
|
-
if (optionsSplitChannel === null || optionsSplitChannel === void 0 ? void 0 : optionsSplitChannel.every((channel) => !channel.overlay))
|
|
32799
|
-
return height / numberOfChannels;
|
|
32800
|
-
return height;
|
|
32801
|
-
}
|
|
32802
|
-
return defaultHeight;
|
|
33407
|
+
// Don't initialize drag if it's already set up
|
|
33408
|
+
if (this.dragStream)
|
|
33409
|
+
return;
|
|
33410
|
+
this.dragStream = createDragStream(this.wrapper);
|
|
33411
|
+
const unsubscribeDrag = effect(() => {
|
|
33412
|
+
const drag = this.dragStream.signal.value;
|
|
33413
|
+
if (!drag)
|
|
33414
|
+
return;
|
|
33415
|
+
const width = this.wrapper.getBoundingClientRect().width;
|
|
33416
|
+
const relX = clampToUnit(drag.x / width);
|
|
33417
|
+
if (drag.type === 'start') {
|
|
33418
|
+
this.isDragging = true;
|
|
33419
|
+
this.emit('dragstart', relX);
|
|
33420
|
+
}
|
|
33421
|
+
else if (drag.type === 'move') {
|
|
33422
|
+
this.emit('drag', relX);
|
|
33423
|
+
}
|
|
33424
|
+
else if (drag.type === 'end') {
|
|
33425
|
+
this.isDragging = false;
|
|
33426
|
+
this.emit('dragend', relX);
|
|
33427
|
+
}
|
|
33428
|
+
}, [this.dragStream.signal]);
|
|
33429
|
+
this.subscriptions.push(unsubscribeDrag);
|
|
32803
33430
|
}
|
|
32804
33431
|
initHtml() {
|
|
32805
33432
|
const div = document.createElement('div');
|
|
@@ -32836,6 +33463,7 @@ class Renderer extends EventEmitter {
|
|
|
32836
33463
|
}
|
|
32837
33464
|
:host .canvases {
|
|
32838
33465
|
min-height: ${this.getHeight(this.options.height, this.options.splitChannels)}px;
|
|
33466
|
+
pointer-events: none;
|
|
32839
33467
|
}
|
|
32840
33468
|
:host .canvases > div {
|
|
32841
33469
|
position: relative;
|
|
@@ -32912,150 +33540,134 @@ class Renderer extends EventEmitter {
|
|
|
32912
33540
|
this.setScroll(scrollStart);
|
|
32913
33541
|
}
|
|
32914
33542
|
destroy() {
|
|
32915
|
-
var _a
|
|
33543
|
+
var _a;
|
|
32916
33544
|
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
32917
33545
|
this.container.remove();
|
|
32918
|
-
(
|
|
32919
|
-
|
|
33546
|
+
if (this.resizeObserver) {
|
|
33547
|
+
this.resizeObserver.disconnect();
|
|
33548
|
+
this.resizeObserver = null;
|
|
33549
|
+
}
|
|
33550
|
+
(_a = this.unsubscribeOnScroll) === null || _a === void 0 ? void 0 : _a.forEach((unsubscribe) => unsubscribe());
|
|
32920
33551
|
this.unsubscribeOnScroll = [];
|
|
33552
|
+
if (this.dragStream) {
|
|
33553
|
+
this.dragStream.cleanup();
|
|
33554
|
+
this.dragStream = null;
|
|
33555
|
+
}
|
|
33556
|
+
if (this.scrollStream) {
|
|
33557
|
+
this.scrollStream.cleanup();
|
|
33558
|
+
this.scrollStream = null;
|
|
33559
|
+
}
|
|
32921
33560
|
}
|
|
32922
33561
|
createDelay(delayMs = 10) {
|
|
32923
33562
|
let timeout;
|
|
32924
|
-
let
|
|
33563
|
+
let rejectFn;
|
|
32925
33564
|
const onClear = () => {
|
|
32926
|
-
if (timeout)
|
|
33565
|
+
if (timeout) {
|
|
32927
33566
|
clearTimeout(timeout);
|
|
32928
|
-
|
|
32929
|
-
|
|
33567
|
+
timeout = undefined;
|
|
33568
|
+
}
|
|
33569
|
+
if (rejectFn) {
|
|
33570
|
+
rejectFn();
|
|
33571
|
+
rejectFn = undefined;
|
|
33572
|
+
}
|
|
32930
33573
|
};
|
|
32931
33574
|
this.timeouts.push(onClear);
|
|
32932
33575
|
return () => {
|
|
32933
|
-
return new Promise((
|
|
33576
|
+
return new Promise((resolve, reject) => {
|
|
33577
|
+
// Clear any pending delay
|
|
32934
33578
|
onClear();
|
|
32935
|
-
reject
|
|
33579
|
+
// Store reject function for cleanup
|
|
33580
|
+
rejectFn = reject;
|
|
33581
|
+
// Set new timeout
|
|
32936
33582
|
timeout = setTimeout(() => {
|
|
32937
33583
|
timeout = undefined;
|
|
32938
|
-
|
|
32939
|
-
|
|
33584
|
+
rejectFn = undefined;
|
|
33585
|
+
resolve();
|
|
32940
33586
|
}, delayMs);
|
|
32941
33587
|
});
|
|
32942
33588
|
};
|
|
32943
33589
|
}
|
|
32944
|
-
|
|
32945
|
-
|
|
32946
|
-
|
|
32947
|
-
|
|
32948
|
-
|
|
32949
|
-
|
|
32950
|
-
|
|
32951
|
-
|
|
32952
|
-
|
|
32953
|
-
const gradient = ctx.createLinearGradient(0, 0, 0, gradientHeight);
|
|
32954
|
-
const colorStopPercentage = 1 / (color.length - 1);
|
|
32955
|
-
color.forEach((color, index) => {
|
|
32956
|
-
const offset = index * colorStopPercentage;
|
|
32957
|
-
gradient.addColorStop(offset, color);
|
|
33590
|
+
getHeight(optionsHeight, optionsSplitChannel) {
|
|
33591
|
+
var _a;
|
|
33592
|
+
const numberOfChannels = ((_a = this.audioData) === null || _a === void 0 ? void 0 : _a.numberOfChannels) || 1;
|
|
33593
|
+
return resolveChannelHeight({
|
|
33594
|
+
optionsHeight,
|
|
33595
|
+
optionsSplitChannels: optionsSplitChannel,
|
|
33596
|
+
parentHeight: this.parent.clientHeight,
|
|
33597
|
+
numberOfChannels,
|
|
33598
|
+
defaultHeight: DEFAULT_HEIGHT,
|
|
32958
33599
|
});
|
|
32959
|
-
|
|
33600
|
+
}
|
|
33601
|
+
convertColorValues(color, ctx) {
|
|
33602
|
+
return resolveColorValue(color, this.getPixelRatio(), ctx === null || ctx === void 0 ? void 0 : ctx.canvas.height);
|
|
32960
33603
|
}
|
|
32961
33604
|
getPixelRatio() {
|
|
32962
|
-
return
|
|
33605
|
+
return getPixelRatio(window.devicePixelRatio);
|
|
32963
33606
|
}
|
|
32964
33607
|
renderBarWaveform(channelData, options, ctx, vScale) {
|
|
32965
|
-
const topChannel = channelData[0];
|
|
32966
|
-
const bottomChannel = channelData[1] || channelData[0];
|
|
32967
|
-
const length = topChannel.length;
|
|
32968
33608
|
const { width, height } = ctx.canvas;
|
|
32969
|
-
const halfHeight
|
|
32970
|
-
|
|
32971
|
-
|
|
32972
|
-
|
|
32973
|
-
|
|
32974
|
-
|
|
32975
|
-
|
|
33609
|
+
const { halfHeight, barWidth, barRadius, barIndexScale, barSpacing, barMinHeight } = calculateBarRenderConfig({
|
|
33610
|
+
width,
|
|
33611
|
+
height,
|
|
33612
|
+
length: (channelData[0] || []).length,
|
|
33613
|
+
options,
|
|
33614
|
+
pixelRatio: this.getPixelRatio(),
|
|
33615
|
+
});
|
|
33616
|
+
const segments = calculateBarSegments({
|
|
33617
|
+
channelData,
|
|
33618
|
+
barIndexScale,
|
|
33619
|
+
barSpacing,
|
|
33620
|
+
barWidth,
|
|
33621
|
+
halfHeight,
|
|
33622
|
+
vScale,
|
|
33623
|
+
canvasHeight: height,
|
|
33624
|
+
barAlign: options.barAlign,
|
|
33625
|
+
barMinHeight,
|
|
33626
|
+
});
|
|
32976
33627
|
ctx.beginPath();
|
|
32977
|
-
|
|
32978
|
-
|
|
32979
|
-
|
|
32980
|
-
|
|
32981
|
-
|
|
32982
|
-
|
|
32983
|
-
const topBarHeight = Math.round(maxTop * halfHeight * vScale);
|
|
32984
|
-
const bottomBarHeight = Math.round(maxBottom * halfHeight * vScale);
|
|
32985
|
-
const barHeight = topBarHeight + bottomBarHeight || 1;
|
|
32986
|
-
// Vertical alignment
|
|
32987
|
-
let y = halfHeight - topBarHeight;
|
|
32988
|
-
if (options.barAlign === 'top') {
|
|
32989
|
-
y = 0;
|
|
32990
|
-
}
|
|
32991
|
-
else if (options.barAlign === 'bottom') {
|
|
32992
|
-
y = height - barHeight;
|
|
32993
|
-
}
|
|
32994
|
-
ctx[rectFn](prevX * (barWidth + barGap), y, barWidth, barHeight, barRadius);
|
|
32995
|
-
prevX = x;
|
|
32996
|
-
maxTop = 0;
|
|
32997
|
-
maxBottom = 0;
|
|
33628
|
+
for (const segment of segments) {
|
|
33629
|
+
if (barRadius && 'roundRect' in ctx) {
|
|
33630
|
+
ctx.roundRect(segment.x, segment.y, segment.width, segment.height, barRadius);
|
|
33631
|
+
}
|
|
33632
|
+
else {
|
|
33633
|
+
ctx.rect(segment.x, segment.y, segment.width, segment.height);
|
|
32998
33634
|
}
|
|
32999
|
-
const magnitudeTop = Math.abs(topChannel[i] || 0);
|
|
33000
|
-
const magnitudeBottom = Math.abs(bottomChannel[i] || 0);
|
|
33001
|
-
if (magnitudeTop > maxTop)
|
|
33002
|
-
maxTop = magnitudeTop;
|
|
33003
|
-
if (magnitudeBottom > maxBottom)
|
|
33004
|
-
maxBottom = magnitudeBottom;
|
|
33005
33635
|
}
|
|
33006
33636
|
ctx.fill();
|
|
33007
33637
|
ctx.closePath();
|
|
33008
33638
|
}
|
|
33009
33639
|
renderLineWaveform(channelData, _options, ctx, vScale) {
|
|
33010
|
-
const
|
|
33011
|
-
|
|
33012
|
-
const length = channel.length;
|
|
33013
|
-
const { height } = ctx.canvas;
|
|
33014
|
-
const halfHeight = height / 2;
|
|
33015
|
-
const hScale = ctx.canvas.width / length;
|
|
33016
|
-
ctx.moveTo(0, halfHeight);
|
|
33017
|
-
let prevX = 0;
|
|
33018
|
-
let max = 0;
|
|
33019
|
-
for (let i = 0; i <= length; i++) {
|
|
33020
|
-
const x = Math.round(i * hScale);
|
|
33021
|
-
if (x > prevX) {
|
|
33022
|
-
const h = Math.round(max * halfHeight * vScale) || 1;
|
|
33023
|
-
const y = halfHeight + h * (index === 0 ? -1 : 1);
|
|
33024
|
-
ctx.lineTo(prevX, y);
|
|
33025
|
-
prevX = x;
|
|
33026
|
-
max = 0;
|
|
33027
|
-
}
|
|
33028
|
-
const value = Math.abs(channel[i] || 0);
|
|
33029
|
-
if (value > max)
|
|
33030
|
-
max = value;
|
|
33031
|
-
}
|
|
33032
|
-
ctx.lineTo(prevX, halfHeight);
|
|
33033
|
-
};
|
|
33640
|
+
const { width, height } = ctx.canvas;
|
|
33641
|
+
const paths = calculateLinePaths({ channelData, width, height, vScale });
|
|
33034
33642
|
ctx.beginPath();
|
|
33035
|
-
|
|
33036
|
-
|
|
33643
|
+
for (const path of paths) {
|
|
33644
|
+
if (!path.length)
|
|
33645
|
+
continue;
|
|
33646
|
+
ctx.moveTo(path[0].x, path[0].y);
|
|
33647
|
+
for (let i = 1; i < path.length; i++) {
|
|
33648
|
+
const point = path[i];
|
|
33649
|
+
ctx.lineTo(point.x, point.y);
|
|
33650
|
+
}
|
|
33651
|
+
}
|
|
33037
33652
|
ctx.fill();
|
|
33038
33653
|
ctx.closePath();
|
|
33039
33654
|
}
|
|
33040
33655
|
renderWaveform(channelData, options, ctx) {
|
|
33041
|
-
ctx.fillStyle = this.convertColorValues(options.waveColor);
|
|
33042
|
-
// Custom rendering function
|
|
33656
|
+
ctx.fillStyle = this.convertColorValues(options.waveColor, ctx);
|
|
33043
33657
|
if (options.renderFunction) {
|
|
33044
33658
|
options.renderFunction(channelData, ctx);
|
|
33045
33659
|
return;
|
|
33046
33660
|
}
|
|
33047
|
-
|
|
33048
|
-
|
|
33049
|
-
|
|
33050
|
-
|
|
33051
|
-
|
|
33052
|
-
}
|
|
33053
|
-
|
|
33054
|
-
if (options.barWidth || options.barGap || options.barAlign) {
|
|
33661
|
+
const vScale = calculateVerticalScale({
|
|
33662
|
+
channelData,
|
|
33663
|
+
barHeight: options.barHeight,
|
|
33664
|
+
normalize: options.normalize,
|
|
33665
|
+
maxPeak: options.maxPeak,
|
|
33666
|
+
});
|
|
33667
|
+
if (shouldRenderBars(options)) {
|
|
33055
33668
|
this.renderBarWaveform(channelData, options, ctx, vScale);
|
|
33056
33669
|
return;
|
|
33057
33670
|
}
|
|
33058
|
-
// Render waveform as a polyline
|
|
33059
33671
|
this.renderLineWaveform(channelData, options, ctx, vScale);
|
|
33060
33672
|
}
|
|
33061
33673
|
renderSingleCanvas(data, options, width, height, offset, canvasContainer, progressContainer) {
|
|
@@ -33068,7 +33680,13 @@ class Renderer extends EventEmitter {
|
|
|
33068
33680
|
canvas.style.left = `${Math.round(offset)}px`;
|
|
33069
33681
|
canvasContainer.appendChild(canvas);
|
|
33070
33682
|
const ctx = canvas.getContext('2d');
|
|
33071
|
-
|
|
33683
|
+
if (options.renderFunction) {
|
|
33684
|
+
ctx.fillStyle = this.convertColorValues(options.waveColor, ctx);
|
|
33685
|
+
options.renderFunction(data, ctx);
|
|
33686
|
+
}
|
|
33687
|
+
else {
|
|
33688
|
+
this.renderWaveform(data, options, ctx);
|
|
33689
|
+
}
|
|
33072
33690
|
// Draw a progress canvas
|
|
33073
33691
|
if (canvas.width > 0 && canvas.height > 0) {
|
|
33074
33692
|
const progressCanvas = canvas.cloneNode();
|
|
@@ -33076,7 +33694,7 @@ class Renderer extends EventEmitter {
|
|
|
33076
33694
|
progressCtx.drawImage(canvas, 0, 0);
|
|
33077
33695
|
// Set the composition method to draw only where the waveform is drawn
|
|
33078
33696
|
progressCtx.globalCompositeOperation = 'source-in';
|
|
33079
|
-
progressCtx.fillStyle = this.convertColorValues(options.progressColor);
|
|
33697
|
+
progressCtx.fillStyle = this.convertColorValues(options.progressColor, progressCtx);
|
|
33080
33698
|
// This rectangle acts as a mask thanks to the composition method
|
|
33081
33699
|
progressCtx.fillRect(0, 0, canvas.width, canvas.height);
|
|
33082
33700
|
progressContainer.appendChild(progressCanvas);
|
|
@@ -33086,17 +33704,8 @@ class Renderer extends EventEmitter {
|
|
|
33086
33704
|
const pixelRatio = this.getPixelRatio();
|
|
33087
33705
|
const { clientWidth } = this.scrollContainer;
|
|
33088
33706
|
const totalWidth = width / pixelRatio;
|
|
33089
|
-
|
|
33707
|
+
const singleCanvasWidth = calculateSingleCanvasWidth({ clientWidth, totalWidth, options });
|
|
33090
33708
|
let drawnIndexes = {};
|
|
33091
|
-
// Adjust width to avoid gaps between canvases when using bars
|
|
33092
|
-
if (options.barWidth || options.barGap) {
|
|
33093
|
-
const barWidth = options.barWidth || 0.5;
|
|
33094
|
-
const barGap = options.barGap || barWidth / 2;
|
|
33095
|
-
const totalBarWidth = barWidth + barGap;
|
|
33096
|
-
if (singleCanvasWidth % totalBarWidth !== 0) {
|
|
33097
|
-
singleCanvasWidth = Math.floor(singleCanvasWidth / totalBarWidth) * totalBarWidth;
|
|
33098
|
-
}
|
|
33099
|
-
}
|
|
33100
33709
|
// Nothing to render
|
|
33101
33710
|
if (singleCanvasWidth === 0)
|
|
33102
33711
|
return;
|
|
@@ -33110,24 +33719,15 @@ class Renderer extends EventEmitter {
|
|
|
33110
33719
|
const offset = index * singleCanvasWidth;
|
|
33111
33720
|
let clampedWidth = Math.min(totalWidth - offset, singleCanvasWidth);
|
|
33112
33721
|
// Clamp the width to the bar grid to avoid empty canvases at the end
|
|
33113
|
-
|
|
33114
|
-
const barWidth = options.barWidth || 0.5;
|
|
33115
|
-
const barGap = options.barGap || barWidth / 2;
|
|
33116
|
-
const totalBarWidth = barWidth + barGap;
|
|
33117
|
-
clampedWidth = Math.floor(clampedWidth / totalBarWidth) * totalBarWidth;
|
|
33118
|
-
}
|
|
33722
|
+
clampedWidth = clampWidthToBarGrid(clampedWidth, options);
|
|
33119
33723
|
if (clampedWidth <= 0)
|
|
33120
33724
|
return;
|
|
33121
|
-
const data =
|
|
33122
|
-
const start = Math.floor((offset / totalWidth) * channel.length);
|
|
33123
|
-
const end = Math.floor(((offset + clampedWidth) / totalWidth) * channel.length);
|
|
33124
|
-
return channel.slice(start, end);
|
|
33125
|
-
});
|
|
33725
|
+
const data = sliceChannelData({ channelData, offset, clampedWidth, totalWidth });
|
|
33126
33726
|
this.renderSingleCanvas(data, options, clampedWidth, height, offset, canvasContainer, progressContainer);
|
|
33127
33727
|
};
|
|
33128
33728
|
// Clear canvases to avoid too many DOM nodes
|
|
33129
33729
|
const clearCanvases = () => {
|
|
33130
|
-
if (Object.keys(drawnIndexes).length
|
|
33730
|
+
if (shouldClearCanvases(Object.keys(drawnIndexes).length)) {
|
|
33131
33731
|
canvasContainer.innerHTML = '';
|
|
33132
33732
|
progressContainer.innerHTML = '';
|
|
33133
33733
|
drawnIndexes = {};
|
|
@@ -33143,21 +33743,18 @@ class Renderer extends EventEmitter {
|
|
|
33143
33743
|
return;
|
|
33144
33744
|
}
|
|
33145
33745
|
// Lazy rendering
|
|
33146
|
-
const
|
|
33147
|
-
|
|
33148
|
-
|
|
33149
|
-
|
|
33150
|
-
|
|
33151
|
-
|
|
33746
|
+
const initialRange = getLazyRenderRange({
|
|
33747
|
+
scrollLeft: this.scrollContainer.scrollLeft,
|
|
33748
|
+
totalWidth,
|
|
33749
|
+
numCanvases,
|
|
33750
|
+
});
|
|
33751
|
+
initialRange.forEach((index) => draw(index));
|
|
33152
33752
|
// Subscribe to the scroll event to draw additional canvases
|
|
33153
33753
|
if (numCanvases > 1) {
|
|
33154
33754
|
const unsubscribe = this.on('scroll', () => {
|
|
33155
33755
|
const { scrollLeft } = this.scrollContainer;
|
|
33156
|
-
const canvasIndex = Math.floor((scrollLeft / totalWidth) * numCanvases);
|
|
33157
33756
|
clearCanvases();
|
|
33158
|
-
|
|
33159
|
-
draw(canvasIndex);
|
|
33160
|
-
draw(canvasIndex + 1);
|
|
33757
|
+
getLazyRenderRange({ scrollLeft, totalWidth, numCanvases }).forEach((index) => draw(index));
|
|
33161
33758
|
});
|
|
33162
33759
|
this.unsubscribeOnScroll.push(unsubscribe);
|
|
33163
33760
|
}
|
|
@@ -33196,12 +33793,15 @@ class Renderer extends EventEmitter {
|
|
|
33196
33793
|
// Determine the width of the waveform
|
|
33197
33794
|
const pixelRatio = this.getPixelRatio();
|
|
33198
33795
|
const parentWidth = this.scrollContainer.clientWidth;
|
|
33199
|
-
const scrollWidth
|
|
33796
|
+
const { scrollWidth, isScrollable, useParentWidth, width } = calculateWaveformLayout({
|
|
33797
|
+
duration: audioData.duration,
|
|
33798
|
+
minPxPerSec: this.options.minPxPerSec || 0,
|
|
33799
|
+
parentWidth,
|
|
33800
|
+
fillParent: this.options.fillParent,
|
|
33801
|
+
pixelRatio,
|
|
33802
|
+
});
|
|
33200
33803
|
// Whether the container should scroll
|
|
33201
|
-
this.isScrollable =
|
|
33202
|
-
const useParentWidth = this.options.fillParent && !this.isScrollable;
|
|
33203
|
-
// Width of the waveform in pixels
|
|
33204
|
-
const width = (useParentWidth ? parentWidth : scrollWidth) * pixelRatio;
|
|
33804
|
+
this.isScrollable = isScrollable;
|
|
33205
33805
|
// Set the width of the wrapper
|
|
33206
33806
|
this.wrapper.style.width = useParentWidth ? '100%' : `${scrollWidth}px`;
|
|
33207
33807
|
// Set additional styles
|
|
@@ -33244,12 +33844,7 @@ class Renderer extends EventEmitter {
|
|
|
33244
33844
|
// Adjust the scroll position so that the cursor stays in the same place
|
|
33245
33845
|
if (this.isScrollable && scrollWidth !== this.scrollContainer.scrollWidth) {
|
|
33246
33846
|
const { right: after } = this.progressWrapper.getBoundingClientRect();
|
|
33247
|
-
|
|
33248
|
-
// to limit compounding floating-point drift
|
|
33249
|
-
// we need to round to the half px furthest from 0
|
|
33250
|
-
delta *= 2;
|
|
33251
|
-
delta = delta < 0 ? Math.floor(delta) : Math.ceil(delta);
|
|
33252
|
-
delta /= 2;
|
|
33847
|
+
const delta = roundToHalfAwayFromZero(after - before);
|
|
33253
33848
|
this.scrollContainer.scrollLeft += delta;
|
|
33254
33849
|
}
|
|
33255
33850
|
}
|
|
@@ -33280,16 +33875,9 @@ class Renderer extends EventEmitter {
|
|
|
33280
33875
|
// Keep the cursor centered when playing
|
|
33281
33876
|
const center = progressWidth - scrollLeft - middle;
|
|
33282
33877
|
if (isPlaying && this.options.autoCenter && center > 0) {
|
|
33283
|
-
this.scrollContainer.scrollLeft +=
|
|
33878
|
+
this.scrollContainer.scrollLeft += center;
|
|
33284
33879
|
}
|
|
33285
33880
|
}
|
|
33286
|
-
// Emit the scroll event
|
|
33287
|
-
{
|
|
33288
|
-
const newScroll = this.scrollContainer.scrollLeft;
|
|
33289
|
-
const startX = newScroll / scrollWidth;
|
|
33290
|
-
const endX = (newScroll + clientWidth) / scrollWidth;
|
|
33291
|
-
this.emit('scroll', startX, endX, newScroll, newScroll + clientWidth);
|
|
33292
|
-
}
|
|
33293
33881
|
}
|
|
33294
33882
|
renderProgress(progress, isPlaying) {
|
|
33295
33883
|
if (isNaN(progress))
|
|
@@ -33301,7 +33889,8 @@ class Renderer extends EventEmitter {
|
|
|
33301
33889
|
this.cursor.style.transform = this.options.cursorWidth
|
|
33302
33890
|
? `translateX(-${progress * this.options.cursorWidth}px)`
|
|
33303
33891
|
: '';
|
|
33304
|
-
if
|
|
33892
|
+
// Only scroll if we have valid audio data to prevent race conditions during loading
|
|
33893
|
+
if (this.isScrollable && this.options.autoScroll && this.audioData && this.audioData.duration > 0) {
|
|
33305
33894
|
this.scrollIntoView(progress, isPlaying);
|
|
33306
33895
|
}
|
|
33307
33896
|
}
|
|
@@ -33332,27 +33921,39 @@ class Renderer extends EventEmitter {
|
|
|
33332
33921
|
});
|
|
33333
33922
|
}
|
|
33334
33923
|
}
|
|
33335
|
-
Renderer.MAX_CANVAS_WIDTH = 8000;
|
|
33336
|
-
Renderer.MAX_NODES = 10;
|
|
33337
33924
|
|
|
33338
33925
|
class Timer extends EventEmitter {
|
|
33339
33926
|
constructor() {
|
|
33340
33927
|
super(...arguments);
|
|
33341
|
-
this.
|
|
33928
|
+
this.animationFrameId = null;
|
|
33929
|
+
this.isRunning = false;
|
|
33342
33930
|
}
|
|
33343
33931
|
start() {
|
|
33344
|
-
|
|
33345
|
-
|
|
33346
|
-
|
|
33347
|
-
|
|
33348
|
-
|
|
33349
|
-
|
|
33932
|
+
// Prevent multiple simultaneous loops
|
|
33933
|
+
if (this.isRunning)
|
|
33934
|
+
return;
|
|
33935
|
+
this.isRunning = true;
|
|
33936
|
+
const tick = () => {
|
|
33937
|
+
// Only continue if timer is still running
|
|
33938
|
+
if (!this.isRunning)
|
|
33939
|
+
return;
|
|
33940
|
+
this.emit('tick');
|
|
33941
|
+
// Schedule next frame
|
|
33942
|
+
this.animationFrameId = requestAnimationFrame(tick);
|
|
33943
|
+
};
|
|
33944
|
+
// Start the loop
|
|
33945
|
+
tick();
|
|
33350
33946
|
}
|
|
33351
33947
|
stop() {
|
|
33352
|
-
this.
|
|
33948
|
+
this.isRunning = false;
|
|
33949
|
+
// Cancel any pending animation frame
|
|
33950
|
+
if (this.animationFrameId !== null) {
|
|
33951
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
33952
|
+
this.animationFrameId = null;
|
|
33953
|
+
}
|
|
33353
33954
|
}
|
|
33354
33955
|
destroy() {
|
|
33355
|
-
this.
|
|
33956
|
+
this.stop();
|
|
33356
33957
|
}
|
|
33357
33958
|
}
|
|
33358
33959
|
|
|
@@ -33367,6 +33968,10 @@ var __awaiter$4 = (undefined && undefined.__awaiter) || function (thisArg, _argu
|
|
|
33367
33968
|
};
|
|
33368
33969
|
/**
|
|
33369
33970
|
* A Web Audio buffer player emulating the behavior of an HTML5 Audio element.
|
|
33971
|
+
*
|
|
33972
|
+
* Note: This class does not manage blob: URLs. If you pass a blob: URL to setSrc(),
|
|
33973
|
+
* you are responsible for revoking it when done. The Player class (player.ts) handles
|
|
33974
|
+
* blob URL lifecycle management automatically.
|
|
33370
33975
|
*/
|
|
33371
33976
|
class WebAudioPlayer extends EventEmitter {
|
|
33372
33977
|
constructor(audioContext = new AudioContext()) {
|
|
@@ -33427,14 +34032,21 @@ class WebAudioPlayer extends EventEmitter {
|
|
|
33427
34032
|
this.emit('canplay');
|
|
33428
34033
|
if (this.autoplay)
|
|
33429
34034
|
this.play();
|
|
34035
|
+
})
|
|
34036
|
+
.catch((err) => {
|
|
34037
|
+
// Emit error for proper error handling
|
|
34038
|
+
console.error('WebAudioPlayer load error:', err);
|
|
33430
34039
|
});
|
|
33431
34040
|
}
|
|
33432
34041
|
_play() {
|
|
33433
|
-
var _a;
|
|
33434
34042
|
if (!this.paused)
|
|
33435
34043
|
return;
|
|
33436
34044
|
this.paused = false;
|
|
33437
|
-
|
|
34045
|
+
// Clean up old buffer node completely before creating new one
|
|
34046
|
+
if (this.bufferNode) {
|
|
34047
|
+
this.bufferNode.onended = null;
|
|
34048
|
+
this.bufferNode.disconnect();
|
|
34049
|
+
}
|
|
33438
34050
|
this.bufferNode = this.audioContext.createBufferSource();
|
|
33439
34051
|
if (this.buffer) {
|
|
33440
34052
|
this.bufferNode.buffer = this.buffer;
|
|
@@ -33590,6 +34202,239 @@ class WebAudioPlayer extends EventEmitter {
|
|
|
33590
34202
|
}
|
|
33591
34203
|
}
|
|
33592
34204
|
|
|
34205
|
+
/**
|
|
34206
|
+
* Centralized reactive state for WaveSurfer
|
|
34207
|
+
*
|
|
34208
|
+
* This module provides a single source of truth for all WaveSurfer state.
|
|
34209
|
+
* State is managed using reactive signals that automatically notify subscribers.
|
|
34210
|
+
*/
|
|
34211
|
+
/**
|
|
34212
|
+
* Create a new WaveSurfer state instance
|
|
34213
|
+
*
|
|
34214
|
+
* @param playerSignals - Optional signals from Player to compose with WaveSurfer state
|
|
34215
|
+
*
|
|
34216
|
+
* @example
|
|
34217
|
+
* ```typescript
|
|
34218
|
+
* // Without Player signals (standalone)
|
|
34219
|
+
* const { state, actions } = createWaveSurferState()
|
|
34220
|
+
*
|
|
34221
|
+
* // With Player signals (composed)
|
|
34222
|
+
* const { state, actions } = createWaveSurferState({
|
|
34223
|
+
* isPlaying: player.isPlayingSignal,
|
|
34224
|
+
* currentTime: player.currentTimeSignal,
|
|
34225
|
+
* // ...
|
|
34226
|
+
* })
|
|
34227
|
+
*
|
|
34228
|
+
* // Read state
|
|
34229
|
+
* console.log(state.isPlaying.value)
|
|
34230
|
+
*
|
|
34231
|
+
* // Update state
|
|
34232
|
+
* actions.setPlaying(true)
|
|
34233
|
+
*
|
|
34234
|
+
* // Subscribe to changes
|
|
34235
|
+
* state.isPlaying.subscribe(playing => {
|
|
34236
|
+
* console.log('Playing:', playing)
|
|
34237
|
+
* })
|
|
34238
|
+
* ```
|
|
34239
|
+
*/
|
|
34240
|
+
function createWaveSurferState(playerSignals) {
|
|
34241
|
+
var _a, _b, _c, _d, _e, _f;
|
|
34242
|
+
// Use Player signals if provided, otherwise create new ones
|
|
34243
|
+
const currentTime = (_a = playerSignals === null || playerSignals === void 0 ? void 0 : playerSignals.currentTime) !== null && _a !== void 0 ? _a : signal(0);
|
|
34244
|
+
const duration = (_b = playerSignals === null || playerSignals === void 0 ? void 0 : playerSignals.duration) !== null && _b !== void 0 ? _b : signal(0);
|
|
34245
|
+
const isPlaying = (_c = playerSignals === null || playerSignals === void 0 ? void 0 : playerSignals.isPlaying) !== null && _c !== void 0 ? _c : signal(false);
|
|
34246
|
+
const isSeeking = (_d = playerSignals === null || playerSignals === void 0 ? void 0 : playerSignals.isSeeking) !== null && _d !== void 0 ? _d : signal(false);
|
|
34247
|
+
const volume = (_e = playerSignals === null || playerSignals === void 0 ? void 0 : playerSignals.volume) !== null && _e !== void 0 ? _e : signal(1);
|
|
34248
|
+
const playbackRate = (_f = playerSignals === null || playerSignals === void 0 ? void 0 : playerSignals.playbackRate) !== null && _f !== void 0 ? _f : signal(1);
|
|
34249
|
+
// WaveSurfer-specific signals (not in Player)
|
|
34250
|
+
const audioBuffer = signal(null);
|
|
34251
|
+
const peaks = signal(null);
|
|
34252
|
+
const url = signal('');
|
|
34253
|
+
const zoom = signal(0);
|
|
34254
|
+
const scrollPosition = signal(0);
|
|
34255
|
+
// Computed values (derived state)
|
|
34256
|
+
const isPaused = computed(() => !isPlaying.value, [isPlaying]);
|
|
34257
|
+
const canPlay = computed(() => audioBuffer.value !== null, [audioBuffer]);
|
|
34258
|
+
const isReady = computed(() => {
|
|
34259
|
+
return canPlay.value && duration.value > 0;
|
|
34260
|
+
}, [canPlay, duration]);
|
|
34261
|
+
const progress = computed(() => currentTime.value, [currentTime]);
|
|
34262
|
+
const progressPercent = computed(() => {
|
|
34263
|
+
return duration.value > 0 ? currentTime.value / duration.value : 0;
|
|
34264
|
+
}, [currentTime, duration]);
|
|
34265
|
+
// Public read-only state
|
|
34266
|
+
const state = {
|
|
34267
|
+
currentTime,
|
|
34268
|
+
duration,
|
|
34269
|
+
isPlaying,
|
|
34270
|
+
isPaused,
|
|
34271
|
+
isSeeking,
|
|
34272
|
+
volume,
|
|
34273
|
+
playbackRate,
|
|
34274
|
+
audioBuffer,
|
|
34275
|
+
peaks,
|
|
34276
|
+
url,
|
|
34277
|
+
zoom,
|
|
34278
|
+
scrollPosition,
|
|
34279
|
+
canPlay,
|
|
34280
|
+
isReady,
|
|
34281
|
+
progress,
|
|
34282
|
+
progressPercent,
|
|
34283
|
+
};
|
|
34284
|
+
// Actions that modify state
|
|
34285
|
+
const actions = {
|
|
34286
|
+
setCurrentTime: (time) => {
|
|
34287
|
+
const clampedTime = Math.max(0, Math.min(duration.value || Infinity, time));
|
|
34288
|
+
currentTime.set(clampedTime);
|
|
34289
|
+
},
|
|
34290
|
+
setDuration: (d) => {
|
|
34291
|
+
duration.set(Math.max(0, d));
|
|
34292
|
+
},
|
|
34293
|
+
setPlaying: (playing) => {
|
|
34294
|
+
isPlaying.set(playing);
|
|
34295
|
+
},
|
|
34296
|
+
setSeeking: (seeking) => {
|
|
34297
|
+
isSeeking.set(seeking);
|
|
34298
|
+
},
|
|
34299
|
+
setVolume: (v) => {
|
|
34300
|
+
const clampedVolume = Math.max(0, Math.min(1, v));
|
|
34301
|
+
volume.set(clampedVolume);
|
|
34302
|
+
},
|
|
34303
|
+
setPlaybackRate: (rate) => {
|
|
34304
|
+
const clampedRate = Math.max(0.1, Math.min(16, rate));
|
|
34305
|
+
playbackRate.set(clampedRate);
|
|
34306
|
+
},
|
|
34307
|
+
setAudioBuffer: (buffer) => {
|
|
34308
|
+
audioBuffer.set(buffer);
|
|
34309
|
+
if (buffer) {
|
|
34310
|
+
duration.set(buffer.duration);
|
|
34311
|
+
}
|
|
34312
|
+
},
|
|
34313
|
+
setPeaks: (p) => {
|
|
34314
|
+
peaks.set(p);
|
|
34315
|
+
},
|
|
34316
|
+
setUrl: (u) => {
|
|
34317
|
+
url.set(u);
|
|
34318
|
+
},
|
|
34319
|
+
setZoom: (z) => {
|
|
34320
|
+
zoom.set(Math.max(0, z));
|
|
34321
|
+
},
|
|
34322
|
+
setScrollPosition: (pos) => {
|
|
34323
|
+
scrollPosition.set(Math.max(0, pos));
|
|
34324
|
+
},
|
|
34325
|
+
};
|
|
34326
|
+
return { state, actions };
|
|
34327
|
+
}
|
|
34328
|
+
|
|
34329
|
+
/**
|
|
34330
|
+
* State-driven event emission utilities
|
|
34331
|
+
*
|
|
34332
|
+
* Automatically emit events when reactive state changes.
|
|
34333
|
+
* Ensures events are always in sync with state and removes manual emit() calls.
|
|
34334
|
+
*/
|
|
34335
|
+
/**
|
|
34336
|
+
* Setup automatic event emission from state changes
|
|
34337
|
+
*
|
|
34338
|
+
* This function subscribes to all relevant state signals and automatically
|
|
34339
|
+
* emits corresponding events when state changes. This ensures:
|
|
34340
|
+
* - Events are always in sync with state
|
|
34341
|
+
* - No manual emit() calls needed
|
|
34342
|
+
* - Can't forget to emit an event
|
|
34343
|
+
* - Clear event sources (state changes)
|
|
34344
|
+
*
|
|
34345
|
+
* @example
|
|
34346
|
+
* ```typescript
|
|
34347
|
+
* const { state } = createWaveSurferState()
|
|
34348
|
+
* const wavesurfer = new WaveSurfer()
|
|
34349
|
+
*
|
|
34350
|
+
* const cleanup = setupStateEventEmission(state, wavesurfer)
|
|
34351
|
+
*
|
|
34352
|
+
* // Now state changes automatically emit events
|
|
34353
|
+
* state.isPlaying.set(true) // → wavesurfer.emit('play')
|
|
34354
|
+
* ```
|
|
34355
|
+
*
|
|
34356
|
+
* @param state - Reactive state to observe
|
|
34357
|
+
* @param emitter - Event emitter to emit events on
|
|
34358
|
+
* @returns Cleanup function that removes all subscriptions
|
|
34359
|
+
*/
|
|
34360
|
+
function setupStateEventEmission(state, emitter) {
|
|
34361
|
+
const cleanups = [];
|
|
34362
|
+
// ============================================================================
|
|
34363
|
+
// Play/Pause Events
|
|
34364
|
+
// ============================================================================
|
|
34365
|
+
// Emit play/pause events when playing state changes
|
|
34366
|
+
cleanups.push(effect(() => {
|
|
34367
|
+
const isPlaying = state.isPlaying.value;
|
|
34368
|
+
emitter.emit(isPlaying ? 'play' : 'pause');
|
|
34369
|
+
}, [state.isPlaying]));
|
|
34370
|
+
// ============================================================================
|
|
34371
|
+
// Time Update Events
|
|
34372
|
+
// ============================================================================
|
|
34373
|
+
// Emit timeupdate when current time changes
|
|
34374
|
+
cleanups.push(effect(() => {
|
|
34375
|
+
const currentTime = state.currentTime.value;
|
|
34376
|
+
emitter.emit('timeupdate', currentTime);
|
|
34377
|
+
// Also emit audioprocess when playing
|
|
34378
|
+
if (state.isPlaying.value) {
|
|
34379
|
+
emitter.emit('audioprocess', currentTime);
|
|
34380
|
+
}
|
|
34381
|
+
}, [state.currentTime, state.isPlaying]));
|
|
34382
|
+
// ============================================================================
|
|
34383
|
+
// Seeking Events
|
|
34384
|
+
// ============================================================================
|
|
34385
|
+
// Emit seeking event when seeking state changes to true
|
|
34386
|
+
cleanups.push(effect(() => {
|
|
34387
|
+
const isSeeking = state.isSeeking.value;
|
|
34388
|
+
if (isSeeking) {
|
|
34389
|
+
emitter.emit('seeking', state.currentTime.value);
|
|
34390
|
+
}
|
|
34391
|
+
}, [state.isSeeking, state.currentTime]));
|
|
34392
|
+
// ============================================================================
|
|
34393
|
+
// Ready Event
|
|
34394
|
+
// ============================================================================
|
|
34395
|
+
// Emit ready when state becomes ready
|
|
34396
|
+
let wasReady = false;
|
|
34397
|
+
cleanups.push(effect(() => {
|
|
34398
|
+
const isReady = state.isReady.value;
|
|
34399
|
+
if (isReady && !wasReady) {
|
|
34400
|
+
wasReady = true;
|
|
34401
|
+
emitter.emit('ready', state.duration.value);
|
|
34402
|
+
}
|
|
34403
|
+
}, [state.isReady, state.duration]));
|
|
34404
|
+
// ============================================================================
|
|
34405
|
+
// Finish Event
|
|
34406
|
+
// ============================================================================
|
|
34407
|
+
// Emit finish when playback ends (reached duration and stopped)
|
|
34408
|
+
let wasPlayingAtEnd = false;
|
|
34409
|
+
cleanups.push(effect(() => {
|
|
34410
|
+
const isPlaying = state.isPlaying.value;
|
|
34411
|
+
const currentTime = state.currentTime.value;
|
|
34412
|
+
const duration = state.duration.value;
|
|
34413
|
+
// Check if we're at the end
|
|
34414
|
+
const isAtEnd = duration > 0 && currentTime >= duration;
|
|
34415
|
+
// Emit finish when we were playing at end and now stopped
|
|
34416
|
+
if (wasPlayingAtEnd && !isPlaying && isAtEnd) {
|
|
34417
|
+
emitter.emit('finish');
|
|
34418
|
+
}
|
|
34419
|
+
// Track if we're playing at the end
|
|
34420
|
+
wasPlayingAtEnd = isPlaying && isAtEnd;
|
|
34421
|
+
}, [state.isPlaying, state.currentTime, state.duration]));
|
|
34422
|
+
// ============================================================================
|
|
34423
|
+
// Zoom Events
|
|
34424
|
+
// ============================================================================
|
|
34425
|
+
// Emit zoom when zoom level changes
|
|
34426
|
+
cleanups.push(effect(() => {
|
|
34427
|
+
const zoom = state.zoom.value;
|
|
34428
|
+
if (zoom > 0) {
|
|
34429
|
+
emitter.emit('zoom', zoom);
|
|
34430
|
+
}
|
|
34431
|
+
}, [state.zoom]));
|
|
34432
|
+
// Return cleanup function
|
|
34433
|
+
return () => {
|
|
34434
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
34435
|
+
};
|
|
34436
|
+
}
|
|
34437
|
+
|
|
33593
34438
|
var __awaiter$5 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
33594
34439
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
33595
34440
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -33616,6 +34461,14 @@ class WaveSurfer extends Player {
|
|
|
33616
34461
|
static create(options) {
|
|
33617
34462
|
return new WaveSurfer(options);
|
|
33618
34463
|
}
|
|
34464
|
+
/** Get the reactive state for advanced use cases */
|
|
34465
|
+
getState() {
|
|
34466
|
+
return this.wavesurferState;
|
|
34467
|
+
}
|
|
34468
|
+
/** Get the renderer instance for plugin access to reactive streams */
|
|
34469
|
+
getRenderer() {
|
|
34470
|
+
return this.renderer;
|
|
34471
|
+
}
|
|
33619
34472
|
/** Create a new WaveSurfer instance */
|
|
33620
34473
|
constructor(options) {
|
|
33621
34474
|
const media = options.media ||
|
|
@@ -33632,13 +34485,27 @@ class WaveSurfer extends Player {
|
|
|
33632
34485
|
this.subscriptions = [];
|
|
33633
34486
|
this.mediaSubscriptions = [];
|
|
33634
34487
|
this.abortController = null;
|
|
34488
|
+
this.reactiveCleanups = [];
|
|
33635
34489
|
this.options = Object.assign({}, defaultOptions, options);
|
|
34490
|
+
// Initialize reactive state
|
|
34491
|
+
// Pass Player signals to compose them into WaveSurferState
|
|
34492
|
+
const { state, actions } = createWaveSurferState({
|
|
34493
|
+
isPlaying: this.isPlayingSignal,
|
|
34494
|
+
currentTime: this.currentTimeSignal,
|
|
34495
|
+
duration: this.durationSignal,
|
|
34496
|
+
volume: this.volumeSignal,
|
|
34497
|
+
playbackRate: this.playbackRateSignal,
|
|
34498
|
+
isSeeking: this.seekingSignal,
|
|
34499
|
+
});
|
|
34500
|
+
this.wavesurferState = state;
|
|
34501
|
+
this.wavesurferActions = actions;
|
|
33636
34502
|
this.timer = new Timer();
|
|
33637
34503
|
const audioElement = media ? undefined : this.getMediaElement();
|
|
33638
34504
|
this.renderer = new Renderer(this.options, audioElement);
|
|
33639
34505
|
this.initPlayerEvents();
|
|
33640
34506
|
this.initRendererEvents();
|
|
33641
34507
|
this.initTimerEvents();
|
|
34508
|
+
this.initReactiveState();
|
|
33642
34509
|
this.initPlugins();
|
|
33643
34510
|
// Read the initial URL before load has been called
|
|
33644
34511
|
const initialUrl = this.options.url || this.getSrc() || '';
|
|
@@ -33651,7 +34518,10 @@ class WaveSurfer extends Player {
|
|
|
33651
34518
|
if (initialUrl || (peaks && duration)) {
|
|
33652
34519
|
// Swallow async errors because they cannot be caught from a constructor call.
|
|
33653
34520
|
// Subscribe to the wavesurfer's error event to handle them.
|
|
33654
|
-
this.load(initialUrl, peaks, duration).catch(() =>
|
|
34521
|
+
this.load(initialUrl, peaks, duration).catch((err) => {
|
|
34522
|
+
// Emit error event for proper error handling
|
|
34523
|
+
this.emit('error', err instanceof Error ? err : new Error(String(err)));
|
|
34524
|
+
});
|
|
33655
34525
|
}
|
|
33656
34526
|
});
|
|
33657
34527
|
}
|
|
@@ -33673,6 +34543,12 @@ class WaveSurfer extends Player {
|
|
|
33673
34543
|
}
|
|
33674
34544
|
}));
|
|
33675
34545
|
}
|
|
34546
|
+
initReactiveState() {
|
|
34547
|
+
// Bridge reactive state to EventEmitter for backwards compatibility
|
|
34548
|
+
this.reactiveCleanups.push(setupStateEventEmission(this.wavesurferState, {
|
|
34549
|
+
emit: this.emit.bind(this),
|
|
34550
|
+
}));
|
|
34551
|
+
}
|
|
33676
34552
|
initPlayerEvents() {
|
|
33677
34553
|
if (this.isPlaying()) {
|
|
33678
34554
|
this.emit('play');
|
|
@@ -33737,33 +34613,44 @@ class WaveSurfer extends Player {
|
|
|
33737
34613
|
// DragEnd
|
|
33738
34614
|
this.renderer.on('dragend', (relativeX) => {
|
|
33739
34615
|
this.emit('dragend', relativeX);
|
|
34616
|
+
}),
|
|
34617
|
+
// Resize
|
|
34618
|
+
this.renderer.on('resize', () => {
|
|
34619
|
+
this.emit('resize');
|
|
33740
34620
|
}));
|
|
33741
34621
|
// Drag
|
|
33742
34622
|
{
|
|
33743
34623
|
let debounce;
|
|
33744
|
-
this.
|
|
34624
|
+
const unsubscribeDrag = this.renderer.on('drag', (relativeX) => {
|
|
34625
|
+
var _a;
|
|
33745
34626
|
if (!this.options.interact)
|
|
33746
34627
|
return;
|
|
33747
34628
|
// Update the visual position
|
|
33748
34629
|
this.renderer.renderProgress(relativeX);
|
|
33749
34630
|
// Set the audio position with a debounce
|
|
33750
34631
|
clearTimeout(debounce);
|
|
33751
|
-
let debounceTime;
|
|
34632
|
+
let debounceTime = 0;
|
|
34633
|
+
const dragToSeek = this.options.dragToSeek;
|
|
33752
34634
|
if (this.isPlaying()) {
|
|
33753
34635
|
debounceTime = 0;
|
|
33754
34636
|
}
|
|
33755
|
-
else if (
|
|
34637
|
+
else if (dragToSeek === true) {
|
|
33756
34638
|
debounceTime = 200;
|
|
33757
34639
|
}
|
|
33758
|
-
else if (typeof
|
|
33759
|
-
debounceTime =
|
|
34640
|
+
else if (dragToSeek && typeof dragToSeek === 'object') {
|
|
34641
|
+
debounceTime = (_a = dragToSeek.debounceTime) !== null && _a !== void 0 ? _a : 200;
|
|
33760
34642
|
}
|
|
33761
34643
|
debounce = setTimeout(() => {
|
|
33762
34644
|
this.seekTo(relativeX);
|
|
33763
34645
|
}, debounceTime);
|
|
33764
34646
|
this.emit('interaction', relativeX * this.getDuration());
|
|
33765
34647
|
this.emit('drag', relativeX);
|
|
33766
|
-
})
|
|
34648
|
+
});
|
|
34649
|
+
// Clear debounce timeout on destroy
|
|
34650
|
+
this.subscriptions.push(() => {
|
|
34651
|
+
clearTimeout(debounce);
|
|
34652
|
+
unsubscribeDrag();
|
|
34653
|
+
});
|
|
33767
34654
|
}
|
|
33768
34655
|
}
|
|
33769
34656
|
initPlugins() {
|
|
@@ -33850,12 +34737,15 @@ class WaveSurfer extends Player {
|
|
|
33850
34737
|
this.pause();
|
|
33851
34738
|
this.decodedData = null;
|
|
33852
34739
|
this.stopAtPosition = null;
|
|
34740
|
+
// Abort any ongoing fetch before starting a new one
|
|
34741
|
+
(_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();
|
|
34742
|
+
this.abortController = null;
|
|
33853
34743
|
// Fetch the entire audio as a blob if pre-decoded data is not provided
|
|
33854
34744
|
if (!blob && !channelData) {
|
|
33855
34745
|
const fetchParams = this.options.fetchParams || {};
|
|
33856
34746
|
if (window.AbortController && !fetchParams.signal) {
|
|
33857
34747
|
this.abortController = new AbortController();
|
|
33858
|
-
fetchParams.signal =
|
|
34748
|
+
fetchParams.signal = this.abortController.signal;
|
|
33859
34749
|
}
|
|
33860
34750
|
const onProgress = (percentage) => this.emit('loading', percentage);
|
|
33861
34751
|
blob = yield Fetcher.fetchBlob(url, onProgress, fetchParams);
|
|
@@ -33945,8 +34835,8 @@ class WaveSurfer extends Player {
|
|
|
33945
34835
|
const channel = this.decodedData.getChannelData(i);
|
|
33946
34836
|
const data = [];
|
|
33947
34837
|
const sampleSize = channel.length / maxLength;
|
|
33948
|
-
for (let
|
|
33949
|
-
const sample = channel.slice(Math.floor(
|
|
34838
|
+
for (let j = 0; j < maxLength; j++) {
|
|
34839
|
+
const sample = channel.slice(Math.floor(j * sampleSize), Math.ceil((j + 1) * sampleSize));
|
|
33950
34840
|
let max = 0;
|
|
33951
34841
|
for (let x = 0; x < sample.length; x++) {
|
|
33952
34842
|
const n = sample[x];
|
|
@@ -33979,7 +34869,7 @@ class WaveSurfer extends Player {
|
|
|
33979
34869
|
this.updateProgress(time);
|
|
33980
34870
|
this.emit('timeupdate', time);
|
|
33981
34871
|
}
|
|
33982
|
-
/** Seek to a
|
|
34872
|
+
/** Seek to a ratio of audio as [0..1] (0 = beginning, 1 = end) */
|
|
33983
34873
|
seekTo(progress) {
|
|
33984
34874
|
const time = this.getDuration() * progress;
|
|
33985
34875
|
this.setTime(time);
|
|
@@ -34043,6 +34933,8 @@ class WaveSurfer extends Player {
|
|
|
34043
34933
|
this.plugins.forEach((plugin) => plugin.destroy());
|
|
34044
34934
|
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
34045
34935
|
this.unsubscribePlayerEvents();
|
|
34936
|
+
this.reactiveCleanups.forEach((cleanup) => cleanup());
|
|
34937
|
+
this.reactiveCleanups = [];
|
|
34046
34938
|
this.timer.destroy();
|
|
34047
34939
|
this.renderer.destroy();
|
|
34048
34940
|
super.destroy();
|
|
@@ -35260,7 +36152,11 @@ var Attachment = function Attachment(_ref) {
|
|
|
35260
36152
|
channelId: channelId,
|
|
35261
36153
|
incoming: incoming,
|
|
35262
36154
|
viewOnce: viewOnce,
|
|
35263
|
-
setViewOnceVoiceModalOpen: viewOnce ?
|
|
36155
|
+
setViewOnceVoiceModalOpen: viewOnce ? function () {
|
|
36156
|
+
if (connectionStatus === CONNECTION_STATUS.CONNECTED) {
|
|
36157
|
+
setViewOnceVoiceModalOpen(true);
|
|
36158
|
+
}
|
|
36159
|
+
} : undefined
|
|
35264
36160
|
})) : attachment.type === attachmentTypes.link ? null : (
|
|
35265
36161
|
/*#__PURE__*/
|
|
35266
36162
|
React__default.createElement(AttachmentFile$1, {
|