fix: [cm-song] 나레이션 제작 TTS 오디오 재생 버튼 미표시 수정

- TTS try-catch 블록 누락 수정 (JS 구문 오류 해결)
- audioReady display:flex 명시적 설정 (hidden 제거 후 레이아웃 보장)
This commit is contained in:
김보곤
2026-03-05 14:56:42 +09:00
parent 3bba48e443
commit da1fc41acb

View File

@@ -141,27 +141,44 @@ class="w-full py-3.5 bg-indigo-600 hover:bg-indigo-700 disabled:bg-gray-300 disa
</div> </div>
<!-- 오디오 플레이어 --> <!-- 오디오 플레이어 -->
<div id="audioSection" class="hidden"> <div id="audioSection">
<div class="flex flex-col items-center gap-4"> <div class="flex flex-col items-center gap-4">
<audio id="audioPlayer" class="hidden"></audio> <audio id="audioPlayer" class="hidden"></audio>
<button
type="button"
id="playBtn"
class="w-16 h-16 bg-indigo-600 hover:bg-indigo-700 text-white rounded-full flex items-center justify-center shadow-lg hover:shadow-xl transition hover:scale-105"
>
<i id="playIcon" class="ri-play-fill text-3xl ml-0.5"></i>
</button>
<p id="playLabel" class="text-sm text-gray-500 font-medium">들어보기</p>
<!-- 다운로드 버튼 --> <!-- 음성 로딩 -->
<button <div id="audioLoading" class="flex flex-col items-center gap-2">
type="button" <div class="w-16 h-16 bg-gray-200 rounded-full flex items-center justify-center">
id="downloadBtn" <i class="ri-loader-4-line text-2xl text-gray-400 animate-spin"></i>
class="flex items-center gap-2 px-5 py-2.5 border-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 rounded-xl text-sm font-medium transition" </div>
> <p class="text-sm text-gray-400 font-medium">음성 생성 ...</p>
<i class="ri-download-line"></i> </div>
음성 파일 다운로드
</button> <!-- 재생 버튼 (음성 준비 완료 ) -->
<div id="audioReady" class="hidden" style="flex-direction: column; align-items: center; gap: 1rem;">
<button
type="button"
id="playBtn"
class="w-16 h-16 bg-indigo-600 hover:bg-indigo-700 text-white rounded-full flex items-center justify-center shadow-lg hover:shadow-xl transition hover:scale-105"
>
<i id="playIcon" class="ri-play-fill text-3xl ml-0.5"></i>
</button>
<p id="playLabel" class="text-sm text-gray-500 font-medium">들어보기</p>
<!-- 다운로드 버튼 -->
<button
type="button"
id="downloadBtn"
class="flex items-center gap-2 px-5 py-2.5 border-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 rounded-xl text-sm font-medium transition"
>
<i class="ri-download-line"></i>
음성 파일 다운로드
</button>
</div>
<!-- 음성 생성 실패 -->
<div id="audioError" class="hidden text-center">
<p class="text-sm text-red-400">음성 생성에 실패했습니다.</p>
</div>
</div> </div>
</div> </div>
@@ -210,6 +227,9 @@ class="flex items-center gap-1.5 px-5 py-2 bg-white hover:bg-gray-50 text-gray-6
const playBtn = document.getElementById('playBtn'); const playBtn = document.getElementById('playBtn');
const playIcon = document.getElementById('playIcon'); const playIcon = document.getElementById('playIcon');
const playLabel = document.getElementById('playLabel'); const playLabel = document.getElementById('playLabel');
const audioLoading = document.getElementById('audioLoading');
const audioReady = document.getElementById('audioReady');
const audioError = document.getElementById('audioError');
const downloadBtn = document.getElementById('downloadBtn'); const downloadBtn = document.getElementById('downloadBtn');
const saveBtn = document.getElementById('saveBtn'); const saveBtn = document.getElementById('saveBtn');
const retryBtn = document.getElementById('retryBtn'); const retryBtn = document.getElementById('retryBtn');
@@ -306,7 +326,10 @@ function showState(state) {
generateBtn.innerHTML = '<i class="ri-loader-4-line animate-spin"></i> 나레이션 제작 중...'; generateBtn.innerHTML = '<i class="ri-loader-4-line animate-spin"></i> 나레이션 제작 중...';
showState('loading'); showState('loading');
loadingText.textContent = 'AI가 나레이션을 작성하고 있어요...'; loadingText.textContent = 'AI가 나레이션을 작성하고 있어요...';
audioSection.classList.add('hidden'); audioLoading.classList.remove('hidden');
audioReady.classList.add('hidden');
audioReady.style.display = '';
audioError.classList.add('hidden');
currentAudioData = null; currentAudioData = null;
currentAudioMimeType = null; currentAudioMimeType = null;
currentAudioBlob = null; currentAudioBlob = null;
@@ -340,38 +363,48 @@ function showState(state) {
showState('result'); showState('result');
// 2. TTS 음성 생성 // 2. TTS 음성 생성
loadingText.textContent = '목소리를 녹음하고 있어요...'; try {
const audioRes = await fetch('{{ route("rd.cm-song.generate-audio") }}', { const audioRes = await fetch('{{ route("rd.cm-song.generate-audio") }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}', 'X-CSRF-TOKEN': '{{ csrf_token() }}',
}, },
body: JSON.stringify({ lyrics: lyricsData.lyrics }), body: JSON.stringify({ lyrics: lyricsData.lyrics }),
}); });
const audioData = await audioRes.json(); const audioData = await audioRes.json();
if (audioData.success && audioData.audio_data) { if (audioData.success && audioData.audio_data) {
currentAudioData = audioData.audio_data; currentAudioData = audioData.audio_data;
currentAudioMimeType = audioData.mime_type || 'audio/L16;rate=24000'; currentAudioMimeType = audioData.mime_type || 'audio/L16;rate=24000';
const mimeType = currentAudioMimeType; const mimeType = currentAudioMimeType;
let audioUrl; let audioUrl;
if (mimeType.includes('L16') || mimeType.includes('pcm')) { if (mimeType.includes('L16') || mimeType.includes('pcm')) {
const rateMatch = mimeType.match(/rate=(\d+)/); const rateMatch = mimeType.match(/rate=(\d+)/);
const rate = rateMatch ? parseInt(rateMatch[1]) : 24000; const rate = rateMatch ? parseInt(rateMatch[1]) : 24000;
const pcmBytes = base64ToUint8Array(audioData.audio_data); const pcmBytes = base64ToUint8Array(audioData.audio_data);
currentAudioBlob = pcmToWav(pcmBytes, rate); currentAudioBlob = pcmToWav(pcmBytes, rate);
audioUrl = URL.createObjectURL(currentAudioBlob); audioUrl = URL.createObjectURL(currentAudioBlob);
} else {
const rawBytes = base64ToUint8Array(audioData.audio_data);
currentAudioBlob = new Blob([rawBytes], { type: mimeType });
audioUrl = URL.createObjectURL(currentAudioBlob);
}
audioPlayer.src = audioUrl;
audioLoading.classList.add('hidden');
audioReady.classList.remove('hidden');
audioReady.style.display = 'flex';
} else { } else {
const rawBytes = base64ToUint8Array(audioData.audio_data); audioLoading.classList.add('hidden');
currentAudioBlob = new Blob([rawBytes], { type: mimeType }); audioError.classList.remove('hidden');
audioUrl = URL.createObjectURL(currentAudioBlob);
} }
} catch (audioErr) {
audioPlayer.src = audioUrl; console.error('음성 생성 오류:', audioErr);
audioSection.classList.remove('hidden'); audioLoading.classList.add('hidden');
audioError.classList.remove('hidden');
} }
} catch (error) { } catch (error) {