@waveform-playlist/browser 11.2.0 → 11.3.1
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 +20 -7
- package/dist/index.d.ts +20 -7
- package/dist/index.js +156 -293
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +140 -272
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -10
package/dist/index.mjs
CHANGED
|
@@ -2675,6 +2675,12 @@ function useTrackDynamicEffects() {
|
|
|
2675
2675
|
|
|
2676
2676
|
// src/hooks/useExportWav.ts
|
|
2677
2677
|
import { useState as useState11, useCallback as useCallback14 } from "react";
|
|
2678
|
+
import {
|
|
2679
|
+
gainToDb,
|
|
2680
|
+
trackChannelCount,
|
|
2681
|
+
applyFadeIn,
|
|
2682
|
+
applyFadeOut
|
|
2683
|
+
} from "@waveform-playlist/core";
|
|
2678
2684
|
import {
|
|
2679
2685
|
getUnderlyingAudioParam,
|
|
2680
2686
|
getGlobalAudioContext as getGlobalAudioContext2
|
|
@@ -2788,47 +2794,24 @@ function useExportWav() {
|
|
|
2788
2794
|
totalDurationSamples += Math.round(sampleRate * 0.1);
|
|
2789
2795
|
const duration = totalDurationSamples / sampleRate;
|
|
2790
2796
|
const tracksToRender = mode === "individual" ? [{ track: tracks[trackIndex], state: trackStates[trackIndex], index: trackIndex }] : tracks.map((track, index) => ({ track, state: trackStates[index], index }));
|
|
2791
|
-
const hasSolo = trackStates.some((state) => state.soloed);
|
|
2792
|
-
const
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
);
|
|
2808
|
-
} else {
|
|
2809
|
-
const offlineCtx = new OfflineAudioContext(2, totalDurationSamples, sampleRate);
|
|
2810
|
-
let scheduledClips = 0;
|
|
2811
|
-
const totalClips = tracksToRender.reduce((sum, { track }) => sum + track.clips.length, 0);
|
|
2812
|
-
for (const { track, state } of tracksToRender) {
|
|
2813
|
-
if (state.muted && !state.soloed) continue;
|
|
2814
|
-
if (hasSolo && !state.soloed) continue;
|
|
2815
|
-
for (const clip of track.clips) {
|
|
2816
|
-
yield scheduleClip(offlineCtx, clip, state, sampleRate, applyEffects);
|
|
2817
|
-
scheduledClips++;
|
|
2818
|
-
const currentProgress = scheduledClips / totalClips * 0.5;
|
|
2819
|
-
setProgress(currentProgress);
|
|
2820
|
-
onProgress == null ? void 0 : onProgress(currentProgress);
|
|
2821
|
-
}
|
|
2822
|
-
}
|
|
2823
|
-
setProgress(0.5);
|
|
2824
|
-
onProgress == null ? void 0 : onProgress(0.5);
|
|
2825
|
-
renderedBuffer = yield offlineCtx.startRendering();
|
|
2826
|
-
}
|
|
2827
|
-
setProgress(0.9);
|
|
2828
|
-
onProgress == null ? void 0 : onProgress(0.9);
|
|
2797
|
+
const hasSolo = mode === "master" && trackStates.some((state) => state.soloed);
|
|
2798
|
+
const reportProgress = (p) => {
|
|
2799
|
+
setProgress(p);
|
|
2800
|
+
onProgress == null ? void 0 : onProgress(p);
|
|
2801
|
+
};
|
|
2802
|
+
const renderedBuffer = yield renderOffline(
|
|
2803
|
+
tracksToRender,
|
|
2804
|
+
hasSolo,
|
|
2805
|
+
duration,
|
|
2806
|
+
sampleRate,
|
|
2807
|
+
applyEffects,
|
|
2808
|
+
effectsFunction,
|
|
2809
|
+
createOfflineTrackEffects,
|
|
2810
|
+
reportProgress
|
|
2811
|
+
);
|
|
2812
|
+
reportProgress(0.9);
|
|
2829
2813
|
const blob = encodeWav(renderedBuffer, { bitDepth });
|
|
2830
|
-
|
|
2831
|
-
onProgress == null ? void 0 : onProgress(1);
|
|
2814
|
+
reportProgress(1);
|
|
2832
2815
|
if (autoDownload) {
|
|
2833
2816
|
const exportFilename = mode === "individual" ? `${filename}_${tracks[trackIndex].name}` : filename;
|
|
2834
2817
|
downloadBlob(blob, `${exportFilename}.wav`);
|
|
@@ -2855,29 +2838,35 @@ function useExportWav() {
|
|
|
2855
2838
|
error
|
|
2856
2839
|
};
|
|
2857
2840
|
}
|
|
2858
|
-
function
|
|
2841
|
+
function renderOffline(tracksToRender, hasSolo, duration, sampleRate, applyEffects, effectsFunction, createOfflineTrackEffects, onProgress) {
|
|
2859
2842
|
return __async(this, null, function* () {
|
|
2860
2843
|
const { Offline, Volume: Volume2, Gain, Panner, Player, ToneAudioBuffer } = yield import("tone");
|
|
2861
2844
|
onProgress(0.1);
|
|
2845
|
+
const audibleTracks = tracksToRender.filter(({ state }) => {
|
|
2846
|
+
if (state.muted && !state.soloed) return false;
|
|
2847
|
+
if (hasSolo && !state.soloed) return false;
|
|
2848
|
+
return true;
|
|
2849
|
+
});
|
|
2850
|
+
const outputChannels = audibleTracks.reduce(
|
|
2851
|
+
(max, { track }) => Math.max(max, trackChannelCount(track)),
|
|
2852
|
+
1
|
|
2853
|
+
);
|
|
2862
2854
|
let buffer;
|
|
2863
2855
|
try {
|
|
2864
2856
|
buffer = yield Offline(
|
|
2865
2857
|
(_0) => __async(null, [_0], function* ({ transport, destination }) {
|
|
2866
2858
|
const masterVolume = new Volume2(0);
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
cleanup = effectsFunction(masterVolume, destination, true);
|
|
2859
|
+
if (effectsFunction && applyEffects) {
|
|
2860
|
+
effectsFunction(masterVolume, destination, true);
|
|
2870
2861
|
} else {
|
|
2871
2862
|
masterVolume.connect(destination);
|
|
2872
2863
|
}
|
|
2873
|
-
for (const { track, state } of
|
|
2874
|
-
if (state.muted && !state.soloed) continue;
|
|
2875
|
-
if (hasSolo && !state.soloed) continue;
|
|
2864
|
+
for (const { track, state } of audibleTracks) {
|
|
2876
2865
|
const trackVolume = new Volume2(gainToDb(state.volume));
|
|
2877
|
-
const trackPan = new Panner(state.pan);
|
|
2866
|
+
const trackPan = new Panner({ pan: state.pan, channelCount: trackChannelCount(track) });
|
|
2878
2867
|
const trackMute = new Gain(state.muted ? 0 : 1);
|
|
2879
2868
|
const trackEffects = createOfflineTrackEffects == null ? void 0 : createOfflineTrackEffects(track.id);
|
|
2880
|
-
if (trackEffects) {
|
|
2869
|
+
if (trackEffects && applyEffects) {
|
|
2881
2870
|
trackEffects(trackMute, masterVolume, true);
|
|
2882
2871
|
} else {
|
|
2883
2872
|
trackMute.connect(masterVolume);
|
|
@@ -2894,6 +2883,12 @@ function renderWithToneEffects(tracksToRender, _trackStates, hasSolo, duration,
|
|
|
2894
2883
|
fadeIn,
|
|
2895
2884
|
fadeOut
|
|
2896
2885
|
} = clip;
|
|
2886
|
+
if (!audioBuffer) {
|
|
2887
|
+
console.warn(
|
|
2888
|
+
'[waveform-playlist] Skipping clip "' + (clip.name || clip.id) + '" - no audioBuffer for export'
|
|
2889
|
+
);
|
|
2890
|
+
continue;
|
|
2891
|
+
}
|
|
2897
2892
|
const startTime = startSample / sampleRate;
|
|
2898
2893
|
const clipDuration = durationSamples / sampleRate;
|
|
2899
2894
|
const offset = offsetSamples / sampleRate;
|
|
@@ -2902,167 +2897,56 @@ function renderWithToneEffects(tracksToRender, _trackStates, hasSolo, duration,
|
|
|
2902
2897
|
const fadeGain = new Gain(clipGain);
|
|
2903
2898
|
player.connect(fadeGain);
|
|
2904
2899
|
fadeGain.connect(trackVolume);
|
|
2905
|
-
if (
|
|
2906
|
-
const fadeInStart = startTime;
|
|
2907
|
-
const fadeInEnd = startTime + fadeIn.duration;
|
|
2908
|
-
const audioParam = getUnderlyingAudioParam(fadeGain.gain);
|
|
2909
|
-
if (audioParam) {
|
|
2910
|
-
audioParam.setValueAtTime(0, fadeInStart);
|
|
2911
|
-
audioParam.linearRampToValueAtTime(clipGain, fadeInEnd);
|
|
2912
|
-
}
|
|
2913
|
-
}
|
|
2914
|
-
if (fadeOut) {
|
|
2915
|
-
const fadeOutStart = startTime + clipDuration - fadeOut.duration;
|
|
2916
|
-
const fadeOutEnd = startTime + clipDuration;
|
|
2900
|
+
if (applyEffects) {
|
|
2917
2901
|
const audioParam = getUnderlyingAudioParam(fadeGain.gain);
|
|
2918
2902
|
if (audioParam) {
|
|
2919
|
-
audioParam
|
|
2920
|
-
|
|
2903
|
+
applyClipFades(audioParam, clipGain, startTime, clipDuration, fadeIn, fadeOut);
|
|
2904
|
+
} else if (fadeIn || fadeOut) {
|
|
2905
|
+
console.warn(
|
|
2906
|
+
'[waveform-playlist] Cannot apply fades for clip "' + (clip.name || clip.id) + '" - AudioParam not accessible'
|
|
2907
|
+
);
|
|
2921
2908
|
}
|
|
2922
2909
|
}
|
|
2923
2910
|
player.start(startTime, offset, clipDuration);
|
|
2924
2911
|
}
|
|
2925
2912
|
}
|
|
2926
2913
|
transport.start(0);
|
|
2927
|
-
if (cleanup) {
|
|
2928
|
-
}
|
|
2929
2914
|
}),
|
|
2930
2915
|
duration,
|
|
2931
|
-
|
|
2932
|
-
// stereo
|
|
2916
|
+
outputChannels,
|
|
2933
2917
|
sampleRate
|
|
2934
2918
|
);
|
|
2935
2919
|
} catch (err) {
|
|
2936
2920
|
if (err instanceof Error) {
|
|
2937
2921
|
throw err;
|
|
2938
2922
|
} else {
|
|
2939
|
-
throw new Error(
|
|
2923
|
+
throw new Error("Tone.Offline rendering failed: " + String(err));
|
|
2940
2924
|
}
|
|
2941
2925
|
}
|
|
2942
2926
|
onProgress(0.9);
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
function gainToDb(gain) {
|
|
2947
|
-
return 20 * Math.log10(Math.max(gain, 1e-4));
|
|
2948
|
-
}
|
|
2949
|
-
function scheduleClip(ctx, clip, trackState, sampleRate, applyEffects) {
|
|
2950
|
-
return __async(this, null, function* () {
|
|
2951
|
-
const {
|
|
2952
|
-
audioBuffer,
|
|
2953
|
-
startSample,
|
|
2954
|
-
durationSamples,
|
|
2955
|
-
offsetSamples,
|
|
2956
|
-
gain: clipGain,
|
|
2957
|
-
fadeIn,
|
|
2958
|
-
fadeOut
|
|
2959
|
-
} = clip;
|
|
2960
|
-
if (!audioBuffer) {
|
|
2961
|
-
console.warn(`Skipping clip "${clip.name || clip.id}" - no audioBuffer for export`);
|
|
2962
|
-
return;
|
|
2927
|
+
const result = buffer.get();
|
|
2928
|
+
if (!result) {
|
|
2929
|
+
throw new Error("Offline rendering produced no audio buffer");
|
|
2963
2930
|
}
|
|
2964
|
-
|
|
2965
|
-
const duration = durationSamples / sampleRate;
|
|
2966
|
-
const offset = offsetSamples / sampleRate;
|
|
2967
|
-
const source = ctx.createBufferSource();
|
|
2968
|
-
source.buffer = audioBuffer;
|
|
2969
|
-
const gainNode = ctx.createGain();
|
|
2970
|
-
const baseGain = clipGain * trackState.volume;
|
|
2971
|
-
const pannerNode = ctx.createStereoPanner();
|
|
2972
|
-
pannerNode.pan.value = trackState.pan;
|
|
2973
|
-
source.connect(gainNode);
|
|
2974
|
-
gainNode.connect(pannerNode);
|
|
2975
|
-
pannerNode.connect(ctx.destination);
|
|
2976
|
-
if (applyEffects) {
|
|
2977
|
-
if (fadeIn) {
|
|
2978
|
-
gainNode.gain.setValueAtTime(0, startTime);
|
|
2979
|
-
} else {
|
|
2980
|
-
gainNode.gain.setValueAtTime(baseGain, startTime);
|
|
2981
|
-
}
|
|
2982
|
-
if (fadeIn) {
|
|
2983
|
-
const fadeInStart = startTime;
|
|
2984
|
-
const fadeInEnd = startTime + fadeIn.duration;
|
|
2985
|
-
applyFadeEnvelope(
|
|
2986
|
-
gainNode.gain,
|
|
2987
|
-
fadeInStart,
|
|
2988
|
-
fadeInEnd,
|
|
2989
|
-
0,
|
|
2990
|
-
baseGain,
|
|
2991
|
-
fadeIn.type || "linear"
|
|
2992
|
-
);
|
|
2993
|
-
}
|
|
2994
|
-
if (fadeOut) {
|
|
2995
|
-
const fadeOutStart = startTime + duration - fadeOut.duration;
|
|
2996
|
-
const fadeOutEnd = startTime + duration;
|
|
2997
|
-
if (!fadeIn || fadeIn.duration < duration - fadeOut.duration) {
|
|
2998
|
-
gainNode.gain.setValueAtTime(baseGain, fadeOutStart);
|
|
2999
|
-
}
|
|
3000
|
-
applyFadeEnvelope(
|
|
3001
|
-
gainNode.gain,
|
|
3002
|
-
fadeOutStart,
|
|
3003
|
-
fadeOutEnd,
|
|
3004
|
-
baseGain,
|
|
3005
|
-
0,
|
|
3006
|
-
fadeOut.type || "linear"
|
|
3007
|
-
);
|
|
3008
|
-
}
|
|
3009
|
-
} else {
|
|
3010
|
-
gainNode.gain.setValueAtTime(baseGain, startTime);
|
|
3011
|
-
}
|
|
3012
|
-
source.start(startTime, offset, duration);
|
|
2931
|
+
return result;
|
|
3013
2932
|
});
|
|
3014
2933
|
}
|
|
3015
|
-
function
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
gainParam.setValueAtTime(startValue, startTime);
|
|
3021
|
-
gainParam.linearRampToValueAtTime(endValue, endTime);
|
|
3022
|
-
break;
|
|
3023
|
-
case "exponential": {
|
|
3024
|
-
const expStart = Math.max(startValue, 1e-4);
|
|
3025
|
-
const expEnd = Math.max(endValue, 1e-4);
|
|
3026
|
-
gainParam.setValueAtTime(expStart, startTime);
|
|
3027
|
-
gainParam.exponentialRampToValueAtTime(expEnd, endTime);
|
|
3028
|
-
if (endValue === 0) {
|
|
3029
|
-
gainParam.setValueAtTime(0, endTime);
|
|
3030
|
-
}
|
|
3031
|
-
break;
|
|
3032
|
-
}
|
|
3033
|
-
case "logarithmic": {
|
|
3034
|
-
const logCurve = generateFadeCurve(startValue, endValue, 256, "logarithmic");
|
|
3035
|
-
gainParam.setValueCurveAtTime(logCurve, startTime, duration);
|
|
3036
|
-
break;
|
|
3037
|
-
}
|
|
3038
|
-
case "sCurve": {
|
|
3039
|
-
const sCurve = generateFadeCurve(startValue, endValue, 256, "sCurve");
|
|
3040
|
-
gainParam.setValueCurveAtTime(sCurve, startTime, duration);
|
|
3041
|
-
break;
|
|
3042
|
-
}
|
|
3043
|
-
default:
|
|
3044
|
-
gainParam.setValueAtTime(startValue, startTime);
|
|
3045
|
-
gainParam.linearRampToValueAtTime(endValue, endTime);
|
|
2934
|
+
function applyClipFades(gainParam, clipGain, startTime, clipDuration, fadeIn, fadeOut) {
|
|
2935
|
+
if (fadeIn) {
|
|
2936
|
+
gainParam.setValueAtTime(0, startTime);
|
|
2937
|
+
} else {
|
|
2938
|
+
gainParam.setValueAtTime(clipGain, startTime);
|
|
3046
2939
|
}
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
if (curveType === "logarithmic") {
|
|
3055
|
-
if (range > 0) {
|
|
3056
|
-
curveValue = Math.log10(1 + t * 9) / Math.log10(10);
|
|
3057
|
-
} else {
|
|
3058
|
-
curveValue = 1 - Math.log10(1 + (1 - t) * 9) / Math.log10(10);
|
|
3059
|
-
}
|
|
3060
|
-
} else {
|
|
3061
|
-
curveValue = t * t * (3 - 2 * t);
|
|
2940
|
+
if (fadeIn) {
|
|
2941
|
+
applyFadeIn(gainParam, startTime, fadeIn.duration, fadeIn.type || "linear", 0, clipGain);
|
|
2942
|
+
}
|
|
2943
|
+
if (fadeOut) {
|
|
2944
|
+
const fadeOutStart = startTime + clipDuration - fadeOut.duration;
|
|
2945
|
+
if (!fadeIn || fadeIn.duration < clipDuration - fadeOut.duration) {
|
|
2946
|
+
gainParam.setValueAtTime(clipGain, fadeOutStart);
|
|
3062
2947
|
}
|
|
3063
|
-
|
|
2948
|
+
applyFadeOut(gainParam, fadeOutStart, fadeOut.duration, fadeOut.type || "linear", clipGain, 0);
|
|
3064
2949
|
}
|
|
3065
|
-
return curve;
|
|
3066
2950
|
}
|
|
3067
2951
|
|
|
3068
2952
|
// src/hooks/useAnimationFrameLoop.ts
|
|
@@ -3759,6 +3643,7 @@ var WaveformPlaylistProvider = ({
|
|
|
3759
3643
|
const playbackEndTimeRef = useRef15(null);
|
|
3760
3644
|
const scrollContainerRef = useRef15(null);
|
|
3761
3645
|
const isAutomaticScrollRef = useRef15(false);
|
|
3646
|
+
const frameCallbacksRef = useRef15(/* @__PURE__ */ new Map());
|
|
3762
3647
|
const continuousPlayRef = useRef15((_d = annotationList == null ? void 0 : annotationList.isContinuousPlay) != null ? _d : false);
|
|
3763
3648
|
const activeAnnotationIdRef = useRef15(null);
|
|
3764
3649
|
const engineTracksRef = useRef15(null);
|
|
@@ -4219,10 +4104,30 @@ var WaveformPlaylistProvider = ({
|
|
|
4219
4104
|
const elapsed = getContext2().currentTime - ((_a2 = playbackStartTimeRef.current) != null ? _a2 : 0);
|
|
4220
4105
|
return ((_b2 = audioStartPositionRef.current) != null ? _b2 : 0) + elapsed;
|
|
4221
4106
|
}, []);
|
|
4107
|
+
const registerFrameCallback = useCallback19((id, cb) => {
|
|
4108
|
+
frameCallbacksRef.current.set(id, cb);
|
|
4109
|
+
}, []);
|
|
4110
|
+
const unregisterFrameCallback = useCallback19((id) => {
|
|
4111
|
+
frameCallbacksRef.current.delete(id);
|
|
4112
|
+
}, []);
|
|
4222
4113
|
const startAnimationLoop = useCallback19(() => {
|
|
4114
|
+
const audioCtx = getGlobalAudioContext4();
|
|
4223
4115
|
const updateTime = () => {
|
|
4224
4116
|
const time = getPlaybackTime();
|
|
4225
4117
|
currentTimeRef.current = time;
|
|
4118
|
+
const latency = "outputLatency" in audioCtx ? audioCtx.outputLatency : 0;
|
|
4119
|
+
const visualTime = Math.max(0, time - latency);
|
|
4120
|
+
const sr = sampleRateRef.current;
|
|
4121
|
+
const spp = samplesPerPixelRef.current;
|
|
4122
|
+
const frameData = {
|
|
4123
|
+
time,
|
|
4124
|
+
visualTime,
|
|
4125
|
+
sampleRate: sr,
|
|
4126
|
+
samplesPerPixel: spp
|
|
4127
|
+
};
|
|
4128
|
+
for (const cb of frameCallbacksRef.current.values()) {
|
|
4129
|
+
cb(frameData);
|
|
4130
|
+
}
|
|
4226
4131
|
const currentAnnotations = annotationsRef.current;
|
|
4227
4132
|
if (currentAnnotations.length > 0) {
|
|
4228
4133
|
const currentAnnotation = currentAnnotations.find(
|
|
@@ -4257,10 +4162,9 @@ var WaveformPlaylistProvider = ({
|
|
|
4257
4162
|
}
|
|
4258
4163
|
if (isAutomaticScrollRef.current && scrollContainerRef.current && duration > 0) {
|
|
4259
4164
|
const container = scrollContainerRef.current;
|
|
4260
|
-
const
|
|
4261
|
-
const pixelPosition = time * sr / samplesPerPixelRef.current;
|
|
4165
|
+
const pixelPosition = visualTime * sr / spp;
|
|
4262
4166
|
const containerWidth = container.clientWidth;
|
|
4263
|
-
const targetScrollLeft = Math.max(0, pixelPosition - containerWidth / 2);
|
|
4167
|
+
const targetScrollLeft = Math.round(Math.max(0, pixelPosition - containerWidth / 2));
|
|
4264
4168
|
container.scrollLeft = targetScrollLeft;
|
|
4265
4169
|
}
|
|
4266
4170
|
if (playbackEndTimeRef.current !== null && time >= playbackEndTimeRef.current) {
|
|
@@ -4496,7 +4400,9 @@ var WaveformPlaylistProvider = ({
|
|
|
4496
4400
|
currentTimeRef,
|
|
4497
4401
|
playbackStartTimeRef,
|
|
4498
4402
|
audioStartPositionRef,
|
|
4499
|
-
getPlaybackTime
|
|
4403
|
+
getPlaybackTime,
|
|
4404
|
+
registerFrameCallback,
|
|
4405
|
+
unregisterFrameCallback
|
|
4500
4406
|
}),
|
|
4501
4407
|
[
|
|
4502
4408
|
isPlaying,
|
|
@@ -4504,7 +4410,9 @@ var WaveformPlaylistProvider = ({
|
|
|
4504
4410
|
currentTimeRef,
|
|
4505
4411
|
playbackStartTimeRef,
|
|
4506
4412
|
audioStartPositionRef,
|
|
4507
|
-
getPlaybackTime
|
|
4413
|
+
getPlaybackTime,
|
|
4414
|
+
registerFrameCallback,
|
|
4415
|
+
unregisterFrameCallback
|
|
4508
4416
|
]
|
|
4509
4417
|
);
|
|
4510
4418
|
const stateValue = useMemo4(
|
|
@@ -5299,32 +5207,19 @@ var PositionDisplay = styled.span`
|
|
|
5299
5207
|
var AudioPosition = ({ className }) => {
|
|
5300
5208
|
var _a;
|
|
5301
5209
|
const timeRef = useRef17(null);
|
|
5302
|
-
const
|
|
5303
|
-
const { isPlaying, currentTimeRef, getPlaybackTime } = usePlaybackAnimation();
|
|
5210
|
+
const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
|
|
5304
5211
|
const { timeFormat: format } = usePlaylistData();
|
|
5305
5212
|
useEffect12(() => {
|
|
5306
|
-
const
|
|
5307
|
-
var _a2;
|
|
5308
|
-
if (timeRef.current) {
|
|
5309
|
-
const time = isPlaying ? getPlaybackTime() : (_a2 = currentTimeRef.current) != null ? _a2 : 0;
|
|
5310
|
-
timeRef.current.textContent = formatTime(time, format);
|
|
5311
|
-
}
|
|
5312
|
-
if (isPlaying) {
|
|
5313
|
-
animationFrameRef.current = requestAnimationFrame(updateTime);
|
|
5314
|
-
}
|
|
5315
|
-
};
|
|
5213
|
+
const id = "audio-position";
|
|
5316
5214
|
if (isPlaying) {
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5215
|
+
registerFrameCallback(id, ({ time }) => {
|
|
5216
|
+
if (timeRef.current) {
|
|
5217
|
+
timeRef.current.textContent = formatTime(time, format);
|
|
5218
|
+
}
|
|
5219
|
+
});
|
|
5320
5220
|
}
|
|
5321
|
-
return () =>
|
|
5322
|
-
|
|
5323
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
5324
|
-
animationFrameRef.current = null;
|
|
5325
|
-
}
|
|
5326
|
-
};
|
|
5327
|
-
}, [isPlaying, format, currentTimeRef, getPlaybackTime]);
|
|
5221
|
+
return () => unregisterFrameCallback(id);
|
|
5222
|
+
}, [isPlaying, format, registerFrameCallback, unregisterFrameCallback]);
|
|
5328
5223
|
useEffect12(() => {
|
|
5329
5224
|
var _a2;
|
|
5330
5225
|
if (!isPlaying && timeRef.current) {
|
|
@@ -5507,33 +5402,20 @@ var PlayheadLine = styled2.div.attrs((props) => ({
|
|
|
5507
5402
|
`;
|
|
5508
5403
|
var AnimatedPlayhead = ({ color = "#ff0000" }) => {
|
|
5509
5404
|
const playheadRef = useRef18(null);
|
|
5510
|
-
const
|
|
5511
|
-
const { isPlaying, currentTimeRef, getPlaybackTime } = usePlaybackAnimation();
|
|
5405
|
+
const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
|
|
5512
5406
|
const { samplesPerPixel, sampleRate, progressBarWidth } = usePlaylistData();
|
|
5513
5407
|
useEffect13(() => {
|
|
5514
|
-
const
|
|
5515
|
-
var _a;
|
|
5516
|
-
if (playheadRef.current) {
|
|
5517
|
-
const time = isPlaying ? getPlaybackTime() : (_a = currentTimeRef.current) != null ? _a : 0;
|
|
5518
|
-
const position = time * sampleRate / samplesPerPixel;
|
|
5519
|
-
playheadRef.current.style.transform = `translate3d(${position}px, 0, 0)`;
|
|
5520
|
-
}
|
|
5521
|
-
if (isPlaying) {
|
|
5522
|
-
animationFrameRef.current = requestAnimationFrame(updatePosition);
|
|
5523
|
-
}
|
|
5524
|
-
};
|
|
5408
|
+
const id = "playhead";
|
|
5525
5409
|
if (isPlaying) {
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5410
|
+
registerFrameCallback(id, ({ visualTime, sampleRate: sr, samplesPerPixel: spp }) => {
|
|
5411
|
+
if (playheadRef.current) {
|
|
5412
|
+
const px = visualTime * sr / spp;
|
|
5413
|
+
playheadRef.current.style.transform = `translate3d(${px}px, 0, 0)`;
|
|
5414
|
+
}
|
|
5415
|
+
});
|
|
5529
5416
|
}
|
|
5530
|
-
return () =>
|
|
5531
|
-
|
|
5532
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
5533
|
-
animationFrameRef.current = null;
|
|
5534
|
-
}
|
|
5535
|
-
};
|
|
5536
|
-
}, [isPlaying, sampleRate, samplesPerPixel, currentTimeRef, getPlaybackTime]);
|
|
5417
|
+
return () => unregisterFrameCallback(id);
|
|
5418
|
+
}, [isPlaying, registerFrameCallback, unregisterFrameCallback]);
|
|
5537
5419
|
useEffect13(() => {
|
|
5538
5420
|
var _a;
|
|
5539
5421
|
if (!isPlaying && playheadRef.current) {
|
|
@@ -5546,7 +5428,7 @@ var AnimatedPlayhead = ({ color = "#ff0000" }) => {
|
|
|
5546
5428
|
};
|
|
5547
5429
|
|
|
5548
5430
|
// src/components/ChannelWithProgress.tsx
|
|
5549
|
-
import { useRef as useRef19, useEffect as useEffect14 } from "react";
|
|
5431
|
+
import { useId, useRef as useRef19, useEffect as useEffect14 } from "react";
|
|
5550
5432
|
import styled3 from "styled-components";
|
|
5551
5433
|
import {
|
|
5552
5434
|
clipPixelWidth as computeClipPixelWidth
|
|
@@ -5612,10 +5494,10 @@ var ChannelWithProgress = (_a) => {
|
|
|
5612
5494
|
"clipOffsetSeconds"
|
|
5613
5495
|
]);
|
|
5614
5496
|
const progressRef = useRef19(null);
|
|
5615
|
-
const
|
|
5497
|
+
const callbackId = useId();
|
|
5616
5498
|
const theme = useTheme();
|
|
5617
5499
|
const { waveHeight } = usePlaylistInfo();
|
|
5618
|
-
const { isPlaying, currentTimeRef,
|
|
5500
|
+
const { isPlaying, currentTimeRef, registerFrameCallback, unregisterFrameCallback } = usePlaybackAnimation();
|
|
5619
5501
|
const { samplesPerPixel, sampleRate } = usePlaylistData();
|
|
5620
5502
|
const progressColor = (theme == null ? void 0 : theme.waveProgressColor) || "rgba(0, 0, 0, 0.1)";
|
|
5621
5503
|
const clipPixelWidth = computeClipPixelWidth(
|
|
@@ -5624,46 +5506,32 @@ var ChannelWithProgress = (_a) => {
|
|
|
5624
5506
|
samplesPerPixel
|
|
5625
5507
|
);
|
|
5626
5508
|
useEffect14(() => {
|
|
5627
|
-
const updateProgress = () => {
|
|
5628
|
-
var _a2;
|
|
5629
|
-
if (progressRef.current) {
|
|
5630
|
-
const currentTime = isPlaying ? getPlaybackTime() : (_a2 = currentTimeRef.current) != null ? _a2 : 0;
|
|
5631
|
-
const currentSample = currentTime * sampleRate;
|
|
5632
|
-
const clipEndSample = clipStartSample + clipDurationSamples;
|
|
5633
|
-
let ratio = 0;
|
|
5634
|
-
if (currentSample <= clipStartSample) {
|
|
5635
|
-
ratio = 0;
|
|
5636
|
-
} else if (currentSample >= clipEndSample) {
|
|
5637
|
-
ratio = 1;
|
|
5638
|
-
} else {
|
|
5639
|
-
const playedSamples = currentSample - clipStartSample;
|
|
5640
|
-
ratio = playedSamples / clipDurationSamples;
|
|
5641
|
-
}
|
|
5642
|
-
progressRef.current.style.transform = `scaleX(${ratio})`;
|
|
5643
|
-
}
|
|
5644
|
-
if (isPlaying) {
|
|
5645
|
-
animationFrameRef.current = requestAnimationFrame(updateProgress);
|
|
5646
|
-
}
|
|
5647
|
-
};
|
|
5648
5509
|
if (isPlaying) {
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5510
|
+
registerFrameCallback(callbackId, ({ visualTime, sampleRate: sr }) => {
|
|
5511
|
+
if (progressRef.current) {
|
|
5512
|
+
const currentSample = visualTime * sr;
|
|
5513
|
+
const clipEndSample = clipStartSample + clipDurationSamples;
|
|
5514
|
+
let ratio = 0;
|
|
5515
|
+
if (currentSample <= clipStartSample) {
|
|
5516
|
+
ratio = 0;
|
|
5517
|
+
} else if (currentSample >= clipEndSample) {
|
|
5518
|
+
ratio = 1;
|
|
5519
|
+
} else {
|
|
5520
|
+
const playedSamples = currentSample - clipStartSample;
|
|
5521
|
+
ratio = playedSamples / clipDurationSamples;
|
|
5522
|
+
}
|
|
5523
|
+
progressRef.current.style.transform = `scaleX(${ratio})`;
|
|
5524
|
+
}
|
|
5525
|
+
});
|
|
5652
5526
|
}
|
|
5653
|
-
return () =>
|
|
5654
|
-
if (animationFrameRef.current) {
|
|
5655
|
-
cancelAnimationFrame(animationFrameRef.current);
|
|
5656
|
-
animationFrameRef.current = null;
|
|
5657
|
-
}
|
|
5658
|
-
};
|
|
5527
|
+
return () => unregisterFrameCallback(callbackId);
|
|
5659
5528
|
}, [
|
|
5660
5529
|
isPlaying,
|
|
5661
|
-
sampleRate,
|
|
5662
5530
|
clipStartSample,
|
|
5663
5531
|
clipDurationSamples,
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5532
|
+
callbackId,
|
|
5533
|
+
registerFrameCallback,
|
|
5534
|
+
unregisterFrameCallback
|
|
5667
5535
|
]);
|
|
5668
5536
|
useEffect14(() => {
|
|
5669
5537
|
var _a2;
|