Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 339

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 392

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 394
HTML调取摄像头拍照事例代码,扩展常见直播方案 - Joyber 的博客

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>摄像头拍照保存</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;font-family:system-ui;}
body{padding:20px;max-width:800px;margin:0 auto;}
#video{width:100%;border:1px solid #ccc;background:#000;transform:scaleX(-1);/*前置预览镜像*/}
#canvas{display:none;}
#preview{margin:15px 0;max-width:100%;border:1px solid #ccc;}
.btns{margin:15px 0;display:flex;gap:10px;flex-wrap:wrap;}
button{padding:10px 18px;font-size:16px;cursor:pointer;}
</style>
</head>
<body>
<h3>摄像头拍照</h3>
<video id="video" autoplay playsinline></video>
<canvas id="canvas"></canvas>
<div class="btns">
<button id="startBtn">开启摄像头</button>
<button id="stopBtn">关闭摄像头</button>
<button id="takeBtn">拍照</button>
<button id="saveBtn">保存图片</button>
</div>
<img id="preview" alt="预览图">

<script>
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const preview = document.getElementById('preview');
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const takeBtn = document.getElementById('takeBtn');
const saveBtn = document.getElementById('saveBtn');

let mediaStream = null;
let imgUrl = '';

// 开启摄像头
startBtn.onclick = async () => {
  try{
    mediaStream = await navigator.mediaDevices.getUserMedia({
      video:{facingMode:'user'}
    });
    video.srcObject = mediaStream;
  }catch(err){
    alert('无法打开摄像头,请授予摄像头权限:' + err.message);
    console.error(err);
  }
};

// 关闭摄像头
stopBtn.onclick = ()=>{
  if(mediaStream){
    mediaStream.getTracks().forEach(track=>track.stop());
    mediaStream = null;
    video.srcObject = null;
  }
}

// 拍照,前置画面镜像翻转
takeBtn.onclick = () => {
  if(!mediaStream){
    alert("请先开启摄像头");
    return;
  }
  if(!video.videoWidth){
    alert("视频还未就绪,请稍等");
    return;
  }
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  // 水平翻转画布,拍出来和预览一样镜像
  ctx.save();
  ctx.scale(-1, 1);
  ctx.drawImage(video, -canvas.width, 0, canvas.width, canvas.height);
  ctx.restore();
  imgUrl = canvas.toDataURL('image/png');
  preview.src = imgUrl;
};

// 保存下载图片
saveBtn.onclick = () => {
  if(!imgUrl){
    alert('请先拍照');
    return;
  }
  const a = document.createElement('a');
  a.href = imgUrl;
  a.download = `photo_${Date.now()}.png`;
  a.click();
};
</script>
</body>
</html>

摄像头视频流怎么推送到后端,做直播视频流

网页摄像头推流到后端做直播:完整技术方案

浏览器端采集摄像头,向后端推送视频流,主流分两大类:

  1. WebRTC(推荐低延迟直播,实时互动,延迟<500ms)
  2. MediaRecorder + HTTP上传(简单HLS分片直播,延迟高,5~20秒,上手简单)
前置基础:还是用 navigator.mediaDevices.getUserMedia 获取摄像头MediaStream,和前面拍照网页是同一个媒体源。

方案1:MediaRecorder + HTTP分片上传(简易直播,适合入门)

原理:浏览器拿到摄像头流,用MediaRecorder编码成小块视频片段(blob),通过fetch持续POST发给后端;后端接收片段,转封装为HLS,前端用video.js播放。

  • 优点:技术栈简单,不需要复杂的信令服务器;后端用Node/Python/PHP都能写接收接口。
  • 缺点:延迟高,不适合实时互动,适合监控、低要求直播。

前端核心代码片段

let mediaStream;
let mediaRecorder;
const chunkList = [];

// 获取摄像头
mediaStream = await navigator.mediaDevices.getUserMedia({
    video: { facingMode: 'user', width: {ideal:1280}, height:{ideal:720} },
    audio: true // 同时采集音频
});

// 实例化录制器,编码格式
mediaRecorder = new MediaRecorder(mediaStream, {
    mimeType: 'video/webm;codecs=vp9,opus'
});

