uniapp -- 实现微信小程序、app、H5端视频上传

布局及实现代码:

html 复制代码
<template>
	<view class="flex flex-column p-4 grid-gap-4">
		<view class="flex flex-column grid-gap-4 bg-white p-4 rounded-4">
			<view class="font-weight-600">视频名称</view>
			<input type="text" v-model="form.name" class="bg-placeholder rounded-4 p-4" placeholder="请输入视频名称..."
				maxlength="50">
		</view>
		<view class="flex flex-column grid-gap-4 bg-white p-4 rounded-4">
			<view class="bg-placeholder rounded-4 p-4 flex flex-center" @tap="chooseVideo">
				<video :src="tempFilePath" class="rounded-4" style="width: 100%;height: calc(200px * 100vw / 375px);"
					:direction="0" v-if="form.video"></video>
				<view class="flex flex-column grid-gap-2 flex-center py-10 text-center" v-else-if="!showVideo">
					<view v-if="uploadState" class="flex flex-column w-100 grid-gap-2">
						<view class="bg-grey w-100 rounded-round" style="height: 10px;">
							<view class="bg-success rounded-round" style="height: 10px;transition: width .3s;"
								:style="{ width: progress + '%' }"></view>
						</view>
						<text class="text-grey h6 text-center">{{ progress }}%</text>
					</view>
					<image src="@/static/uploads-plus.png" mode="scaleToFill" style="width: 50px;height: 50px;" v-else />
					<text class="text-text">上传视频</text>
					<!-- #ifdef MP-WEIXIN -->
					<text class="text-grey h10">或者</text>
					<view class="flex flex-center" @tap.stop="chooseMessageFile">
						<text class="text-success h10">从微信中选择</text>
						<uni-icons type="right" color="var(--xl-success)" size="12"></uni-icons>
					</view>
					<!-- #endif -->
					<text class="text-grey h10">上传单人视频(建议用不说话视频)</text>
					<text class="text-grey h10">注意每一帧都要有露脸,侧脸幅度不可过大</text>
				</view>
				<video :src="tempFilePath" class="rounded-4" style="width: 100%;height: calc(200px * 100vw / 375px);"
					:direction="0" v-else-if="tempFilePath && showVideo" @loadedmetadata="loadedmetadata"></video>
			</view>
			<view class="font-weight-600">视频要求:</view>
			<view class="grid-columns-4 grid-gap-4">
				<view class="grid-column-2 flex">
					<text class="text-grey h10">视频方向:</text>
					<text class="text-text h10">横向或纵向</text>
				</view>
				<view class="grid-column-2 flex">
					<text class="text-grey h10">分辨率:</text>
					<text class="text-text h10">360p~4K</text>
				</view>
				<view class="grid-column-2 flex">
					<text class="text-grey h10">文件格式:</text>
					<text class="text-text h10">mp4,mov</text>
				</view>
				<view class="grid-column-2 flex">
					<text class="text-grey h10">文件大小:</text>
					<text class="text-text h10">不超过300M</text>
				</view>
				<view class="grid-column-2 flex">
					<text class="text-grey h10">视频时长:</text>
					<text class="text-text h10">10秒~5分钟</text>
				</view>
			</view>
		</view>
	</view>
</template>
<script lang="ts" setup>
import { ref, nextTick } from 'vue';
import { $message, $http, $page } from "@/utils";
import { onLoad, onShow } from "@dcloudio/uni-app";

const form = ref({
	name: '',
	video: ''
});
const tempFilePath = ref('');
let fileObj = {
	name: '',
	type: '',
	size: ''
}

