@waveform-playlist/browser 11.0.1 → 11.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -57,14 +57,15 @@ import * as Tone2 from "tone";
57
57
  import {
58
58
  createContext,
59
59
  useContext,
60
- useState as useState14,
60
+ useState as useState15,
61
61
  useEffect as useEffect10,
62
- useRef as useRef14,
63
- useCallback as useCallback18,
62
+ useRef as useRef15,
63
+ useCallback as useCallback19,
64
64
  useMemo as useMemo4
65
65
  } from "react";
66
66
  import { ThemeProvider } from "styled-components";
67
67
  import {
68
+ configureGlobalContext,
68
69
  createToneAdapter,
69
70
  getGlobalAudioContext as getGlobalAudioContext4
70
71
  } from "@waveform-playlist/playout";
@@ -78,11 +79,13 @@ import { getContext as getContext2 } from "tone";
78
79
  import WaveformData from "waveform-data";
79
80
  function loadWaveformData(src) {
80
81
  return __async(this, null, function* () {
82
+ var _a, _b;
81
83
  const response = yield fetch(src);
82
84
  if (!response.ok) {
83
85
  throw new Error(`Failed to fetch waveform data: ${response.statusText}`);
84
86
  }
85
- const isBinary = src.endsWith(".dat");
87
+ const { pathname } = new URL(src, (_b = (_a = globalThis.location) == null ? void 0 : _a.href) != null ? _b : "http://localhost");
88
+ const isBinary = pathname.toLowerCase().endsWith(".dat");
86
89
  if (isBinary) {
87
90
  const arrayBuffer = yield response.arrayBuffer();
88
91
  return WaveformData.create(arrayBuffer);
@@ -451,12 +454,52 @@ function useSelectedTrack({ engineRef }) {
451
454
  };
452
455
  }
453
456
 
457
+ // src/hooks/useUndoState.ts
458
+ import { useState as useState7, useCallback as useCallback6, useRef as useRef6 } from "react";
459
+ function useUndoState({ engineRef }) {
460
+ const [canUndo, setCanUndo] = useState7(false);
461
+ const [canRedo, setCanRedo] = useState7(false);
462
+ const canUndoRef = useRef6(false);
463
+ const canRedoRef = useRef6(false);
464
+ const undo = useCallback6(() => {
465
+ if (!engineRef.current) {
466
+ console.warn("[waveform-playlist] undo: engine not ready, call ignored");
467
+ return;
468
+ }
469
+ engineRef.current.undo();
470
+ }, [engineRef]);
471
+ const redo = useCallback6(() => {
472
+ if (!engineRef.current) {
473
+ console.warn("[waveform-playlist] redo: engine not ready, call ignored");
474
+ return;
475
+ }
476
+ engineRef.current.redo();
477
+ }, [engineRef]);
478
+ const onEngineState = useCallback6((state) => {
479
+ if (state.canUndo !== canUndoRef.current) {
480
+ canUndoRef.current = state.canUndo;
481
+ setCanUndo(state.canUndo);
482
+ }
483
+ if (state.canRedo !== canRedoRef.current) {
484
+ canRedoRef.current = state.canRedo;
485
+ setCanRedo(state.canRedo);
486
+ }
487
+ }, []);
488
+ return {
489
+ canUndo,
490
+ canRedo,
491
+ undo,
492
+ redo,
493
+ onEngineState
494
+ };
495
+ }
496
+
454
497
  // src/hooks/useAudioEffects.ts
455
- import { useRef as useRef6, useCallback as useCallback6 } from "react";
498
+ import { useRef as useRef7, useCallback as useCallback7 } from "react";
456
499
  import { Analyser } from "tone";
457
500
  var useMasterAnalyser = (fftSize = 256) => {
458
- const analyserRef = useRef6(null);
459
- const masterEffects = useCallback6(
501
+ const analyserRef = useRef7(null);
502
+ const masterEffects = useCallback7(
460
503
  (masterGainNode, destination, _isOffline) => {
461
504
  const analyserNode = new Analyser("fft", fftSize);
462
505
  masterGainNode.connect(analyserNode);
@@ -473,7 +516,7 @@ var useMasterAnalyser = (fftSize = 256) => {
473
516
  };
474
517
 
475
518
  // src/hooks/useAudioTracks.ts
476
- import { useState as useState7, useEffect, useRef as useRef7, useMemo } from "react";
519
+ import { useState as useState8, useEffect, useRef as useRef8, useMemo } from "react";
477
520
  import {
478
521
  createTrack,
479
522
  createClipFromSeconds
@@ -534,13 +577,13 @@ function buildTrackFromConfig(config, index, audioBuffer, stableIds, contextSamp
534
577
  function useAudioTracks(configs, options = {}) {
535
578
  const { immediate = false, progressive = false } = options;
536
579
  const isImmediate = immediate || progressive;
537
- const [loading, setLoading] = useState7(true);
538
- const [error, setError] = useState7(null);
539
- const [loadedCount, setLoadedCount] = useState7(0);
580
+ const [loading, setLoading] = useState8(true);
581
+ const [error, setError] = useState8(null);
582
+ const [loadedCount, setLoadedCount] = useState8(0);
540
583
  const totalCount = configs.length;
541
- const [loadedBuffers, setLoadedBuffers] = useState7(/* @__PURE__ */ new Map());
542
- const stableIdsRef = useRef7(/* @__PURE__ */ new Map());
543
- const contextSampleRateRef = useRef7(48e3);
584
+ const [loadedBuffers, setLoadedBuffers] = useState8(/* @__PURE__ */ new Map());
585
+ const stableIdsRef = useRef8(/* @__PURE__ */ new Map());
586
+ const contextSampleRateRef = useRef8(48e3);
544
587
  const derivedTracks = useMemo(() => {
545
588
  if (!isImmediate) return null;
546
589
  const result = [];
@@ -556,8 +599,8 @@ function useAudioTracks(configs, options = {}) {
556
599
  }
557
600
  return result;
558
601
  }, [isImmediate, configs, loadedBuffers]);
559
- const [tracks, setTracks] = useState7(derivedTracks != null ? derivedTracks : []);
560
- const prevDerivedRef = useRef7(derivedTracks);
602
+ const [tracks, setTracks] = useState8(derivedTracks != null ? derivedTracks : []);
603
+ const prevDerivedRef = useRef8(derivedTracks);
561
604
  if (derivedTracks !== prevDerivedRef.current) {
562
605
  prevDerivedRef.current = derivedTracks;
563
606
  if (derivedTracks) setTracks(derivedTracks);
@@ -735,6 +778,13 @@ function useClipDragHandlers({
735
778
  if (!data) return;
736
779
  if (!data.boundary) {
737
780
  originalClipStateRef.current = null;
781
+ if (engineRef.current) {
782
+ engineRef.current.beginTransaction();
783
+ } else {
784
+ console.warn(
785
+ "[waveform-playlist] onDragStart: engine not ready, move will not be grouped for undo"
786
+ );
787
+ }
738
788
  return;
739
789
  }
740
790
  const track = tracks[data.trackIndex];
@@ -746,9 +796,16 @@ function useClipDragHandlers({
746
796
  startSample: clip.startSample
747
797
  };
748
798
  isDraggingRef.current = true;
799
+ if (engineRef.current) {
800
+ engineRef.current.beginTransaction();
801
+ } else {
802
+ console.warn(
803
+ "[waveform-playlist] onDragStart: engine not ready, trim will not be grouped for undo"
804
+ );
805
+ }
749
806
  }
750
807
  },
751
- [tracks, isDraggingRef]
808
+ [tracks, isDraggingRef, engineRef]
752
809
  );
753
810
  const onDragMove = React.useCallback(
754
811
  (event) => {
@@ -814,7 +871,7 @@ function useClipDragHandlers({
814
871
  );
815
872
  const onDragEnd = React.useCallback(
816
873
  (event) => {
817
- var _a, _b, _c;
874
+ var _a, _b, _c, _d, _e, _f, _g;
818
875
  if (event.canceled) {
819
876
  if (originalClipStateRef.current) {
820
877
  const cancelData = (_a = event.operation.source) == null ? void 0 : _a.data;
@@ -839,23 +896,30 @@ function useClipDragHandlers({
839
896
  isDraggingRef.current = false;
840
897
  originalClipStateRef.current = null;
841
898
  lastBoundaryDeltaRef.current = 0;
899
+ (_b = engineRef.current) == null ? void 0 : _b.abortTransaction();
900
+ return;
901
+ }
902
+ const data = (_c = event.operation.source) == null ? void 0 : _c.data;
903
+ if (!data) {
904
+ isDraggingRef.current = false;
905
+ (_d = engineRef.current) == null ? void 0 : _d.abortTransaction();
842
906
  return;
843
907
  }
844
- const data = (_b = event.operation.source) == null ? void 0 : _b.data;
845
- if (!data) return;
846
908
  const { trackIndex, clipId, boundary } = data;
847
909
  const sampleDelta = boundary ? lastBoundaryDeltaRef.current : event.operation.transform.x * samplesPerPixel;
848
- const trackId = (_c = tracks[trackIndex]) == null ? void 0 : _c.id;
910
+ const trackId = (_e = tracks[trackIndex]) == null ? void 0 : _e.id;
849
911
  if (boundary) {
850
912
  isDraggingRef.current = false;
851
913
  if (!trackId) {
852
914
  console.warn(
853
915
  `[waveform-playlist] onDragEnd: track at index ${trackIndex} not found \u2014 trim not synced to adapter`
854
916
  );
917
+ (_f = engineRef.current) == null ? void 0 : _f.abortTransaction();
855
918
  } else if (!engineRef.current) {
856
919
  console.warn("[waveform-playlist] engineRef is null \u2014 trim not synced to adapter");
857
920
  } else {
858
921
  engineRef.current.trimClip(trackId, clipId, boundary, Math.floor(sampleDelta));
922
+ engineRef.current.commitTransaction();
859
923
  }
860
924
  originalClipStateRef.current = null;
861
925
  lastBoundaryDeltaRef.current = 0;
@@ -865,10 +929,12 @@ function useClipDragHandlers({
865
929
  console.warn(
866
930
  `[waveform-playlist] onDragEnd: track at index ${trackIndex} not found \u2014 move not synced to adapter`
867
931
  );
932
+ (_g = engineRef.current) == null ? void 0 : _g.abortTransaction();
868
933
  } else if (!engineRef.current) {
869
934
  console.warn("[waveform-playlist] engineRef is null \u2014 move not synced to adapter");
870
935
  } else {
871
936
  engineRef.current.moveClip(trackId, clipId, Math.floor(sampleDelta));
937
+ engineRef.current.commitTransaction();
872
938
  }
873
939
  },
874
940
  [tracks, onTracksChange, samplesPerPixel, engineRef, isDraggingRef]
@@ -1084,14 +1150,14 @@ function useDragSensors(options = {}) {
1084
1150
  }
1085
1151
 
1086
1152
  // src/hooks/useClipSplitting.ts
1087
- import { useCallback as useCallback7 } from "react";
1153
+ import { useCallback as useCallback8 } from "react";
1088
1154
  import { calculateSplitPoint, canSplitAt } from "@waveform-playlist/engine";
1089
1155
  var useClipSplitting = (options) => {
1090
1156
  const { tracks, engineRef } = options;
1091
1157
  const { sampleRate } = usePlaylistData();
1092
1158
  const { currentTimeRef } = usePlaybackAnimation();
1093
1159
  const { selectedTrackId } = usePlaylistState();
1094
- const splitClipAt = useCallback7(
1160
+ const splitClipAt = useCallback8(
1095
1161
  (trackIndex, clipIndex, splitTime) => {
1096
1162
  const { samplesPerPixel } = options;
1097
1163
  const track = tracks[trackIndex];
@@ -1115,7 +1181,7 @@ var useClipSplitting = (options) => {
1115
1181
  },
1116
1182
  [tracks, options, engineRef, sampleRate]
1117
1183
  );
1118
- const splitClipAtPlayhead = useCallback7(() => {
1184
+ const splitClipAtPlayhead = useCallback8(() => {
1119
1185
  var _a;
1120
1186
  if (!selectedTrackId) {
1121
1187
  console.warn("[waveform-playlist] No track selected \u2014 click a clip to select a track first");
@@ -1146,32 +1212,12 @@ var useClipSplitting = (options) => {
1146
1212
  };
1147
1213
 
1148
1214
  // src/hooks/useKeyboardShortcuts.ts
1149
- import { useEffect as useEffect2, useCallback as useCallback8 } from "react";
1150
- function handleKeyboardEvent(event, shortcuts, enabled) {
1151
- if (!enabled) return;
1152
- if (event.repeat) return;
1153
- const target = event.target;
1154
- if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
1155
- return;
1156
- }
1157
- const matchingShortcut = shortcuts.find((shortcut) => {
1158
- const keyMatch = event.key.toLowerCase() === shortcut.key.toLowerCase() || event.key === shortcut.key;
1159
- const ctrlMatch = shortcut.ctrlKey === void 0 || event.ctrlKey === shortcut.ctrlKey;
1160
- const shiftMatch = shortcut.shiftKey === void 0 || event.shiftKey === shortcut.shiftKey;
1161
- const metaMatch = shortcut.metaKey === void 0 || event.metaKey === shortcut.metaKey;
1162
- const altMatch = shortcut.altKey === void 0 || event.altKey === shortcut.altKey;
1163
- return keyMatch && ctrlMatch && shiftMatch && metaMatch && altMatch;
1164
- });
1165
- if (matchingShortcut) {
1166
- if (matchingShortcut.preventDefault !== false) {
1167
- event.preventDefault();
1168
- }
1169
- matchingShortcut.action();
1170
- }
1171
- }
1215
+ import { useEffect as useEffect2, useCallback as useCallback9 } from "react";
1216
+ import { handleKeyboardEvent } from "@waveform-playlist/core";
1217
+ import { handleKeyboardEvent as handleKeyboardEvent2, getShortcutLabel } from "@waveform-playlist/core";
1172
1218
  var useKeyboardShortcuts = (options) => {
1173
1219
  const { shortcuts, enabled = true } = options;
1174
- const handleKeyDown = useCallback8(
1220
+ const handleKeyDown = useCallback9(
1175
1221
  (event) => handleKeyboardEvent(event, shortcuts, enabled),
1176
1222
  [shortcuts, enabled]
1177
1223
  );
@@ -1183,42 +1229,24 @@ var useKeyboardShortcuts = (options) => {
1183
1229
  };
1184
1230
  }, [handleKeyDown, enabled]);
1185
1231
  };
1186
- var getShortcutLabel = (shortcut) => {
1187
- const parts = [];
1188
- const isMac = typeof navigator !== "undefined" && navigator.platform.includes("Mac");
1189
- if (shortcut.metaKey) {
1190
- parts.push(isMac ? "Cmd" : "Ctrl");
1191
- }
1192
- if (shortcut.ctrlKey && !shortcut.metaKey) {
1193
- parts.push("Ctrl");
1194
- }
1195
- if (shortcut.altKey) {
1196
- parts.push(isMac ? "Option" : "Alt");
1197
- }
1198
- if (shortcut.shiftKey) {
1199
- parts.push("Shift");
1200
- }
1201
- parts.push(shortcut.key.toUpperCase());
1202
- return parts.join("+");
1203
- };
1204
1232
 
1205
1233
  // src/hooks/usePlaybackShortcuts.ts
1206
- import { useCallback as useCallback9 } from "react";
1234
+ import { useCallback as useCallback10 } from "react";
1207
1235
  var usePlaybackShortcuts = (options = {}) => {
1208
1236
  const { enabled = true, additionalShortcuts = [], shortcuts: overrideShortcuts } = options;
1209
1237
  const { isPlaying } = usePlaybackAnimation();
1210
1238
  const { setCurrentTime, play, pause, stop } = usePlaylistControls();
1211
- const togglePlayPause = useCallback9(() => {
1239
+ const togglePlayPause = useCallback10(() => {
1212
1240
  if (isPlaying) {
1213
1241
  pause();
1214
1242
  } else {
1215
1243
  play();
1216
1244
  }
1217
1245
  }, [isPlaying, play, pause]);
1218
- const stopPlayback = useCallback9(() => {
1246
+ const stopPlayback = useCallback10(() => {
1219
1247
  stop();
1220
1248
  }, [stop]);
1221
- const rewindToStart = useCallback9(() => {
1249
+ const rewindToStart = useCallback10(() => {
1222
1250
  setCurrentTime(0);
1223
1251
  if (isPlaying) {
1224
1252
  play(0);
@@ -1258,7 +1286,7 @@ var usePlaybackShortcuts = (options = {}) => {
1258
1286
  };
1259
1287
 
1260
1288
  // src/hooks/useAnnotationKeyboardControls.ts
1261
- import { useCallback as useCallback10, useMemo as useMemo3, useEffect as useEffect3 } from "react";
1289
+ import { useCallback as useCallback11, useMemo as useMemo3, useEffect as useEffect3 } from "react";
1262
1290
  var LINK_THRESHOLD2 = 0.01;
1263
1291
  var TIME_DELTA = 0.01;
1264
1292
  function useAnnotationKeyboardControls({
@@ -1278,7 +1306,7 @@ function useAnnotationKeyboardControls({
1278
1306
  if (!activeAnnotationId) return -1;
1279
1307
  return annotations.findIndex((a) => a.id === activeAnnotationId);
1280
1308
  }, [annotations, activeAnnotationId]);
1281
- const scrollToAnnotation = useCallback10(
1309
+ const scrollToAnnotation = useCallback11(
1282
1310
  (annotationId) => {
1283
1311
  if (!(scrollContainerRef == null ? void 0 : scrollContainerRef.current) || !samplesPerPixel || !sampleRate) return;
1284
1312
  const annotation = annotations.find((a) => a.id === annotationId);
@@ -1306,7 +1334,7 @@ function useAnnotationKeyboardControls({
1306
1334
  scrollToAnnotation(activeAnnotationId);
1307
1335
  }
1308
1336
  }, [activeAnnotationId, scrollToAnnotation, scrollContainerRef, samplesPerPixel, sampleRate]);
1309
- const moveStartBoundary = useCallback10(
1337
+ const moveStartBoundary = useCallback11(
1310
1338
  (delta) => {
1311
1339
  if (activeIndex < 0) return;
1312
1340
  const annotation = annotations[activeIndex];
@@ -1335,7 +1363,7 @@ function useAnnotationKeyboardControls({
1335
1363
  },
1336
1364
  [annotations, activeIndex, linkEndpoints, onAnnotationsChange]
1337
1365
  );
1338
- const moveEndBoundary = useCallback10(
1366
+ const moveEndBoundary = useCallback11(
1339
1367
  (delta) => {
1340
1368
  if (activeIndex < 0) return;
1341
1369
  const annotation = annotations[activeIndex];
@@ -1395,7 +1423,7 @@ function useAnnotationKeyboardControls({
1395
1423
  },
1396
1424
  [annotations, activeIndex, duration, linkEndpoints, onAnnotationsChange]
1397
1425
  );
1398
- const selectPrevious = useCallback10(() => {
1426
+ const selectPrevious = useCallback11(() => {
1399
1427
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1400
1428
  if (activeIndex <= 0) {
1401
1429
  onActiveAnnotationChange(annotations[annotations.length - 1].id);
@@ -1403,7 +1431,7 @@ function useAnnotationKeyboardControls({
1403
1431
  onActiveAnnotationChange(annotations[activeIndex - 1].id);
1404
1432
  }
1405
1433
  }, [annotations, activeIndex, onActiveAnnotationChange]);
1406
- const selectNext = useCallback10(() => {
1434
+ const selectNext = useCallback11(() => {
1407
1435
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1408
1436
  if (activeIndex < 0 || activeIndex >= annotations.length - 1) {
1409
1437
  onActiveAnnotationChange(annotations[0].id);
@@ -1411,19 +1439,19 @@ function useAnnotationKeyboardControls({
1411
1439
  onActiveAnnotationChange(annotations[activeIndex + 1].id);
1412
1440
  }
1413
1441
  }, [annotations, activeIndex, onActiveAnnotationChange]);
1414
- const selectFirst = useCallback10(() => {
1442
+ const selectFirst = useCallback11(() => {
1415
1443
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1416
1444
  onActiveAnnotationChange(annotations[0].id);
1417
1445
  }, [annotations, onActiveAnnotationChange]);
1418
- const selectLast = useCallback10(() => {
1446
+ const selectLast = useCallback11(() => {
1419
1447
  if (!onActiveAnnotationChange || annotations.length === 0) return;
1420
1448
  onActiveAnnotationChange(annotations[annotations.length - 1].id);
1421
1449
  }, [annotations, onActiveAnnotationChange]);
1422
- const clearSelection = useCallback10(() => {
1450
+ const clearSelection = useCallback11(() => {
1423
1451
  if (!onActiveAnnotationChange) return;
1424
1452
  onActiveAnnotationChange(null);
1425
1453
  }, [onActiveAnnotationChange]);
1426
- const playActiveAnnotation = useCallback10(() => {
1454
+ const playActiveAnnotation = useCallback11(() => {
1427
1455
  if (activeIndex < 0 || !onPlay) return;
1428
1456
  const annotation = annotations[activeIndex];
1429
1457
  const playDuration = !continuousPlay ? annotation.end - annotation.start : void 0;
@@ -1535,7 +1563,7 @@ function useAnnotationKeyboardControls({
1535
1563
  }
1536
1564
 
1537
1565
  // src/hooks/useDynamicEffects.ts
1538
- import { useState as useState8, useCallback as useCallback11, useRef as useRef8, useEffect as useEffect4 } from "react";
1566
+ import { useState as useState9, useCallback as useCallback12, useRef as useRef9, useEffect as useEffect4 } from "react";
1539
1567
 
1540
1568
  // src/effects/effectDefinitions.ts
1541
1569
  var effectDefinitions = [
@@ -2231,13 +2259,13 @@ function createEffectChain(effects) {
2231
2259
  // src/hooks/useDynamicEffects.ts
2232
2260
  import { Analyser as Analyser2 } from "tone";
2233
2261
  function useDynamicEffects(fftSize = 256) {
2234
- const [activeEffects, setActiveEffects] = useState8([]);
2235
- const activeEffectsRef = useRef8(activeEffects);
2262
+ const [activeEffects, setActiveEffects] = useState9([]);
2263
+ const activeEffectsRef = useRef9(activeEffects);
2236
2264
  activeEffectsRef.current = activeEffects;
2237
- const effectInstancesRef = useRef8(/* @__PURE__ */ new Map());
2238
- const analyserRef = useRef8(null);
2239
- const graphNodesRef = useRef8(null);
2240
- const rebuildChain = useCallback11((effects) => {
2265
+ const effectInstancesRef = useRef9(/* @__PURE__ */ new Map());
2266
+ const analyserRef = useRef9(null);
2267
+ const graphNodesRef = useRef9(null);
2268
+ const rebuildChain = useCallback12((effects) => {
2241
2269
  const nodes = graphNodesRef.current;
2242
2270
  if (!nodes) return;
2243
2271
  const { masterGainNode, destination, analyserNode } = nodes;
@@ -2265,7 +2293,7 @@ function useDynamicEffects(fftSize = 256) {
2265
2293
  analyserNode.connect(destination);
2266
2294
  }
2267
2295
  }, []);
2268
- const addEffect = useCallback11((effectId) => {
2296
+ const addEffect = useCallback12((effectId) => {
2269
2297
  const definition = getEffectDefinition(effectId);
2270
2298
  if (!definition) {
2271
2299
  console.error(`Unknown effect: ${effectId}`);
@@ -2286,7 +2314,7 @@ function useDynamicEffects(fftSize = 256) {
2286
2314
  };
2287
2315
  setActiveEffects((prev) => [...prev, newActiveEffect]);
2288
2316
  }, []);
2289
- const removeEffect = useCallback11((instanceId) => {
2317
+ const removeEffect = useCallback12((instanceId) => {
2290
2318
  const instance = effectInstancesRef.current.get(instanceId);
2291
2319
  if (instance) {
2292
2320
  instance.dispose();
@@ -2294,7 +2322,7 @@ function useDynamicEffects(fftSize = 256) {
2294
2322
  }
2295
2323
  setActiveEffects((prev) => prev.filter((e) => e.instanceId !== instanceId));
2296
2324
  }, []);
2297
- const updateParameter = useCallback11(
2325
+ const updateParameter = useCallback12(
2298
2326
  (instanceId, paramName, value) => {
2299
2327
  const instance = effectInstancesRef.current.get(instanceId);
2300
2328
  if (instance) {
@@ -2308,7 +2336,7 @@ function useDynamicEffects(fftSize = 256) {
2308
2336
  },
2309
2337
  []
2310
2338
  );
2311
- const toggleBypass = useCallback11((instanceId) => {
2339
+ const toggleBypass = useCallback12((instanceId) => {
2312
2340
  var _a;
2313
2341
  const effect = activeEffectsRef.current.find((e) => e.instanceId === instanceId);
2314
2342
  if (!effect) return;
@@ -2322,7 +2350,7 @@ function useDynamicEffects(fftSize = 256) {
2322
2350
  (prev) => prev.map((e) => e.instanceId === instanceId ? __spreadProps(__spreadValues({}, e), { bypassed: newBypassed }) : e)
2323
2351
  );
2324
2352
  }, []);
2325
- const reorderEffects = useCallback11((fromIndex, toIndex) => {
2353
+ const reorderEffects = useCallback12((fromIndex, toIndex) => {
2326
2354
  setActiveEffects((prev) => {
2327
2355
  const newEffects = [...prev];
2328
2356
  const [removed] = newEffects.splice(fromIndex, 1);
@@ -2330,7 +2358,7 @@ function useDynamicEffects(fftSize = 256) {
2330
2358
  return newEffects;
2331
2359
  });
2332
2360
  }, []);
2333
- const clearAllEffects = useCallback11(() => {
2361
+ const clearAllEffects = useCallback12(() => {
2334
2362
  effectInstancesRef.current.forEach((inst) => inst.dispose());
2335
2363
  effectInstancesRef.current.clear();
2336
2364
  setActiveEffects([]);
@@ -2338,7 +2366,7 @@ function useDynamicEffects(fftSize = 256) {
2338
2366
  useEffect4(() => {
2339
2367
  rebuildChain(activeEffects);
2340
2368
  }, [activeEffects, rebuildChain]);
2341
- const masterEffects = useCallback11(
2369
+ const masterEffects = useCallback12(
2342
2370
  (masterGainNode, destination, _isOffline) => {
2343
2371
  const analyserNode = new Analyser2("fft", fftSize);
2344
2372
  analyserRef.current = analyserNode;
@@ -2377,7 +2405,7 @@ function useDynamicEffects(fftSize = 256) {
2377
2405
  effectInstances.clear();
2378
2406
  };
2379
2407
  }, []);
2380
- const createOfflineEffectsFunction = useCallback11(() => {
2408
+ const createOfflineEffectsFunction = useCallback12(() => {
2381
2409
  const nonBypassedEffects = activeEffects.filter((e) => !e.bypassed);
2382
2410
  if (nonBypassedEffects.length === 0) {
2383
2411
  return void 0;
@@ -2419,14 +2447,14 @@ function useDynamicEffects(fftSize = 256) {
2419
2447
  }
2420
2448
 
2421
2449
  // src/hooks/useTrackDynamicEffects.ts
2422
- import { useState as useState9, useCallback as useCallback12, useRef as useRef9, useEffect as useEffect5 } from "react";
2450
+ import { useState as useState10, useCallback as useCallback13, useRef as useRef10, useEffect as useEffect5 } from "react";
2423
2451
  function useTrackDynamicEffects() {
2424
- const [trackEffectsState, setTrackEffectsState] = useState9(
2452
+ const [trackEffectsState, setTrackEffectsState] = useState10(
2425
2453
  /* @__PURE__ */ new Map()
2426
2454
  );
2427
- const trackEffectInstancesRef = useRef9(/* @__PURE__ */ new Map());
2428
- const trackGraphNodesRef = useRef9(/* @__PURE__ */ new Map());
2429
- const rebuildTrackChain = useCallback12((trackId, trackEffects) => {
2455
+ const trackEffectInstancesRef = useRef10(/* @__PURE__ */ new Map());
2456
+ const trackGraphNodesRef = useRef10(/* @__PURE__ */ new Map());
2457
+ const rebuildTrackChain = useCallback13((trackId, trackEffects) => {
2430
2458
  const nodes = trackGraphNodesRef.current.get(trackId);
2431
2459
  if (!nodes) return;
2432
2460
  const { graphEnd, masterGainNode } = nodes;
@@ -2456,7 +2484,7 @@ function useTrackDynamicEffects() {
2456
2484
  currentNode.connect(masterGainNode);
2457
2485
  }
2458
2486
  }, []);
2459
- const addEffectToTrack = useCallback12((trackId, effectId) => {
2487
+ const addEffectToTrack = useCallback13((trackId, effectId) => {
2460
2488
  const definition = getEffectDefinition(effectId);
2461
2489
  if (!definition) {
2462
2490
  console.error(`Unknown effect: ${effectId}`);
@@ -2485,7 +2513,7 @@ function useTrackDynamicEffects() {
2485
2513
  return newState;
2486
2514
  });
2487
2515
  }, []);
2488
- const removeEffectFromTrack = useCallback12((trackId, instanceId) => {
2516
+ const removeEffectFromTrack = useCallback13((trackId, instanceId) => {
2489
2517
  const instancesMap = trackEffectInstancesRef.current.get(trackId);
2490
2518
  const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
2491
2519
  if (instance) {
@@ -2502,7 +2530,7 @@ function useTrackDynamicEffects() {
2502
2530
  return newState;
2503
2531
  });
2504
2532
  }, []);
2505
- const updateTrackEffectParameter = useCallback12(
2533
+ const updateTrackEffectParameter = useCallback13(
2506
2534
  (trackId, instanceId, paramName, value) => {
2507
2535
  const instancesMap = trackEffectInstancesRef.current.get(trackId);
2508
2536
  const instance = instancesMap == null ? void 0 : instancesMap.get(instanceId);
@@ -2523,7 +2551,7 @@ function useTrackDynamicEffects() {
2523
2551
  },
2524
2552
  []
2525
2553
  );
2526
- const toggleBypass = useCallback12((trackId, instanceId) => {
2554
+ const toggleBypass = useCallback13((trackId, instanceId) => {
2527
2555
  var _a;
2528
2556
  const trackEffects = trackEffectsStateRef.current.get(trackId) || [];
2529
2557
  const effect = trackEffects.find((e) => e.instanceId === instanceId);
@@ -2545,7 +2573,7 @@ function useTrackDynamicEffects() {
2545
2573
  return newState;
2546
2574
  });
2547
2575
  }, []);
2548
- const clearTrackEffects = useCallback12((trackId) => {
2576
+ const clearTrackEffects = useCallback13((trackId) => {
2549
2577
  const instancesMap = trackEffectInstancesRef.current.get(trackId);
2550
2578
  if (instancesMap) {
2551
2579
  instancesMap.forEach((inst) => inst.dispose());
@@ -2557,9 +2585,9 @@ function useTrackDynamicEffects() {
2557
2585
  return newState;
2558
2586
  });
2559
2587
  }, []);
2560
- const trackEffectsStateRef = useRef9(trackEffectsState);
2588
+ const trackEffectsStateRef = useRef10(trackEffectsState);
2561
2589
  trackEffectsStateRef.current = trackEffectsState;
2562
- const getTrackEffectsFunction = useCallback12(
2590
+ const getTrackEffectsFunction = useCallback13(
2563
2591
  (trackId) => {
2564
2592
  return (graphEnd, masterGainNode, _isOffline) => {
2565
2593
  trackGraphNodesRef.current.set(trackId, {
@@ -2602,7 +2630,7 @@ function useTrackDynamicEffects() {
2602
2630
  trackEffectInstances.clear();
2603
2631
  };
2604
2632
  }, []);
2605
- const createOfflineTrackEffectsFunction = useCallback12(
2633
+ const createOfflineTrackEffectsFunction = useCallback13(
2606
2634
  (trackId) => {
2607
2635
  const trackEffects = trackEffectsState.get(trackId) || [];
2608
2636
  const nonBypassedEffects = trackEffects.filter((e) => !e.bypassed);
@@ -2646,7 +2674,7 @@ function useTrackDynamicEffects() {
2646
2674
  }
2647
2675
 
2648
2676
  // src/hooks/useExportWav.ts
2649
- import { useState as useState10, useCallback as useCallback13 } from "react";
2677
+ import { useState as useState11, useCallback as useCallback14 } from "react";
2650
2678
  import {
2651
2679
  getUnderlyingAudioParam,
2652
2680
  getGlobalAudioContext as getGlobalAudioContext2
@@ -2723,10 +2751,10 @@ function downloadBlob(blob, filename) {
2723
2751
 
2724
2752
  // src/hooks/useExportWav.ts
2725
2753
  function useExportWav() {
2726
- const [isExporting, setIsExporting] = useState10(false);
2727
- const [progress, setProgress] = useState10(0);
2728
- const [error, setError] = useState10(null);
2729
- const exportWav = useCallback13(
2754
+ const [isExporting, setIsExporting] = useState11(false);
2755
+ const [progress, setProgress] = useState11(0);
2756
+ const [error, setError] = useState11(null);
2757
+ const exportWav = useCallback14(
2730
2758
  (_0, _1, ..._2) => __async(null, [_0, _1, ..._2], function* (tracks, trackStates, options = {}) {
2731
2759
  const {
2732
2760
  filename = "export",
@@ -3038,16 +3066,16 @@ function generateFadeCurve(startValue, endValue, numPoints, curveType) {
3038
3066
  }
3039
3067
 
3040
3068
  // src/hooks/useAnimationFrameLoop.ts
3041
- import { useCallback as useCallback14, useEffect as useEffect6, useRef as useRef10 } from "react";
3069
+ import { useCallback as useCallback15, useEffect as useEffect6, useRef as useRef11 } from "react";
3042
3070
  var useAnimationFrameLoop = () => {
3043
- const animationFrameRef = useRef10(null);
3044
- const stopAnimationFrameLoop = useCallback14(() => {
3071
+ const animationFrameRef = useRef11(null);
3072
+ const stopAnimationFrameLoop = useCallback15(() => {
3045
3073
  if (animationFrameRef.current !== null) {
3046
3074
  cancelAnimationFrame(animationFrameRef.current);
3047
3075
  animationFrameRef.current = null;
3048
3076
  }
3049
3077
  }, []);
3050
- const startAnimationFrameLoop = useCallback14(
3078
+ const startAnimationFrameLoop = useCallback15(
3051
3079
  (callback) => {
3052
3080
  stopAnimationFrameLoop();
3053
3081
  animationFrameRef.current = requestAnimationFrame(callback);
@@ -3067,7 +3095,7 @@ var useAnimationFrameLoop = () => {
3067
3095
  };
3068
3096
 
3069
3097
  // src/hooks/useWaveformDataCache.ts
3070
- import { useState as useState11, useEffect as useEffect7, useRef as useRef11, useCallback as useCallback15 } from "react";
3098
+ import { useState as useState12, useEffect as useEffect7, useRef as useRef12, useCallback as useCallback16 } from "react";
3071
3099
 
3072
3100
  // src/workers/peaksWorker.ts
3073
3101
  import WaveformData2 from "waveform-data";
@@ -3299,14 +3327,14 @@ function createPeaksWorker() {
3299
3327
 
3300
3328
  // src/hooks/useWaveformDataCache.ts
3301
3329
  function useWaveformDataCache(tracks, baseScale) {
3302
- const [cache, setCache] = useState11(() => /* @__PURE__ */ new Map());
3303
- const [isGenerating, setIsGenerating] = useState11(false);
3304
- const workerRef = useRef11(null);
3305
- const generatedByBufferRef = useRef11(/* @__PURE__ */ new WeakMap());
3306
- const inflightByBufferRef = useRef11(/* @__PURE__ */ new WeakMap());
3307
- const subscribersByBufferRef = useRef11(/* @__PURE__ */ new WeakMap());
3308
- const pendingCountRef = useRef11(0);
3309
- const getWorker = useCallback15(() => {
3330
+ const [cache, setCache] = useState12(() => /* @__PURE__ */ new Map());
3331
+ const [isGenerating, setIsGenerating] = useState12(false);
3332
+ const workerRef = useRef12(null);
3333
+ const generatedByBufferRef = useRef12(/* @__PURE__ */ new WeakMap());
3334
+ const inflightByBufferRef = useRef12(/* @__PURE__ */ new WeakMap());
3335
+ const subscribersByBufferRef = useRef12(/* @__PURE__ */ new WeakMap());
3336
+ const pendingCountRef = useRef12(0);
3337
+ const getWorker = useCallback16(() => {
3310
3338
  if (!workerRef.current) {
3311
3339
  workerRef.current = createPeaksWorker();
3312
3340
  }
@@ -3428,7 +3456,7 @@ function useWaveformDataCache(tracks, baseScale) {
3428
3456
  }
3429
3457
 
3430
3458
  // src/hooks/useDynamicTracks.ts
3431
- import { useState as useState12, useCallback as useCallback16, useRef as useRef12, useEffect as useEffect8 } from "react";
3459
+ import { useState as useState13, useCallback as useCallback17, useRef as useRef13, useEffect as useEffect8 } from "react";
3432
3460
  import { createTrack as createTrack2, createClipFromSeconds as createClipFromSeconds2 } from "@waveform-playlist/core";
3433
3461
  import { getGlobalAudioContext as getGlobalAudioContext3 } from "@waveform-playlist/playout";
3434
3462
  function getSourceName(source) {
@@ -3463,12 +3491,12 @@ function decodeSource(source, audioContext, signal) {
3463
3491
  });
3464
3492
  }
3465
3493
  function useDynamicTracks() {
3466
- const [tracks, setTracks] = useState12([]);
3467
- const [loadingCount, setLoadingCount] = useState12(0);
3468
- const [errors, setErrors] = useState12([]);
3469
- const cancelledRef = useRef12(false);
3470
- const loadingIdsRef = useRef12(/* @__PURE__ */ new Set());
3471
- const abortControllersRef = useRef12(/* @__PURE__ */ new Map());
3494
+ const [tracks, setTracks] = useState13([]);
3495
+ const [loadingCount, setLoadingCount] = useState13(0);
3496
+ const [errors, setErrors] = useState13([]);
3497
+ const cancelledRef = useRef13(false);
3498
+ const loadingIdsRef = useRef13(/* @__PURE__ */ new Set());
3499
+ const abortControllersRef = useRef13(/* @__PURE__ */ new Map());
3472
3500
  useEffect8(() => {
3473
3501
  const controllers = abortControllersRef.current;
3474
3502
  return () => {
@@ -3479,7 +3507,7 @@ function useDynamicTracks() {
3479
3507
  controllers.clear();
3480
3508
  };
3481
3509
  }, []);
3482
- const addTracks = useCallback16((sources) => {
3510
+ const addTracks = useCallback17((sources) => {
3483
3511
  if (sources.length === 0) return;
3484
3512
  const audioContext = getGlobalAudioContext3();
3485
3513
  const placeholders = sources.map((source) => ({
@@ -3529,7 +3557,7 @@ function useDynamicTracks() {
3529
3557
  }))();
3530
3558
  }
3531
3559
  }, []);
3532
- const removeTrack = useCallback16((trackId) => {
3560
+ const removeTrack = useCallback17((trackId) => {
3533
3561
  setTracks((prev) => prev.filter((t) => t.id !== trackId));
3534
3562
  const controller = abortControllersRef.current.get(trackId);
3535
3563
  if (controller) {
@@ -3551,20 +3579,20 @@ function useDynamicTracks() {
3551
3579
  }
3552
3580
 
3553
3581
  // src/hooks/useOutputMeter.ts
3554
- import { useEffect as useEffect9, useState as useState13, useRef as useRef13, useCallback as useCallback17 } from "react";
3582
+ import { useEffect as useEffect9, useState as useState14, useRef as useRef14, useCallback as useCallback18 } from "react";
3555
3583
  import { getGlobalContext } from "@waveform-playlist/playout";
3556
3584
  import { gainToNormalized } from "@waveform-playlist/core";
3557
3585
  import { meterProcessorUrl } from "@waveform-playlist/worklets";
3558
3586
  var PEAK_DECAY = 0.98;
3559
3587
  function useOutputMeter(options = {}) {
3560
3588
  const { channelCount = 2, updateRate = 60, isPlaying = false } = options;
3561
- const [levels, setLevels] = useState13(() => new Array(channelCount).fill(0));
3562
- const [peakLevels, setPeakLevels] = useState13(() => new Array(channelCount).fill(0));
3563
- const [rmsLevels, setRmsLevels] = useState13(() => new Array(channelCount).fill(0));
3564
- const workletNodeRef = useRef13(null);
3565
- const smoothedPeakRef = useRef13(new Array(channelCount).fill(0));
3566
- const [meterError, setMeterError] = useState13(null);
3567
- const resetPeak = useCallback17(
3589
+ const [levels, setLevels] = useState14(() => new Array(channelCount).fill(0));
3590
+ const [peakLevels, setPeakLevels] = useState14(() => new Array(channelCount).fill(0));
3591
+ const [rmsLevels, setRmsLevels] = useState14(() => new Array(channelCount).fill(0));
3592
+ const workletNodeRef = useRef14(null);
3593
+ const smoothedPeakRef = useRef14(new Array(channelCount).fill(0));
3594
+ const [meterError, setMeterError] = useState14(null);
3595
+ const resetPeak = useCallback18(
3568
3596
  () => setPeakLevels(new Array(channelCount).fill(0)),
3569
3597
  [channelCount]
3570
3598
  );
@@ -3675,11 +3703,12 @@ var WaveformPlaylistProvider = ({
3675
3703
  soundFontCache,
3676
3704
  deferEngineRebuild = false,
3677
3705
  indefinitePlayback = false,
3706
+ sampleRate: sampleRateProp,
3678
3707
  children
3679
3708
  }) => {
3680
3709
  var _a, _b, _c, _d;
3681
3710
  const progressBarWidth = progressBarWidthProp != null ? progressBarWidthProp : barWidth + barGap;
3682
- const indefinitePlaybackRef = useRef14(indefinitePlayback);
3711
+ const indefinitePlaybackRef = useRef15(indefinitePlayback);
3683
3712
  indefinitePlaybackRef.current = indefinitePlayback;
3684
3713
  const stableZoomLevels = useMemo4(
3685
3714
  () => zoomLevels,
@@ -3699,48 +3728,60 @@ var WaveformPlaylistProvider = ({
3699
3728
  }
3700
3729
  return annotationList.annotations;
3701
3730
  }, [annotationList == null ? void 0 : annotationList.annotations]);
3702
- const annotationsRef = useRef14(annotations);
3731
+ const annotationsRef = useRef15(annotations);
3703
3732
  annotationsRef.current = annotations;
3704
- const [activeAnnotationId, setActiveAnnotationIdState] = useState14(null);
3705
- const [isPlaying, setIsPlaying] = useState14(false);
3706
- const [currentTime, setCurrentTime] = useState14(0);
3707
- const [duration, setDuration] = useState14(0);
3708
- const [audioBuffers, setAudioBuffers] = useState14([]);
3709
- const [peaksDataArray, setPeaksDataArray] = useState14([]);
3710
- const [trackStates, setTrackStates] = useState14([]);
3711
- const [isAutomaticScroll, setIsAutomaticScroll] = useState14(automaticScroll);
3712
- const [continuousPlay, setContinuousPlayState] = useState14(
3733
+ const [activeAnnotationId, setActiveAnnotationIdState] = useState15(null);
3734
+ const [isPlaying, setIsPlaying] = useState15(false);
3735
+ const [currentTime, setCurrentTime] = useState15(0);
3736
+ const [duration, setDuration] = useState15(0);
3737
+ const [audioBuffers, setAudioBuffers] = useState15([]);
3738
+ const [peaksDataArray, setPeaksDataArray] = useState15([]);
3739
+ const [trackStates, setTrackStates] = useState15([]);
3740
+ const [isAutomaticScroll, setIsAutomaticScroll] = useState15(automaticScroll);
3741
+ const [continuousPlay, setContinuousPlayState] = useState15(
3713
3742
  (_a = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _a : false
3714
3743
  );
3715
- const [linkEndpoints, setLinkEndpoints] = useState14((_b = annotationList == null ? void 0 : annotationList.linkEndpoints) != null ? _b : false);
3716
- const [annotationsEditable, setAnnotationsEditable] = useState14((_c = annotationList == null ? void 0 : annotationList.editable) != null ? _c : false);
3717
- const [isReady, setIsReady] = useState14(false);
3718
- const engineRef = useRef14(null);
3719
- const audioInitializedRef = useRef14(false);
3720
- const isPlayingRef = useRef14(false);
3744
+ const [linkEndpoints, setLinkEndpoints] = useState15((_b = annotationList == null ? void 0 : annotationList.linkEndpoints) != null ? _b : false);
3745
+ const [annotationsEditable, setAnnotationsEditable] = useState15((_c = annotationList == null ? void 0 : annotationList.editable) != null ? _c : false);
3746
+ const [isReady, setIsReady] = useState15(false);
3747
+ const engineRef = useRef15(null);
3748
+ const audioInitializedRef = useRef15(false);
3749
+ const isPlayingRef = useRef15(false);
3721
3750
  isPlayingRef.current = isPlaying;
3722
- const playStartPositionRef = useRef14(0);
3723
- const currentTimeRef = useRef14(0);
3724
- const tracksRef = useRef14(tracks);
3725
- const soundFontCacheRef = useRef14(soundFontCache);
3751
+ const playStartPositionRef = useRef15(0);
3752
+ const currentTimeRef = useRef15(0);
3753
+ const tracksRef = useRef15(tracks);
3754
+ const soundFontCacheRef = useRef15(soundFontCache);
3726
3755
  soundFontCacheRef.current = soundFontCache;
3727
- const trackStatesRef = useRef14(trackStates);
3728
- const playbackStartTimeRef = useRef14(0);
3729
- const audioStartPositionRef = useRef14(0);
3730
- const playbackEndTimeRef = useRef14(null);
3731
- const scrollContainerRef = useRef14(null);
3732
- const isAutomaticScrollRef = useRef14(false);
3733
- const continuousPlayRef = useRef14((_d = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _d : false);
3734
- const activeAnnotationIdRef = useRef14(null);
3735
- const engineTracksRef = useRef14(null);
3736
- const lastTracksVersionRef = useRef14(0);
3737
- const skipEngineDisposeRef = useRef14(false);
3738
- const isDraggingRef = useRef14(false);
3739
- const prevTracksRef = useRef14([]);
3740
- const samplesPerPixelRef = useRef14(initialSamplesPerPixel);
3741
- const sampleRateRef = useRef14(
3742
- typeof AudioContext !== "undefined" ? getGlobalAudioContext4().sampleRate : 48e3
3743
- );
3756
+ const trackStatesRef = useRef15(trackStates);
3757
+ const playbackStartTimeRef = useRef15(0);
3758
+ const audioStartPositionRef = useRef15(0);
3759
+ const playbackEndTimeRef = useRef15(null);
3760
+ const scrollContainerRef = useRef15(null);
3761
+ const isAutomaticScrollRef = useRef15(false);
3762
+ const continuousPlayRef = useRef15((_d = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _d : false);
3763
+ const activeAnnotationIdRef = useRef15(null);
3764
+ const engineTracksRef = useRef15(null);
3765
+ const lastTracksVersionRef = useRef15(0);
3766
+ const skipEngineDisposeRef = useRef15(false);
3767
+ const isDraggingRef = useRef15(false);
3768
+ const prevTracksRef = useRef15([]);
3769
+ const samplesPerPixelRef = useRef15(initialSamplesPerPixel);
3770
+ const [initialSampleRate] = useState15(() => {
3771
+ if (typeof AudioContext === "undefined") return sampleRateProp != null ? sampleRateProp : 48e3;
3772
+ try {
3773
+ if (sampleRateProp !== void 0) {
3774
+ return configureGlobalContext({ sampleRate: sampleRateProp });
3775
+ }
3776
+ return getGlobalAudioContext4().sampleRate;
3777
+ } catch (err) {
3778
+ console.warn(
3779
+ "[waveform-playlist] Failed to configure AudioContext: " + String(err) + " \u2014 falling back to " + (sampleRateProp != null ? sampleRateProp : 48e3) + " Hz"
3780
+ );
3781
+ return sampleRateProp != null ? sampleRateProp : 48e3;
3782
+ }
3783
+ });
3784
+ const sampleRateRef = useRef15(initialSampleRate);
3744
3785
  const { timeFormat, setTimeFormat, formatTime: formatTime2 } = useTimeFormat();
3745
3786
  const zoom = useZoomControls({ engineRef, initialSamplesPerPixel });
3746
3787
  const { samplesPerPixel, onEngineState: onZoomEngineState } = zoom;
@@ -3777,21 +3818,28 @@ var WaveformPlaylistProvider = ({
3777
3818
  onEngineState: onSelectedTrackEngineState,
3778
3819
  selectedTrackIdRef
3779
3820
  } = useSelectedTrack({ engineRef });
3821
+ const {
3822
+ canUndo,
3823
+ canRedo,
3824
+ undo,
3825
+ redo,
3826
+ onEngineState: onUndoEngineState
3827
+ } = useUndoState({ engineRef });
3780
3828
  const { animationFrameRef, startAnimationFrameLoop, stopAnimationFrameLoop } = useAnimationFrameLoop();
3781
3829
  const baseScale = useMemo4(
3782
3830
  () => Math.min(...stableZoomLevels != null ? stableZoomLevels : [256, 512, 1024, 2048, 4096, 8192]),
3783
3831
  [stableZoomLevels]
3784
3832
  );
3785
3833
  const { cache: waveformDataCache } = useWaveformDataCache(tracks, baseScale);
3786
- const setContinuousPlay = useCallback18((value) => {
3834
+ const setContinuousPlay = useCallback19((value) => {
3787
3835
  continuousPlayRef.current = value;
3788
3836
  setContinuousPlayState(value);
3789
3837
  }, []);
3790
- const setActiveAnnotationId = useCallback18((value) => {
3838
+ const setActiveAnnotationId = useCallback19((value) => {
3791
3839
  activeAnnotationIdRef.current = value;
3792
3840
  setActiveAnnotationIdState(value);
3793
3841
  }, []);
3794
- const setLoopRegionFromSelection = useCallback18(() => {
3842
+ const setLoopRegionFromSelection = useCallback19(() => {
3795
3843
  var _a2, _b2;
3796
3844
  const start = (_a2 = selectionStartRef.current) != null ? _a2 : 0;
3797
3845
  const end = (_b2 = selectionEndRef.current) != null ? _b2 : 0;
@@ -3829,7 +3877,7 @@ var WaveformPlaylistProvider = ({
3829
3877
  container.scrollLeft = newScrollLeft;
3830
3878
  samplesPerPixelRef.current = newSamplesPerPixel;
3831
3879
  }, [samplesPerPixel, duration]);
3832
- const pendingResumeRef = useRef14(null);
3880
+ const pendingResumeRef = useRef15(null);
3833
3881
  useEffect10(() => {
3834
3882
  var _a2, _b2, _c2, _d2;
3835
3883
  if (isEngineTracks || isDraggingRef.current) {
@@ -4005,6 +4053,7 @@ var WaveformPlaylistProvider = ({
4005
4053
  onSelectedTrackEngineState(state);
4006
4054
  onZoomEngineState(state);
4007
4055
  onVolumeEngineState(state);
4056
+ onUndoEngineState(state);
4008
4057
  if (!suppressTracksMirroring && state.tracksVersion !== lastTracksVersionRef.current) {
4009
4058
  lastTracksVersionRef.current = state.tracksVersion;
4010
4059
  engineTracksRef.current = state.tracks;
@@ -4062,6 +4111,7 @@ var WaveformPlaylistProvider = ({
4062
4111
  onSelectedTrackEngineState,
4063
4112
  onZoomEngineState,
4064
4113
  onVolumeEngineState,
4114
+ onUndoEngineState,
4065
4115
  onTracksChange,
4066
4116
  masterVolumeRef,
4067
4117
  selectionStartRef,
@@ -4081,15 +4131,28 @@ var WaveformPlaylistProvider = ({
4081
4131
  let peaks;
4082
4132
  if (clip.waveformData) {
4083
4133
  try {
4134
+ const wdRate = clip.waveformData.sample_rate;
4135
+ const clipRate = clip.sampleRate;
4136
+ let peakOffset = clip.offsetSamples;
4137
+ let peakDuration = clip.durationSamples;
4138
+ let peakSpp = samplesPerPixel;
4139
+ if (wdRate !== clipRate && clipRate > 0 && wdRate > 0) {
4140
+ const ratio = wdRate / clipRate;
4141
+ peakOffset = Math.round(clip.offsetSamples * ratio);
4142
+ peakDuration = Math.round(clip.durationSamples * ratio);
4143
+ peakSpp = Math.max(1, Math.round(samplesPerPixel * ratio));
4144
+ }
4084
4145
  peaks = extractPeaksFromWaveformDataFull(
4085
4146
  clip.waveformData,
4086
- samplesPerPixel,
4147
+ peakSpp,
4087
4148
  mono,
4088
- clip.offsetSamples,
4089
- clip.durationSamples
4149
+ peakOffset,
4150
+ peakDuration
4090
4151
  );
4091
4152
  } catch (err) {
4092
- console.warn("[waveform-playlist] Failed to extract peaks from waveformData:", err);
4153
+ console.warn(
4154
+ "[waveform-playlist] Failed to extract peaks from waveformData: " + String(err)
4155
+ );
4093
4156
  }
4094
4157
  }
4095
4158
  if (!peaks) {
@@ -4104,7 +4167,9 @@ var WaveformPlaylistProvider = ({
4104
4167
  clip.durationSamples
4105
4168
  );
4106
4169
  } catch (err) {
4107
- console.warn("[waveform-playlist] Failed to extract peaks from cache:", err);
4170
+ console.warn(
4171
+ "[waveform-playlist] Failed to extract peaks from cache: " + String(err)
4172
+ );
4108
4173
  }
4109
4174
  }
4110
4175
  }
@@ -4139,8 +4204,8 @@ var WaveformPlaylistProvider = ({
4139
4204
  });
4140
4205
  setPeaksDataArray(allTrackPeaks);
4141
4206
  }, [tracks, samplesPerPixel, mono, waveformDataCache, deferEngineRebuild]);
4142
- const getPlaybackTimeFallbackWarnedRef = useRef14(false);
4143
- const getPlaybackTime = useCallback18(() => {
4207
+ const getPlaybackTimeFallbackWarnedRef = useRef15(false);
4208
+ const getPlaybackTime = useCallback19(() => {
4144
4209
  var _a2, _b2;
4145
4210
  if (engineRef.current) {
4146
4211
  return engineRef.current.getCurrentTime();
@@ -4154,7 +4219,7 @@ var WaveformPlaylistProvider = ({
4154
4219
  const elapsed = getContext2().currentTime - ((_a2 = playbackStartTimeRef.current) != null ? _a2 : 0);
4155
4220
  return ((_b2 = audioStartPositionRef.current) != null ? _b2 : 0) + elapsed;
4156
4221
  }, []);
4157
- const startAnimationLoop = useCallback18(() => {
4222
+ const startAnimationLoop = useCallback19(() => {
4158
4223
  const updateTime = () => {
4159
4224
  const time = getPlaybackTime();
4160
4225
  currentTimeRef.current = time;
@@ -4272,7 +4337,7 @@ var WaveformPlaylistProvider = ({
4272
4337
  stopAnimationLoop();
4273
4338
  });
4274
4339
  }, [tracks, startAnimationLoop, stopAnimationLoop]);
4275
- const play = useCallback18(
4340
+ const play = useCallback19(
4276
4341
  (startTime, playDuration) => __async(null, null, function* () {
4277
4342
  if (!engineRef.current) return;
4278
4343
  const actualStartTime = startTime != null ? startTime : currentTimeRef.current;
@@ -4303,7 +4368,7 @@ var WaveformPlaylistProvider = ({
4303
4368
  }),
4304
4369
  [startAnimationLoop, stopAnimationLoop]
4305
4370
  );
4306
- const pause = useCallback18(() => {
4371
+ const pause = useCallback19(() => {
4307
4372
  if (!engineRef.current) return;
4308
4373
  const pauseTime = getPlaybackTime();
4309
4374
  engineRef.current.pause();
@@ -4312,7 +4377,7 @@ var WaveformPlaylistProvider = ({
4312
4377
  currentTimeRef.current = pauseTime;
4313
4378
  setCurrentTime(pauseTime);
4314
4379
  }, [stopAnimationLoop, getPlaybackTime]);
4315
- const stop = useCallback18(() => {
4380
+ const stop = useCallback19(() => {
4316
4381
  if (!engineRef.current) return;
4317
4382
  engineRef.current.stop();
4318
4383
  setIsPlaying(false);
@@ -4321,7 +4386,7 @@ var WaveformPlaylistProvider = ({
4321
4386
  setCurrentTime(playStartPositionRef.current);
4322
4387
  setActiveAnnotationId(null);
4323
4388
  }, [stopAnimationLoop, setActiveAnnotationId]);
4324
- const seekTo = useCallback18(
4389
+ const seekTo = useCallback19(
4325
4390
  (time) => {
4326
4391
  const clampedTime = Math.max(0, Math.min(time, duration));
4327
4392
  currentTimeRef.current = clampedTime;
@@ -4332,7 +4397,7 @@ var WaveformPlaylistProvider = ({
4332
4397
  },
4333
4398
  [duration, isPlaying, play]
4334
4399
  );
4335
- const setTrackMute = useCallback18(
4400
+ const setTrackMute = useCallback19(
4336
4401
  (trackIndex, muted) => {
4337
4402
  var _a2;
4338
4403
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4346,7 +4411,7 @@ var WaveformPlaylistProvider = ({
4346
4411
  },
4347
4412
  [trackStates]
4348
4413
  );
4349
- const setTrackSolo = useCallback18(
4414
+ const setTrackSolo = useCallback19(
4350
4415
  (trackIndex, soloed) => {
4351
4416
  var _a2;
4352
4417
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4360,7 +4425,7 @@ var WaveformPlaylistProvider = ({
4360
4425
  },
4361
4426
  [trackStates]
4362
4427
  );
4363
- const setTrackVolume = useCallback18(
4428
+ const setTrackVolume = useCallback19(
4364
4429
  (trackIndex, volume2) => {
4365
4430
  var _a2;
4366
4431
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4374,7 +4439,7 @@ var WaveformPlaylistProvider = ({
4374
4439
  },
4375
4440
  [trackStates]
4376
4441
  );
4377
- const setTrackPan = useCallback18(
4442
+ const setTrackPan = useCallback19(
4378
4443
  (trackIndex, pan) => {
4379
4444
  var _a2;
4380
4445
  const trackId = (_a2 = tracksRef.current[trackIndex]) == null ? void 0 : _a2.id;
@@ -4388,7 +4453,7 @@ var WaveformPlaylistProvider = ({
4388
4453
  },
4389
4454
  [trackStates]
4390
4455
  );
4391
- const setSelection = useCallback18(
4456
+ const setSelection = useCallback19(
4392
4457
  (start, end) => {
4393
4458
  setSelectionEngine(start, end);
4394
4459
  currentTimeRef.current = start;
@@ -4401,12 +4466,12 @@ var WaveformPlaylistProvider = ({
4401
4466
  },
4402
4467
  [isPlaying, setSelectionEngine]
4403
4468
  );
4404
- const setScrollContainer = useCallback18((element) => {
4469
+ const setScrollContainer = useCallback19((element) => {
4405
4470
  scrollContainerRef.current = element;
4406
4471
  }, []);
4407
- const onAnnotationsChangeRef = useRef14(onAnnotationsChange);
4472
+ const onAnnotationsChangeRef = useRef15(onAnnotationsChange);
4408
4473
  onAnnotationsChangeRef.current = onAnnotationsChange;
4409
- const setAnnotations = useCallback18(
4474
+ const setAnnotations = useCallback19(
4410
4475
  (action) => {
4411
4476
  const updated = typeof action === "function" ? action(annotationsRef.current) : action;
4412
4477
  if (!onAnnotationsChangeRef.current) {
@@ -4456,7 +4521,9 @@ var WaveformPlaylistProvider = ({
4456
4521
  selectedTrackId,
4457
4522
  loopStart,
4458
4523
  loopEnd,
4459
- indefinitePlayback
4524
+ indefinitePlayback,
4525
+ canUndo,
4526
+ canRedo
4460
4527
  }),
4461
4528
  [
4462
4529
  continuousPlay,
@@ -4471,17 +4538,19 @@ var WaveformPlaylistProvider = ({
4471
4538
  selectedTrackId,
4472
4539
  loopStart,
4473
4540
  loopEnd,
4474
- indefinitePlayback
4541
+ indefinitePlayback,
4542
+ canUndo,
4543
+ canRedo
4475
4544
  ]
4476
4545
  );
4477
- const setCurrentTimeControl = useCallback18(
4546
+ const setCurrentTimeControl = useCallback19(
4478
4547
  (time) => {
4479
4548
  currentTimeRef.current = time;
4480
4549
  setCurrentTime(time);
4481
4550
  },
4482
4551
  [currentTimeRef]
4483
4552
  );
4484
- const setAutomaticScrollControl = useCallback18((enabled) => {
4553
+ const setAutomaticScrollControl = useCallback19((enabled) => {
4485
4554
  setIsAutomaticScroll(enabled);
4486
4555
  }, []);
4487
4556
  const controlsValue = useMemo4(
@@ -4522,7 +4591,10 @@ var WaveformPlaylistProvider = ({
4522
4591
  setLoopEnabled,
4523
4592
  setLoopRegion,
4524
4593
  setLoopRegionFromSelection,
4525
- clearLoopRegion
4594
+ clearLoopRegion,
4595
+ // Undo/redo
4596
+ undo,
4597
+ redo
4526
4598
  }),
4527
4599
  [
4528
4600
  play,
@@ -4552,7 +4624,9 @@ var WaveformPlaylistProvider = ({
4552
4624
  setLoopEnabled,
4553
4625
  setLoopRegion,
4554
4626
  setLoopRegionFromSelection,
4555
- clearLoopRegion
4627
+ clearLoopRegion,
4628
+ undo,
4629
+ redo
4556
4630
  ]
4557
4631
  );
4558
4632
  const dataValue = useMemo4(
@@ -4643,10 +4717,10 @@ var usePlaylistData = () => {
4643
4717
  import {
4644
4718
  createContext as createContext2,
4645
4719
  useContext as useContext2,
4646
- useState as useState15,
4720
+ useState as useState16,
4647
4721
  useEffect as useEffect11,
4648
- useRef as useRef15,
4649
- useCallback as useCallback19,
4722
+ useRef as useRef16,
4723
+ useCallback as useCallback20,
4650
4724
  useMemo as useMemo5
4651
4725
  } from "react";
4652
4726
  import { ThemeProvider as ThemeProvider2 } from "styled-components";
@@ -4678,11 +4752,11 @@ var MediaElementPlaylistProvider = ({
4678
4752
  }) => {
4679
4753
  var _a;
4680
4754
  const progressBarWidth = progressBarWidthProp != null ? progressBarWidthProp : barWidth + barGap;
4681
- const [isPlaying, setIsPlaying] = useState15(false);
4682
- const [currentTime, setCurrentTime] = useState15(0);
4683
- const [duration, setDuration] = useState15(0);
4684
- const [peaksDataArray, setPeaksDataArray] = useState15([]);
4685
- const [playbackRate, setPlaybackRateState] = useState15(initialPlaybackRate);
4755
+ const [isPlaying, setIsPlaying] = useState16(false);
4756
+ const [currentTime, setCurrentTime] = useState16(0);
4757
+ const [duration, setDuration] = useState16(0);
4758
+ const [peaksDataArray, setPeaksDataArray] = useState16([]);
4759
+ const [playbackRate, setPlaybackRateState] = useState16(initialPlaybackRate);
4686
4760
  const annotations = useMemo5(() => {
4687
4761
  if (!(annotationList == null ? void 0 : annotationList.annotations)) return [];
4688
4762
  if (process.env.NODE_ENV !== "production" && annotationList.annotations.length > 0) {
@@ -4696,21 +4770,21 @@ var MediaElementPlaylistProvider = ({
4696
4770
  }
4697
4771
  return annotationList.annotations;
4698
4772
  }, [annotationList == null ? void 0 : annotationList.annotations]);
4699
- const annotationsRef = useRef15(annotations);
4773
+ const annotationsRef = useRef16(annotations);
4700
4774
  annotationsRef.current = annotations;
4701
- const [activeAnnotationId, setActiveAnnotationIdState] = useState15(null);
4702
- const [continuousPlay, setContinuousPlayState] = useState15(
4775
+ const [activeAnnotationId, setActiveAnnotationIdState] = useState16(null);
4776
+ const [continuousPlay, setContinuousPlayState] = useState16(
4703
4777
  (_a = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _a : false
4704
4778
  );
4705
- const [samplesPerPixel] = useState15(initialSamplesPerPixel);
4706
- const [isAutomaticScroll, setIsAutomaticScroll] = useState15(automaticScroll);
4707
- const playoutRef = useRef15(null);
4708
- const currentTimeRef = useRef15(0);
4709
- const continuousPlayRef = useRef15(continuousPlay);
4710
- const activeAnnotationIdRef = useRef15(null);
4711
- const scrollContainerRef = useRef15(null);
4712
- const isAutomaticScrollRef = useRef15(automaticScroll);
4713
- const samplesPerPixelRef = useRef15(initialSamplesPerPixel);
4779
+ const [samplesPerPixel] = useState16(initialSamplesPerPixel);
4780
+ const [isAutomaticScroll, setIsAutomaticScroll] = useState16(automaticScroll);
4781
+ const playoutRef = useRef16(null);
4782
+ const currentTimeRef = useRef16(0);
4783
+ const continuousPlayRef = useRef16(continuousPlay);
4784
+ const activeAnnotationIdRef = useRef16(null);
4785
+ const scrollContainerRef = useRef16(null);
4786
+ const isAutomaticScrollRef = useRef16(automaticScroll);
4787
+ const samplesPerPixelRef = useRef16(initialSamplesPerPixel);
4714
4788
  const { startAnimationFrameLoop, stopAnimationFrameLoop } = useAnimationFrameLoop();
4715
4789
  useEffect11(() => {
4716
4790
  continuousPlayRef.current = continuousPlay;
@@ -4718,15 +4792,15 @@ var MediaElementPlaylistProvider = ({
4718
4792
  useEffect11(() => {
4719
4793
  isAutomaticScrollRef.current = isAutomaticScroll;
4720
4794
  }, [isAutomaticScroll]);
4721
- const setActiveAnnotationId = useCallback19((value) => {
4795
+ const setActiveAnnotationId = useCallback20((value) => {
4722
4796
  activeAnnotationIdRef.current = value;
4723
4797
  setActiveAnnotationIdState(value);
4724
4798
  }, []);
4725
- const setContinuousPlay = useCallback19((value) => {
4799
+ const setContinuousPlay = useCallback20((value) => {
4726
4800
  continuousPlayRef.current = value;
4727
4801
  setContinuousPlayState(value);
4728
4802
  }, []);
4729
- const setScrollContainer = useCallback19((element) => {
4803
+ const setScrollContainer = useCallback20((element) => {
4730
4804
  scrollContainerRef.current = element;
4731
4805
  }, []);
4732
4806
  const sampleRate = track.waveformData.sample_rate;
@@ -4806,7 +4880,7 @@ var MediaElementPlaylistProvider = ({
4806
4880
  console.warn("[waveform-playlist] Failed to extract peaks from waveform data:", err);
4807
4881
  }
4808
4882
  }, [track.waveformData, track.name, samplesPerPixel, sampleRate]);
4809
- const startAnimationLoop = useCallback19(() => {
4883
+ const startAnimationLoop = useCallback20(() => {
4810
4884
  const updateTime = () => {
4811
4885
  var _a2, _b, _c;
4812
4886
  const time = (_b = (_a2 = playoutRef.current) == null ? void 0 : _a2.getCurrentTime()) != null ? _b : 0;
@@ -4849,7 +4923,7 @@ var MediaElementPlaylistProvider = ({
4849
4923
  startAnimationFrameLoop(updateTime);
4850
4924
  }, [setActiveAnnotationId, sampleRate, startAnimationFrameLoop]);
4851
4925
  const stopAnimationLoop = stopAnimationFrameLoop;
4852
- const play = useCallback19(
4926
+ const play = useCallback20(
4853
4927
  (startTime) => {
4854
4928
  if (!playoutRef.current) return;
4855
4929
  const actualStartTime = startTime != null ? startTime : currentTimeRef.current;
@@ -4859,14 +4933,14 @@ var MediaElementPlaylistProvider = ({
4859
4933
  },
4860
4934
  [startAnimationLoop]
4861
4935
  );
4862
- const pause = useCallback19(() => {
4936
+ const pause = useCallback20(() => {
4863
4937
  if (!playoutRef.current) return;
4864
4938
  playoutRef.current.pause();
4865
4939
  setIsPlaying(false);
4866
4940
  stopAnimationLoop();
4867
4941
  setCurrentTime(playoutRef.current.getCurrentTime());
4868
4942
  }, [stopAnimationLoop]);
4869
- const stop = useCallback19(() => {
4943
+ const stop = useCallback20(() => {
4870
4944
  if (!playoutRef.current) return;
4871
4945
  playoutRef.current.stop();
4872
4946
  setIsPlaying(false);
@@ -4875,7 +4949,7 @@ var MediaElementPlaylistProvider = ({
4875
4949
  setCurrentTime(0);
4876
4950
  setActiveAnnotationId(null);
4877
4951
  }, [stopAnimationLoop, setActiveAnnotationId]);
4878
- const seekTo = useCallback19(
4952
+ const seekTo = useCallback20(
4879
4953
  (time) => {
4880
4954
  const clampedTime = Math.max(0, Math.min(time, duration));
4881
4955
  currentTimeRef.current = clampedTime;
@@ -4886,7 +4960,7 @@ var MediaElementPlaylistProvider = ({
4886
4960
  },
4887
4961
  [duration]
4888
4962
  );
4889
- const setPlaybackRate = useCallback19((rate) => {
4963
+ const setPlaybackRate = useCallback20((rate) => {
4890
4964
  const clampedRate = Math.max(0.5, Math.min(2, rate));
4891
4965
  setPlaybackRateState(clampedRate);
4892
4966
  if (playoutRef.current) {
@@ -4912,9 +4986,9 @@ var MediaElementPlaylistProvider = ({
4912
4986
  }),
4913
4987
  [continuousPlay, annotations, activeAnnotationId, playbackRate, isAutomaticScroll]
4914
4988
  );
4915
- const onAnnotationsChangeRef = useRef15(onAnnotationsChange);
4989
+ const onAnnotationsChangeRef = useRef16(onAnnotationsChange);
4916
4990
  onAnnotationsChangeRef.current = onAnnotationsChange;
4917
- const setAnnotations = useCallback19(
4991
+ const setAnnotations = useCallback20(
4918
4992
  (action) => {
4919
4993
  const updated = typeof action === "function" ? action(annotationsRef.current) : action;
4920
4994
  if (!onAnnotationsChangeRef.current) {
@@ -5021,7 +5095,7 @@ var useMediaElementData = () => {
5021
5095
  };
5022
5096
 
5023
5097
  // src/components/PlaybackControls.tsx
5024
- import { useCallback as useCallback20 } from "react";
5098
+ import { useCallback as useCallback21 } from "react";
5025
5099
  import { BaseControlButton } from "@waveform-playlist/ui-components";
5026
5100
  import { jsx as jsx3 } from "react/jsx-runtime";
5027
5101
  var PlayButton = ({ className }) => {
@@ -5157,7 +5231,7 @@ var ClearAllButton = ({
5157
5231
  className
5158
5232
  }) => {
5159
5233
  const { stop } = usePlaylistControls();
5160
- const handleClick = useCallback20(() => {
5234
+ const handleClick = useCallback21(() => {
5161
5235
  stop();
5162
5236
  onClearAll();
5163
5237
  }, [stop, onClearAll]);
@@ -5185,7 +5259,7 @@ var ZoomOutButton = ({
5185
5259
  };
5186
5260
 
5187
5261
  // src/components/ContextualControls.tsx
5188
- import { useRef as useRef16, useEffect as useEffect12 } from "react";
5262
+ import { useRef as useRef17, useEffect as useEffect12 } from "react";
5189
5263
  import {
5190
5264
  MasterVolumeControl as BaseMasterVolumeControl,
5191
5265
  TimeFormatSelect as BaseTimeFormatSelect,
@@ -5224,8 +5298,8 @@ var PositionDisplay = styled.span`
5224
5298
  `;
5225
5299
  var AudioPosition = ({ className }) => {
5226
5300
  var _a;
5227
- const timeRef = useRef16(null);
5228
- const animationFrameRef = useRef16(null);
5301
+ const timeRef = useRef17(null);
5302
+ const animationFrameRef = useRef17(null);
5229
5303
  const { isPlaying, currentTimeRef, getPlaybackTime } = usePlaybackAnimation();
5230
5304
  const { timeFormat: format } = usePlaylistData();
5231
5305
  useEffect12(() => {
@@ -5384,7 +5458,7 @@ function useClipInteractionEnabled() {
5384
5458
  }
5385
5459
 
5386
5460
  // src/components/PlaylistVisualization.tsx
5387
- import { useContext as useContext6, useRef as useRef19, useState as useState16, useMemo as useMemo6, useCallback as useCallback21 } from "react";
5461
+ import { useContext as useContext6, useRef as useRef20, useState as useState17, useMemo as useMemo6, useCallback as useCallback22 } from "react";
5388
5462
  import { createPortal } from "react-dom";
5389
5463
  import styled4 from "styled-components";
5390
5464
  import { getGlobalContext as getGlobalContext2 } from "@waveform-playlist/playout";
@@ -5414,7 +5488,7 @@ import {
5414
5488
  } from "@waveform-playlist/ui-components";
5415
5489
 
5416
5490
  // src/components/AnimatedPlayhead.tsx
5417
- import { useRef as useRef17, useEffect as useEffect13 } from "react";
5491
+ import { useRef as useRef18, useEffect as useEffect13 } from "react";
5418
5492
  import styled2 from "styled-components";
5419
5493
  import { jsx as jsx8 } from "react/jsx-runtime";
5420
5494
  var PlayheadLine = styled2.div.attrs((props) => ({
@@ -5432,8 +5506,8 @@ var PlayheadLine = styled2.div.attrs((props) => ({
5432
5506
  will-change: transform;
5433
5507
  `;
5434
5508
  var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5435
- const playheadRef = useRef17(null);
5436
- const animationFrameRef = useRef17(null);
5509
+ const playheadRef = useRef18(null);
5510
+ const animationFrameRef = useRef18(null);
5437
5511
  const { isPlaying, currentTimeRef, getPlaybackTime } = usePlaybackAnimation();
5438
5512
  const { samplesPerPixel, sampleRate, progressBarWidth } = usePlaylistData();
5439
5513
  useEffect13(() => {
@@ -5472,7 +5546,7 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5472
5546
  };
5473
5547
 
5474
5548
  // src/components/ChannelWithProgress.tsx
5475
- import { useRef as useRef18, useEffect as useEffect14 } from "react";
5549
+ import { useRef as useRef19, useEffect as useEffect14 } from "react";
5476
5550
  import styled3 from "styled-components";
5477
5551
  import {
5478
5552
  clipPixelWidth as computeClipPixelWidth
@@ -5537,8 +5611,8 @@ var ChannelWithProgress = (_a) => {
5537
5611
  "clipSampleRate",
5538
5612
  "clipOffsetSeconds"
5539
5613
  ]);
5540
- const progressRef = useRef18(null);
5541
- const animationFrameRef = useRef18(null);
5614
+ const progressRef = useRef19(null);
5615
+ const animationFrameRef = useRef19(null);
5542
5616
  const theme = useTheme();
5543
5617
  const { waveHeight } = usePlaylistInfo();
5544
5618
  const { isPlaying, currentTimeRef, getPlaybackTime } = usePlaybackAnimation();
@@ -5821,11 +5895,11 @@ var PlaylistVisualization = ({
5821
5895
  )
5822
5896
  };
5823
5897
  }, [spectrogram == null ? void 0 : spectrogram.spectrogramWorkerApi]);
5824
- const [settingsModalTrackId, setSettingsModalTrackId] = useState16(null);
5825
- const [isSelecting, setIsSelecting] = useState16(false);
5826
- const mouseDownTimeRef = useRef19(0);
5827
- const scrollContainerRef = useRef19(null);
5828
- const handleScrollContainerRef = useCallback21(
5898
+ const [settingsModalTrackId, setSettingsModalTrackId] = useState17(null);
5899
+ const [isSelecting, setIsSelecting] = useState17(false);
5900
+ const mouseDownTimeRef = useRef20(0);
5901
+ const scrollContainerRef = useRef20(null);
5902
+ const handleScrollContainerRef = useCallback22(
5829
5903
  (element) => {
5830
5904
  scrollContainerRef.current = element;
5831
5905
  setScrollContainer(element);
@@ -5863,7 +5937,7 @@ var PlaylistVisualization = ({
5863
5937
  );
5864
5938
  }
5865
5939
  });
5866
- const selectTrack = useCallback21(
5940
+ const selectTrack = useCallback22(
5867
5941
  (trackIndex) => {
5868
5942
  if (trackIndex >= 0 && trackIndex < tracks.length) {
5869
5943
  const track = tracks[trackIndex];
@@ -6304,7 +6378,7 @@ var PlaylistVisualization = ({
6304
6378
  };
6305
6379
 
6306
6380
  // src/components/PlaylistAnnotationList.tsx
6307
- import { useCallback as useCallback22 } from "react";
6381
+ import { useCallback as useCallback23 } from "react";
6308
6382
  import { jsx as jsx11 } from "react/jsx-runtime";
6309
6383
  var PlaylistAnnotationList = ({
6310
6384
  height,
@@ -6319,7 +6393,7 @@ var PlaylistAnnotationList = ({
6319
6393
  const integration = useAnnotationIntegration();
6320
6394
  const { setAnnotations } = usePlaylistControls();
6321
6395
  const resolvedConfig = annotationListConfig != null ? annotationListConfig : { linkEndpoints, continuousPlay };
6322
- const handleAnnotationUpdate = useCallback22(
6396
+ const handleAnnotationUpdate = useCallback23(
6323
6397
  (updatedAnnotations) => {
6324
6398
  setAnnotations(updatedAnnotations);
6325
6399
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6403,7 +6477,7 @@ var Waveform = ({
6403
6477
  };
6404
6478
 
6405
6479
  // src/components/MediaElementPlaylist.tsx
6406
- import { useContext as useContext7, useRef as useRef22, useState as useState17, useCallback as useCallback23 } from "react";
6480
+ import { useContext as useContext7, useRef as useRef23, useState as useState18, useCallback as useCallback24 } from "react";
6407
6481
  import { DragDropProvider } from "@dnd-kit/react";
6408
6482
  import { RestrictToHorizontalAxis } from "@dnd-kit/abstract/modifiers";
6409
6483
  import {
@@ -6439,7 +6513,7 @@ var noDropAnimationPlugins = (defaults) => {
6439
6513
  };
6440
6514
 
6441
6515
  // src/components/AnimatedMediaElementPlayhead.tsx
6442
- import { useRef as useRef20, useEffect as useEffect15 } from "react";
6516
+ import { useRef as useRef21, useEffect as useEffect15 } from "react";
6443
6517
  import styled5 from "styled-components";
6444
6518
  import { jsx as jsx13 } from "react/jsx-runtime";
6445
6519
  var PlayheadLine2 = styled5.div`
@@ -6456,8 +6530,8 @@ var PlayheadLine2 = styled5.div`
6456
6530
  var AnimatedMediaElementPlayhead = ({
6457
6531
  color = "#ff0000"
6458
6532
  }) => {
6459
- const playheadRef = useRef20(null);
6460
- const animationFrameRef = useRef20(null);
6533
+ const playheadRef = useRef21(null);
6534
+ const animationFrameRef = useRef21(null);
6461
6535
  const { isPlaying, currentTimeRef } = useMediaElementAnimation();
6462
6536
  const { samplesPerPixel, sampleRate, progressBarWidth } = useMediaElementData();
6463
6537
  useEffect15(() => {
@@ -6496,7 +6570,7 @@ var AnimatedMediaElementPlayhead = ({
6496
6570
  };
6497
6571
 
6498
6572
  // src/components/ChannelWithMediaElementProgress.tsx
6499
- import { useRef as useRef21, useEffect as useEffect16 } from "react";
6573
+ import { useRef as useRef22, useEffect as useEffect16 } from "react";
6500
6574
  import styled6 from "styled-components";
6501
6575
  import {
6502
6576
  SmartChannel as SmartChannel2,
@@ -6541,8 +6615,8 @@ var ChannelWithMediaElementProgress = (_a) => {
6541
6615
  "clipStartSample",
6542
6616
  "clipDurationSamples"
6543
6617
  ]);
6544
- const progressRef = useRef21(null);
6545
- const animationFrameRef = useRef21(null);
6618
+ const progressRef = useRef22(null);
6619
+ const animationFrameRef = useRef22(null);
6546
6620
  const theme = useTheme3();
6547
6621
  const { waveHeight } = usePlaylistInfo2();
6548
6622
  const { isPlaying, currentTimeRef } = useMediaElementAnimation();
@@ -6685,11 +6759,11 @@ var MediaElementPlaylist = ({
6685
6759
  fadeIn,
6686
6760
  fadeOut
6687
6761
  } = useMediaElementData();
6688
- const [selectionStart, setSelectionStart] = useState17(0);
6689
- const [selectionEnd, setSelectionEnd] = useState17(0);
6690
- const [isSelecting, setIsSelecting] = useState17(false);
6691
- const scrollContainerRef = useRef22(null);
6692
- const handleScrollContainerRef = useCallback23(
6762
+ const [selectionStart, setSelectionStart] = useState18(0);
6763
+ const [selectionEnd, setSelectionEnd] = useState18(0);
6764
+ const [isSelecting, setIsSelecting] = useState18(false);
6765
+ const scrollContainerRef = useRef23(null);
6766
+ const handleScrollContainerRef = useCallback24(
6693
6767
  (el) => {
6694
6768
  scrollContainerRef.current = el;
6695
6769
  setScrollContainer(el);
@@ -6697,7 +6771,7 @@ var MediaElementPlaylist = ({
6697
6771
  [setScrollContainer]
6698
6772
  );
6699
6773
  const tracksFullWidth = Math.floor(duration * sampleRate / samplesPerPixel);
6700
- const handleAnnotationClick = useCallback23(
6774
+ const handleAnnotationClick = useCallback24(
6701
6775
  (annotation) => __async(null, null, function* () {
6702
6776
  setActiveAnnotationId(annotation.id);
6703
6777
  try {
@@ -6712,7 +6786,7 @@ var MediaElementPlaylist = ({
6712
6786
  }),
6713
6787
  [setActiveAnnotationId, play]
6714
6788
  );
6715
- const handleAnnotationUpdate = useCallback23(
6789
+ const handleAnnotationUpdate = useCallback24(
6716
6790
  (updatedAnnotations) => {
6717
6791
  setAnnotations(updatedAnnotations);
6718
6792
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -6726,8 +6800,8 @@ var MediaElementPlaylist = ({
6726
6800
  duration,
6727
6801
  linkEndpoints: linkEndpointsProp
6728
6802
  });
6729
- const mouseDownTimeRef = useRef22(0);
6730
- const handleMouseDown = useCallback23(
6803
+ const mouseDownTimeRef = useRef23(0);
6804
+ const handleMouseDown = useCallback24(
6731
6805
  (e) => {
6732
6806
  const rect = e.currentTarget.getBoundingClientRect();
6733
6807
  const x = e.clientX - rect.left;
@@ -6739,7 +6813,7 @@ var MediaElementPlaylist = ({
6739
6813
  },
6740
6814
  [samplesPerPixel, sampleRate]
6741
6815
  );
6742
- const handleMouseMove = useCallback23(
6816
+ const handleMouseMove = useCallback24(
6743
6817
  (e) => {
6744
6818
  if (!isSelecting) return;
6745
6819
  const rect = e.currentTarget.getBoundingClientRect();
@@ -6749,7 +6823,7 @@ var MediaElementPlaylist = ({
6749
6823
  },
6750
6824
  [isSelecting, samplesPerPixel, sampleRate]
6751
6825
  );
6752
- const handleMouseUp = useCallback23(
6826
+ const handleMouseUp = useCallback24(
6753
6827
  (e) => {
6754
6828
  if (!isSelecting) return;
6755
6829
  setIsSelecting(false);
@@ -6935,7 +7009,7 @@ var MediaElementPlaylist = ({
6935
7009
  };
6936
7010
 
6937
7011
  // src/components/MediaElementAnnotationList.tsx
6938
- import { useCallback as useCallback24 } from "react";
7012
+ import { useCallback as useCallback25 } from "react";
6939
7013
  import { jsx as jsx16 } from "react/jsx-runtime";
6940
7014
  var MediaElementAnnotationList = ({
6941
7015
  height,
@@ -6951,7 +7025,7 @@ var MediaElementAnnotationList = ({
6951
7025
  const integration = useAnnotationIntegration();
6952
7026
  const { setAnnotations } = useMediaElementControls();
6953
7027
  const resolvedConfig = annotationListConfig != null ? annotationListConfig : { linkEndpoints: false, continuousPlay };
6954
- const handleAnnotationUpdate = useCallback24(
7028
+ const handleAnnotationUpdate = useCallback25(
6955
7029
  (updatedAnnotations) => {
6956
7030
  setAnnotations(updatedAnnotations);
6957
7031
  onAnnotationUpdate == null ? void 0 : onAnnotationUpdate(updatedAnnotations);
@@ -7026,6 +7100,7 @@ var KeyboardShortcuts = ({
7026
7100
  playback = false,
7027
7101
  clipSplitting = false,
7028
7102
  annotations = false,
7103
+ undo: undoEnabled = false,
7029
7104
  additionalShortcuts = []
7030
7105
  }) => {
7031
7106
  const { tracks, samplesPerPixel, playoutRef, duration } = usePlaylistData();
@@ -7035,7 +7110,7 @@ var KeyboardShortcuts = ({
7035
7110
  activeAnnotationId,
7036
7111
  continuousPlay
7037
7112
  } = usePlaylistState();
7038
- const { setAnnotations, setActiveAnnotationId, scrollContainerRef, play } = usePlaylistControls();
7113
+ const { setAnnotations, setActiveAnnotationId, scrollContainerRef, play, undo, redo } = usePlaylistControls();
7039
7114
  const { splitClipAtPlayhead } = useClipSplitting({
7040
7115
  tracks,
7041
7116
  samplesPerPixel,
@@ -7050,6 +7125,14 @@ var KeyboardShortcuts = ({
7050
7125
  preventDefault: true
7051
7126
  });
7052
7127
  }
7128
+ if (undoEnabled) {
7129
+ allAdditional.push(
7130
+ { key: "z", ctrlKey: true, shiftKey: false, action: undo, description: "Undo" },
7131
+ { key: "z", metaKey: true, shiftKey: false, action: undo, description: "Undo" },
7132
+ { key: "z", ctrlKey: true, shiftKey: true, action: redo, description: "Redo" },
7133
+ { key: "z", metaKey: true, shiftKey: true, action: redo, description: "Redo" }
7134
+ );
7135
+ }
7053
7136
  if (additionalShortcuts.length > 0) {
7054
7137
  allAdditional.push(...additionalShortcuts);
7055
7138
  }