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) {
|
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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user