|
@@ -40,7 +40,9 @@
|
|
|
flash="off"
|
|
|
class="user-camera-video"
|
|
|
@error="handleCameraError"
|
|
|
- :record-audio="true">
|
|
|
+ :record-audio="true"
|
|
|
+ frame-size="medium"
|
|
|
+ resolution="medium">
|
|
|
</camera>
|
|
|
<!-- 在H5/App环境中使用video元素 -->
|
|
|
<video v-else
|
|
@@ -276,7 +278,7 @@ export default {
|
|
|
} else {
|
|
|
// H5/App环境使用浏览器的API
|
|
|
try {
|
|
|
- // 请求摄像头和麦克风权限并获取媒体流
|
|
|
+ // 请求摄像头和麦克风权限并获取媒体流,添加更多约束
|
|
|
const constraints = {
|
|
|
audio: {
|
|
|
echoCancellation: true,
|
|
@@ -284,8 +286,9 @@ export default {
|
|
|
autoGainControl: true
|
|
|
},
|
|
|
video: {
|
|
|
- width: { ideal: 640 },
|
|
|
- height: { ideal: 480 },
|
|
|
+ width: { ideal: 640, max: 1280 }, // 控制视频宽度
|
|
|
+ height: { ideal: 480, max: 720 }, // 控制视频高度
|
|
|
+ frameRate: { ideal: 15, max: 24 }, // 控制帧率
|
|
|
facingMode: 'user'
|
|
|
}
|
|
|
};
|
|
@@ -790,9 +793,11 @@ export default {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 开始录像,确保同时录制音频
|
|
|
+ // 开始录像,添加质量控制参数
|
|
|
const options = {
|
|
|
timeout: 60000, // 最长录制60秒
|
|
|
+ quality: 'medium', // 可选值:'low', 'medium', 'high'
|
|
|
+ compressed: true, // 是否压缩视频
|
|
|
success: () => {
|
|
|
console.log('小程序录像开始成功');
|
|
|
},
|
|
@@ -805,7 +810,6 @@ export default {
|
|
|
this.proceedToNextQuestion();
|
|
|
},
|
|
|
timeoutCallback: () => {
|
|
|
- // 录制超时自动停止时的回调
|
|
|
console.log('录制超时自动停止');
|
|
|
this.stopRecordingAnswer();
|
|
|
}
|
|
@@ -951,11 +955,11 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 创建MediaRecorder实例,设置较高的音频比特率
|
|
|
+ // 创建MediaRecorder实例,设置较低的比特率以减小文件大小
|
|
|
const options = {
|
|
|
mimeType: mimeType || '',
|
|
|
- audioBitsPerSecond: 128000,
|
|
|
- videoBitsPerSecond: 2500000
|
|
|
+ audioBitsPerSecond: 64000, // 降低音频比特率
|
|
|
+ videoBitsPerSecond: 1000000, // 降低视频比特率到1Mbps
|
|
|
};
|
|
|
|
|
|
try {
|
|
@@ -978,7 +982,7 @@ export default {
|
|
|
};
|
|
|
|
|
|
// 监听录制停止事件
|
|
|
- this.mediaRecorder.onstop = () => {
|
|
|
+ this.mediaRecorder.onstop = async () => {
|
|
|
console.log('MediaRecorder停止,数据块数量:', this.recordedChunks.length);
|
|
|
|
|
|
if (this.recordedChunks.length === 0) {
|
|
@@ -994,14 +998,36 @@ export default {
|
|
|
// 创建Blob对象
|
|
|
const mimeType = this.mediaRecorder.mimeType || 'video/webm';
|
|
|
const blob = new Blob(this.recordedChunks, { type: mimeType });
|
|
|
- console.log('创建Blob,大小:', blob.size, '类型:', mimeType);
|
|
|
+ console.log('创建Blob,原始大小:', blob.size, '类型:', mimeType);
|
|
|
|
|
|
- // 将Blob转换为File对象
|
|
|
- const fileName = `answer_${this.currentVideoIndex}_${Date.now()}.webm`;
|
|
|
- const file = new File([blob], fileName, { type: mimeType });
|
|
|
+ // 显示压缩中提示
|
|
|
+ uni.showLoading({
|
|
|
+ title: '正在处理视频...',
|
|
|
+ mask: true
|
|
|
+ });
|
|
|
|
|
|
- // 上传文件
|
|
|
- this.uploadRecordedVideo(file);
|
|
|
+ try {
|
|
|
+ // 压缩视频
|
|
|
+ const compressedBlob = await this.compressVideo(blob);
|
|
|
+
|
|
|
+ // 将Blob转换为File对象
|
|
|
+ const fileName = `answer_${this.currentVideoIndex}_${Date.now()}.webm`;
|
|
|
+ const file = new File([compressedBlob], fileName, { type: mimeType });
|
|
|
+
|
|
|
+ // 隐藏压缩提示
|
|
|
+ uni.hideLoading();
|
|
|
+
|
|
|
+ // 上传文件
|
|
|
+ this.uploadRecordedVideo(file);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('视频处理失败:', error);
|
|
|
+ uni.hideLoading();
|
|
|
+
|
|
|
+ // 如果压缩失败,使用原始视频
|
|
|
+ const fileName = `answer_${this.currentVideoIndex}_${Date.now()}.webm`;
|
|
|
+ const file = new File([blob], fileName, { type: mimeType });
|
|
|
+ this.uploadRecordedVideo(file);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
// 监听错误
|
|
@@ -1135,7 +1161,7 @@ export default {
|
|
|
// 使用XMLHttpRequest直接上传
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
xhr.open('POST', `${apiBaseUrl}/api/system/upload/`, true);
|
|
|
-
|
|
|
+ xhr.timeout = 120000; // 30秒超时
|
|
|
xhr.onload = () => {
|
|
|
uni.hideLoading();
|
|
|
if (xhr.status === 200) {
|
|
@@ -1692,6 +1718,92 @@ export default {
|
|
|
icon: 'none'
|
|
|
});
|
|
|
}
|
|
|
+ },
|
|
|
+
|
|
|
+ // 添加一个新方法用于压缩视频
|
|
|
+ async compressVideo(videoBlob) {
|
|
|
+ // 如果视频大小小于某个阈值,直接返回原视频
|
|
|
+ if (videoBlob.size < 5 * 1024 * 1024) { // 小于5MB
|
|
|
+ return videoBlob;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('开始压缩视频,原始大小:', videoBlob.size);
|
|
|
+
|
|
|
+ // 创建一个视频元素来加载视频
|
|
|
+ const videoElement = document.createElement('video');
|
|
|
+ videoElement.muted = true;
|
|
|
+ videoElement.autoplay = false;
|
|
|
+
|
|
|
+ // 创建一个canvas元素用于绘制视频帧
|
|
|
+ const canvas = document.createElement('canvas');
|
|
|
+ const ctx = canvas.getContext('2d');
|
|
|
+
|
|
|
+ // 设置视频源
|
|
|
+ videoElement.src = URL.createObjectURL(videoBlob);
|
|
|
+
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ videoElement.onloadedmetadata = () => {
|
|
|
+ // 设置canvas尺寸为视频的一半
|
|
|
+ const width = Math.floor(videoElement.videoWidth / 2);
|
|
|
+ const height = Math.floor(videoElement.videoHeight / 2);
|
|
|
+ canvas.width = width;
|
|
|
+ canvas.height = height;
|
|
|
+
|
|
|
+ // 创建MediaRecorder来录制canvas
|
|
|
+ const stream = canvas.captureStream(15); // 15fps
|
|
|
+
|
|
|
+ // 如果原视频有音轨,添加到新流中
|
|
|
+ if (videoElement.captureStream) {
|
|
|
+ const originalStream = videoElement.captureStream();
|
|
|
+ const audioTracks = originalStream.getAudioTracks();
|
|
|
+ if (audioTracks.length > 0) {
|
|
|
+ stream.addTrack(audioTracks[0]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置较低的比特率
|
|
|
+ const options = {
|
|
|
+ mimeType: 'video/webm;codecs=vp8,opus',
|
|
|
+ audioBitsPerSecond: 64000,
|
|
|
+ videoBitsPerSecond: 800000 // 800kbps
|
|
|
+ };
|
|
|
+
|
|
|
+ const mediaRecorder = new MediaRecorder(stream, options);
|
|
|
+ const chunks = [];
|
|
|
+
|
|
|
+ mediaRecorder.ondataavailable = (e) => {
|
|
|
+ if (e.data.size > 0) {
|
|
|
+ chunks.push(e.data);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ mediaRecorder.onstop = () => {
|
|
|
+ const compressedBlob = new Blob(chunks, { type: 'video/webm' });
|
|
|
+ console.log('视频压缩完成,压缩后大小:', compressedBlob.size);
|
|
|
+ resolve(compressedBlob);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 开始播放视频并录制
|
|
|
+ videoElement.onplay = () => {
|
|
|
+ mediaRecorder.start(100);
|
|
|
+
|
|
|
+ // 绘制视频帧到canvas
|
|
|
+ const drawFrame = () => {
|
|
|
+ if (videoElement.paused || videoElement.ended) {
|
|
|
+ mediaRecorder.stop();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.drawImage(videoElement, 0, 0, width, height);
|
|
|
+ requestAnimationFrame(drawFrame);
|
|
|
+ };
|
|
|
+
|
|
|
+ drawFrame();
|
|
|
+ };
|
|
|
+
|
|
|
+ videoElement.play();
|
|
|
+ };
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
}
|