refactor: extract director playback helpers

This commit is contained in:
OpenClaw Agent
2026-07-09 15:13:38 +00:00
parent 268e543210
commit b9d68aea62

View File

@@ -2023,6 +2023,105 @@ class TimelineEditor {
} }
} }
_buildPlaybackAudioSources() {
const segmentsToPlay = [];
if (this.audioTrackEnabled && !this.retakeMode) {
for (const seg of this.timeline.audioSegments || []) {
segmentsToPlay.push({
type: "audio",
originalSeg: seg,
start: seg.start,
length: seg.length,
trimStart: seg.trimStart || 0,
audioFile: seg.audioFile,
audioB64: seg.audioB64,
_blobUrl: seg._blobUrl,
fileSize: seg.fileSize,
});
}
}
const isOverrideAudio = !!(this.node.properties.overrideAudio || this.timeline.overrideAudio);
if (isOverrideAudio && !this.retakeMode) {
for (const seg of this.timeline.motionSegments || []) {
if (!seg.videoFile && !seg._blobUrl) continue;
segmentsToPlay.push({
type: "motion",
originalSeg: seg,
start: seg.start,
length: seg.length,
trimStart: seg.trimStart || 0,
audioFile: seg.videoFile || seg.fileName,
audioB64: null,
_blobUrl: seg._blobUrl,
fileSize: seg.fileSize,
});
}
}
return segmentsToPlay;
}
_buildPlaybackMockSeg(item) {
return {
audioFile: item.audioFile,
audioB64: item.audioB64,
_blobUrl: item._blobUrl,
fileSize: item.fileSize,
waveformPeaks: item.originalSeg.waveformPeaks,
};
}
async _preparePlaybackAudioBuffer(item, playId) {
const mockSeg = this._buildPlaybackMockSeg(item);
await this._getOrExtractAudio(mockSeg);
if (this._currentPlayId !== playId || !this.isPlaying) {
return { aborted: true, mockSeg, audioBuffer: null };
}
if (mockSeg.waveformPeaks && !item.originalSeg.waveformPeaks) {
item.originalSeg.waveformPeaks = mockSeg.waveformPeaks;
this.render();
}
if (!this._isAudioDecodingAllowed(mockSeg)) {
return { aborted: true, mockSeg, audioBuffer: null };
}
let audioBuffer = item.originalSeg._audioBuffer;
if (!audioBuffer) {
if (!mockSeg.audioFile && !mockSeg._blobUrl && !mockSeg.audioB64) {
return { aborted: true, mockSeg, audioBuffer: null };
}
audioBuffer = await this._loadAudioBuffer(mockSeg);
item.originalSeg._audioBuffer = audioBuffer;
}
return { aborted: false, mockSeg, audioBuffer };
}
_computePlaybackSchedule(item, currentFrameCalculated, currentPlayTime, frameRate) {
const segStartFrame = item.start;
if (currentFrameCalculated < segStartFrame) {
const waitFrames = segStartFrame - currentFrameCalculated;
const waitTimeSec = waitFrames / frameRate;
return {
startTime: currentPlayTime + waitTimeSec,
fileOffsetSec: item.trimStart / frameRate,
playDurationSec: item.length / frameRate,
};
}
const framesToSkip = currentFrameCalculated - segStartFrame;
return {
startTime: currentPlayTime,
fileOffsetSec: (item.trimStart + framesToSkip) / frameRate,
playDurationSec: (item.length - framesToSkip) / frameRate,
};
}
async _preloadAudioSegment(seg) { async _preloadAudioSegment(seg) {
if (seg._audioBuffer || seg._decoding) return; if (seg._audioBuffer || seg._decoding) return;
if (!seg.audioFile && !seg._blobUrl) return; if (!seg.audioFile && !seg._blobUrl) return;
@@ -11399,92 +11498,21 @@ class TimelineEditor {
this.playbackStartFrame = this.currentFrame; this.playbackStartFrame = this.currentFrame;
this.playbackStartTime = this.audioContext.currentTime; this.playbackStartTime = this.audioContext.currentTime;
// Build the list of active segments to play const segmentsToPlay = this._buildPlaybackAudioSources();
const segmentsToPlay = [];
// 1. Standard Audio Segments on the audio track (only if the track is enabled and NOT in retake mode)
if (this.audioTrackEnabled && !this.retakeMode) {
if (this.timeline.audioSegments) {
for (let seg of this.timeline.audioSegments) {
segmentsToPlay.push({
type: 'audio',
originalSeg: seg,
start: seg.start,
length: seg.length,
trimStart: seg.trimStart || 0,
audioFile: seg.audioFile,
audioB64: seg.audioB64,
_blobUrl: seg._blobUrl,
fileSize: seg.fileSize
});
}
}
}
// 2. Motion Video Segments (only if overrideAudio toggle is ON and NOT in retake mode)
const isOverrideAudio = !!(this.node.properties.overrideAudio || this.timeline.overrideAudio);
if (isOverrideAudio && !this.retakeMode) {
if (this.timeline.motionSegments) {
for (let seg of this.timeline.motionSegments) {
if (seg.videoFile || seg._blobUrl) {
segmentsToPlay.push({
type: 'motion',
originalSeg: seg,
start: seg.start,
length: seg.length,
trimStart: seg.trimStart || 0,
audioFile: seg.videoFile || seg.fileName,
audioB64: null,
_blobUrl: seg._blobUrl,
fileSize: seg.fileSize
});
}
}
}
}
// Decode and schedule all scheduled segments that happen AT or AFTER currentFrame in the background // Decode and schedule all scheduled segments that happen AT or AFTER currentFrame in the background
for (let item of segmentsToPlay) { for (let item of segmentsToPlay) {
const segStartFrame = item.start; const segStartFrame = item.start;
const segEndFrame = item.start + item.length; const segEndFrame = item.start + item.length;
if (segEndFrame <= this.currentFrame) continue; if (segEndFrame <= this.currentFrame) continue;
(async () => { (async () => {
try { try {
// Build mock seg object for helper compatibility const { aborted, audioBuffer } = await this._preparePlaybackAudioBuffer(item, playId);
const mockSeg = { if (aborted) return;
audioFile: item.audioFile,
audioB64: item.audioB64, if (this._currentPlayId !== playId || !this.isPlaying) return;
_blobUrl: item._blobUrl,
fileSize: item.fileSize,
waveformPeaks: item.originalSeg.waveformPeaks
};
await this._getOrExtractAudio(mockSeg);
if (this._currentPlayId !== playId || !this.isPlaying) return;
if (mockSeg.waveformPeaks && !item.originalSeg.waveformPeaks) {
item.originalSeg.waveformPeaks = mockSeg.waveformPeaks;
this.render();
}
if (!this._isAudioDecodingAllowed(mockSeg)) {
return;
}
// Build audio buffer
let audioBuffer = item.originalSeg._audioBuffer;
if (!audioBuffer) {
if (!mockSeg.audioFile && !mockSeg._blobUrl && !mockSeg.audioB64) {
return;
}
audioBuffer = await this._loadAudioBuffer(mockSeg);
item.originalSeg._audioBuffer = audioBuffer;
}
if (this._currentPlayId !== playId || !this.isPlaying) return;
// Determine current playback position dynamically in Web Audio time // Determine current playback position dynamically in Web Audio time
const currentPlayTime = this.audioContext.currentTime; const currentPlayTime = this.audioContext.currentTime;
@@ -11494,22 +11522,12 @@ class TimelineEditor {
// If playback has already moved beyond the segment end, skip playing it // If playback has already moved beyond the segment end, skip playing it
if (currentFrameCalculated >= segEndFrame) return; if (currentFrameCalculated >= segEndFrame) return;
let startTime, fileOffsetSec, playDurationSec; const { startTime, fileOffsetSec, playDurationSec } = this._computePlaybackSchedule(
item,
if (currentFrameCalculated < segStartFrame) { currentFrameCalculated,
// Segment starts in the future relative to current playback position currentPlayTime,
const waitFrames = segStartFrame - currentFrameCalculated; frameRate,
const waitTimeSec = waitFrames / frameRate; );
startTime = currentPlayTime + waitTimeSec;
fileOffsetSec = item.trimStart / frameRate;
playDurationSec = item.length / frameRate;
} else {
// Segment is already playing. Start immediately, but offset into the audio buffer
startTime = currentPlayTime;
const framesToSkip = currentFrameCalculated - segStartFrame;
fileOffsetSec = (item.trimStart + framesToSkip) / frameRate;
playDurationSec = (item.length - framesToSkip) / frameRate;
}
if (playDurationSec <= 0) return; if (playDurationSec <= 0) return;