From f82bffa6fd63ec04a676fe79567a5aeb2fe9b0ad Mon Sep 17 00:00:00 2001 From: luke-speechify <289678208+luke-speechify@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:10:38 +0100 Subject: [PATCH] feat: add streaming-tts-karaoke demo Realtime streaming TTS on POST /v1/audio/stream/with-timestamps (SSE): each word highlights the instant its audio + timestamp arrive over the wire, then a second highlight follows playback via the MediaSource API. A one-route Node proxy pipes the SSE through so the API key stays server-side. Hosted under /streaming-tts-karaoke, Turnstile-gated, with a real-API Playwright e2e. --- README.md | 1 + demos/streaming-tts-karaoke/.env.example | 1 + demos/streaming-tts-karaoke/.gitignore | 11 + demos/streaming-tts-karaoke/README.md | 65 ++++ .../app/api/stream/route.ts | 63 ++++ demos/streaming-tts-karaoke/app/globals.css | 117 +++++++ demos/streaming-tts-karaoke/app/layout.tsx | 21 ++ .../app/lib/turnstile.ts | 37 ++ demos/streaming-tts-karaoke/app/page.tsx | 323 ++++++++++++++++++ demos/streaming-tts-karaoke/demo.json | 6 + .../e2e/streaming-tts-karaoke.spec.ts | 44 +++ demos/streaming-tts-karaoke/next.config.ts | 11 + demos/streaming-tts-karaoke/package.json | 27 ++ .../playwright.config.ts | 20 ++ demos/streaming-tts-karaoke/tsconfig.json | 41 +++ pnpm-lock.yaml | 72 +++- pnpm-workspace.yaml | 1 + site/public/index.html | 4 +- vercel.json | 8 + 19 files changed, 867 insertions(+), 6 deletions(-) create mode 100644 demos/streaming-tts-karaoke/.env.example create mode 100644 demos/streaming-tts-karaoke/.gitignore create mode 100644 demos/streaming-tts-karaoke/README.md create mode 100644 demos/streaming-tts-karaoke/app/api/stream/route.ts create mode 100644 demos/streaming-tts-karaoke/app/globals.css create mode 100644 demos/streaming-tts-karaoke/app/layout.tsx create mode 100644 demos/streaming-tts-karaoke/app/lib/turnstile.ts create mode 100644 demos/streaming-tts-karaoke/app/page.tsx create mode 100644 demos/streaming-tts-karaoke/demo.json create mode 100644 demos/streaming-tts-karaoke/e2e/streaming-tts-karaoke.spec.ts create mode 100644 demos/streaming-tts-karaoke/next.config.ts create mode 100644 demos/streaming-tts-karaoke/package.json create mode 100644 demos/streaming-tts-karaoke/playwright.config.ts create mode 100644 demos/streaming-tts-karaoke/tsconfig.json diff --git a/README.md b/README.md index 6434db4..cd1ff5b 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Demos with a **Live** link run in your browser at [demos.speechify.ai](https://d | [`demos/voice-agent-showcase/`](./demos/voice-agent-showcase) | Cloudflare Workers | | One page, ten live Voice Agents API demos: calendar booking, policy-bound support, a page copilot, form intake, US outbound calls with a 5-minute cap, a voice gallery, mid-call language handoff, cross-call memory, a grounded knowledge base, and dual-control troubleshooting. | | [`demos/vercel-ai-sdk/`](./demos/vercel-ai-sdk) | TypeScript (Vercel AI SDK) | | Speechify TTS through the Vercel AI SDK's unified `generateSpeech` interface via the official `@speechify/vercel` provider — one-line swap from OpenAI/ElevenLabs, plus word-level speech marks from `providerMetadata`. | | [`demos/puter-txt2speech/`](./demos/puter-txt2speech) | HTML (puter.js) | | Speaks with a Simba 3.2 voice via the Speechify provider in Puter's puter.ai.txt2speech() — one static page, your key configured once on the Puter instance. | +| [`demos/streaming-tts-karaoke/`](./demos/streaming-tts-karaoke) | Next.js | [Open](https://demos.speechify.ai/streaming-tts-karaoke) | Realtime TTS with word timestamps: each word lights up the instant its audio arrives over the stream, then again as it plays. Key held server-side. | ## Get an API key diff --git a/demos/streaming-tts-karaoke/.env.example b/demos/streaming-tts-karaoke/.env.example new file mode 100644 index 0000000..534cec4 --- /dev/null +++ b/demos/streaming-tts-karaoke/.env.example @@ -0,0 +1 @@ +SPEECHIFY_API_KEY=your_api_key_here diff --git a/demos/streaming-tts-karaoke/.gitignore b/demos/streaming-tts-karaoke/.gitignore new file mode 100644 index 0000000..4c516b0 --- /dev/null +++ b/demos/streaming-tts-karaoke/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +.next/ +.env +next-env.d.ts +*.tsbuildinfo + +# Playwright +test-results/ +playwright-report/ +/.playwright/ +.last-run.json diff --git a/demos/streaming-tts-karaoke/README.md b/demos/streaming-tts-karaoke/README.md new file mode 100644 index 0000000..08277bc --- /dev/null +++ b/demos/streaming-tts-karaoke/README.md @@ -0,0 +1,65 @@ +# Streaming TTS karaoke + +Realtime text-to-speech with word timestamps. Speechify's streaming endpoint +returns audio **and** per-word timings as it synthesizes, so this demo shows two +highlights at once: + +1. **Received** — each word lights up the instant its audio + timestamp arrive + over the stream. This is the wire speed: the marks race ahead of playback. +2. **Playing** — a second highlight follows the actual audio position as it plays + back through the browser. + +The Speechify API key never reaches the browser — the page talks to a one-route +server proxy. + +Pairs with the Speechify post *Realtime streaming TTS with word highlighting*. + +## What you get + +- **[`app/api/stream/route.ts`](./app/api/stream/route.ts)** — a Node route that + proxies `POST /v1/audio/stream/with-timestamps` (Server-Sent Events) and pipes + it straight to the browser, key held server-side. +- **[`app/page.tsx`](./app/page.tsx)** — parses the SSE `speech.chunk` events, + maps each word mark to the rendered text by character offset (the "received" + highlight), streams the base64 audio into a `