// 兼容性状态类型 interface AVPlaybackStatus { isLoaded: boolean; isPlaying?: boolean; durationMillis?: number; naturalSize?: { width: number; height: number; }; positionMillis?: number; } export interface VideoMetadata { width: number; height: number; duration: number; aspectRatio: number; orientation: 'portrait' | 'landscape' | 'square'; } /** * 从AVPlaybackStatus中提取视频元数据 */ export function extractVideoMetadata(status: AVPlaybackStatus): VideoMetadata | null { if (!status.isLoaded) { return null; } // 尝试从不同的属性获取视频尺寸 let width = 0, height = 0; // 检查是否有naturalSize属性 if ('naturalSize' in status && status.naturalSize) { const naturalSize = status.naturalSize as any; width = naturalSize.width || 0; height = naturalSize.height || 0; } // 如果没有获取到尺寸,使用默认值 if (width === 0 || height === 0) { width = 1920; // 默认宽度 height = 1080; // 默认高度 } const duration = status.durationMillis ? status.durationMillis / 1000 : 0; const aspectRatio = width > 0 ? width / height : 1; // 判断视频方向 let orientation: 'portrait' | 'landscape' | 'square'; if (Math.abs(width - height) < 10) { orientation = 'square'; } else if (width > height) { orientation = 'landscape'; } else { orientation = 'portrait'; } return { width, height, duration, aspectRatio, orientation, }; } /** * 根据容器尺寸和视频元数据计算最佳显示尺寸 */ export function calculateOptimalVideoSize( containerWidth: number, maxHeight: number, metadata: VideoMetadata, resizeMode: 'contain' | 'cover' | 'stretch' = 'contain' ): { width: number; height: number } { const { aspectRatio } = metadata; switch (resizeMode) { case 'contain': // 保持宽高比,完全显示在容器内 if (containerWidth / aspectRatio <= maxHeight) { return { width: containerWidth, height: containerWidth / aspectRatio, }; } else { return { width: maxHeight * aspectRatio, height: maxHeight, }; } case 'cover': // 保持宽高比,填满容器(可能裁剪) if (containerWidth / aspectRatio >= maxHeight) { return { width: containerWidth, height: containerWidth / aspectRatio, }; } else { return { width: maxHeight * aspectRatio, height: maxHeight, }; } case 'stretch': // 拉伸填满容器 return { width: containerWidth, height: maxHeight, }; default: return { width: containerWidth, height: containerWidth / aspectRatio, }; } } /** * 格式化视频时长 */ export function formatVideoDuration(seconds: number): string { const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.floor(seconds % 60); if (minutes === 0) { return `${remainingSeconds}秒`; } else if (remainingSeconds === 0) { return `${minutes}分钟`; } else { return `${minutes}分${remainingSeconds}秒`; } } /** * 格式化视频尺寸信息 */ export function formatVideoSize(metadata: VideoMetadata): string { const { width, height } = metadata; if (width >= 1000 || height >= 1000) { return `${(width / 1000).toFixed(1)}k × ${(height / 1000).toFixed(1)}k`; } else { return `${width} × ${height}`; } } /** * 检测视频文件类型 */ export function getVideoFileType(url: string): string | null { const extension = url.split('.').pop()?.toLowerCase(); const videoExtensions = [ 'mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv', 'flv', 'wmv', 'm4v', '3gp' ]; return extension && videoExtensions.includes(extension) ? extension : null; } /** * 生成视频封面图的URI(如果需要) */ export function generateVideoPosterUrl(videoUrl: string): string { // 某些CDN支持通过参数生成封面图 // 这里可以根据实际使用的视频服务进行调整 if (videoUrl.includes('youtube.com') || videoUrl.includes('youtu.be')) { // YouTube视频封面图逻辑 const videoId = extractYouTubeVideoId(videoUrl); return videoId ? `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg` : ''; } // 其他视频服务的封面图逻辑可以在这里添加 return ''; } /** * 从YouTube URL提取视频ID */ function extractYouTubeVideoId(url: string): string | null { const regex = /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/; const match = url.match(regex); return match ? match[1] : null; } /** * 验证视频URL是否有效 */ export function isValidVideoUrl(url: string): boolean { if (!url || typeof url !== 'string') { return false; } try { const urlObj = new URL(url); const isValidProtocol = ['http:', 'https:', 'ftp:'].includes(urlObj.protocol); const hasVideoExtension = getVideoFileType(url) !== null; return isValidProtocol && (hasVideoExtension || url.includes('stream')); } catch { return false; } } /** * 获取推荐的缩略图时间点(视频的10%、30%、60%位置) */ export function getThumbnailTimepoints(duration: number): number[] { if (duration <= 0) return [1]; const points = [ duration * 0.1, duration * 0.3, duration * 0.6, ]; return points.map(t => Math.max(1, Math.floor(t))); }