mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-26 07:45:34 +00:00
Merge pull request #586 from webadderallorg/fix/editor-refresh-companion-audio-preview
fix(editor): refresh companion audio preview
This commit is contained in:
@@ -501,6 +501,7 @@ export default function VideoEditor() {
|
||||
>({});
|
||||
const [defaultSourceAudioTrackSettings, setDefaultSourceAudioTrackSettings] =
|
||||
useState<SourceAudioTrackSettings>({});
|
||||
const [sourceAudioFallbackRefreshKey, setSourceAudioFallbackRefreshKey] = useState(0);
|
||||
const [hasClipSourceAudio, setHasClipSourceAudio] = useState(false);
|
||||
const [autoCaptions, setAutoCaptions] = useState<CaptionCue[]>([]);
|
||||
const [autoCaptionSettings, setAutoCaptionSettings] = useState<AutoCaptionSettings>(
|
||||
@@ -2298,26 +2299,29 @@ export default function VideoEditor() {
|
||||
}
|
||||
|
||||
return window.electronAPI.onRecordingSessionChanged((session) => {
|
||||
const sessionSourcePath = session?.videoPath ? fromFileUrl(session.videoPath) : null;
|
||||
const sessionWebcamPath = session?.webcamPath ? fromFileUrl(session.webcamPath) : null;
|
||||
console.log("[VideoEditor] onRecordingSessionChanged received!", {
|
||||
hasSession: Boolean(session),
|
||||
hasSessionVideoPath: Boolean(session?.videoPath),
|
||||
hasVideoSourcePath: Boolean(videoSourcePath),
|
||||
match: session?.videoPath === videoSourcePath,
|
||||
hasWebcamPath: Boolean(session?.webcamPath),
|
||||
match: sessionSourcePath === videoSourcePath,
|
||||
hasWebcamPath: Boolean(sessionWebcamPath),
|
||||
});
|
||||
|
||||
if (!session || session.videoPath !== videoSourcePath) {
|
||||
if (!session || sessionSourcePath !== videoSourcePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
setWebcam((prev) => ({
|
||||
...prev,
|
||||
enabled: Boolean(session.webcamPath),
|
||||
sourcePath: session.webcamPath ?? null,
|
||||
timeOffsetMs: session.webcamPath
|
||||
enabled: Boolean(sessionWebcamPath),
|
||||
sourcePath: sessionWebcamPath,
|
||||
timeOffsetMs: sessionWebcamPath
|
||||
? (session.timeOffsetMs ?? prev.timeOffsetMs)
|
||||
: DEFAULT_WEBCAM_TIME_OFFSET_MS,
|
||||
}));
|
||||
setSourceAudioFallbackRefreshKey((key) => key + 1);
|
||||
});
|
||||
}, [videoSourcePath]);
|
||||
|
||||
@@ -3173,6 +3177,7 @@ export default function VideoEditor() {
|
||||
duration,
|
||||
isPlaying,
|
||||
previewVolume,
|
||||
sourceAudioFallbackRefreshKey,
|
||||
summarizeErrorMessage,
|
||||
onSourceFallbackLoadError: (error) => {
|
||||
toast.warning(
|
||||
@@ -3182,6 +3187,15 @@ export default function VideoEditor() {
|
||||
},
|
||||
});
|
||||
|
||||
const startPlayback = useCallback(() => {
|
||||
const playback = videoPlaybackRef.current;
|
||||
const video = playback?.video;
|
||||
if (!playback || !video) return;
|
||||
|
||||
audio.playSourceAudioPreview();
|
||||
playback.play().catch((err) => console.error("Video play failed:", err));
|
||||
}, [audio.playSourceAudioPreview]);
|
||||
|
||||
function togglePlayPause() {
|
||||
const playback = videoPlaybackRef.current;
|
||||
const video = playback?.video;
|
||||
@@ -3190,7 +3204,7 @@ export default function VideoEditor() {
|
||||
if (!video.paused && !video.ended) {
|
||||
playback.pause();
|
||||
} else {
|
||||
playback.play().catch((err) => console.error("Video play failed:", err));
|
||||
startPlayback();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3901,7 +3915,7 @@ export default function VideoEditor() {
|
||||
const playback = videoPlaybackRef.current;
|
||||
if (playback?.video) {
|
||||
if (playback.video.paused) {
|
||||
playback.play().catch(console.error);
|
||||
startPlayback();
|
||||
} else {
|
||||
playback.pause();
|
||||
}
|
||||
@@ -3911,7 +3925,7 @@ export default function VideoEditor() {
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown, { capture: true });
|
||||
return () => window.removeEventListener("keydown", handleKeyDown, { capture: true });
|
||||
}, [shortcuts, isMac, handleUndo, handleRedo]);
|
||||
}, [shortcuts, isMac, handleUndo, handleRedo, startPlayback]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedZoomId && !zoomRegions.some((region) => region.id === selectedZoomId)) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { buildResolvedAudioPlan } from "@/lib/exporter/audioRoutingEngine";
|
||||
import { resolveMediaElementSource } from "@/lib/exporter/localMediaSource";
|
||||
import {
|
||||
@@ -72,7 +72,7 @@ export function useAudioPreviewSync({
|
||||
const sourceAudioResumePromiseRef = useRef<Promise<void> | null>(null);
|
||||
const lastSourceAudioSyncTimeRef = useRef<number | null>(null);
|
||||
|
||||
const ensureSourceAudioContext = () => {
|
||||
const ensureSourceAudioContext = useCallback(() => {
|
||||
if (!sourceAudioContextRef.current) {
|
||||
const context = new AudioContext({ latencyHint: "interactive" });
|
||||
const masterGain = context.createGain();
|
||||
@@ -82,9 +82,9 @@ export function useAudioPreviewSync({
|
||||
sourceAudioMasterGainRef.current = masterGain;
|
||||
}
|
||||
return sourceAudioContextRef.current;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const ensureSourceAudioRunning = () => {
|
||||
const ensureSourceAudioRunning = useCallback(() => {
|
||||
const context = ensureSourceAudioContext();
|
||||
if (context.state === "running") {
|
||||
return Promise.resolve();
|
||||
@@ -98,7 +98,15 @@ export function useAudioPreviewSync({
|
||||
});
|
||||
}
|
||||
return sourceAudioResumePromiseRef.current;
|
||||
};
|
||||
}, [ensureSourceAudioContext]);
|
||||
|
||||
const playSourceAudioPreview = useCallback(() => {
|
||||
void ensureSourceAudioRunning();
|
||||
for (const audio of sourceAudioElementsRef.current.values()) {
|
||||
if (!audio.src) continue;
|
||||
audio.play().catch(() => undefined);
|
||||
}
|
||||
}, [ensureSourceAudioRunning]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -216,6 +224,10 @@ export function useAudioPreviewSync({
|
||||
|
||||
sourceAudioElementRevokersRef.current.set(audioPath, resolved.revoke);
|
||||
latestAudio.src = resolved.src;
|
||||
latestAudio.load();
|
||||
if (isPlaying) {
|
||||
playSourceAudioPreview();
|
||||
}
|
||||
} catch (error) {
|
||||
if (cancelled) {
|
||||
return;
|
||||
@@ -252,10 +264,12 @@ export function useAudioPreviewSync({
|
||||
};
|
||||
}, [
|
||||
getSourceTrackPreviewGain,
|
||||
isPlaying,
|
||||
isCurrentClipMuted,
|
||||
onSourceFallbackLoadError,
|
||||
resolvedSourceTracks,
|
||||
previewVolume,
|
||||
playSourceAudioPreview,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -425,6 +439,7 @@ export function useAudioPreviewSync({
|
||||
previewVolume,
|
||||
resolvedSourceTracks,
|
||||
sourceAudioFallbackStartDelayMsByPath,
|
||||
ensureSourceAudioRunning,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -438,5 +453,7 @@ export function useAudioPreviewSync({
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [isPlaying, resolvedSourceTracks.length]);
|
||||
}, [isPlaying, resolvedSourceTracks.length, ensureSourceAudioRunning]);
|
||||
|
||||
return { playSourceAudioPreview };
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import { SOURCE_AUDIO_FALLBACK_TOAST_ID } from "@/components/video-editor/audio/
|
||||
|
||||
interface UseSourceAudioFallbackParams {
|
||||
currentSourcePath: string | null;
|
||||
refreshKey?: number;
|
||||
summarizeErrorMessage: (message: string) => string;
|
||||
}
|
||||
|
||||
export function useSourceAudioFallback({
|
||||
currentSourcePath,
|
||||
refreshKey = 0,
|
||||
summarizeErrorMessage,
|
||||
}: UseSourceAudioFallbackParams) {
|
||||
const [sourceAudioFallbackPaths, setSourceAudioFallbackPaths] = useState<string[]>([]);
|
||||
@@ -17,6 +19,8 @@ export function useSourceAudioFallback({
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
// Refetch when late recording sidecars are finalized after the editor opens.
|
||||
void refreshKey;
|
||||
setSourceAudioFallbackPaths([]);
|
||||
setSourceAudioFallbackStartDelayMsByPath({});
|
||||
|
||||
@@ -62,7 +66,7 @@ export function useSourceAudioFallback({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [currentSourcePath, summarizeErrorMessage]);
|
||||
}, [currentSourcePath, refreshKey, summarizeErrorMessage]);
|
||||
|
||||
return { sourceAudioFallbackPaths, sourceAudioFallbackStartDelayMsByPath };
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useMemo } from "react";
|
||||
import type { SourceAudioTrackSettings } from "@/components/video-editor/audio/audioTypes";
|
||||
import { resolveSourceTrackRoutingPolicy } from "@/lib/exporter/sourceTrackRoutingPolicy";
|
||||
import type {
|
||||
AudioRegion,
|
||||
ClipRegion,
|
||||
SpeedRegion,
|
||||
} from "../types";
|
||||
import type { SourceAudioTrackSettings } from "@/components/video-editor/audio/audioTypes";
|
||||
import { getActiveClipIdAtSourceTime, isClipMutedById } from "./clipAudio";
|
||||
import { useAudioPreviewSync } from "./useAudioPreviewSync";
|
||||
import { useClipAudioSettingsController } from "./useClipAudioSettingsController";
|
||||
@@ -45,6 +45,7 @@ interface UseVideoEditorAudioParams {
|
||||
duration: number;
|
||||
isPlaying: boolean;
|
||||
previewVolume: number;
|
||||
sourceAudioFallbackRefreshKey?: number;
|
||||
summarizeErrorMessage: (message: string) => string;
|
||||
onSourceFallbackLoadError: (error: unknown) => void;
|
||||
}
|
||||
@@ -64,6 +65,7 @@ export function useVideoEditorAudio({
|
||||
duration,
|
||||
isPlaying,
|
||||
previewVolume,
|
||||
sourceAudioFallbackRefreshKey = 0,
|
||||
summarizeErrorMessage,
|
||||
onSourceFallbackLoadError,
|
||||
}: UseVideoEditorAudioParams) {
|
||||
@@ -75,6 +77,7 @@ export function useVideoEditorAudio({
|
||||
const { sourceAudioFallbackPaths, sourceAudioFallbackStartDelayMsByPath } =
|
||||
useSourceAudioFallback({
|
||||
currentSourcePath: fallbackLookupSourcePath,
|
||||
refreshKey: sourceAudioFallbackRefreshKey,
|
||||
summarizeErrorMessage,
|
||||
});
|
||||
|
||||
@@ -113,7 +116,7 @@ export function useVideoEditorAudio({
|
||||
setDefaultSourceAudioTrackSettings,
|
||||
});
|
||||
|
||||
useAudioPreviewSync({
|
||||
const { playSourceAudioPreview } = useAudioPreviewSync({
|
||||
audioRegions,
|
||||
previewVolume,
|
||||
isPlaying,
|
||||
@@ -138,6 +141,7 @@ export function useVideoEditorAudio({
|
||||
sourceAudioTrackMeta,
|
||||
activeSourceAudioTrackSettings,
|
||||
selectedClipSourceAudioTrackSettings,
|
||||
playSourceAudioPreview,
|
||||
getSourceAudioTrackSettingsForClip,
|
||||
onSourceAudioTracksMetaChange,
|
||||
onSelectedClipSourceAudioTrackVolumeChange,
|
||||
|
||||
Reference in New Issue
Block a user