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 4cf208e2d8
commit 56ab5d86b6

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 id="audioSection" class="hidden">
<div id="audioSection">
<div class="flex flex-col items-center gap-4">
<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
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 id="audioLoading" class="flex flex-col items-center gap-2">
<div class="w-16 h-16 bg-gray-200 rounded-full flex items-center justify-center">
<i class="ri-loader-4-line text-2xl text-gray-400 animate-spin"></i>
</div>
<p class="text-sm text-gray-400 font-medium">음성 생성 ...</p>
</div>
<!-- 재생 버튼 (음성 준비 완료 ) -->
<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>
@@ -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 playIcon = document.getElementById('playIcon');
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 saveBtn = document.getElementById('saveBtn');
const retryBtn = document.getElementById('retryBtn');
@@ -306,7 +326,10 @@ function showState(state) {
generateBtn.innerHTML = '<i class="ri-loader-4-line animate-spin"></i> 나레이션 제작 중...';
showState('loading');
loadingText.textContent = 'AI가 나레이션을 작성하고 있어요...';
audioSection.classList.add('hidden');
audioLoading.classList.remove('hidden');
audioReady.classList.add('hidden');
audioReady.style.display = '';
audioError.classList.add('hidden');
currentAudioData = null;
currentAudioMimeType = null;
currentAudioBlob = null;
@@ -340,38 +363,48 @@ function showState(state) {
showState('result');
// 2. TTS 음성 생성
loadingText.textContent = '목소리를 녹음하고 있어요...';
const audioRes = await fetch('{{ route("rd.cm-song.generate-audio") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}',
},
body: JSON.stringify({ lyrics: lyricsData.lyrics }),
});
try {
const audioRes = await fetch('{{ route("rd.cm-song.generate-audio") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}',
},
body: JSON.stringify({ lyrics: lyricsData.lyrics }),
});
const audioData = await audioRes.json();
if (audioData.success && audioData.audio_data) {
currentAudioData = audioData.audio_data;
currentAudioMimeType = audioData.mime_type || 'audio/L16;rate=24000';
const audioData = await audioRes.json();
if (audioData.success && audioData.audio_data) {
currentAudioData = audioData.audio_data;
currentAudioMimeType = audioData.mime_type || 'audio/L16;rate=24000';
const mimeType = currentAudioMimeType;
let audioUrl;
const mimeType = currentAudioMimeType;
let audioUrl;
if (mimeType.includes('L16') || mimeType.includes('pcm')) {
const rateMatch = mimeType.match(/rate=(\d+)/);
const rate = rateMatch ? parseInt(rateMatch[1]) : 24000;
const pcmBytes = base64ToUint8Array(audioData.audio_data);
currentAudioBlob = pcmToWav(pcmBytes, rate);
audioUrl = URL.createObjectURL(currentAudioBlob);
if (mimeType.includes('L16') || mimeType.includes('pcm')) {
const rateMatch = mimeType.match(/rate=(\d+)/);
const rate = rateMatch ? parseInt(rateMatch[1]) : 24000;
const pcmBytes = base64ToUint8Array(audioData.audio_data);
currentAudioBlob = pcmToWav(pcmBytes, rate);
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 {
const rawBytes = base64ToUint8Array(audioData.audio_data);
currentAudioBlob = new Blob([rawBytes], { type: mimeType });
audioUrl = URL.createObjectURL(currentAudioBlob);
audioLoading.classList.add('hidden');
audioError.classList.remove('hidden');
}
audioPlayer.src = audioUrl;
audioSection.classList.remove('hidden');
} catch (audioErr) {
console.error('음성 생성 오류:', audioErr);
audioLoading.classList.add('hidden');
audioError.classList.remove('hidden');
}
} catch (error) {