纯web技术一起实现摄像头和麦克风视频录制,并带历史记录功能
目录
前言
如题,今天我们用纯 web 技术,实现摄像头+麦克风 视频的录制功能,代码约 100 余行, 主要涉及的知识点:
- MediaDevices
提供对连接的媒体输入设备(如照相机和麦克风)的访问,以及屏幕共享等。 - MediaRecorder
录制音频或者视频。 - IndexedDB
储存较大数据结构的事务性数据库。 - URL
用来把视频的 Blob 数据生成地址,提供给video
标签使用。
效果演示
真机效果:
PC 端 + 模拟移动 + 虚拟摄像头(VCam)
源码地址
注意:
- 权限问题,需要显式的授权
- 如果手机端预览,需要启用 https,demo 已经附带证书
思路
- 利用 MediaDevices 唤起摄像头和麦克风
- 把第一步获取的流,同时用于
video
和MediaRecorder
因为录制的同时需要看到我们摄像头的内容 - 录制结束后,把录制视频存入 indexedDB
- 按照 keys 列出已录制的视频,点击后,获取 Blob 文件,生成 url,提供给 video 标签播放。
实现
唤起摄像头和麦克风并获得其流
这里需要用的就是 MediaDevices,对应的 API 就是navigator.mediaDevices.getUserMedia
核心代码如下:
const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" }, // 唤起内面的摄像头, audio: true // 需要音频,例如麦克风 }) // 把流传给 video 元素,即可看到摄像头内容 videoEL.srcObject = stream; // 初始化 MediaRecorder mediaRecorder = new MediaRecorder(stream, { mimeType: "video/webm" });
注意事项:
- getUserMedia 方法的 facingMode 参数
user
为前置的摄像头,environment
为后置摄像头。 - new MediaRecorder 的参数
{ mimeType: "video/webm" }
如果未设置正确,可能就只有视频,没有麦克风声音了
录制和保存
录制必然有开始和停止两个操作,实现方式很多,我们就采用最简单的两个按钮形式, 并分别给注册上相关的事件处理程序。
代码如下:
这里有一个小的知识点,任何有 id 属性的节点,你均可使用 id 属性对应的变量直接访问该元素。
<button id="btnRecord" class="btn">录制</button> <button id="btnStop" class="btn" >停止</button> btnRecord.addEventListener("click", () => { startRecord(mediaRecorder); mediaRecorder.start(); }); btnStop.addEventListener("click", () => { mediaRecorder.stop(); })
是不是很简单。 注意上面停止按钮点击后,我们调用了mediaRecorder.stop
方法,其之后会触发recorder.onstop
事件,这个时候,我们唤起弹出框,让用户输入视频的名字,然后将内容保存到 indexedDB 即可。
indexedDB 的存取有很多封装库,indexedDB 参见部分列出了不少于 10 个库,这里我们采用 idb-keyval库,其简单且小巧的(~600B)基于 Promise 的键值对存储,使用也是极其简单,get, set 就行了。
具备上面的知识后,看代码:是不是很简单。
function startRecord(recorder) { var chunks = []; // 收集数据 recorder.ondataavailable = function (e) { chunks.push(e.data); } // 监听停止事件 recorder.onstop = async () => { var clipName = prompt('请输入视频的名字'); var blob = new Blob(chunks, { 'type': 'audio/mp4;' }); await idbKeyval.set(clipName + ".mp4", blob); listHistory(); } }
历史和观看
历史嘛,那就是读取 keys,严格意义上,应该使用 indexedDB 的游标来读取,本文为了简单,直接读取所有的 keys,然后判断文件后缀来过滤。
async function listHistory() { list.innerHTML = null; const keys = await idbKeyval.keys(); console.log("keys:", keys); keys.filter(k => k.endsWith(".mp4")).forEach(key => { const divEl = document.createElement("div"); divEl.textContent = key; divEl.onclick = () => playVideo(key); list.appendChild(divEl); }); }
到此为止,我们就差点击某个历史视频之后的播放逻辑了,也很简单:
async function playVideo(key) { const blob = await idbKeyval.get(key); // 生成地址 fplayer.src = URL.createObjectURL(blob); fplayer.style.display = "block"; fplayer.play(); }
到此文本,所有的核心代码都已经实现了。
小结
是不是很简单,一切都看起来没那么难,这样,你才容易入坑啊。
原文链接:点击这里
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
码云笔记 » 纯web技术一起实现摄像头和麦克风视频录制,并带历史记录功能
码云笔记 » 纯web技术一起实现摄像头和麦克风视频录制,并带历史记录功能