mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-09 22:53:02 -04:00
Add IndexedDB caching for TTS voice lines in VcNarrator plugin (#174)
This commit is contained in:
parent
054e2a0208
commit
23932f5f07
1 changed files with 91 additions and 3 deletions
|
@ -24,6 +24,51 @@ import {
|
||||||
UserStore,
|
UserStore,
|
||||||
} from "@webpack/common";
|
} from "@webpack/common";
|
||||||
|
|
||||||
|
///
|
||||||
|
// Create an in-memory cache (temporary, lost on restart)
|
||||||
|
const ttsCache = new Map<string, string>();
|
||||||
|
|
||||||
|
// Helper function to open (or create) an IndexedDB database.
|
||||||
|
function openDB(): Promise<IDBDatabase> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = indexedDB.open("VcNarratorDB", 1);
|
||||||
|
request.onupgradeneeded = () => {
|
||||||
|
const db = request.result;
|
||||||
|
// Create an object store called "voices" if it doesn't already exist.
|
||||||
|
if (!db.objectStoreNames.contains("voices")) {
|
||||||
|
db.createObjectStore("voices");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.onsuccess = () => resolve(request.result);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get a cached voice line from IndexedDB.
|
||||||
|
async function getCachedVoiceFromDB(cacheKey: string): Promise<Blob | null> {
|
||||||
|
const db = await openDB();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction("voices", "readonly");
|
||||||
|
const store = tx.objectStore("voices");
|
||||||
|
const request = store.get(cacheKey);
|
||||||
|
request.onsuccess = () => resolve(request.result || null);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to store a voice line in IndexedDB.
|
||||||
|
async function setCachedVoiceInDB(cacheKey: string, blob: Blob): Promise<void> {
|
||||||
|
const db = await openDB();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction("voices", "readwrite");
|
||||||
|
const store = tx.objectStore("voices");
|
||||||
|
const request = store.put(blob, cacheKey);
|
||||||
|
request.onsuccess = () => resolve();
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
interface VoiceState {
|
interface VoiceState {
|
||||||
userId: string;
|
userId: string;
|
||||||
channelId?: string;
|
channelId?: string;
|
||||||
|
@ -43,10 +88,42 @@ const VoiceStateStore = findByPropsLazy(
|
||||||
// Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would
|
// Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would
|
||||||
// not say the second mute, which would lead you to believe they're unmuted
|
// not say the second mute, which would lead you to believe they're unmuted
|
||||||
|
|
||||||
async function speak(
|
///
|
||||||
text: string,
|
async function speak(text: string) {
|
||||||
) {
|
|
||||||
if (text.trim().length === 0) return;
|
if (text.trim().length === 0) return;
|
||||||
|
|
||||||
|
// Create a unique cache key using the voice setting and the text.
|
||||||
|
const cacheKey = `${settings.store.customVoice}_${text}`;
|
||||||
|
|
||||||
|
// 1. Check the in-memory cache (fast check)
|
||||||
|
if (ttsCache.has(cacheKey)) {
|
||||||
|
const cachedUrl = ttsCache.get(cacheKey)!;
|
||||||
|
const audio = new Audio(cachedUrl);
|
||||||
|
audio.volume = settings.store.volume;
|
||||||
|
audio.playbackRate = settings.store.rate;
|
||||||
|
audio.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check the persistent IndexedDB cache.
|
||||||
|
try {
|
||||||
|
const cachedBlob = await getCachedVoiceFromDB(cacheKey);
|
||||||
|
if (cachedBlob) {
|
||||||
|
// Create a URL from the stored Blob.
|
||||||
|
const url = URL.createObjectURL(cachedBlob);
|
||||||
|
// Save it in the in-memory cache for next time.
|
||||||
|
ttsCache.set(cacheKey, url);
|
||||||
|
const audio = new Audio(url);
|
||||||
|
audio.volume = settings.store.volume;
|
||||||
|
audio.playbackRate = settings.store.rate;
|
||||||
|
audio.play();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error accessing IndexedDB:", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. If not found in either cache, fetch from the TTS API.
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
"https://tiktok-tts.weilnet.workers.dev/api/generation",
|
"https://tiktok-tts.weilnet.workers.dev/api/generation",
|
||||||
{
|
{
|
||||||
|
@ -76,12 +153,23 @@ async function speak(
|
||||||
const blob = new Blob([new Uint8Array(binaryData)], { type: "audio/mpeg" });
|
const blob = new Blob([new Uint8Array(binaryData)], { type: "audio/mpeg" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
// Save the URL in the in-memory cache.
|
||||||
|
ttsCache.set(cacheKey, url);
|
||||||
|
|
||||||
|
// Store the Blob in IndexedDB for persistence.
|
||||||
|
try {
|
||||||
|
await setCachedVoiceInDB(cacheKey, blob);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error storing in IndexedDB:", err);
|
||||||
|
}
|
||||||
|
|
||||||
const audio = new Audio(url);
|
const audio = new Audio(url);
|
||||||
audio.volume = settings.store.volume;
|
audio.volume = settings.store.volume;
|
||||||
audio.playbackRate = settings.store.rate;
|
audio.playbackRate = settings.store.rate;
|
||||||
audio.play();
|
audio.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
function clean(str: string) {
|
function clean(str: string) {
|
||||||
const replacer = settings.store.latinOnly
|
const replacer = settings.store.latinOnly
|
||||||
? /[^\p{Script=Latin}\p{Number}\p{Punctuation}\s]/gu
|
? /[^\p{Script=Latin}\p{Number}\p{Punctuation}\s]/gu
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue