支持的特性
- 插入摄像头设备后,无需手动选择,自动显示摄像头画面,需要预先授权
- 支持多个摄像头切换显示
- 多个摄像头时支持 默认显示特定名称的摄像头
- 支持拍照
- 支持照片放大,缩小
显示效果
完整代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>摄像头实时检测,拍照</title>
<style type="text/css">
body {
text-align: center;
}
.flex {
display: flex;
flex-direction: row;
/* align-items: center; */
justify-content: center;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<h1>摄像头实时检测</h1>
<div class="flex">
<div>
<video id="videoElement" autoplay playsinline controls></video>
<p>
<label for="videoSource">选择摄像头:</label>
<select id="videoSource"></select>
<span class="hidden" id="closeCamera" onclick="stopVideoStream()">关闭摄像头</span>
<span id="cameraStatus">摄像头状态:未连接或已被占用</span>
<button id="captureButton" onclick="takePicture()">拍照</button>
</p>
</div>
<div style="margin-left: 20px">
<div style="border: 1px solid #ddd; box-sizing: content-box">
<canvas id="paperCanvas" width="640px" height="480px"></canvas>
</div>
</div>
</div>
<script src="./paper-full.min.js"></script>
<script>
const video = document.getElementById("videoElement");
const cameraStatus = document.getElementById("cameraStatus");
const captureButton = document.getElementById("captureButton");
const videoSelect = document.getElementById("videoSource");
let mediaStream;
let raster;
function gotDevices(deviceInfos) {
videoSelect.value = "";
videoSelect.innerHTML = "";
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
const option = document.createElement("option");
option.value = deviceInfo.deviceId;
if (deviceInfo.kind === "videoinput") {
option.text = deviceInfo.label || "摄像头 " + (videoSelect.length + 1);
videoSelect.appendChild(option);
if (deviceInfo.label.includes("TOOCAA")) {
videoSelect.value = deviceInfo.deviceId;
}
}
}
}
function getStream() {
if (window.stream) {
window.stream.getTracks().forEach((track) => {
track.stop();
});
}
const constraints = {
video: {
deviceId: { exact: videoSelect.value },
},
};
navigator.mediaDevices.getUserMedia(constraints).then(gotStream).catch(handleError);
}
function gotStream(stream) {
window.stream = stream;
video.srcObject = stream;
cameraStatus.textContent = "摄像头状态:已连接";
const videoTrack = stream.getVideoTracks()[0];
const settings = videoTrack.getSettings();
captureButton.style.display = "inline-block";
console.log(settings);
}
function startVideoStream() {
if (navigator.mediaDevices) {
navigator.mediaDevices
.getUserMedia({ video: true })
.then(function (stream) {
mediaStream = stream; // 保存媒体流以便后续操作
video.srcObject = stream;
cameraStatus.textContent = "摄像头状态:已连接";
const videoTrack = stream.getVideoTracks()[0];
const settings = videoTrack.getSettings();
captureButton.style.display = "inline-block";
console.log(settings);
})
.catch(function (error) {
console.error("无法获取摄像头:", error);
cameraStatus.textContent = "摄像头状态:未连接或已被占用";
captureButton.style.display = "none";
});
}
}
function stopVideoStream() {
if (mediaStream) {
mediaStream.getTracks().forEach((track) => track.stop()); // 停止所有媒体轨道
}
video.srcObject = null; // 清除视频源
cameraStatus.textContent = "摄像头状态:已断开";
captureButton.style.display = "none";
}
function initPaperCanvas() {
paper.setup("paperCanvas");
paper.view.element.addEventListener("wheel", function (event) {
event.preventDefault();
// 计算缩放因子
var delta = event.deltaY > 0 ? 0.9 : 1.1; // 向下滚动缩小视图,向上滚动放大视图
// 鼠标位置相对于视图的当前坐标
var mousePosition = new paper.Point(event.offsetX, event.offsetY);
var viewPosition = paper.view.viewToProject(mousePosition);
// 应用缩放
paper.view.scale(delta, viewPosition);
});
// 鼠标拖动事件处理移动
const tool = new paper.Tool();
var lastPoint = null; // 上一次鼠标位置
var dragging = false;
var lastViewCenter;
tool.onMouseDown = (event) => {
lastPoint = event.point;
dragging = true;
};
tool.onMouseDrag = (event) => {
if (dragging && lastPoint) {
lastViewCenter = paper.view.center;
const delta = lastPoint.subtract(event.point);
paper.view.center = paper.view.center.add(delta);
lastPoint = event.point.add(paper.view.center.subtract(lastViewCenter));
}
};
tool.onMouseUp = () => {
// 结束拖动
dragging = false;
};
}
window.onload = () => {
if (navigator.mediaDevices) {
navigator.mediaDevices.enumerateDevices().then(gotDevices).then(getStream).catch(handleError);
}
videoSelect.onchange = getStream;
// 监听媒体设备变化事件
if (navigator.mediaDevices) {
navigator.mediaDevices.addEventListener("devicechange", function (event) {
// 尝试重新获取媒体流以检查摄像头是否仍然可用
navigator.mediaDevices
.getUserMedia({ video: true })
.then(function () {
navigator.mediaDevices.enumerateDevices().then(gotDevices).then(getStream).catch(handleError);
// startVideoStream(); // 摄像头已连接,重新开始视频流
})
.catch(function () {
stopVideoStream(); // 摄像头已断开,停止视频流并更新状态
// destoryCanvas();
});
});
}
initPaperCanvas();
};
// 拍照功能
function takePicture() {
let canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const context = canvas.getContext("2d");
context.drawImage(video, 0, 0, canvas.width, canvas.height);
displayPictureOnPaper(canvas.toDataURL("image/png"));
canvas = null;
}
function displayPictureOnPaper(imageData) {
raster = new paper.Raster({
source: imageData,
position: paper.view.center,
});
}
function destoryCanvas() {
if (raster) {
raster.remove();
}
}
function handleError(error) {
console.error("无法获取摄像头:", error);
cameraStatus.textContent = "摄像头状态:未连接或已被占用";
captureButton.style.display = "none";
}
</script>
</body>
</html>