Opera8 commited on
Commit
56c74ce
·
verified ·
1 Parent(s): 58f76b4

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +197 -0
templates/index.html ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fa" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Gemini Real-time TTS</title>
6
+ <style>
7
+ 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; }
8
+ .container { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); width: 100%; max-width: 600px; }
9
+ h1 { text-align: center; color: #333; }
10
+ textarea { width: 100%; padding: 10px; font-size: 16px; border-radius: 5px; border: 1px solid #ccc; margin-bottom: 15px; box-sizing: border-box; resize: vertical; }
11
+ .button-container { display: flex; gap: 10px; }
12
+ button { flex-grow: 1; padding: 12px; font-size: 18px; border: none; border-radius: 5px; color: white; cursor: pointer; transition: background-color 0.2s; }
13
+ #speak-button { background-color: #007bff; }
14
+ #stop-button { background-color: #dc3545; }
15
+ button:disabled { background-color: #a0cfff; cursor: not-allowed; }
16
+ #stop-button:disabled { background-color: #f5c6cb; }
17
+ #status { margin-top: 15px; text-align: center; color: #555; font-style: italic; }
18
+ #audio-player-container { margin-top: 20px; }
19
+ audio { width: 100%; }
20
+ </style>
21
+ </head>
22
+ <body>
23
+ <div class="container">
24
+ <h1>🎙️ پخش صدای آنی Gemini</h1>
25
+ <textarea id="text-input" rows="4" placeholder="متن خود را اینجا وارد کنید..."></textarea>
26
+ <div class="button-container">
27
+ <button id="speak-button">صحبت کن</button>
28
+ <button id="stop-button" disabled>توقف</button>
29
+ </div>
30
+ <div id="status">در حال اتصال به سرور...</div>
31
+ <div id="audio-player-container" style="display: none;">
32
+ <p>پخش مجدد:</p>
33
+ <audio id="audio-player" controls></audio>
34
+ </div>
35
+ </div>
36
+
37
+ <script>
38
+ const textInput = document.getElementById('text-input');
39
+ const speakButton = document.getElementById('speak-button');
40
+ const stopButton = document.getElementById('stop-button');
41
+ const statusDiv = document.getElementById('status');
42
+ const audioPlayerContainer = document.getElementById('audio-player-container');
43
+ const audioPlayer = document.getElementById('audio-player');
44
+
45
+ let audioContext;
46
+ let audioQueue = [];
47
+ let sourceNodes = [];
48
+ let isPlaying = false;
49
+ let isStopped = false;
50
+ let nextStartTime = 0;
51
+ let socket;
52
+
53
+ function initializeAudio() {
54
+ if (!audioContext || audioContext.state === 'suspended') {
55
+ audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 24000 });
56
+ }
57
+ nextStartTime = audioContext.currentTime;
58
+ }
59
+
60
+ function getWebSocketURL() {
61
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
62
+ return `${protocol}//${window.location.host}/ws`;
63
+ }
64
+
65
+ function connectWebSocket() {
66
+ const wsURL = getWebSocketURL();
67
+ socket = new WebSocket(wsURL);
68
+
69
+ socket.onopen = () => {
70
+ statusDiv.textContent = "آماده دریافت متن";
71
+ speakButton.disabled = false;
72
+ };
73
+
74
+ socket.onmessage = async (event) => {
75
+ if (typeof event.data === 'string') {
76
+ const message = JSON.parse(event.data);
77
+ if (message.event === "STREAM_ENDED") {
78
+ handleStreamEnd(message.url);
79
+ } else if (message.event === "ERROR") {
80
+ statusDiv.textContent = `خطا: ${message.message}`;
81
+ resetUI();
82
+ }
83
+ } else {
84
+ if (isStopped) return;
85
+
86
+ const arrayBuffer = await event.data.arrayBuffer();
87
+ const pcmData = new Int16Array(arrayBuffer);
88
+
89
+ audioQueue.push(pcmData);
90
+
91
+ if (!isPlaying) {
92
+ playFromQueue();
93
+ }
94
+ }
95
+ };
96
+
97
+ socket.onclose = () => {
98
+ statusDiv.textContent = "اتصال قطع شد. تلاش مجدد...";
99
+ resetUI(true);
100
+ setTimeout(connectWebSocket, 3000);
101
+ };
102
+ }
103
+
104
+ async function playFromQueue() {
105
+ if (audioQueue.length === 0 || isStopped) {
106
+ isPlaying = false;
107
+ return;
108
+ }
109
+
110
+ isPlaying = true;
111
+
112
+ while (audioQueue.length > 0) {
113
+ const pcmData = audioQueue.shift();
114
+
115
+ const float32Data = new Float32Array(pcmData.length);
116
+ for (let i = 0; i < pcmData.length; i++) {
117
+ float32Data[i] = pcmData[i] / 32768.0;
118
+ }
119
+
120
+ const audioBuffer = audioContext.createBuffer(1, float32Data.length, audioContext.sampleRate);
121
+ audioBuffer.getChannelData(0).set(float32Data);
122
+
123
+ const source = audioContext.createBufferSource();
124
+ source.buffer = audioBuffer;
125
+ source.connect(audioContext.destination);
126
+
127
+ const currentTime = audioContext.currentTime;
128
+ if (nextStartTime < currentTime) {
129
+ nextStartTime = currentTime;
130
+ }
131
+
132
+ source.start(nextStartTime);
133
+ sourceNodes.push(source);
134
+
135
+ nextStartTime += audioBuffer.duration;
136
+ }
137
+
138
+ isPlaying = false;
139
+ }
140
+
141
+ function handleStreamEnd(audioUrl) {
142
+ if (audioUrl) {
143
+ audioPlayer.src = audioUrl;
144
+ audioPlayerContainer.style.display = 'block';
145
+ }
146
+ const checkPlaybackEnd = setInterval(() => {
147
+ if (audioQueue.length === 0 && audioContext.currentTime > nextStartTime) {
148
+ if(!isStopped) {
149
+ statusDiv.textContent = "پخش تمام شد.";
150
+ resetUI();
151
+ }
152
+ clearInterval(checkPlaybackEnd);
153
+ }
154
+ }, 100);
155
+ }
156
+
157
+ function resetUI(isConnectionError = false) {
158
+ speakButton.disabled = isConnectionError;
159
+ stopButton.disabled = true;
160
+ isPlaying = false;
161
+ }
162
+
163
+ speakButton.addEventListener('click', () => {
164
+ const text = textInput.value.trim();
165
+ if (!text || !socket || socket.readyState !== WebSocket.OPEN) return;
166
+
167
+ initializeAudio();
168
+ isStopped = false;
169
+
170
+ audioQueue = [];
171
+ sourceNodes.forEach(source => source.stop());
172
+ sourceNodes = [];
173
+
174
+ socket.send(text);
175
+
176
+ speakButton.disabled = true;
177
+ stopButton.disabled = false;
178
+ statusDiv.textContent = "در حال دریافت و پخش صدا...";
179
+
180
+ audioPlayerContainer.style.display = 'none';
181
+ audioPlayer.src = "";
182
+ });
183
+
184
+ stopButton.addEventListener('click', () => {
185
+ isStopped = true;
186
+ audioQueue = [];
187
+ sourceNodes.forEach(source => source.stop());
188
+ sourceNodes = [];
189
+ statusDiv.textContent = "پخش متوقف شد.";
190
+ resetUI();
191
+ });
192
+
193
+ window.addEventListener('load', connectWebSocket);
194
+ </script>
195
+
196
+ </body>
197
+ </html>