This commit is contained in:
2026-03-30 11:22:19 +02:00
parent db9201e2ed
commit d3197b9603
7 changed files with 57 additions and 7 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-video02" name = "tauri-video02"
version = "0.1.0" version = "0.1.1"
description = "A Tauri Video App" description = "A Tauri Video App"
authors = ["sinus@sasedev.net"] authors = ["sinus@sasedev.net"]
edition = "2024" edition = "2024"

View File

@@ -4,6 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="stylesheet" href="styles.css" /> <link rel="stylesheet" href="styles.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self';" />
<title>Tauri GST Record + Preview</title> <title>Tauri GST Record + Preview</title>
</head> </head>

View File

@@ -58,8 +58,8 @@ async function refreshPreviewFrame(): Promise<void> {
try { try {
const encoded = await invoke<string | null>("get_video_preview_frame_base64"); const encoded = await invoke<string | null>("get_video_preview_frame_base64");
if (encoded !== null && encoded.length > 0 && videoPreview) { if (encoded !== null && encoded.length > 0) {
videoPreview.src = `data:image/jpeg;base64,${encoded}`; updatePreviewImageFromBase64(encoded);
} }
} catch (error) { } catch (error) {
console.error("preview refresh failed", error); console.error("preview refresh failed", error);
@@ -73,6 +73,12 @@ function startPreviewPolling(): void {
return; return;
} }
console.log("starting preview polling");
window.setTimeout(() => {
void refreshPreviewFrame();
}, 120);
previewTimer = window.setInterval(() => { previewTimer = window.setInterval(() => {
void refreshPreviewFrame(); void refreshPreviewFrame();
}, 200); }, 200);
@@ -87,6 +93,11 @@ function stopPreviewPolling(): void {
previewRequestInFlight = false; previewRequestInFlight = false;
if (videoPreview) if (videoPreview)
videoPreview.removeAttribute("src"); videoPreview.removeAttribute("src");
if (previewObjectUrl !== null) {
URL.revokeObjectURL(previewObjectUrl);
previewObjectUrl = null;
}
} }
startAudioBtn.addEventListener("click", async () => { startAudioBtn.addEventListener("click", async () => {
@@ -143,3 +154,34 @@ stopVideoBtn.addEventListener("click", async () => {
setVideoButtons(true); setVideoButtons(true);
} }
}); });
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);
}
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 (videoPreview)
videoPreview.src = objectUrl;
}

View File

@@ -45,6 +45,7 @@ button:disabled {
.preview-wrap { .preview-wrap {
width: 100%; width: 100%;
aspect-ratio: 16/9; aspect-ratio: 16/9;
min-height: 240px;
background: #0b1220; background: #0b1220;
border: 1px solid #374151; border: 1px solid #374151;
border-radius: 8px; border-radius: 8px;

View File

@@ -1,7 +1,7 @@
{ {
"name": "tauri-video02", "name": "tauri-video02",
"private": true, "private": true,
"version": "0.1.0", "version": "0.1.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -52,7 +52,7 @@ fn build_pipeline(output_path: &std::path::Path) -> Result<gst::Pipeline, AppErr
"autovideosrc ! videoconvert ! tee name=t ", "autovideosrc ! videoconvert ! tee name=t ",
"t. ! queue ! vp8enc deadline=1 ! webmmux ! filesink location=\"{}\" ", "t. ! queue ! vp8enc deadline=1 ! webmmux ! filesink location=\"{}\" ",
"t. ! queue leaky=downstream max-size-buffers=1 ! videoconvert ! ", "t. ! queue leaky=downstream max-size-buffers=1 ! videoconvert ! ",
"jpegenc quality=80 ! appsink name=preview_sink emit-signals=false max-buffers=1 drop=true sync=false" "jpegenc quality=80 ! appsink name=preview_sink max-buffers=1 drop=true sync=false"
), ),
location location
); );
@@ -123,6 +123,8 @@ fn attach_preview_callbacks(
let bytes = map.as_slice().to_vec(); let bytes = map.as_slice().to_vec();
tracing::info!(size = bytes.len(), "preview sample received");
let lock_result = preview_store.lock(); let lock_result = preview_store.lock();
match lock_result { match lock_result {
Ok(mut guard) => { Ok(mut guard) => {
@@ -284,9 +286,12 @@ pub fn get_video_preview_frame_base64(
}; };
let Some(bytes) = guard.as_ref() else { let Some(bytes) = guard.as_ref() else {
tracing::info!("preview frame requested but none is available yet");
return Ok(None); return Ok(None);
}; };
tracing::info!(size = bytes.len(), "preview frame requested and returned");
let encoded = base64::engine::general_purpose::STANDARD.encode(bytes); let encoded = base64::engine::general_purpose::STANDARD.encode(bytes);
Ok(Some(encoded)) Ok(Some(encoded))
} }

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "tauri-video02", "productName": "tauri-video02",
"version": "0.1.0", "version": "0.1.1",
"identifier": "com.sinus.tauri-video02", "identifier": "com.sinus.tauri-video02",
"build": { "build": {
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",