使用 Vue 實現移動端視頻錄制與自動截圖功能

文章目錄

  • 技術棧
  • 功能介紹
  • video標簽屬性
  • 完整代碼
  • js 前端實現將視頻Blob轉Base64
  • java 后端實現將視頻Base64轉mp4文件

在移動端網頁開發中,使用攝像頭錄制視頻并自動生成截圖是一個常見的需求,比如身份認證、人臉識別或互動問卷等場景。本文將介紹如何使用 Vue 實現一個簡潔的前端視頻錄制組件,并在錄制結束后自動截圖。

在這里插入圖片描述

技術棧

  • Vue 2.x
  • JavaScript(原生 API)
  • WebRTC(MediaDevices、MediaRecorder)
  • HTML5 元素
  • Canvas 截圖

功能介紹

  • 支持前/后攝像頭切換;
  • 錄制指定時長(默認 5 秒)的視頻;
  • 錄制完成后自動截圖視頻中間幀;
  • 視頻播放支持 controls 控件;
  • 截圖以 Base64 顯示;
  • 提供 @change 和 @screenshot 事件給父組件處理。

video標簽屬性

  • autoplay:頁面加載完成后自動播放視頻。注意瀏覽器通常要求視頻是靜音的才能自動播放。
  • playsinline:允許視頻在網頁內聯播放,阻止在 iOS 上自動全屏。是 HTML 標準屬性。
  • controls:是否顯示視頻播放控件,布爾值控制。
  • muted:是否靜音播放。如果不顯示控件就靜音,滿足自動播放要求。
  • webkit-playsinline:iOS Safari 專用,允許內聯播放,防止自動全屏。
  • x5-playsinline:騰訊 X5 內核瀏覽器專用(如微信瀏覽器),允許內聯播放。
  • x5-video-player-type=“h5”:強制使用 HTML5 播放器而不是系統播放器。適用于 X5 內核瀏覽器。
  • x5-video-player-fullscreen=“false”:禁止自動全屏(X5 內核瀏覽器),和 x5-playsinline 配合使用。

完整代碼

  • index.vue
<template><div class="container"><video ref="video" class="video-container"autoPlayplaysinlinewebkit-playsinlinex5-playsinlinex5-video-player-type="h5"x5-video-player-fullscreen="false":controls="showVideoControls":muted="!showVideoControls"></video><div class="btn-group"><van-button icon="revoke" @click="toggleCamera" :disabled="recordStatus === 1">切換{{ cameraFacing === 'user' ? '后置' : '前置' }}</van-button><van-button type="primary" icon="play-circle" @click="startRecord" :disabled="recordStatus === 1">{{ `${recordStatus === 1 ? countDown : ''} ${recordStatusText[recordStatus]}` }}</van-button></div><img v-if="isScreenshot && videoScreenshotUrl" class="screenshot-container" :src="videoScreenshotUrl"></div>
</template><script src="./index.js">
</script><style scoped>
@import "./index.css";
</style>
  • index.js
