From 1e9b14fcaad33b69615d568e65715bb1130a55ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Ml=C3=A1dek?= Date: Sun, 27 Jul 2025 21:00:26 +0200 Subject: [PATCH] feat: implement global audio loading queue to prevent concurrent connection limits --- src/components/AudioArea.vue | 45 ++++++++-------- src/services/AudioLoader.ts | 101 +++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 24 deletions(-) create mode 100644 src/services/AudioLoader.ts diff --git a/src/components/AudioArea.vue b/src/components/AudioArea.vue index 2be1cad..fa150bd 100644 --- a/src/components/AudioArea.vue +++ b/src/components/AudioArea.vue @@ -5,6 +5,7 @@ - + diff --git a/src/services/AudioLoader.ts b/src/services/AudioLoader.ts new file mode 100644 index 0000000..e9a9fd2 --- /dev/null +++ b/src/services/AudioLoader.ts @@ -0,0 +1,101 @@ +/** + * Global audio loading queue service to prevent hitting browser connection limits + */ + +// Configuration +const MAX_CONCURRENT_LOADS = 3; + +// State +let activeLoads = 0; +const audioQueue: Array<{ + src: string; + onComplete: (blobUrl: string) => void; + onError: (error: Error) => void; +}> = []; + +/** + * Queue an audio file for loading, respecting the global concurrent loading limit + * Returns a promise that resolves with the blob URL when loading is complete + */ +export function queueAudioForLoading( + src: string, + onComplete?: (blobUrl: string) => void, + onError?: (error: Error) => void +): Promise { + return new Promise((resolve, reject) => { + // Add to queue + audioQueue.push({ + src, + onComplete: (blobUrl) => { + if (onComplete) onComplete(blobUrl); + resolve(blobUrl); + }, + onError: (error) => { + if (onError) onError(error); + reject(error); + } + }); + + // Try to process queue + processQueue(); + }); +} + +/** + * Process the next items in the queue if we have capacity + */ +function processQueue() { + // Load more audio files if we have capacity and items in the queue + while (activeLoads < MAX_CONCURRENT_LOADS && audioQueue.length > 0) { + const next = audioQueue.shift(); + if (next) { + loadAudio(next.src, next.onComplete, next.onError); + } + } +} + +/** + * Internal function to handle the actual audio loading using fetch API + * This ensures the entire audio file is downloaded + */ +async function loadAudio( + src: string, + onComplete: (blobUrl: string) => void, + onError: (error: Error) => void +) { + // Increment active loads counter + activeLoads++; + + console.debug(`[AudioLoader] Loading ${src}`); + + try { + // Fetch the entire audio file + const response = await fetch(src); + + if (!response.ok) { + throw new Error(`Failed to load audio: ${response.statusText}`); + } + + // Convert to blob to ensure full download + const blob = await response.blob(); + + // Create a blob URL to use as the audio source + const blobUrl = URL.createObjectURL(blob); + + console.debug(`[AudioLoader] Successfully loaded ${src}`); + + // Call completion handler + onComplete(blobUrl); + } catch (error) { + console.error(`[AudioLoader] Error loading audio: ${error}`); + + // Call error handler + onError(error as Error); + } finally { + // Decrement counter when audio is loaded (or failed) + activeLoads--; + + // Process next audio in queue if available + processQueue(); + } +}