一、记录开发中封装的视频录制功能

封装

// 请在以下环境调试使用媒体流功能:
// localhost域 ||  HTTPS协议域名 ||  file:///协议本地文件
// 其他情况,navigator.mediaDevices 的值或为 undefined。
// chrome调试参考 https://blog.csdn.net/weixin_49293345/article/details/112600213?spm=1001.2014.3001.5501import fixWebmDuration from 'webm-duration-fix';declare global {interface Navigator {webkitGetUserMedia: any;mozGetUserMedia: any;}
}class NewNavigator extends Navigator {mediaDevices: any;
}export enum ErrorCode {INITE_ERROR = 100, // 初始化失败STARTRECORD_ERROR = 101, // 开始录制错误STOPRECORD_ERROR = 102, // 结束录制错误
}export interface ErrorData {code: ErrorCode;message: string;
}export type ErrorCallback = (error: ErrorData) => void;export interface GetUserMediaConfig {audio: boolean;video: {width?: { min?: number; ideal?: number; max?: number };height?: { min?: number; ideal?: number; max?: number };facingMode: "user" | { exact: "environment" };};
}export type StopRecordCallback = (data: {videoBlob: Blob;imageUrl: string;
}) => void;const videoEl = document.createElement("video");
videoEl.style.width = "100%";
videoEl.style.height = "100%";
videoEl.style.objectFit = "cover";
videoEl.style.position = "absolute";
videoEl.style.left = "0";
videoEl.style.top = "0";
videoEl.setAttribute('autoplay', '');
videoEl.setAttribute('playsInline', 'true');
videoEl.setAttribute('muted', 'true');const navigator: NewNavigator = window.navigator;// 初始化媒体流方法
const initMediaDevices = function () {// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象if (navigator.mediaDevices === undefined) {navigator.mediaDevices = {};}// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia// 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。if (navigator.mediaDevices.getUserMedia === undefined) {navigator.mediaDevices.getUserMedia = function (constraints: GetUserMediaConfig) {// 首先,如果有getUserMedia的话,就获得它var getUserMedia =navigator.webkitGetUserMedia || navigator.mozGetUserMedia;// 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口if (!getUserMedia) {return Promise.reject(new Error("getUserMedia is not implemented in this browser"));}// 否则,为老的navigator.getUserMedia方法包裹一个Promisereturn new Promise(function (resolve, reject) {getUserMedia.call(navigator, constraints, resolve, reject);});};}
};
initMediaDevices();let video_timer: any = null;
const debounce = (fun: Function, time: number) => {if (video_timer) {clearTimeout(video_timer);}video_timer = setTimeout(() => {fun();video_timer = null;}, time);
};
class RecordJs {public readonly container;public recording: boolean; // 是否正在录制public mediaStream: any; // 记录媒体流public mediaRecorder: any; // 记录媒体录制流public recorderFile: Blob; // 录制到的文件public imageUrl: string; // 截取到的图片public stopRecordCallback?: StopRecordCallback; // 录制完回调public errorCallback?: ErrorCallback; // 错误回调constructor(container: HTMLElement, errorCallback?: ErrorCallback) {this.container = container;this.recording = false;this.mediaStream = null;this.mediaRecorder = null;this.recorderFile = new Blob();this.imageUrl = "";this.stopRecordCallback = () => {};this.errorCallback = errorCallback;this.container.style.position = "relative";}// 截取最后一帧显示cropImage(): string {const canvas = document.createElement("canvas");canvas.width = this.container.clientWidth;canvas.height = this.container.clientHeight;canvas.getContext("2d")?.drawImage(videoEl, 0, 0, canvas.width, canvas.height);const dataUrl = canvas.toDataURL("image/png") || "";this.imageUrl = dataUrl;return dataUrl;}// 调用Api打开媒体流_getUserMedia(videoEnable: {front: boolean; // 控制前后摄像头width?: number; // 获取一个最接近 width*height 的相机分辨率height?: number;},audioEnable = false) {const constraints = {audio: audioEnable,video: {facingMode: videoEnable.front ? "user" : { exact: "environment" },width: { ideal: videoEnable.width },height: { ideal: videoEnable.height },},};const that = this;navigator.mediaDevices.getUserMedia(constraints).then(function (stream: any) {// 先清除存在的媒体流that.closeRecord();// 初始化媒体流文件 ---startlet chunks: any = [];that.mediaStream = stream;that.mediaRecorder = new MediaRecorder(stream);that.mediaRecorder.ondataavailable = function (e: BlobEvent) {that.mediaRecorder.blobs.push(e.data);chunks.push(e.data);};that.mediaRecorder.blobs = [];that.mediaRecorder.onstop = async function (e: Event) {that.recorderFile = new Blob(chunks, {type: that.mediaRecorder.mimeType,});// 处理录制视频播放获取时长问题try {const fixBlob = await fixWebmDuration(that.recorderFile);that.recorderFile = fixBlob;} catch (error) {}chunks = [];that.recording = false;if (that.stopRecordCallback) {try {that.stopRecordCallback({videoBlob: that.recorderFile,imageUrl: that.imageUrl,});} catch (error: unknown) {console.log("录制完业务回调出错了:" + error);}}};// 初始化媒体流文件 ---end// 初始化video播放内容 ---start// 旧的浏览器可能没有srcObjectif ("srcObject" in videoEl) {videoEl.srcObject = stream;} else {// 防止在新的浏览器里使用它,应为它已经不再支持了videoEl.src = window.URL.createObjectURL(stream);}if (that.container.lastChild?.nodeName === "VIDEO") {that.container.removeChild(that.container.lastChild);}// 前置摄像头左右视觉效果对调videoEl.style.transform = videoEnable.front ? "rotateY(180deg)" : "";that.container.appendChild(videoEl);videoEl.onloadedmetadata = function (e) {// 这里处理了一点关于ios video.play()播放NotAllowedError问题const playPromise = videoEl.play();that.container.appendChild(videoEl);if (playPromise !== undefined) {playPromise.then(() => {videoEl.muted = true;setTimeout(() => {videoEl.play();}, 10);}).catch((error) => {console.log(error);});}};// 初始化video播放内容 ---end}).catch(function (error: Error) {that.errorCallback &&that.errorCallback({code: ErrorCode.INITE_ERROR,message: error.message,});console.log(error.name + ": " + error.message);});}// 打开媒体流openRecord(videoEnable: {front: boolean; // 控制前后摄像头width?: number; // 获取一个最接近 width*height 的相机分辨率height?: number;},audioEnable = false) {// 打开媒体流方法为异步 防止频繁切换前后摄像头调用媒体流非正常显示问题debounce(() => this._getUserMedia(videoEnable, audioEnable), 300);}// 开始录制startRecord() {if (this.recording) {this.stopRecord();}this.recording = true;try {this.mediaRecorder?.start();} catch (error) {this.errorCallback &&this.errorCallback({code: ErrorCode.STARTRECORD_ERROR,message: "开始录制出错了:" + error,});console.log("开始录制出错了:" + error);}}// 停止录制stopRecord(callback?: StopRecordCallback) {if (this.recording && this.mediaStream) {this.stopRecordCallback = callback;this.cropImage();// 终止录制器if (this.mediaRecorder && this.mediaRecorder.state !== "inactive") {try {this.mediaRecorder.stop();} catch (error) {this.errorCallback &&this.errorCallback({code: ErrorCode.STOPRECORD_ERROR,message: "结束录制出错了:" + error,});console.log("结束录制出错了:" + error);}}}}// 关闭媒体流closeRecord() {this.stopRecord();if (!this.mediaStream) return;const tracks = (this.mediaStream as MediaStream).getTracks();tracks.forEach((track) => {if (typeof track.stop === 'function') {track.stop();}});}
}export default RecordJs;

