From ec69b4c94081d4d8d51debd3ba4d31a5d4774cae Mon Sep 17 00:00:00 2001 From: Juliet Date: Mon, 13 Apr 2026 07:37:19 +0200 Subject: [PATCH] fix record format detection --- bun.lock | 8 ++++---- web/bun.lock | 4 ++-- web/src/pages/Upload.tsx | 34 +++++++++++++++++++--------------- web/src/pages/View.tsx | 26 +++++++++++++++++++++++++- 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/bun.lock b/bun.lock index 4849c71..871b1f8 100644 --- a/bun.lock +++ b/bun.lock @@ -5,14 +5,14 @@ "": { "name": "drop", "dependencies": { - "hono": "latest", + "hono": "^4.12.12", }, "devDependencies": { - "@types/bun": "latest", - "oxfmt": "latest", + "@types/bun": "^1.3.12", + "oxfmt": "^0.44.0", }, "peerDependencies": { - "typescript": "latest", + "typescript": "^6.0.2", }, }, }, diff --git a/web/bun.lock b/web/bun.lock index 9b53254..cc1b14d 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -11,8 +11,8 @@ "@tailwindcss/vite": "^4.2.2", "tailwindcss": "^4.2.2", "typescript": "^6.0.2", - "vite": "^8.0.2", - "vite-plugin-solid": "^2.11.11", + "vite": "^8.0.8", + "vite-plugin-solid": "^2.11.12", }, }, }, diff --git a/web/src/pages/Upload.tsx b/web/src/pages/Upload.tsx index c45bddc..69ee1ca 100644 --- a/web/src/pages/Upload.tsx +++ b/web/src/pages/Upload.tsx @@ -23,19 +23,21 @@ function parseDuration(s: string): number | undefined { type Status = "idle" | "encrypting" | "uploading"; type View = "result" | "uploading" | "file" | "empty" | "recording"; -const REC_MIMES: { mime: string; ext: string }[] = [ - { mime: "audio/webm;codecs=opus", ext: "webm" }, - { mime: "audio/webm", ext: "webm" }, - { mime: "audio/mp4", ext: "mp4" }, - { mime: "audio/ogg;codecs=opus", ext: "ogg" }, -]; -function pickRecMime() { +const REC_MIMES = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4", "audio/ogg;codecs=opus"]; +function pickRecMime(): string | null { const MR = (window as any).MediaRecorder; if (!MR) return null; for (const m of REC_MIMES) { - if (MR.isTypeSupported?.(m.mime)) return m; + if (MR.isTypeSupported?.(m)) return m; } - return { mime: "", ext: "webm" }; + return ""; +} +function extForAudio(mimeType: string): string { + const base = mimeType.split(";")[0].toLowerCase(); + if (base.includes("mp4") || base.includes("aac") || base.includes("mpeg")) return "m4a"; + if (base.includes("ogg")) return "ogg"; + if (base.includes("wav")) return "wav"; + return "webm"; } function formatTime(s: number) { @@ -159,8 +161,8 @@ export default function Upload() { }; const startRecording = async () => { - const picked = pickRecMime(); - if (!picked) { + const pickedMime = pickRecMime(); + if (pickedMime === null) { setError("recording not supported in this browser"); return; } @@ -198,16 +200,18 @@ export default function Upload() { } catch {} const chunks: BlobPart[] = []; - const mr = new MediaRecorder(mediaStream, picked.mime ? { mimeType: picked.mime } : undefined); + const mr = new MediaRecorder(mediaStream, pickedMime ? { mimeType: pickedMime } : undefined); mediaRecorder = mr; mr.ondataavailable = (e) => { if (e.data.size > 0) chunks.push(e.data); }; mr.onstop = () => { - const type = picked.mime.split(";")[0] || "audio/webm"; - const blob = new Blob(chunks, { type }); + const actualMime = mr.mimeType || pickedMime || "audio/webm"; + const baseType = actualMime.split(";")[0] || "audio/webm"; + const ext = extForAudio(actualMime); + const blob = new Blob(chunks, { type: baseType }); const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19); - const f = new File([blob], `recording-${ts}.${picked.ext}`, { type }); + const f = new File([blob], `recording-${ts}.${ext}`, { type: baseType }); setFile(f); stopRecStream(); setRecording(false); diff --git a/web/src/pages/View.tsx b/web/src/pages/View.tsx index a138d52..548b8e1 100644 --- a/web/src/pages/View.tsx +++ b/web/src/pages/View.tsx @@ -18,6 +18,26 @@ import { const ghostClass = "text-muted hover:text-accent hover:border-accent border border-border bg-transparent rounded px-2 py-1 text-sm transition-colors"; +function isAudioOnlyWebm(url: string): Promise { + return new Promise((resolve) => { + const v = document.createElement("video"); + v.preload = "metadata"; + v.muted = true; + let done = false; + const finish = (result: boolean) => { + if (done) return; + done = true; + v.removeAttribute("src"); + v.load(); + resolve(result); + }; + v.onloadedmetadata = () => finish(v.videoWidth === 0 && v.videoHeight === 0); + v.onerror = () => finish(false); + setTimeout(() => finish(false), 2000); + v.src = url; + }); +} + type Stage = "loading" | "meta" | "decrypting" | "content" | "error"; type ContentType = "text" | "image" | "video" | "audio" | "binary"; @@ -146,7 +166,11 @@ export default function View() { setContentType("image"); } else if (VIDEO_EXTS.has(ext)) { setMediaSrc(url); - setContentType("video"); + if (ext === "webm" && (await isAudioOnlyWebm(url))) { + setContentType("audio"); + } else { + setContentType("video"); + } } else { setMediaSrc(url); setContentType("audio"); -- 2.51.2