@mastashake08/vue-4k-camera-recorder
Version:
A Vue 3 component using MediaRecorder and MediaCapabilities API to record 4K video from the user's camera.
196 lines (195 loc) • 9.29 kB
JavaScript
import { ref as s, computed as C, onMounted as K, onUnmounted as F, createElementBlock as v, openBlock as p, createElementVNode as l, createCommentVNode as h, toDisplayString as b, createTextVNode as w, normalizeClass as V, Fragment as M } from "vue";
const A = (k, m) => {
const a = k.__vccOpts || k;
for (const [_, r] of m)
a[_] = r;
return a;
}, B = { class: "camera-recorder" }, L = {
key: 0,
class: "error"
}, j = { key: 1 }, x = { class: "status" }, z = { key: 0 }, H = { class: "controls" }, q = ["disabled"], Y = ["disabled"], G = ["disabled"], W = { class: "video-container" }, J = {
key: 0,
class: "download-section"
}, Q = ["href", "download"], I = 3840, D = 2160, O = 30, P = 50 * 1e3 * 1e3, X = {
__name: "CameraRecorder",
setup(k) {
const m = [
"video/webm;codecs=vp9",
"video/webm;codecs=av01",
// AV1 encoding support is less common
"video/mp4;codecs=h264",
// More common fallback
"video/webm;codecs=vp8"
// Older fallback
// Add more specific types if needed, e.g., with profile/level
// 'video/mp4; codecs="avc1.640028"', // H.264 High Profile Level 4.0
], a = s(null), _ = s(null), r = s(null), u = s(null), E = s([]), R = s(null), n = s(!1), d = s(!1), t = s("Idle. Check support first."), c = s(null), f = s(""), S = C(
() => "mediaDevices" in navigator && "getUserMedia" in navigator.mediaDevices && "MediaRecorder" in window && "mediaCapabilities" in navigator
), y = C(
() => {
var o;
return ((o = c.value) == null ? void 0 : o.supported) ?? !1;
}
), T = C(() => {
var i;
const o = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"), e = ((i = f.value) == null ? void 0 : i.split(";")[0].split("/")[1]) || "webm";
return `recording-4k-${o}.${e}`;
});
async function U() {
if (!S.value) {
t.value = "Browser lacks necessary API support.";
return;
}
d.value = !0, t.value = "Checking 4K recording capabilities...", c.value = null, f.value = "";
let o = !1;
for (const e of m) {
const i = {
contentType: e,
width: I,
height: D,
bitrate: P,
framerate: O
};
try {
const g = await navigator.mediaCapabilities.encodingInfo({
type: "record",
// Check for recording, 'transmission' also possible
video: i
// audio: { // Optionally check audio encoding too
// contentType: 'audio/opus', // Example
// channels: 2,
// bitrate: 128000,
// samplerate: 48000
// }
});
if (console.log(`Checking ${e}:`, g), g.supported) {
c.value = { supported: !0, config: i, powerEfficient: g.powerEfficient, smooth: g.smooth }, f.value = e, t.value = `4K recording check complete. Supported (${e}).`, o = !0;
break;
}
} catch (g) {
console.error(`Error checking capabilities for ${e}:`, g), o || (c.value = { supported: !1, reason: `${e}: ${g.message}` });
}
}
o || (t.value = "4K recording check complete. No suitable configuration found.", c.value || (c.value = { supported: !1, reason: "Failed to check any configuration." })), d.value = !1;
}
async function N() {
if (!y.value || !f.value) {
t.value = "Cannot start: 4K recording not supported or not checked.";
return;
}
if (!n.value) {
R.value = null, E.value = [], d.value = !0, t.value = "Requesting camera access...";
try {
r.value = await navigator.mediaDevices.getUserMedia({
audio: !0,
// Include audio
video: {
width: { ideal: I },
// Use 'exact' to enforce 4K
height: { ideal: D },
frameRate: { ideal: O }
// 'ideal' is less strict than 'exact'
}
}), t.value = "Camera access granted. Starting recorder...", a.value && (a.value.srcObject = r.value, a.value.captureStream = a.value.captureStream || a.value.mozCaptureStream, await a.value.play());
const o = {
mimeType: f.value,
// Use the validated mimeType
videoBitsPerSecond: P
// Specify the high bitrate
// audioBitsPerSecond: 128000 // Optional: Specify audio bitrate
};
if (!MediaRecorder.isTypeSupported(o.mimeType))
throw console.warn(`MediaRecorder does not support ${o.mimeType}, falling back or failing.`), new Error(`MediaRecorder reported unsupported mimeType: ${o.mimeType}`);
u.value = new MediaRecorder(r.value, o), u.value.ondataavailable = (e) => {
e.data && e.data.size > 0 && E.value.push(e.data);
}, u.value.onstop = () => {
t.value = "Recording stopped. Processing video...";
const e = new Blob(E.value, { type: f.value });
R.value = URL.createObjectURL(e), n.value = !1, d.value = !1, t.value = "Processing complete. Download ready.", r.value && (r.value.getTracks().forEach((i) => i.stop()), r.value = null), a.value && (a.value.srcObject = null);
}, u.value.onerror = (e) => {
console.error("MediaRecorder Error:", e.error), t.value = `Recording Error: ${e.error.name || "Unknown error"}`, n.value = !1, d.value = !1, r.value && (r.value.getTracks().forEach((i) => i.stop()), r.value = null), a.value && (a.value.srcObject = null);
}, u.value.start(), n.value = !0, d.value = !1, t.value = "Recording 4K video...";
} catch (o) {
console.error("Error starting recording:", o);
let e = "Failed to start recording.";
o.name === "NotAllowedError" ? e = "Permission denied. Please allow camera/microphone access." : o.name === "NotFoundError" ? e = "No camera/microphone found." : o.name === "NotReadableError" ? e = "Camera/microphone is already in use or hardware error." : o.name === "OverconstrainedError" ? e = `Could not satisfy constraints (e.g., 4K resolution not supported by device): ${o.constraint}` : o.message.includes("MediaRecorder reported unsupported mimeType") && (e = o.message), t.value = e, d.value = !1, n.value = !1, r.value && (r.value.getTracks().forEach((i) => i.stop()), r.value = null);
}
}
}
function $() {
!n.value || !u.value || (t.value = "Stopping recording...", u.value.stop());
}
return K(() => {
}), F(() => {
n.value && u.value && u.value.stop(), r.value && r.value.getTracks().forEach((o) => o.stop()), R.value && URL.revokeObjectURL(R.value);
}), (o, e) => (p(), v("div", B, [
e[5] || (e[5] = l("h2", null, "4K Camera Recorder", -1)),
S.value ? (p(), v("div", j, [
l("div", x, [
l("p", null, "Status: " + b(t.value), 1),
c.value !== null ? (p(), v("p", z, [
e[0] || (e[0] = w(" 4K High-Bitrate Recording Supported: ")),
l("span", {
class: V(y.value ? "supported" : "not-supported")
}, [
w(b(y.value ? "Yes" : "No") + " ", 1),
y.value && f.value ? (p(), v(M, { key: 0 }, [
w(" (Using: " + b(f.value) + ")", 1)
], 64)) : h("", !0),
!y.value && c.value ? (p(), v(M, { key: 1 }, [
w(" (Reason: " + b(c.value.reason || "Unsupported configuration") + ")", 1)
], 64)) : h("", !0)
], 2)
])) : h("", !0)
]),
l("div", H, [
l("button", {
onClick: U,
disabled: d.value || n.value
}, " Check 4K Support ", 8, q),
n.value ? h("", !0) : (p(), v("button", {
key: 0,
onClick: N,
disabled: d.value || n.value || !y.value
}, " Start 4K Recording ", 8, Y)),
n.value ? (p(), v("button", {
key: 1,
onClick: $,
disabled: !n.value
}, " Stop Recording ", 8, G)) : h("", !0)
]),
l("div", W, [
e[1] || (e[1] = l("h3", null, "Preview", -1)),
l("video", {
ref_key: "videoPreviewRef",
ref: a,
autoplay: "",
muted: "",
playsinline: ""
}, null, 512)
]),
R.value ? (p(), v("div", J, [
e[2] || (e[2] = l("h3", null, "Recording Complete", -1)),
e[3] || (e[3] = l("p", null, "Download your recording:", -1)),
l("a", {
href: R.value,
download: T.value,
ref_key: "downloadLinkRef",
ref: _
}, b(T.value), 9, Q),
e[4] || (e[4] = l("p", null, "(File size might be very large)", -1))
])) : h("", !0)
])) : (p(), v("div", L, " Required APIs (getUserMedia, MediaRecorder, MediaCapabilities) are not fully supported by your browser. "))
]));
}
}, Z = /* @__PURE__ */ A(X, [["__scopeId", "data-v-40afb732"]]), oe = {
install: (k, m) => {
const a = (m == null ? void 0 : m.componentName) || "CameraRecorder";
k.component(a, Z);
}
};
export {
Z as CameraRecorder,
oe as default
};
//# sourceMappingURL=vue-4k-camera-recorder.es.js.map