使用

import RecordJs from './RecordJs' // 引入 RecordJs
const recordJs = new RecordJs(Dom元素, 错误回调函数) // 初始化 给定一个dom作为容器
recordJs.openRecord({ front: true }) // 开启前摄像头
recordJs.startRecord(); // 开始录制
recordJs.stopRecord((data)=>{// 传入结束录制回调// data对象返回视频blob和截取的最后一帧图片url// do something...
})

二、关于录制后播放问题

1.如何播放

recordJs.stopRecord((data)=>{// 传入结束录制回调// data对象返回视频blob和截取的最后一帧图片url// do something...const { videoBlob } = data;const videoFile = new File([blobData], 'filename.mp4', { type: 'video/mp4' });const videoUrl = URL.createObjectURL(videoFile)// 将 videoUrl 给到播放组件 video的src或者其他// 如果使用的是video.js第三方播放器播放,// videojs.src()调用时需指定type,videojs.src({ src: videoUrl, type: 'video/mp4' })
})

2.播放时长显示问题,进度条不显示,或者最后几秒才出现总时长

借助方法库 “webm-duration-fix”: “^1.0.4”,实测有用
具体用法已经更新到封装的代码里

完!

前端实现视频录制功能相关推荐

  1. FFmpeg 开发(12):Android FFmpeg 实现带滤镜的微信小视频录制功能

    前文利用 FFmpeg 分别实现了对 Android Camera2 采集的预览帧进行编码生成 mp4 文件,以及对 Android AudioRecorder 采集 PCM 音频进行编码生成 aac ...

  2. RobotStudio软件:机器人仿真视频录制功能使用方法

    目录 功能介绍 屏幕录像机设置 仿真视频录制命令介绍 仿真视频录制操作 仿真视频查看方法 本文已经首发在个人微信公众号:工业机器人仿真与编程(微信号:IndRobSim),欢迎关注! 功能介绍 Rob ...

  3. DELMIA教程:仿真视频录制功能介绍与使用方法

    目录 功能简介 打开视频录制器 视频生成通用参数设置 视频图像采集参数设置 视频编码与播放参数设置 仿真视频录制操作 仿真视频播放 功能简介 使用DELMIA软件制作好的虚拟仿真项目,往往需要通过视频 ...

  4. 没加前后摄像头切换功能的视频录制功能

    这个是视频录制和拍照功能合到一个页面的初期版本,有助于更好地理解视频录制功能,但可能有预览黑屏和画面反转的问题: TakePicActivity.java: package com.haier.uho ...

  5. Android仿微信小视频录制功能(二)

    Android仿微信小视频录制功能(二) 接着上一篇,在完成了录制功能后,伟大的哲学家沃兹基索德曾经说过:"有录就有放.",那么紧接着就来实现播放功能,按照国际惯例,先上下效果图: ...

  6. Android仿微信小视频录制功能

    还没看完,应该还不错,先收藏,觉得可以开拓 https://blog.csdn.net/u012227600/article/details/50835633 -------------------- ...

  7. Java集成腾讯云音视频录制功能

    Java集成腾讯云音视频录制功能 为什么要实现音视频录制功能 因为我们做的是一个医院的项目,医生和患者可能进行视频通话和语音通话,为了保证通话的质量以及后续的问题, 我们就需要进行音视频录制,以便后续 ...

  8. Android-Studio 用ShareSDK集成社会化分享,短信验证码,视频录制功能

    ShareSDK主要用于qq.qq空间.微信.微信朋友圈的分享功能以及第三方登录功能 SMSSDK主要用于短信验证码 集成工作:将sharesdk与smssdk同时集成到android-studio项 ...

  9. 类似于抖音等短视频录制功能的SDK接入教程

    1.1概述 美摄SDK致力于解决移动端视频开发的技术门槛,使仅有android界面开发经验的程序员,都可以开发出性能优异.渲染效果丰富的的视频录制.编辑功能.我们的优势体现在: 录制.剪辑不限时长 行 ...

  10. 基于Libvlc库的视频录制功能简述(C#)

    最近在学习libvlc库,如下讲述如何播放网络或本地视频并进行视频录制的功能: 一.先看效果: 1-1.主界面:首先Init环境初始化,然后可选择播放网络流或是本地视频文件: 1-2.若播放网络流,则 ...

最新文章

  1. CentOS 6.0 VNC远程桌面配置
  2. Django使用缓存笔记
  3. 斯坦福CS231n项目实战(二):线性支持向量机SVM
  4. [听尉迟方侃侃]平台
  5. fiddler修改支付金额_不容忽视的记账工具:支付宝记账
  6. 写入和读取外部存储文件
  7. 第七部分:小插曲,Deferred
  8. 【C/C++】C/C++中Static的作用详述
  9. Scaleform在游戏制作中的使用
  10. 如何对接快递助手物流查询接口【干货】
  11. matlab tic and toc,什么是Python等价于Matlab的tic和toc函数?
  12. 虚拟机opnsense作为dhcp服务器,ESXI 与 OPNSense 配合
  13. 离散数学 之 命题公式的主析取合取范式(java实现)
  14. Class的基本语法
  15. java 识别图片 边框_atitit.验证码识别step3----去除边框---- 图像处理类库 attilax总结java版本...
  16. php聊天室的设计实现,基于PHP的Ajax聊天室系统的设计与实现
  17. c语言保存mat图片的方法,把opencv Mat 按位存成bmp二值图像 (1bit 1pixel)
  18. osi七层模型_4、OSI七层模型简介
  19. 湖北科技职业学院计算机专业代码,志愿填报:普通文理类专业代码
  20. matlab qam 格雷编码,89. 格雷编码

热门文章

  1. java获取本机的外网IP地址(亲测有效)
  2. Pytorch基础知识之pth文件与DataLoader数据加载器
  3. parallelStream数据丢失问题
  4. 爱国者u盘linux驱动,爱国者u盘驱动
  5. Enchanted —— Taylor Swift
  6. 怎样打印计算机桌面,敬业签电脑桌面便签软件怎么打印便签内容?
  7. 安卓桌面软件_iOS便签软件求推荐,有可以在iOS系统上使用的桌面便签软件吗 - 学显...
  8. springboot2集成shiro认证鉴权(上篇)
  9. 互联网公司常用的黑话,你中招了多少?
  10. git本地库案例-找回删除的文件