開源項目名:SimpleCameraJS
ruikocon/SimpleCameraJS (github.com)
使用RecordRTC.js配合原生JS實現的攝像頭代碼,實現了簡單的獲取/顯示分辨率,顯示圖像,錄像並保存到本地mp4,截圖並保存到本地jpg的功能。
RecordRTC是 WebRTC-Experiment 的一部分,用於實現攝像頭相關功能,這是RecordRTC的 官網 和 Github
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>SimpleCameraJS</title>
<style>
/* 簡單設置一下按鈕風格 */
button {
width: 120px;
height: 45px;
font-size: 15px;
border-radius: 2em;
transition: all 0.1s linear;
box-shadow: 2px 2px 4px #c5c5c5, -2px -2px 4px #f3f3f3;
border: 1px solid #e0e0e0;
}
button:not(:disabled):hover {
box-shadow: 6px 6px 12px #c5c5c5, -6px -6px 12px #f3f3f3;
}
button:not(:disabled):active {
transform: scale(0.95);
box-shadow: none;
}
/* 以下css爲攝像頭顯示視頻界面周圍的邊框,仿造攝像機,更加美觀,同時在四角顯示分辨率,幀數和錄像時間等信息 */
/* 參考代碼 https://blog.cubieserver.de/2020/how-to-create-a-video-camera-like-overlay-with-html-css/ */
.overlay {
--border-style: 2px solid white;
--border-space: 20px;
position: absolute;
width: 1280px;
height: 720px;
}
.overlay-helper {
position: absolute;
width: 100%;
height: 100%;
}
.overlay-element {
padding: 20px;
width: 150px;
height: 100px;
position: absolute;
}
.overlay-text {
font-size: 18px;
color: white;
text-shadow: 1px 1px 0 rgb(58, 58, 58), -1px 1px 0 rgb(58, 58, 58),
1px -1px 0 rgb(58, 58, 58), -1px -1px 0 rgb(58, 58, 58);
}
.overlay .top-left {
border-left: var(--border-style);
border-top: var(--border-style);
top: var(--border-space);
left: var(--border-space);
text-align: left;
}
.overlay .top-right {
border-right: var(--border-style);
border-top: var(--border-style);
top: var(--border-space);
right: var(--border-space);
text-align: right;
}
.overlay .bottom-left {
border-left: var(--border-style);
border-bottom: var(--border-style);
bottom: var(--border-space);
left: var(--border-space);
text-align: left;
}
.overlay .bottom-right {
border-right: var(--border-style);
border-bottom: var(--border-style);
bottom: var(--border-space);
right: var(--border-space);
text-align: right;
}
#overlay-bottom-left-text {
position: absolute;
bottom: var(--border-space);
left: var(--border-space);
}
#overlay-bottom-right-text {
position: absolute;
bottom: var(--border-space);
right: var(--border-space);
}
.blink {
animation: blink 0.5s linear infinite alternate;
}
.fade {
animation: fade 2s;
animation-timing-function: ease-in-out;
animation-fill-mode: forwards;
}
@keyframes fade {
to {
opacity: 0;
}
}
@keyframes blink {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
</style>
</head>
<body>
<div style="display: flex; width: 600px; justify-content: space-around">
<button id="open" onclick="open_camera()">開啓攝像頭</button>
<button id="record" onclick="record_act()" disabled="true">
開始錄像
</button>
<!-- <button id="stop" onclick="record_stop()">停止錄像</button> -->
<button id="photo" onclick="take_photo()" disabled="true">截圖</button>
<!-- <button id="close" onclick="close_camera()">關閉攝像頭</button> -->
</div>
<br />
<div id="content">
<div class="overlay">
<div class="overlay-helper">
<div class="overlay-element top-left">
<span
id="overlay-top-left-text"
class="overlay-text blink"
style="display: none"
>
🔴 REC
</span>
</div>
<div class="overlay-element top-right">
<span
id="overlay-top-right-text"
class="overlay-text"
style="display: none"
>
00:00
</span>
</div>
<div class="overlay-element bottom-left">
<span
id="overlay-bottom-left-text"
class="overlay-text"
style="display: none"
>
1 FPS
</span>
</div>
<div class="overlay-element bottom-right">
<span
id="overlay-bottom-right-text"
class="overlay-text"
style="display: none"
>
1x1
</span>
</div>
</div>
<!-- 這裏可以放一個logo 然後實現漸隱效果 -->
<div
style="
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
"
id="div_logo"
>
<img src="logo.png" height="140px" draggable="false" alt="LOGO" />
</div>
</div>
</div>
<video
id="video"
width="1280"
height="720"
autoplay
style="border: 2px solid #c5c5c5"
></video>
<!-- <script src="js/jquery-3.7.0.min.js"></script> -->
<script src="RecordRTC.js"></script>
<script>
"use strict";
var scale = 1;
var recorder;
var video = document.getElementById("video");
var video_width = 1280;
var video_height = 720;
var video_fps = 60;
//var img = document.getElementById("img");
var timer = null;
var start_time;
var now_time;
//默認編碼
let mimeType = "video/webm\;codecs=h265"; // H26四,把5改成4 因爲有審覈發不出來
let recorderType = MediaStreamRecorder;
//加入對支持的編碼格式進行測試的代碼
isMimeTypeSupported("video/webm\;codecs=h265"); //H26四,把5改成4 因爲有審覈發不出來
isMimeTypeSupported("video/x-matroska;codecs=avc1"); //MKV
isMimeTypeSupported("video/webm\;codecs=vp9"); //VP9
isMimeTypeSupported("video/webm\;codecs=vp8"); //VP8
isMimeTypeSupported("video/mpeg"); // video/mp4;codecs=avc1
isMimeTypeSupported("video/mp4;codecs=avc1"); // video/mp4;codecs=avc1
isMimeTypeSupported("video/webm"); // do NOT pass any codecs (vp8 by default)
//isMimeTypeSupported("video/webm\;codecs=vp10"); // JUST TEST
//自行切換編碼格式
if (isMimeTypeSupported(mimeType) === false) {
mimeType = "video/x-matroska;codecs=avc1"; // MKV
if (isMimeTypeSupported(mimeType) === false) {
mimeType = "video/webm\;codecs=vp9"; // VP9
if (isMimeTypeSupported(mimeType) === false) {
mimeType = "video/webm\;codecs=vp8"; // VP8
if (isMimeTypeSupported(mimeType) === false) {
mimeType = "video/webm"; // 不使用任何編碼器 (默認vp8)
if (isMimeTypeSupported(mimeType) === false) {
//切換到Whammy編碼器 (WebP+WebM)
mimeType = "video/webm";
recorderType = WhammyRecorder;
}
}
}
}
}
function isMimeTypeSupported(mimeType) {
if (typeof MediaRecorder === "undefined") {
console.log(mimeType, "is *** NOT *** supported.");
return false;
}
if (typeof MediaRecorder.isTypeSupported !== "function") {
console.log(mimeType, "is supported.");
return true;
}
var ret = MediaRecorder.isTypeSupported(mimeType);
if (ret === false) {
console.log(mimeType, "is *** NOT *** supported.");
} else {
console.log(mimeType, "is supported.");
}
return ret;
}
//開啓攝像頭之後顯示底部信息
function show_bottom() {
document.getElementById("overlay-bottom-left-text").style.display = "";
document.getElementById("overlay-bottom-right-text").style.display = "";
document.getElementById("div_logo").classList.add("fade");
document.getElementById(
"overlay-bottom-right-text"
).innerText = `${video_width} x ${video_height}`;
document.getElementById(
"overlay-bottom-left-text"
).innerText = `${video_fps} FPS`;
}
//開始爲錄像時間計時
function start_count() {
document.getElementById("overlay-top-left-text").style.display = "";
document.getElementById("overlay-top-right-text").style.display = "";
document
.querySelector(".overlay")
.style.setProperty("--border-style", "2px solid red");
//計時器並不準確,所以使用內置時鐘的當前時間減去之前時間的差值來表示錄像時間
let dt = new Date();
start_time = dt.getTime();
timer = setInterval(function () {
let d = new Date();
let now_time = d.getTime();
let diff_time = now_time - start_time;
let mini_sec = Math.floor((diff_time % 1000) / 10)
.toString()
.padStart(2, "0"); //秒數後面兩位
let sec = Math.floor((diff_time % 60000) / 1000)
.toString()
.padStart(2, "0"); //秒數
let minute = Math.floor((diff_time % 360000) / 60000)
.toString()
.padStart(2, "0"); //分鐘數
document.getElementById(
"overlay-top-right-text"
).innerText = `${minute}:${sec}:${mini_sec}`;
}, 10);
}
//停止計時
function stop_count() {
document.getElementById("overlay-top-left-text").style.display = "none";
document.getElementById("overlay-top-right-text").style.display =
"none";
document
.querySelector(".overlay")
.style.setProperty("--border-style", "2px solid white");
clearInterval(timer);
}
async function open_camera() {
//錄像功能使用RecordRTC https://github.com/muaz-khan/RecordRTC
//官網 https://recordrtc.org/
//RecordRTC是WebRTC-Experiment的一部分 https://github.com/muaz-khan/WebRTC-Experiment/tree/master
//獲取分辨率部分代碼參考 https://usefulangle.com/post/355/javascript-get-camera-resolution
//沒有直接的方法獲取相機支持的最大分辨率。所以只能指定一個最大的理想值,如果可用則設爲理想值
//如果理想值不可用,則瀏覽器將使用最接近理想值的值。
//WebRTC獲取相機分辨率的一個實例代碼 https://webrtc.github.io/samples/src/content/getusermedia/resolution/ *****值得參考!!!****
navigator.mediaDevices
.getUserMedia({
audio: true,
video: {
width: { ideal: 3840 },
height: { ideal: 2160 },
frameRate: { ideal: 60 },
},
})
.then(async function (stream) {
let settings = stream.getVideoTracks()[0].getSettings();
video_width = settings.width;
video_height = settings.height;
video_fps = settings.frameRate;
//console.log("視頻實際寬度: " + width + "px");
//console.log("視頻實際高度: " + height + "px");
//console.log("視頻實際FPS: " + video_fps);
//設置video 寬度 高度 和 視頻流
video.width = video_width;
video.height = video_height;
video.srcObject = stream;
recorder = RecordRTC(stream, {
type: "video",
mimeType: "video/mp4",
recorderType: recorderType,
});
show_bottom();
//禁用open按鈕,啓用錄像和截圖按鈕
document.getElementById("record").disabled = false;
document.getElementById("photo").disabled = false;
document.getElementById("open").disabled = true;
})
.catch(function (e) {
console.log(e);
alert("分辨率無效,請重新選擇並開啓攝像頭");
});
}
function close_camera() {
video.srcObject = null;
}
function record_act() {
console.log(recorder.state);
if (recorder.state == "inactive" || recorder.state == "stopped") {
document.getElementById("record").innerHTML = "停止錄像";
record_start();
} else if (recorder.state == "recording") {
record_stop();
document.getElementById("record").innerHTML = "開始錄像";
}
}
function record_start() {
recorder.startRecording();
start_count();
}
//停止錄像按鈕事件
function record_stop() {
recorder.stopRecording(function () {
let blob = recorder.getBlob();
stop_count();
//保存mp4,有個問題,現在保存的mp4只能在瀏覽器裏打開,有空再研究
invokeSaveAsDialog(blob, "video.mp4");
});
}
function take_photo() {
var canvas = document.createElement("canvas");
canvas.width = video.videoWidth * scale;
canvas.height = video.videoHeight * scale;
canvas
.getContext("2d")
.drawImage(video, 0, 0, canvas.width, canvas.height);
//將圖片保存到本地
downloadBaseFile(canvas.toDataURL("image/jpeg"), "img.jpg");
}
//下載圖片到本地
function downloadBaseFile(contentBase, fileName) {
const linkSource = contentBase;
const downloadLink = document.createElement("a");
document.body.appendChild(downloadLink);
downloadLink.href = linkSource;
downloadLink.target = "_self";
downloadLink.download = fileName;
downloadLink.click();
downloadLink.remove();
}
</script>
</body>
</html>