VUE调用摄像头,拍摄视频上传demo
前端代码
<template>
<div id="videoDemo">
<div>
<el-form ref="uploadForm" :model="uploadForm" label-width="120px">
<el-row>
<el-form-item label="单号编码" prop="code">
<el-input v-model="uploadForm.code" placeholder="单号" @keyup.enter.native="checkOrder" />
</el-form-item>
</el-row>
<el-row>
<button @click="startRecording" class="large-button" :disabled="isRecording">开始录制</button>
<button @click="stopRecording" class="large-button" :disabled="!isRecording">停止录制</button>
</el-row>
</el-form>
</div>
<div style="display: flex;align-items: stretch; ">
<video id="video" ref="video" width="1000" height="750" autoplay></video>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
mediaRecorder: null,
isRecording: false,
stream: null,
recordedBlobs: [],
uploadInterval: null,
uploadForm: {
code: undefined,
lastCode: undefined,
},
isUploading: false, // 新增一个标志,用于判断是否正在上传
};
},
methods: {
checkOrder() {
console.log('我进来了checkOrder');
// 当我文本框回车的时候,校验单号是否和上一次单号一致,不一致的话,就停止录制并上传,继续开启下一次的录制
if (this.uploadForm.code != undefined && this.uploadForm.code.trim().length > 0) {
if (this.uploadForm.lastCode == undefined) {
this.startRecording();
} else {
if (this.uploadForm.code != this.uploadForm.lastCode) {
this.stopRecording(); // 在这里调用上传视频的方法,确保上传的是完整的视频文件
setTimeout(() => {
this.startRecording();
}, 500); // 延迟500毫秒后再开始新的录制,确保状态更新
}
}
console.log(this.uploadForm.lastCode);
this.uploadForm.lastCode = this.uploadForm.code;
}
},
handleMediaRecorderStop() {
// 最后一次上传时调用
// 确保不在上传过程中才进行上传
if (!this.isUploading) {
this.isUploading = true; // 标记为正在上传
this.uploadVideo();
}
},
startRecording() {
console.log('我进来了startRecording');
if (!this.stream) {
this.askForPermission();
return;
}
this.setupMediaRecorder();
this.isRecording = true;
this.setupUploadInterval();
console.log('start recording 执行结束');
},
setupMediaRecorder() {
this.recordedBlobs = [];
if (this.mediaRecorder && this.mediaRecorder.state === "recording") {
// 如果当前已经在录制中,则不再尝试开始新的录制
return;
}
this.mediaRecorder = new MediaRecorder(this.stream, { mimeType: 'video/webm' });
this.mediaRecorder.ondataavailable = event => {
if (event.data && event.data.size > 0) {
this.recordedBlobs.push(event.data);
}
};
// this.mediaRecorder.onstop = this.handleMediaRecorderStop;
this.mediaRecorder.onstop = () => {
this.handleMediaRecorderStop();
if (this.isRecording) {
// 如果仍在录制状态,稍后尝试开始新的录制段
setTimeout(() => {
this.setupMediaRecorder();
if (this.mediaRecorder.state === "inactive") {
this.mediaRecorder.start(10);// 每秒生成一个chunk
}
}, 100); // 延迟100毫秒后尝试开始新的录制,确保状态更新
}
};
if (this.mediaRecorder.state === "inactive") {
this.mediaRecorder.start(10); // 开始新的录制
}
},
stopRecording() {
console.log('我进来了stop');
if (this.mediaRecorder && this.mediaRecorder.state === "recording") {
this.mediaRecorder.stop();
this.isRecording = false;
clearInterval(this.uploadInterval);
this.uploadInterval = null;
// if (!this.isUploading) { // 检查是否已经在上传
// this.isUploading = true; // 设置正在上传标志
// setTimeout(() => {
// this.uploadVideo(); // 在延迟后上传视频
// }, 200); // 延迟200毫秒
// }
}
// setTimeout(async () => { // 添加延迟以确保视频文件完整生成
// }, 1000); // 延迟1秒
console.log('stop执行结束');
},
getFileName() {
const timestamp = new Date();
let filename = '';
const dd = `${timestamp.getFullYear()}-${timestamp.getMonth() + 1}-${timestamp.getDate()}-${timestamp.getHours()}-${timestamp.getMinutes()}-${timestamp.getSeconds()}`;
if (this.uploadForm.code != undefined && this.uploadForm.code.trim().length > 0) {
filename = this.uploadForm.code + '_' + dd + '.webm';
} else {
filename = dd + '.webm';
}
return filename;
},
uploadVideo() {
const blob = new Blob(this.recordedBlobs, { type: 'video/webm' });
const formData = new FormData();
let filename = this.getFileName();
formData.append('file', blob, filename);
axios.post('http://localhost:8421/upload_one', formData)
.then(response => {
console.log('Upload success:', response);
})
.catch(error => {
console.error('Upload error:', error);
}).finally(() => {
this.isUploading = false; // 上传完成后,无论成功还是失败,都重置上传状态
});;
},
askForPermission() {
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
this.stream = stream;
this.$refs.video.srcObject = stream;
// 只有在成功获取流后才能开始录制
// this.startRecording();
})
.catch(error => {
console.error('Media access error:', error);
});
},
setupUploadInterval() {
clearInterval(this.uploadInterval); // 清除之前的定时器
this.uploadInterval = setInterval(() => {
if (this.mediaRecorder && this.mediaRecorder.state === "recording" ) {
this.mediaRecorder.stop(); // 停止当前录制,触发onstop事件上传当前录制的视频
}
}, 30000); // 每隔30秒上传一次
},
},
mounted() {
this.askForPermission();
},
};
</script>
<style>
.large-button {
padding: 25px 50px;
/* 调整内边距来增加按钮的大小 */
font-size: 40px;
/* 调整字体大小 */
}
</style>
后端代码使用python接收上传文件
# -*- ecoding: utf-8 -*-
# @ModuleName: test002
# 当你要使用这份文件时,
# 代表你已经完全理解文件内容的含义,
# 并愿意为使用此文件产生的一切后果,付全部责任
# @Funcation:
# @Author: darling
# @Time: 2024-06-17 14:21
from flask_cors import CORS
from flask import Flask, request
import os
from loguru import logger
app = Flask(__name__)
CORS(app)
@app.route('/upload_one', methods=['POST'])
def upload_one():
'''
前端上传,批量选择后,前端循环上传,后端单个接收
:return:
'''
file = request.files['file'] # 获取上传的文件
if file:
logger.info('获取到文件{}', file.filename)
file.save(os.path.join('files', file.filename)) # 保存文件到当前目录
logger.info('保存结束{}', file.filename)
return '文件上传成功!'
else:
return '文件上传失败!'
@app.route('/upload_batch', methods=['POST'])
def upload_batch():
'''
前端上传,批量选择后一次性上传,后端循环保存
:return:
'''
files = request.files.getlist('files') # 获取上传的文件列表
if files:
for file in files:
logger.info('获取到文件{}', file.filename)
file.save(os.path.join('files', file.filename)) # 保存文件到当前目录
logger.info('保存结束{}', file.filename)
return '文件上传成功!'
else:
return '文件上传失败!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8421)
惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。
一代天骄,成吉思汗,只识弯弓射大雕。
俱往矣,数风流人物,还看今朝