fix: address native mac review feedback
This commit is contained in:
+66
-40
@@ -1,4 +1,5 @@
|
|||||||
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
|
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
|
||||||
|
import { EventEmitter } from "node:events";
|
||||||
import { constants as fsConstants } from "node:fs";
|
import { constants as fsConstants } from "node:fs";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
@@ -47,6 +48,7 @@ const RECORDING_FILE_PREFIX = "recording-";
|
|||||||
const RECORDING_SESSION_SUFFIX = ".session.json";
|
const RECORDING_SESSION_SUFFIX = ".session.json";
|
||||||
const ALLOWED_IMPORT_VIDEO_EXTENSIONS = new Set([".webm", ".mp4", ".mov", ".avi", ".mkv"]);
|
const ALLOWED_IMPORT_VIDEO_EXTENSIONS = new Set([".webm", ".mp4", ".mov", ".avi", ".mkv"]);
|
||||||
const PREVIEW_AUDIO_DIR = path.join(app.getPath("userData"), "preview-audio");
|
const PREVIEW_AUDIO_DIR = path.join(app.getPath("userData"), "preview-audio");
|
||||||
|
const nativeMacCaptureEvents = new EventEmitter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Paths explicitly approved by the user via file picker dialogs or project loads.
|
* Paths explicitly approved by the user via file picker dialogs or project loads.
|
||||||
@@ -996,6 +998,43 @@ function tryParseNativeHelperEvent(line: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function inspectNativeMacCaptureOutput() {
|
||||||
|
for (const line of nativeMacCaptureOutput.split(/\r?\n/)) {
|
||||||
|
const event = tryParseNativeHelperEvent(line.trim());
|
||||||
|
if (event) {
|
||||||
|
nativeMacCaptureEvents.emit("helper-event", event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachNativeMacCaptureOutputDrain(proc: ChildProcessWithoutNullStreams) {
|
||||||
|
let lineBuffer = "";
|
||||||
|
const drain = (chunk: Buffer) => {
|
||||||
|
const text = chunk.toString();
|
||||||
|
nativeMacCaptureOutput += text;
|
||||||
|
lineBuffer += text;
|
||||||
|
const lines = lineBuffer.split(/\r?\n/);
|
||||||
|
lineBuffer = lines.pop() ?? "";
|
||||||
|
for (const line of lines) {
|
||||||
|
const event = tryParseNativeHelperEvent(line.trim());
|
||||||
|
if (event) {
|
||||||
|
nativeMacCaptureEvents.emit("helper-event", event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const cleanup = () => {
|
||||||
|
proc.stdout.off("data", drain);
|
||||||
|
proc.stderr.off("data", drain);
|
||||||
|
proc.off("close", cleanup);
|
||||||
|
proc.off("error", cleanup);
|
||||||
|
};
|
||||||
|
|
||||||
|
proc.stdout.on("data", drain);
|
||||||
|
proc.stderr.on("data", drain);
|
||||||
|
proc.once("close", cleanup);
|
||||||
|
proc.once("error", cleanup);
|
||||||
|
}
|
||||||
|
|
||||||
function waitForNativeMacCaptureStart(proc: ChildProcessWithoutNullStreams) {
|
function waitForNativeMacCaptureStart(proc: ChildProcessWithoutNullStreams) {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
@@ -1003,25 +1042,19 @@ function waitForNativeMacCaptureStart(proc: ChildProcessWithoutNullStreams) {
|
|||||||
reject(new Error("Timed out waiting for native macOS capture to start"));
|
reject(new Error("Timed out waiting for native macOS capture to start"));
|
||||||
}, 10_000);
|
}, 10_000);
|
||||||
|
|
||||||
const inspect = (chunk: Buffer) => {
|
const inspect = (event: Record<string, unknown>) => {
|
||||||
nativeMacCaptureOutput += chunk.toString();
|
if (event.event === "recording-started") {
|
||||||
for (const line of nativeMacCaptureOutput.split(/\r?\n/)) {
|
cleanup();
|
||||||
const event = tryParseNativeHelperEvent(line.trim());
|
resolve();
|
||||||
if (!event) continue;
|
return;
|
||||||
if (event.event === "recording-started") {
|
}
|
||||||
cleanup();
|
if (event.event === "error") {
|
||||||
resolve();
|
cleanup();
|
||||||
return;
|
reject(new Error(String(event.message ?? event.code ?? "Native macOS capture failed")));
|
||||||
}
|
|
||||||
if (event.event === "error") {
|
|
||||||
cleanup();
|
|
||||||
reject(new Error(event.message ?? event.code ?? "Native macOS capture failed"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOutput = (chunk: Buffer) => inspect(chunk);
|
const onOutput = (event: Record<string, unknown>) => inspect(event);
|
||||||
const onClose = (code: number | null) => {
|
const onClose = (code: number | null) => {
|
||||||
cleanup();
|
cleanup();
|
||||||
reject(
|
reject(
|
||||||
@@ -1037,16 +1070,15 @@ function waitForNativeMacCaptureStart(proc: ChildProcessWithoutNullStreams) {
|
|||||||
};
|
};
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
proc.stdout.off("data", onOutput);
|
nativeMacCaptureEvents.off("helper-event", onOutput);
|
||||||
proc.stderr.off("data", onOutput);
|
|
||||||
proc.off("close", onClose);
|
proc.off("close", onClose);
|
||||||
proc.off("error", onError);
|
proc.off("error", onError);
|
||||||
};
|
};
|
||||||
|
|
||||||
proc.stdout.on("data", onOutput);
|
nativeMacCaptureEvents.on("helper-event", onOutput);
|
||||||
proc.stderr.on("data", onOutput);
|
|
||||||
proc.once("close", onClose);
|
proc.once("close", onClose);
|
||||||
proc.once("error", onError);
|
proc.once("error", onError);
|
||||||
|
inspectNativeMacCaptureOutput();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1063,25 +1095,19 @@ function waitForNativeMacCaptureStop(proc: ChildProcessWithoutNullStreams) {
|
|||||||
);
|
);
|
||||||
}, 30_000);
|
}, 30_000);
|
||||||
|
|
||||||
const inspect = (chunk: Buffer) => {
|
const inspect = (event: Record<string, unknown>) => {
|
||||||
nativeMacCaptureOutput += chunk.toString();
|
if (event.event === "recording-stopped") {
|
||||||
for (const line of nativeMacCaptureOutput.split(/\r?\n/)) {
|
cleanup();
|
||||||
const event = tryParseNativeHelperEvent(line.trim());
|
resolve(String(event.screenPath ?? nativeMacCaptureTargetPath ?? ""));
|
||||||
if (!event) continue;
|
return;
|
||||||
if (event.event === "recording-stopped") {
|
}
|
||||||
cleanup();
|
if (event.event === "error") {
|
||||||
resolve(event.screenPath ?? nativeMacCaptureTargetPath ?? "");
|
cleanup();
|
||||||
return;
|
reject(new Error(String(event.message ?? event.code ?? "Native macOS capture failed")));
|
||||||
}
|
|
||||||
if (event.event === "error") {
|
|
||||||
cleanup();
|
|
||||||
reject(new Error(event.message ?? event.code ?? "Native macOS capture failed"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOutput = (chunk: Buffer) => inspect(chunk);
|
const onOutput = (event: Record<string, unknown>) => inspect(event);
|
||||||
const onClose = (code: number | null) => {
|
const onClose = (code: number | null) => {
|
||||||
if (code === 0 && nativeMacCaptureTargetPath) {
|
if (code === 0 && nativeMacCaptureTargetPath) {
|
||||||
cleanup();
|
cleanup();
|
||||||
@@ -1102,16 +1128,15 @@ function waitForNativeMacCaptureStop(proc: ChildProcessWithoutNullStreams) {
|
|||||||
};
|
};
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
proc.stdout.off("data", onOutput);
|
nativeMacCaptureEvents.off("helper-event", onOutput);
|
||||||
proc.stderr.off("data", onOutput);
|
|
||||||
proc.off("close", onClose);
|
proc.off("close", onClose);
|
||||||
proc.off("error", onError);
|
proc.off("error", onError);
|
||||||
};
|
};
|
||||||
|
|
||||||
proc.stdout.on("data", onOutput);
|
nativeMacCaptureEvents.on("helper-event", onOutput);
|
||||||
proc.stderr.on("data", onOutput);
|
|
||||||
proc.once("close", onClose);
|
proc.once("close", onClose);
|
||||||
proc.once("error", onError);
|
proc.once("error", onError);
|
||||||
|
inspectNativeMacCaptureOutput();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1722,6 +1747,7 @@ export function registerIpcHandlers(
|
|||||||
stdio: ["pipe", "pipe", "pipe"],
|
stdio: ["pipe", "pipe", "pipe"],
|
||||||
});
|
});
|
||||||
nativeMacCaptureProcess = proc;
|
nativeMacCaptureProcess = proc;
|
||||||
|
attachNativeMacCaptureOutputDrain(proc);
|
||||||
|
|
||||||
await waitForNativeMacCaptureStart(proc);
|
await waitForNativeMacCaptureStart(proc);
|
||||||
const captureStartedAtMs = Date.now();
|
const captureStartedAtMs = Date.now();
|
||||||
|
|||||||
@@ -101,6 +101,10 @@ func parentElement(_ element: AXUIElement) -> AXUIElement? {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard CFGetTypeID(value) == AXUIElementGetTypeID() else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return (value as! AXUIElement)
|
return (value as! AXUIElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -327,8 +327,8 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
|
|||||||
AVCaptureDevice.requestAccess(for: .audio) { _ in
|
AVCaptureDevice.requestAccess(for: .audio) { _ in
|
||||||
semaphore.signal()
|
semaphore.signal()
|
||||||
}
|
}
|
||||||
semaphore.wait()
|
let waitResult = semaphore.wait(timeout: .now() + 30)
|
||||||
if AVCaptureDevice.authorizationStatus(for: .audio) != .authorized {
|
if waitResult == .timedOut || AVCaptureDevice.authorizationStatus(for: .audio) != .authorized {
|
||||||
throw HelperError.permissionDenied("Microphone permission is required for native microphone capture.")
|
throw HelperError.permissionDenied("Microphone permission is required for native microphone capture.")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@
|
|||||||
"i18n:check": "node scripts/i18n-check.mjs",
|
"i18n:check": "node scripts/i18n-check.mjs",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build:native:mac": "node scripts/build-macos-screencapturekit-helper.mjs",
|
"build:native:mac": "node scripts/build-macos-screencapturekit-helper.mjs",
|
||||||
"build:mac": "tsc && vite build && electron-builder --mac",
|
"build:mac": "npm run build:native:mac && tsc && vite build && electron-builder --mac",
|
||||||
"build:native:win": "node scripts/build-windows-wgc-helper.mjs",
|
"build:native:win": "node scripts/build-windows-wgc-helper.mjs",
|
||||||
"build:win": "npm run build:native:win && tsc && vite build && electron-builder --win --config.npmRebuild=false",
|
"build:win": "npm run build:native:win && tsc && vite build && electron-builder --win --config.npmRebuild=false",
|
||||||
"build:linux": "tsc && vite build && electron-builder --linux AppImage deb pacman --config.npmRebuild=false",
|
"build:linux": "tsc && vite build && electron-builder --linux AppImage deb pacman --config.npmRebuild=false",
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ if (result.status !== 0) {
|
|||||||
|
|
||||||
fs.mkdirSync(buildDir, { recursive: true });
|
fs.mkdirSync(buildDir, { recursive: true });
|
||||||
fs.mkdirSync(distributableDir, { recursive: true });
|
fs.mkdirSync(distributableDir, { recursive: true });
|
||||||
|
for (const artifactPath of [builtHelperPath, builtCursorHelperPath]) {
|
||||||
|
if (!fs.existsSync(artifactPath)) {
|
||||||
|
console.error(`Swift build completed but expected artifact was not found: ${artifactPath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
fs.copyFileSync(builtHelperPath, localHelperPath);
|
fs.copyFileSync(builtHelperPath, localHelperPath);
|
||||||
fs.copyFileSync(builtHelperPath, distributablePath);
|
fs.copyFileSync(builtHelperPath, distributablePath);
|
||||||
fs.copyFileSync(builtCursorHelperPath, localCursorHelperPath);
|
fs.copyFileSync(builtCursorHelperPath, localCursorHelperPath);
|
||||||
|
|||||||
@@ -282,21 +282,24 @@ export function LaunchWindow() {
|
|||||||
return () => cancelAnimationFrame(id);
|
return () => cancelAnimationFrame(id);
|
||||||
}, [isLanguageMenuOpen]);
|
}, [isLanguageMenuOpen]);
|
||||||
|
|
||||||
|
const hudMouseEventsEnabledRef = useRef<boolean | undefined>(undefined);
|
||||||
const setHudMouseEventsEnabled = useCallback((enabled: boolean) => {
|
const setHudMouseEventsEnabled = useCallback((enabled: boolean) => {
|
||||||
|
if (hudMouseEventsEnabledRef.current === enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hudMouseEventsEnabledRef.current = enabled;
|
||||||
window.electronAPI?.setHudOverlayIgnoreMouseEvents?.(!enabled);
|
window.electronAPI?.setHudOverlayIgnoreMouseEvents?.(!enabled);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.electronAPI?.setHudOverlayIgnoreMouseEvents?.(true);
|
setHudMouseEventsEnabled(false);
|
||||||
return () => {
|
return () => {
|
||||||
window.electronAPI?.setHudOverlayIgnoreMouseEvents?.(false);
|
window.electronAPI?.setHudOverlayIgnoreMouseEvents?.(false);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [setHudMouseEventsEnabled]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isLanguageMenuOpen) {
|
setHudMouseEventsEnabled(isLanguageMenuOpen);
|
||||||
setHudMouseEventsEnabled(true);
|
|
||||||
}
|
|
||||||
}, [isLanguageMenuOpen, setHudMouseEventsEnabled]);
|
}, [isLanguageMenuOpen, setHudMouseEventsEnabled]);
|
||||||
|
|
||||||
const [selectedSource, setSelectedSource] = useState("Screen");
|
const [selectedSource, setSelectedSource] = useState("Screen");
|
||||||
@@ -389,7 +392,7 @@ export function LaunchWindow() {
|
|||||||
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
||||||
event.currentTarget.releasePointerCapture(event.pointerId);
|
event.currentTarget.releasePointerCapture(event.pointerId);
|
||||||
}
|
}
|
||||||
setHudMouseEventsEnabled(true);
|
setHudMouseEventsEnabled(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -775,7 +775,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
webcam: {
|
webcam: {
|
||||||
enabled: false,
|
enabled: webcamEnabled,
|
||||||
deviceId: webcamDeviceId,
|
deviceId: webcamDeviceId,
|
||||||
deviceName: webcamDeviceName,
|
deviceName: webcamDeviceName,
|
||||||
width: WEBCAM_TARGET_WIDTH,
|
width: WEBCAM_TARGET_WIDTH,
|
||||||
@@ -857,6 +857,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (!isCountdownRunActive(countdownRunToken)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (webcamStream.current) {
|
if (webcamStream.current) {
|
||||||
nativeWebcamRecorder = createRecorderHandle(webcamStream.current, {
|
nativeWebcamRecorder = createRecorderHandle(webcamStream.current, {
|
||||||
mimeType: selectMimeType(),
|
mimeType: selectMimeType(),
|
||||||
@@ -867,6 +870,12 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
|||||||
setWebcamEnabledState(false);
|
setWebcamEnabledState(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!isCountdownRunActive(countdownRunToken)) {
|
||||||
|
if (nativeWebcamRecorder && nativeWebcamRecorder.recorder.state !== "inactive") {
|
||||||
|
nativeWebcamRecorder.recorder.stop();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const request: NativeMacRecordingRequest = {
|
const request: NativeMacRecordingRequest = {
|
||||||
schemaVersion: 1,
|
schemaVersion: 1,
|
||||||
recordingId: activeRecordingId,
|
recordingId: activeRecordingId,
|
||||||
@@ -916,6 +925,13 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
|||||||
}
|
}
|
||||||
throw new Error(result.error ?? "Native macOS capture failed.");
|
throw new Error(result.error ?? "Native macOS capture failed.");
|
||||||
}
|
}
|
||||||
|
if (!isCountdownRunActive(countdownRunToken)) {
|
||||||
|
if (nativeWebcamRecorder && nativeWebcamRecorder.recorder.state !== "inactive") {
|
||||||
|
nativeWebcamRecorder.recorder.stop();
|
||||||
|
}
|
||||||
|
await window.electronAPI.stopNativeMacRecording(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
recordingId.current = result.recordingId;
|
recordingId.current = result.recordingId;
|
||||||
nativeMacRecording.current = {
|
nativeMacRecording.current = {
|
||||||
@@ -969,9 +985,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
|||||||
if (platform === "darwin" && cursorCaptureMode === "editable-overlay") {
|
if (platform === "darwin" && cursorCaptureMode === "editable-overlay") {
|
||||||
const access = await window.electronAPI.requestNativeMacCursorAccess();
|
const access = await window.electronAPI.requestNativeMacCursorAccess();
|
||||||
if (!access.granted) {
|
if (!access.granted) {
|
||||||
toast.info(
|
toast.info(t("recording.accessibilityAllowAndRetry"));
|
||||||
"Allow Accessibility access for OpenScreen, then press record again to start the countdown.",
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1415,6 +1429,18 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (nativeMacRecording.current) {
|
||||||
|
const activeRecordingId = recordingId.current;
|
||||||
|
restarting.current = true;
|
||||||
|
discardRecordingId.current = activeRecordingId;
|
||||||
|
try {
|
||||||
|
await finalizeNativeMacRecording(true);
|
||||||
|
await startRecording();
|
||||||
|
} finally {
|
||||||
|
restarting.current = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const activeScreenRecorder = screenRecorder.current;
|
const activeScreenRecorder = screenRecorder.current;
|
||||||
if (!activeScreenRecorder || activeScreenRecorder.recorder.state === "inactive") return;
|
if (!activeScreenRecorder || activeScreenRecorder.recorder.state === "inactive") return;
|
||||||
@@ -1480,6 +1506,13 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
|||||||
void finalizeNativeWindowsRecording(true);
|
void finalizeNativeWindowsRecording(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (nativeMacRecording.current) {
|
||||||
|
const activeRecordingId = recordingId.current;
|
||||||
|
discardRecordingId.current = activeRecordingId;
|
||||||
|
allowAutoFinalize.current = false;
|
||||||
|
void finalizeNativeMacRecording(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const activeScreenRecorder = screenRecorder.current;
|
const activeScreenRecorder = screenRecorder.current;
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"cameraDenied": "تم رفض الوصول إلى الكاميرا. سيستمر التسجيل بدون كاميرا الويب.",
|
"cameraDenied": "تم رفض الوصول إلى الكاميرا. سيستمر التسجيل بدون كاميرا الويب.",
|
||||||
"cameraDisconnected": "تم فصل كاميرا الويب.",
|
"cameraDisconnected": "تم فصل كاميرا الويب.",
|
||||||
"cameraNotFound": "لم يتم العثور على كاميرا.",
|
"cameraNotFound": "لم يتم العثور على كاميرا.",
|
||||||
"permissionDenied": "تم رفض إذن التسجيل. يرجى السماح بتسجيل الشاشة."
|
"permissionDenied": "تم رفض إذن التسجيل. يرجى السماح بتسجيل الشاشة.",
|
||||||
|
"accessibilityAllowAndRetry": "اسمح بوصول تسهيلات الاستخدام لـ OpenScreen، ثم اضغط على التسجيل مرة أخرى لبدء العد التنازلي."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"cameraDenied": "Camera access denied. Recording will continue without webcam.",
|
"cameraDenied": "Camera access denied. Recording will continue without webcam.",
|
||||||
"cameraDisconnected": "Webcam disconnected.",
|
"cameraDisconnected": "Webcam disconnected.",
|
||||||
"cameraNotFound": "Camera not found.",
|
"cameraNotFound": "Camera not found.",
|
||||||
"permissionDenied": "Recording permission denied. Please allow screen recording."
|
"permissionDenied": "Recording permission denied. Please allow screen recording.",
|
||||||
|
"accessibilityAllowAndRetry": "Allow Accessibility access for OpenScreen, then press record again to start the countdown."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,8 @@
|
|||||||
"cameraDenied": "Acceso a la cámara denegado. La grabación continuará sin cámara web.",
|
"cameraDenied": "Acceso a la cámara denegado. La grabación continuará sin cámara web.",
|
||||||
"cameraDisconnected": "Cámara web desconectada.",
|
"cameraDisconnected": "Cámara web desconectada.",
|
||||||
"cameraNotFound": "Cámara no encontrada.",
|
"cameraNotFound": "Cámara no encontrada.",
|
||||||
"permissionDenied": "Permiso de grabación denegado. Por favor permite la grabación de pantalla."
|
"permissionDenied": "Permiso de grabación denegado. Por favor permite la grabación de pantalla.",
|
||||||
|
"accessibilityAllowAndRetry": "Permite el acceso de accesibilidad para OpenScreen y luego pulsa grabar de nuevo para iniciar la cuenta atrás."
|
||||||
},
|
},
|
||||||
"loadingVideo": "Cargando video...",
|
"loadingVideo": "Cargando video...",
|
||||||
"newRecording": {
|
"newRecording": {
|
||||||
|
|||||||
@@ -39,7 +39,8 @@
|
|||||||
"cameraDenied": "Accès à la caméra refusé. L'enregistrement continuera sans webcam.",
|
"cameraDenied": "Accès à la caméra refusé. L'enregistrement continuera sans webcam.",
|
||||||
"cameraDisconnected": "Webcam déconnectée.",
|
"cameraDisconnected": "Webcam déconnectée.",
|
||||||
"cameraNotFound": "Caméra introuvable.",
|
"cameraNotFound": "Caméra introuvable.",
|
||||||
"permissionDenied": "Permission d'enregistrement refusée. Veuillez autoriser l'enregistrement d'écran."
|
"permissionDenied": "Permission d'enregistrement refusée. Veuillez autoriser l'enregistrement d'écran.",
|
||||||
|
"accessibilityAllowAndRetry": "Autorisez l'accès Accessibilité pour OpenScreen, puis appuyez de nouveau sur enregistrer pour lancer le compte à rebours."
|
||||||
},
|
},
|
||||||
"loadingVideo": "Chargement de la vidéo..."
|
"loadingVideo": "Chargement de la vidéo..."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"cameraDenied": "カメラのアクセスが拒否されました。ウェブカメラなしで録画を続行します。",
|
"cameraDenied": "カメラのアクセスが拒否されました。ウェブカメラなしで録画を続行します。",
|
||||||
"permissionDenied": "録画の権限が拒否されました。画面録画を許可してください。",
|
"permissionDenied": "録画の権限が拒否されました。画面録画を許可してください。",
|
||||||
"cameraDisconnected": "ウェブカメラが切断されました。",
|
"cameraDisconnected": "ウェブカメラが切断されました。",
|
||||||
"cameraNotFound": "カメラが見つかりません。"
|
"cameraNotFound": "カメラが見つかりません。",
|
||||||
|
"accessibilityAllowAndRetry": "OpenScreenにアクセシビリティアクセスを許可してから、もう一度録画を押してカウントダウンを開始してください。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"cameraDenied": "카메라 접근이 거부되었습니다. 웹캠 없이 녹화를 계속합니다.",
|
"cameraDenied": "카메라 접근이 거부되었습니다. 웹캠 없이 녹화를 계속합니다.",
|
||||||
"permissionDenied": "녹화 권한이 거부되었습니다. 화면 녹화를 허용해 주세요.",
|
"permissionDenied": "녹화 권한이 거부되었습니다. 화면 녹화를 허용해 주세요.",
|
||||||
"cameraDisconnected": "웹캠 연결이 끊어졌습니다.",
|
"cameraDisconnected": "웹캠 연결이 끊어졌습니다.",
|
||||||
"cameraNotFound": "카메라를 찾을 수 없습니다."
|
"cameraNotFound": "카메라를 찾을 수 없습니다.",
|
||||||
|
"accessibilityAllowAndRetry": "OpenScreen의 손쉬운 사용 접근을 허용한 다음, 카운트다운을 시작하려면 다시 녹화를 누르세요."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"cameraDenied": "Доступ к камере запрещён. Запись продолжится без веб-камеры.",
|
"cameraDenied": "Доступ к камере запрещён. Запись продолжится без веб-камеры.",
|
||||||
"cameraDisconnected": "Веб-камера отключена.",
|
"cameraDisconnected": "Веб-камера отключена.",
|
||||||
"cameraNotFound": "Камера не найдена.",
|
"cameraNotFound": "Камера не найдена.",
|
||||||
"permissionDenied": "Разрешение на запись запрещено. Пожалуйста, разрешите запись экрана."
|
"permissionDenied": "Разрешение на запись запрещено. Пожалуйста, разрешите запись экрана.",
|
||||||
|
"accessibilityAllowAndRetry": "Разрешите OpenScreen доступ к Универсальному доступу, затем снова нажмите запись, чтобы начать обратный отсчет."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,8 @@
|
|||||||
"cameraDenied": "Kamera erişimi reddedildi. Kayıt kamera olmadan devam edecek.",
|
"cameraDenied": "Kamera erişimi reddedildi. Kayıt kamera olmadan devam edecek.",
|
||||||
"permissionDenied": "Kayıt izni reddedildi. Lütfen ekran kaydına izin verin.",
|
"permissionDenied": "Kayıt izni reddedildi. Lütfen ekran kaydına izin verin.",
|
||||||
"cameraDisconnected": "Webcam bağlantısı kesildi.",
|
"cameraDisconnected": "Webcam bağlantısı kesildi.",
|
||||||
"cameraNotFound": "Kamera bulunamadı."
|
"cameraNotFound": "Kamera bulunamadı.",
|
||||||
|
"accessibilityAllowAndRetry": "OpenScreen için Erişilebilirlik erişimine izin verin, ardından geri sayımı başlatmak için tekrar kayda basın."
|
||||||
},
|
},
|
||||||
"loadingVideo": "Video yükleniyor...",
|
"loadingVideo": "Video yükleniyor...",
|
||||||
"newRecording": {
|
"newRecording": {
|
||||||
|
|||||||
@@ -14,8 +14,7 @@
|
|||||||
"disableSystemAudio": "Sistem sesini devre dışı bırak",
|
"disableSystemAudio": "Sistem sesini devre dışı bırak",
|
||||||
"enableMicrophone": "Mikrofonu etkinleştir",
|
"enableMicrophone": "Mikrofonu etkinleştir",
|
||||||
"disableMicrophone": "Mikrofonu devre dışı bırak",
|
"disableMicrophone": "Mikrofonu devre dışı bırak",
|
||||||
"defaultMicrophone": "Varsayılan Mikrofon",
|
"defaultMicrophone": "Varsayılan Mikrofon"
|
||||||
"nrLevel": {}
|
|
||||||
},
|
},
|
||||||
"webcam": {
|
"webcam": {
|
||||||
"enableWebcam": "Kamerayı etkinleştir",
|
"enableWebcam": "Kamerayı etkinleştir",
|
||||||
|
|||||||
@@ -194,7 +194,5 @@
|
|||||||
"language": {
|
"language": {
|
||||||
"title": "Dil"
|
"title": "Dil"
|
||||||
},
|
},
|
||||||
"audio": {
|
"audio": {}
|
||||||
"nrLevel": {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"cameraDenied": "Quyền truy cập máy ảnh bị từ chối. Sẽ tiếp tục ghi hình không có webcam.",
|
"cameraDenied": "Quyền truy cập máy ảnh bị từ chối. Sẽ tiếp tục ghi hình không có webcam.",
|
||||||
"cameraDisconnected": "Webcam bị ngắt kết nối.",
|
"cameraDisconnected": "Webcam bị ngắt kết nối.",
|
||||||
"cameraNotFound": "Không tìm thấy máy ảnh.",
|
"cameraNotFound": "Không tìm thấy máy ảnh.",
|
||||||
"permissionDenied": "Quyền ghi hình bị từ chối. Vui lòng cho phép ghi màn hình."
|
"permissionDenied": "Quyền ghi hình bị từ chối. Vui lòng cho phép ghi màn hình.",
|
||||||
|
"accessibilityAllowAndRetry": "Cho phép OpenScreen truy cập Trợ năng, sau đó nhấn ghi lại để bắt đầu đếm ngược."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"cameraDenied": "摄像头权限被拒绝。录制将继续,但不包含摄像头画面。",
|
"cameraDenied": "摄像头权限被拒绝。录制将继续,但不包含摄像头画面。",
|
||||||
"cameraDisconnected": "摄像头已断开连接。",
|
"cameraDisconnected": "摄像头已断开连接。",
|
||||||
"cameraNotFound": "未找到摄像头。",
|
"cameraNotFound": "未找到摄像头。",
|
||||||
"permissionDenied": "录屏权限被拒绝。请允许屏幕录制。"
|
"permissionDenied": "录屏权限被拒绝。请允许屏幕录制。",
|
||||||
|
"accessibilityAllowAndRetry": "允许 OpenScreen 使用辅助功能权限,然后再次按录制以开始倒计时。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"cameraDenied": "攝影機權限被拒絕。錄製將繼續,但不包含攝影機畫面。",
|
"cameraDenied": "攝影機權限被拒絕。錄製將繼續,但不包含攝影機畫面。",
|
||||||
"permissionDenied": "錄影權限被拒絕。請允許螢幕錄製。",
|
"permissionDenied": "錄影權限被拒絕。請允許螢幕錄製。",
|
||||||
"cameraDisconnected": "網路攝影機已中斷連線。",
|
"cameraDisconnected": "網路攝影機已中斷連線。",
|
||||||
"cameraNotFound": "找不到攝影機。"
|
"cameraNotFound": "找不到攝影機。",
|
||||||
|
"accessibilityAllowAndRetry": "允許 OpenScreen 使用輔助使用權限,然後再次按下錄製以開始倒數。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user