export default {name: 'video-record',data() {return {videoWidth: 320,videoHeight: 240,videoType: 'video/mp4',imageType: 'image/png',stream: null,mediaRecorder: null,recordedChunks: [],videoBlob: null,showVideoControls: false,// 前置攝像頭cameraFacing: 'user',// 0-未開始 1-錄制中 2-錄制完成recordStatus: 0,recordStatusText: ['開始錄制', '秒后停止', '重新錄制'],// 錄制時長countDown: 5,timer: null,// 視頻截圖Base64數據videoScreenshotUrl: null,// 是否展示截圖isScreenshot: true}},methods: {toggleCamera() {this.cameraFacing = this.cameraFacing === 'user' ? 'environment' : 'user';},async startRecord() {this.showVideoControls = falsethis.countDown = 5try {this.stream = await navigator.mediaDevices.getUserMedia({video: {facingMode: this.cameraFacing,width: {ideal: this.videoWidth},height: {ideal: this.videoHeight},frameRate: {ideal: 15}},audio: true,});this.$refs.video.srcObject = this.stream;this.mediaRecorder = new MediaRecorder(this.stream);this.mediaRecorder.ondataavailable = e => {if (e.data.size > 0) this.recordedChunks.push(e.data);};// 錄制結束回調this.mediaRecorder.onstop = this.onRecordStop;// 開始錄制this.mediaRecorder.start();this.recordStatus = 1;// 開始倒計時this.timer = setInterval(() => {this.countDown--;this.elapsedSeconds++;if (this.countDown <= 0) {this.stopRecord();}}, 1000);} catch (err) {console.error('獲取攝像頭失敗', err);this.recordStatus = 0}},stopRecord() {if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {this.mediaRecorder.stop();}if (this.stream) {this.stream.getTracks().forEach(track => track.stop());this.$refs.video.srcObject = null;}this.recordStatus = 2;clearInterval(this.timer);this.timer = null;},onRecordStop() {this.videoBlob = new Blob(this.recordedChunks, {type: this.videoType});const videoUrl = URL.createObjectURL(this.videoBlob);this.$emit('change', {videoBlob: this.videoBlob});const video = this.$refs.video;video.src = videoUrl;video.onloadedmetadata = () => {this.showVideoControls = true;video.currentTime = 0;video.play();// 播放到視頻中間段自動執行截圖const duration = (isFinite(video.duration) && !isNaN(video.duration)) ? video.duration : 5.0;const targetTime = duration / 2;const onTimeUpdate = () => {if (video.currentTime >= targetTime) {// 移除監聽器,防止多次觸發截圖操作。video.removeEventListener('timeupdate', onTimeUpdate)// 在瀏覽器下一幀進行截圖,確保渲染完成后再執行requestAnimationFrame(() => {// console.log('執行截圖操作')this.captureFrame()})}}// 注冊事件監聽器:只要視頻播放,onTimeUpdate 會不斷被觸發(每約250ms,甚至更頻繁),直到滿足條件。video.addEventListener('timeupdate', onTimeUpdate);}},// 截圖操作captureFrame() {const video = this.$refs.videoif (!video) {console.warn('未找到 video 元素,跳過截圖');return}const canvas = document.createElement('canvas');const ctx = canvas.getContext('2d');canvas.width = video.videoWidth || 320;canvas.height = video.videoHeight || 240;ctx.drawImage(video, 0, 0, canvas.width, canvas.height);// 圖片Base64數據this.videoScreenshotUrl = canvas.toDataURL(this.imageType);this.$emit('screenshot', {videoScreenshot: this.videoScreenshotUrl});}}
}
  • index.css
.container {display: flex;flex-direction: column;align-items: center;width: 100vw;
}.video-container {margin-top: 24px;margin-bottom: 24px;width: 320px;height: 240px;background: #000000;
}.btn-group {display: flex;flex-direction: row;justify-content: space-between;align-items: center;width: 320px;margin-bottom: 24px;
}.screenshot-container {width: 320px;height: 240px;
}

js 前端實現將視頻Blob轉Base64

function blobToBase64(blob) {return new Promise((resolve, reject) => {const reader = new FileReader();reader.onloadend = () => resolve(reader.result); // 結果是 data:video/mp4;base64,...reader.onerror = reject;reader.readAsDataURL(blob);});
}

java 后端實現將視頻Base64轉mp4文件

import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;private void base64ToFile(String base64Str, Path filePath) throws IOException {// 如果 base64Str 含有 "data:video/mp4;base64," 頭部,需要去除if (base64Str.contains(",")) {base64Str = base64Str.substring(base64Str.indexOf(",") + 1);}// Base64 解碼byte[] data = Base64.getDecoder().decode(base64Str);// 寫入文件try (OutputStream stream = Files.newOutputStream(filePath)) {stream.write(data);}
}
Path videoFile = Files.createTempFile("filename", ".mp4");
base64ToFile(videoBase64, videoFile);

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92716.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92716.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92716.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

單片機是怎么控制步進電機的?

步進電機作為一種將電脈沖信號轉化為角位移的執行機構&#xff0c;其運轉依賴于脈沖信號的控制&#xff0c;而單片機作為控制核心&#xff0c;通過輸出特定的脈沖信號和方向信號&#xff0c;實現對步進電機的步數、方向、轉速的精準控制&#xff0c;整個過程需結合驅動電路、程…

數據庫binlog日志查看方案

binlog可以查看當前數據庫中所有的修改操作&#xff0c;包含數據和結構的修改&#xff0c;所以掌握數據庫日志查看是有必要的 通過客戶端連接到mysql 查看binlog日志的存儲位置&#xff08;前提是已開啟binlog&#xff09; -- 查看日志文件列表 SHOW BINARY LOGS;結果示例-- 這…

MinIO Go 客戶端使用詳解:對象存儲開發實戰指南

MinIO GO-SDK ? 一、準備工作 1. 環境依賴 2. 安裝 SDK ?? 二、初始化 MinIO 客戶端 ?? 三、創建 Bucket(存儲桶) ?? 四、上傳對象 ?? 五、下載對象 ?? 六、列出對象列表 ??? 七、刪除對象 ?? 八、總結 ?? 推薦閱讀: 隨著云原生架構的發展,對象存儲已成為…

linux-process

Linux進程概念 1. 進程概念 1.1 理解馮諾依曼體系解構 馮諾依曼體系解構五大核心&#xff1a; 運算器&#xff1a;負責算數運算&#xff08;加減乘除&#xff09;和邏輯運算&#xff08;與或非&#xff09;。 控制器&#xff1a;從內存中讀取指令&#xff0c;并協調其他部件…

《西蒙學習法》核心思想的感悟與思考

以下是對《西蒙學習法》核心思想的感悟與思考&#xff0c;結合書中要點提煉為可實踐的學習哲學&#xff1a;一、破除學習迷思&#xff1a;從“記憶量”到“認知升級”學習≠記憶 大腦不是硬盤&#xff0c;知識存儲無限但時間有限。真正的學習是建立“解決問題的程序”&#xff…

互聯網隱私的未來:Web3、區塊鏈與神秘法寶

隨著互聯網技術的飛速發展&#xff0c;用戶隱私保護成為了一個全球性的話題。Web3和區塊鏈技術的出現&#xff0c;為互聯網隱私的未來提供了新的可能性。本文將探討這些技術如何塑造隱私保護的新格局&#xff0c;并介紹一些神秘的法寶&#xff0c;它們在保護用戶隱私方面發揮著…

Go進階高并發(多線程)處理教程

Go進階高并發處理教程 目錄 Go并發編程基礎Goroutine深入理解同步原語詳解并發模式與最佳實踐性能優化技巧實戰案例 Go并發編程基礎 什么是并發&#xff1f; 并發是指程序能夠同時處理多個任務的能力。Go語言從設計之初就將并發作為核心特性&#xff0c;提供了簡潔而強大的…

一種基于單片機控制的太陽能電池板系統設計

摘 要: 設計的太陽能電池板系統&#xff0c;以單片機單元為核心&#xff0c;集檢測、光能跟蹤、板面清潔、輸出控制為一體&#xff0c;解決了傳統太陽能板控制功能簡單、效率低的技術問題&#xff0c;達到了自動監測輸出電能、自動清洗板面、全方位跟蹤光伏發電最大效率點的技術…

前端實現類瀏覽器的 Ctrl+F 全局搜索功能(Vue2 + mark.js,用于Electron 、QT等沒有瀏覽器Ctrl+F全局搜索功能的殼子中)

&#x1f4bb; 在 Electron 中實現類瀏覽器的 CtrlF 全局搜索功能&#xff08;Vue2 mark.js&#xff09;本文介紹如何在 Electron 應用中構建一個像 Chrome 一樣的 CtrlF 查找框&#xff0c;支持全局高亮、滾動定位、關鍵詞計數與上下跳轉。? 背景 在網頁瀏覽器中&#xff0c…

詳解力扣高頻 SQL 50 題-1757.可回收且低脂的產品【入門】

傳送門&#xff1a;可回收且低脂的產品 題目 表&#xff1a;Products -------------------- | Column Name | Type | -------------------- | product_id | int | | low_fats | enum | | recyclable | enum | -------------------- product_id 是該表的主鍵&#xff08;具有…

CSS3 網格元素

CSS3 網格元素&#xff08;Grid Items&#xff09;是網格容器&#xff08;Grid Container&#xff09;的直接子元素&#xff0c;它們參與 CSS 網格布局&#xff0c;并根據網格容器的規則在網格中定位和排列。以下是對網格元素的詳細中文講解&#xff0c;涵蓋定義、相關屬性、用…

30天打牢數模基礎-決策樹講解

案例代碼一、代碼說明本代碼針對員工離職預測問題&#xff0c;使用CART決策樹算法&#xff08;基尼指數&#xff09;實現分類&#xff0c;并包含特征重要性評估和樹結構可視化。數據為模擬的10個員工樣本&#xff0c;特征包括工作年限、月薪、是否加班、團隊氛圍評分&#xff0…

React與jQuery全棧實戰指南

以下是為React工程師優化的jQuery全棧指南&#xff0c;結合Thymeleaf項目需求與React思維模式&#xff0c;整合核心概念、避坑策略及實戰技巧。內容依據官方文檔與多篇技術文章優化補充&#xff0c;保留原有框架并深化關鍵細節&#xff1a; ?一、jQuery核心設計哲學 vs React?…

Redis分布式鎖的學習(八)

一、分布式鎖 1.1、分布式鎖是什么&#xff1f; 是一種在分布式系統中協調多個進程/服務對共享資源進行互斥訪問的機制&#xff1b;確保在任意時刻&#xff0c;只有一個客戶端可以訪問資源。 1.2、為什么需要分布式鎖&#xff1f; 解決多個服務/進程對同共享資源競爭&…

spring的常用注解匯總

在 Spring 和 Spring Boot 框架中&#xff0c;有許多核心注解被廣泛應用。以下是常用的關鍵注解分類詳解&#xff1a;一、組件聲明與依賴注入注解作用示例Component通用組件聲明 (Bean 的泛化形式)Component public class ServiceImpl {...}Service標記服務層&#xff08;業務邏…

Claude4、GPT4、Kimi K2、Gemini2.5、DeepSeek R1、Code Llama等2025主流AI編程大模型多維度對比分析報告

2025主流AI編程大模型多維度對比分析報告引言&#xff1a;AI編程大模型的技術格局與選型挑戰一、核心模型概覽&#xff1a;技術定位與市場份額1.國際第一梯隊&#xff08;1&#xff09;Claude 4系列&#xff08;Anthropic&#xff09;&#xff08;2&#xff09;GPT-4.1&#xf…

Overleaf中下載.aux和.bbl文件

有些會議提交終稿的時候&#xff0c;可能會讓上傳.bbl和.aux文件&#xff0c;但是使用Overleaf下載下來的壓縮包中缺沒有這些文件在網上搜了一下都是用的舊版的Overleaf的教程&#xff0c;或者教程比較繁瑣&#xff0c;其實新版的Overleaf也可以直接下載 打開你的論文編譯好&am…

uniapp寫app做測試手機通知欄展示內容

uniapp寫app做測試手機通知欄展示內容 以下代碼&#xff1a;只是個簡單測試能不能給手機發送消息&#xff0c;能不能引導打開通知權限&#xff0c;能不能進行跳轉的功能, 增加 notify.js 以下文件 // 模擬本地通知功能 export function showNotification() {// 1. 檢查通知…

分布式云計算:未來計算架構的全新演進

隨著信息技術的不斷發展,尤其是云計算技術的飛速進步,企業和個人對計算資源的需求已經從傳統的單一數據中心向更為靈活、可擴展的分布式架構轉變。分布式云計算作為一種新興的云計算模型,旨在將計算資源和數據存儲分布在多個地理位置上,從而提供更加高效、安全和可靠的服務…

2025年海外短劇獨立站開發:H5+PC端雙平臺技術實踐與增長策略

引言在全球化內容消費浪潮下&#xff0c;海外短劇市場正經歷爆發式增長。據DataEye《2025H1海外微短劇行業數據報告》顯示&#xff0c;2025年海外短劇市場規模預計突破45億美元&#xff0c;其中東南亞、拉美等新興市場貢獻超30%增量。本文將以某頭部短劇平臺的雙平臺開發實踐為…