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