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