效果預(yù)覽
1.錄制前

2.錄制中

3.錄制結(jié)束下載文件至本地

4.視頻文件同時(shí)上傳至后端接口


后端準(zhǔn)備
參考博客https://blog.csdn.net/wuchenlhy/article/details/79311234?spm=1001.2014.3001.5506
博主在后端這塊寫的十分簡潔明了,可以直接參考實(shí)現(xiàn)開設(shè)后端簡單文件上傳接口的方法
前端組件實(shí)現(xiàn)
參考文章:
https://blog.csdn.net/XH_jing/article/details/117415496
http://t.csdn.cn/ap9Zy
http://t.csdn.cn/NpKtL
Vue 調(diào)用本地?cái)z像頭實(shí)現(xiàn)拍照功能,由于調(diào)用攝像頭有使用權(quán)限,只能在本地運(yùn)行,線上需用 https 域名才可以使用。
需求分析
點(diǎn)擊“視頻錄制”按鈕,打開攝像頭開啟錄制,同時(shí)按鈕中內(nèi)容更換為“停止錄制”
點(diǎn)擊“停止錄制”按鈕,關(guān)閉攝像頭并保存錄制文件,同時(shí)將以錄制的文件上傳后端接口
使用API
MediaDevices.getUserMedia()
該 API 會(huì)提示用戶給予使用媒體輸入的許可,媒體輸入會(huì)產(chǎn)生一個(gè)MediaStream,里面包含了請求的媒體類型的軌道。此流可以包含一個(gè)視頻軌道(來自硬件或者虛擬視頻源,比如相機(jī)、視頻采集設(shè)備和屏幕共享服務(wù)等等)、一個(gè)音頻軌道(同樣來自硬件或虛擬音頻源,比如麥克風(fēng)、A/D轉(zhuǎn)換器等等),也可能是其它軌道類型。
它返回一個(gè) Promise 對(duì)象,成功后會(huì) resolve 回調(diào)一個(gè) MediaStream 對(duì)象。若用戶拒絕了使用權(quán)限,或者需要的媒體源不可用,Promise 會(huì) reject 回調(diào)一個(gè) PermissionDeniedError 或者 NotFoundError。
HTML部分
<template>
<div class="CallCamera">
<!-- 下載按鈕 -->
<a id="downLoadLink" style="display: none"></a>
<div class="video-box">
<video ref="video"></video>
<!-- 視頻錄制或暫停 -->
<button @click="recordOrStop" id="record_btu">視頻錄制</button>
</div>
</div>
</template>
JS部分
1.調(diào)用開啟攝像頭
// 調(diào)用打開攝像頭功能
getCamera() {
// 舊版本瀏覽器可能根本不支持mediaDevices,我們首先設(shè)置一個(gè)空對(duì)象
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
// 正常支持版本
navigator.mediaDevices
.getUserMedia({
video: true,
audio: true,
})
.then((stream) => {
// 攝像頭開啟成功
this.mediaStreamTrack = typeof stream.stop === 'function' ? stream : stream.getTracks()[0];
this.video_stream = stream;
this.$refs.video.srcObject = stream;
this.$refs.video.play();
})
.catch(err => {
console.log(err);
});
},
2.視頻錄制并下載以及停止錄制
// 視頻錄制
record() {
console.log("record");
this.isRecord = !this.isRecord;
let mediaRecorder;
let options;
this.recordedBlobs = [];
if (typeof MediaRecorder.isTypeSupported === "function") {
// 根據(jù)瀏覽器來設(shè)置編碼參數(shù)
if (MediaRecorder.isTypeSupported("video/webm;codecs=vp9")) {
options = {
MimeType: "video/webm;codecs=h264",
};
} else if (MediaRecorder.isTypeSupported("video/webm;codecs=h264")) {
options = {
MimeType: "video/webm;codecs=h264",
};
} else if (MediaRecorder.isTypeSupported("video/webm;codecs=vp8")) {
options = {
MimeType: "video/webm;codecs=vp8",
};
}
mediaRecorder = new MediaRecorder(this.video_stream, options);
} else {
// console.log('isTypeSupported is not supported, using default codecs for browser');
console.log("當(dāng)前不支持isTypeSupported,使用瀏覽器的默認(rèn)編解碼器");
mediaRecorder = new MediaRecorder(this.video_stream);
}
mediaRecorder.start();
// 視頻錄制監(jiān)聽事件
mediaRecorder.ondataavailable = (e) => {
console.log(e);
// 錄制的視頻數(shù)據(jù)有效
if (e.data && e.data.size > 0) {
this.recordedBlobs.push(e.data);
}
};
// 停止錄像后增加下載視頻功能,將視頻流轉(zhuǎn)為mp4格式
mediaRecorder.onstop = () => {
const blob = new Blob(this.recordedBlobs, { type: "video/mp4" });
this.recordedBlobs = [];
// 將視頻鏈接轉(zhuǎn)換完可以用于在瀏覽器上預(yù)覽的本地視頻
const videoUrl = window.URL.createObjectURL(blob);
// 設(shè)置下載鏈接
document.getElementById("downLoadLink").href = videoUrl;
// 設(shè)置下載mp4格式視頻
document.getElementById("downLoadLink").download = "media.mp4";
document.getElementById("downLoadLink").innerHTML =
"DownLoad video file";
// 生成隨機(jī)數(shù)字
const rand = Math.floor(Math.random() * 1000000);
// 生成視頻名
const name = `video${rand}.mp4`;
// setAttribute() 方法添加指定的屬性,并為其賦指定的值
document.getElementById("downLoadLink").setAttribute("download", name);
document.getElementById("downLoadLink").setAttribute("name", name);
console.log(name);
const filename = "C:\\Users\\k1114\\Downloads\\" + name;
console.log(filename);
// 0.5s后自動(dòng)下載視頻
setTimeout(() => {
document.getElementById("downLoadLink").click();
}, 500);
};
},
// 停止錄制
stop() {
this.isRecord = !this.isRecord;
if (!this.$refs.video.srcObject) return;
const stream = this.$refs.video.srcObject;
const tracks = stream.getTracks();
// 關(guān)閉攝像頭和音頻
tracks.forEach((track) => {
track.stop();
});
},
3.獲取文件流并上傳后端
直接將視頻流定義為file對(duì)象,通過post方法上傳接口
//上傳至后端
let fileobj = new File(
[blob],
name,
{
type: "video/mp4",
});
let formData = new FormData(); //創(chuàng)建form對(duì)象
formData.append("filename", fileobj); //通過append向form對(duì)象添加數(shù)據(jù)
console.log(formData.get("filename")); //FormData私有類對(duì)象,訪問不到,可以通過get判斷值是否傳進(jìn)去
//上傳
this.$axios
.post("http://127.0.0.1:8000/runcase/start", formData, {
headers: { "Content-Type": "none" },
}) //請求頭要為表單
.then((response) => {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
4.按鈕事件
通過 document.getElementById("record_btu") 獲取按鈕對(duì)象,并根據(jù)點(diǎn)擊事件修改按鈕內(nèi)容和樣式
根據(jù) this.isRecord (boolen)判斷視頻錄制狀態(tài),在進(jìn)入頁面時(shí)聲明為false,按鈕觸發(fā)點(diǎn)擊事件后修改為對(duì)立狀態(tài)
因?yàn)樵阡浿茣r(shí)要在頁面的video窗口實(shí)時(shí)回顯,如果不靜音頁面會(huì)產(chǎn)生回音,所以在開啟攝像頭時(shí)靜音頁面
觸發(fā)按鈕事件開啟攝像頭后直接開始錄制會(huì)觸發(fā)錯(cuò)誤(我不知道具體原因,可能是攝像頭還沒有初始化完畢),所以設(shè)置了延時(shí)執(zhí)行錄制文章來源:http://www.zghlxwxcb.cn/news/detail-628301.html
recordOrStop() {
if (this.isRecord) {
document.getElementById("record_btu").innerText = "視頻錄制";
document.getElementById("record_btu").style.backgroundColor =
"rgb(22, 204, 195)";
document.getElementById("record_btu").style.color = "black";
this.stop();
} else {
this.getCamera(); //打開攝像頭
this.mutePage(); //將頁面靜音
document.getElementById("record_btu").innerText = "結(jié)束錄制";
document.getElementById("record_btu").style.backgroundColor =
"rgb(255 99 71)";
document.getElementById("record_btu").style.color = "white";
setTimeout(() => {
// 方法區(qū)
this.record();
}, 2000); //設(shè)置錄制延遲 2s
}
完整JS代碼文章來源地址http://www.zghlxwxcb.cn/news/detail-628301.html
<script>
export default {
data() {
return {
mediaStreamTrack: {}, // 退出時(shí)關(guān)閉攝像頭
video_stream: "", // 視頻stream
recordedBlobs: [], // 視頻音頻 blobs
isRecord: false, // 視頻是否正在錄制
};
},
mounted() {
// 進(jìn)入頁面 調(diào)用攝像頭
// this.getCamera();
// this.mutePage();
window.onerror = () => {
// mes 報(bào)錯(cuò)信息, source 那個(gè)文件, line 第幾行, column 第幾列, error 錯(cuò)誤的對(duì)象
this.$notify({
title: "錄制失敗",
message: "請點(diǎn)擊結(jié)束錄制并重新嘗試",
type: "warning",
});
};
},
methods: {
// Mute a singular HTML5 element
muteMe(elem) {
elem.muted = true;
elem.pause();
},
// Try to mute all video and audio elements on the page
mutePage() {
document
.querySelectorAll("video, audio")
.forEach((elem) => this.muteMe(elem));
},
// 調(diào)用打開攝像頭功能
getCamera() {
// 舊版本瀏覽器可能根本不支持mediaDevices,我們首先設(shè)置一個(gè)空對(duì)象
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
navigator.mediaDevices
.getUserMedia({
video: true,
audio: true,
})
.then((stream) => {
// 攝像頭開啟成功
this.mediaStreamTrack =
typeof stream.stop === "function" ? stream : stream.getTracks()[0];
this.video_stream = stream;
this.$refs.video.srcObject = stream;
this.$refs.video.play();
})
.catch((err) => {
console.log(err);
});
},
// 錄制或暫停
recordOrStop() {
if (this.isRecord) {
document.getElementById("record_btu").innerText = "視頻錄制";
document.getElementById("record_btu").style.backgroundColor =
"rgb(22, 204, 195)";
document.getElementById("record_btu").style.color = "black";
this.stop();
} else {
this.getCamera();
this.mutePage();
document.getElementById("record_btu").innerText = "結(jié)束錄制";
document.getElementById("record_btu").style.backgroundColor =
"rgb(255 99 71)";
document.getElementById("record_btu").style.color = "white";
setTimeout(() => {
// 方法區(qū)
this.record();
}, 2000);
}
},
// 視頻錄制
record() {
console.log("record");
this.isRecord = !this.isRecord;
let mediaRecorder;
let options;
this.recordedBlobs = [];
if (typeof MediaRecorder.isTypeSupported === "function") {
// 根據(jù)瀏覽器來設(shè)置編碼參數(shù)
if (MediaRecorder.isTypeSupported("video/webm;codecs=vp9")) {
options = {
MimeType: "video/webm;codecs=h264",
};
} else if (MediaRecorder.isTypeSupported("video/webm;codecs=h264")) {
options = {
MimeType: "video/webm;codecs=h264",
};
} else if (MediaRecorder.isTypeSupported("video/webm;codecs=vp8")) {
options = {
MimeType: "video/webm;codecs=vp8",
};
}
mediaRecorder = new MediaRecorder(this.video_stream, options);
} else {
// console.log('isTypeSupported is not supported, using default codecs for browser');
console.log("當(dāng)前不支持isTypeSupported,使用瀏覽器的默認(rèn)編解碼器");
mediaRecorder = new MediaRecorder(this.video_stream);
}
mediaRecorder.start();
// 視頻錄制監(jiān)聽事件
mediaRecorder.ondataavailable = (e) => {
console.log(e);
// 錄制的視頻數(shù)據(jù)有效
if (e.data && e.data.size > 0) {
this.recordedBlobs.push(e.data);
}
};
// 停止錄像后增加下載視頻功能,將視頻流轉(zhuǎn)為mp4格式
mediaRecorder.onstop = () => {
const blob = new Blob(this.recordedBlobs, { type: "video/mp4" });
this.recordedBlobs = [];
// 將視頻鏈接轉(zhuǎn)換完可以用于在瀏覽器上預(yù)覽的本地視頻
const videoUrl = window.URL.createObjectURL(blob);
// 設(shè)置下載鏈接
document.getElementById("downLoadLink").href = videoUrl;
// 設(shè)置下載mp4格式視頻
document.getElementById("downLoadLink").download = "media.mp4";
document.getElementById("downLoadLink").innerHTML =
"DownLoad video file";
// 生成隨機(jī)數(shù)字
const rand = Math.floor(Math.random() * 1000000);
// 生成視頻名
const name = `video${rand}.mp4`;
// setAttribute() 方法添加指定的屬性,并為其賦指定的值
document.getElementById("downLoadLink").setAttribute("download", name);
document.getElementById("downLoadLink").setAttribute("name", name);
console.log(name);
const filename = "C:\\Users\\k1114\\Downloads\\" + name;
console.log(filename);
// 0.5s后自動(dòng)下載視頻
setTimeout(() => {
document.getElementById("downLoadLink").click();
}, 500);
//上傳至后端
let fileobj = new File(
[blob],
name,
{
type: "video/mp4",
});
let formData = new FormData(); //創(chuàng)建form對(duì)象
formData.append("filename", fileobj); //通過append向form對(duì)象添加數(shù)據(jù)
console.log(formData.get("filename")); //FormData私有類對(duì)象,訪問不到,可以通過get判斷值是否傳進(jìn)去
//上傳
this.$axios
.post("http://127.0.0.1:8000/runcase/start", formData, {
headers: { "Content-Type": "none" },
}) //請求頭要為表單
.then((response) => {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
};
},
// 停止錄制
stop() {
this.isRecord = !this.isRecord;
if (!this.$refs.video.srcObject) return;
const stream = this.$refs.video.srcObject;
const tracks = stream.getTracks();
// 關(guān)閉攝像頭和音頻
tracks.forEach((track) => {
track.stop();
});
},
},
};
</script>
CSS部分
<style>
.CallCamera {
flex-wrap: wrap;
align-content: space-between;
}
.video-box {
width: 800px;
height: 600px;
text-align: center;
}
video {
width: 740px;
height: 555px;
background-color: black;
object-fit: fill;
margin: auto;
/*position: relative;*/
/*position: absolute;*/
}
canvas {
width: 100%;
height: 100%;
}
.CallCamera button {
width: 100px;
height: 40px;
position: relative;
margin: auto;
border-radius: 15px;
background-color: rgb(22, 204, 195);
cursor: pointer;
transition: 0.5s;
top: 20px;
}
.CallCamera button:hover {
font-size: 15px;
background-color: rgb(255 99 71);
color: white;
}
.img_bg_camera img {
width: 300px;
height: 200px;
}
</style>
到了這里,關(guān)于VUE+Django實(shí)現(xiàn)前端開啟攝像頭錄制存儲(chǔ)視頻并直接上傳后端的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!