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 做美颜、滤镜
相关推荐
烛阴1 小时前
掌握 TypeScript 的边界:any, unknown, void, never 的正确用法与陷阱
前端·javascript·typescript
Jerry1 小时前
迁移到 Jetpack Compose
前端
FFF-X2 小时前
前端无感刷新 Token 的 Axios 封装方案
前端
qq_589568102 小时前
javaweb开发笔记—— 前端工程化
java·前端
gnip2 小时前
包管理工具的发展
前端
韩沛晓3 小时前
uniapp跨域怎么解决
前端·javascript·uni-app
前端工作日常3 小时前
以 Vue 项目为例串联eslint整个流程
前端·eslint
程序员鱼皮3 小时前
太香了!我连夜给项目加上了这套 Java 监控系统
java·前端·程序员
Rubin933 小时前
TS 相关
javascript