fix: [cm-song] 나레이션 제작 TTS 오디오 재생 버튼 미표시 수정
- TTS try-catch 블록 누락 수정 (JS 구문 오류 해결) - audioReady display:flex 명시적 설정 (hidden 제거 후 레이아웃 보장)
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user