const loadedmetadata = (e: any) => {
	showVideo.value = false;
	console.log('选择的文件:', e);

	if (e.detail?.duration < 5) {
		uni.showModal({
			title: '提示',
			content: '视频时长不得少于5秒',
			showCancel: false,
			confirmText: '我知道了'
		});
		form.value.video = '';
		tempFilePath.value = '';
	} else if (!form.value.video) {
		uploadFile();
	}
}
const submit = () => {
	if (!form.value.name) {
		$message.error('请输入数字人分身名称');
		return;
	}
	console.log('form:', form)
	if (!form.value.video) {
		$message.error('请上传视频');
		return;

		console.log('数据:', form.value);

	}
}
const progress = ref(0);
const uploadState = ref(false);
const showVideo = ref(false);
// 其他端选择视频
const chooseVideo = () => {
	uni.chooseVideo({
		compressed: true, // WEBCONFIG.value?.upload_video_compressed ? true : false,
		camera: 'front',
		success: (res: any) => {
			console.log('success:', res)
			// 保存文件信息
			fileObj = res.tempFile
			tempFilePath.value = res.tempFilePath;
			showVideo.value = true;
			form.value.video = '';
			// APP不支持@loadedmetadata
			// #ifdef APP-PLUS
			if (showVideo.value && tempFilePath.value) {
				const val = {
					detail: {
						duration: res.duration
					}
				}
				loadedmetadata(val)
			}
			// #endif
			// form.value.video = res.tempFilePath;
		}
	})
}

// 文件上传
const uploadFile = () => {
	uni.showLoading({
		title: '上传中...',
		mask: true
	})
	progress.value = 0;
	uploadState.value = true;
	// 微信小程序上传视频
	const task = $http.upload('Generation/uploadVideo', {
		filePath: tempFilePath.value,
		name: 'file',
		success: (res: any) => {
			uni.hideLoading();
			uploadState.value = false;
			if (res.statusCode === 200) {
				try {
					const data = JSON.parse(res.data);
					if (data.code === $http.ResponseCode.SUCCESS) {
						form.value.video = data.data.url;
						nextTick(() => {
							showVideo.value = true;
						})
					} else {
						$message.error(data.msg);
					}
					return;
				} catch (e) {
				}
			}
			$message.error('上传失败:' + JSON.stringify(res));
		},
		fail: (err: any) => {
			uni.hideLoading();
			uploadState.value = false;
			$message.error('上传失败:' + JSON.stringify(err));
		},
	}, true);
	task.onProgressUpdate((res: any) => {
		progress.value = res.progress;
		console.log('上传进度' + res.progress);
		console.log('已经上传的数据长度' + res.totalBytesSent);
		console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend);
	});
}


// 微信小程序选择视频
const chooseMessageFile = () => {
	wx.chooseMessageFile({
		count: 1,
		type: 'file',
		extension: ['mp4', 'mov'],
		success: (res: any) => {
			tempFilePath.value = res.tempFiles[0].path;
			showVideo.value = true;
			form.value.video = '';
		}
	})
}

onShow(() => {
})

</script>
<style lang="scss" scoped>
.generation-example-item {
	position: relative;
	--w: 34.6666666667vw;
	--h: 22.6666666667vw;
	height: var(--h);
	width: var(--w);
	flex-shrink: 0;

	.videocam {
		position: absolute;
		top: 50%;
		right: 50%;
		transform: translate(50%, -50%);

		.play-icon-image {
			width: 30px;
			height: 30px;
		}
	}
}

.footer-placeholder {
	--footer-height: 115px;
	height: var(--footer-height);
	box-sizing: content-box;

	.footer {
		background: radial-gradient(60% 80% at 50% 100%,
				rgba(79, 255, 249, 0.2) 0%,
				rgba(181, 226, 255, 0) 80%,
				rgba(255, 238, 240, 0) 100%),
			radial-gradient(20% 90% at 50% 90%,
				rgba(141, 255, 164, 0.3) 0%,
				rgba(255, 255, 255, 0) 100%), var(--xl-bg);
		height: var(--footer-height);
		box-sizing: content-box;
		position: fixed;
		bottom: 0;
		left: 0;
		right: 0;
		z-index: 100;
	}
}
</style>

实现页面效果:



文件太大时上传速度很慢。

优化:实现TOS 预签名URL上传

H5和APP端可使用

