music(perf): 切歌延迟修 — getAudioUrl 同步短路 + SW install 并发
deploy music / build-and-deploy (push) Successful in 2m4s

诊断:之前 loadPiece 链上加了 `audio.src = await getAudioUrl(...)`,await IDB
即使 cache disabled 也排队个 microtask;叠加 SW install 串行 23 个 fetch
让首次部署后明显卡。

修法:
- getAudioUrl 改同步:内存 blob 命中 / cache 关 → 立返;启用 cache 时内存没
  → 仍返网络 URL,后台 warm IDB 下次用
- audio.src = getAudioUrl(id) 不再 await,零等待
- SW install 改 cache.addAll 并发(HTTP/2 多路),失败回退串行
This commit is contained in:
Fam Zheng
2026-05-26 09:37:24 +01:00
parent 8991033f70
commit adbd259a32
3 changed files with 33 additions and 14 deletions
+21 -6
View File
@@ -124,14 +124,29 @@ export async function getCachedBlobUrl(store, attId) {
return url return url
} }
export async function getAudioUrl(attId) { // 短路:内存里已有 blob URL → 同步返回;未启用 cache → 直接网络 URL 不查 IDB
const cached = await getCachedBlobUrl(STORE_AUDIO, attId) // 只有启用 cache 且内存没 cache 命中才掏 IDB
return cached || attachmentUrl(attId) export function getAudioUrl(attId) {
if (blobUrlCache.has(attId)) return blobUrlCache.get(attId)
if (!isCacheEnabled()) return attachmentUrl(attId)
// 启用了但内存没缓存:网络立返,后台尝试 IDB 命中后下次会用上
warmCachedBlob(STORE_AUDIO, attId)
return attachmentUrl(attId)
} }
export async function getImageUrl(attId) { export function getImageUrl(attId) {
const cached = await getCachedBlobUrl(STORE_IMAGE, attId) if (blobUrlCache.has(attId)) return blobUrlCache.get(attId)
return cached || attachmentUrl(attId) if (!isCacheEnabled()) return attachmentUrl(attId)
warmCachedBlob(STORE_IMAGE, attId)
return attachmentUrl(attId)
}
function warmCachedBlob(store, attId) {
idbGet(store, attId).then((blob) => {
if (blob && !blobUrlCache.has(attId)) {
blobUrlCache.set(attId, URL.createObjectURL(blob))
}
}).catch(() => {})
} }
// ---- 状态 ---- // ---- 状态 ----
+5 -1
View File
@@ -20,13 +20,17 @@ self.addEventListener('install', (event) => {
event.waitUntil( event.waitUntil(
(async () => { (async () => {
const cache = await caches.open(CACHE) const cache = await caches.open(CACHE)
// 串行避免一次性请求过多 // 并发 addAll,HTTP/2 多路复用,一次过;失败回退串行
try {
await cache.addAll(URLS)
} catch {
for (const u of URLS) { for (const u of URLS) {
try { try {
const r = await fetch(u, { cache: 'reload' }) const r = await fetch(u, { cache: 'reload' })
if (r.ok) await cache.put(u, r) if (r.ok) await cache.put(u, r)
} catch {} } catch {}
} }
}
await self.skipWaiting() await self.skipWaiting()
})(), })(),
) )
+2 -2
View File
@@ -773,8 +773,8 @@ async function loadPiece(id) {
await nextTick() await nextTick()
const first = audioAttachments.value[0] const first = audioAttachments.value[0]
if (first && audioEl.value) { if (first && audioEl.value) {
// 优先用 IDB 缓存的 blob URL,没有再走网络 // 优先用 IDB 缓存的 blob URL(cache 关时同步返回网络 URL,零延迟)
audioEl.value.src = await getAudioUrl(first.id) audioEl.value.src = getAudioUrl(first.id)
if (wasPlaying) audioEl.value.play().catch(() => {}) if (wasPlaying) audioEl.value.play().catch(() => {})
} else if (audioEl.value) { } else if (audioEl.value) {
audioEl.value.removeAttribute('src') audioEl.value.removeAttribute('src')