ttsfm / templates /websocket_demo.html
NitinBot001's picture
Upload 20 files
bf90fc9 verified
{% extends "base.html" %}
{% block title %}{{ _('websocket.title', 'WebSocket Streaming Demo') }} - TTSFM{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row">
<div class="col-lg-10 mx-auto">
<h1 class="text-center mb-4">
<i class="fas fa-bolt text-warning"></i>
{{ _('websocket.title', 'WebSocket Streaming Demo') }}
</h1>
<!-- Connection Status -->
<div class="alert alert-info" id="connection-status">
<i class="fas fa-plug me-2"></i>
<span id="status-text">Connecting to WebSocket server...</span>
</div>
<!-- Input Form -->
<div class="card shadow-sm mb-4">
<div class="card-body">
<h5 class="card-title">{{ _('playground.generate_speech', 'Generate Speech') }}</h5>
<form id="streaming-form">
<div class="mb-3">
<label for="text-input" class="form-label">
{{ _('playground.text_input', 'Text to Convert') }}
</label>
<textarea
class="form-control"
id="text-input"
rows="4"
maxlength="4096"
placeholder="{{ _('playground.text_placeholder', 'Enter your text here...') }}"
>Experience the future of text-to-speech with real-time WebSocket streaming! This innovative feature delivers audio chunks as they're generated, providing a more responsive and engaging user experience.</textarea>
<div class="form-text">
<i class="fas fa-info-circle me-1"></i>
Streaming will split text into chunks for real-time delivery
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="voice-select" class="form-label">
{{ _('playground.voice', 'Voice') }}
</label>
<select class="form-select" id="voice-select">
<option value="alloy">Alloy</option>
<option value="echo">Echo</option>
<option value="fable">Fable</option>
<option value="onyx">Onyx</option>
<option value="nova">Nova</option>
<option value="shimmer">Shimmer</option>
</select>
</div>
<div class="col-md-6 mb-3">
<label for="format-select" class="form-label">
{{ _('playground.format', 'Audio Format') }}
</label>
<select class="form-select" id="format-select">
<option value="mp3">MP3</option>
<option value="wav">WAV</option>
<option value="opus">OPUS</option>
</select>
</div>
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<button type="submit" class="btn btn-primary" id="stream-btn">
<i class="fas fa-bolt me-2"></i>
Start Streaming
</button>
<button type="button" class="btn btn-danger" id="cancel-btn" style="display: none;">
<i class="fas fa-stop me-2"></i>
Cancel
</button>
</div>
</form>
</div>
</div>
<!-- Progress Section -->
<div class="card shadow-sm mb-4" id="progress-section" style="display: none;">
<div class="card-body">
<h5 class="card-title">Streaming Progress</h5>
<div class="progress mb-3" style="height: 25px;">
<div
class="progress-bar progress-bar-striped progress-bar-animated"
id="progress-bar"
role="progressbar"
style="width: 0%"
>
<span id="progress-text">0%</span>
</div>
</div>
<div class="row text-center">
<div class="col-md-4">
<h6>Chunks Received</h6>
<p class="h4"><span id="chunks-received">0</span> / <span id="total-chunks">0</span></p>
</div>
<div class="col-md-4">
<h6>Data Transferred</h6>
<p class="h4" id="data-transferred">0 KB</p>
</div>
<div class="col-md-4">
<h6>Generation Time</h6>
<p class="h4" id="generation-time">0.0s</p>
</div>
</div>
</div>
</div>
<!-- Audio Chunks Display -->
<div class="card shadow-sm mb-4" id="chunks-section" style="display: none;">
<div class="card-body">
<h5 class="card-title">Audio Chunks</h5>
<div id="chunks-container" class="row g-2">
<!-- Chunks will be added here dynamically -->
</div>
</div>
</div>
<!-- Final Audio Player -->
<div class="card shadow-sm" id="audio-section" style="display: none;">
<div class="card-body">
<h5 class="card-title">Generated Audio</h5>
<audio id="audio-player" controls class="w-100"></audio>
<div class="mt-2">
<button class="btn btn-success" id="download-btn">
<i class="fas fa-download me-2"></i>
Download Audio
</button>
</div>
</div>
</div>
<!-- Info Section -->
<div class="card shadow-sm mt-4">
<div class="card-body">
<h5 class="card-title">
<i class="fas fa-info-circle text-info me-2"></i>
About WebSocket Streaming
</h5>
<p>
This demo showcases real-time audio streaming using WebSockets. Instead of waiting
for the entire audio to be generated, you receive chunks as they're processed,
providing immediate feedback and a more responsive experience.
</p>
<ul>
<li><strong>Lower Perceived Latency:</strong> Start receiving audio before generation completes</li>
<li><strong>Progress Tracking:</strong> Real-time updates on generation progress</li>
<li><strong>Cancellable:</strong> Stop generation mid-stream if needed</li>
<li><strong>Efficient:</strong> Stream chunks as they're ready, no waiting</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Include Socket.IO -->
<script src="https://cdn.socket.io/4.6.0/socket.io.min.js"></script>
<!-- Include our WebSocket client -->
<script src="{{ url_for('static', filename='js/websocket-tts.js') }}"></script>
<script>
// Initialize WebSocket client
let wsClient = null;
let currentRequestId = null;
let startTime = null;
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
// Create WebSocket client
wsClient = new WebSocketTTSClient({
debug: true,
onConnect: () => {
updateConnectionStatus('connected');
},
onDisconnect: () => {
updateConnectionStatus('disconnected');
},
onError: (error) => {
updateConnectionStatus('error');
showError(`Connection error: ${error.message}`);
}
});
// Form submission
document.getElementById('streaming-form').addEventListener('submit', handleStreamingSubmit);
// Cancel button
document.getElementById('cancel-btn').addEventListener('click', handleCancel);
});
function updateConnectionStatus(status) {
const statusEl = document.getElementById('connection-status');
const statusText = document.getElementById('status-text');
statusEl.className = 'alert';
switch(status) {
case 'connected':
statusEl.classList.add('alert-success');
statusText.innerHTML = '<i class="fas fa-check-circle me-2"></i>Connected to WebSocket server';
break;
case 'disconnected':
statusEl.classList.add('alert-warning');
statusText.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>Disconnected from server';
break;
case 'error':
statusEl.classList.add('alert-danger');
statusText.innerHTML = '<i class="fas fa-times-circle me-2"></i>Connection error';
break;
default:
statusEl.classList.add('alert-info');
statusText.innerHTML = '<i class="fas fa-plug me-2"></i>Connecting...';
}
}
async function handleStreamingSubmit(e) {
e.preventDefault();
if (!wsClient || !wsClient.isConnected()) {
showError('WebSocket not connected. Please refresh the page.');
return;
}
// Get form values
const text = document.getElementById('text-input').value.trim();
const voice = document.getElementById('voice-select').value;
const format = document.getElementById('format-select').value;
if (!text) {
showError('Please enter some text to convert.');
return;
}
// Reset UI
resetUI();
// Show progress section
document.getElementById('progress-section').style.display = 'block';
document.getElementById('chunks-section').style.display = 'block';
document.getElementById('stream-btn').disabled = true;
document.getElementById('cancel-btn').style.display = 'inline-block';
startTime = Date.now();
try {
const result = await wsClient.generateSpeech(text, {
voice: voice,
format: format,
chunkSize: 512, // Smaller chunks for more updates
onStart: (data) => {
currentRequestId = data.request_id;
console.log('Stream started:', data);
},
onProgress: (progress) => {
updateProgress(progress);
},
onChunk: (chunk) => {
handleAudioChunk(chunk);
},
onComplete: (result) => {
handleStreamComplete(result);
},
onError: (error) => {
showError(`Streaming error: ${error.message}`);
}
});
console.log('Streaming completed:', result);
} catch (error) {
showError(`Failed to generate speech: ${error.message}`);
resetUI();
}
}
function updateProgress(progress) {
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const chunksReceived = document.getElementById('chunks-received');
const totalChunks = document.getElementById('total-chunks');
const generationTime = document.getElementById('generation-time');
progressBar.style.width = `${progress.progress}%`;
progressText.textContent = `${progress.progress}%`;
chunksReceived.textContent = progress.chunksCompleted;
totalChunks.textContent = progress.totalChunks;
if (startTime) {
const elapsed = (Date.now() - startTime) / 1000;
generationTime.textContent = `${elapsed.toFixed(1)}s`;
}
}
function handleAudioChunk(chunk) {
const container = document.getElementById('chunks-container');
// Create chunk visualization
const chunkEl = document.createElement('div');
chunkEl.className = 'col-auto';
chunkEl.innerHTML = `
<div class="badge bg-primary p-2" title="Chunk ${chunk.chunkIndex + 1}">
<i class="fas fa-music me-1"></i>
${chunk.chunkIndex + 1}
<small class="d-block">${(chunk.audioData.byteLength / 1024).toFixed(1)}KB</small>
</div>
`;
container.appendChild(chunkEl);
// Update data transferred
const currentData = parseFloat(document.getElementById('data-transferred').textContent);
const newData = currentData + (chunk.audioData.byteLength / 1024);
document.getElementById('data-transferred').textContent = `${newData.toFixed(1)} KB`;
}
function handleStreamComplete(result) {
// Create blob from combined audio
const blob = new Blob([result.audioData], { type: `audio/${result.format}` });
const url = URL.createObjectURL(blob);
// Set up audio player
const audioPlayer = document.getElementById('audio-player');
audioPlayer.src = url;
// Show audio section
document.getElementById('audio-section').style.display = 'block';
// Set up download button
document.getElementById('download-btn').onclick = () => {
const a = document.createElement('a');
a.href = url;
a.download = `tts_stream_${Date.now()}.${result.format}`;
a.click();
};
// Update final stats
document.getElementById('generation-time').textContent = `${(result.generationTime / 1000).toFixed(2)}s`;
// Reset buttons
document.getElementById('stream-btn').disabled = false;
document.getElementById('cancel-btn').style.display = 'none';
// Update progress bar to success
const progressBar = document.getElementById('progress-bar');
progressBar.classList.remove('progress-bar-animated');
progressBar.classList.add('bg-success');
}
function handleCancel() {
if (currentRequestId) {
wsClient.cancelStream(currentRequestId);
showInfo('Stream cancelled');
resetUI();
}
}
function resetUI() {
document.getElementById('progress-section').style.display = 'none';
document.getElementById('chunks-section').style.display = 'none';
document.getElementById('audio-section').style.display = 'none';
document.getElementById('stream-btn').disabled = false;
document.getElementById('cancel-btn').style.display = 'none';
document.getElementById('chunks-container').innerHTML = '';
document.getElementById('progress-bar').style.width = '0%';
document.getElementById('progress-bar').className = 'progress-bar progress-bar-striped progress-bar-animated';
document.getElementById('data-transferred').textContent = '0 KB';
currentRequestId = null;
startTime = null;
}
function showError(message) {
console.error(message);
// You could add a toast notification here
}
function showInfo(message) {
console.info(message);
// You could add a toast notification here
}
</script>
{% endblock %}