sceyt-chat-react-uikit 1.8.0-beta.6 → 1.8.0-beta.8

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 (3) hide show
  1. package/index.js +566 -1239
  2. package/index.modern.js +566 -1239
  3. package/package.json +3 -1
package/index.js CHANGED
@@ -15,6 +15,8 @@ var LinkifyIt = _interopDefault(require('linkify-it'));
15
15
  var Cropper = _interopDefault(require('react-easy-crop'));
16
16
  var Carousel = _interopDefault(require('react-elastic-carousel'));
17
17
  var reactCircularProgressbar = require('react-circular-progressbar');
18
+ var ffmpeg = require('@ffmpeg/ffmpeg');
19
+ var util = require('@ffmpeg/util');
18
20
  var lexical = require('lexical');
19
21
  var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
20
22
  var LexicalComposer = require('@lexical/react/LexicalComposer');
@@ -32188,16 +32190,15 @@ class EventEmitter {
32188
32190
  if (!this.listeners[event]) {
32189
32191
  this.listeners[event] = new Set();
32190
32192
  }
32193
+ this.listeners[event].add(listener);
32191
32194
  if (options === null || options === void 0 ? void 0 : options.once) {
32192
- // Create a wrapper that removes itself after being called once
32193
- const onceWrapper = (...args) => {
32194
- this.un(event, onceWrapper);
32195
- listener(...args);
32195
+ const unsubscribeOnce = () => {
32196
+ this.un(event, unsubscribeOnce);
32197
+ this.un(event, listener);
32196
32198
  };
32197
- this.listeners[event].add(onceWrapper);
32198
- return () => this.un(event, onceWrapper);
32199
+ this.on(event, unsubscribeOnce);
32200
+ return unsubscribeOnce;
32199
32201
  }
32200
- this.listeners[event].add(listener);
32201
32202
  return () => this.un(event, listener);
32202
32203
  }
32203
32204
  /** Unsubscribe from an event */
@@ -32267,13 +32268,8 @@ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _argume
32267
32268
  function decode(audioData, sampleRate) {
32268
32269
  return __awaiter(this, void 0, void 0, function* () {
32269
32270
  const audioCtx = new AudioContext({ sampleRate });
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
+ const decode = audioCtx.decodeAudioData(audioData);
32272
+ return decode.finally(() => audioCtx.close());
32277
32273
  });
32278
32274
  }
32279
32275
  /** Normalize peaks to -1..1 */
@@ -32297,36 +32293,17 @@ function normalize(channelData) {
32297
32293
  }
32298
32294
  /** Create an audio buffer from pre-decoded audio data */
32299
32295
  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
- }
32307
32296
  // If a single array of numbers is passed, make it an array of arrays
32308
32297
  if (typeof channelData[0] === 'number')
32309
32298
  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
- }
32314
32299
  // Normalize to -1..1
32315
32300
  normalize(channelData);
32316
- // Convert to Float32Array for consistency
32317
- const float32Channels = channelData.map((channel) => channel instanceof Float32Array ? channel : Float32Array.from(channel));
32318
32301
  return {
32319
32302
  duration,
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
- },
32303
+ length: channelData[0].length,
32304
+ sampleRate: channelData[0].length / duration,
32305
+ numberOfChannels: channelData.length,
32306
+ getChannelData: (i) => channelData === null || channelData === void 0 ? void 0 : channelData[i],
32330
32307
  copyFromChannel: AudioBuffer.prototype.copyFromChannel,
32331
32308
  copyToChannel: AudioBuffer.prototype.copyToChannel,
32332
32309
  };
@@ -32395,26 +32372,28 @@ function watchProgress(response, progressCallback) {
32395
32372
  const contentLength = Number(response.headers.get('Content-Length')) || 0;
32396
32373
  let receivedLength = 0;
32397
32374
  // Process the data
32398
- const processChunk = (value) => {
32375
+ const processChunk = (value) => __awaiter$1(this, void 0, void 0, function* () {
32399
32376
  // Add to the received length
32400
32377
  receivedLength += (value === null || value === void 0 ? void 0 : value.length) || 0;
32401
32378
  const percentage = Math.round((receivedLength / contentLength) * 100);
32402
32379
  progressCallback(percentage);
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
- }
32380
+ });
32381
+ const read = () => __awaiter$1(this, void 0, void 0, function* () {
32382
+ let data;
32383
+ try {
32384
+ data = yield reader.read();
32385
+ }
32386
+ catch (_a) {
32387
+ // Ignore errors because we can only handle the main response
32388
+ return;
32389
+ }
32390
+ // Continue reading data until done
32391
+ if (!data.done) {
32411
32392
  processChunk(data.value);
32393
+ yield read();
32412
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
+ });
32396
+ read();
32418
32397
  });
32419
32398
  }
32420
32399
  function fetchBlob(url, progressCallback, requestInit) {
@@ -32433,121 +32412,6 @@ const Fetcher = {
32433
32412
  fetchBlob,
32434
32413
  };
32435
32414
 
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
-
32551
32415
  var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
32552
32416
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
32553
32417
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -32558,33 +32422,9 @@ var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _argu
32558
32422
  });
32559
32423
  };
32560
32424
  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
- }
32584
32425
  constructor(options) {
32585
32426
  super();
32586
32427
  this.isExternalMedia = false;
32587
- this.reactiveMediaEventCleanups = [];
32588
32428
  if (options.media) {
32589
32429
  this.media = options.media;
32590
32430
  this.isExternalMedia = true;
@@ -32592,16 +32432,6 @@ class Player extends EventEmitter {
32592
32432
  else {
32593
32433
  this.media = document.createElement('audio');
32594
32434
  }
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();
32605
32435
  // Controls
32606
32436
  if (options.mediaControls) {
32607
32437
  this.media.controls = true;
@@ -32619,48 +32449,6 @@ class Player extends EventEmitter {
32619
32449
  }, { once: true });
32620
32450
  }
32621
32451
  }
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
- }
32664
32452
  onMediaEvent(event, callback, options) {
32665
32453
  this.media.addEventListener(event, callback, options);
32666
32454
  return () => this.media.removeEventListener(event, callback, options);
@@ -32697,27 +32485,17 @@ class Player extends EventEmitter {
32697
32485
  }
32698
32486
  }
32699
32487
  destroy() {
32700
- // Cleanup reactive media event listeners
32701
- this.reactiveMediaEventCleanups.forEach((cleanup) => cleanup());
32702
- this.reactiveMediaEventCleanups = [];
32703
32488
  if (this.isExternalMedia)
32704
32489
  return;
32705
32490
  this.media.pause();
32491
+ this.media.remove();
32706
32492
  this.revokeSrc();
32707
32493
  this.media.removeAttribute('src');
32708
32494
  // Load resets the media element to its initial state
32709
32495
  this.media.load();
32710
- // Remove from DOM after cleanup
32711
- this.media.remove();
32712
32496
  }
32713
32497
  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
32718
32498
  this.media = element;
32719
- // Reinitialize reactive event listeners on new media element
32720
- this.setupReactiveMediaEvents();
32721
32499
  }
32722
32500
  /** Start playing the audio */
32723
32501
  play() {
@@ -32797,312 +32575,23 @@ class Player extends EventEmitter {
32797
32575
  }
32798
32576
  }
