<template>
<view class="content">
<view class="header">
<text class="title">视频压缩测试</text>
</view>
<!-- 选择视频按钮 -->
<view class="btn-section">
<button type="primary" @click="selectVideo" :disabled="loading">选择视频</button>
</view>
<!-- 视频信息显示 -->
<view class="video-info" v-if="selectedVideo">
<text class="section-title">原始视频信息</text>
<view class="info-item">
<text class="info-label">路径:</text>
<text class="info-value">{{ selectedVideo.tempFilePath }}</text>
</view>
<view class="info-item" v-if="selectedVideo.size">
<text class="info-label">大小:</text>
<text class="info-value">{{ formatFileSize(selectedVideo.size) }}</text>
</view>
<view class="info-item" v-if="selectedVideo.duration">
<text class="info-label">时长:</text>
<text class="info-value">{{ formatDuration(selectedVideo.duration) }}</text>
</view>
<view class="info-item" v-if="selectedVideo.width && selectedVideo.height">
<text class="info-label">分辨率:</text>
<text class="info-value">{{ selectedVideo.width }}x{{ selectedVideo.height }}</text>
</view>
<view class="info-item" v-if="originalVideoInfo">
<text class="info-label">方向:</text>
<text class="info-value">{{ originalVideoInfo.orientation }}</text>
</view>
</view>
<!-- 压缩选项 -->
<view class="compress-options" v-if="selectedVideo">
<text class="section-title">压缩选项</text>
<view class="option-group">
<text class="option-label">压缩质量:</text>
<radio-group @change="onQualityChange">
<label class="radio-item">
<radio value="low" :checked="compressOptions.quality === 'low'" />
<text>低质量</text>
</label>
<label class="radio-item">
<radio value="medium" :checked="compressOptions.quality === 'medium'" />
<text>中等质量</text>
</label>
<label class="radio-item">
<radio value="high" :checked="compressOptions.quality === 'high'" />
<text>高质量</text>
</label>
</radio-group>
</view>
</view>
<!-- 压缩按钮 -->
<view class="btn-section" v-if="selectedVideo">
<button type="primary" @click="compressVideo" :disabled="loading">
{{ loading ? '压缩中...' : '开始压缩' }}
</button>
</view>
<!-- 加载状态 -->
<view class="loading" v-if="loading">
<text>{{ loadingText }}</text>
</view>
<!-- 压缩结果 -->
<view class="compress-result" v-if="compressResult">
<text class="section-title">压缩结果</text>
<!-- 视频预览对比 -->
<view class="video-preview-section">
<text class="preview-title">视频预览对比</text>
<view class="video-compare">
<!-- 原始视频预览 -->
<view class="video-item">
<text class="video-label">原始视频</text>
<video
:src="selectedVideo.tempFilePath"
class="preview-video"
controls
:show-center-play-btn="true"
:show-fullscreen-btn="true"
:enable-progress-gesture="true"
></video>
<view class="video-info-summary">
<text class="size-text">{{ formatFileSize(compressResult.originalSize) }}</text>
<text class="resolution-text" v-if="originalVideoInfo">
{{ originalVideoInfo.width }}x{{ originalVideoInfo.height }}
</text>
<text class="orientation-text" v-if="originalVideoInfo">
{{ originalVideoInfo.orientation }}
</text>
</view>
</view>
<!-- 压缩后视频预览 -->
<view class="video-item">
<text class="video-label">压缩后视频</text>
<video
:src="compressResult.compressedPath"
class="preview-video"
controls
:show-center-play-btn="true"
:show-fullscreen-btn="true"
:enable-progress-gesture="true"
></video>
<view class="video-info-summary">
<text class="size-text">{{ formatFileSize(compressResult.compressedSize) }}</text>
<text class="resolution-text" v-if="compressResult.compressedVideoInfo">
{{ compressResult.compressedVideoInfo.width }}x{{ compressResult.compressedVideoInfo.height }}
</text>
<text class="orientation-text" v-if="compressResult.compressedVideoInfo">
{{ compressResult.compressedVideoInfo.orientation }}
</text>
<text class="compression-text">
{{ calculateCompressionRatio(compressResult.originalSize, compressResult.compressedSize) }}
</text>
</view>
</view>
</view>
</view>
<!-- 详细信息 -->
<view class="detail-info">
<view class="info-item">
<text class="info-label">压缩后路径:</text>
<text class="info-value">{{ compressResult.compressedPath }}</text>
</view>
<view class="info-item">
<text class="info-label">文件大小:</text>
<text class="info-value">
{{ formatFileSize(compressResult.originalSize) }} →
{{ formatFileSize(compressResult.compressedSize) }}
({{ calculateCompressionRatio(compressResult.originalSize, compressResult.compressedSize) }})
</text>
</view>
<view class="info-item">
<text class="info-label">压缩质量:</text>
<text class="info-value">{{ compressResult.quality }}</text>
</view>
<view class="info-item">
<text class="info-label">耗时:</text>
<text class="info-value">{{ compressResult.duration }}ms</text>
</view>
</view>
</view>
<!-- 错误信息 -->
<view class="error-message" v-if="errorMessage">
<text class="error-text">{{ errorMessage }}</text>
<button type="default" size="mini" @click="clearError">清除</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
selectedVideo: null,
originalVideoInfo: null,
compressResult: null,
loading: false,
loadingText: '',
errorMessage: '',
compressOptions: {
quality: 'high'
}
}
},
onLoad() {
console.log('页面加载完成');
},
methods: {
/**
* 选择视频文件
*/
async selectVideo() {
try {
this.loading = true;
this.loadingText = '选择视频中...';
this.clearPreviousResult();
// 选择视频文件
const result = await this.chooseVideo();
this.selectedVideo = result;
this.loadingText = '获取视频信息中...';
// 获取详细的视频信息
try {
const videoInfo = await this.getVideoInfo(result.tempFilePath);
this.originalVideoInfo = videoInfo;
// 打印详细的视频信息
console.log('=== 选择视频详细信息 ===', {
选择结果: result,
视频信息: videoInfo,
文件路径: result.tempFilePath,
文件大小: result.size ? this.formatFileSize(result.size) : '未知',
视频时长: result.duration ? this.formatDuration(result.duration) : '未知',
视频分辨率: `${result.width || '未知'}x${result.height || '未知'}`,
视频方向: videoInfo.orientation || '未知',
旋转角度: videoInfo.rotate || 0,
视频格式: videoInfo.type || '未知',
帧率: videoInfo.fps || '未知',
选择时间: new Date().toLocaleTimeString()
});
} catch (infoError) {
console.warn('获取详细视频信息失败:', infoError);
this.originalVideoInfo = null;
}
this.loading = false;
this.loadingText = '';
uni.showToast({
title: '视频选择成功',
icon: 'success'
});
} catch (error) {
this.loading = false;
this.loadingText = '';
console.error('选择视频失败:', error);
this.showError('选择视频失败: ' + error.message);
}
},
/**
* 调用 uni.chooseVideo
*/
chooseVideo() {
return new Promise((resolve, reject) => {
uni.chooseVideo({
sourceType: ['camera', 'album'],
maxDuration: 300, // 最大5分钟
compressed: false, // 不自动压缩,我们自己压缩
success: (res) => {
console.log('选择视频成功:', res);
resolve(res);
},
fail: (err) => {
console.error('选择视频失败:', err);
reject(new Error(err.errMsg || '选择视频失败'));
}
});
});
},
/**
* 获取视频详细信息
*/
getVideoInfo(videoPath) {
return new Promise((resolve, reject) => {
uni.getVideoInfo({
src: videoPath,
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(new Error(err.errMsg || '获取视频信息失败'));
}
});
});
},
/**
* 压缩视频
*/
async compressVideo() {
if (!this.selectedVideo) {
this.showError('请先选择视频');
return;
}
try {
this.loading = true;
this.loadingText = '压缩视频中...';
this.clearPreviousResult();
const startTime = Date.now();
const videoPath = this.selectedVideo.tempFilePath;
// 获取原始文件大小
const originalSize = await this.getFileSize(videoPath);
// 打印压缩前信息
console.log('=== 视频压缩前信息 ===', {
视频路径: videoPath,
文件大小: originalSize,
格式化大小: this.formatFileSize(originalSize),
视频信息: this.originalVideoInfo,
压缩质量: this.compressOptions.quality,
开始时间: new Date(startTime).toLocaleTimeString()
});
// 调用压缩
const compressedPath = await this.executeVideoCompress(videoPath, this.compressOptions.quality);
const endTime = Date.now();
const duration = endTime - startTime;
// 获取压缩后文件大小和信息
const compressedSize = await this.getFileSize(compressedPath);
let compressedVideoInfo = null;
try {
compressedVideoInfo = await this.getVideoInfo(compressedPath);
} catch (infoError) {
console.warn('获取压缩后视频信息失败:', infoError);
}
// 打印压缩后信息
console.log('=== 视频压缩后信息 ===', {
压缩后路径: compressedPath,
文件大小: compressedSize,
格式化大小: this.formatFileSize(compressedSize),
视频信息: compressedVideoInfo,
压缩耗时: duration + 'ms',
结束时间: new Date(endTime).toLocaleTimeString()
});
// 打印压缩前后对比
this.logCompressionComparison(originalSize, compressedSize, this.originalVideoInfo, compressedVideoInfo, duration);
// 保存压缩结果
this.compressResult = {
compressedPath,
originalSize,
compressedSize,
compressedVideoInfo,
duration,
quality: this.compressOptions.quality
};
this.loading = false;
this.loadingText = '';
console.log('压缩完成,结果已保存:', this.compressResult);
uni.showToast({
title: '压缩完成',
icon: 'success'
});
} catch (error) {
this.loading = false;
this.loadingText = '';
console.error('压缩视频失败:', error);
this.showError('压缩视频失败: ' + error.message);
}
},
/**
* 执行视频压缩
*/
executeVideoCompress(videoPath, quality) {
return new Promise((resolve, reject) => {
uni.compressVideo({
src: videoPath,
quality: quality,
success: (res) => {
console.log('视频压缩成功:', res);
resolve(res.tempFilePath);
},
fail: (err) => {
console.error('视频压缩失败:', err);
reject(new Error(err.errMsg || '视频压缩失败'));
}
});
});
},
/**
* 获取文件大小
*/
getFileSize(filePath) {
// #ifdef APP-PLUS
return new Promise((resolve, reject) => {
plus.io.resolveLocalFileSystemURL(filePath, (entry) => {
entry.getMetadata((metadata) => {
resolve(metadata.size);
}, reject);
}, reject);
});
// #endif
// #ifdef MP
return new Promise((resolve, reject) => {
uni.getFileInfo({
filePath,
success: (res) => resolve(res.size),
fail: reject
});
});
// #endif
// #ifdef H5
return Promise.resolve(0); // H5端暂时返回0
// #endif
},
/**
* 压缩质量变化
*/
onQualityChange(e) {
this.compressOptions.quality = e.detail.value;
console.log('压缩质量变更为:', e.detail.value);
},
/**
* 格式化文件大小
*/
formatFileSize(bytes) {
if (!bytes || bytes === 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
},
/**
* 格式化时长
*/
formatDuration(seconds) {
if (!seconds) return '0秒';
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
if (minutes > 0) {
return `${minutes}分${remainingSeconds}秒`;
} else {
return `${remainingSeconds}秒`;
}
},
/**
* 计算压缩率
*/
calculateCompressionRatio(originalSize, compressedSize) {
if (!originalSize || !compressedSize) return '未知';
const ratio = ((originalSize - compressedSize) / originalSize * 100);
if (ratio > 0) {
return `压缩${ratio.toFixed(1)}%`;
} else {
const increase = ((compressedSize - originalSize) / originalSize * 100);
return `增大${increase.toFixed(1)}%`;
}
},
/**
* 显示错误信息
*/
showError(message) {
this.errorMessage = message;
uni.showToast({
title: '操作失败',
icon: 'error'
});
},
/**
* 清除错误信息
*/
clearError() {
this.errorMessage = '';
},
/**
* 清除之前的结果
*/
clearPreviousResult() {
this.compressResult = null;
this.errorMessage = '';
},
/**
* 打印压缩前后对比信息
*/
logCompressionComparison(originalSize, compressedSize, originalInfo, compressedInfo, duration) {
console.log('=== 视频压缩前后对比 ===');
// 文件大小对比
const compressionRatio = ((originalSize - compressedSize) / originalSize * 100).toFixed(1);
const sizeChange = compressedSize - originalSize;
const sizeChangePercent = (sizeChange / originalSize * 100).toFixed(1);
console.log('? 文件大小对比:', {
压缩前: {
字节数: originalSize,
格式化: this.formatFileSize(originalSize)
},
压缩后: {
字节数: compressedSize,
格式化: this.formatFileSize(compressedSize)
},
大小变化: {
字节数: sizeChange,
百分比: sizeChangePercent + '%',
压缩率: compressionRatio + '%',
结果: sizeChange > 0 ? '文件增大' : '文件减小'
}
});
// 视频参数对比
if (originalInfo && compressedInfo) {
console.log('? 视频参数对比:', {
压缩前: {
分辨率: `${originalInfo.width}x${originalInfo.height}`,
方向: originalInfo.orientation,
旋转角度: originalInfo.rotate || 0,
时长: originalInfo.duration || 0,
帧率: originalInfo.fps || '未知',
格式: originalInfo.type || '未知'
},
压缩后: {
分辨率: `${compressedInfo.width}x${compressedInfo.height}`,
方向: compressedInfo.orientation,
旋转角度: compressedInfo.rotate || 0,
时长: compressedInfo.duration || 0,
帧率: compressedInfo.fps || '未知',
格式: compressedInfo.type || '未知'
},
参数变化: {
分辨率变化: originalInfo.width !== compressedInfo.width || originalInfo.height !== compressedInfo.height,
方向变化: originalInfo.orientation !== compressedInfo.orientation,
旋转角度变化: (originalInfo.rotate || 0) !== (compressedInfo.rotate || 0)
}
});
// 检查方向问题
if (originalInfo.orientation !== compressedInfo.orientation) {
console.warn('⚠️ 检测到视频方向变化!', {
原始方向: originalInfo.orientation,
压缩后方向: compressedInfo.orientation,
提示: '可能需要进行方向修复'
});
}
// 检查尺寸变化
const widthChange = Math.abs(originalInfo.width - compressedInfo.width);
const heightChange = Math.abs(originalInfo.height - compressedInfo.height);
if (widthChange > 0 || heightChange > 0) {
console.warn('⚠️ 检测到视频尺寸变化!', {
原始尺寸: `${originalInfo.width}x${originalInfo.height}`,
压缩后尺寸: `${compressedInfo.width}x${compressedInfo.height}`,
宽度变化: widthChange,
高度变化: heightChange
});
}
} else {
console.warn('⚠️ 无法获取完整的视频参数信息进行对比');
}
// 性能指标
console.log('⏱️ 性能指标:', {
压缩耗时: duration + 'ms',
压缩速度: (originalSize / 1024 / (duration / 1000)).toFixed(2) + 'KB/s',
压缩质量: this.compressOptions.quality,
数据处理量: this.formatFileSize(originalSize)
});
console.log('=== 压缩对比结束 ===');
}
}
}
</script>
<style>
.content {
padding: 20rpx;
background-color: #f8f8f8;
min-height: 100vh;
}
.header {
text-align: center;
margin-bottom: 40rpx;
}
.title {
font-size: 48rpx;
font-weight: bold;
color: #333;
}
.btn-section {
margin: 30rpx 0;
padding: 0 20rpx;
}
.btn-section button {
width: 100%;
height: 80rpx;
border-radius: 40rpx;
}
.video-info, .compress-options, .compress-result {
background-color: #fff;
margin: 20rpx 0;
padding: 30rpx;
border-radius: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1);
}
.section-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
display: block;
}
.info-item {
margin-bottom: 20rpx;
padding: 15rpx;
background-color: #f9f9f9;
border-radius: 10rpx;
}
.info-label {
font-size: 28rpx;
color: #666;
font-weight: bold;
display: block;
margin-bottom: 8rpx;
}
.info-value {
font-size: 26rpx;
color: #333;
word-break: break-all;
white-space: pre-wrap;
}
.option-group {
margin-bottom: 30rpx;
}
.option-label {
font-size: 32rpx;
color: #333;
font-weight: bold;
display: block;
margin-bottom: 15rpx;
}
radio-group {
display: flex;
flex-direction: column;
gap: 15rpx;
}
.radio-item {
display: flex;
align-items: center;
padding: 15rpx;
background-color: #f9f9f9;
border-radius: 10rpx;
border: 2rpx solid transparent;
transition: all 0.3s;
}
.radio-item:active {
background-color: #e3f2fd;
border-color: #2196f3;
}
.radio-item radio {
margin-right: 15rpx;
}
.radio-item text {
font-size: 28rpx;
color: #333;
}
.loading {
text-align: center;
padding: 40rpx;
background-color: #fff;
margin: 20rpx 0;
border-radius: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1);
}
.loading text {
font-size: 32rpx;
color: #666;
}
.error-message {
background-color: #ffebee;
border: 2rpx solid #f44336;
border-radius: 20rpx;
padding: 30rpx;
margin: 20rpx 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.error-text {
font-size: 28rpx;
color: #c62828;
flex: 1;
margin-right: 20rpx;
}
/* 视频预览相关样式 */
.video-preview-section {
margin-top: 30rpx;
}
.preview-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
display: block;
}
.video-compare {
display: flex;
flex-direction: column;
gap: 30rpx;
}
/* 在大屏幕上使用水平布局 */
@media screen and (min-width: 750rpx) {
.video-compare {
flex-direction: row;
justify-content: space-between;
}
.video-item {
flex: 1;
margin: 0 10rpx;
}
}
.video-item {
background-color: #f9f9f9;
border-radius: 15rpx;
padding: 20rpx;
border: 2rpx solid #e0e0e0;
}
.video-label {
font-size: 28rpx;
font-weight: bold;
color: #555;
margin-bottom: 15rpx;
display: block;
text-align: center;
}
.preview-video {
width: 100%;
height: 400rpx;
border-radius: 10rpx;
background-color: #000;
object-fit: contain;
}
.video-info-summary {
margin-top: 15rpx;
padding: 15rpx;
background-color: #fff;
border-radius: 8rpx;
border: 1rpx solid #e0e0e0;
text-align: center;
}
.size-text {
font-size: 26rpx;
color: #333;
font-weight: bold;
display: block;
margin-bottom: 5rpx;
}
.resolution-text {
font-size: 24rpx;
color: #666;
display: block;
margin-bottom: 5rpx;
}
.orientation-text {
font-size: 22rpx;
color: #9c27b0;
display: block;
margin-bottom: 3rpx;
}
.compression-text {
font-size: 24rpx;
color: #4caf50;
font-weight: bold;
display: block;
}
.detail-info {
margin-top: 30rpx;
padding-top: 30rpx;
border-top: 2rpx solid #e0e0e0;
}
</style>

