// 格式化相对时间 export function formatRelativeTime(date: string | Date): string { const now = new Date(); const target = new Date(date); const diffMs = now.getTime() - target.getTime(); const diffSeconds = Math.floor(diffMs / 1000); const diffMinutes = Math.floor(diffSeconds / 60); const diffHours = Math.floor(diffMinutes / 60); const diffDays = Math.floor(diffHours / 24); const diffMonths = Math.floor(diffDays / 30); const diffYears = Math.floor(diffDays / 365); if (diffYears > 0) return `${diffYears}年前`; if (diffMonths > 0) return `${diffMonths}月前`; if (diffDays > 0) return `${diffDays}天前`; if (diffHours > 0) return `${diffHours}小时前`; if (diffMinutes > 0) return `${diffMinutes}分钟前`; return "刚刚"; } // 格式化时间显示 (mm:ss) export function formatTime(seconds: number): string { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, "0")}`; } // 处理评论文本中的表情占位符 export function parseCommentText( text: string, ): (string | { type: "emoji"; name: string })[] { const parts: (string | { type: "emoji"; name: string })[] = []; const regex = /\[([^\]]+)\]/g; let lastIndex = 0; let match: RegExpExecArray | null; while ((match = regex.exec(text)) !== null) { // 添加表情前的文本 if (match.index > lastIndex) { parts.push(text.slice(lastIndex, match.index)); } // 添加表情 parts.push({ type: "emoji", name: match[1] }); lastIndex = regex.lastIndex; } // 添加剩余文本 if (lastIndex < text.length) { parts.push(text.slice(lastIndex)); } return parts; } // 从 localStorage 获取数值 export function getNumberFromStorage( key: string, defaultValue: number, ): number { if (typeof window === "undefined") return defaultValue; const saved = localStorage.getItem(key); if (!saved) return defaultValue; const parsed = parseFloat(saved); return Number.isNaN(parsed) ? defaultValue : parsed; } // 从 localStorage 获取字符串 export function getStringFromStorage( key: string, defaultValue: string, ): string { if (typeof window === "undefined") return defaultValue; return localStorage.getItem(key) || defaultValue; } // 保存到 localStorage export function saveToStorage(key: string, value: string | number): void { if (typeof window === "undefined") return; localStorage.setItem(key, value.toString()); } // 稳定的绝对时间格式(与 locale 无关),用于避免 SSR/CSR 水合不一致 // 输出示例:2025-10-20 08:23:05 UTC export function formatAbsoluteUTC(date: string | Date): string { const d = new Date(date); const Y = d.getUTCFullYear(); const M = String(d.getUTCMonth() + 1).padStart(2, "0"); const D = String(d.getUTCDate()).padStart(2, "0"); const h = String(d.getUTCHours()).padStart(2, "0"); const m = String(d.getUTCMinutes()).padStart(2, "0"); const s = String(d.getUTCSeconds()).padStart(2, "0"); return `${Y}-${M}-${D} ${h}:${m}:${s} UTC`; } // 主动释放 HTMLMediaElement 解码缓冲与网络流,防止 SPA 连续切页导致内存/硬件解码器泄漏 export function releaseMediaElement( el: HTMLMediaElement | null | undefined, ): void { if (!el) return; try { el.pause(); el.removeAttribute("src"); el.load(); } catch {} } const PROGRESS_STORAGE_PREFIX = "aweme_progress_"; const PROGRESS_TTL_MS = 5 * 60 * 1000; const MAX_PROGRESS_ENTRIES = 30; // 清理过期或超量的视频播放进度缓存,避免长期浏览堆积 localStorage export function cleanupExpiredProgressStorage(): void { if (typeof window === "undefined") return; try { const now = Date.now(); const validEntries: { key: string; timestamp: number }[] = []; for (let i = localStorage.length - 1; i >= 0; i--) { const key = localStorage.key(i); if (!key || !key.startsWith(PROGRESS_STORAGE_PREFIX)) continue; const raw = localStorage.getItem(key); if (!raw) { localStorage.removeItem(key); continue; } try { const parsed = JSON.parse(raw) as { timestamp?: unknown }; const timestamp = typeof parsed.timestamp === "number" ? parsed.timestamp : 0; if (now - timestamp >= PROGRESS_TTL_MS) { localStorage.removeItem(key); } else { validEntries.push({ key, timestamp }); } } catch { localStorage.removeItem(key); } } if (validEntries.length > MAX_PROGRESS_ENTRIES) { validEntries.sort((a, b) => b.timestamp - a.timestamp); for (let i = MAX_PROGRESS_ENTRIES; i < validEntries.length; i++) { localStorage.removeItem(validEntries[i].key); } } } catch {} }