// 每500ms生成一块视频分片
mediaRecorder.ondataavailable = async (e) => {
    if(e.data.size > 0){
        const formData = new FormData();
        formData.append('videoChunk', e.data);
        // POST上传分片到后端接口
        await fetch('/api/upload-chunk', {
            method: 'POST',
            body: formData
        })
    }
}
mediaRecorder.start(500); // 每500ms触发一次ondataavailable

后端职责:

  1. 接收webm分片
  2. 把分片追加到视频文件,或者交给ffmpeg转码,生成HLS m3u8 + ts切片
  3. 对外提供HLS地址给播放器观看
限制:浏览器编码格式有限,大多输出webm;如果要mp4,浏览器原生MediaRecorder不支持mp4实时流式输出。

方案2:WebRTC(低延迟实时直播,首选)

WebRTC可以直接把摄像头MediaStream实时推流到后端,延迟极低。
两种部署模式:

模式A:SFU 媒体转发服务器(最常用直播方案)

浏览器(推流端) ↔ SFU服务器 ↔ 多个观看浏览器

  • 浏览器采集摄像头流,通过SDP信令,把RTP音视频包发给SFU
  • SFU负责接收、转发视频流给所有观众,不做转码(性能好)
  • 常用开源SFU:Mediasoup、Janus、LiveKit、Pion

流程:

  1. 前端getUserMedia拿到mediaStream
  2. 创建RTCPeerConnection
  3. 将摄像头track添加到peer连接
  4. 通过websocket作为信令通道,交换SDP、ICE候选
  5. 建立P2P/服务器中转,RTP数据包持续推送到后端SFU
// WebRTC极简前端示例
const pc = new RTCPeerConnection({
  iceServers: [{urls: 'stun:stun.l.google.com:19302'}]
});
// 将摄像头流加入peer连接
mediaStream.getTracks().forEach(track=>pc.addTrack(track, mediaStream));
// 创建offer,通过websocket发给后端信令服务器
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// 发送offer到后端websocket信令
ws.send(JSON.stringify({type:'offer', sdp:offer.sdp}));

模式B:P2P直连

浏览器直接和观看端P2P,后端只做信令转发,不转发媒体。
缺点:不适合多人直播,网络穿透失败率高,生产直播很少用

方案3:WebRTC 推流到RTMP服务器(传统直播流)

很多后端直播服务是RTMP(Nginx+rtmp-module,SRS),浏览器本身不原生支持RTMP
两个解决办法:

  1. 前端WebRTC推流 → 后端SRS/Mediasoup接收WebRTC,内部转RTMP,推到CDN。(推荐,SRS原生支持WebRTC转RTMP)
  2. 用wasm在浏览器内把MediaStream编码成H.264,封装RTMP,这个方案前端CPU占用很高,不推荐生产。
SRS 是国内最常用开源直播服务器:支持 WebRTC 推流、转RTMP、转HLS,部署简单。

关键概念区分

  1. MediaStream:摄像头原始未编码音视频帧(就是你拍照网页里的流),只能在浏览器内部使用,不能直接发给后端,必须编码打包。
  2. MediaRecorder:浏览器内置编码器,把MediaStream编码成webm片段。
  3. WebRTC RTP:实时编码,小包传输,低延迟。
  4. RTMP:传统直播协议,浏览器不原生支持,多用于CDN。

选型建议

  • 低延迟互动直播、远程监控、实时画面:WebRTC + SRS / LiveKit
  • 简单监控,允许10秒以上延迟,快速开发:MediaRecorder分片上传 + ffmpeg转HLS
  • 需要对接阿里云/腾讯云直播CDN:前端WebRTC推SRS,SRS转RTMP推CDN

常见坑

  1. 必须HTTPS/localhost,否则getUserMedia无法打开摄像头。
  2. WebRTC需要信令服务器(websocket),媒体流和信令是两条独立通道。
  3. 带宽:1080p30视频推流,至少需要4Mbps上行带宽。
  4. 编码:浏览器支持H.264 / VP8 / VP9,不同浏览器支持不一样。
  5. 高DPI屏幕不影响视频流本身,视频流分辨率由getUserMedia的constraints指定,不是屏幕分辨率。

拓展:和你之前拍照代码的关联

getUserMedia拿到的MediaStream,既可以给video预览、canvas截图,也可以直接喂给MediaRecorder或者RTCPeerConnection做推流,同一个媒体源复用。

标签: none

添加新评论