jam-tracks / frontend /src /hooks /usePresetCache.js
Mina Emadi
Add IndexedDB preset caching with background prefetch
d2f89e9
import { useState, useEffect, useCallback } from 'react'
import { isCached, cacheStem } from '../utils/presetCache'
const STEM_NAMES = ['guitar', 'drums', 'bass', 'synth', 'click_record']
/**
* Checks IndexedDB for cached presets on mount, then background-fetches
* any that are missing using the stateless /api/preset-stem endpoint.
*
* Returns cacheStatus: { [presetName]: 'cached' | 'loading' | 'uncached' }
*/
export function usePresetCache() {
const [cacheStatus, setCacheStatus] = useState({})
const prefetchPreset = useCallback(async (presetName) => {
setCacheStatus(prev => ({ ...prev, [presetName]: 'loading' }))
try {
const results = await Promise.all(
STEM_NAMES.map(async (stemName) => {
const response = await fetch(`/api/preset-stem/${presetName}/${stemName}`)
if (!response.ok) return false
const sampleRate = parseInt(response.headers.get('X-Sample-Rate'))
const numChannels = parseInt(response.headers.get('X-Channels'))
const numFrames = parseInt(response.headers.get('X-Frames'))
const bytes = await response.arrayBuffer()
await cacheStem(presetName, stemName, { bytes, sampleRate, numChannels, numFrames })
return true
})
)
const allOk = results.every(Boolean)
setCacheStatus(prev => ({ ...prev, [presetName]: allOk ? 'cached' : 'uncached' }))
} catch {
setCacheStatus(prev => ({ ...prev, [presetName]: 'uncached' }))
}
}, [])
useEffect(() => {
let cancelled = false
async function init() {
try {
const res = await fetch('/api/presets')
const { presets } = await res.json()
if (cancelled || !presets?.length) return
// Check which presets are already in IndexedDB
const statuses = {}
await Promise.all(
presets.map(async (name) => {
statuses[name] = (await isCached(name, STEM_NAMES)) ? 'cached' : 'uncached'
})
)
if (cancelled) return
setCacheStatus(statuses)
// Background-fetch uncached presets one at a time to avoid saturating bandwidth
for (const name of presets) {
if (cancelled) break
if (statuses[name] === 'uncached') {
await prefetchPreset(name)
}
}
} catch {
// Prefetch is best-effort — silently fail
}
}
init()
return () => { cancelled = true }
}, [prefetchPreset])
return { cacheStatus }
}