32799
32577
 
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();
32578
+ function makeDraggable(element, onDrag, onStart, onEnd, threshold = 3, mouseButton = 0, touchDelay = 100) {
32579
+ if (!element)
32580
+ return () => void 0;
33087
32581
  const isTouchDevice = matchMedia('(pointer: coarse)').matches;
33088
32582
  let unsubscribeDocument = () => void 0;
33089
32583
  const onPointerDown = (event) => {
33090
32584
  if (event.button !== mouseButton)
33091
32585
  return;
33092
- activePointers.set(event.pointerId, event);
33093
- if (activePointers.size > 1) {
33094
- return;
33095
- }
32586
+ event.preventDefault();
32587
+ event.stopPropagation();
33096
32588
  let startX = event.clientX;
33097
32589
  let startY = event.clientY;
33098
32590
  let isDragging = false;
33099
32591
  const touchStartTime = Date.now();
33100
- const rect = element.getBoundingClientRect();
33101
- const { left, top } = rect;
33102
32592
  const onPointerMove = (event) => {
33103
- if (event.defaultPrevented || activePointers.size > 1) {
33104
- return;
33105
- }
32593
+ event.preventDefault();
32594
+ event.stopPropagation();
33106
32595
  if (isTouchDevice && Date.now() - touchStartTime < touchDelay)
33107
32596
  return;
33108
32597
  const x = event.clientX;
@@ -33110,45 +32599,29 @@ function createDragStream(element, options = {}) {
33110
32599
  const dx = x - startX;
33111
32600
  const dy = y - startY;
33112
32601
  if (isDragging || Math.abs(dx) > threshold || Math.abs(dy) > threshold) {
33113
- event.preventDefault();
33114
- event.stopPropagation();
32602
+ const rect = element.getBoundingClientRect();
32603
+ const { left, top } = rect;
33115
32604
  if (!isDragging) {
33116
- // Emit start event
33117
- dragSignal.set({
33118
- type: 'start',
33119
- x: startX - left,
33120
- y: startY - top,
33121
- });
32605
+ onStart === null || onStart === void 0 ? void 0 : onStart(startX - left, startY - top);
33122
32606
  isDragging = true;
33123
32607
  }
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
- });
32608
+ onDrag(dx, dy, x - left, y - top);
33132
32609
  startX = x;
33133
32610
  startY = y;
33134
32611
  }
33135
32612
  };
33136
32613
  const onPointerUp = (event) => {
33137
- activePointers.delete(event.pointerId);
33138
32614
  if (isDragging) {
33139
32615
  const x = event.clientX;
33140
32616
  const y = event.clientY;
33141
- // Emit end event
33142
- dragSignal.set({
33143
- type: 'end',
33144
- x: x - left,
33145
- y: y - top,
33146
- });
32617
+ const rect = element.getBoundingClientRect();
32618
+ const { left, top } = rect;
32619
+ onEnd === null || onEnd === void 0 ? void 0 : onEnd(x - left, y - top);
33147
32620
  }
33148
32621
  unsubscribeDocument();
33149
32622
  };
33150
32623
  const onPointerLeave = (e) => {
33151
- activePointers.delete(e.pointerId);
32624
+ // Listen to events only on the document and not on inner elements
33152
32625
  if (!e.relatedTarget || e.relatedTarget === document.documentElement) {
33153
32626
  onPointerUp(e);
33154
32627
  }
@@ -33160,9 +32633,6 @@ function createDragStream(element, options = {}) {
33160
32633
  }
33161
32634
  };
33162
32635
  const onTouchMove = (event) => {
33163
- if (event.defaultPrevented || activePointers.size > 1) {
33164
- return;
33165
- }
33166
32636
  if (isDragging) {
33167
32637
  event.preventDefault();
33168
32638
  }
@@ -33185,114 +32655,9 @@ function createDragStream(element, options = {}) {
33185
32655
  };
33186
32656
  };
33187
32657
  element.addEventListener('pointerdown', onPointerDown);
33188
- const cleanupFn = () => {
32658
+ return () => {
33189
32659
  unsubscribeDocument();
33190
32660
  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,
33296
32661
  };
33297
32662
  }
33298
32663
 
@@ -33327,8 +32692,6 @@ class Renderer extends EventEmitter {
33327
32692
  this.isDragging = false;
33328
32693
  this.subscriptions = [];
33329
32694
  this.unsubscribeOnScroll = [];
33330
- this.dragStream = null;
33331
- this.scrollStream = null;
33332
32695
  this.subscriptions = [];
33333
32696
  this.options = options;
33334
32697
  const parent = this.parentFromOptionsContainer(options.container);
@@ -33360,30 +32723,35 @@ class Renderer extends EventEmitter {
33360
32723
  return parent;
33361
32724
  }
33362
32725
  initEvents() {
32726
+ const getClickPosition = (e) => {
32727
+ const rect = this.wrapper.getBoundingClientRect();
32728
+ const x = e.clientX - rect.left;
32729
+ const y = e.clientY - rect.top;
32730
+ const relativeX = x / rect.width;
32731
+ const relativeY = y / rect.height;
32732
+ return [relativeX, relativeY];
32733
+ };
33363
32734
  // Add a click listener
33364
32735
  this.wrapper.addEventListener('click', (e) => {
33365
- const rect = this.wrapper.getBoundingClientRect();
33366
- const [x, y] = getRelativePointerPosition(rect, e.clientX, e.clientY);
32736
+ const [x, y] = getClickPosition(e);
33367
32737
  this.emit('click', x, y);
33368
32738
  });
33369
32739
  // Add a double click listener
33370
32740
  this.wrapper.addEventListener('dblclick', (e) => {
33371
- const rect = this.wrapper.getBoundingClientRect();
33372
- const [x, y] = getRelativePointerPosition(rect, e.clientX, e.clientY);
32741
+ const [x, y] = getClickPosition(e);
33373
32742
  this.emit('dblclick', x, y);
33374
32743
  });
33375
32744
  // Drag
33376
32745
  if (this.options.dragToSeek === true || typeof this.options.dragToSeek === 'object') {
33377
32746
  this.initDrag();
33378
32747
  }
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);
32748
+ // Add a scroll listener
32749
+ this.scrollContainer.addEventListener('scroll', () => {
32750
+ const { scrollLeft, scrollWidth, clientWidth } = this.scrollContainer;
32751
+ const startX = scrollLeft / scrollWidth;
32752
+ const endX = (scrollLeft + clientWidth) / scrollWidth;
32753
+ this.emit('scroll', startX, endX, scrollLeft, scrollLeft + clientWidth);
32754
+ });
33387
32755
  // Re-render the waveform on container resize
33388
32756
  if (typeof ResizeObserver === 'function') {
33389
32757
  const delay = this.createDelay(100);
@@ -33401,32 +32769,39 @@ class Renderer extends EventEmitter {
33401
32769
  return;
33402
32770
  this.lastContainerWidth = width;
33403
32771
  this.reRender();
33404
- this.emit('resize');
33405
32772
  }
33406
32773
  initDrag() {
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);
32774
+ this.subscriptions.push(makeDraggable(this.wrapper,
32775
+ // On drag
32776
+ (_, __, x) => {
32777
+ this.emit('drag', Math.max(0, Math.min(1, x / this.wrapper.getBoundingClientRect().width)));
32778
+ },
32779
+ // On start drag
32780
+ (x) => {
32781
+ this.isDragging = true;
32782
+ this.emit('dragstart', Math.max(0, Math.min(1, x / this.wrapper.getBoundingClientRect().width)));
32783
+ },
32784
+ // On end drag
32785
+ (x) => {
32786
+ this.isDragging = false;
32787
+ this.emit('dragend', Math.max(0, Math.min(1, x / this.wrapper.getBoundingClientRect().width)));
32788
+ }));
32789
+ }
32790
+ getHeight(optionsHeight, optionsSplitChannel) {
32791
+ var _a;
32792
+ const defaultHeight = 128;
32793
+ const numberOfChannels = ((_a = this.audioData) === null || _a === void 0 ? void 0 : _a.numberOfChannels) || 1;
32794
+ if (optionsHeight == null)
32795
+ return defaultHeight;
32796
+ if (!isNaN(Number(optionsHeight)))
32797
+ return Number(optionsHeight);
32798
+ if (optionsHeight === 'auto') {
32799
+ const height = this.parent.clientHeight || defaultHeight;
32800
+ if (optionsSplitChannel === null || optionsSplitChannel === void 0 ? void 0 : optionsSplitChannel.every((channel) => !channel.overlay))
32801
+ return height / numberOfChannels;
32802
+ return height;
32803
+ }
32804
+ return defaultHeight;
33430
32805
  }
33431
32806
  initHtml() {
33432
32807
  const div = document.createElement('div');
@@ -33463,7 +32838,6 @@ class Renderer extends EventEmitter {
33463
32838
  }
33464
32839
  :host .canvases {
33465
32840
  min-height: ${this.getHeight(this.options.height, this.options.splitChannels)}px;
33466
- pointer-events: none;
33467
32841
  }
33468
32842
  :host .canvases > div {
33469
32843
  position: relative;
@@ -33540,134 +32914,150 @@ class Renderer extends EventEmitter {
33540
32914
  this.setScroll(scrollStart);
33541
32915
  }
33542
32916
  destroy() {
33543
- var _a;
32917
+ var _a, _b;
33544
32918
  this.subscriptions.forEach((unsubscribe) => unsubscribe());
33545
32919
  this.container.remove();
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
+ (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
32921
+ (_b = this.unsubscribeOnScroll) === null || _b === void 0 ? void 0 : _b.forEach((unsubscribe) => unsubscribe());
33551
32922
  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
- }
33560
32923
  }
33561
32924
  createDelay(delayMs = 10) {
33562
32925
  let timeout;
33563
- let rejectFn;
32926
+ let reject;
33564
32927
  const onClear = () => {
33565
- if (timeout) {
32928
+ if (timeout)
33566
32929
  clearTimeout(timeout);
33567
- timeout = undefined;
33568
- }
33569
- if (rejectFn) {
33570
- rejectFn();
33571
- rejectFn = undefined;
33572
- }
32930
+ if (reject)
32931
+ reject();
33573
32932
  };
33574
32933
  this.timeouts.push(onClear);
33575
32934
  return () => {
33576
- return new Promise((resolve, reject) => {
33577
- // Clear any pending delay
32935
+ return new Promise((resolveFn, rejectFn) => {
33578
32936
  onClear();
33579
- // Store reject function for cleanup
33580
- rejectFn = reject;
33581
- // Set new timeout
32937
+ reject = rejectFn;
33582
32938
  timeout = setTimeout(() => {
33583
32939
  timeout = undefined;
33584
- rejectFn = undefined;
33585
- resolve();
32940
+ reject = undefined;
32941
+ resolveFn();
33586
32942
  }, delayMs);
33587
32943
  });
33588
32944
  };
33589
32945
  }
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,
32946
+ // Convert array of color values to linear gradient
32947
+ convertColorValues(color) {
32948
+ if (!Array.isArray(color))
32949
+ return color || '';
32950
+ if (color.length < 2)
32951
+ return color[0] || '';
32952
+ const canvasElement = document.createElement('canvas');
32953
+ const ctx = canvasElement.getContext('2d');
32954
+ const gradientHeight = canvasElement.height * (window.devicePixelRatio || 1);
32955
+ const gradient = ctx.createLinearGradient(0, 0, 0, gradientHeight);
32956
+ const colorStopPercentage = 1 / (color.length - 1);
32957
+ color.forEach((color, index) => {
32958
+ const offset = index * colorStopPercentage;
32959
+ gradient.addColorStop(offset, color);
33599
32960
  });
33600
- }
33601
- convertColorValues(color, ctx) {
33602
- return resolveColorValue(color, this.getPixelRatio(), ctx === null || ctx === void 0 ? void 0 : ctx.canvas.height);
32961
+ return gradient;
33603
32962
  }
33604
32963
  getPixelRatio() {
33605
- return getPixelRatio(window.devicePixelRatio);
32964
+ return Math.max(1, window.devicePixelRatio || 1);
33606
32965
  }
33607
32966
  renderBarWaveform(channelData, options, ctx, vScale) {
32967
+ const topChannel = channelData[0];
32968
+ const bottomChannel = channelData[1] || channelData[0];
32969
+ const length = topChannel.length;
33608
32970
  const { width, height } = ctx.canvas;
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
- });
32971
+ const halfHeight = height / 2;
32972
+ const pixelRatio = this.getPixelRatio();
32973
+ const barWidth = options.barWidth ? options.barWidth * pixelRatio : 1;
32974
+ const barGap = options.barGap ? options.barGap * pixelRatio : options.barWidth ? barWidth / 2 : 0;
32975
+ const barRadius = options.barRadius || 0;
32976
+ const barIndexScale = width / (barWidth + barGap) / length;
32977
+ const rectFn = barRadius && 'roundRect' in ctx ? 'roundRect' : 'rect';
33627
32978
  ctx.beginPath();
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);
32979
+ let prevX = 0;
32980
+ let maxTop = 0;
32981
+ let maxBottom = 0;
32982
+ for (let i = 0; i <= length; i++) {
32983
+ const x = Math.round(i * barIndexScale);
32984
+ if (x > prevX) {
32985
+ const topBarHeight = Math.round(maxTop * halfHeight * vScale);
32986
+ const bottomBarHeight = Math.round(maxBottom * halfHeight * vScale);
32987
+ const barHeight = topBarHeight + bottomBarHeight || 1;
32988
+ // Vertical alignment
32989
+ let y = halfHeight - topBarHeight;
32990
+ if (options.barAlign === 'top') {
32991
+ y = 0;
32992
+ }
32993
+ else if (options.barAlign === 'bottom') {
32994
+ y = height - barHeight;
32995
+ }
32996
+ ctx[rectFn](prevX * (barWidth + barGap), y, barWidth, barHeight, barRadius);
32997
+ prevX = x;
32998
+ maxTop = 0;
32999
+ maxBottom = 0;
33634
33000
  }
33001
+ const magnitudeTop = Math.abs(topChannel[i] || 0);
33002
+ const magnitudeBottom = Math.abs(bottomChannel[i] || 0);
33003
+ if (magnitudeTop > maxTop)
33004
+ maxTop = magnitudeTop;
33005
+ if (magnitudeBottom > maxBottom)
33006
+ maxBottom = magnitudeBottom;
33635
33007
  }
33636
33008
  ctx.fill();
33637
33009
  ctx.closePath();
33638
33010
  }
33639
33011
  renderLineWaveform(channelData, _options, ctx, vScale) {
33640
- const { width, height } = ctx.canvas;
33641
- const paths = calculateLinePaths({ channelData, width, height, vScale });
33642
- ctx.beginPath();
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);
33012
+ const drawChannel = (index) => {
33013
+ const channel = channelData[index] || channelData[0];
33014
+ const length = channel.length;
33015
+ const { height } = ctx.canvas;
33016
+ const halfHeight = height / 2;
33017
+ const hScale = ctx.canvas.width / length;
33018
+ ctx.moveTo(0, halfHeight);
33019
+ let prevX = 0;
33020
+ let max = 0;
33021
+ for (let i = 0; i <= length; i++) {
33022
+ const x = Math.round(i * hScale);
33023
+ if (x > prevX) {
33024
+ const h = Math.round(max * halfHeight * vScale) || 1;
33025
+ const y = halfHeight + h * (index === 0 ? -1 : 1);
33026
+ ctx.lineTo(prevX, y);
33027
+ prevX = x;
33028
+ max = 0;
33029
+ }
33030
+ const value = Math.abs(channel[i] || 0);
33031
+ if (value > max)
33032
+ max = value;
33650
33033
  }
33651
- }
33034
+ ctx.lineTo(prevX, halfHeight);
33035
+ };
33036
+ ctx.beginPath();
33037
+ drawChannel(0);
33038
+ drawChannel(1);
33652
33039
  ctx.fill();
33653
33040
  ctx.closePath();
33654
33041
  }
33655
33042
  renderWaveform(channelData, options, ctx) {
33656
- ctx.fillStyle = this.convertColorValues(options.waveColor, ctx);
33043
+ ctx.fillStyle = this.convertColorValues(options.waveColor);
33044
+ // Custom rendering function
33657
33045
  if (options.renderFunction) {
33658
33046
  options.renderFunction(channelData, ctx);
33659
33047
  return;
33660
33048
  }
33661
- const vScale = calculateVerticalScale({
33662
- channelData,
33663
- barHeight: options.barHeight,
33664
- normalize: options.normalize,
33665
- maxPeak: options.maxPeak,
33666
- });
33667
- if (shouldRenderBars(options)) {
33049
+ // Vertical scaling
33050
+ let vScale = options.barHeight || 1;
33051
+ if (options.normalize) {
33052
+ const max = Array.from(channelData[0]).reduce((max, value) => Math.max(max, Math.abs(value)), 0);
33053
+ vScale = max ? 1 / max : 1;
33054
+ }
33055
+ // Render waveform as bars
33056
+ if (options.barWidth || options.barGap || options.barAlign) {
33668
33057
  this.renderBarWaveform(channelData, options, ctx, vScale);
33669
33058
  return;
33670
33059
  }
33060
+ // Render waveform as a polyline
33671
33061
  this.renderLineWaveform(channelData, options, ctx, vScale);
33672
33062
  }
33673
33063
  renderSingleCanvas(data, options, width, height, offset, canvasContainer, progressContainer) {
@@ -33680,13 +33070,7 @@ class Renderer extends EventEmitter {
33680
33070
  canvas.style.left = `${Math.round(offset)}px`;
33681
33071
  canvasContainer.appendChild(canvas);
33682
33072
  const ctx = canvas.getContext('2d');
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
- }
33073
+ this.renderWaveform(data, options, ctx);
33690
33074
  // Draw a progress canvas
33691
33075
  if (canvas.width > 0 && canvas.height > 0) {
33692
33076
  const progressCanvas = canvas.cloneNode();
@@ -33694,7 +33078,7 @@ class Renderer extends EventEmitter {
33694
33078
  progressCtx.drawImage(canvas, 0, 0);
33695
33079
  // Set the composition method to draw only where the waveform is drawn
33696
33080
  progressCtx.globalCompositeOperation = 'source-in';
33697
- progressCtx.fillStyle = this.convertColorValues(options.progressColor, progressCtx);
33081
+ progressCtx.fillStyle = this.convertColorValues(options.progressColor);
33698
33082
  // This rectangle acts as a mask thanks to the composition method
33699
33083
  progressCtx.fillRect(0, 0, canvas.width, canvas.height);
33700
33084
  progressContainer.appendChild(progressCanvas);
@@ -33704,8 +33088,17 @@ class Renderer extends EventEmitter {
33704
33088
  const pixelRatio = this.getPixelRatio();
33705
33089
  const { clientWidth } = this.scrollContainer;
33706
33090
  const totalWidth = width / pixelRatio;
33707
- const singleCanvasWidth = calculateSingleCanvasWidth({ clientWidth, totalWidth, options });
33091
+ let singleCanvasWidth = Math.min(Renderer.MAX_CANVAS_WIDTH, clientWidth, totalWidth);
33708
33092
  let drawnIndexes = {};
33093
+ // Adjust width to avoid gaps between canvases when using bars
33094
+ if (options.barWidth || options.barGap) {
33095
+ const barWidth = options.barWidth || 0.5;
33096
+ const barGap = options.barGap || barWidth / 2;
33097
+ const totalBarWidth = barWidth + barGap;
33098
+ if (singleCanvasWidth % totalBarWidth !== 0) {
33099
+ singleCanvasWidth = Math.floor(singleCanvasWidth / totalBarWidth) * totalBarWidth;
33100
+ }
33101
+ }
33709
33102
  // Nothing to render
33710
33103
  if (singleCanvasWidth === 0)
33711
33104
  return;
@@ -33719,15 +33112,24 @@ class Renderer extends EventEmitter {
33719
33112
  const offset = index * singleCanvasWidth;
33720
33113
  let clampedWidth = Math.min(totalWidth - offset, singleCanvasWidth);
33721
33114
  // Clamp the width to the bar grid to avoid empty canvases at the end
33722
- clampedWidth = clampWidthToBarGrid(clampedWidth, options);
33115
+ if (options.barWidth || options.barGap) {
33116
+ const barWidth = options.barWidth || 0.5;
33117
+ const barGap = options.barGap || barWidth / 2;
33118
+ const totalBarWidth = barWidth + barGap;
33119
+ clampedWidth = Math.floor(clampedWidth / totalBarWidth) * totalBarWidth;
33120
+ }
33723
33121
  if (clampedWidth <= 0)
33724
33122
  return;
33725
- const data = sliceChannelData({ channelData, offset, clampedWidth, totalWidth });
33123
+ const data = channelData.map((channel) => {
33124
+ const start = Math.floor((offset / totalWidth) * channel.length);
33125
+ const end = Math.floor(((offset + clampedWidth) / totalWidth) * channel.length);
33126
+ return channel.slice(start, end);
33127
+ });
33726
33128
  this.renderSingleCanvas(data, options, clampedWidth, height, offset, canvasContainer, progressContainer);
33727
33129
  };
33728
33130
  // Clear canvases to avoid too many DOM nodes
33729
33131
  const clearCanvases = () => {
33730
- if (shouldClearCanvases(Object.keys(drawnIndexes).length)) {
33132
+ if (Object.keys(drawnIndexes).length > Renderer.MAX_NODES) {
33731
33133
  canvasContainer.innerHTML = '';
33732
33134
  progressContainer.innerHTML = '';
33733
33135
  drawnIndexes = {};
@@ -33743,18 +33145,21 @@ class Renderer extends EventEmitter {
33743
33145
  return;
33744
33146
  }
33745
33147
  // Lazy rendering
33746
- const initialRange = getLazyRenderRange({
33747
- scrollLeft: this.scrollContainer.scrollLeft,
33748
- totalWidth,
33749
- numCanvases,
33750
- });
33751
- initialRange.forEach((index) => draw(index));
33148
+ const viewPosition = this.scrollContainer.scrollLeft / totalWidth;
33149
+ const startCanvas = Math.floor(viewPosition * numCanvases);
33150
+ // Draw the canvases in the viewport first
33151
+ draw(startCanvas - 1);
33152
+ draw(startCanvas);
33153
+ draw(startCanvas + 1);
33752
33154
  // Subscribe to the scroll event to draw additional canvases
33753
33155
  if (numCanvases > 1) {
33754
33156
  const unsubscribe = this.on('scroll', () => {
33755
33157
  const { scrollLeft } = this.scrollContainer;
33158
+ const canvasIndex = Math.floor((scrollLeft / totalWidth) * numCanvases);
33756
33159
  clearCanvases();
33757
- getLazyRenderRange({ scrollLeft, totalWidth, numCanvases }).forEach((index) => draw(index));
33160
+ draw(canvasIndex - 1);
33161
+ draw(canvasIndex);
33162
+ draw(canvasIndex + 1);
33758
33163
  });
33759
33164
  this.unsubscribeOnScroll.push(unsubscribe);
33760
33165
  }
@@ -33793,15 +33198,12 @@ class Renderer extends EventEmitter {
33793
33198
  // Determine the width of the waveform
33794
33199
  const pixelRatio = this.getPixelRatio();
33795
33200
  const parentWidth = this.scrollContainer.clientWidth;
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
- });
33201
+ const scrollWidth = Math.ceil(audioData.duration * (this.options.minPxPerSec || 0));
33803
33202
  // Whether the container should scroll
33804
- this.isScrollable = isScrollable;
33203
+ this.isScrollable = scrollWidth > parentWidth;
33204
+ const useParentWidth = this.options.fillParent && !this.isScrollable;
33205
+ // Width of the waveform in pixels
33206
+ const width = (useParentWidth ? parentWidth : scrollWidth) * pixelRatio;
33805
33207
  // Set the width of the wrapper
33806
33208
  this.wrapper.style.width = useParentWidth ? '100%' : `${scrollWidth}px`;
33807
33209
  // Set additional styles
@@ -33844,7 +33246,12 @@ class Renderer extends EventEmitter {
33844
33246
  // Adjust the scroll position so that the cursor stays in the same place
33845
33247
  if (this.isScrollable && scrollWidth !== this.scrollContainer.scrollWidth) {
33846
33248
  const { right: after } = this.progressWrapper.getBoundingClientRect();
33847
- const delta = roundToHalfAwayFromZero(after - before);
33249
+ let delta = after - before;
33250
+ // to limit compounding floating-point drift
33251
+ // we need to round to the half px furthest from 0
33252
+ delta *= 2;
33253
+ delta = delta < 0 ? Math.floor(delta) : Math.ceil(delta);
33254
+ delta /= 2;
33848
33255
  this.scrollContainer.scrollLeft += delta;
33849
33256
  }
33850
33257
  }
@@ -33875,9 +33282,16 @@ class Renderer extends EventEmitter {
33875
33282
  // Keep the cursor centered when playing
33876
33283
  const center = progressWidth - scrollLeft - middle;
33877
33284
  if (isPlaying && this.options.autoCenter && center > 0) {
33878
- this.scrollContainer.scrollLeft += center;
33285
+ this.scrollContainer.scrollLeft += Math.min(center, 10);
33879
33286
  }
33880
33287
  }
33288
+ // Emit the scroll event
33289
+ {
33290
+ const newScroll = this.scrollContainer.scrollLeft;
33291
+ const startX = newScroll / scrollWidth;
33292
+ const endX = (newScroll + clientWidth) / scrollWidth;
33293
+ this.emit('scroll', startX, endX, newScroll, newScroll + clientWidth);
33294
+ }
33881
33295
  }
33882
33296
  renderProgress(progress, isPlaying) {
33883
33297
  if (isNaN(progress))
@@ -33889,8 +33303,7 @@ class Renderer extends EventEmitter {
33889
33303
  this.cursor.style.transform = this.options.cursorWidth
33890
33304
  ? `translateX(-${progress * this.options.cursorWidth}px)`
33891
33305
  : '';
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) {
33306
+ if (this.isScrollable && this.options.autoScroll) {
33894
33307
  this.scrollIntoView(progress, isPlaying);
33895
33308
  }
33896
33309
  }
@@ -33921,39 +33334,27 @@ class Renderer extends EventEmitter {
33921
33334
  });
33922
33335
  }
33923
33336
  }
33337
+ Renderer.MAX_CANVAS_WIDTH = 8000;
33338
+ Renderer.MAX_NODES = 10;
33924
33339
 
33925
33340
  class Timer extends EventEmitter {
33926
33341
  constructor() {
33927
33342
  super(...arguments);
33928
- this.animationFrameId = null;
33929
- this.isRunning = false;
33343
+ this.unsubscribe = () => undefined;
33930
33344
  }
33931
33345
  start() {
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();
33346
+ this.unsubscribe = this.on('tick', () => {
33347
+ requestAnimationFrame(() => {
33348
+ this.emit('tick');
33349
+ });
33350
+ });
33351
+ this.emit('tick');
33946
33352
  }
33947
33353
  stop() {
33948
- this.isRunning = false;
33949
- // Cancel any pending animation frame
33950
- if (this.animationFrameId !== null) {
33951
- cancelAnimationFrame(this.animationFrameId);
33952
- this.animationFrameId = null;
33953
- }
33354
+ this.unsubscribe();
33954
33355
  }
33955
33356
  destroy() {
33956
- this.stop();
33357
+ this.unsubscribe();
33957
33358
  }
33958
33359
  }
33959
33360
 
@@ -33968,10 +33369,6 @@ var __awaiter$4 = (undefined && undefined.__awaiter) || function (thisArg, _argu
33968
33369
  };
33969
33370
  /**
33970
33371
  * 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.
33975
33372
  */
33976
33373
  class WebAudioPlayer extends EventEmitter {
33977
33374
  constructor(audioContext = new AudioContext()) {
@@ -34032,21 +33429,14 @@ class WebAudioPlayer extends EventEmitter {
34032
33429
  this.emit('canplay');
34033
33430
  if (this.autoplay)
34034
33431
  this.play();
34035
- })
34036
- .catch((err) => {
34037
- // Emit error for proper error handling
34038
- console.error('WebAudioPlayer load error:', err);
34039
33432
  });
34040
33433
  }
34041
33434
  _play() {
33435
+ var _a;
34042
33436
  if (!this.paused)
34043
33437
  return;
34044
33438
  this.paused = false;
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
- }
33439
+ (_a = this.bufferNode) === null || _a === void 0 ? void 0 : _a.disconnect();
34050
33440
  this.bufferNode = this.audioContext.createBufferSource();
34051
33441
  if (this.buffer) {
34052
33442
  this.bufferNode.buffer = this.buffer;
@@ -34202,239 +33592,6 @@ class WebAudioPlayer extends EventEmitter {
34202
33592
  }
34203
33593
  }
34204
33594
 
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
-
34438
33595
  var __awaiter$5 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
34439
33596
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
34440
33597
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -34461,14 +33618,6 @@ class WaveSurfer extends Player {
34461
33618
  static create(options) {
34462
33619
  return new WaveSurfer(options);
34463
33620
  }
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
- }
34472
33621
  /** Create a new WaveSurfer instance */
34473
33622
  constructor(options) {
34474
33623
  const media = options.media ||
@@ -34485,27 +33634,13 @@ class WaveSurfer extends Player {
34485
33634
  this.subscriptions = [];
34486
33635
  this.mediaSubscriptions = [];
34487
33636
  this.abortController = null;
34488
- this.reactiveCleanups = [];
34489
33637
  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;
34502
33638
  this.timer = new Timer();
34503
33639
  const audioElement = media ? undefined : this.getMediaElement();
34504
33640
  this.renderer = new Renderer(this.options, audioElement);
34505
33641
  this.initPlayerEvents();
34506
33642
  this.initRendererEvents();
34507
33643
  this.initTimerEvents();
34508
- this.initReactiveState();
34509
33644
  this.initPlugins();
34510
33645
  // Read the initial URL before load has been called
34511
33646
  const initialUrl = this.options.url || this.getSrc() || '';
@@ -34518,10 +33653,7 @@ class WaveSurfer extends Player {
34518
33653
  if (initialUrl || (peaks && duration)) {
34519
33654
  // Swallow async errors because they cannot be caught from a constructor call.
34520
33655
  // Subscribe to the wavesurfer's error event to handle them.
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
- });
33656
+ this.load(initialUrl, peaks, duration).catch(() => null);
34525
33657
  }
34526
33658
  });
34527
33659
  }
@@ -34543,12 +33675,6 @@ class WaveSurfer extends Player {
34543
33675
  }
34544
33676
  }));
