fix: chỉnh sửa tốc độ của audio clip realtime chính xác
This commit is contained in:
+33
-22
@@ -801,19 +801,20 @@
|
||||
const parent = canvas.parentElement;
|
||||
const scrollContainer = parent ? parent.parentElement : null;
|
||||
const scrollLeft = scrollContainer ? scrollContainer.scrollLeft : 0;
|
||||
const duration = buffer.duration;
|
||||
const bufDuration = buffer.duration;
|
||||
const wallDuration = bufDuration / (speed || 1.0);
|
||||
const mouseX = e.clientX - rect.left + scrollLeft;
|
||||
const startTime = Math.max(0, Math.min(duration, mouseX / zoom));
|
||||
const startTime = Math.max(0, Math.min(wallDuration, mouseX / zoom));
|
||||
|
||||
// Alt+Click near right edge → speed stretch
|
||||
if (e.altKey && onSpeedChange) {
|
||||
const clipRightEdge = (duration / speed) * zoom;
|
||||
const clipRightEdge = (bufDuration / (speed || 1.0)) * zoom;
|
||||
const tolerance = 8;
|
||||
if (Math.abs(mouseX - clipRightEdge) <= tolerance) {
|
||||
isStretchingRef.current = true;
|
||||
stretchStartRef.current = {
|
||||
mouseX,
|
||||
originalDuration: duration,
|
||||
originalDuration: bufDuration,
|
||||
originalSpeed: speed
|
||||
};
|
||||
canvas.style.cursor = 'ew-resize';
|
||||
@@ -846,7 +847,7 @@
|
||||
|
||||
const handleMouseMove = (moveEvent) => {
|
||||
const currentX = moveEvent.clientX - rect.left + scrollLeft;
|
||||
const ct = Math.max(0, Math.min(duration, currentX / zoom));
|
||||
const ct = Math.max(0, Math.min(wallDuration, currentX / zoom));
|
||||
onPlayheadSet(ct);
|
||||
};
|
||||
|
||||
@@ -872,7 +873,7 @@
|
||||
|
||||
const handleMouseMove = (moveEvent) => {
|
||||
const currentX = moveEvent.clientX - rect.left + scrollLeft;
|
||||
const ct = Math.max(0, Math.min(duration, currentX / zoom));
|
||||
const ct = Math.max(0, Math.min(wallDuration, currentX / zoom));
|
||||
onPlayheadSet(ct);
|
||||
};
|
||||
|
||||
@@ -893,7 +894,7 @@
|
||||
|
||||
const handleMouseMove = (moveEvent) => {
|
||||
const currentX = moveEvent.clientX - rect.left + scrollLeft;
|
||||
const ct = Math.max(0, Math.min(duration, currentX / zoom));
|
||||
const ct = Math.max(0, Math.min(wallDuration, currentX / zoom));
|
||||
onSelectRange(startTime, ct);
|
||||
};
|
||||
|
||||
@@ -915,7 +916,7 @@
|
||||
const scrollContainer = parent ? parent.parentElement : null;
|
||||
const scrollLeft = scrollContainer ? scrollContainer.scrollLeft : 0;
|
||||
const x = e.clientX - rect.left + scrollLeft;
|
||||
const clickTime = Math.max(0, Math.min(buffer.duration, x / zoom));
|
||||
const clickTime = Math.max(0, Math.min(buffer.duration / (speed || 1.0), x / zoom));
|
||||
onContextMenu(e, clickTime);
|
||||
};
|
||||
|
||||
@@ -1321,6 +1322,7 @@
|
||||
const activeSourcesRef = useRef([]);
|
||||
const activeTrackNodesRef = useRef({}); // { [trackId]: { gainNode, pannerNode } }
|
||||
const startOffsetTimeRef = useRef(0);
|
||||
const startBufferOffsetRef = useRef(0);
|
||||
const startAudioTimeRef = useRef(0);
|
||||
const animationFrameIdRef = useRef(null);
|
||||
const toastTimeoutRef = useRef(null);
|
||||
@@ -2522,8 +2524,10 @@
|
||||
}, [zoom, minZoom, maxDuration, activeTab]);
|
||||
|
||||
// ── Update Playhead ──
|
||||
const startSubTabPlayback = (st, offsetTime) => {
|
||||
const startSubTabPlayback = (st, offsetWallTime) => {
|
||||
const context = getAudioContext();
|
||||
const speed = st.speed || 1.0;
|
||||
const offsetBuffer = offsetWallTime * speed;
|
||||
const eff = st.buffer.getChannelData(0);
|
||||
const edBuffer = context.createBuffer(1, eff.length, st.buffer.sampleRate);
|
||||
const edData = edBuffer.getChannelData(0);
|
||||
@@ -2554,7 +2558,7 @@
|
||||
|
||||
const source = context.createBufferSource();
|
||||
source.buffer = edBuffer;
|
||||
source.playbackRate.value = st.speed || 1.0;
|
||||
source.playbackRate.value = speed;
|
||||
|
||||
// Look up track for volume/pan
|
||||
const subTrack = tracks.find(t => t.id === st.trackId);
|
||||
@@ -2572,10 +2576,11 @@
|
||||
gainNode.connect(pannerNode);
|
||||
pannerNode.connect(context.destination);
|
||||
|
||||
source.start(context.currentTime, offsetTime);
|
||||
source.start(context.currentTime, offsetBuffer);
|
||||
activeSourcesRef.current = [source];
|
||||
activeTrackNodesRef.current[st.trackId] = { gainNode, pannerNode, source };
|
||||
startOffsetTimeRef.current = offsetTime;
|
||||
startOffsetTimeRef.current = offsetWallTime;
|
||||
startBufferOffsetRef.current = offsetBuffer;
|
||||
startAudioTimeRef.current = context.currentTime;
|
||||
};
|
||||
|
||||
@@ -2584,29 +2589,29 @@
|
||||
const st = subTabs.find(s => s.id === activeTab);
|
||||
if (!st || !st.isPlaying || !st.buffer) return;
|
||||
const context = getAudioContext();
|
||||
const elapsed = context.currentTime - startAudioTimeRef.current;
|
||||
const speedFactor = st.speed || 1.0;
|
||||
const updatedTime = startOffsetTimeRef.current + elapsed * speedFactor;
|
||||
const effectiveDuration = st.buffer.duration / speedFactor;
|
||||
const elapsed = context.currentTime - startAudioTimeRef.current;
|
||||
const wallTime = startOffsetTimeRef.current + elapsed;
|
||||
const bufferPos = startBufferOffsetRef.current + elapsed * speedFactor;
|
||||
|
||||
// Loop sub-tab selection
|
||||
// Loop sub-tab selection (bufferPos is buffer-time)
|
||||
if (st.selectionStart !== null && st.selectionEnd !== null && st.selectionStart !== st.selectionEnd) {
|
||||
const start = Math.min(st.selectionStart, st.selectionEnd);
|
||||
const end = Math.max(st.selectionStart, st.selectionEnd);
|
||||
if (updatedTime >= end) {
|
||||
if (bufferPos >= end) {
|
||||
stopAllPlayback();
|
||||
setSubTabs(prev => prev.map(s => s.id === activeTab ? {
|
||||
...s,
|
||||
currentTime: start,
|
||||
currentTime: wallTime,
|
||||
isPlaying: true
|
||||
} : s));
|
||||
startSubTabPlayback(st, start);
|
||||
startSubTabPlayback(st, wallTime);
|
||||
animationFrameIdRef.current = requestAnimationFrame(updatePlayhead);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedTime >= effectiveDuration) {
|
||||
if (bufferPos >= st.buffer.duration) {
|
||||
stopAllPlayback();
|
||||
if (isLoopingSelection) {
|
||||
setSubTabs(prev => prev.map(s => s.id === activeTab ? {
|
||||
@@ -2622,7 +2627,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
setSubTabs(prev => prev.map(s => s.id === activeTab ? { ...s, currentTime: updatedTime } : s));
|
||||
setSubTabs(prev => prev.map(s => s.id === activeTab ? { ...s, currentTime: wallTime } : s));
|
||||
animationFrameIdRef.current = requestAnimationFrame(updatePlayhead);
|
||||
return;
|
||||
}
|
||||
@@ -2800,7 +2805,7 @@
|
||||
stopAllPlayback();
|
||||
const startOffset = st.currentTime || 0;
|
||||
startSubTabPlayback(st, startOffset);
|
||||
setSubTabs(prev => prev.map(s => s.id === activeTab ? { ...s, isPlaying: true } : s));
|
||||
setSubTabs(prev => prev.map(s => s.id === activeTab ? { ...s, isPlaying: true, currentTime: startOffset } : s));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -4999,6 +5004,12 @@
|
||||
} : s));
|
||||
const n = activeTrackNodesRef.current[st.trackId];
|
||||
if (n && n.source) n.source.playbackRate.value = newSpeed;
|
||||
// Reset time refs to prevent playhead jump when speed changes mid-playback
|
||||
const ctx = getAudioContext();
|
||||
const elapsed = ctx.currentTime - startAudioTimeRef.current;
|
||||
startBufferOffsetRef.current = startBufferOffsetRef.current + elapsed * (st.speed || 1.0);
|
||||
startOffsetTimeRef.current = startOffsetTimeRef.current + elapsed;
|
||||
startAudioTimeRef.current = ctx.currentTime;
|
||||
}}
|
||||
/>
|
||||
<div onMouseDown={handleSubTabResizeMouseDown}
|
||||
|
||||
Reference in New Issue
Block a user