Ttslive / templates /index.html
Opera8's picture
Create templates/index.html
56c74ce verified
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<title>Gemini Real-time TTS</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background-color: #f0f2f5; margin: 0; padding: 20px; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
.container { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); width: 100%; max-width: 600px; }
h1 { text-align: center; color: #333; }
textarea { width: 100%; padding: 10px; font-size: 16px; border-radius: 5px; border: 1px solid #ccc; margin-bottom: 15px; box-sizing: border-box; resize: vertical; }
.button-container { display: flex; gap: 10px; }
button { flex-grow: 1; padding: 12px; font-size: 18px; border: none; border-radius: 5px; color: white; cursor: pointer; transition: background-color 0.2s; }
#speak-button { background-color: #007bff; }
#stop-button { background-color: #dc3545; }
button:disabled { background-color: #a0cfff; cursor: not-allowed; }
#stop-button:disabled { background-color: #f5c6cb; }
#status { margin-top: 15px; text-align: center; color: #555; font-style: italic; }
#audio-player-container { margin-top: 20px; }
audio { width: 100%; }
</style>
</head>
<body>
<div class="container">
<h1>🎙️ پخش صدای آنی Gemini</h1>
<textarea id="text-input" rows="4" placeholder="متن خود را اینجا وارد کنید..."></textarea>
<div class="button-container">
<button id="speak-button">صحبت کن</button>
<button id="stop-button" disabled>توقف</button>
</div>
<div id="status">در حال اتصال به سرور...</div>
<div id="audio-player-container" style="display: none;">
<p>پخش مجدد:</p>
<audio id="audio-player" controls></audio>
</div>
</div>
<script>
const textInput = document.getElementById('text-input');
const speakButton = document.getElementById('speak-button');
const stopButton = document.getElementById('stop-button');
const statusDiv = document.getElementById('status');
const audioPlayerContainer = document.getElementById('audio-player-container');
const audioPlayer = document.getElementById('audio-player');
let audioContext;
let audioQueue = [];
let sourceNodes = [];
let isPlaying = false;
let isStopped = false;
let nextStartTime = 0;
let socket;
function initializeAudio() {
if (!audioContext || audioContext.state === 'suspended') {
audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 24000 });
}
nextStartTime = audioContext.currentTime;
}
function getWebSocketURL() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
return `${protocol}//${window.location.host}/ws`;
}
function connectWebSocket() {
const wsURL = getWebSocketURL();
socket = new WebSocket(wsURL);
socket.onopen = () => {
statusDiv.textContent = "آماده دریافت متن";
speakButton.disabled = false;
};
socket.onmessage = async (event) => {
if (typeof event.data === 'string') {
const message = JSON.parse(event.data);
if (message.event === "STREAM_ENDED") {
handleStreamEnd(message.url);
} else if (message.event === "ERROR") {
statusDiv.textContent = `خطا: ${message.message}`;
resetUI();
}
} else {
if (isStopped) return;
const arrayBuffer = await event.data.arrayBuffer();
const pcmData = new Int16Array(arrayBuffer);
audioQueue.push(pcmData);
if (!isPlaying) {
playFromQueue();
}
}
};
socket.onclose = () => {
statusDiv.textContent = "اتصال قطع شد. تلاش مجدد...";
resetUI(true);
setTimeout(connectWebSocket, 3000);
};
}
async function playFromQueue() {
if (audioQueue.length === 0 || isStopped) {
isPlaying = false;
return;
}
isPlaying = true;
while (audioQueue.length > 0) {
const pcmData = audioQueue.shift();
const float32Data = new Float32Array(pcmData.length);
for (let i = 0; i < pcmData.length; i++) {
float32Data[i] = pcmData[i] / 32768.0;
}
const audioBuffer = audioContext.createBuffer(1, float32Data.length, audioContext.sampleRate);
audioBuffer.getChannelData(0).set(float32Data);
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
const currentTime = audioContext.currentTime;
if (nextStartTime < currentTime) {
nextStartTime = currentTime;
}
source.start(nextStartTime);
sourceNodes.push(source);
nextStartTime += audioBuffer.duration;
}
isPlaying = false;
}
function handleStreamEnd(audioUrl) {
if (audioUrl) {
audioPlayer.src = audioUrl;
audioPlayerContainer.style.display = 'block';
}
const checkPlaybackEnd = setInterval(() => {
if (audioQueue.length === 0 && audioContext.currentTime > nextStartTime) {
if(!isStopped) {
statusDiv.textContent = "پخش تمام شد.";
resetUI();
}
clearInterval(checkPlaybackEnd);
}
}, 100);
}
function resetUI(isConnectionError = false) {
speakButton.disabled = isConnectionError;
stopButton.disabled = true;
isPlaying = false;
}
speakButton.addEventListener('click', () => {
const text = textInput.value.trim();
if (!text || !socket || socket.readyState !== WebSocket.OPEN) return;
initializeAudio();
isStopped = false;
audioQueue = [];
sourceNodes.forEach(source => source.stop());
sourceNodes = [];
socket.send(text);
speakButton.disabled = true;
stopButton.disabled = false;
statusDiv.textContent = "در حال دریافت و پخش صدا...";
audioPlayerContainer.style.display = 'none';
audioPlayer.src = "";
});
stopButton.addEventListener('click', () => {
isStopped = true;
audioQueue = [];
sourceNodes.forEach(source => source.stop());
sourceNodes = [];
statusDiv.textContent = "پخش متوقف شد.";
resetUI();
});
window.addEventListener('load', connectWebSocket);
</script>
</body>
</html>