mrr.sj.front/components/upload-video/upload-video.vue

421 lines
11 KiB
Vue
Raw Normal View History

2026-03-24 11:45:13 +08:00
<template>
<!-- 上传短视频 -->
<view class="upload-content">
<view class="progress-box" v-if="showProgress">
<text class="progress-text">{{progress}}%</text>
<text class="progress-lable">上传中</text>
<progress :percent="progress"></progress>
</view>
<view v-if="video" class="video-preview">
<video
:src="'https://app.mrrweb.com.cn' + video"
ref="videoPlayer"
v-if="video"
class="preview-video"
:controls="false"
object-fit="cover"
:autoplay="true"
:muted="true"
:initial-time="1"
:show-center-play-btn="false"
:show-loading="false"
:enable-progress-gesture="false"
:show-fullscreen-btn="false"
:show-play-btn="false"
@play="onPlay"
></video>
<!-- 备用提示 -->
<view v-if="showFallback" class="video-fallback">
<text>视频加载失败请尝试刷新或更换视频源</text>
</view>
<view class="delete-btn" @click="deleteVideo">×</view>
</view>
<view v-else class="upload-btn" @click="chooseVideo">
<text class="plus-icon">+</text>
</view>
</view>
</template>
<script>
import request from '../../utils/request';
import sparkMD5 from 'spark-md5'; // 引入MD5计算库
export default {
name:"upload-video",
props: {
video: {
type: String,
default: ''
},
videoNum: {
type: Number,
default: 1
},
},
data() {
return {
autoplay: false,
showProgress: false,
progress: 0,
showFallback: false,
finalVideoUrl: '',
platformAutoplay: true,
autoplayMuted: true,
};
},
// watch: {
// video: {
// handler(newValue) {
// this.video = newValue
// },
// deep: true
// }
// },
methods: {
// 获取文件 (兼容各平台)
getFile() {
return new Promise((resolve, reject) => {
// #ifdef H5
const input = document.createElement('input')
input.type = 'file'
input.accept = this.accept
input.onchange = (e) => {
// resolve(e.target.files[0])
resolve(e)
}
input.click()
// #endif
// #ifndef H5
uni.chooseVideo({
count: 1,
sourceType: ['album', 'camera'],
compressed: false,
maxDuration: 180,
camera: 'back',
success: (res) => {
// resolve(res.tempFiles[0])
resolve(res)
},
fail: (err) => {
reject(err)
}
})
// #endif
})
},
// 上传视频区域
async chooseVideo() {
// 选择文件(添加类型校验)
// const res = await uni.chooseVideo({
// sourceType: ['album', 'camera'],
// compressed: false,
// maxDuration: 180,
// camera: 'back'
// });
const res = await this.getFile();
console.log(res,';;;;;;;;;;;;;;;;;;;;;;;;;;;')
this.showProgress = true;
let size = res.size;
if(size > 5*1024*1024) {
// 统一获取文件列表
const files = res.tempFile || res;
const file = res.tempFile || res;
console.log('file',file)
// if (!files || files.length === 0) {
// uni.showToast({ title: '文件选择取消', icon: 'none' });
// return;
// }
if (!file) {
uni.showToast({ title: '视频选择取消', icon: 'none' });
return;
}
progress: (res) => {
console.log(`分片${index}进度: ${res.progress}%`);
}
// 通过 tempFiles 获取文件UniApp规范
// const file = files[0];
// 计算文件MD5优化大文件计算
const md5 = await this.calculateFileMD5(file);
// 分片大小2MB
const chunkSize = 1 * 1024 * 1024;
const totalChunks = Math.ceil(file.size / chunkSize);
// 分片上传
for (let index = 0; index < totalChunks; index++) {
const start = index * chunkSize;
const end = Math.min(file.size, start + chunkSize);
// #ifdef H5
// 浏览器环境使用标准的 slice 方法
const chunk = file.slice(start, end);
// 上传分片
await this.uploadChunk(chunk, index, totalChunks, md5);
// #endif
// #ifndef H5
// 非浏览器环境使用 Uniapp API
const fs = uni.getFileSystemManager();
const arrayBuffer = fs.readFileSync({
filePath: file,
position: start,
length: end - start
});
// 上传分片
await this.uploadChunk(arrayBuffer, index, totalChunks, md5);
// #endif
// 上传分片
// await this.uploadChunk(chunk, index, totalChunks, md5);
this.progress = parseInt((index+1)/totalChunks*100)
}
// 合并请求
await this.mergeChunks(md5, file.name);
}else {
let item = {
filePath: res.tempFilePath,
type: 'video'
}
this.uploadVideo(item)
}
},
uploadVideo(item) {
request.uploadVideo('/user/uploadvideo',item.filePath,'video',{type: item.type}).then(res=>{
this.$emit('addVideo',res.path)
// this.initVideo();
this.showProgress = false;
})
},
// 计算文件MD5分块计算
calculateFileMD5(file) {
return new Promise((resolve) => {
const chunkSize = 1 * 1024 * 1024;
const chunks = Math.ceil(file.size / chunkSize);
const spark = new sparkMD5.ArrayBuffer();
let currentChunk = 0;
const loadNext = () => {
const start = currentChunk * chunkSize;
const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
// #ifdef H5
const reader = new FileReader();
// #endif
// #ifndef H5
const reader = uni.getfilesystem;
// #endif
reader.onload = (e) => {
spark.append(e.target.result);
currentChunk++;
if (currentChunk < chunks) {
loadNext();
} else {
resolve(spark.end());
}
};
reader.readAsArrayBuffer(file.slice(start, end));
};
loadNext();
});
},
async uploadChunk(chunk, index, totalChunks, md5) {
let that = this;
try {
// 获取ArrayBuffer
const arrayBuffer = await new Promise((resolve) => {
// #ifdef H5
const reader = new FileReader();
// #endif
// #ifndef H5
const reader = uni.getfilesystem;
// #endif
reader.onload = () => resolve(reader.result);
reader.readAsArrayBuffer(chunk);
});
// 平台差异化处理
let tempFilePath = '';
// 微信小程序/App端写入临时文件
// #ifdef MP-WEIXIN || APP-PLUS
const fs = uni.getFileSystemManager();
tempFilePath = `${getTempDir()}${md5}_${Date.now()}_${index}.tmp`;
await new Promise((resolve, reject) => {
fs.writeFile({
filePath: tempFilePath,
data: arrayBuffer,
encoding: 'binary',
success: resolve,
fail: (err) => reject(`文件写入失败: ${JSON.stringify(err)}`)
});
});
// #endif
// H5端特殊处理
// #ifdef H5
const blob = new Blob([arrayBuffer], { type: chunk.type });
const file = new File([blob], `${md5}_${index}.bin`, {
type: chunk.type,
lastModified: Date.now()
});
tempFilePath = file;
// #endif
const uploadTask = await request.uploadTask('/user/uploadvideoother',tempFilePath,'file',{chunkIndex: index,totalChunks: totalChunks,md5: md5});
// 上传后清理临时文件非H5
// #ifdef MP-WEIXIN || APP-PLUS
fs.unlink({ filePath: tempFilePath });
// #endif
return uploadTask;
} catch (error) {
console.error('分片上传失败:', error);
throw new Error(`分片${index}上传失败: ${error.message}`);
}
},
// 合并请求
mergeChunks(md5, fileName) {
let that = this;
request.post('/user/uploadvideomerge',{md5: md5,filename: fileName}).then(res=>{
that.$emit('addVideo',res.path)
// that.initVideo();
that.showProgress = false;
})
},
// 上传视频结束区域
// 删除视频
deleteVideo() {
this.$emit('deleteVideo')
},
initVideo() {
// 平台自动播放处理
this.platformAutoplay = this.autoplay;
// iOS自动播放必须静音
// #ifdef APP-PLUS
if (plus.os.name === 'iOS' && this.autoplay) {
this.autoplayMuted = true;
}
// #endif
// 路径处理
this.processVideoUrl();
},
processVideoUrl() {
if (!this.video) return;
// 处理APP本地路径
if (this.video.startsWith('file://') || this.video.startsWith('/storage')) {
// #ifdef APP-PLUS
this.finalVideoUrl = plus.io.convertLocalFileSystemURL(this.video);
// #endif
// #ifdef MP-WEIXIN || H5
this.finalVideoUrl = this.video;
// #endif
} else {
this.finalVideoUrl = this.video;
}
},
onPlay() {
console.log('视频开始播放');
this.platformAutoplay = true;
this.showFallback = false;
},
onError(e) {
console.error('视频播放错误:', e);
this.showFallback = true;
// 尝试备用方案
this.tryFallbackSource();
},
tryFallbackSource() {
// 可以在这里实现备用视频源逻辑
}
}
}
</script>
<style>
.upload-content {
display: flex;
flex-wrap: wrap;
position: relative;
}
.progress-box {
width: 204rpx;
height: 204rpx;
background-color: rgba(0 ,0 ,0 , 0.5);
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 99;
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
}
.progress-text {
font-size: 42rpx;
color: #FFFFFF;
font-weight: 500;
}
.progress-lable {
font-size: 28rpx;
color: #FFFFFF;
}
.progress-box progress {
/* width: 204rpx; */
position: absolute;
left: 0;
right: 0;
bottom: 0;
}
.video-preview,
.upload-btn {
width: 200rpx;
height: 200rpx;
margin-right: 10rpx;
margin-bottom: 20rpx;
position: relative;
}
.preview-video {
width: 200rpx;
height: 200rpx;
border-radius: 8rpx;
}
.upload-btn {
border: 2rpx dashed #DDDDDD;
border-radius: 8rpx;
display: flex;
justify-content: center;
align-items: center;
}
.plus-icon {
font-size: 60rpx;
color: #999999;
}
.delete-btn {
position: absolute;
top: -20rpx;
right: -20rpx;
width: 40rpx;
height: 40rpx;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
color: #FFFFFF;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
}
</style>