diff --git a/js/ltx_director.js b/js/ltx_director.js index b4ccab8..c9f7bad 100644 --- a/js/ltx_director.js +++ b/js/ltx_director.js @@ -2122,6 +2122,41 @@ class TimelineEditor { }; } + _syncActiveVideoSegments(segments, currentFrame, frameRate, activeType, dragSelectionType = null) { + const activeList = (this._isDragging && this._previewSegments && dragSelectionType === this.selectionType) + ? this._previewSegments + : segments; + const activeSeg = activeList.find(s => s.type === activeType && currentFrame >= s.start && currentFrame < s.start + s.length); + const activeVideoEl = activeSeg ? activeSeg.videoEl : null; + + for (const seg of activeList) { + if (seg.type !== activeType || !seg.videoEl) continue; + if (seg === activeSeg) { + const expectedSec = (seg.trimStart + (currentFrame - seg.start)) / frameRate; + if (seg.videoEl.paused && !seg.videoEl.seeking) { + seg.videoEl.currentTime = expectedSec; + seg.videoEl.play().catch(e => console.warn("Video play prevented", e)); + } else if (!seg.videoEl.paused && Math.abs(seg.videoEl.currentTime - expectedSec) > 0.5) { + seg.videoEl.currentTime = expectedSec; + } + } else if (seg.videoEl !== activeVideoEl && !seg.videoEl.paused) { + seg.videoEl.pause(); + } + } + } + + _pauseAndSeekSegments(segments, currentFrame, frameRate, activeType) { + for (const seg of segments) { + if (seg.type !== activeType || !seg.videoEl) continue; + if (!seg.videoEl.paused) { + seg.videoEl.pause(); + } + if (currentFrame >= seg.start && currentFrame < seg.start + seg.length) { + seg.videoEl.currentTime = (seg.trimStart + (currentFrame - seg.start)) / frameRate; + } + } + } + async _preloadAudioSegment(seg) { if (seg._audioBuffer || seg._decoding) return; if (!seg.audioFile && !seg._blobUrl) return; @@ -11582,11 +11617,11 @@ class TimelineEditor { } // Sync video playback - if (this.retakeMode) { - if (this.timeline.retakeVideo) { - const retakeVid = this.timeline.retakeVideo; - this._ensureVideoEl(retakeVid); - if (retakeVid.videoEl) { + if (this.retakeMode) { + if (this.timeline.retakeVideo) { + const retakeVid = this.timeline.retakeVideo; + this._ensureVideoEl(retakeVid); + if (retakeVid.videoEl) { const expectedSec = this.currentFrame / frameRate; if (retakeVid.videoEl.paused && !retakeVid.videoEl.seeking) { retakeVid.videoEl.currentTime = expectedSec; @@ -11600,66 +11635,18 @@ class TimelineEditor { // Pause all other video elements const allSegments = [...(this.timeline.segments || []), ...(this.timeline.motionSegments || [])]; for (const seg of allSegments) { - if (seg.videoEl && !seg.videoEl.paused) { - seg.videoEl.pause(); - } - } - } else { - const activeSegments = (this._isDragging && this._previewSegments && this.selectionType !== "audio") ? this._previewSegments : this.timeline.segments; - const activeSeg = activeSegments.find(s => s.type === "video" && this.currentFrame >= s.start && this.currentFrame < s.start + s.length); - const activeVideoEl = activeSeg ? activeSeg.videoEl : null; - - for (const seg of activeSegments) { - if (seg.type === "video" && seg.videoEl) { - if (seg === activeSeg) { - const expectedSec = (seg.trimStart + (this.currentFrame - seg.start)) / frameRate; - if (seg.videoEl.paused && !seg.videoEl.seeking) { - // Not playing and no seek in flight — start a fresh seek+play - seg.videoEl.currentTime = expectedSec; - seg.videoEl.play().catch(e => console.warn("Video play prevented", e)); - } else if (!seg.videoEl.paused && Math.abs(seg.videoEl.currentTime - expectedSec) > 0.5) { - // Already playing but drifted — resync - seg.videoEl.currentTime = expectedSec; - } - // If paused && seeking: a seek+play is already in flight, let it finish - } else { - // Only pause if this segment's video element is NOT shared with the currently active segment - if (seg.videoEl !== activeVideoEl && !seg.videoEl.paused) { - seg.videoEl.pause(); - } - } - } - } - } - - // Sync motion playback - if (!this.retakeMode) { - const activeMotionSegments = (this._isDragging && this._previewSegments && this.selectionType === "motion") ? this._previewSegments : this.timeline.motionSegments; - const activeMotionSeg = activeMotionSegments.find(s => s.type === "motion_video" && this.currentFrame >= s.start && this.currentFrame < s.start + s.length); - const activeMotionVideoEl = activeMotionSeg ? activeMotionSeg.videoEl : null; - - for (const seg of activeMotionSegments) { - if (seg.type === "motion_video" && seg.videoEl) { - if (seg === activeMotionSeg) { - const expectedSec = (seg.trimStart + (this.currentFrame - seg.start)) / frameRate; - if (seg.videoEl.paused && !seg.videoEl.seeking) { - // Not playing and no seek in flight — start a fresh seek+play - seg.videoEl.currentTime = expectedSec; - seg.videoEl.play().catch(e => console.warn("Video play prevented", e)); - } else if (!seg.videoEl.paused && Math.abs(seg.videoEl.currentTime - expectedSec) > 0.5) { - // Already playing but drifted — resync - seg.videoEl.currentTime = expectedSec; - } - // If paused && seeking: a seek+play is already in flight, let it finish - } else { - // Only pause if this segment's video element is NOT shared with the currently active motion segment - if (seg.videoEl !== activeMotionVideoEl && !seg.videoEl.paused) { - seg.videoEl.pause(); - } - } - } - } - } + if (seg.videoEl && !seg.videoEl.paused) { + seg.videoEl.pause(); + } + } + } else { + this._syncActiveVideoSegments(this.timeline.segments, this.currentFrame, frameRate, "video", "image"); + } + + // Sync motion playback + if (!this.retakeMode) { + this._syncActiveVideoSegments(this.timeline.motionSegments, this.currentFrame, frameRate, "motion_video", "motion"); + } this.render(); this._playLoopId = requestAnimationFrame(loop); @@ -11676,40 +11663,20 @@ class TimelineEditor { try { this.audioContext.suspend(); } catch (e) { } } - if (this.retakeMode && this.timeline.retakeVideo) { - const retakeVid = this.timeline.retakeVideo; - if (retakeVid.videoEl) { - if (!retakeVid.videoEl.paused) { + if (this.retakeMode && this.timeline.retakeVideo) { + const retakeVid = this.timeline.retakeVideo; + if (retakeVid.videoEl) { + if (!retakeVid.videoEl.paused) { retakeVid.videoEl.pause(); } retakeVid.videoEl.muted = true; // Mute again on pause/stop to prevent transient audio bursts - retakeVid.videoEl.currentTime = this.currentFrame / this.getFrameRate(); - } - } else { - // Sync video segments on pause - for (const seg of this.timeline.segments) { - if (seg.type === "video" && seg.videoEl) { - if (!seg.videoEl.paused) { - seg.videoEl.pause(); - } - if (this.currentFrame >= seg.start && this.currentFrame < seg.start + seg.length) { - seg.videoEl.currentTime = (seg.trimStart + (this.currentFrame - seg.start)) / this.getFrameRate(); - } - } - } - - // Sync motion segments on pause - for (const seg of this.timeline.motionSegments) { - if (seg.type === "motion_video" && seg.videoEl) { - if (!seg.videoEl.paused) { - seg.videoEl.pause(); - } - if (this.currentFrame >= seg.start && this.currentFrame < seg.start + seg.length) { - seg.videoEl.currentTime = (seg.trimStart + (this.currentFrame - seg.start)) / this.getFrameRate(); - } - } - } - } + retakeVid.videoEl.currentTime = this.currentFrame / this.getFrameRate(); + } + } else { + const frameRate = this.getFrameRate(); + this._pauseAndSeekSegments(this.timeline.segments, this.currentFrame, frameRate, "video"); + this._pauseAndSeekSegments(this.timeline.motionSegments, this.currentFrame, frameRate, "motion_video"); + } for (let node of this.activeAudioNodes) { try { node.stop(); } catch (e) { }