前端实现视频录制功能
生活随笔
收集整理的這篇文章主要介紹了
前端实现视频录制功能
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、記錄開發中封裝的視頻錄制功能
封裝
// 請在以下環境調試使用媒體流功能: // 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”,實測有用
具體用法已經更新到封裝的代碼里
完!
總結
以上是生活随笔為你收集整理的前端实现视频录制功能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA提取纯文本_从常见文档中提取纯文
- 下一篇: ni visa pci_VISA/MAS