Last active
December 26, 2025 03:28
-
-
Save solarkraft/2d4b42c8d3fbd0c985ed7f8a87420d29 to your computer and use it in GitHub Desktop.
Simple demo of live audio transcription using the Deepgram Live Audio API (api.deepgram.com/v1/listen) via the Deepgram SDK
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Deepgram Live Demo</title> | |
| <script src="https://cdn.jsdelivr.net/npm/@deepgram/sdk@v4.11.2"></script> | |
| <style> | |
| body { | |
| font-family: sans-serif; | |
| max-width: 60rem; | |
| margin: 3rem auto; | |
| line-height: 1.5; | |
| text-align: left; | |
| } | |
| label { | |
| display: inline-block; | |
| width: 6rem; | |
| } | |
| input { | |
| padding: 0.5rem; | |
| font-size: 1rem; | |
| border-radius: 0.25rem; | |
| border: 1px solid #ccc; | |
| } | |
| button { | |
| padding: 0.5rem; | |
| font-size: 1rem; | |
| } | |
| .api-input { | |
| width: 20rem; | |
| } | |
| #captions { | |
| margin-top: 2rem; | |
| padding: 1.5rem; | |
| border: 1px solid #ccc; | |
| border-radius: 0.5rem; | |
| min-height: 10rem; | |
| font-size: 1.5rem; | |
| font-family: serif; | |
| } | |
| .interim { | |
| color: #666; | |
| font-style: italic; | |
| } | |
| .placeholder { | |
| color: #666; | |
| } | |
| .status-recording #captions { | |
| box-shadow: 0 0 0.5rem #007bff; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Deepgram Live Demo</h1> | |
| <p> | |
| This is a simple demo of live audio transcription using the <a | |
| href="https://developers.deepgram.com/reference/speech-to-text/listen-streaming" target="_blank">Deepgram | |
| Live Audio API</a> with the <a href="https://github.com/deepgram/sdk-js" target="_blank">Deepgram SDK</a>. | |
| </p> | |
| <p>This demo outputs Opus encoded audio, which is relatively simple and highly bandwidth efficient, but adds some | |
| latency.</p> | |
| <p> | |
| Enter your base URL and your API key below, then click "Start Transcribing" to begin. | |
| </p> | |
| <p> | |
| <label>Base URL:</label> | |
| <input type="text" id="apiUrl" class="api-input" value="https://api.deepgram.com"> | |
| </p> | |
| <p> | |
| <label>API Key:</label> | |
| <input type="password" id="apiKey" class="api-input"> | |
| </p> | |
| <button id="record">Start Transcribing</button> | |
| <div id="captions"> | |
| <span class="placeholder">Transcript will show up here ...</span> | |
| </div> | |
| <script> | |
| // Load saved API key and URL on page load | |
| window.addEventListener('DOMContentLoaded', () => { | |
| const savedApiKey = localStorage.getItem('deepgram_api_key'); | |
| if (savedApiKey) { | |
| document.getElementById('apiKey').value = savedApiKey; | |
| } | |
| const savedApiUrl = localStorage.getItem('deepgram_api_url'); | |
| if (savedApiUrl) { | |
| document.getElementById('apiUrl').value = savedApiUrl; | |
| } | |
| }); | |
| const captions = document.getElementById("captions"); | |
| const apiKeyInput = document.getElementById("apiKey"); | |
| const apiUrlInput = document.getElementById("apiUrl"); | |
| const recordButton = document.getElementById("record"); | |
| let microphone = null; | |
| let liveClient = null; | |
| let finalTranscript = ""; // Accumulate final results here | |
| async function getMicrophone() { | |
| const userMedia = await navigator.mediaDevices.getUserMedia({ | |
| audio: { | |
| channelCount: 1, | |
| sampleRate: 16000, | |
| echoCancellation: true, | |
| noiseSuppression: true, | |
| }, | |
| }); | |
| return new MediaRecorder(userMedia); | |
| } | |
| async function openMicrophone(microphone, liveClient) { | |
| await microphone.start(100); | |
| microphone.onstart = () => { | |
| console.log("client: microphone opened"); | |
| document.body.classList.add("status-recording"); | |
| recordButton.textContent = "Stop Transcribing"; | |
| }; | |
| microphone.onstop = () => { | |
| console.log("client: microphone closed"); | |
| document.body.classList.remove("status-recording"); | |
| recordButton.textContent = "Start Transcribing"; | |
| }; | |
| microphone.ondataavailable = (e) => { | |
| console.log("client: sent data to websocket"); | |
| liveClient.send(e.data); | |
| }; | |
| } | |
| async function closeMicrophone(microphone) { | |
| microphone.stop(); | |
| } | |
| async function startRecording() { | |
| const apiKey = apiKeyInput.value.trim(); | |
| const apiUrl = apiUrlInput.value.trim(); | |
| if (!apiKey) { | |
| alert("Please enter your Deepgram API key first."); | |
| return; | |
| } | |
| if (!apiUrl) { | |
| alert("Please enter the Deepgram API URL."); | |
| return; | |
| } | |
| // Save API key and URL to localStorage | |
| localStorage.setItem('deepgram_api_key', apiKey); | |
| localStorage.setItem('deepgram_api_url', apiUrl); | |
| const { createClient } = deepgram; | |
| const client = createClient({ | |
| key: apiKey, | |
| global: { url: apiUrl } | |
| }); | |
| liveClient = client.listen.live({ | |
| model: "nova-2", | |
| smart_format: true, | |
| interim_results: true | |
| }); | |
| liveClient.on("open", async () => { | |
| console.log("client: connected to websocket"); | |
| liveClient.on("Results", (data) => { | |
| // Check if we have the expected structure | |
| if (!data.channel || !data.channel.alternatives || data.channel.alternatives.length === 0) { | |
| return; | |
| } | |
| const transcript = data.channel.alternatives[0].transcript; | |
| const isFinal = data.is_final; | |
| console.log("Transcript:", transcript, "Full response:", data); | |
| if (transcript) { | |
| if (isFinal) { | |
| finalTranscript += transcript + " "; | |
| captions.innerHTML = `<span class="final">${finalTranscript}</span>`; | |
| } else { | |
| captions.innerHTML = `<span class="final">${finalTranscript}</span><span class="interim">${transcript}</span>`; | |
| } | |
| } | |
| }); | |
| liveClient.on("error", (e) => { | |
| console.error(e); | |
| alert("Error: " + e.message); | |
| }); | |
| liveClient.on("warning", (e) => console.warn(e)); | |
| liveClient.on("Metadata", (e) => console.log(e)); | |
| liveClient.on("close", (e) => { | |
| console.log("LiveClient closed", e); | |
| }); | |
| // Open microphone after socket is ready | |
| microphone = await getMicrophone(); | |
| await openMicrophone(microphone, liveClient); | |
| }); | |
| } | |
| async function stopRecording() { | |
| await closeMicrophone(microphone); | |
| microphone = null; | |
| if (liveClient) { | |
| liveClient.requestClose(); | |
| liveClient = null; | |
| } | |
| } | |
| recordButton.addEventListener("click", async () => { | |
| if (!microphone) { | |
| await startRecording(); | |
| } else { | |
| await stopRecording(); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment