From 2b0a831c1eac0fa67c2fd34be10d542d75321822 Mon Sep 17 00:00:00 2001 From: harold Date: Thu, 22 May 2025 00:44:40 +0500 Subject: [PATCH] add fix --- internal/model/widget-templates.go | 256 +++++++++-------------------- 1 file changed, 79 insertions(+), 177 deletions(-) diff --git a/internal/model/widget-templates.go b/internal/model/widget-templates.go index 5c4d503..11c1f2e 100644 --- a/internal/model/widget-templates.go +++ b/internal/model/widget-templates.go @@ -64,7 +64,6 @@ func GetTemplate1(streamerID int, donatHost, ttsHost string) string { let widgetUrl = 'https://%s/api'; let ttsUrl = 'https://%s/api/tts'; -// Функция для периодических запросов онлайн-статуса function startHeartbeat(streamerID) { setInterval(async () => { try { @@ -75,7 +74,7 @@ function startHeartbeat(streamerID) { } catch (error) { console.error('Heartbeat error:', error); } - }, 10000); // 10 секунд + }, 10000); } function createTextWithAmount(text, amount) { @@ -108,11 +107,7 @@ function createTextElement(text) { async function getDonatInfo(streamerID) { try { let response = await fetch(widgetUrl + '/widget/get-donat-for-playing/' + streamerID); - if (!response.ok) { - console.error('Failed to get donat info:', response.status); - return null; - } - return await response.json(); + return response.ok ? await response.json() : null; } catch (error) { console.error('Fetch error (getDonatInfo):', error); return null; @@ -121,39 +116,32 @@ async function getDonatInfo(streamerID) { function playAudio(url, volume, signal) { return new Promise((resolve, reject) => { - if (signal?.aborted) { - return reject(new DOMException('Aborted', 'AbortError')); - } + if (signal?.aborted) return reject(new DOMException('Aborted', 'AbortError')); const audio = new Audio(url); audio.volume = volume; - let resolved = false; - const onAbort = () => { - if (resolved) return; + const cleanup = () => { resolved = true; audio.pause(); audio.src = ""; audio.removeEventListener('ended', onEnded); audio.removeEventListener('error', onError); + }; + + const onAbort = () => { + if (!resolved) cleanup(); reject(new DOMException('Aborted', 'AbortError')); }; const onEnded = () => { - if (resolved) return; - resolved = true; - signal?.removeEventListener('abort', onAbort); - audio.removeEventListener('error', onError); + cleanup(); resolve(audio); }; const onError = (event) => { - if (resolved) return; - resolved = true; - signal?.removeEventListener('abort', onAbort); - audio.removeEventListener('ended', onEnded); - console.error('Audio playback error:', event); + cleanup(); reject(event.error || new Error('Audio playback failed')); }; @@ -162,13 +150,9 @@ function playAudio(url, volume, signal) { audio.addEventListener('error', onError, { once: true }); audio.play().catch(err => { - if (resolved) return; - resolved = true; - signal?.removeEventListener('abort', onAbort); - audio.removeEventListener('ended', onEnded); - audio.removeEventListener('error', onError); + cleanup(); if (err.name === 'AbortError' && signal?.aborted) { - reject(new DOMException('Aborted', 'AbortError')); + reject(new DOMException('Aborted', 'AbortError')); } else { reject(err); } @@ -176,141 +160,76 @@ function playAudio(url, volume, signal) { }); } -function playSpeech(text, voiceSettings, signal) { - return new Promise((resolve, reject) => { - if (signal?.aborted) { - return reject(new DOMException('Aborted', 'AbortError')); - } - if (!voiceSettings.voice_enabled) { - return resolve(null); - } +async function playSpeech(text, voiceSettings, signal) { + if (signal?.aborted) throw new DOMException('Aborted', 'AbortError'); + if (!voiceSettings.voice_enabled) return null; - const requestBody = { - text: text, - speed: (voiceSettings.voice_speed || 'medium').toLowerCase(), - scenery: voiceSettings.scenery || 'default', - sound_percent: voiceSettings.voice_sound_percent || 100, - min_price: voiceSettings.min_price || 0, - languages: voiceSettings.languages || ['ru'] - }; + const requestBody = { + text: text, + speed: (voiceSettings.voice_speed || 'medium').toLowerCase(), + scenery: voiceSettings.scenery || 'default', + sound_percent: voiceSettings.voice_sound_percent || 100, + min_price: voiceSettings.min_price || 0, + languages: voiceSettings.languages || ['ru'] + }; - let audio; - let objectUrl; - let resolved = false; - - const cleanupAndReject = (error) => { - if (resolved) return; - resolved = true; - if (audio) { - audio.pause(); - audio.src = ""; - } - if (objectUrl) { - URL.revokeObjectURL(objectUrl); - objectUrl = null; - } - reject(error); - }; - - const onAbort = () => { - cleanupAndReject(new DOMException('Aborted', 'AbortError')); - }; - signal?.addEventListener('abort', onAbort, { once: true }); - - fetch(ttsUrl + '/generate', { + try { + const response = await fetch(ttsUrl + '/generate', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(requestBody), signal: signal - }) - .then(response => { - if (signal?.aborted) throw new DOMException('Aborted', 'AbortError'); - if (!response.ok) throw new Error('TTS API error: ' + response.status); - return response.blob(); - }) - .then(blob => { - if (signal?.aborted) throw new DOMException('Aborted', 'AbortError'); - objectUrl = URL.createObjectURL(blob); - audio = new Audio(objectUrl); - audio.volume = (voiceSettings.voice_sound_percent || 100) / 100; - - const onEnded = () => { - if (resolved) return; - resolved = true; - signal?.removeEventListener('abort', onAbort); - if (objectUrl) URL.revokeObjectURL(objectUrl); - objectUrl = null; - resolve(audio); - }; - const onError = (event) => { - if (resolved) return; - signal?.removeEventListener('abort', onAbort); - cleanupAndReject(event.error || new Error('TTS audio playback failed')); - }; - - audio.addEventListener('ended', onEnded, { once: true }); - audio.addEventListener('error', onError, { once: true }); - - return audio.play(); - }) - .catch(error => { - if (resolved) return; - signal?.removeEventListener('abort', onAbort); - cleanupAndReject(error); }); - }); + + if (!response.ok) throw new Error('TTS API error: ' + response.status); + const blob = await response.blob(); + const objectUrl = URL.createObjectURL(blob); + const audio = new Audio(objectUrl); + audio.volume = (voiceSettings.voice_sound_percent || 100) / 100; + + return new Promise((resolve, reject) => { + audio.addEventListener('ended', () => { + URL.revokeObjectURL(objectUrl); + resolve(audio); + }, { once: true }); + + audio.addEventListener('error', (err) => { + URL.revokeObjectURL(objectUrl); + reject(err); + }, { once: true }); + + audio.play().catch(reject); + }); + } catch (error) { + throw error; + } } async function playMedia(donat, voiceSettings) { - let audioElement = null; - let ttsAudio = null; const controller = new AbortController(); let timeoutId; - const mediaOperation = (async () => { - try { - if (donat.play_content && donat.audio_link) { - if (controller.signal.aborted) return; - audioElement = await playAudio( - donat.audio_link, - (voiceSettings.voice_sound_percent || 100) / 100, - controller.signal - ); - if (controller.signal.aborted) return; - if (donat.text) { - ttsAudio = await playSpeech(donat.text, voiceSettings, controller.signal); - } - } else if (donat.text && donat.voice_enabled) { - if (controller.signal.aborted) return; - ttsAudio = await playSpeech(donat.text, voiceSettings, controller.signal); - } - } catch (error) { - if (error.name !== 'AbortError') { - console.error('Error during media playback:', error); - throw error; - } - } - })(); - - const timeoutPromise = new Promise((resolve, reject) => { - timeoutId = setTimeout(() => { - controller.abort(); - resolve('timeout'); - }, (donat.duration || 0) * 1000); - }); - try { + const mediaOperation = (async () => { + if (donat.play_content && donat.audio_link) { + await playAudio(donat.audio_link, (voiceSettings.voice_sound_percent || 100) / 100, controller.signal); + if (donat.text) await playSpeech(donat.text, voiceSettings, controller.signal); + } else if (donat.text && donat.voice_enabled) { + await playSpeech(donat.text, voiceSettings, controller.signal); + } + })(); + + const timeoutPromise = new Promise((_, reject) => { + timeoutId = setTimeout(() => { + controller.abort(); + reject(new DOMException('Timeout', 'AbortError')); + }, (donat.duration || 5) * 1000); + }); + await Promise.race([mediaOperation, timeoutPromise]); } finally { clearTimeout(timeoutId); - if (audioElement && !audioElement.paused) { - audioElement.pause(); - audioElement.currentTime = 0; - } - if (ttsAudio && !ttsAudio.paused) { - ttsAudio.pause(); - ttsAudio.currentTime = 0; - } + controller.abort(); } } @@ -322,33 +241,25 @@ function clearContainer(container) { async function widgetView() { const streamerID = '%v'; - startHeartbeat(streamerID); // Запускаем heartbeat - + startHeartbeat(streamerID); const contentDiv = document.getElementById('content'); - if (!contentDiv) { - console.error('Content container not found!'); - return; - } - while (true) { const iterationStart = Date.now(); let currentDonat = null; try { - const donatData = await getDonatInfo(streamerID); - if (!donatData || Object.keys(donatData).length === 0) { + currentDonat = await getDonatInfo(streamerID); + if (!currentDonat || Object.keys(currentDonat).length === 0) { await new Promise(r => setTimeout(r, 5000)); continue; } - currentDonat = donatData; clearContainer(contentDiv); if (currentDonat.image_link) { const img = document.createElement('img'); img.src = currentDonat.image_link; - img.alt = "Donation Image"; contentDiv.appendChild(img); } @@ -360,8 +271,8 @@ async function widgetView() { } if (currentDonat.show_text && currentDonat.text) { - const textElem = currentDonat.amount ? - createTextWithAmount(currentDonat.text, currentDonat.amount) : + const textElem = currentDonat.amount ? + createTextWithAmount(currentDonat.text, currentDonat.amount) : createTextElement(currentDonat.text); contentDiv.appendChild(textElem); } @@ -378,33 +289,24 @@ async function widgetView() { await playMedia(currentDonat, voiceSettings); if (currentDonat.order_id) { - try { - await fetch(widgetUrl + '/widget/donat/viewed', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({order_id: currentDonat.order_id}), - }); - } catch (error) { - console.error('Ошибка подтверждения просмотра:', error); - } + await fetch(widgetUrl + '/widget/donat/viewed', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({order_id: currentDonat.order_id}), + }); } - } catch (error) { - console.error('Ошибка обработки доната в цикле:', error); - if (!currentDonat) { - await new Promise(r => setTimeout(r, 5000)); - continue; - } + console.error('Ошибка обработки доната:', error); } + // Очистка контента после отображения const elapsedMs = Date.now() - iterationStart; - const targetDisplayTimeMs = (currentDonat?.duration || 5) * 1000; + const remainingTimeMs = Math.max(0, (currentDonat?.duration || 5) * 1000 - elapsedMs); - const remainingTimeMs = Math.max(0, targetDisplayTimeMs - elapsedMs); - if (remainingTimeMs > 0) { await new Promise(r => setTimeout(r, remainingTimeMs)); } + clearContainer(contentDiv); // Ключевое изменение здесь } }