@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.d.mts +19 -2
- package/dist/index.d.ts +19 -2
- package/dist/index.js +109 -95
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +111 -96
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -10
package/dist/index.d.mts
CHANGED
|
@@ -41,6 +41,15 @@ interface TrackState$1 {
|
|
|
41
41
|
volume: number;
|
|
42
42
|
pan: number;
|
|
43
43
|
}
|
|
44
|
+
/** Per-frame data passed to registered animation callbacks. */
|
|
45
|
+
interface FrameData {
|
|
46
|
+
/** Raw engine time (for state/logic — NOT for visual positioning). */
|
|
47
|
+
readonly time: number;
|
|
48
|
+
/** time - outputLatency (for DOM positioning — matches speaker output). */
|
|
49
|
+
readonly visualTime: number;
|
|
50
|
+
readonly sampleRate: number;
|
|
51
|
+
readonly samplesPerPixel: number;
|
|
52
|
+
}
|
|
44
53
|
interface PlaybackAnimationContextValue {
|
|
45
54
|
isPlaying: boolean;
|
|
46
55
|
currentTime: number;
|
|
@@ -49,6 +58,10 @@ interface PlaybackAnimationContextValue {
|
|
|
49
58
|
audioStartPositionRef: React__default.RefObject<number>;
|
|
50
59
|
/** Returns current playback time from engine (auto-wraps at loop boundaries). */
|
|
51
60
|
getPlaybackTime: () => number;
|
|
61
|
+
/** Register a per-frame callback driven by the single animation loop. */
|
|
62
|
+
registerFrameCallback: (id: string, cb: (data: FrameData) => void) => void;
|
|
63
|
+
/** Unregister a per-frame callback. */
|
|
64
|
+
unregisterFrameCallback: (id: string) => void;
|
|
52
65
|
}
|
|
53
66
|
interface PlaylistStateContextValue {
|
|
54
67
|
continuousPlay: boolean;
|
|
@@ -186,6 +199,10 @@ interface WaveformPlaylistProviderProps {
|
|
|
186
199
|
/** Disable automatic stop when the cursor reaches the end of the longest
|
|
187
200
|
* track. Useful for DAW-style recording beyond existing audio. */
|
|
188
201
|
indefinitePlayback?: boolean;
|
|
202
|
+
/** Desired AudioContext sample rate. Creates a cross-browser AudioContext at
|
|
203
|
+
* this rate via standardized-audio-context. Pre-computed peaks (.dat files)
|
|
204
|
+
* render instantly when they match. On mismatch, falls back to worker. */
|
|
205
|
+
sampleRate?: number;
|
|
189
206
|
children: ReactNode;
|
|
190
207
|
}
|
|
191
208
|
declare const WaveformPlaylistProvider: React__default.FC<WaveformPlaylistProviderProps>;
|
|
@@ -1170,7 +1187,7 @@ declare const TimeFormatSelect: React__default.FC<{
|
|
|
1170
1187
|
}>;
|
|
1171
1188
|
/**
|
|
1172
1189
|
* Audio position display that uses the playlist context.
|
|
1173
|
-
*
|
|
1190
|
+
* Updates via the shared animation frame registry — no own rAF loop.
|
|
1174
1191
|
* Direct DOM manipulation avoids React re-renders.
|
|
1175
1192
|
*/
|
|
1176
1193
|
declare const AudioPosition: React__default.FC<{
|
|
@@ -1807,4 +1824,4 @@ declare function getWaveformDataMetadata(src: string): Promise<{
|
|
|
1807
1824
|
bits: 8 | 16;
|
|
1808
1825
|
}>;
|
|
1809
1826
|
|
|
1810
|
-
export { type ActiveEffect, type AnnotationIntegration, AnnotationIntegrationProvider, AudioPosition, type AudioTrackConfig, AutomaticScrollCheckbox, ClearAllButton, type ClearAllButtonProps, ClipCollisionModifier, ClipInteractionProvider, type ClipInteractionProviderProps, ContinuousPlayCheckbox, DownloadAnnotationsButton, EditableCheckbox, type EffectDefinition, type EffectInstance, type EffectParameter, type ExportOptions, type ExportResult, ExportWavButton, type ExportWavButtonProps, FastForwardButton, type GetAnnotationBoxLabelFn, KeyboardShortcuts, type KeyboardShortcutsProps, LinkEndpointsCheckbox, LoopButton, MasterVolumeControl, type MasterVolumeControls, type MediaElementAnimationContextValue, MediaElementAnnotationList, type MediaElementAnnotationListProps, type MediaElementControlsContextValue, type MediaElementDataContextValue, MediaElementPlaylist, type MediaElementPlaylistProps, MediaElementPlaylistProvider, type MediaElementStateContextValue, type MediaElementTrackConfig, MediaElementWaveform, type MediaElementWaveformProps, type OnAnnotationUpdateFn, type ParameterType, PauseButton, PlayButton, PlaylistAnnotationList, type PlaylistAnnotationListProps, PlaylistVisualization, type PlaylistVisualizationProps, RewindButton, SelectionTimeInputs, SetLoopRegionButton, SkipBackwardButton, SkipForwardButton, SnapToGridModifier, type SpectrogramIntegration, SpectrogramIntegrationProvider, StopButton, type TimeFormatControls, TimeFormatSelect, type TrackActiveEffect, type TrackEffectsState, type TrackLoadError, type TrackSource, type TrackState$1 as TrackState, type UseDynamicEffectsReturn, type UseDynamicTracksReturn, type UseExportWavReturn, type UseOutputMeterOptions, type UseOutputMeterReturn, type UsePlaybackShortcutsOptions, type UsePlaybackShortcutsReturn, type UseTrackDynamicEffectsReturn, Waveform, WaveformPlaylistProvider, type WaveformProps, type WaveformTrack, type ZoomControls, ZoomInButton, ZoomOutButton, createEffectChain, createEffectInstance, effectCategories, effectDefinitions, getEffectDefinition, getEffectsByCategory, getWaveformDataMetadata, loadPeaksFromWaveformData, loadWaveformData, noDropAnimationPlugins, useAnnotationDragHandlers, useAnnotationIntegration, useAnnotationKeyboardControls, useAudioTracks, useClipDragHandlers, useClipInteractionEnabled, useClipSplitting, useDragSensors, useDynamicEffects, useDynamicTracks, useExportWav, useKeyboardShortcuts, useMasterAnalyser, useMasterVolume, useMediaElementAnimation, useMediaElementControls, useMediaElementData, useMediaElementState, useOutputMeter, usePlaybackAnimation, usePlaybackShortcuts, usePlaylistControls, usePlaylistData, usePlaylistState, useSpectrogramIntegration, useTimeFormat, useTrackDynamicEffects, useZoomControls, waveformDataToPeaks };
|
|
1827
|
+
export { type ActiveEffect, type AnnotationIntegration, AnnotationIntegrationProvider, AudioPosition, type AudioTrackConfig, AutomaticScrollCheckbox, ClearAllButton, type ClearAllButtonProps, ClipCollisionModifier, ClipInteractionProvider, type ClipInteractionProviderProps, ContinuousPlayCheckbox, DownloadAnnotationsButton, EditableCheckbox, type EffectDefinition, type EffectInstance, type EffectParameter, type ExportOptions, type ExportResult, ExportWavButton, type ExportWavButtonProps, FastForwardButton, type FrameData, type GetAnnotationBoxLabelFn, KeyboardShortcuts, type KeyboardShortcutsProps, LinkEndpointsCheckbox, LoopButton, MasterVolumeControl, type MasterVolumeControls, type MediaElementAnimationContextValue, MediaElementAnnotationList, type MediaElementAnnotationListProps, type MediaElementControlsContextValue, type MediaElementDataContextValue, MediaElementPlaylist, type MediaElementPlaylistProps, MediaElementPlaylistProvider, type MediaElementStateContextValue, type MediaElementTrackConfig, MediaElementWaveform, type MediaElementWaveformProps, type OnAnnotationUpdateFn, type ParameterType, PauseButton, PlayButton, PlaylistAnnotationList, type PlaylistAnnotationListProps, PlaylistVisualization, type PlaylistVisualizationProps, RewindButton, SelectionTimeInputs, SetLoopRegionButton, SkipBackwardButton, SkipForwardButton, SnapToGridModifier, type SpectrogramIntegration, SpectrogramIntegrationProvider, StopButton, type TimeFormatControls, TimeFormatSelect, type TrackActiveEffect, type TrackEffectsState, type TrackLoadError, type TrackSource, type TrackState$1 as TrackState, type UseDynamicEffectsReturn, type UseDynamicTracksReturn, type UseExportWavReturn, type UseOutputMeterOptions, type UseOutputMeterReturn, type UsePlaybackShortcutsOptions, type UsePlaybackShortcutsReturn, type UseTrackDynamicEffectsReturn, Waveform, WaveformPlaylistProvider, type WaveformProps, type WaveformTrack, type ZoomControls, ZoomInButton, ZoomOutButton, createEffectChain, createEffectInstance, effectCategories, effectDefinitions, getEffectDefinition, getEffectsByCategory, getWaveformDataMetadata, loadPeaksFromWaveformData, loadWaveformData, noDropAnimationPlugins, useAnnotationDragHandlers, useAnnotationIntegration, useAnnotationKeyboardControls, useAudioTracks, useClipDragHandlers, useClipInteractionEnabled, useClipSplitting, useDragSensors, useDynamicEffects, useDynamicTracks, useExportWav, useKeyboardShortcuts, useMasterAnalyser, useMasterVolume, useMediaElementAnimation, useMediaElementControls, useMediaElementData, useMediaElementState, useOutputMeter, usePlaybackAnimation, usePlaybackShortcuts, usePlaylistControls, usePlaylistData, usePlaylistState, useSpectrogramIntegration, useTimeFormat, useTrackDynamicEffects, useZoomControls, waveformDataToPeaks };
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,15 @@ interface TrackState$1 {
|
|
|
41
41
|
volume: number;
|
|
42
42
|
pan: number;
|
|
43
43
|
}
|
|
44
|
+
/** Per-frame data passed to registered animation callbacks. */
|
|
45
|
+
interface FrameData {
|
|
46
|
+
/** Raw engine time (for state/logic — NOT for visual positioning). */
|
|
47
|
+
readonly time: number;
|
|
48
|
+
/** time - outputLatency (for DOM positioning — matches speaker output). */
|
|
49
|
+
readonly visualTime: number;
|
|
50
|
+
readonly sampleRate: number;
|
|
51
|
+
readonly samplesPerPixel: number;
|
|
52
|
+
}
|
|
44
53
|
interface PlaybackAnimationContextValue {
|
|
45
54
|
isPlaying: boolean;
|
|
46
55
|
currentTime: number;
|
|
@@ -49,6 +58,10 @@ interface PlaybackAnimationContextValue {
|
|
|
49
58
|
audioStartPositionRef: React__default.RefObject<number>;
|
|
50
59
|
/** Returns current playback time from engine (auto-wraps at loop boundaries). */
|
|
51
60
|
getPlaybackTime: () => number;
|
|
61
|
+
/** Register a per-frame callback driven by the single animation loop. */
|
|
62
|
+
registerFrameCallback: (id: string, cb: (data: FrameData) => void) => void;
|
|
63
|
+
/** Unregister a per-frame callback. */
|
|
64
|
+
unregisterFrameCallback: (id: string) => void;
|
|
52
65
|
}
|
|
53
66
|
interface PlaylistStateContextValue {
|
|
54
67
|
continuousPlay: boolean;
|
|
@@ -186,6 +199,10 @@ interface WaveformPlaylistProviderProps {
|
|
|
186
199
|
/** Disable automatic stop when the cursor reaches the end of the longest
|
|
187
200
|
* track. Useful for DAW-style recording beyond existing audio. */
|
|
188
201
|
indefinitePlayback?: boolean;
|
|
202
|
+
/** Desired AudioContext sample rate. Creates a cross-browser AudioContext at
|
|
203
|
+
* this rate via standardized-audio-context. Pre-computed peaks (.dat files)
|
|
204
|
+
* render instantly when they match. On mismatch, falls back to worker. */
|
|
205
|
+
sampleRate?: number;
|
|
189
206
|
children: ReactNode;
|
|
190
207
|
}
|
|
191
208
|
declare const WaveformPlaylistProvider: React__default.FC<WaveformPlaylistProviderProps>;
|
|
@@ -1170,7 +1187,7 @@ declare const TimeFormatSelect: React__default.FC<{
|
|
|
1170
1187
|
}>;
|
|
1171
1188
|
/**
|
|
1172
1189
|
* Audio position display that uses the playlist context.
|
|
1173
|
-
*
|
|
1190
|
+
* Updates via the shared animation frame registry — no own rAF loop.
|
|
1174
1191
|
* Direct DOM manipulation avoids React re-renders.
|
|
1175
1192
|
*/
|
|
1176
1193
|
declare const AudioPosition: React__default.FC<{
|
|
@@ -1807,4 +1824,4 @@ declare function getWaveformDataMetadata(src: string): Promise<{
|
|
|
1807
1824
|
bits: 8 | 16;
|
|
1808
1825
|
}>;
|
|
1809
1826
|
|
|
1810
|
-
export { type ActiveEffect, type AnnotationIntegration, AnnotationIntegrationProvider, AudioPosition, type AudioTrackConfig, AutomaticScrollCheckbox, ClearAllButton, type ClearAllButtonProps, ClipCollisionModifier, ClipInteractionProvider, type ClipInteractionProviderProps, ContinuousPlayCheckbox, DownloadAnnotationsButton, EditableCheckbox, type EffectDefinition, type EffectInstance, type EffectParameter, type ExportOptions, type ExportResult, ExportWavButton, type ExportWavButtonProps, FastForwardButton, type GetAnnotationBoxLabelFn, KeyboardShortcuts, type KeyboardShortcutsProps, LinkEndpointsCheckbox, LoopButton, MasterVolumeControl, type MasterVolumeControls, type MediaElementAnimationContextValue, MediaElementAnnotationList, type MediaElementAnnotationListProps, type MediaElementControlsContextValue, type MediaElementDataContextValue, MediaElementPlaylist, type MediaElementPlaylistProps, MediaElementPlaylistProvider, type MediaElementStateContextValue, type MediaElementTrackConfig, MediaElementWaveform, type MediaElementWaveformProps, type OnAnnotationUpdateFn, type ParameterType, PauseButton, PlayButton, PlaylistAnnotationList, type PlaylistAnnotationListProps, PlaylistVisualization, type PlaylistVisualizationProps, RewindButton, SelectionTimeInputs, SetLoopRegionButton, SkipBackwardButton, SkipForwardButton, SnapToGridModifier, type SpectrogramIntegration, SpectrogramIntegrationProvider, StopButton, type TimeFormatControls, TimeFormatSelect, type TrackActiveEffect, type TrackEffectsState, type TrackLoadError, type TrackSource, type TrackState$1 as TrackState, type UseDynamicEffectsReturn, type UseDynamicTracksReturn, type UseExportWavReturn, type UseOutputMeterOptions, type UseOutputMeterReturn, type UsePlaybackShortcutsOptions, type UsePlaybackShortcutsReturn, type UseTrackDynamicEffectsReturn, Waveform, WaveformPlaylistProvider, type WaveformProps, type WaveformTrack, type ZoomControls, ZoomInButton, ZoomOutButton, createEffectChain, createEffectInstance, effectCategories, effectDefinitions, getEffectDefinition, getEffectsByCategory, getWaveformDataMetadata, loadPeaksFromWaveformData, loadWaveformData, noDropAnimationPlugins, useAnnotationDragHandlers, useAnnotationIntegration, useAnnotationKeyboardControls, useAudioTracks, useClipDragHandlers, useClipInteractionEnabled, useClipSplitting, useDragSensors, useDynamicEffects, useDynamicTracks, useExportWav, useKeyboardShortcuts, useMasterAnalyser, useMasterVolume, useMediaElementAnimation, useMediaElementControls, useMediaElementData, useMediaElementState, useOutputMeter, usePlaybackAnimation, usePlaybackShortcuts, usePlaylistControls, usePlaylistData, usePlaylistState, useSpectrogramIntegration, useTimeFormat, useTrackDynamicEffects, useZoomControls, waveformDataToPeaks };
|
|
1827
|
+
export { type ActiveEffect, type AnnotationIntegration, AnnotationIntegrationProvider, AudioPosition, type AudioTrackConfig, AutomaticScrollCheckbox, ClearAllButton, type ClearAllButtonProps, ClipCollisionModifier, ClipInteractionProvider, type ClipInteractionProviderProps, ContinuousPlayCheckbox, DownloadAnnotationsButton, EditableCheckbox, type EffectDefinition, type EffectInstance, type EffectParameter, type ExportOptions, type ExportResult, ExportWavButton, type ExportWavButtonProps, FastForwardButton, type FrameData, type GetAnnotationBoxLabelFn, KeyboardShortcuts, type KeyboardShortcutsProps, LinkEndpointsCheckbox, LoopButton, MasterVolumeControl, type MasterVolumeControls, type MediaElementAnimationContextValue, MediaElementAnnotationList, type MediaElementAnnotationListProps, type MediaElementControlsContextValue, type MediaElementDataContextValue, MediaElementPlaylist, type MediaElementPlaylistProps, MediaElementPlaylistProvider, type MediaElementStateContextValue, type MediaElementTrackConfig, MediaElementWaveform, type MediaElementWaveformProps, type OnAnnotationUpdateFn, type ParameterType, PauseButton, PlayButton, PlaylistAnnotationList, type PlaylistAnnotationListProps, PlaylistVisualization, type PlaylistVisualizationProps, RewindButton, SelectionTimeInputs, SetLoopRegionButton, SkipBackwardButton, SkipForwardButton, SnapToGridModifier, type SpectrogramIntegration, SpectrogramIntegrationProvider, StopButton, type TimeFormatControls, TimeFormatSelect, type TrackActiveEffect, type TrackEffectsState, type TrackLoadError, type TrackSource, type TrackState$1 as TrackState, type UseDynamicEffectsReturn, type UseDynamicTracksReturn, type UseExportWavReturn, type UseOutputMeterOptions, type UseOutputMeterReturn, type UsePlaybackShortcutsOptions, type UsePlaybackShortcutsReturn, type UseTrackDynamicEffectsReturn, Waveform, WaveformPlaylistProvider, type WaveformProps, type WaveformTrack, type ZoomControls, ZoomInButton, ZoomOutButton, createEffectChain, createEffectInstance, effectCategories, effectDefinitions, getEffectDefinition, getEffectsByCategory, getWaveformDataMetadata, loadPeaksFromWaveformData, loadWaveformData, noDropAnimationPlugins, useAnnotationDragHandlers, useAnnotationIntegration, useAnnotationKeyboardControls, useAudioTracks, useClipDragHandlers, useClipInteractionEnabled, useClipSplitting, useDragSensors, useDynamicEffects, useDynamicTracks, useExportWav, useKeyboardShortcuts, useMasterAnalyser, useMasterVolume, useMediaElementAnimation, useMediaElementControls, useMediaElementData, useMediaElementState, useOutputMeter, usePlaybackAnimation, usePlaybackShortcuts, usePlaylistControls, usePlaylistData, usePlaylistState, useSpectrogramIntegration, useTimeFormat, useTrackDynamicEffects, useZoomControls, waveformDataToPeaks };
|
package/dist/index.js
CHANGED
|
@@ -172,11 +172,13 @@ var import_tone4 = require("tone");
|
|
|
172
172
|
var import_waveform_data = __toESM(require("waveform-data"));
|
|
173
173
|
function loadWaveformData(src) {
|
|
174
174
|
return __async(this, null, function* () {
|
|
175
|
+
var _a, _b;
|
|
175
176
|
const response = yield fetch(src);
|
|
176
177
|
if (!response.ok) {
|
|
177
178
|
throw new Error(`Failed to fetch waveform data: ${response.statusText}`);
|
|
178
179
|
}
|
|
179
|
-
const
|
|
180
|
+
const { pathname } = new URL(src, (_b = (_a = globalThis.location) == null ? void 0 : _a.href) != null ? _b : "http://localhost");
|
|
181
|
+
const isBinary = pathname.toLowerCase().endsWith(".dat");
|
|
180
182
|
if (isBinary) {
|
|
181
183
|
const arrayBuffer = yield response.arrayBuffer();
|
|
182
184
|
return import_waveform_data.default.create(arrayBuffer);
|
|
@@ -3764,6 +3766,7 @@ var WaveformPlaylistProvider = ({
|
|
|
3764
3766
|
soundFontCache,
|
|
3765
3767
|
deferEngineRebuild = false,
|
|
3766
3768
|
indefinitePlayback = false,
|
|
3769
|
+
sampleRate: sampleRateProp,
|
|
3767
3770
|
children
|
|
3768
3771
|
}) => {
|
|
3769
3772
|
var _a, _b, _c, _d;
|
|
@@ -3819,6 +3822,7 @@ var WaveformPlaylistProvider = ({
|
|
|
3819
3822
|
const playbackEndTimeRef = (0, import_react24.useRef)(null);
|
|
3820
3823
|
const scrollContainerRef = (0, import_react24.useRef)(null);
|
|
3821
3824
|
const isAutomaticScrollRef = (0, import_react24.useRef)(false);
|
|
3825
|
+
const frameCallbacksRef = (0, import_react24.useRef)(/* @__PURE__ */ new Map());
|
|
3822
3826
|
const continuousPlayRef = (0, import_react24.useRef)((_d = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _d : false);
|
|
3823
3827
|
const activeAnnotationIdRef = (0, import_react24.useRef)(null);
|
|
3824
3828
|
const engineTracksRef = (0, import_react24.useRef)(null);
|
|
@@ -3827,9 +3831,21 @@ var WaveformPlaylistProvider = ({
|
|
|
3827
3831
|
const isDraggingRef = (0, import_react24.useRef)(false);
|
|
3828
3832
|
const prevTracksRef = (0, import_react24.useRef)([]);
|
|
3829
3833
|
const samplesPerPixelRef = (0, import_react24.useRef)(initialSamplesPerPixel);
|
|
3830
|
-
const
|
|
3831
|
-
typeof AudioContext
|
|
3832
|
-
|
|
3834
|
+
const [initialSampleRate] = (0, import_react24.useState)(() => {
|
|
3835
|
+
if (typeof AudioContext === "undefined") return sampleRateProp != null ? sampleRateProp : 48e3;
|
|
3836
|
+
try {
|
|
3837
|
+
if (sampleRateProp !== void 0) {
|
|
3838
|
+
return (0, import_playout5.configureGlobalContext)({ sampleRate: sampleRateProp });
|
|
3839
|
+
}
|
|
3840
|
+
return (0, import_playout5.getGlobalAudioContext)().sampleRate;
|
|
3841
|
+
} catch (err) {
|
|
3842
|
+
console.warn(
|
|
3843
|
+
"[waveform-playlist] Failed to configure AudioContext: " + String(err) + " \u2014 falling back to " + (sampleRateProp != null ? sampleRateProp : 48e3) + " Hz"
|
|
3844
|
+
);
|
|
3845
|
+
return sampleRateProp != null ? sampleRateProp : 48e3;
|
|
3846
|
+
}
|
|
3847
|
+
});
|
|
3848
|
+
const sampleRateRef = (0, import_react24.useRef)(initialSampleRate);
|
|
3833
3849
|
const { timeFormat, setTimeFormat, formatTime: formatTime2 } = useTimeFormat();
|
|
3834
3850
|
const zoom = useZoomControls({ engineRef, initialSamplesPerPixel });
|
|
3835
3851
|
const { samplesPerPixel, onEngineState: onZoomEngineState } = zoom;
|
|
@@ -4179,15 +4195,28 @@ var WaveformPlaylistProvider = ({
|
|
|
4179
4195
|
let peaks;
|
|
4180
4196
|
if (clip.waveformData) {
|
|
4181
4197
|
try {
|
|
4198
|
+
const wdRate = clip.waveformData.sample_rate;
|
|
4199
|
+
const clipRate = clip.sampleRate;
|
|
4200
|
+
let peakOffset = clip.offsetSamples;
|
|
4201
|
+
let peakDuration = clip.durationSamples;
|
|
4202
|
+
let peakSpp = samplesPerPixel;
|
|
4203
|
+
if (wdRate !== clipRate && clipRate > 0 && wdRate > 0) {
|
|
4204
|
+
const ratio = wdRate / clipRate;
|
|
4205
|
+
peakOffset = Math.round(clip.offsetSamples * ratio);
|
|
4206
|
+
peakDuration = Math.round(clip.durationSamples * ratio);
|
|
4207
|
+
peakSpp = Math.max(1, Math.round(samplesPerPixel * ratio));
|
|
4208
|
+
}
|
|
4182
4209
|
peaks = extractPeaksFromWaveformDataFull(
|
|
4183
4210
|
clip.waveformData,
|
|
4184
|
-
|
|
4211
|
+
peakSpp,
|
|
4185
4212
|
mono,
|
|
4186
|
-
|
|
4187
|
-
|
|
4213
|
+
peakOffset,
|
|
4214
|
+
peakDuration
|
|
4188
4215
|
);
|
|
4189
4216
|
} catch (err) {
|
|
4190
|
-
console.warn(
|
|
4217
|
+
console.warn(
|
|
4218
|
+
"[waveform-playlist] Failed to extract peaks from waveformData: " + String(err)
|
|
4219
|
+
);
|
|
4191
4220
|
}
|
|
4192
4221
|
}
|
|
4193
4222
|
if (!peaks) {
|
|
@@ -4202,7 +4231,9 @@ var WaveformPlaylistProvider = ({
|
|
|
4202
4231
|
clip.durationSamples
|
|
4203
4232
|
);
|
|
4204
4233
|
} catch (err) {
|
|
4205
|
-
console.warn(
|
|
4234
|
+
console.warn(
|
|
4235
|
+
"[waveform-playlist] Failed to extract peaks from cache: " + String(err)
|
|
4236
|
+
);
|
|
4206
4237
|
}
|
|
4207
4238
|
}
|
|
4208
4239
|
}
|
|
@@ -4252,10 +4283,30 @@ var WaveformPlaylistProvider = ({
|
|
|
4252
4283
|
const elapsed = (0, import_tone4.getContext)().currentTime - ((_a2 = playbackStartTimeRef.current) != null ? _a2 : 0);
|
|
4253
4284
|
return ((_b2 = audioStartPositionRef.current) != null ? _b2 : 0) + elapsed;
|
|
4254
4285
|
}, []);
|
|
4286
|
+
const registerFrameCallback = (0, import_react24.useCallback)((id, cb) => {
|
|
4287
|
+
frameCallbacksRef.current.set(id, cb);
|
|
4288
|
+
}, []);
|
|
4289
|
+
const unregisterFrameCallback = (0, import_react24.useCallback)((id) => {
|
|
4290
|
+
frameCallbacksRef.current.delete(id);
|
|
4291
|
+
}, []);
|
|
4255
4292
|
const startAnimationLoop = (0, import_react24.useCallback)(() => {
|
|
4293
|
+
const audioCtx = (0, import_playout5.getGlobalAudioContext)();
|
|
4256
4294
|
const updateTime = () => {
|
|
4257
4295
|
const time = getPlaybackTime();
|
|
4258
4296
|
currentTimeRef.current = time;
|
|
4297
|
+
const latency = "outputLatency" in audioCtx ? audioCtx.outputLatency : 0;
|
|
4298
|
+
const visualTime = Math.max(0, time - latency);
|
|
4299
|
+
const sr = sampleRateRef.current;
|
|
4300
|
+
const spp = samplesPerPixelRef.current;
|
|
4301
|
+
const frameData = {
|
|
4302
|
+
time,
|
|
4303
|
+
visualTime,
|
|
4304
|
+
sampleRate: sr,
|
|
4305
|
+
samplesPerPixel: spp
|
|
4306
|
+
};
|
|
4307
|
+
for (const cb of frameCallbacksRef.current.values()) {
|
|
4308
|
+
cb(frameData);
|
|
4309
|
+
}
|
|
4259
4310
|
const currentAnnotations = annotationsRef.current;
|
|
4260
4311
|
if (currentAnnotations.length > 0) {
|
|
4261
4312
|
const currentAnnotation = currentAnnotations.find(
|
|
@@ -4290,10 +4341,9 @@ var WaveformPlaylistProvider = ({
|
|
|
4290
4341
|
}
|
|
4291
4342
|
if (isAutomaticScrollRef.current && scrollContainerRef.current && duration > 0) {
|
|
4292
4343
|
const container = scrollContainerRef.current;
|
|
4293
|
-
const
|
|
4294
|
-
const pixelPosition = time * sr / samplesPerPixelRef.current;
|
|
4344
|
+
const pixelPosition = visualTime * sr / spp;
|
|
4295
4345
|
const containerWidth = container.clientWidth;
|
|
4296
|
-
const targetScrollLeft = Math.max(0, pixelPosition - containerWidth / 2);
|
|
4346
|
+
const targetScrollLeft = Math.round(Math.max(0, pixelPosition - containerWidth / 2));
|
|
4297
4347
|
container.scrollLeft = targetScrollLeft;
|
|
4298
4348
|
}
|
|
4299
4349
|
if (playbackEndTimeRef.current !== null && time >= playbackEndTimeRef.current) {
|
|
@@ -4529,7 +4579,9 @@ var WaveformPlaylistProvider = ({
|
|
|
4529
4579
|
currentTimeRef,
|
|
4530
4580
|
playbackStartTimeRef,
|
|
4531
4581
|
audioStartPositionRef,
|
|
4532
|
-
getPlaybackTime
|
|
4582
|
+
getPlaybackTime,
|
|
4583
|
+
registerFrameCallback,
|
|
4584
|
+
unregisterFrameCallback
|
|
4533
4585
|
}),
|
|
4534
4586
|
[
|
|
4535
4587
|
isPlaying,
|
|
@@ -4537,7 +4589,9 @@ var WaveformPlaylistProvider = ({
|
|
|
4537
4589
|
currentTimeRef,
|
|
4538
4590
|
playbackStartTimeRef,
|
|
4539
4591
|
audioStartPositionRef,
|
|
4540
|
-
getPlaybackTime
|
|
4592
|
+
getPlaybackTime,
|
|
4593
|
+
registerFrameCallback,
|
|
4594
|
+
unregisterFrameCallback
|
|
4541
4595
|
]
|
|
4542
4596
|
);
|
|
4543
4597
|
const stateValue = (0, import_react24.useMemo)(
|
|
@@ -5318,32 +5372,19 @@ var PositionDisplay = import_styled_components3.default.span`
|
|
|
5318
5372
|
var AudioPosition = ({ className }) => {
|
|
5319
5373
|
var _a;
|
|
5320
5374
|
const timeRef = (0, import_react27.useRef)(null);
|
|
5321
|
-
const
|
|
5322
|
-
const { isPlaying, currentTimeRef, getPlaybackTime } = usePlaybackAnimation();
|
|
5375
|
+
const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
|
|
5323
5376
|
const { timeFormat: format } = usePlaylistData();
|
|
5324
5377
|
(0, import_react27.useEffect)(() => {
|
|
5325
|
-
const
|
|
5326
|
-
var _a2;
|
|
5327
|
-
if (timeRef.current) {
|
|
5328
|
-
const time = isPlaying ? getPlaybackTime() : (_a2 = currentTimeRef.current) != null ? _a2 : 0;
|
|
5329
|
-
timeRef.current.textContent = (0, import_ui_components6.formatTime)(time, format);
|
|
5330
|
-
}
|
|
5331
|
-
if (isPlaying) {
|
|
5332
|
-
animationFrameRef.current = requestAnimationFrame(updateTime);
|
|
5333
|
-
}
|
|
5334
|
-
};
|
|
5378
|
+
const id = "audio-position";
|
|
5335
5379
|
if (isPlaying) {
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5380
|
+
registerFrameCallback(id, ({ time }) => {
|
|
5381
|
+
if (timeRef.current) {
|
|
5382
|
+
timeRef.current.textContent = (0, import_ui_components6.formatTime)(time, format);
|
|
5383
|
+
}
|
|
5384
|
+
});
|
|
5339
5385
|
}
|
|
5340
|
-
return () =>
|
|
5341
|
-
|
|
5342
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
5343
|
-
animationFrameRef.current = null;
|
|
5344
|
-
}
|
|
5345
|
-
};
|
|
5346
|
-
}, [isPlaying, format, currentTimeRef, getPlaybackTime]);
|
|
5386
|
+
return () => unregisterFrameCallback(id);
|
|
5387
|
+
}, [isPlaying, format, registerFrameCallback, unregisterFrameCallback]);
|
|
5347
5388
|
(0, import_react27.useEffect)(() => {
|
|
5348
5389
|
var _a2;
|
|
5349
5390
|
if (!isPlaying && timeRef.current) {
|
|
@@ -5503,33 +5544,20 @@ var PlayheadLine = import_styled_components4.default.div.attrs((props) => ({
|
|
|
5503
5544
|
`;
|
|
5504
5545
|
var AnimatedPlayhead = ({ color = "#ff0000" }) => {
|
|
5505
5546
|
const playheadRef = (0, import_react30.useRef)(null);
|
|
5506
|
-
const
|
|
5507
|
-
const { isPlaying, currentTimeRef, getPlaybackTime } = usePlaybackAnimation();
|
|
5547
|
+
const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
|
|
5508
5548
|
const { samplesPerPixel, sampleRate, progressBarWidth } = usePlaylistData();
|
|
5509
5549
|
(0, import_react30.useEffect)(() => {
|
|
5510
|
-
const
|
|
5511
|
-
var _a;
|
|
5512
|
-
if (playheadRef.current) {
|
|
5513
|
-
const time = isPlaying ? getPlaybackTime() : (_a = currentTimeRef.current) != null ? _a : 0;
|
|
5514
|
-
const position = time * sampleRate / samplesPerPixel;
|
|
5515
|
-
playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;
|
|
5516
|
-
}
|
|
5517
|
-
if (isPlaying) {
|
|
5518
|
-
animationFrameRef.current = requestAnimationFrame(updatePosition);
|
|
5519
|
-
}
|
|
5520
|
-
};
|
|
5550
|
+
const id = "playhead";
|
|
5521
5551
|
if (isPlaying) {
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5552
|
+
registerFrameCallback(id, ({ visualTime, sampleRate: sr, samplesPerPixel: spp }) => {
|
|
5553
|
+
if (playheadRef.current) {
|
|
5554
|
+
const px = visualTime * sr / spp;
|
|
5555
|
+
playheadRef.current.style.transform = `translate3d(${px}px, 0, 0)`;
|
|
5556
|
+
}
|
|
5557
|
+
});
|
|
5525
5558
|
}
|
|
5526
|
-
return () =>
|
|
5527
|
-
|
|
5528
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
5529
|
-
animationFrameRef.current = null;
|
|
5530
|
-
}
|
|
5531
|
-
};
|
|
5532
|
-
}, [isPlaying, sampleRate, samplesPerPixel, currentTimeRef, getPlaybackTime]);
|
|
5559
|
+
return () => unregisterFrameCallback(id);
|
|
5560
|
+
}, [isPlaying, registerFrameCallback, unregisterFrameCallback]);
|
|
5533
5561
|
(0, import_react30.useEffect)(() => {
|
|
5534
5562
|
var _a;
|
|
5535
5563
|
if (!isPlaying && playheadRef.current) {
|
|
@@ -5601,10 +5629,10 @@ var ChannelWithProgress = (_a) => {
|
|
|
5601
5629
|
"clipOffsetSeconds"
|
|
5602
5630
|
]);
|
|
5603
5631
|
const progressRef = (0, import_react31.useRef)(null);
|
|
5604
|
-
const
|
|
5632
|
+
const callbackId = (0, import_react31.useId)();
|
|
5605
5633
|
const theme = (0, import_ui_components8.useTheme)();
|
|
5606
5634
|
const { waveHeight } = (0, import_ui_components8.usePlaylistInfo)();
|
|
5607
|
-
const { isPlaying, currentTimeRef,
|
|
5635
|
+
const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
|
|
5608
5636
|
const { samplesPerPixel, sampleRate } = usePlaylistData();
|
|
5609
5637
|
const progressColor = (theme == null ? void 0 : theme.waveProgressColor) || "rgba(0, 0, 0, 0.1)";
|
|
5610
5638
|
const clipPixelWidth = (0, import_core6.clipPixelWidth)(
|
|
@@ -5613,46 +5641,32 @@ var ChannelWithProgress = (_a) => {
|
|
|
5613
5641
|
samplesPerPixel
|
|
5614
5642
|
);
|
|
5615
5643
|
(0, import_react31.useEffect)(() => {
|
|
5616
|
-
const updateProgress = () => {
|
|
5617
|
-
var _a2;
|
|
5618
|
-
if (progressRef.current) {
|
|
5619
|
-
const currentTime = isPlaying ? getPlaybackTime() : (_a2 = currentTimeRef.current) != null ? _a2 : 0;
|
|
5620
|
-
const currentSample = currentTime * sampleRate;
|
|
5621
|
-
const clipEndSample = clipStartSample + clipDurationSamples;
|
|
5622
|
-
let ratio = 0;
|
|
5623
|
-
if (currentSample <= clipStartSample) {
|
|
5624
|
-
ratio = 0;
|
|
5625
|
-
} else if (currentSample >= clipEndSample) {
|
|
5626
|
-
ratio = 1;
|
|
5627
|
-
} else {
|
|
5628
|
-
const playedSamples = currentSample - clipStartSample;
|
|
5629
|
-
ratio = playedSamples / clipDurationSamples;
|
|
5630
|
-
}
|
|
5631
|
-
progressRef.current.style.transform = `scaleX(${ratio})`;
|
|
5632
|
-
}
|
|
5633
|
-
if (isPlaying) {
|
|
5634
|
-
animationFrameRef.current = requestAnimationFrame(updateProgress);
|
|
5635
|
-
}
|
|
5636
|
-
};
|
|
5637
5644
|
if (isPlaying) {
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5645
|
+
registerFrameCallback(callbackId, ({ visualTime, sampleRate: sr }) => {
|
|
5646
|
+
if (progressRef.current) {
|
|
5647
|
+
const currentSample = visualTime * sr;
|
|
5648
|
+
const clipEndSample = clipStartSample + clipDurationSamples;
|
|
5649
|
+
let ratio = 0;
|
|
5650
|
+
if (currentSample <= clipStartSample) {
|
|
5651
|
+
ratio = 0;
|
|
5652
|
+
} else if (currentSample >= clipEndSample) {
|
|
5653
|
+
ratio = 1;
|
|
5654
|
+
} else {
|
|
5655
|
+
const playedSamples = currentSample - clipStartSample;
|
|
5656
|
+
ratio = playedSamples / clipDurationSamples;
|
|
5657
|
+
}
|
|
5658
|
+
progressRef.current.style.transform = `scaleX(${ratio})`;
|
|
5659
|
+
}
|
|
5660
|
+
});
|
|
5641
5661
|
}
|
|
5642
|
-
return () =>
|
|
5643
|
-
if (animationFrameRef.current) {
|
|
5644
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
5645
|
-
animationFrameRef.current = null;
|
|
5646
|
-
}
|
|
5647
|
-
};
|
|
5662
|
+
return () => unregisterFrameCallback(callbackId);
|
|
5648
5663
|
}, [
|
|
5649
5664
|
isPlaying,
|
|
5650
|
-
sampleRate,
|
|
5651
5665
|
clipStartSample,
|
|
5652
5666
|
clipDurationSamples,
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5667
|
+
callbackId,
|
|
5668
|
+
registerFrameCallback,
|
|
5669
|
+
unregisterFrameCallback
|
|
5656
5670
|
]);
|
|
5657
5671
|
(0, import_react31.useEffect)(() => {
|
|
5658
5672
|
var _a2;
|