H5 实时摄像头 + 麦克风:完整可运行 Demo 与深度拆解

为什么写这篇

网上关于 WebRTC 的教程大多只讲「摄像头预览」或「纯录音」,很少把「实时画面 + 实时麦克风」同时跑通,还附送「抓拍 + 录制 + 下载」。今天我把踩坑后的最简可运行代码整理成一篇,5 分钟带你复现。


一、技术要点拆解

功能 关键 API 注意点
获取音视频流 navigator.mediaDevices.getUserMedia(constraints) 必须在 HTTPS 或 localhost;首次需用户授权
实时预览 <video autoplay playsinline> + video.srcObject = stream 移动端必须加 playsinline,否则全屏
麦克风耳返 <video muted> 去掉即可 Chrome 默认回声消除,不会啸叫
拍照 canvas.drawImage(video, 0, 0) + canvas.toDataURL() 生成 JPEG/PNG 直接展示
录制 MediaRecorder 兼容性写法:先检查 mimeType
下载 URL.createObjectURL(blob) + <a download> 记得 revokeObjectURL 避免内存泄漏

二、代码架构

ini 复制代码
index.html
├── <video id="preview">      // 实时预览
├── <button id="snapBtn">     // 抓拍
├── <canvas id="canvas">      // 抓拍中转
├── <img id="photo">          // 抓拍结果
├── <button id="recBtn">      // 开始录制
├── <button id="stopBtn">     // 停止录制
├── <video id="recorded">     // 回放
└── <a id="downLink">         // 下载

三、完整代码(复制即跑)

⚠️ 保存为 index.html,通过 HTTPS 或 localhost 打开!

ini 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>H5 摄像头/麦克风快速测试</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
  body{margin:0;font-family:Arial;background:#111;color:#fff}
  video{width:100%;max-width:480px;background:#000}
  button{margin:6px;padding:8px 14px;font-size:15px}
  .box{max-width:480px;margin:auto;text-align:center}
</style>
</head>
<body>
<div class="box">
  <h3>摄像头 / 麦克风测试</h3>

  <!-- 实时预览 -->
  <video id="preview" autoplay playsinline muted></video>

  <!-- 抓拍 -->
  <br>
  <button id="snapBtn">抓拍照片</button>
  <canvas id="canvas" style="display:none"></canvas>
  <img id="photo" style="max-width:100%;margin-top:6px;display:none">

  <!-- 录制 -->
  <br>
  <button id="recBtn">开始录制</button>
  <button id="stopBtn" disabled>停止录制</button>
  <br>
  <video id="recorded" controls style="margin-top:6px;display:none"></video>
  <br>
  <a id="downLink" style="display:none;color:#0f0">下载</a>
</div>

<script>
/************ 1. 实时预览 ************/
const preview = document.getElementById('preview');
const constraints = { video: { facingMode: 'user' }, audio: true };
navigator.mediaDevices.getUserMedia(constraints)
  .then(stream => {
    preview.srcObject = stream;
    window.stream = stream; // 后面录制复用
  })
  .catch(err => alert('获取摄像头/麦克风失败:' + err));

/************ 2. 抓拍 ************/
document.getElementById('snapBtn').addEventListener('click', () => {
  const canvas = document.getElementById('canvas');
  const video  = preview;
  canvas.width = video.videoWidth;
  canvas.height= video.videoHeight;
  canvas.getContext('2d').drawImage(video, 0, 0);
  const dataURL = canvas.toDataURL('image/jpeg');
  const photo = document.getElementById('photo');
  photo.src = dataURL;
  photo.style.display = 'inline-block';
});

/************ 3. 录制 ************/
let mediaRecorder;
let recordedBlobs = [];
const recBtn  = document.getElementById('recBtn');
const stopBtn = document.getElementById('stopBtn');
const recorded = document.getElementById('recorded');
const downLink = document.getElementById('downLink');

recBtn.onclick = () => {
  recorded.style.display = 'none';
  downLink.style.display = 'none';
  recordedBlobs = [];
  const options = { mimeType: 'video/webm;codecs=vp9,opus' };
  if (!MediaRecorder.isTypeSupported(options.mimeType)) {
    alert('浏览器不支持 webm/opus,将尝试默认格式');
    options.mimeType = undefined;
  }
  mediaRecorder = new MediaRecorder(window.stream, options);
  mediaRecorder.ondataavailable = e => {
    if (e.data && e.data.size > 0) recordedBlobs.push(e.data);
  };
  mediaRecorder.onstop = () => {
    const blob = new Blob(recordedBlobs, { type: 'video/webm' });
    recorded.src = URL.createObjectURL(blob);
    recorded.style.display = 'inline-block';
    downLink.href = recorded.src;
    downLink.download = 'test.webm';
    downLink.style.display = 'inline-block';
  };
  mediaRecorder.start();
  recBtn.disabled = true;
  stopBtn.disabled = false;
};

stopBtn.onclick = () => {
  mediaRecorder.stop();
  recBtn.disabled = false;
  stopBtn.disabled = true;
};
</script>
</body>
</html>

四、常见问题 FAQ

  1. 本地双击无法调用设备?
    必须使用 HTTPS 或 localhost。最简单的本地服务器:npx serve .
  2. iOS 不能播放?
    确保 <video>playsinline 属性,且 iOS 14+ 才支持 MediaRecorder
  3. 下载的文件没有声音?
    检查 constraints 里是否加了 audio: true;录制时 mimeType 要包含 opus

五、下一步可以玩什么?

  • getUserMedia 换成 getDisplayMedia 做屏幕录制
  • 接入 WebRTC 做 1 对 1 视频通话
  • 用 Canvas 2D + WebGL 做美颜、滤镜
相关推荐
Nan_Shu_61413 小时前
学习: Threejs (2)
前端·javascript·学习
G_G#13 小时前
纯前端js插件实现同一浏览器控制只允许打开一个标签,处理session变更问题
前端·javascript·浏览器标签页通信·只允许一个标签页
@大迁世界13 小时前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路14 小时前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug14 小时前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213814 小时前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中14 小时前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路14 小时前
GDAL 实现矢量合并
前端
hxjhnct14 小时前
React useContext的缺陷
前端·react.js·前端框架
冰暮流星14 小时前
javascript逻辑运算符
开发语言·javascript·ecmascript