mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-26 07:45:34 +00:00
fix(windows): resolve cursor telemetry paths and prevent export finalization stalls
Credit: Adapted from OpenScreen upstream PR contributions.
This commit is contained in:
@@ -121,6 +121,23 @@ function getTelemetryPathForVideo(videoPath: string) {
|
||||
return `${videoPath}.cursor.json`
|
||||
}
|
||||
|
||||
function normalizeVideoPathInput(videoPath: string) {
|
||||
if (!videoPath.startsWith('file:')) {
|
||||
return videoPath
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(videoPath)
|
||||
let filePath = decodeURIComponent(url.pathname)
|
||||
if (/^\/[A-Za-z]:/.test(filePath)) {
|
||||
filePath = filePath.slice(1)
|
||||
}
|
||||
return filePath
|
||||
} catch {
|
||||
return videoPath.replace(/^file:\/\//, '')
|
||||
}
|
||||
}
|
||||
|
||||
async function hasSiblingProjectFile(videoPath: string) {
|
||||
const baseName = path.basename(videoPath, path.extname(videoPath))
|
||||
const candidateExtensions = [PROJECT_FILE_EXTENSION, ...LEGACY_PROJECT_FILE_EXTENSIONS]
|
||||
@@ -1770,12 +1787,12 @@ export function registerIpcHandlers(
|
||||
})
|
||||
|
||||
ipcMain.handle('get-cursor-telemetry', async (_, videoPath?: string) => {
|
||||
const targetVideoPath = videoPath ?? currentVideoPath
|
||||
const targetVideoPath = videoPath ? normalizeVideoPathInput(videoPath) : currentVideoPath
|
||||
if (!targetVideoPath) {
|
||||
return { success: true, samples: [] }
|
||||
}
|
||||
|
||||
const telemetryPath = `${targetVideoPath}.cursor.json`
|
||||
const telemetryPath = getTelemetryPathForVideo(targetVideoPath)
|
||||
try {
|
||||
const content = await fs.readFile(telemetryPath, 'utf-8')
|
||||
const parsed = JSON.parse(content)
|
||||
|
||||
@@ -25,6 +25,7 @@ export const PROJECT_VERSION = 1;
|
||||
|
||||
export interface ProjectEditorState {
|
||||
wallpaper: string;
|
||||
backgroundTransparency: boolean;
|
||||
shadowIntensity: number;
|
||||
backgroundBlur: number;
|
||||
zoomMotionBlur: number;
|
||||
@@ -78,7 +79,11 @@ export function fromFileUrl(fileUrl: string): string {
|
||||
|
||||
try {
|
||||
const url = new URL(fileUrl);
|
||||
return decodeURIComponent(url.pathname);
|
||||
let filePath = decodeURIComponent(url.pathname);
|
||||
if (/^\/[A-Za-z]:/.test(filePath)) {
|
||||
filePath = filePath.slice(1);
|
||||
}
|
||||
return filePath;
|
||||
} catch {
|
||||
return fileUrl.replace(/^file:\/\//, "");
|
||||
}
|
||||
@@ -255,6 +260,9 @@ export function normalizeProjectEditor(editor: Partial<ProjectEditorState>): Pro
|
||||
|
||||
return {
|
||||
wallpaper: typeof editor.wallpaper === "string" ? editor.wallpaper : WALLPAPER_PATHS[0],
|
||||
backgroundTransparency: typeof (editor as Partial<ProjectEditorState>).backgroundTransparency === "boolean"
|
||||
? (editor as Partial<ProjectEditorState>).backgroundTransparency as boolean
|
||||
: false,
|
||||
shadowIntensity: typeof editor.shadowIntensity === "number" ? editor.shadowIntensity : 0.67,
|
||||
backgroundBlur: normalizedBackgroundBlur,
|
||||
zoomMotionBlur: normalizedZoomMotionBlur,
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { ZoomRegion, CropRegion, TrimRegion, AnnotationRegion, SpeedRegion,
|
||||
interface VideoExporterConfig extends ExportConfig {
|
||||
videoUrl: string;
|
||||
wallpaper: string;
|
||||
backgroundTransparency?: boolean;
|
||||
zoomRegions: ZoomRegion[];
|
||||
trimRegions?: TrimRegion[];
|
||||
speedRegions?: SpeedRegion[];
|
||||
@@ -47,6 +48,7 @@ export class VideoExporter {
|
||||
private videoColorSpace: VideoColorSpaceInit | undefined;
|
||||
private pendingMuxing: Promise<void> = Promise.resolve();
|
||||
private chunkCount = 0;
|
||||
private readonly WINDOWS_FINALIZATION_TIMEOUT_MS = 60_000;
|
||||
|
||||
constructor(config: VideoExporterConfig) {
|
||||
this.config = config;
|
||||
@@ -66,6 +68,7 @@ export class VideoExporter {
|
||||
width: this.config.width,
|
||||
height: this.config.height,
|
||||
wallpaper: this.config.wallpaper,
|
||||
backgroundTransparency: this.config.backgroundTransparency,
|
||||
zoomRegions: this.config.zoomRegions,
|
||||
showShadow: this.config.showShadow,
|
||||
shadowIntensity: this.config.shadowIntensity,
|
||||
@@ -139,22 +142,25 @@ export class VideoExporter {
|
||||
|
||||
// Finalize encoding
|
||||
if (this.encoder && this.encoder.state === 'configured') {
|
||||
await this.encoder.flush();
|
||||
await this.awaitWithWindowsTimeout(this.encoder.flush(), 'encoder flush');
|
||||
}
|
||||
|
||||
// Wait for queued muxing operations to complete
|
||||
await this.pendingMuxing;
|
||||
await this.awaitWithWindowsTimeout(this.pendingMuxing, 'muxing queued video chunks');
|
||||
|
||||
if (hasAudio && !this.cancelled) {
|
||||
const demuxer = this.streamingDecoder.getDemuxer();
|
||||
if (demuxer) {
|
||||
this.audioProcessor = new AudioProcessor();
|
||||
await this.audioProcessor.process(demuxer, this.muxer!, this.config.trimRegions);
|
||||
await this.awaitWithWindowsTimeout(
|
||||
this.audioProcessor.process(demuxer, this.muxer!, this.config.trimRegions),
|
||||
'audio processing',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize muxer and get output blob
|
||||
const blob = await this.muxer!.finalize();
|
||||
const blob = await this.awaitWithWindowsTimeout(this.muxer!.finalize(), 'muxer finalization');
|
||||
|
||||
return { success: true, blob };
|
||||
} catch (error) {
|
||||
@@ -168,6 +174,36 @@ export class VideoExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private isWindowsPlatform(): boolean {
|
||||
if (typeof navigator === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
return /Win/i.test(navigator.platform);
|
||||
}
|
||||
|
||||
private async awaitWithWindowsTimeout<T>(promise: Promise<T>, stage: string): Promise<T> {
|
||||
if (!this.isWindowsPlatform()) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
try {
|
||||
return await Promise.race([
|
||||
promise,
|
||||
new Promise<T>((_, reject) => {
|
||||
timeoutId = setTimeout(() => {
|
||||
reject(new Error(`Export timed out during ${stage} on Windows`));
|
||||
}, this.WINDOWS_FINALIZATION_TIMEOUT_MS);
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async encodeRenderedFrame(timestamp: number, frameDuration: number, frameIndex: number) {
|
||||
const canvas = this.renderer!.getCanvas();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user