提交 edb4a1f9 authored 作者: lidongxu's avatar lidongxu

Merge branch 'release' into dev

......@@ -84,11 +84,12 @@
ref="videoRef"
alt=""
muted
autoplay
playsinline
preload="metadata"
:poster="getVideoPoster(url)"
preload="auto"
class="preview-media"
@loadeddata="onVideoLoadedData(videoRef)" />
@loadeddata="onVideoLoadedData({ value: videoRef.value, url: url })"
@error="onVideoError(url)" />
<div class="play-hint"
@click.stop="showVideoFullscreen(url)">
......@@ -447,6 +448,10 @@ const commitStorePhotosRead = async (file) => {
url: pictureUrl,
status: 'done'
}]
// 上传完成后,为视频生成海报
if (getFileType(pictureUrl) === 'video') {
generateVideoPoster(pictureUrl);
}
await createInspectionTaskAPI({
storeCode: form.storeCode,
storeName: form.storeName,
......@@ -536,17 +541,26 @@ const isPlaying = ref(false);
// 视频相关状态
const videoPosters = ref({});
const videoLoadingStatus = ref({}); // 跟踪每个视频的加载状态
const isWebView = ref(false);
// 组件挂载时检测环境
onMounted(() => {
detectEnvironment();
// 为已有的视频URL生成海报图
if (form.commitStorePicture && form.commitStorePicture.length > 0) {
form.commitStorePicture.forEach(item => {
if (item.url && getFileType(item.url) === 'video') {
generateVideoPoster(item.url);
}
});
}
});
// 检测是否在webView中
const detectEnvironment = () => {
// 检测是否在iOS设备上
const isiOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
// 检测是否在webView中 - 可以根据实际项目中的webView特征调整
// 这里使用了一些常见的webView特征检测方法
const isInWebView = () => {
......@@ -556,35 +570,117 @@ const detectEnvironment = () => {
const hasUIWebViewProperty = typeof navigator.standalone !== 'undefined' && !navigator.standalone;
// 检查userAgent中是否包含特定App的标识
const hasCustomUserAgent = /AppName|CustomWebView/i.test(navigator.userAgent);
return hasUIWebViewProperty || hasCustomUserAgent;
}
return false;
};
isWebView.value = isInWebView();
};
// 生成视频海报图
const generateVideoPoster = async (videoUrl) => {
// 避免重复生成
if (videoPosters.value[videoUrl] || videoLoadingStatus.value[videoUrl]) {
return;
}
// 标记为加载中
videoLoadingStatus.value[videoUrl] = true;
try {
// 创建一个临时video元素用于获取第一帧
const tempVideo = document.createElement('video');
tempVideo.src = videoUrl;
tempVideo.muted = true;
tempVideo.preload = 'metadata';
// 等待视频元数据加载完成
await new Promise((resolve, reject) => {
tempVideo.onloadedmetadata = resolve;
tempVideo.onerror = reject;
// 设置超时,防止加载过久
setTimeout(() => reject(new Error('Video metadata load timeout')), 5000);
});
// 设置当前时间为0.1秒(有时第0秒是黑屏)
tempVideo.currentTime = 0.1;
// 等待视频帧加载完成
await new Promise((resolve, reject) => {
tempVideo.onseeked = resolve;
tempVideo.onerror = reject;
setTimeout(() => reject(new Error('Video frame seek timeout')), 3000);
});
// 使用canvas绘制第一帧
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 设置canvas尺寸为视频的原始尺寸或适合预览的尺寸
const targetWidth = 300; // 可以根据需要调整
const aspectRatio = tempVideo.videoHeight / tempVideo.videoWidth;
canvas.width = targetWidth;
canvas.height = Math.floor(targetWidth * aspectRatio);
// 绘制视频帧到canvas
ctx.drawImage(tempVideo, 0, 0, canvas.width, canvas.height);
// 转换为图片URL
const posterUrl = canvas.toDataURL('image/jpeg', 0.9); // 0.9是压缩质量
// 缓存海报图
videoPosters.value[videoUrl] = posterUrl;
// 清理临时资源
tempVideo.src = '';
} catch (error) {
console.warn('生成视频海报失败,使用默认海报:', error);
// 生成一个默认的占位海报图
videoPosters.value[videoUrl] = generateDefaultVideoPoster();
} finally {
// 标记为加载完成
videoLoadingStatus.value[videoUrl] = false;
}
};
// 获取视频海报图
// 生成默认的视频占位海报
const generateDefaultVideoPoster = () => {
// 创建一个简单的SVG作为默认海报
return 'data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 width%3D%22300%22 height%3D%22170%22%3E%3Crect width%3D%22100%25%22 height%3D%22100%25%22 fill%3D%22%23222%22%2F%3E%3Ccircle cx%3D%22150%22 cy%3D%2285%22 r%3D%2240%22 fill%3D%22%23444%22%2F%3E%3Cpath d%3D%22M130 65 l40 20 l-40 20 z%22 fill%3D%22%23fff%22%2F%3E%3Ctext x%3D%22150%22 y%3D%22140%22 font-family%3D%22Arial%22 font-size%3D%2214%22 fill%3D%22%23999%22 text-anchor%3D%22middle%22%3E视频预览%3C%2Ftext%3E%3C%2Fsvg%3E';
};
// 获取视频海报图(修改之前的实现)
const getVideoPoster = (url) => {
// 如果已经有缓存的海报图,则直接返回
// 如果已经生成了海报图,直接返回
if (videoPosters.value[url]) {
return videoPosters.value[url];
}
// 为iOS设备生成一个临时海报图
if (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream) {
// 可以返回一个默认的视频占位图
// 或者更好的方法是使用视频的第一帧作为海报(需要额外处理)
return 'data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 width%3D%22100%25%22 height%3D%22100%25%22%3E%3Crect width%3D%22100%25%22 height%3D%22100%25%22 fill%3D%22%23000%22%2F%3E%3Cpath d%3D%22M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9l-8 8z%22 fill%3D%22%23999%22%2F%3E%3C%2Fsvg%3E';
// 如果正在生成中,返回一个临时占位符
if (videoLoadingStatus.value[url]) {
return generateDefaultVideoPoster();
}
return '';
// 否则开始生成海报图
generateVideoPoster(url);
// 立即返回默认海报,生成完成后会自动更新
return generateDefaultVideoPoster();
};
// 视频数据加载完成后触发
const onVideoLoadedData = (videoRef) => {
const video = videoRef.value;
// 视频加载错误处理
const onVideoError = (url) => {
console.error('视频加载错误:', url);
// 设置默认海报图
if (!videoPosters.value[url]) {
videoPosters.value[url] = generateDefaultVideoPoster();
}
// 重置加载状态
videoLoadingStatus.value[url] = false;
};
// 修改onVideoLoadedData函数以接收更多参数
const onVideoLoadedData = ({value: video, url}) => {
if (!video) return;
// 对于非webView环境,可以考虑预加载视频的第一帧作为海报
......@@ -593,13 +689,13 @@ const onVideoLoadedData = (videoRef) => {
// 尝试获取视频的第一帧作为海报图
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.width = video.videoWidth || 300;
canvas.height = video.videoHeight || 170;
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
// 将canvas转换为图片URL并缓存
const posterUrl = canvas.toDataURL('image/jpeg');
videoPosters.value[video.src] = posterUrl;
videoPosters.value[url] = posterUrl;
video.poster = posterUrl;
} catch (error) {
console.error('生成视频海报失败:', error);
......@@ -642,7 +738,7 @@ const showVideoFullscreen = async (url) => {
// 跨浏览器请求全屏(保留之前的实现)
const requestFullscreen = (element) => {
if (!element) return Promise.reject(new Error('Element not found'));
const methods = [
// 标准API
() => element.requestFullscreen(),
......@@ -657,14 +753,14 @@ const requestFullscreen = (element) => {
return Promise.reject(new Error('webkitEnterFullscreen not supported'));
}
];
// 尝试各种方法
return new Promise((resolve, reject) => {
for (const method of methods) {
try {
const result = method();
if (result && result.then) {
result.then(resolve).catch(() => {});
result.then(resolve).catch(() => { });
} else {
resolve();
return;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论