js 复制代码
const uploadFile = () => {
	uni.showLoading({
		title: '上传中...',
		mask: true
	})
	progress.value = 0;
	uploadState.value = true;
	console.log('是否是微信小程序:', isWeixin.value)
	if (isWeixin.value) {
		// 微信小程序上传视频
		const task = $http.upload('Generation/uploadVideo', {
			filePath: tempFilePath.value,
			name: 'file',
			success: (res: any) => {
				uni.hideLoading();
				uploadState.value = false;
				if (res.statusCode === 200) {
					try {
						const data = JSON.parse(res.data);
						if (data.code === $http.ResponseCode.SUCCESS) {
							form.value.video = data.data.url;
							nextTick(() => {
								showVideo.value = true;
							})
						} else {
							$message.error(data.msg);
						}
						return;
					} catch (e) {
					}
				}
				$message.error('上传失败:' + JSON.stringify(res));
			},
			fail: (err: any) => {
				uni.hideLoading();
				uploadState.value = false;
				$message.error('上传失败:' + JSON.stringify(err));
			},
		}, true);
		task.onProgressUpdate((res: any) => {
			progress.value = res.progress;
			console.log('上传进度' + res.progress);
			console.log('已经上传的数据长度' + res.totalBytesSent);
			console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend);
		});
	} else {
		// H5和APP视频上传
		// 获取预签名URL:uploadTos
		console.log('tempFilePath', tempFilePath.value)
		let fileName = fileObj.name;
		let fileType = fileObj.type;
		let fileSize = fileObj.size;
		$http.post('Generation/uploadTos', {
			data: JSON.stringify({
				type: fileType,
				name: fileName,
				size: fileSize,
			})
		}).then((res: any) => {
			// console.log('获取预签名URL:', res);
			if (res.code == 200) {
				let url = res.data;
				axios({
					url: url,
					method: "put",
					data: fileObj,
					contentType: false,
					processData: false,

					//原生获取上传进度的事件
					onUploadProgress: (progressEvent: any) => {
						let processTwo = ((progressEvent.loaded / progressEvent.total) * 100) | 0;
						progress.value = processTwo * 1;
						let progressTex = `上传进度:${processTwo}%`;
						console.log(progressTex);

					},
				}).then((res2: any) => {
					if (res2.status !== 200) {
						uni.hideLoading();
						uploadState.value = false;
						$message.error('上传失败');
						return
					}
					// 截取视频地址:
					// let responseURL = res2.request.responseURL.split('?')[0]
					let responseURL = res2.request.responseURL.split('?')[0]
					// 视频上传完成回调后端接口---判断上传成功or失败
					$http.post('Generation/uploadAiBack', {
						data: { url: responseURL }
					}).then((res3: any) => {
						// console.log('视频上传完成回调', res3);
						// 视频上传成功
						if (res3.code === $http.ResponseCode.SUCCESS) {
							form.value.video = url;
							uni.hideLoading();
							uploadState.value = false;
							nextTick(() => {
								showVideo.value = true;
							})
						} else {
							// 视频上传失败
							uni.hideLoading();
							uploadState.value = false;
							$message.error(res3.msg);
						}
					})


				})
			} else {
				uni.hideLoading();
				uploadState.value = false;
				$message.error('获取预签名URL失败');
			}
		})
	}
}

http请求封装:这是开发的项目里的文件,非自己封装的,可用自己的请求方法更改

js 复制代码
import { useUserStore } from "@/stores";
import { useStorage } from "./storage";
let baseHOST = ''; // 请求地址

