@waveform-playlist/browser 11.1.0 → 11.3.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
@@ -65,6 +65,7 @@ import {
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);
@@ -3700,6 +3703,7 @@ var WaveformPlaylistProvider = ({
3700
3703
  soundFontCache,
3701
3704
  deferEngineRebuild = false,
3702
3705
  indefinitePlayback = false,
3706
+ sampleRate: sampleRateProp,
3703
3707
  children
3704
3708
  }) => {
3705
3709
  var _a, _b, _c, _d;
@@ -3755,6 +3759,7 @@ var WaveformPlaylistProvider = ({
3755
3759
  const playbackEndTimeRef = useRef15(null);
3756
3760
  const scrollContainerRef = useRef15(null);
3757
3761
  const isAutomaticScrollRef = useRef15(false);
3762
+ const frameCallbacksRef = useRef15(/* @__PURE__ */ new Map());
3758
3763
  const continuousPlayRef = useRef15((_d = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _d : false);
3759
3764
  const activeAnnotationIdRef = useRef15(null);
3760
3765
  const engineTracksRef = useRef15(null);
@@ -3763,9 +3768,21 @@ var WaveformPlaylistProvider = ({
3763
3768
  const isDraggingRef = useRef15(false);
3764
3769
  const prevTracksRef = useRef15([]);
3765
3770
  const samplesPerPixelRef = useRef15(initialSamplesPerPixel);
3766
- const sampleRateRef = useRef15(
3767
- typeof AudioContext !== "undefined" ? getGlobalAudioContext4().sampleRate : 48e3
3768
- );
3771
+ const [initialSampleRate] = useState15(() => {
3772
+ if (typeof AudioContext === "undefined") return sampleRateProp != null ? sampleRateProp : 48e3;
3773
+ try {
3774
+ if (sampleRateProp !== void 0) {
3775
+ return configureGlobalContext({ sampleRate: sampleRateProp });
3776
+ }
3777
+ return getGlobalAudioContext4().sampleRate;
3778
+ } catch (err) {
3779
+ console.warn(
3780
+ "[waveform-playlist] Failed to configure AudioContext: " + String(err) + " \u2014 falling back to " + (sampleRateProp != null ? sampleRateProp : 48e3) + " Hz"
3781
+ );
3782
+ return sampleRateProp != null ? sampleRateProp : 48e3;
3783
+ }
3784
+ });
3785
+ const sampleRateRef = useRef15(initialSampleRate);
3769
3786
  const { timeFormat, setTimeFormat, formatTime: formatTime2 } = useTimeFormat();
3770
3787
  const zoom = useZoomControls({ engineRef, initialSamplesPerPixel });
3771
3788
  const { samplesPerPixel, onEngineState: onZoomEngineState } = zoom;
@@ -4115,15 +4132,28 @@ var WaveformPlaylistProvider = ({
4115
4132
  let peaks;
4116
4133
  if (clip.waveformData) {
4117
4134
  try {
4135
+ const wdRate = clip.waveformData.sample_rate;
4136
+ const clipRate = clip.sampleRate;
4137
+ let peakOffset = clip.offsetSamples;
4138
+ let peakDuration = clip.durationSamples;
4139
+ let peakSpp = samplesPerPixel;
4140
+ if (wdRate !== clipRate && clipRate > 0 && wdRate > 0) {
4141
+ const ratio = wdRate / clipRate;
4142
+ peakOffset = Math.round(clip.offsetSamples * ratio);
4143
+ peakDuration = Math.round(clip.durationSamples * ratio);
4144
+ peakSpp = Math.max(1, Math.round(samplesPerPixel * ratio));
4145
+ }
4118
4146
  peaks = extractPeaksFromWaveformDataFull(
4119
4147
  clip.waveformData,
4120
- samplesPerPixel,
4148
+ peakSpp,
4121
4149
  mono,
4122
- clip.offsetSamples,
4123
- clip.durationSamples
4150
+ peakOffset,
4151
+ peakDuration
4124
4152
  );
4125
4153
  } catch (err) {
4126
- console.warn("[waveform-playlist] Failed to extract peaks from waveformData:", err);
4154
+ console.warn(
4155
+ "[waveform-playlist] Failed to extract peaks from waveformData: " + String(err)
4156
+ );
4127
4157
  }
4128
4158
  }
4129
4159
  if (!peaks) {
@@ -4138,7 +4168,9 @@ var WaveformPlaylistProvider = ({
4138
4168
  clip.durationSamples
4139
4169
  );
4140
4170
  } catch (err) {
4141
- console.warn("[waveform-playlist] Failed to extract peaks from cache:", err);
4171
+ console.warn(
4172
+ "[waveform-playlist] Failed to extract peaks from cache: " + String(err)
4173
+ );
4142
4174
  }
4143
4175
  }
4144
4176
  }
@@ -4188,10 +4220,30 @@ var WaveformPlaylistProvider = ({
4188
4220
  const elapsed = getContext2().currentTime - ((_a2 = playbackStartTimeRef.current) != null ? _a2 : 0);
4189
4221
  return ((_b2 = audioStartPositionRef.current) != null ? _b2 : 0) + elapsed;
4190
4222
  }, []);
4223
+ const registerFrameCallback = useCallback19((id, cb) => {
4224
+ frameCallbacksRef.current.set(id, cb);
4225
+ }, []);
4226
+ const unregisterFrameCallback = useCallback19((id) => {
4227
+ frameCallbacksRef.current.delete(id);
4228
+ }, []);
4191
4229
  const startAnimationLoop = useCallback19(() => {
4230
+ const audioCtx = getGlobalAudioContext4();
4192
4231
  const updateTime = () => {
4193
4232
  const time = getPlaybackTime();
4194
4233
  currentTimeRef.current = time;
4234
+ const latency = "outputLatency" in audioCtx ? audioCtx.outputLatency : 0;
4235
+ const visualTime = Math.max(0, time - latency);
4236
+ const sr = sampleRateRef.current;
4237
+ const spp = samplesPerPixelRef.current;
4238
+ const frameData = {
4239
+ time,
4240
+ visualTime,
4241
+ sampleRate: sr,
4242
+ samplesPerPixel: spp
4243
+ };
4244
+ for (const cb of frameCallbacksRef.current.values()) {
4245
+ cb(frameData);
4246
+ }
4195
4247
  const currentAnnotations = annotationsRef.current;
4196
4248
  if (currentAnnotations.length > 0) {
4197
4249
  const currentAnnotation = currentAnnotations.find(
@@ -4226,10 +4278,9 @@ var WaveformPlaylistProvider = ({
4226
4278
  }
4227
4279
  if (isAutomaticScrollRef.current && scrollContainerRef.current && duration > 0) {
4228
4280
  const container = scrollContainerRef.current;
4229
- const sr = sampleRateRef.current;
4230
- const pixelPosition = time * sr / samplesPerPixelRef.current;
4281
+ const pixelPosition = visualTime * sr / spp;
4231
4282
  const containerWidth = container.clientWidth;
4232
- const targetScrollLeft = Math.max(0, pixelPosition - containerWidth / 2);
4283
+ const targetScrollLeft = Math.round(Math.max(0, pixelPosition - containerWidth / 2));
4233
4284
  container.scrollLeft = targetScrollLeft;
4234
4285
  }
4235
4286
  if (playbackEndTimeRef.current !== null && time >= playbackEndTimeRef.current) {
@@ -4465,7 +4516,9 @@ var WaveformPlaylistProvider = ({
4465
4516
  currentTimeRef,
4466
4517
  playbackStartTimeRef,
4467
4518
  audioStartPositionRef,
4468
- getPlaybackTime
4519
+ getPlaybackTime,
4520
+ registerFrameCallback,
4521
+ unregisterFrameCallback
4469
4522
  }),
4470
4523
  [
4471
4524
  isPlaying,
@@ -4473,7 +4526,9 @@ var WaveformPlaylistProvider = ({
4473
4526
  currentTimeRef,
4474
4527
  playbackStartTimeRef,
4475
4528
  audioStartPositionRef,
4476
- getPlaybackTime
4529
+ getPlaybackTime,
4530
+ registerFrameCallback,
4531
+ unregisterFrameCallback
4477
4532
  ]
4478
4533
  );
4479
4534
  const stateValue = useMemo4(
@@ -5268,32 +5323,19 @@ var PositionDisplay = styled.span`
5268
5323
  var AudioPosition = ({ className }) => {
5269
5324
  var _a;
5270
5325
  const timeRef = useRef17(null);
5271
- const animationFrameRef = useRef17(null);
5272
- const { isPlaying, currentTimeRef, getPlaybackTime } = usePlaybackAnimation();
5326
+ const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
5273
5327
  const { timeFormat: format } = usePlaylistData();
5274
5328
  useEffect12(() => {
5275
- const updateTime = () => {
5276
- var _a2;
5277
- if (timeRef.current) {
5278
- const time = isPlaying ? getPlaybackTime() : (_a2 = currentTimeRef.current) != null ? _a2 : 0;
5279
- timeRef.current.textContent = formatTime(time, format);
5280
- }
5281
- if (isPlaying) {
5282
- animationFrameRef.current = requestAnimationFrame(updateTime);
5283
- }
5284
- };
5329
+ const id = "audio-position";
5285
5330
  if (isPlaying) {
5286
- animationFrameRef.current = requestAnimationFrame(updateTime);
5287
- } else {
5288
- updateTime();
5331
+ registerFrameCallback(id, ({ time }) => {
5332
+ if (timeRef.current) {
5333
+ timeRef.current.textContent = formatTime(time, format);
5334
+ }
5335
+ });
5289
5336
  }
5290
- return () => {
5291
- if (animationFrameRef.current) {
5292
- cancelAnimationFrame(animationFrameRef.current);
5293
- animationFrameRef.current = null;
5294
- }
5295
- };
5296
- }, [isPlaying, format, currentTimeRef, getPlaybackTime]);
5337
+ return () => unregisterFrameCallback(id);
5338
+ }, [isPlaying, format, registerFrameCallback, unregisterFrameCallback]);
5297
5339
  useEffect12(() => {
5298
5340
  var _a2;
5299
5341
  if (!isPlaying && timeRef.current) {
@@ -5476,33 +5518,20 @@ var PlayheadLine = styled2.div.attrs((props) => ({
5476
5518
  `;
5477
5519
  var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5478
5520
  const playheadRef = useRef18(null);
5479
- const animationFrameRef = useRef18(null);
5480
- const { isPlaying, currentTimeRef, getPlaybackTime } = usePlaybackAnimation();
5521
+ const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
5481
5522
  const { samplesPerPixel, sampleRate, progressBarWidth } = usePlaylistData();
5482
5523
  useEffect13(() => {
5483
- const updatePosition = () => {
5484
- var _a;
5485
- if (playheadRef.current) {
5486
- const time = isPlaying ? getPlaybackTime() : (_a = currentTimeRef.current) != null ? _a : 0;
5487
- const position = time * sampleRate / samplesPerPixel;
5488
- playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;
5489
- }
5490
- if (isPlaying) {
5491
- animationFrameRef.current = requestAnimationFrame(updatePosition);
5492
- }
5493
- };
5524
+ const id = "playhead";
5494
5525
  if (isPlaying) {
5495
- animationFrameRef.current = requestAnimationFrame(updatePosition);
5496
- } else {
5497
- updatePosition();
5526
+ registerFrameCallback(id, ({ visualTime, sampleRate: sr, samplesPerPixel: spp }) => {
5527
+ if (playheadRef.current) {
5528
+ const px = visualTime * sr / spp;
5529
+ playheadRef.current.style.transform = `translate3d(${px}px, 0, 0)`;
5530
+ }
5531
+ });
5498
5532
  }
5499
- return () => {
5500
- if (animationFrameRef.current) {
5501
- cancelAnimationFrame(animationFrameRef.current);
5502
- animationFrameRef.current = null;
5503
- }
5504
- };
5505
- }, [isPlaying, sampleRate, samplesPerPixel, currentTimeRef, getPlaybackTime]);
5533
+ return () => unregisterFrameCallback(id);
5534
+ }, [isPlaying, registerFrameCallback, unregisterFrameCallback]);
5506
5535
  useEffect13(() => {
5507
5536
  var _a;
5508
5537
  if (!isPlaying && playheadRef.current) {
@@ -5515,7 +5544,7 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5515
5544
  };
5516
5545
 
5517
5546
  // src/components/ChannelWithProgress.tsx
5518
- import { useRef as useRef19, useEffect as useEffect14 } from "react";
5547
+ import { useId, useRef as useRef19, useEffect as useEffect14 } from "react";
5519
5548
  import styled3 from "styled-components";
5520
5549
  import {
5521
5550
  clipPixelWidth as computeClipPixelWidth
@@ -5581,10 +5610,10 @@ var ChannelWithProgress = (_a) => {
5581
5610
  "clipOffsetSeconds"
5582
5611
  ]);
5583
5612
  const progressRef = useRef19(null);
5584
- const animationFrameRef = useRef19(null);
5613
+ const callbackId = useId();
5585
5614
  const theme = useTheme();
5586
5615
  const { waveHeight } = usePlaylistInfo();
5587
- const { isPlaying, currentTimeRef, getPlaybackTime } = usePlaybackAnimation();
5616
+ const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
5588
5617
  const { samplesPerPixel, sampleRate } = usePlaylistData();
5589
5618
  const progressColor = (theme == null ? void 0 : theme.waveProgressColor) || "rgba(0, 0, 0, 0.1)";
5590
5619
  const clipPixelWidth = computeClipPixelWidth(
@@ -5593,46 +5622,32 @@ var ChannelWithProgress = (_a) => {
5593
5622
  samplesPerPixel
5594
5623
  );
5595
5624
  useEffect14(() => {
5596
- const updateProgress = () => {
5597
- var _a2;
5598
- if (progressRef.current) {
5599
- const currentTime = isPlaying ? getPlaybackTime() : (_a2 = currentTimeRef.current) != null ? _a2 : 0;
5600
- const currentSample = currentTime * sampleRate;
5601
- const clipEndSample = clipStartSample + clipDurationSamples;
5602
- let ratio = 0;
5603
- if (currentSample <= clipStartSample) {
5604
- ratio = 0;
5605
- } else if (currentSample >= clipEndSample) {
5606
- ratio = 1;
5607
- } else {
5608
- const playedSamples = currentSample - clipStartSample;
5609
- ratio = playedSamples / clipDurationSamples;
5610
- }
5611
- progressRef.current.style.transform = `scaleX(${ratio})`;
5612
- }
5613
- if (isPlaying) {
5614
- animationFrameRef.current = requestAnimationFrame(updateProgress);
5615
- }
5616
- };
5617
5625
  if (isPlaying) {
5618
- animationFrameRef.current = requestAnimationFrame(updateProgress);
5619
- } else {
5620
- updateProgress();
5626
+ registerFrameCallback(callbackId, ({ visualTime, sampleRate: sr }) => {
5627
+ if (progressRef.current) {
5628
+ const currentSample = visualTime * sr;
5629
+ const clipEndSample = clipStartSample + clipDurationSamples;
5630
+ let ratio = 0;
5631
+ if (currentSample <= clipStartSample) {
5632
+ ratio = 0;
5633
+ } else if (currentSample >= clipEndSample) {
5634
+ ratio = 1;
5635
+ } else {
5636
+ const playedSamples = currentSample - clipStartSample;
5637
+ ratio = playedSamples / clipDurationSamples;
5638
+ }
5639
+ progressRef.current.style.transform = `scaleX(${ratio})`;
5640
+ }
5641
+ });
5621
5642
  }
5622
- return () => {
5623
- if (animationFrameRef.current) {
5624
- cancelAnimationFrame(animationFrameRef.current);
5625
- animationFrameRef.current = null;
5626
- }
5627
- };
5643
+ return () => unregisterFrameCallback(callbackId);
5628
5644
  }, [
5629
5645
  isPlaying,
5630
- sampleRate,
5631
5646
  clipStartSample,
5632
5647
  clipDurationSamples,
5633
- clipPixelWidth,
5634
- currentTimeRef,
5635
- getPlaybackTime
5648
+ callbackId,
5649
+ registerFrameCallback,
5650
+ unregisterFrameCallback
5636
5651
  ]);
5637
5652
  useEffect14(() => {
5638
5653
  var _a2;