feat: add native Windows recorder helper

This commit is contained in:
EtienneLescot
2026-05-05 16:07:07 +02:00
parent d21e5eb34c
commit 062cf2a87c
27 changed files with 2873 additions and 139 deletions
+9 -9
View File
@@ -54,8 +54,8 @@ const PRETTY_NATIVE_CURSOR_ASSETS: Partial<Record<NativeCursorType, PrettyNative
imageDataUrl: arrowUrl,
width: 32,
height: 32,
hotspotX: 5.8,
hotspotY: 3.2,
hotspotX: 16.25,
hotspotY: 15.03,
},
text: {
imageDataUrl: textUrl,
@@ -67,9 +67,9 @@ const PRETTY_NATIVE_CURSOR_ASSETS: Partial<Record<NativeCursorType, PrettyNative
pointer: {
imageDataUrl: pointerUrl,
width: 32,
height: 32,
hotspotX: 11.8,
hotspotY: 2.6,
height: 33,
hotspotX: 16.65,
hotspotY: 14.24,
},
crosshair: {
imageDataUrl: crosshairUrl,
@@ -131,15 +131,15 @@ const PRETTY_NATIVE_CURSOR_ASSETS: Partial<Record<NativeCursorType, PrettyNative
imageDataUrl: appStartingUrl,
width: 32,
height: 32,
hotspotX: 5.8,
hotspotY: 3.2,
hotspotX: 7.25,
hotspotY: 4.03,
},
help: {
imageDataUrl: helpUrl,
width: 32,
height: 32,
hotspotX: 5.8,
hotspotY: 3.2,
hotspotX: 7.25,
hotspotY: 4.03,
},
"up-arrow": {
imageDataUrl: upArrowUrl,
+4 -24
View File
@@ -3,6 +3,7 @@ import type { SpeedRegion, TrimRegion } from "@/components/video-editor/types";
import type { VideoMuxer } from "./muxer";
const AUDIO_BITRATE = 128_000;
const EXPORT_AUDIO_CODEC = "mp4a.40.2";
const DECODE_BACKPRESSURE_LIMIT = 20;
const MIN_SPEED_REGION_DELTA_MS = 0.0001;
const SEEK_TIMEOUT_MS = 5_000;
@@ -138,7 +139,7 @@ export class AudioProcessor {
const channels = audioConfig.numberOfChannels || 2;
const encodeConfig: AudioEncoderConfig = {
codec: "opus",
codec: EXPORT_AUDIO_CODEC,
sampleRate,
numberOfChannels: channels,
bitrate: AUDIO_BITRATE,
@@ -146,7 +147,7 @@ export class AudioProcessor {
const encodeSupport = await AudioEncoder.isConfigSupported(encodeConfig);
if (!encodeSupport.supported) {
console.warn("[AudioProcessor] Opus encoding not supported, skipping audio");
console.warn("[AudioProcessor] AAC encoding not supported, skipping audio");
for (const frame of decodedFrames) frame.close();
return;
}
@@ -397,28 +398,7 @@ export class AudioProcessor {
try {
await demuxer.load(file);
const audioConfig = await demuxer.getDecoderConfig("audio");
const reader = demuxer.read("audio").getReader();
let isFirstChunk = true;
try {
while (!this.cancelled) {
const { done, value: chunk } = await reader.read();
if (done || !chunk) break;
if (isFirstChunk) {
await muxer.addAudioChunk(chunk, { decoderConfig: audioConfig });
isFirstChunk = false;
} else {
await muxer.addAudioChunk(chunk);
}
}
} finally {
try {
await reader.cancel();
} catch {
/* reader already closed */
}
}
await this.processTrimOnlyAudio(demuxer, muxer, []);
} finally {
try {
demuxer.destroy();
+1 -1
View File
@@ -40,7 +40,7 @@ export class VideoMuxer {
// Create audio source if needed
if (this.hasAudio) {
this.audioSource = new EncodedAudioPacketSource("opus");
this.audioSource = new EncodedAudioPacketSource("aac");
this.output.addAudioTrack(this.audioSource);
}
+41
View File
@@ -0,0 +1,41 @@
export type NativeWindowsSourceType = "display" | "window";
export type NativeWindowsRecordingRequest = {
recordingId?: number;
source: {
type: NativeWindowsSourceType;
sourceId: string;
displayId?: number;
windowHandle?: string;
};
video: {
fps: number;
width: number;
height: number;
};
audio: {
system: {
enabled: boolean;
};
microphone: {
enabled: boolean;
deviceId?: string;
gain: number;
};
};
webcam: {
enabled: boolean;
deviceId?: string;
width: number;
height: number;
fps: number;
};
};
export type NativeWindowsRecordingStartResult = {
success: boolean;
recordingId?: number;
path?: string;
helperPath?: string;
error?: string;
};