ArunKr commited on
Commit
33973ad
·
verified ·
1 Parent(s): dbacbf7

Create static/clients.html

Browse files
Files changed (1) hide show
  1. static/clients.html +161 -0
static/clients.html ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Kokoro SSE Client (Buffer → Play)</title>
7
+ <style>
8
+ :root { color-scheme: dark; }
9
+ body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell; background: #0b1220; color: #e5e7eb; }
10
+ .wrap { max-width: 920px; margin: 0 auto; padding: 24px; }
11
+ input, textarea, select, button {
12
+ background: #0f172a; color: #e5e7eb; border: 1px solid #334155; border-radius: 8px; padding: 10px 12px; width: 100%;
13
+ }
14
+ button { cursor: pointer; }
15
+ .row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
16
+ .mt { margin-top: 12px; }
17
+ .log { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; background:#0f172a; padding:12px; border-radius:10px; min-height:120px; white-space: pre-wrap;}
18
+ .pill { display:inline-block; padding:2px 8px; border:1px solid #334155; border-radius:9999px; font-size:12px; }
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <div class="wrap">
23
+ <h1>Kokoro TTS — <span class="pill">SSE</span></h1>
24
+ <p>This demo <b>buffers</b> audio from SSE and plays after the stream completes (simpler & reliable). For live playback, see the Python + ffplay client.</p>
25
+ <textarea id="text" rows="5" placeholder="Type something to synthesize...">Hello from Kokoro streaming via Server-Sent Events!</textarea>
26
+ <div class="row mt">
27
+ <div>
28
+ <label>Voice</label>
29
+ <input id="voice" value="af_sarah" />
30
+ </div>
31
+ <div>
32
+ <label>Speed (0.5 - 1.5)</label>
33
+ <input id="speed" value="1.0" />
34
+ </div>
35
+ </div>
36
+ <div class="row mt">
37
+ <div>
38
+ <label>Lang</label>
39
+ <input id="lang" value="en-us" />
40
+ </div>
41
+ <div style="display:flex;align-items:flex-end; gap:12px;">
42
+ <button id="start">Start SSE</button>
43
+ <button id="stop">Stop</button>
44
+ </div>
45
+ </div>
46
+ <div class="mt">
47
+ <audio id="player" controls></audio>
48
+ </div>
49
+ <div class="mt log" id="log"></div>
50
+ </div>
51
+
52
+ <script>
53
+ const log = (...args) => {
54
+ const el = document.getElementById('log');
55
+ el.textContent += args.join(' ') + "\n";
56
+ el.scrollTop = el.scrollHeight;
57
+ console.log(...args);
58
+ };
59
+
60
+ let es = null;
61
+ let chunks = []; // Uint8Arrays of PCM16
62
+ let total = 0;
63
+
64
+ function stopSSE() {
65
+ if (es) {
66
+ es.close();
67
+ es = null;
68
+ log("SSE closed.");
69
+ }
70
+ }
71
+
72
+ document.getElementById('stop').onclick = stopSSE;
73
+
74
+ document.getElementById('start').onclick = async () => {
75
+ stopSSE();
76
+ chunks = [];
77
+ total = 0;
78
+ document.getElementById('player').src = "";
79
+ const text = encodeURIComponent(document.getElementById('text').value);
80
+ const voice = encodeURIComponent(document.getElementById('voice').value || 'af_sarah');
81
+ const speed = encodeURIComponent(document.getElementById('speed').value || '1.0');
82
+ const lang = encodeURIComponent(document.getElementById('lang').value || 'en-us');
83
+ const url = `/v1/tts.sse?text=${text}&voice=${voice}&speed=${speed}&lang=${lang}`;
84
+ log("Connecting:", url);
85
+ es = new EventSource(url);
86
+
87
+ es.onmessage = (ev) => {
88
+ try {
89
+ const obj = JSON.parse(ev.data);
90
+ const b = atob(obj.pcm16);
91
+ const u8 = new Uint8Array(b.length);
92
+ for (let i = 0; i < b.length; i++) u8[i] = b.charCodeAt(i);
93
+ chunks.push(u8);
94
+ total += u8.byteLength;
95
+ if ((obj.seq % 10) === 0) log(`Chunk #${obj.seq}, +${u8.byteLength} bytes`);
96
+ } catch (err) {
97
+ log("Parse error:", err);
98
+ }
99
+ };
100
+
101
+ es.addEventListener('done', async (ev) => {
102
+ log("Done:", ev.data);
103
+ stopSSE();
104
+ // merge into a WAV and play
105
+ const wav = buildWavFromPCM16Chunks(chunks, 24000, 1);
106
+ const blob = new Blob([wav], { type: "audio/wav" });
107
+ const url = URL.createObjectURL(blob);
108
+ const player = document.getElementById('player');
109
+ player.src = url;
110
+ player.play();
111
+ log(`Total bytes: ${total}, WAV bytes: ${wav.byteLength}`);
112
+ });
113
+
114
+ es.onerror = (e) => {
115
+ log("SSE error:", e?.message || e);
116
+ };
117
+ };
118
+
119
+ function buildWavFromPCM16Chunks(chunks, sampleRate = 24000, channels = 1) {
120
+ let totalBytes = 0;
121
+ for (const c of chunks) totalBytes += c.byteLength;
122
+ const dataSize = totalBytes;
123
+ const headerSize = 44;
124
+ const buffer = new ArrayBuffer(headerSize + dataSize);
125
+ const view = new DataView(buffer);
126
+
127
+ // RIFF header
128
+ writeStr(view, 0, "RIFF");
129
+ view.setUint32(4, 36 + dataSize, true);
130
+ writeStr(view, 8, "WAVE");
131
+
132
+ // fmt chunk
133
+ writeStr(view, 12, "fmt ");
134
+ view.setUint32(16, 16, true); // PCM
135
+ view.setUint16(20, 1, true); // PCM format
136
+ view.setUint16(22, channels, true);
137
+ view.setUint32(24, sampleRate, true);
138
+ const byteRate = sampleRate * channels * 2; // 16-bit
139
+ view.setUint32(28, byteRate, true);
140
+ view.setUint16(32, channels * 2, true); // block align
141
+ view.setUint16(34, 16, true); // bits per sample
142
+
143
+ // data chunk
144
+ writeStr(view, 36, "data");
145
+ view.setUint32(40, dataSize, true);
146
+
147
+ // write PCM data
148
+ let offset = 44;
149
+ for (const c of chunks) {
150
+ new Uint8Array(buffer, offset, c.byteLength).set(c);
151
+ offset += c.byteLength;
152
+ }
153
+ return new Uint8Array(buffer);
154
+ }
155
+
156
+ function writeStr(view, offset, str) {
157
+ for (let i = 0; i < str.length; i++) view.setUint8(offset + i, str.charCodeAt(i));
158
+ }
159
+ </script>
160
+ </body>
161
+ </html>