v0.2.0 working

This commit is contained in:
2026-03-30 23:11:57 +02:00
parent d3197b9603
commit 65d8d8d3f6
15 changed files with 403 additions and 77 deletions

View File

@@ -1,4 +1,9 @@
import { invoke } from "@tauri-apps/api/core";
import { AvStartResponse } from './ts/bindings/AvStartResponse';
import { AvStopResponse } from './ts/bindings/AvStopResponse';
type Mode = "idle" | "audio" | "video" | "av";
const startAudioBtn = document.querySelector<HTMLButtonElement>("#start-audio-btn");
const stopAudioBtn = document.querySelector<HTMLButtonElement>("#stop-audio-btn");
@@ -7,7 +12,12 @@ const audioStatus = document.querySelector<HTMLElement>("#audio-status");
const startVideoBtn = document.querySelector<HTMLButtonElement>("#start-video-btn");
const stopVideoBtn = document.querySelector<HTMLButtonElement>("#stop-video-btn");
const videoStatus = document.querySelector<HTMLElement>("#video-status");
const videoPreview = document.querySelector<HTMLImageElement>("#video-preview");
const startAvBtn = document.querySelector<HTMLButtonElement>("#start-av-btn");
const stopAvBtn = document.querySelector<HTMLButtonElement>("#stop-av-btn");
const avStatus = document.querySelector<HTMLElement>("#av-status");
const previewElement = document.querySelector<HTMLImageElement>("#video-preview");
if (
startAudioBtn === null ||
@@ -16,19 +26,34 @@ if (
startVideoBtn === null ||
stopVideoBtn === null ||
videoStatus === null ||
videoPreview === null
startAvBtn === null ||
stopAvBtn === null ||
avStatus === null ||
previewElement === null
) {
throw new Error("missing UI elements");
}
let currentMode: Mode = "idle";
let previewTimer: number | null = null;
let previewRequestInFlight = false;
let previewObjectUrl: string | null = null;
function setAudioStatus(message: string): void {
if (audioStatus)
audioStatus.textContent = message;
}
function setVideoStatus(message: string): void {
if (videoStatus)
videoStatus.textContent = message;
}
function setAvStatus(message: string): void {
if (avStatus)
avStatus.textContent = message;
}
function setAudioButtons(isRecording: boolean): void {
if (startAudioBtn)
startAudioBtn.disabled = isRecording;
@@ -36,11 +61,6 @@ function setAudioButtons(isRecording: boolean): void {
stopAudioBtn.disabled = !isRecording;
}
function setVideoStatus(message: string): void {
if (videoStatus)
videoStatus.textContent = message;
}
function setVideoButtons(isRecording: boolean): void {
if (startVideoBtn)
startVideoBtn.disabled = isRecording;
@@ -48,11 +68,92 @@ function setVideoButtons(isRecording: boolean): void {
stopVideoBtn.disabled = !isRecording;
}
function setAvButtons(isRecording: boolean): void {
if (startAvBtn)
startAvBtn.disabled = isRecording;
if (stopAvBtn)
stopAvBtn.disabled = !isRecording;
}
function setAllButtonsForMode(mode: Mode): void {
if (mode === "idle") {
setAudioButtons(false);
setVideoButtons(false);
setAvButtons(false);
return;
}
setAudioButtons(mode === "audio");
setVideoButtons(mode === "video");
setAvButtons(mode === "av");
if (mode !== "audio") {
if (stopAudioBtn)
stopAudioBtn.disabled = true;
if (startAudioBtn)
startAudioBtn.disabled = true;
}
if (mode !== "video") {
if (stopVideoBtn)
stopVideoBtn.disabled = true;
if (startVideoBtn)
startVideoBtn.disabled = true;
}
if (mode !== "av") {
if (stopAvBtn)
stopAvBtn.disabled = true;
if (startAvBtn)
startAvBtn.disabled = true;
}
}
function setCurrentMode(mode: Mode): void {
currentMode = mode;
setAllButtonsForMode(mode);
}
function base64ToUint8Array(base64: string): Uint8Array {
const binary = window.atob(base64);
const bytes = new Uint8Array(binary.length);
let index = 0;
while (index < binary.length) {
bytes[index] = binary.charCodeAt(index);
index += 1;
}
return bytes;
}
function updatePreviewImageFromBase64(encoded: string): void {
const bytes = base64ToUint8Array(encoded);
const copied = new Uint8Array(bytes.byteLength);
copied.set(bytes);
const blob = new Blob([copied.buffer], { type: "image/jpeg" });
const objectUrl = URL.createObjectURL(blob);
if (previewObjectUrl !== null) {
URL.revokeObjectURL(previewObjectUrl);
}
previewObjectUrl = objectUrl;
if (previewElement)
previewElement.src = objectUrl;
}
async function refreshPreviewFrame(): Promise<void> {
if (previewRequestInFlight) {
return;
}
if (currentMode !== "video" && currentMode !== "av") {
return;
}
previewRequestInFlight = true;
try {
@@ -73,8 +174,6 @@ function startPreviewPolling(): void {
return;
}
console.log("starting preview polling");
window.setTimeout(() => {
void refreshPreviewFrame();
}, 120);
@@ -91,97 +190,133 @@ function stopPreviewPolling(): void {
}
previewRequestInFlight = false;
if (videoPreview)
videoPreview.removeAttribute("src");
if (previewObjectUrl !== null) {
URL.revokeObjectURL(previewObjectUrl);
previewObjectUrl = null;
}
if (previewElement)
previewElement.removeAttribute("src");
}
startAudioBtn.addEventListener("click", async () => {
startAudioBtn.disabled = true;
if (currentMode !== "idle") {
setAudioStatus("Another mode is already running.");
return;
}
setCurrentMode("audio");
try {
const path = await invoke<string>("start_audio_recording");
setAudioStatus(`Audio recording started.\nOutput: ${path}`);
setAudioButtons(true);
} catch (error) {
setAudioStatus(`Start audio failed.\n${String(error)}`);
setAudioButtons(false);
setCurrentMode("idle");
}
});
stopAudioBtn.addEventListener("click", async () => {
if (currentMode !== "audio") {
setAudioStatus("Audio mode is not running.");
return;
}
stopAudioBtn.disabled = true;
try {
const path = await invoke<string>("stop_audio_recording");
setAudioStatus(`Audio recording stopped.\nSaved file: ${path}`);
setAudioButtons(false);
setCurrentMode("idle");
} catch (error) {
setAudioStatus(`Stop audio failed.\n${String(error)}`);
setAudioButtons(true);
setCurrentMode("audio");
}
});
startVideoBtn.addEventListener("click", async () => {
startVideoBtn.disabled = true;
if (currentMode !== "idle") {
setVideoStatus("Another mode is already running.");
return;
}
setCurrentMode("video");
try {
const path = await invoke<string>("start_video_recording");
setVideoStatus(`Video recording started.\nOutput: ${path}`);
setVideoButtons(true);
startPreviewPolling();
} catch (error) {
setVideoStatus(`Start video failed.\n${String(error)}`);
setVideoButtons(false);
stopPreviewPolling();
setCurrentMode("idle");
}
});
stopVideoBtn.addEventListener("click", async () => {
if (currentMode !== "video") {
setVideoStatus("Video mode is not running.");
return;
}
stopVideoBtn.disabled = true;
try {
const path = await invoke<string>("stop_video_recording");
setVideoStatus(`Video recording stopped.\nSaved file: ${path}`);
setVideoButtons(false);
stopPreviewPolling();
setCurrentMode("idle");
} catch (error) {
setVideoStatus(`Stop video failed.\n${String(error)}`);
setVideoButtons(true);
setCurrentMode("video");
}
});
let previewObjectUrl: string | null = null;
function base64ToUint8Array(base64: string): Uint8Array {
const binary = window.atob(base64);
const bytes = new Uint8Array(binary.length);
for (let index = 0;index < binary.length;index += 1) {
bytes[index] = binary.charCodeAt(index);
startAvBtn.addEventListener("click", async () => {
if (currentMode !== "idle") {
setAvStatus("Another mode is already running.");
return;
}
return bytes;
}
setCurrentMode("av");
function updatePreviewImageFromBase64(encoded: string): void {
const bytes = base64ToUint8Array(encoded);
const copied = new Uint8Array(bytes.byteLength);
try {
const result = await invoke<AvStartResponse>("start_av_recording");
copied.set(bytes);
setAvStatus(
`AV recording started.\nAudio: ${result.audio_path}\nVideo: ${result.video_path}`
);
const blob = new Blob([copied.buffer], { type: "image/jpeg" });
const objectUrl = URL.createObjectURL(blob);
startPreviewPolling();
} catch (error) {
setAvStatus(`Start AV failed.\n${String(error)}`);
stopPreviewPolling();
setCurrentMode("idle");
}
});
if (previewObjectUrl !== null) {
URL.revokeObjectURL(previewObjectUrl);
stopAvBtn.addEventListener("click", async () => {
if (currentMode !== "av") {
setAvStatus("AV mode is not running.");
return;
}
previewObjectUrl = objectUrl;
if (videoPreview)
videoPreview.src = objectUrl;
}
stopAvBtn.disabled = true;
try {
const result = await invoke<AvStopResponse>("stop_av_recording");
setAvStatus(
`AV recording stopped.\nAudio: ${result.audio_path}\nVideo: ${result.video_path}`
);
stopPreviewPolling();
setCurrentMode("idle");
} catch (error) {
setAvStatus(`Stop AV failed.\n${String(error)}`);
setCurrentMode("av");
}
});
setCurrentMode("idle");