0.1.1
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
@@ -142,4 +153,35 @@ stopVideoBtn.addEventListener("click", async () => {
|
|||||||
setVideoStatus(`Stop video failed.\n${String(error)}`);
|
setVideoStatus(`Stop video failed.\n${String(error)}`);
|
||||||
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user