let baseURL = `${baseHOST}api/`;
// #ifdef H5
import system from './h5/system';
export const $system = system;
// #endif
// #ifdef MP-WEIXIN
import system from './mp-weixin/system';
export const $system = system;
// #endif
// #ifdef MP-TOUTIAO
import system from './mp-toutiao/system';
export const $system = system;
// #endif
// #ifdef APP
import system from './app/system';
export const $system = system;
// #endif
if (system.isProd()) {
	baseHOST = `${system.host()}/app/ycDigitalHuman/`;
	baseURL = `${system.host()}/app/ycDigitalHuman/api/`;
}
const RequestHeaders = (options?: any) => {
	const header: any = {};
	const { getToken, hasLogin } = useUserStore();
	if (hasLogin()) {
		header['Authorization'] = getToken();
	}
	if (options && options.header) {
		for (let x in options.header) {
			header[x] = options.header[x]
		}
	}
	const storeage = useStorage();
	const appid = system.appid();
	if (appid) {
		header['Appid'] = appid;
		const icode = storeage.get('ICODE.' + appid);
		if (icode) {
			header['Icode'] = icode;
		}
		if (storeage.get('icode')) {
			header['Icode'] = storeage.get('icode');
		}
		const PUID = storeage.get('PUID.' + appid);
		if (PUID) {
			header['Puid'] = PUID;
		}
	}
	return header;
}
export const $http = {
	ResponseCode: {
		SUCCESS: 200,
		NEED_LOGIN: 12000,
		PAY_SUCCESS: 9000,
		PAY_NOTPAY: 11000,
		NEED_PAY: 60000,
		CLOCK_IN: 70000,
	},
	baseURL,
	baseHOST,
	RequestHeaders,
	image: (path: string) => {
		return `${baseHOST}${path}`;
	},
	get: (url: string, options?: any): Promise<any> => {
		return new Promise((resolve, reject) => {
			uni.request({
				...options,
				timeout: 6000000,
				header: RequestHeaders(options),
				url: baseURL + url,
				success: (res: any) => {
					if (res.statusCode === 200) {
						resolve(res.data);
					} else {
						reject(res);
					}
				},
				fail: (err: any) => {
					reject(err);
				},
				complete: () => { }
			})
		})
	},
	post: (url: string, options: any): Promise<any> => {
		return new Promise((resolve, reject) => {
			uni.request({
				...options,
				timeout: 6000000,
				header: RequestHeaders(options),
				url: baseURL + url,
				method: 'POST',
				success: (res: any) => {
					if (res.statusCode === 200) {
						resolve(res.data);
					} else {
						reject(res);
					}
				},
				fail: (err: any) => {
					reject(err);
				},
				complete: () => { }
			})
		})
	},
	upload: (url: string, options: any, task?: false): Promise<any> | UniNamespace.UploadTask => {
		if (task) {
			return uni.uploadFile({
				...options,
				timeout: 6000000,
				header: RequestHeaders(options),
				url: baseURL + url
			})
		} else {
			return new Promise((resolve, reject) => {
				uni.uploadFile({
					...options,
					timeout: 6000000,
					header: RequestHeaders(options),
					url: baseURL + url,
					success: (res: any) => {
						if (res.statusCode === 200) {
							try {
								const data = JSON.parse(res.data);
								resolve(data);
							} catch (e) {
								reject(res);
							}
						} else {
							reject(res);
						}
					},
					fail: (err: any) => {
						reject(err);
					},
				})
			})
		}
	}
}
相关推荐
小旋风012341 小时前
uniapp自定义头部(兼容微信小程序(胶囊和状态栏),兼容h5)
微信小程序·uni-app·notepad++
带娃的IT创业者3 小时前
《AI大模型应知应会100篇》第39篇:多模态大模型应用:文本、图像和音频的协同处理
人工智能·microsoft·音视频
耶啵奶膘3 小时前
uniapp+vue3表格样式
uni-app
GalenZhang8884 小时前
Java生成微信小程序码及小程序短链接
java·微信小程序·小程序
18538162800余--4 小时前
短视频矩阵系统可视化剪辑功能开发,支持OEM
线性代数·矩阵·音视频
EasyDSS6 小时前
EasyCVR视频汇聚平台助力大型生产监控项目摄像机选型与应用
网络·人工智能·音视频
换日线°7 小时前
微信小程序连续多个特殊字符自动换行解决方法
微信小程序
海拥7 小时前
《用Cursor和AI绘画24小时开发壁纸小程序》详细开发实录
微信小程序·cursor
科技小E8 小时前
EasyRTC嵌入式音视频通信SDK智能安防与监控系统的全方位升级解决方案
大数据·网络·人工智能·音视频