add dexie as indexedDB buffer store

rapp/detached-control-value-iframe
Robert Rapp 2025-05-30 12:29:14 +02:00
parent 833de3656e
commit 3f213c021a
4 changed files with 1186 additions and 0 deletions

1039
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "audio-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"deploy": "rm -rf ../audio-host/public/audio-app/* && vite build && cp -r dist/* ../audio-host/public/audio-app/"
},
"devDependencies": {
"typescript": "~5.8.3",
"vite": "^6.3.5"
},
"dependencies": {
"@rnbo/js": "^1.3.4",
"dexie": "^4.0.11"
}
}

View File

@ -0,0 +1,106 @@
// composables/useBufferStore.ts
import { db } from '../lib/db'
interface AudioNodeItem {
type: string;
node: AudioNode | null;
started?: boolean;
previousNodeName?:string;
nextNodeName?:string;
}
// Interner Buffer-Cache (nicht reaktiv)
const buffers = new Map<string, AudioBuffer>()
export const useBufferStore = () => ({
get: (id: string) => buffers.get(id),
set: (id: string, val: AudioBuffer) => buffers.set(id, val),
all: () => buffers
})
// Holt einen AudioBuffer aus dem Cache
export function getAudioBuffer(key: string): AudioBuffer | undefined {
return buffers.get(key)
}
// Holt oder erstellt einen BufferSourceNode mit geladenem AudioBuffer
export async function createBufferSource(
name: string,
audioContext: AudioContext
): Promise<AudioNodeItem> {
const store = useBufferStore()
const buffer = store.get(name)
if (!buffer) throw new Error(`Kein Buffer für "${name}" im Cache gefunden.`)
const source = audioContext.createBufferSource()
source.buffer = buffer
source.loop = true
return {
type: 'bufferSource',
node: source,
started: false,
}
}
// Speichert einen ArrayBuffer in IndexedDB unter gegebenem Namen
export async function saveBufferInIndexedDb(
fileName: string,
buffer: ArrayBuffer
): Promise<void> {
if (!db?.audiosources) {
console.warn('[BufferStore] Dexie DB nicht verfügbar')
return
}
try {
const existing = await db.audiosources.get({ name: fileName })
if (existing) {
await db.audiosources.update(existing.id!, { buffer })
console.info(`[BufferStore] Buffer für "${fileName}" aktualisiert`)
} else {
console.log("BUFFER", {buffer})
await db.audiosources.add({ name: fileName, buffer })
console.info(`[BufferStore] Buffer für "${fileName}" gespeichert`)
}
} catch (err) {
console.error('[BufferStore] Fehler beim Speichern in IndexedDB:', err)
}
}
// Lädt AudioBuffer aus IndexedDB oder via fetch
export async function fetchOrLoadBuffer(
name: string,
url: string,
audioContext: AudioContext
): Promise<AudioBuffer> {
const store = useBufferStore()
// Zuerst aus Cache
const cached = store.get(name)
if (cached) return cached
let arrayBuffer: ArrayBuffer
// Versuch aus IndexedDB
const existing = await db?.audiosources?.get({ name })
if (existing?.buffer) {
arrayBuffer = existing.buffer
console.info(`[BufferStore] Buffer für "${name}" aus IndexedDB geladen`)
} else {
// Fallback: fetch
const response = await fetch(url)
if (!response.ok) throw new Error(`Fetch fehlgeschlagen für ${url}`)
arrayBuffer = await response.arrayBuffer()
await saveBufferInIndexedDb(name, arrayBuffer)
}
// Dekodieren & speichern
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer.slice(0))
store.set(name, audioBuffer)
return audioBuffer
}

21
src/lib/db.ts Normal file
View File

@ -0,0 +1,21 @@
import { Dexie, type Table } from 'dexie'
export interface AudioSource {
id?: number;
name: string;
buffer: ArrayBuffer;
}
export default class Database extends Dexie {
// 'audiosources' is added by dexie when declaring the stores()
// We just tell the typing system this is the case
audiosources!: Table<AudioSource>
constructor () {
super('audiosources')
this.version(1).stores({
audiosources: '++id, &name' // Primary key and indexed props
})
}
}
export const db = new Database()