refactor: extract director playback helpers
This commit is contained in:
@@ -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) {
|
||||
if (seg._audioBuffer || seg._decoding) return;
|
||||
if (!seg.audioFile && !seg._blobUrl) return;
|
||||
@@ -11399,92 +11498,21 @@ class TimelineEditor {
|
||||
this.playbackStartFrame = this.currentFrame;
|
||||
this.playbackStartTime = this.audioContext.currentTime;
|
||||
|
||||
// Build the list of active segments to play
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const segmentsToPlay = this._buildPlaybackAudioSources();
|
||||
|
||||
// Decode and schedule all scheduled segments that happen AT or AFTER currentFrame in the background
|
||||
for (let item of segmentsToPlay) {
|
||||
const segStartFrame = item.start;
|
||||
const segEndFrame = item.start + item.length;
|
||||
|
||||
if (segEndFrame <= this.currentFrame) continue;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
// Build mock seg object for helper compatibility
|
||||
const mockSeg = {
|
||||
audioFile: item.audioFile,
|
||||
audioB64: item.audioB64,
|
||||
_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;
|
||||
if (segEndFrame <= this.currentFrame) continue;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const { aborted, audioBuffer } = await this._preparePlaybackAudioBuffer(item, playId);
|
||||
if (aborted) return;
|
||||
|
||||
if (this._currentPlayId !== playId || !this.isPlaying) return;
|
||||
|
||||
// Determine current playback position dynamically in Web Audio time
|
||||
const currentPlayTime = this.audioContext.currentTime;
|
||||
@@ -11494,22 +11522,12 @@ class TimelineEditor {
|
||||
// If playback has already moved beyond the segment end, skip playing it
|
||||
if (currentFrameCalculated >= segEndFrame) return;
|
||||
|
||||
let startTime, fileOffsetSec, playDurationSec;
|
||||
|
||||
if (currentFrameCalculated < segStartFrame) {
|
||||
// Segment starts in the future relative to current playback position
|
||||
const waitFrames = segStartFrame - currentFrameCalculated;
|
||||
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;
|
||||
}
|
||||
const { startTime, fileOffsetSec, playDurationSec } = this._computePlaybackSchedule(
|
||||
item,
|
||||
currentFrameCalculated,
|
||||
currentPlayTime,
|
||||
frameRate,
|
||||
);
|
||||
|
||||
if (playDurationSec <= 0) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user