@waveform-playlist/browser 11.3.1 → 12.1.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
@@ -3634,6 +3634,7 @@ var WaveformPlaylistProvider = ({
3634
3634
  isPlayingRef.current = isPlaying;
3635
3635
  const playStartPositionRef = useRef15(0);
3636
3636
  const currentTimeRef = useRef15(0);
3637
+ const visualTimeRef = useRef15(0);
3637
3638
  const tracksRef = useRef15(tracks);
3638
3639
  const soundFontCacheRef = useRef15(soundFontCache);
3639
3640
  soundFontCacheRef.current = soundFontCache;
@@ -4104,6 +4105,25 @@ var WaveformPlaylistProvider = ({
4104
4105
  const elapsed = getContext2().currentTime - ((_a2 = playbackStartTimeRef.current) != null ? _a2 : 0);
4105
4106
  return ((_b2 = audioStartPositionRef.current) != null ? _b2 : 0) + elapsed;
4106
4107
  }, []);
4108
+ const toVisualTime = useCallback19((rawTime) => {
4109
+ var _a2, _b2;
4110
+ const audioCtx = getGlobalAudioContext4();
4111
+ const latency = "outputLatency" in audioCtx ? audioCtx.outputLatency : 0;
4112
+ const lookAhead = (_b2 = (_a2 = engineRef.current) == null ? void 0 : _a2.lookAhead) != null ? _b2 : 0;
4113
+ const visual = rawTime - latency - lookAhead;
4114
+ return Number.isFinite(visual) ? Math.max(0, visual) : 0;
4115
+ }, []);
4116
+ const setCurrentTimeRefs = useCallback19(
4117
+ (rawTime) => {
4118
+ currentTimeRef.current = rawTime;
4119
+ visualTimeRef.current = toVisualTime(rawTime);
4120
+ },
4121
+ [toVisualTime]
4122
+ );
4123
+ const getLookAhead = useCallback19(() => {
4124
+ var _a2, _b2;
4125
+ return (_b2 = (_a2 = engineRef.current) == null ? void 0 : _a2.lookAhead) != null ? _b2 : 0;
4126
+ }, []);
4107
4127
  const registerFrameCallback = useCallback19((id, cb) => {
4108
4128
  frameCallbacksRef.current.set(id, cb);
4109
4129
  }, []);
@@ -4113,10 +4133,14 @@ var WaveformPlaylistProvider = ({
4113
4133
  const startAnimationLoop = useCallback19(() => {
4114
4134
  const audioCtx = getGlobalAudioContext4();
4115
4135
  const updateTime = () => {
4136
+ var _a2, _b2;
4116
4137
  const time = getPlaybackTime();
4117
4138
  currentTimeRef.current = time;
4118
4139
  const latency = "outputLatency" in audioCtx ? audioCtx.outputLatency : 0;
4119
- const visualTime = Math.max(0, time - latency);
4140
+ const lookAhead = (_b2 = (_a2 = engineRef.current) == null ? void 0 : _a2.lookAhead) != null ? _b2 : 0;
4141
+ const visualRaw = time - latency - lookAhead;
4142
+ const visualTime = Number.isFinite(visualRaw) ? Math.max(0, visualRaw) : 0;
4143
+ visualTimeRef.current = visualTime;
4120
4144
  const sr = sampleRateRef.current;
4121
4145
  const spp = samplesPerPixelRef.current;
4122
4146
  const frameData = {
@@ -4149,7 +4173,7 @@ var WaveformPlaylistProvider = ({
4149
4173
  engineRef.current.stop();
4150
4174
  }
4151
4175
  setIsPlaying(false);
4152
- currentTimeRef.current = playStartPositionRef.current;
4176
+ setCurrentTimeRefs(playStartPositionRef.current);
4153
4177
  setCurrentTime(playStartPositionRef.current);
4154
4178
  return;
4155
4179
  }
@@ -4172,7 +4196,7 @@ var WaveformPlaylistProvider = ({
4172
4196
  engineRef.current.stop();
4173
4197
  }
4174
4198
  setIsPlaying(false);
4175
- currentTimeRef.current = playbackEndTimeRef.current;
4199
+ setCurrentTimeRefs(playbackEndTimeRef.current);
4176
4200
  setCurrentTime(playbackEndTimeRef.current);
4177
4201
  playbackEndTimeRef.current = null;
4178
4202
  return;
@@ -4182,7 +4206,7 @@ var WaveformPlaylistProvider = ({
4182
4206
  engineRef.current.stop();
4183
4207
  }
4184
4208
  setIsPlaying(false);
4185
- currentTimeRef.current = playStartPositionRef.current;
4209
+ setCurrentTimeRefs(playStartPositionRef.current);
4186
4210
  setCurrentTime(playStartPositionRef.current);
4187
4211
  setActiveAnnotationId(null);
4188
4212
  return;
@@ -4190,7 +4214,13 @@ var WaveformPlaylistProvider = ({
4190
4214
  startAnimationFrameLoop(updateTime);
4191
4215
  };
4192
4216
  startAnimationFrameLoop(updateTime);
4193
- }, [duration, setActiveAnnotationId, startAnimationFrameLoop, getPlaybackTime]);
4217
+ }, [
4218
+ duration,
4219
+ setActiveAnnotationId,
4220
+ startAnimationFrameLoop,
4221
+ getPlaybackTime,
4222
+ setCurrentTimeRefs
4223
+ ]);
4194
4224
  const stopAnimationLoop = stopAnimationFrameLoop;
4195
4225
  useEffect10(() => {
4196
4226
  const reschedulePlayback = () => __async(null, null, function* () {
@@ -4246,7 +4276,7 @@ var WaveformPlaylistProvider = ({
4246
4276
  if (!engineRef.current) return;
4247
4277
  const actualStartTime = startTime != null ? startTime : currentTimeRef.current;
4248
4278
  playStartPositionRef.current = actualStartTime;
4249
- currentTimeRef.current = actualStartTime;
4279
+ setCurrentTimeRefs(actualStartTime);
4250
4280
  engineRef.current.stop();
4251
4281
  engineRef.current.seek(actualStartTime);
4252
4282
  stopAnimationLoop();
@@ -4270,7 +4300,7 @@ var WaveformPlaylistProvider = ({
4270
4300
  setIsPlaying(true);
4271
4301
  startAnimationLoop();
4272
4302
  }),
4273
- [startAnimationLoop, stopAnimationLoop]
4303
+ [startAnimationLoop, stopAnimationLoop, setCurrentTimeRefs]
4274
4304
  );
4275
4305
  const pause = useCallback19(() => {
4276
4306
  if (!engineRef.current) return;
@@ -4278,28 +4308,28 @@ var WaveformPlaylistProvider = ({
4278
4308
  engineRef.current.pause();
4279
4309
  setIsPlaying(false);
4280
4310
  stopAnimationLoop();
4281
- currentTimeRef.current = pauseTime;
4311
+ setCurrentTimeRefs(pauseTime);
4282
4312
  setCurrentTime(pauseTime);
4283
- }, [stopAnimationLoop, getPlaybackTime]);
4313
+ }, [stopAnimationLoop, getPlaybackTime, setCurrentTimeRefs]);
4284
4314
  const stop = useCallback19(() => {
4285
4315
  if (!engineRef.current) return;
4286
4316
  engineRef.current.stop();
4287
4317
  setIsPlaying(false);
4288
4318
  stopAnimationLoop();
4289
- currentTimeRef.current = playStartPositionRef.current;
4319
+ setCurrentTimeRefs(playStartPositionRef.current);
4290
4320
  setCurrentTime(playStartPositionRef.current);
4291
4321
  setActiveAnnotationId(null);
4292
- }, [stopAnimationLoop, setActiveAnnotationId]);
4322
+ }, [stopAnimationLoop, setActiveAnnotationId, setCurrentTimeRefs]);
4293
4323
  const seekTo = useCallback19(
4294
4324
  (time) => {
4295
4325
  const clampedTime = Math.max(0, Math.min(time, duration));
4296
- currentTimeRef.current = clampedTime;
4326
+ setCurrentTimeRefs(clampedTime);
4297
4327
  setCurrentTime(clampedTime);
4298
4328
  if (isPlaying && engineRef.current) {
4299
4329
  play(clampedTime);
4300
4330
  }
4301
4331
  },
4302
- [duration, isPlaying, play]
4332
+ [duration, isPlaying, play, setCurrentTimeRefs]
4303
4333
  );
4304
4334
  const setTrackMute = useCallback19(
4305
4335
  (trackIndex, muted) => {
@@ -4360,7 +4390,7 @@ var WaveformPlaylistProvider = ({
4360
4390
  const setSelection = useCallback19(
4361
4391
  (start, end) => {
4362
4392
  setSelectionEngine(start, end);
4363
- currentTimeRef.current = start;
4393
+ setCurrentTimeRefs(start);
4364
4394
  setCurrentTime(start);
4365
4395
  if (isPlaying && engineRef.current) {
4366
4396
  engineRef.current.stop();
@@ -4368,7 +4398,7 @@ var WaveformPlaylistProvider = ({
4368
4398
  engineRef.current.play(start);
4369
4399
  }
4370
4400
  },
4371
- [isPlaying, setSelectionEngine]
4401
+ [isPlaying, setSelectionEngine, setCurrentTimeRefs]
4372
4402
  );
4373
4403
  const setScrollContainer = useCallback19((element) => {
4374
4404
  scrollContainerRef.current = element;
@@ -4398,9 +4428,11 @@ var WaveformPlaylistProvider = ({
4398
4428
  isPlaying,
4399
4429
  currentTime,
4400
4430
  currentTimeRef,
4431
+ visualTimeRef,
4401
4432
  playbackStartTimeRef,
4402
4433
  audioStartPositionRef,
4403
4434
  getPlaybackTime,
4435
+ getLookAhead,
4404
4436
  registerFrameCallback,
4405
4437
  unregisterFrameCallback
4406
4438
  }),
@@ -4408,9 +4440,11 @@ var WaveformPlaylistProvider = ({
4408
4440
  isPlaying,
4409
4441
  currentTime,
4410
4442
  currentTimeRef,
4443
+ visualTimeRef,
4411
4444
  playbackStartTimeRef,
4412
4445
  audioStartPositionRef,
4413
4446
  getPlaybackTime,
4447
+ getLookAhead,
4414
4448
  registerFrameCallback,
4415
4449
  unregisterFrameCallback
4416
4450
  ]
@@ -4453,10 +4487,10 @@ var WaveformPlaylistProvider = ({
4453
4487
  );
4454
4488
  const setCurrentTimeControl = useCallback19(
4455
4489
  (time) => {
4456
- currentTimeRef.current = time;
4490
+ setCurrentTimeRefs(time);
4457
4491
  setCurrentTime(time);
4458
4492
  },
4459
- [currentTimeRef]
4493
+ [setCurrentTimeRefs]
4460
4494
  );
4461
4495
  const setAutomaticScrollControl = useCallback19((enabled) => {
4462
4496
  setIsAutomaticScroll(enabled);
@@ -5356,7 +5390,7 @@ function useClipInteractionEnabled() {
5356
5390
  import { useContext as useContext6, useRef as useRef20, useState as useState17, useMemo as useMemo6, useCallback as useCallback22 } from "react";
5357
5391
  import { createPortal } from "react-dom";
5358
5392
  import styled4 from "styled-components";
5359
- import { getGlobalContext as getGlobalContext2 } from "@waveform-playlist/playout";
5393
+ import { getGlobalAudioContext as getGlobalAudioContext5 } from "@waveform-playlist/playout";
5360
5394
  import {
5361
5395
  Playlist,
5362
5396
  Track as TrackComponent,
@@ -5381,6 +5415,7 @@ import {
5381
5415
  SpectrogramLabels,
5382
5416
  CLIP_HEADER_HEIGHT
5383
5417
  } from "@waveform-playlist/ui-components";
5418
+ import { audibleLatencySamples } from "@waveform-playlist/core";
5384
5419
 
5385
5420
  // src/components/AnimatedPlayhead.tsx
5386
5421
  import { useRef as useRef18, useEffect as useEffect13 } from "react";
@@ -5402,7 +5437,13 @@ var PlayheadLine = styled2.div.attrs((props) => ({
5402
5437
  `;
5403
5438
  var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5404
5439
  const playheadRef = useRef18(null);
5405
- const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
5440
+ const {
5441
+ isPlaying,
5442
+ currentTimeRef,
5443
+ visualTimeRef,
5444
+ registerFrameCallback,
5445
+ unregisterFrameCallback
5446
+ } = usePlaybackAnimation();
5406
5447
  const { samplesPerPixel, sampleRate, progressBarWidth } = usePlaylistData();
5407
5448
  useEffect13(() => {
5408
5449
  const id = "playhead";
@@ -5417,9 +5458,9 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
5417
5458
  return () => unregisterFrameCallback(id);
5418
5459
  }, [isPlaying, registerFrameCallback, unregisterFrameCallback]);
5419
5460
  useEffect13(() => {
5420
- var _a;
5461
+ var _a, _b;
5421
5462
  if (!isPlaying && playheadRef.current) {
5422
- const time = (_a = currentTimeRef.current) != null ? _a : 0;
5463
+ const time = (_b = (_a = visualTimeRef.current) != null ? _a : currentTimeRef.current) != null ? _b : 0;
5423
5464
  const position = time * sampleRate / samplesPerPixel;
5424
5465
  playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;
5425
5466
  }
@@ -5497,7 +5538,13 @@ var ChannelWithProgress = (_a) => {
5497
5538
  const callbackId = useId();
5498
5539
  const theme = useTheme();
5499
5540
  const { waveHeight } = usePlaylistInfo();
5500
- const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
5541
+ const {
5542
+ isPlaying,
5543
+ currentTimeRef,
5544
+ visualTimeRef,
5545
+ registerFrameCallback,
5546
+ unregisterFrameCallback
5547
+ } = usePlaybackAnimation();
5501
5548
  const { samplesPerPixel, sampleRate } = usePlaylistData();
5502
5549
  const progressColor = (theme == null ? void 0 : theme.waveProgressColor) || "rgba(0, 0, 0, 0.1)";
5503
5550
  const clipPixelWidth = computeClipPixelWidth(
@@ -5534,9 +5581,9 @@ var ChannelWithProgress = (_a) => {
5534
5581
  unregisterFrameCallback
5535
5582
  ]);
5536
5583
  useEffect14(() => {
5537
- var _a2;
5584
+ var _a2, _b2;
5538
5585
  if (!isPlaying && progressRef.current) {
5539
- const currentTime = (_a2 = currentTimeRef.current) != null ? _a2 : 0;
5586
+ const currentTime = (_b2 = (_a2 = visualTimeRef.current) != null ? _a2 : currentTimeRef.current) != null ? _b2 : 0;
5540
5587
  const currentSample = currentTime * sampleRate;
5541
5588
  const clipEndSample = clipStartSample + clipDurationSamples;
5542
5589
  let ratio = 0;
@@ -5644,25 +5691,28 @@ var ControlSlot = styled4.div.attrs((props) => ({
5644
5691
  ${(props) => props.$isSelected && `background: ${props.theme.selectedTrackControlsBackground};`}
5645
5692
  `;
5646
5693
  var CustomPlayhead = ({ renderPlayhead, color, samplesPerPixel, sampleRate }) => {
5647
- var _a;
5694
+ var _a, _b;
5648
5695
  const {
5649
5696
  isPlaying,
5650
5697
  currentTimeRef,
5698
+ visualTimeRef,
5651
5699
  playbackStartTimeRef,
5652
5700
  audioStartPositionRef,
5653
5701
  getPlaybackTime
5654
5702
  } = usePlaybackAnimation();
5703
+ const visualTime = (_b = (_a = visualTimeRef.current) != null ? _a : currentTimeRef.current) != null ? _b : 0;
5655
5704
  return renderPlayhead({
5656
- position: ((_a = currentTimeRef.current) != null ? _a : 0) * sampleRate / samplesPerPixel,
5705
+ position: visualTime * sampleRate / samplesPerPixel,
5657
5706
  color,
5658
5707
  isPlaying,
5659
5708
  currentTimeRef,
5709
+ visualTimeRef,
5660
5710
  playbackStartTimeRef,
5661
5711
  audioStartPositionRef,
5662
5712
  samplesPerPixel,
5663
5713
  sampleRate,
5664
5714
  controlsOffset: 0,
5665
- getAudioContextTime: () => getGlobalContext2().rawContext.currentTime,
5715
+ getAudioContextTime: () => getGlobalAudioContext5().currentTime,
5666
5716
  getPlaybackTime
5667
5717
  });
5668
5718
  };
@@ -5687,7 +5737,7 @@ var PlaylistVisualization = ({
5687
5737
  }) => {
5688
5738
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
5689
5739
  const theme = useTheme2();
5690
- const { isPlaying } = usePlaybackAnimation();
5740
+ const { isPlaying, getLookAhead } = usePlaybackAnimation();
5691
5741
  const {
5692
5742
  selectionStart,
5693
5743
  selectionEnd,
@@ -6146,22 +6196,39 @@ var PlaylistVisualization = ({
6146
6196
  clip.clipId
6147
6197
  );
6148
6198
  }),
6149
- (recordingState == null ? void 0 : recordingState.isRecording) && recordingState.trackId === track.id && ((_d2 = recordingState.peaks[0]) == null ? void 0 : _d2.length) > 0 && /* @__PURE__ */ jsx10(
6150
- Clip,
6151
- {
6152
- clipId: "recording-preview",
6153
- trackIndex,
6154
- clipIndex: trackClipPeaks.length,
6155
- trackName: "Recording...",
6156
- startSample: recordingState.startSample,
6157
- durationSamples: recordingState.durationSamples,
6158
- samplesPerPixel,
6159
- showHeader: showClipHeaders,
6160
- disableHeaderDrag: true,
6161
- isSelected: track.id === selectedTrackId,
6162
- trackId: track.id,
6163
- children: (mono ? recordingState.peaks.slice(0, 1) : recordingState.peaks).map(
6164
- (channelPeaks, chIdx) => /* @__PURE__ */ jsx10(
6199
+ (recordingState == null ? void 0 : recordingState.isRecording) && recordingState.trackId === track.id && ((_d2 = recordingState.peaks[0]) == null ? void 0 : _d2.length) > 0 && (() => {
6200
+ const audioCtx = getGlobalAudioContext5();
6201
+ const outputLatency = "outputLatency" in audioCtx ? audioCtx.outputLatency : 0;
6202
+ const lookAhead = getLookAhead();
6203
+ const latencyOffsetSamples = audibleLatencySamples(
6204
+ outputLatency,
6205
+ lookAhead,
6206
+ sampleRate
6207
+ );
6208
+ const latencyPixels = Math.floor(latencyOffsetSamples / samplesPerPixel);
6209
+ const skipPeakElements = latencyPixels * 2;
6210
+ const previewDuration = Math.max(
6211
+ 0,
6212
+ recordingState.durationSamples - latencyOffsetSamples
6213
+ );
6214
+ const previewChannels = (mono ? recordingState.peaks.slice(0, 1) : recordingState.peaks).map(
6215
+ (channelPeaks) => skipPeakElements > 0 && skipPeakElements < channelPeaks.length ? channelPeaks.subarray(skipPeakElements) : channelPeaks
6216
+ );
6217
+ return /* @__PURE__ */ jsx10(
6218
+ Clip,
6219
+ {
6220
+ clipId: "recording-preview",
6221
+ trackIndex,
6222
+ clipIndex: trackClipPeaks.length,
6223
+ trackName: "Recording...",
6224
+ startSample: recordingState.startSample,
6225
+ durationSamples: previewDuration,
6226
+ samplesPerPixel,
6227
+ showHeader: showClipHeaders,
6228
+ disableHeaderDrag: true,
6229
+ isSelected: track.id === selectedTrackId,
6230
+ trackId: track.id,
6231
+ children: previewChannels.map((channelPeaks, chIdx) => /* @__PURE__ */ jsx10(
6165
6232
  ChannelWithProgress,
6166
6233
  {
6167
6234
  index: chIdx,
@@ -6170,14 +6237,14 @@ var PlaylistVisualization = ({
6170
6237
  length: Math.floor(channelPeaks.length / 2),
6171
6238
  isSelected: track.id === selectedTrackId,
6172
6239
  clipStartSample: recordingState.startSample,
6173
- clipDurationSamples: recordingState.durationSamples
6240
+ clipDurationSamples: previewDuration
6174
6241
  },
6175
6242
  `${track.id}-recording-${chIdx}`
6176
- )
6177
- )
6178
- },
6179
- `${track.id}-recording`
6180
- )
6243
+ ))
6244
+ },
6245
+ `${track.id}-recording`
6246
+ );
6247
+ })()
6181
6248
  ]
6182
6249
  },
6183
6250
  track.id