- 发布:2025-09-12 13:48
- 更新:2025-09-12 13:48
- 阅读:28
产品分类: uniapp/App
PC开发环境操作系统: Mac
PC开发环境操作系统版本号: 15.6.1
HBuilderX类型: 正式
HBuilderX版本号: 4.76
手机系统: iOS
手机系统版本号: iOS 16
手机厂商: 苹果
手机机型: iPhoneX
页面类型: vue
vue版本: vue3
打包方式: 云端
项目创建方式: HBuilderX
示例代码:
操作步骤:
提供了800行的完整示例页面代码.可以直接代码在一个页面中复现,以及拍摄了完整的复现过程和录屏中使用的原视频.
提供了800行的完整示例页面代码.可以直接代码在一个页面中复现,以及拍摄了完整的复现过程和录屏中使用的原视频.
预期结果:
常规操作应该是等比例处理? 但是目前明显能发现宽高似乎调转了. 竖屏压缩完应该还是竖屏.横屏应该还是横屏.
常规操作应该是等比例处理? 但是目前明显能发现宽高似乎调转了. 竖屏压缩完应该还是竖屏.横屏应该还是横屏.
实际结果:
实际竖屏变成了横屏.
实际竖屏变成了横屏.
bug描述:
压缩竖屏拍摄的视频就会旋转.代码非常简单. 方向变化了. 而且宽高似乎?
{
"压缩前": {
"宽度": 1080,
"高度": 1920,
"方向": "right",
"时长": 6,
"比特率": 7801,
"帧率": 30.00848,
"格式": "video/mp4"
},
"压缩后": {
"宽度": 960,
"高度": 540,
"方向": "up",
"时长": 6,
"比特率": 1517,
"帧率": 30.00853,
"格式": "video/mp4",
"文件大小": 1193873
}
}
这是压缩前后的uni.getVideoInfo获取对比.
0 个回复