34545
33677
  }
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
- }
34552
33678
  initPlayerEvents() {
34553
33679
  if (this.isPlaying()) {
34554
33680
  this.emit('play');
@@ -34613,44 +33739,33 @@ class WaveSurfer extends Player {
34613
33739
  // DragEnd
34614
33740
  this.renderer.on('dragend', (relativeX) => {
34615
33741
  this.emit('dragend', relativeX);
34616
- }),
34617
- // Resize
34618
- this.renderer.on('resize', () => {
34619
- this.emit('resize');
34620
33742
  }));
34621
33743
  // Drag
34622
33744
  {
34623
33745
  let debounce;
34624
- const unsubscribeDrag = this.renderer.on('drag', (relativeX) => {
34625
- var _a;
33746
+ this.subscriptions.push(this.renderer.on('drag', (relativeX) => {
34626
33747
  if (!this.options.interact)
34627
33748
  return;
34628
33749
  // Update the visual position
34629
33750
  this.renderer.renderProgress(relativeX);
34630
33751
  // Set the audio position with a debounce
34631
33752
  clearTimeout(debounce);
34632
- let debounceTime = 0;
34633
- const dragToSeek = this.options.dragToSeek;
33753
+ let debounceTime;
34634
33754
  if (this.isPlaying()) {
34635
33755
  debounceTime = 0;
34636
33756
  }
34637
- else if (dragToSeek === true) {
33757
+ else if (this.options.dragToSeek === true) {
34638
33758
  debounceTime = 200;
34639
33759
  }
34640
- else if (dragToSeek && typeof dragToSeek === 'object') {
34641
- debounceTime = (_a = dragToSeek.debounceTime) !== null && _a !== void 0 ? _a : 200;
33760
+ else if (typeof this.options.dragToSeek === 'object' && this.options.dragToSeek !== undefined) {
33761
+ debounceTime = this.options.dragToSeek['debounceTime'];
34642
33762
  }
34643
33763
  debounce = setTimeout(() => {
34644
33764
  this.seekTo(relativeX);
34645
33765
  }, debounceTime);
34646
33766
  this.emit('interaction', relativeX * this.getDuration());
34647
33767
  this.emit('drag', relativeX);
34648
- });
34649
- // Clear debounce timeout on destroy
34650
- this.subscriptions.push(() => {
34651
- clearTimeout(debounce);
34652
- unsubscribeDrag();
34653
- });
33768
+ }));
34654
33769
  }
34655
33770
  }
34656
33771
  initPlugins() {
@@ -34737,15 +33852,12 @@ class WaveSurfer extends Player {
34737
33852
  this.pause();
34738
33853
  this.decodedData = null;
34739
33854
  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;
34743
33855
  // Fetch the entire audio as a blob if pre-decoded data is not provided
34744
33856
  if (!blob && !channelData) {
34745
33857
  const fetchParams = this.options.fetchParams || {};
34746
33858
  if (window.AbortController && !fetchParams.signal) {
34747
33859
  this.abortController = new AbortController();
34748
- fetchParams.signal = this.abortController.signal;
33860
+ fetchParams.signal = (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.signal;
34749
33861
  }
34750
33862
  const onProgress = (percentage) => this.emit('loading', percentage);
34751
33863
  blob = yield Fetcher.fetchBlob(url, onProgress, fetchParams);
@@ -34835,8 +33947,8 @@ class WaveSurfer extends Player {
34835
33947
  const channel = this.decodedData.getChannelData(i);
34836
33948
  const data = [];
34837
33949
  const sampleSize = channel.length / maxLength;
34838
- for (let j = 0; j < maxLength; j++) {
34839
- const sample = channel.slice(Math.floor(j * sampleSize), Math.ceil((j + 1) * sampleSize));
33950
+ for (let i = 0; i < maxLength; i++) {
33951
+ const sample = channel.slice(Math.floor(i * sampleSize), Math.ceil((i + 1) * sampleSize));
34840
33952
  let max = 0;
34841
33953
  for (let x = 0; x < sample.length; x++) {
34842
33954
  const n = sample[x];
@@ -34869,7 +33981,7 @@ class WaveSurfer extends Player {
34869
33981
  this.updateProgress(time);
34870
33982
  this.emit('timeupdate', time);
34871
33983
  }
34872
- /** Seek to a ratio of audio as [0..1] (0 = beginning, 1 = end) */
33984
+ /** Seek to a percentage of audio as [0..1] (0 = beginning, 1 = end) */
34873
33985
  seekTo(progress) {
34874
33986
  const time = this.getDuration() * progress;
34875
33987
  this.setTime(time);
@@ -34933,8 +34045,6 @@ class WaveSurfer extends Player {
34933
34045
  this.plugins.forEach((plugin) => plugin.destroy());
34934
34046
  this.subscriptions.forEach((unsubscribe) => unsubscribe());
34935
34047
  this.unsubscribePlayerEvents();
34936
- this.reactiveCleanups.forEach((cleanup) => cleanup());
34937
- this.reactiveCleanups = [];
34938
34048
  this.timer.destroy();
34939
34049
  this.renderer.destroy();
34940
34050
  super.destroy();
@@ -35007,9 +34117,170 @@ var Bar = styled__default.div(_templateObject2$q || (_templateObject2$q = _tagge
35007
34117
  return props.progressColor;
35008
34118
  });
35009
34119
 
34120
+ var ffmpegInstance = null;
34121
+ var isFFmpegLoading = false;
34122
+ var ffmpegLoadPromise = null;
34123
+ var isSafari = function isSafari() {
34124
+ if (typeof window === 'undefined' || !window.navigator) {
34125
+ return false;
34126
+ }
34127
+ var userAgent = window.navigator.userAgent;
34128
+ return /^((?!chrome|android).)*safari/i.test(userAgent);
34129
+ };
34130
+ var initFFmpeg = function initFFmpeg() {
34131
+ try {
34132
+ if (ffmpegInstance) {
34133
+ return Promise.resolve(ffmpegInstance);
34134
+ }
34135
+ if (isFFmpegLoading && ffmpegLoadPromise) {
34136
+ return Promise.resolve(ffmpegLoadPromise.then(function () {
34137
+ if (!ffmpegInstance) {
34138
+ throw new Error('FFmpeg failed to initialize');
34139
+ }
34140
+ return ffmpegInstance;
34141
+ }));
34142
+ }
34143
+ isFFmpegLoading = true;
34144
+ ffmpegLoadPromise = function () {
34145
+ try {
34146
+ return Promise.resolve(_catch(function () {
34147
+ var ffmpeg$1 = new ffmpeg.FFmpeg();
34148
+ var baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
34149
+ var _load = ffmpeg$1.load;
34150
+ return Promise.resolve(util.toBlobURL(baseURL + "/ffmpeg-core.js", 'text/javascript')).then(function (_toBlobURL) {
34151
+ return Promise.resolve(util.toBlobURL(baseURL + "/ffmpeg-core.wasm", 'application/wasm')).then(function (_toBlobURL2) {
34152
+ return Promise.resolve(_load.call(ffmpeg$1, {
34153
+ coreURL: _toBlobURL,
34154
+ wasmURL: _toBlobURL2
34155
+ })).then(function () {
34156
+ ffmpegInstance = ffmpeg$1;
34157
+ isFFmpegLoading = false;
34158
+ });
34159
+ });
34160
+ });
34161
+ }, function (error) {
34162
+ isFFmpegLoading = false;
34163
+ ffmpegLoadPromise = null;
34164
+ ffmpegInstance = null;
34165
+ log.error('Failed to load FFmpeg:', error);
34166
+ throw error;
34167
+ }));
34168
+ } catch (e) {
34169
+ return Promise.reject(e);
34170
+ }
34171
+ }();
34172
+ return Promise.resolve(ffmpegLoadPromise).then(function () {
34173
+ if (!ffmpegInstance) {
34174
+ throw new Error('FFmpeg instance is null after initialization');
34175
+ }
34176
+ return ffmpegInstance;
34177
+ });
34178
+ } catch (e) {
34179
+ return Promise.reject(e);
34180
+ }
34181
+ };
34182
+ var convertMp3ToAac = function convertMp3ToAac(file, messageId) {
34183
+ try {
34184
+ return Promise.resolve(_catch(function () {
34185
+ var maxSize = 50 * 1024 * 1024;
34186
+ if (file.size > maxSize) {
34187
+ throw new Error("File size (" + (file.size / 1024 / 1024).toFixed(2) + "MB) exceeds maximum allowed size (50MB)");
34188
+ }
34189
+ if (file.size === 0) {
34190
+ throw new Error('File is empty');
34191
+ }
34192
+ return Promise.resolve(initFFmpeg()).then(function (ffmpeg) {
34193
+ function _temp8() {
34194
+ function _temp6() {
34195
+ return Promise.resolve(util.fetchFile(file)).then(function (inputData) {
34196
+ return Promise.resolve(ffmpeg.writeFile(messageId + "_input.mp3", inputData)).then(function () {
34197
+ return Promise.resolve(ffmpeg.exec(['-i', messageId + "_input.mp3", '-c:a', 'aac', '-b:a', '128k', '-movflags', '+faststart', messageId + "_output.m4a"])).then(function () {
34198
+ return Promise.resolve(ffmpeg.readFile(messageId + "_output.m4a")).then(function (data) {
34199
+ function _temp4() {
34200
+ function _temp2() {
34201
+ var dataArray;
34202
+ if (data instanceof Uint8Array) {
34203
+ dataArray = data;
34204
+ } else if (typeof data === 'string') {
34205
+ var binaryString = atob(data);
34206
+ var bytes = new Uint8Array(binaryString.length);
34207
+ for (var i = 0; i < binaryString.length; i++) {
34208
+ bytes[i] = binaryString.charCodeAt(i);
34209
+ }
34210
+ dataArray = bytes;
34211
+ } else {
34212
+ dataArray = new Uint8Array(data);
34213
+ }
34214
+ var arrayBuffer = dataArray.buffer.slice(dataArray.byteOffset, dataArray.byteOffset + dataArray.byteLength);
34215
+ var blob = new Blob([arrayBuffer], {
34216
+ type: 'audio/mp4'
34217
+ });
34218
+ var convertedFile = new File([blob], messageId + "_" + file.name.replace('.mp3', '.m4a'), {
34219
+ type: 'audio/mp4',
34220
+ lastModified: file.lastModified
34221
+ });
34222
+ return convertedFile;
34223
+ }
34224
+ var _temp = _catch(function () {
34225
+ return Promise.resolve(ffmpeg.deleteFile(messageId + "_output.m4a")).then(function () {});
34226
+ }, function () {});
34227
+ return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
34228
+ }
34229
+ var _temp3 = _catch(function () {
34230
+ return Promise.resolve(ffmpeg.deleteFile(messageId + "_input.mp3")).then(function () {});
34231
+ }, function () {});
34232
+ return _temp3 && _temp3.then ? _temp3.then(_temp4) : _temp4(_temp3);
34233
+ });
34234
+ });
34235
+ });
34236
+ });
34237
+ }
34238
+ var _temp5 = _catch(function () {
34239
+ return Promise.resolve(ffmpeg.deleteFile(messageId + "_output.m4a")).then(function () {});
34240
+ }, function () {});
34241
+ return _temp5 && _temp5.then ? _temp5.then(_temp6) : _temp6(_temp5);
34242
+ }
34243
+ var _temp7 = _catch(function () {
34244
+ return Promise.resolve(ffmpeg.deleteFile(messageId + "_input.mp3")).then(function () {});
34245
+ }, function () {});
34246
+ return _temp7 && _temp7.then ? _temp7.then(_temp8) : _temp8(_temp7);
34247
+ });
34248
+ }, function (error) {
34249
+ log.error('Failed to convert MP3 to AAC:', error);
34250
+ throw error;
34251
+ }));
34252
+ } catch (e) {
34253
+ return Promise.reject(e);
34254
+ }
34255
+ };
34256
+ var convertAudioForSafari = function convertAudioForSafari(file, messageId) {
34257
+ try {
34258
+ var _exit = false;
34259
+ var _temp9 = function () {
34260
+ if (isSafari() && file.type === 'audio/mpeg' && file.name.endsWith('.mp3')) {
34261
+ return _catch(function () {
34262
+ return Promise.resolve(convertMp3ToAac(file, messageId)).then(function (_await$convertMp3ToAa) {
34263
+ _exit = true;
34264
+ return _await$convertMp3ToAa;
34265
+ });
34266
+ }, function (error) {
34267
+ log.warn('Audio conversion failed, using original file:', error);
34268
+ _exit = true;
34269
+ return file;
34270
+ });
34271
+ }
34272
+ }();
34273
+ return Promise.resolve(_temp9 && _temp9.then ? _temp9.then(function (_result2) {
34274
+ return _exit ? _result2 : file;
34275
+ }) : _exit ? _temp9 : file);
34276
+ } catch (e) {
34277
+ return Promise.reject(e);
34278
+ }
34279
+ };
34280
+
35010
34281
  var _templateObject$v, _templateObject2$r, _templateObject3$l, _templateObject4$h, _templateObject5$e, _templateObject6$c, _templateObject7$b, _templateObject8$b;
35011
34282
  var AudioPlayer = function AudioPlayer(_ref) {
35012
- var _file$metadata, _file$metadata2, _file$metadata3;
34283
+ var _file$metadata5, _file$metadata6, _file$metadata7;
35013
34284
  var url = _ref.url,
35014
34285
  file = _ref.file,
35015
34286
  messagePlayed = _ref.messagePlayed,
@@ -35060,6 +34331,7 @@ var AudioPlayer = function AudioPlayer(_ref) {
35060
34331
  var wavesurfer = React.useRef(null);
35061
34332
  var wavesurferContainer = React.useRef(null);
35062
34333
  var intervalRef = React.useRef(null);
34334
+ var convertedUrlRef = React.useRef(null);
35063
34335
  var handleSetAudioRate = function handleSetAudioRate() {
35064
34336
  if (wavesurfer.current) {
35065
34337
  if (audioRate === 1) {
@@ -35143,84 +34415,131 @@ var AudioPlayer = function AudioPlayer(_ref) {
35143
34415
  }, [recording.initRecording]);
35144
34416
  React.useEffect(function () {
35145
34417
  if (url) {
34418
+ if (convertedUrlRef.current) {
34419
+ URL.revokeObjectURL(convertedUrlRef.current);
34420
+ convertedUrlRef.current = null;
34421
+ }
35146
34422
  if (url !== '_' && !isRendered && wavesurfer && wavesurfer.current) {
35147
34423
  wavesurfer.current.destroy();
35148
34424
  }
35149
34425
  var initWaveSurfer = function initWaveSurfer() {
35150
34426
  try {
35151
- try {
35152
- wavesurfer.current = WaveSurfer.create({
35153
- container: wavesurferContainer.current,
35154
- waveColor: 'transparent',
35155
- progressColor: 'transparent',
35156
- audioRate: audioRate,
35157
- barWidth: 1,
35158
- barHeight: 1,
35159
- hideScrollbar: true,
35160
- barRadius: 1.5,
35161
- cursorWidth: 0,
35162
- barGap: 2,
35163
- height: 20
35164
- });
35165
- var peaks;
35166
- if (file.metadata) {
35167
- if (file.metadata.dur) {
35168
- setDuration(file.metadata.dur);
35169
- setCurrentTime(formatAudioVideoTime(file.metadata.dur));
34427
+ var _exit = false;
34428
+ return Promise.resolve(_catch(function () {
34429
+ var _file$name;
34430
+ function _temp2(_result2) {
34431
+ if (_exit) return _result2;
34432
+ wavesurfer.current = WaveSurfer.create({
34433
+ container: wavesurferContainer.current,
34434
+ waveColor: 'transparent',
34435
+ progressColor: 'transparent',
34436
+ audioRate: audioRate,
34437
+ barWidth: 1,
34438
+ barHeight: 1,
34439
+ hideScrollbar: true,
34440
+ barRadius: 1.5,
34441
+ cursorWidth: 0,
34442
+ barGap: 2,
34443
+ height: 20
34444
+ });
34445
+ var peaks;
34446
+ if (file.metadata) {
34447
+ if (file.metadata.dur) {
34448
+ setDuration(file.metadata.dur);
34449
+ setCurrentTime(formatAudioVideoTime(file.metadata.dur));
34450
+ }
34451
+ if (file.metadata.tmb) {
34452
+ var maxVal = Array.isArray(file.metadata.tmb) && file.metadata.tmb.length > 0 ? file.metadata.tmb.reduce(function (acc, n) {
34453
+ return n > acc ? n : acc;
34454
+ }, -Infinity) : 0;
34455
+ var dec = maxVal / 100;
34456
+ peaks = file.metadata.tmb.map(function (peak) {
34457
+ return peak / dec / 100;
34458
+ });
34459
+ }
35170
34460
  }
35171
- if (file.metadata.tmb) {
35172
- var maxVal = Array.isArray(file.metadata.tmb) && file.metadata.tmb.length > 0 ? file.metadata.tmb.reduce(function (acc, n) {
35173
- return n > acc ? n : acc;
35174
- }, -Infinity) : 0;
35175
- var dec = maxVal / 100;
35176
- peaks = file.metadata.tmb.map(function (peak) {
35177
- return peak / dec / 100;
35178
- });
34461
+ wavesurfer.current.load(audioUrl, peaks);
34462
+ wavesurfer.current.on('ready', function () {
34463
+ var _file$metadata, _file$metadata2;
34464
+ var audioDuration = wavesurfer.current.getDuration();
34465
+ setDuration((file === null || file === void 0 ? void 0 : (_file$metadata = file.metadata) === null || _file$metadata === void 0 ? void 0 : _file$metadata.dur) || audioDuration);
34466
+ setCurrentTime(formatAudioVideoTime((file === null || file === void 0 ? void 0 : (_file$metadata2 = file.metadata) === null || _file$metadata2 === void 0 ? void 0 : _file$metadata2.dur) || audioDuration));
34467
+ wavesurfer.current.drawBuffer = function (d) {
34468
+ log.info('filters --- ', d);
34469
+ };
34470
+ });
34471
+ wavesurfer.current.on('finish', function () {
34472
+ var _file$metadata3, _file$metadata4;
34473
+ setPlayAudio(false);
34474
+ wavesurfer.current.seekTo(0);
34475
+ var audioDuration = wavesurfer.current.getDuration();
34476
+ setDuration((file === null || file === void 0 ? void 0 : (_file$metadata3 = file.metadata) === null || _file$metadata3 === void 0 ? void 0 : _file$metadata3.dur) || audioDuration);
34477
+ setCurrentTime(formatAudioVideoTime((file === null || file === void 0 ? void 0 : (_file$metadata4 = file.metadata) === null || _file$metadata4 === void 0 ? void 0 : _file$metadata4.dur) || audioDuration));
34478
+ setCurrentTimeSeconds(0);
34479
+ if (playingAudioId === file.id) {
34480
+ dispatch(setPlayingAudioIdAC(null));
34481
+ }
34482
+ clearInterval(intervalRef.current);
34483
+ if (onClose) {
34484
+ onClose();
34485
+ }
34486
+ });
34487
+ wavesurfer.current.on('pause', function () {
34488
+ setPlayAudio(false);
34489
+ if (playingAudioId === file.id) {
34490
+ dispatch(setPlayingAudioIdAC(null));
34491
+ }
34492
+ clearInterval(intervalRef.current);
34493
+ });
34494
+ wavesurfer.current.on('interaction', function () {
34495
+ var currentTime = wavesurfer.current.getCurrentTime();
34496
+ setCurrentTime(formatAudioVideoTime(currentTime));
34497
+ setCurrentTimeSeconds(currentTime);
34498
+ });
34499
+ if (url !== '_') {
34500
+ setIsRendered(true);
35179
34501
  }
35180
34502
  }
35181
- wavesurfer.current.load(url, peaks);
35182
- wavesurfer.current.on('ready', function () {
35183
- var audioDuration = wavesurfer.current.getDuration();
35184
- setDuration(audioDuration);
35185
- setCurrentTime(formatAudioVideoTime(audioDuration));
35186
- wavesurfer.current.drawBuffer = function (d) {
35187
- log.info('filters --- ', d);
35188
- };
35189
- });
35190
- wavesurfer.current.on('finish', function () {
35191
- setPlayAudio(false);
35192
- wavesurfer.current.seekTo(0);
35193
- var audioDuration = wavesurfer.current.getDuration();
35194
- setDuration(audioDuration);
35195
- setCurrentTime(formatAudioVideoTime(audioDuration));
35196
- setCurrentTimeSeconds(0);
35197
- if (playingAudioId === file.id) {
35198
- dispatch(setPlayingAudioIdAC(null));
35199
- }
35200
- clearInterval(intervalRef.current);
35201
- if (onClose) {
35202
- onClose();
35203
- }
35204
- });
35205
- wavesurfer.current.on('pause', function () {
35206
- setPlayAudio(false);
35207
- if (playingAudioId === file.id) {
35208
- dispatch(setPlayingAudioIdAC(null));
34503
+ var audioUrl = url;
34504
+ var needsConversion = isSafari() && url && url !== '_' && (url.endsWith('.mp3') || ((_file$name = file.name) === null || _file$name === void 0 ? void 0 : _file$name.endsWith('.mp3')) || file.type === 'audio/mpeg');
34505
+ var _temp = function () {
34506
+ if (needsConversion) {
34507
+ return _catch(function () {
34508
+ if (convertedUrlRef.current) {
34509
+ URL.revokeObjectURL(convertedUrlRef.current);
34510
+ convertedUrlRef.current = null;
34511
+ }
34512
+ var cacheKey = file.id || url;
34513
+ return Promise.resolve(fetch(url, {
34514
+ cache: 'no-store'
34515
+ })).then(function (response) {
34516
+ if (!response.ok) {
34517
+ throw new Error("Failed to fetch audio: " + response.statusText);
34518
+ }
34519
+ return Promise.resolve(response.blob()).then(function (blob) {
34520
+ var uniqueFileName = (file.id || Date.now()) + "_" + (file.name || 'audio.mp3');
34521
+ var audioFile = new File([blob], uniqueFileName, {
34522
+ type: blob.type || 'audio/mpeg',
34523
+ lastModified: Date.now()
34524
+ });
34525
+ return Promise.resolve(convertAudioForSafari(audioFile, file === null || file === void 0 ? void 0 : file.messageId)).then(function (convertedFile) {
34526
+ var convertedBlobUrl = URL.createObjectURL(convertedFile);
34527
+ audioUrl = convertedBlobUrl;
34528
+ convertedUrlRef.current = convertedBlobUrl;
34529
+ log.info("Converted audio for Safari: " + cacheKey + " -> " + convertedBlobUrl);
34530
+ });
34531
+ });
34532
+ });
34533
+ }, function (conversionError) {
34534
+ log.warn('Failed to convert audio for Safari, using original:', conversionError);
34535
+ audioUrl = url;
34536
+ });
35209
34537
  }
35210
- clearInterval(intervalRef.current);
35211
- });
35212
- wavesurfer.current.on('interaction', function () {
35213
- var currentTime = wavesurfer.current.getCurrentTime();
35214
- setCurrentTime(formatAudioVideoTime(currentTime));
35215
- setCurrentTimeSeconds(currentTime);
35216
- });
35217
- if (url !== '_') {
35218
- setIsRendered(true);
35219
- }
35220
- } catch (e) {
34538
+ }();
34539
+ return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
34540
+ }, function (e) {
35221
34541
  log.error('Failed to init wavesurfer', e);
35222
- }
35223
- return Promise.resolve();
34542
+ }));
35224
34543
  } catch (e) {
35225
34544
  return Promise.reject(e);
35226
34545
  }
@@ -35229,8 +34548,16 @@ var AudioPlayer = function AudioPlayer(_ref) {
35229
34548
  }
35230
34549
  return function () {
35231
34550
  clearInterval(intervalRef.current);
34551
+ if (convertedUrlRef.current) {
34552
+ URL.revokeObjectURL(convertedUrlRef.current);
34553
+ convertedUrlRef.current = null;
34554
+ }
34555
+ if (wavesurfer.current) {
34556
+ wavesurfer.current.destroy();
34557
+ wavesurfer.current = null;
34558
+ }
35232
34559
  };
35233
- }, [url]);
34560
+ }, [url, file.id]);
35234
34561
  React.useEffect(function () {
35235
34562
  if (playAudio && playingAudioId && playingAudioId !== "player_" + file.id && wavesurfer.current) {
35236
34563
  setPlayAudio(false);
@@ -35248,8 +34575,8 @@ var AudioPlayer = function AudioPlayer(_ref) {
35248
34575
  iconColor: accentColor
35249
34576
  }))), /*#__PURE__*/React__default.createElement(WaveContainer, null, /*#__PURE__*/React__default.createElement(VisualizationWrapper, null, /*#__PURE__*/React__default.createElement(AudioVisualizationPlaceholder, {
35250
34577
  ref: wavesurferContainer,
35251
- hidden: !!((_file$metadata = file.metadata) !== null && _file$metadata !== void 0 && _file$metadata.tmb && Array.isArray(file.metadata.tmb))
35252
- }), ((_file$metadata2 = file.metadata) === null || _file$metadata2 === void 0 ? void 0 : _file$metadata2.tmb) && Array.isArray(file.metadata.tmb) && (/*#__PURE__*/React__default.createElement(AudioVisualization, {
34578
+ hidden: !!((_file$metadata5 = file.metadata) !== null && _file$metadata5 !== void 0 && _file$metadata5.tmb && Array.isArray(file.metadata.tmb))
34579
+ }), ((_file$metadata6 = file.metadata) === null || _file$metadata6 === void 0 ? void 0 : _file$metadata6.tmb) && Array.isArray(file.metadata.tmb) && (/*#__PURE__*/React__default.createElement(AudioVisualization, {
35253
34580
  tmb: file.metadata.tmb,
35254
34581
  duration: duration || file.metadata.dur || 0,
35255
34582
  currentTime: currentTimeSeconds,
@@ -35265,7 +34592,7 @@ var AudioPlayer = function AudioPlayer(_ref) {
35265
34592
  backgroundColor: backgroundSections
35266
34593
  }, audioRate, /*#__PURE__*/React__default.createElement("span", null, "X"))), /*#__PURE__*/React__default.createElement(Timer$1, {
35267
34594
  color: textSecondary
35268
- }, currentTime || formatAudioVideoTime(((_file$metadata3 = file.metadata) === null || _file$metadata3 === void 0 ? void 0 : _file$metadata3.dur) || 0)));
34595
+ }, currentTime || formatAudioVideoTime(((_file$metadata7 = file.metadata) === null || _file$metadata7 === void 0 ? void 0 : _file$metadata7.dur) || 0)));
35269
34596
  };
35270
34597
  var Container$f = styled__default.div(_templateObject$v || (_templateObject$v = _taggedTemplateLiteralLoose(["\n position: relative;\n display: flex;\n align-items: flex-start;\n width: 230px;\n padding: 8px 12px;\n ", "\n ", "\n"])), function (props) {
35271
34598
  return props.backgroundColor && "background-color: " + props.backgroundColor + ";";
@@ -46317,7 +45644,7 @@ var SendMessageInput = function SendMessageInput(_ref3) {
46317
45644
  if (activeChannel.id) {
46318
45645
  prevActiveChannelId = activeChannel.id;
46319
45646
  }
46320
- if (activeChannel.id && membersHasNext === undefined) {
45647
+ if (activeChannel.id && membersHasNext === undefined && !(activeChannel.type === DEFAULT_CHANNEL_TYPE.DIRECT && activeChannel.memberCount === 2)) {
46321
45648
  dispatch(getMembersAC(activeChannel.id));
46322
45649
  }
46323
45650
  setMentionedUsers([]);
@@ -49950,7 +49277,7 @@ var Members = function Members(_ref) {
49950
49277
  if (getFromContacts) {
49951
49278
  dispatch(getContactsAC());
49952
49279
  }
49953
- if (channel !== null && channel !== void 0 && channel.id) {
49280
+ if (channel !== null && channel !== void 0 && channel.id && !(channel.type === DEFAULT_CHANNEL_TYPE.DIRECT && channel.memberCount === 2)) {
49954
49281
  dispatch(getMembersAC(channel.id));
49955
49282
  }
49956
49283
  }