रियल-टाइम ट्रांसक्रिप्ट दो बार आता है: पहले एक ड्राफ़्ट के रूप में जो लगातार बदलता रहता है, फिर एक अंतिम पंक्ति के रूप में जो नहीं बदलती। ड्राफ़्ट को बदली जाने वाली टेल के रूप में और अंतिम पंक्ति को इतिहास के रूप में दिखाएँ, तो लाइव कैप्शन व्यू का बाकी हिस्सा केवल ढाँचे का काम रह जाता है।
रियल-टाइम सेशन VocalStack API का सबसे छोटा काम है। आपका सर्वर एक HTTP कॉल करता है और एक साइन किया हुआ streamUrl वापस पाता है। आपका क्लाइंट उस URL से एक WebSocket खोलता है, कैप्चर होते ही कच्चा ऑडियो भेजता है, और JSON टेक्स्ट फ़्रेम में ट्रांसक्रिप्ट पढ़ता है, जो बोलने वाले से कुछ ही पलों पीछे होता है। ऑडियो ख़त्म होने पर आप eof भेजते हैं, सर्वर बचा हुआ डेटा फ़्लश करता है, सेशन के मापे गए आँकड़े बताता है और कनेक्शन बंद कर देता है।
कुछ भी संग्रहीत नहीं होता। ट्रांसक्रिप्ट केवल वायर पर मौजूद रहता है, और यही कैप्शन व्यू, मीटिंग ओवरले या आपके अपने ऐप के वॉइस फ़ीचर को चाहिए होता है। अगर आप चाहते हैं कि शब्द बाद में रखे, साझा किए और एक्सपोर्ट किए जाएँ, तो आपको लाइव ट्रांसक्रिप्शन और ब्रॉडकास्ट चाहिए; आख़िरी सेक्शन वहाँ का रास्ता बताता है।
ज़्यादातर स्ट्रीमिंग स्पीच APIs का ढाँचा एक जैसा है: HTTP पर क्रेडेंशियल का आदान-प्रदान, एक लंबे समय तक चलने वाला सॉकेट जिसमें ऑडियो अंदर जाता है और JSON बाहर आता है, और अस्थायी टेक्स्ट को अंतिम टेक्स्ट से अलग पहचानने का कोई तरीका। नाम एक API से दूसरे में अलग होते हैं, पर चलने वाले हिस्से लगभग वही रहते हैं। यहाँ क्रम से:
POST /v1/realtime/sessions भेजें। उत्तर है { sessionId, streamUrl, expiresAt }।streamUrl पर एक WebSocket खोलें।session.ready आने तक session.loading हार्टबीट का इंतज़ार करें।transcript.draft पढ़ें (लाइव टेल को बदलें) और transcript.committed पढ़ें (अंतिम पंक्ति जोड़ें)।{"type":"eof"} भेजें; session.done पढ़ें।सिर्फ़ चरण 1 को आपकी key चाहिए, इसलिए वह आपके सर्वर पर होता है।
Developers, API keys के अंतर्गत Write अनुमति के साथ एक key बनाएँ। key सर्वरों और स्क्रिप्ट्स के लिए है: API उस key को अस्वीकार करती है जो वेब पेज से या URL में भेजी जाए, इसलिए आप सेशन अपने किसी रूट के पीछे बनाते हैं, और पेज को केवल streamUrl दिखता है।
URL में उसका अपना टोकन होता है, और इसका कारण एक ब्राउज़र सीमा है जो सामान्य तौर पर जानने लायक है। ब्राउज़र का WebSocket कंस्ट्रक्टर रिक्वेस्ट हेडर नहीं सेट कर सकता, इसलिए key रखने के लिए कोई Authorization हेडर नहीं होता; हर ब्राउज़र-फ़ेसिंग स्पीच API अपने सॉकेट को URL में एक अल्पकालिक टोकन से प्रमाणित करता है (या subprotocol नाम में छिपाकर)। अल्पकालिक होना ही मुद्दा है: URL लॉग और इतिहास में पहुँच जाता है, इसलिए उसमें जो है उसकी समय-सीमा होनी चाहिए। expiresAt से पहले कनेक्ट करें।
SDK package registry से नहीं, बल्कि Developers area से डाउनलोड होते हैं; क्विकस्टार्ट का Install सेक्शन इस वातावरण के अपने कमांड दिखाता है। TypeScript SDK के साथ, सर्वर रूट कुछ ऐसा दिखता है:
// On your server, behind a route of yours (POST /api/transcribe, say):
import { VocalStack } from '@vocalstack/sdk';
const vs = new VocalStack({ baseUrl: '{{api}}', apiKey: process.env.VOCALSTACK_API_KEY });
export async function createStream(): Promise<{ sessionId: string; streamUrl: string }> {
const { sessionId, streamUrl } = await vs.realtime.createSession({ spokenLanguage: 'en' });
return { sessionId, streamUrl };
}
SDK के बिना यह एक fetch है। यह वाला स्पेनिश माँगता है और सेंटेंस असेंबली बंद करता है, ताकि नीचे दिए वायर सैंपल से मेल खाए:
const res = await fetch('{{api}}/v1/realtime/sessions', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.VOCALSTACK_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ spokenLanguage: 'en', translate: ['es'], sentences: { enabled: false } }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`); // the error envelope: { code, message, … }
const { streamUrl } = (await res.json()) as { sessionId: string; streamUrl: string; expiresAt: string };
हर विकल्प का कोई डिफ़ॉल्ट है। शुरुआत में जो सबसे ज़रूरी हैं वे हैं spokenLanguage (इसे छोड़ें तो भाषा अपने-आप पहचानी जाती है), translate (लक्ष्य भाषाओं की सूची), speakerLabels और latencyMode; बाकी नीचे ज़रूरत पड़ने पर आते हैं।
streamUrl को उस चीज़ को दें जो ऑडियो कैप्चर करती है; इसके बाद किसी key की ज़रूरत नहीं।
streamUrl से वैसे ही कनेक्ट करें, बिना किसी हेडर के। सेशन शुरू होते समय सर्वर session.loading हार्टबीट भेजता है। फिर आता है session.ready। उससे पहले कोई ऑडियो न भेजें। जल्दी भेजा गया ऑडियो बाद के लिए नहीं रखा जाता; सर्वर उसे एक त्रुटि मानता है, error फ़्रेम भेजता है और सॉकेट बंद कर देता है।
सॉकेट खुलने के बाद हर विफलता उसी फ़्रेम के रूप में आती है: {"type":"error","code":"…","message":"…","retryable":true|false}। code एक बंद सूची है (invalid_audio, invalid_options, invalid_token, capacity_exceeded, session_expired, slow_consumer, engine_unavailable, internal), message आपके लॉग के लिए है, और retryable बताता है कि नया सेशन आज़माना उचित है या नहीं। पहले फ़्रेम से ही इसे संभालें:
const ws = new WebSocket(streamUrl); // the token is in the URL: no header
ws.binaryType = 'arraybuffer';
ws.addEventListener('message', (m: MessageEvent) => {
if (typeof m.data !== 'string') return;
const f = JSON.parse(m.data);
switch (f.type) {
case 'session.loading':
console.error('starting…');
break;
case 'session.ready':
startMicrophone(); // send audio only after session.ready
break;
case 'error':
console.error(`${f.code}: ${f.message}`);
if (f.retryable) scheduleReconnect(); // a new session, with backoff; see "Stop"
break;
}
});
कच्चे क्लाइंट को दो और बातें ख़ुद संभालनी होती हैं; SDK पहले से यह करते हैं। पहली, अस्वीकृत अपग्रेड API के सामान्य HTTP त्रुटि लिफ़ाफ़े के साथ आता है: 401 invalid_token, 410 session_expired, या 503 unavailable जिसके साथ मानने लायक Retry-After हेडर होता है।
दूसरी, सैचुरेशन एक त्रुटि की तरह नहीं, सन्नाटे की तरह दिखती है। अगर 30 सेकंड में एक भी फ़्रेम न आए, तो इसे capacity_exceeded मानें और फिर से कनेक्ट करें; नया सेशन आमतौर पर निकल जाता है। अगर लोडिंग फ़्रेम आते रहें पर session.ready कभी न आए, तो इसे engine_unavailable मानें, जो भी retryable है। SDK में, RealtimeSession.connect session.ready पर resolve होता है और readyDeadlineMs के बाद सही कोड के साथ fail होता है।
एक बार session.ready आ जाए, तो सर्वर उसी रफ़्तार से ऑडियो की अपेक्षा करता है जिस रफ़्तार से वह बोला जा रहा है।
ऑडियो कच्चे PCM के बाइनरी फ़्रेम में जाता है: 32-bit float, little-endian, 16 kHz, mono (pcm_f32le, सेशन का डिफ़ॉल्ट audio फ़ॉर्मेट)। इसे कैप्चर होते ही लगभग 100 से 250 ms के हिस्सों में भेजें, और हिस्सों के बीच ट्रांसक्रिप्ट का इंतज़ार न करें।
बाइट का हिसाब एक बार करने लायक है। 16,000 सैंपल प्रति सेकंड, हर सैंपल 4 बाइट, यानी 64,000 बाइट प्रति सेकंड; तो 100 ms है 6,400 बाइट और 250 ms है 16,000। स्पीच मॉडल आम तौर पर 16 kHz mono पर प्रशिक्षित होते हैं, इसीलिए इतनी सारी रियल-टाइम APIs इसे माँगती हैं, जबकि माइक्रोफ़ोन और ब्राउज़र 44.1 या 48 kHz पर और अक्सर stereo में कैप्चर करते हैं। बाइट बाहर जाने से पहले कोई चीज़ ऑडियो को resample करे और चैनलों को एक में मिलाए: यहाँ यह ffmpeg है, और ब्राउज़र में यह SDK का माइक्रोफ़ोन हेल्पर है। 48 kHz ऑडियो भेजकर 16 kHz घोषित करने पर कोई त्रुटि नहीं आती; बस स्लो-मोशन जैसी बोली ट्रांसक्रिप्ट होती है, और वह भी बुरी तरह।
Node में, ffmpeg सबसे सरल कैप्चर डिवाइस है:
import { spawn } from 'node:child_process';
function startMicrophone(): void {
// Raw PCM: f32le, 16 kHz, mono, as binary frames. macOS: -f avfoundation -i :0, Linux: -f pulse -i default.
const mic = spawn('ffmpeg', ['-loglevel', 'error', '-f', 'avfoundation', '-i', ':0', '-f', 'f32le', '-ac', '1', '-ar', '16000', '-']);
mic.stdout.on('data', (chunk: Buffer) => {
// Backpressure: bytes the socket has accepted but not yet put on the wire.
// Two seconds of audio (128,000 bytes) queued means the link, not the server, is behind.
if (ws.bufferedAmount > 128_000) console.error(`link behind by ${ws.bufferedAmount} bytes`);
ws.send(new Uint8Array(chunk));
});
mic.on('close', () => ws.send(JSON.stringify({ type: 'eof' })));
process.once('SIGINT', () => mic.kill());
}
ws.send कभी ब्लॉक नहीं होता; वह कतार में डालता है। bufferedAmount उसी कतार का आकार है, और अच्छे लिंक पर यह लगभग शून्य के आसपास रहता है। लाइव ऑडियो रोका नहीं जा सकता, इसलिए बढ़ती कतार का उपयोगी जवाब है उसे पहचानना, फ़्रेम गिराना नहीं। सर्वर भी बैकप्रेशर लगाता है। वह असली समय से कुछ सेकंड आगे का ऑडियो स्वीकार करता है (limits.audioBufferS), फिर तब तक पढ़ना रोक देता है जब तक पकड़ न ले। वापसी की ओर, कमिट बिना नुकसान कतार में रहते हैं जबकि ड्राफ़्ट एक-साथ मिला दिए जाते हैं, इसलिए धीमा रीडर कम, पर ताज़ा ड्राफ़्ट देखता है। जो क्लाइंट कमिट के साथ भी नहीं चल पाता, उसे slow_consumer और retryable: false वाला error फ़्रेम मिलता है और उसका कनेक्शन काट दिया जाता है; अगर आपको यह दिखे, तो आपका मेसेज हैंडलर ज़रूरत से ज़्यादा काम कर रहा है।
ब्राउज़र में, @vocalstack/sdk/mic का streamMicrophone माइक्रोफ़ोन माँगता है, 16 kHz में resample करता है, और चंक्स को clock फ़्रेम के साथ भेजता है, जिससे सर्वर गैप चिह्नित कर सके जब ब्राउज़र टैब को थ्रॉटल करता है। पेज सेशन आपके रूट से लेता है और key कभी नहीं देखता:
import { RealtimeSession } from '@vocalstack/sdk';
import { streamMicrophone } from '@vocalstack/sdk/mic';
export async function transcribeMicrophone(
stream: { sessionId: string; streamUrl: string },
render: (committed: string, tail: string) => void,
): Promise<void> {
const session = await RealtimeSession.connect(stream);
const mic = await streamMicrophone(session); // asks for the microphone, sends 16 kHz PCM
document.querySelector('#stop')?.addEventListener('click', () => {
void mic.stop();
session.eof();
});
let committed = '';
for await (const ev of session) {
if (ev.type === 'transcript.draft') render(committed, ev.segments.map((s) => s.text).join(' '));
if (ev.type === 'transcript.committed') {
committed += `${ev.segment.text} `;
render(committed, '');
}
}
}
render(committed, tail) का यह सिग्नेचर पूरा UI पैटर्न है; अगला सेक्शन बताता है क्यों।
दो फ़्रेम प्रकार ट्रांसक्रिप्ट बनाते हैं, और इन्हें आपस में मिला देना सबसे आम गलती है।
पहले, दो क्यों हैं। स्पीच मॉडल तय करता है कि कोई शब्द क्या था, कुछ हद तक उसके बाद आने वाले शब्दों से: "I scream" और "ice cream" एक जैसे सुनाई देते हैं, जब तक वाक्य आगे न बढ़े। इसलिए एक रियल-टाइम रिकग्नाइज़र जल्दी एक अनुमान भेजता है, फिर और ऑडियो आने पर उसे संशोधित करता है, और तभी स्थिर होता है जब उसने पर्याप्त सुन लिया हो। हर स्ट्रीमिंग स्पीच API इसे किसी न किसी तरह सामने लाता है। कुछ अनुमानों को "interim" या "partial" नतीजे कहते हैं और स्थिर नतीजों को is_final से चिह्नित करते हैं; शब्दावली अलग है, अनुशासन वही है। यहाँ अनुमान ड्राफ़्ट है और स्थिर टेक्स्ट कमिट।
transcript.draft लाइव टेल है: पिछले कमिट के बाद सुने गए शब्द, जो अभी भी बदल रहे हैं। हर ड्राफ़्ट पिछले का पूरा प्रतिस्थापन है। उसके segments से टेल को फिर से बनाएँ; उसे कभी जोड़ें नहीं। ड्राफ़्ट डिज़ाइन से ही seq नहीं रखते, इसलिए उन्हें उन कमिट से मिलाने की कोशिश न करें जिनमें वे बदलते हैं।
transcript.committed अंतिम है: एक segment जिसमें seq (0, 1, 2, बिना अंतराल के), start, end, text, speaker और हर शब्द के समय के साथ words हैं। इसे जोड़ें और उस ड्राफ़्ट को हटाएँ जिसे यह कवर करता है; अगला ड्राफ़्ट इसी कमिट के अंत से शुरू होता है।
committed: [seq 0] [seq 1]
draft: "how are" ← frame 1
draft: "how are you do" ← frame 2 REPLACES frame 1
committed: [seq 0] [seq 1] [seq 2 "How are you doing?"]
← the commit consumes the tail; drop the draft
जो UI ड्राफ़्ट जोड़ता है, वह दोहराया हुआ, झिलमिलाता टेक्स्ट दिखाता है। आपके कैप्शन व्यू में ऐसी हकलाहट आती है जो बोलने वाले में कभी नहीं थी। सभी कमिट किए गए सेगमेंट दिखाएँ, फिर उनके बाद का नवीनतम ड्राफ़्ट, और जब कमिट आए तो ड्राफ़्ट साफ़ करें। कमिट में ऐसे शब्द हो सकते हैं जो किसी ड्राफ़्ट में कभी दिखे ही नहीं।
ऊपर दिए fetch उदाहरण से कुछ सेकंड की बोली का वायर पर डेटा यहाँ है (translate: ["es"], सेंटेंस असेंबली बंद):
{"type":"session.loading"}
{"type":"session.ready"}
{"type":"transcript.draft","segments":[{"start":0.0,"text":"Good","speaker":0,"tentative":true}]}
{"type":"transcript.draft","segments":[{"start":0.0,"text":"Good morning every","speaker":0,"tentative":true}]}
{"type":"transcript.committed","segment":{"seq":0,"start":0.0,"end":1.4,"text":"Good morning, everyone.","speaker":0,"words":[{"start":0.0,"end":0.3,"text":"Good"},{"start":0.3,"end":0.8,"text":"morning,"},{"start":0.8,"end":1.4,"text":"everyone."}]}}
{"type":"transcript.translated","seq":0,"translations":{"es":"Buenos días a todos."}}
{"type":"transcript.draft","segments":[{"start":1.6,"text":"Thanks for","speaker":0,"tentative":true}]}
इन फ़्रेमों के आसपास दो और फ़्रेम हैं। transcript.amended किसी कमिट किए गए सेगमेंट को उसी जगह सुधारता है (वही seq, नया text या speaker) जब उसका कोई शब्द सुधारा गया हो; उसे उस लाइन पर लागू करें जो आपने पहले बनाई थी। transcript.words कमिट से ठीक पहले टेल के लिए शब्द-समय स्ट्रीम करता है। इसके लिए events.words चाहिए, जो डिफ़ॉल्ट रूप से चालू है; कमिट हमेशा अपने words के साथ आता है, चाहे इवेंट विकल्प कुछ भी कहें। अंत में, अज्ञात फ़्रेम प्रकारों को अनदेखा करें: नए जोड़े जाते हैं बिना सूचना के।
यह रहा Python SDK का रीडिंग लूप, टर्मिनल को कैप्शन व्यू मानते हुए। यहाँ भी सेंटेंस असेंबली बंद है, इसलिए अनुवाद transcript.translated के रूप में आते हैं, और error फ़्रेम लूप से VocalStackError के रूप में उठता है:
import asyncio
import os
from vocalstack import RealtimeSession, VocalStack, VocalStackError
MIC = ["ffmpeg", "-loglevel", "error", "-f", "avfoundation", "-i", ":0",
"-f", "f32le", "-ac", "1", "-ar", "16000", "-"]
async def main() -> None:
async with VocalStack("{{api}}", api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
created = await vs.realtime.create_session(
spoken_language="en", translate=["es"], sentences={"enabled": False}
)
session = await RealtimeSession.connect(created.session_id, created.stream_url)
mic = await asyncio.create_subprocess_exec(*MIC, stdout=asyncio.subprocess.PIPE)
async def feed() -> None:
assert mic.stdout is not None
while chunk := await mic.stdout.read(8000): # 125 ms of audio
await session.send_audio(chunk)
await session.eof() # the server flushes, then sends session.done
feeder = asyncio.create_task(feed())
try:
async with session:
async for event in session:
if event.type == "transcript.draft":
# The live tail: replace it, never append it.
tail = " ".join(s.text for s in event.segments)
print(f"\r\x1b[K\x1b[2m{tail}\x1b[0m", end="", flush=True)
elif event.type == "transcript.committed":
# Final: append it (seq 0, 1, 2...), and the tail it covers is gone.
print(f"\r\x1b[K{event.segment.text}", flush=True)
elif event.type == "transcript.translated":
print(f" es: {event.translations.get('es')}")
await feeder
print(f"done ({session.done_reason})", session.stats)
except VocalStackError as e:
# An `error` frame mid-stream, or a refused connect.
print(f"\nerror {e.code}: {e.message} (retryable: {e.retryable})")
finally:
if mic.returncode is None:
mic.kill()
asyncio.run(main())
प्रति-सेगमेंट अनुवाद, जैसा ऊपर है, सरल रूप है। जो रूप पाठक पसंद करते हैं, उसका अपना सेक्शन है।
translate: ["es", "fr"] सेशन के साथ-साथ अनुवाद माँगता है। वे कैसे आते हैं, यह वाक्य असेंबली (sentences: { enabled: true }) पर निर्भर है, जो डिफ़ॉल्ट रूप से चालू है।
सेंटेंस बंद हों, तो हर कमिट के कुछ देर बाद एक transcript.translated फ़्रेम आता है, जो उसके seq का नाम लेता है और translations को भाषा के अनुसार रखता है; उन्हें उसी सेगमेंट से जोड़ें। अब तक के उदाहरण यही इस्तेमाल करते आए हैं। यह सरल है और कमिट लॉग की तरह पढ़ा जाता है: एक सेगमेंट, एक अनुवाद, और वाक्य की सीमा वहीं जहाँ बोलने वाले ने साँस ली।
सेंटेंस चालू हों, तो सर्वर कमिट को पूरे वाक्यों में जोड़ता है और उनका अनुवाद करता है, जो कहीं बेहतर पढ़ता है। हर transcript.sentence फ़्रेम एक वाक्य का पूरा प्रतिस्थापन है, जो sentenceId से पहचाना जाता है। उसका status open से closed (या dropped, जिसका मतलब है इसे हटाना) तक जाता है, और उसके translations खुले रहते समय अस्थायी होते हैं और बंद होने पर अंतिम। sentenceId से upsert करें, कभी जोड़ें नहीं। TypeScript SDK में इसी के लिए applySentence आता है:
import { applySentence, type SentenceSegment } from '@vocalstack/sdk';
let sentences: SentenceSegment[] = [];
for await (const ev of session) {
if (ev.type !== 'transcript.sentence') continue;
sentences = applySentence(sentences, ev.sentence); // upsert by sentenceId
if (ev.sentence.status === 'closed') console.log(`${ev.sentence.text}\n es: ${ev.sentence.translations?.es}`);
}
अनुवाद ट्रांसक्रिप्ट के एक-दो सेकंड पीछे चलते हैं। अगर वे बोले भी जाने चाहिए, तो voice: ["es"] जोड़ें (इसके लिए सेंटेंस चाहिए)। हर बंद वाक्य का स्पेनिश फिर transcript.voice फ़्रेम के रूप में आता है, जिसमें क्लिप base64 Ogg/Opus में 24 kHz mono पर होता है। लंबा वाक्य हिस्सों (parts में part) में आता है, जिन्हें एक के बाद एक चलाना है। अस्थायी टेक्स्ट कभी नहीं बोला जाता। लाइव अनुवाद गाइड आवाज़ों को विस्तार से बताती है, और सेकंड कहाँ जाते हैं देरी की श्रृंखला समझाता है।
लक्ष्य भाषाएँ सेशन बनाते समय तय नहीं होतीं।
एक session.update टेक्स्ट फ़्रेम spokenLanguage, translate, speakerLabels, latencyMode, voice या voiceByLang को वहीं से बदलता है; जो फ़ील्ड छोड़ दें वे अपरिवर्तित रहते हैं।
{"type":"session.update","translate":["es","fr"],"latencyMode":"fast"}
SDK इसे session.update(…) के रूप में देते हैं। translate पूरी सूची बदल देता है, और [] अनुवाद बंद कर देता है। जो सेगमेंट पहले पहुँच चुके हैं, उन्हें दोबारा ट्रांसक्रिप्ट या अनुवाद नहीं किया जाता, और अगर आप स्पीकर लेबल चालू करते हैं, तो स्पीकर केवल उसी बिंदु से लेबल होते हैं। जो कहा जा चुका, वह कहा हुआ है; सेटिंग्स केवल आगे आने वाली चीज़ों को नियंत्रित करती हैं।
जब ऑडियो ख़त्म हो, {"type":"eof"} भेजें (SDK में session.eof())। सर्वर जो कुछ उसके पास बचा है उसे ट्रांसक्रिप्ट करता है, आख़िरी कमिट भेजता है, फिर session.done भेजकर बंद हो जाता है। इसके बजाय cancel भेजने पर तुरंत रुक जाता है और बाकी छोड़ देता है।
{"type":"session.done","reason":"eof","stats":{"audioS":30.0,"processingS":30.6,"realtimeFactor":0.98}}
reason eof, max_duration (सेशन ने प्लान की अधिकतम लंबाई छू ली और वही फ़्लश चला) या cancelled है। stats सर्वर द्वारा मापे गए हैं: audioS वह ऑडियो है जो आपने भेजा, processingS वह वॉल-क्लॉक समय है जितनी देर सेशन चला, और realtimeFactor उनका अनुपात है। लाइव सेशन 1 से ज़रा नीचे आता है, क्योंकि वॉल-क्लॉक समय में आपके eof के बाद का आख़िरी फ़्लश शामिल होता है। इससे काफ़ी कम संख्या बताती है कि सेशन ऑडियो की अवधि से उल्लेखनीय रूप से ज़्यादा समय तक चला; पहले यह देखें कि ऑडियो कितनी तेज़ी से पहुँचा। फ़ाइल के लिए, वही संख्या बताती है कि ट्रांसक्रिप्ट प्लेबैक से कितनी गुना तेज़ लौटा।
एक सेशन अधिकतम limits.maxDurationS तक चलता है, जो GET /v1/realtime/capabilities से आता है, और उसका ऑडियो आपके प्लान की लाइव दर पर प्रति सेकंड आपका क्रेडिट खर्च करता है, किसी भी ट्रांसक्रिप्शन की तरह 5 मिनट की न्यूनतम के साथ: जो हर सेशन ऑडियो प्राप्त करता है उसके लिए कम से कम 5 मिनट का बिल बनता है (कीमतें)। वही सॉकेट पूरी फ़ाइल भी ले सकता है: सेशन को input: { kind: "file" } के साथ बनाएँ, फ़ाइल के अपने बाइट (mp3, wav, flac, ogg, m4a) और eof भेजें, और ट्रांसक्रिप्ट उस गति से कई गुना तेज़ लौटता है जिस गति से ऑडियो चलता है, बीच में transcript.progress फ़्रेम के साथ।
कोई रिज़्यूम नहीं है। कनेक्शन टूटने पर सेशन ख़त्म हो जाता है; दोबारा कनेक्ट करने का मतलब है नया सेशन बनाना, जिसका अपना न्यूनतम बिल होगा, और अपने लाइव स्रोत से फिर स्ट्रीम करना, seq 0 से शुरू होकर। दोबारा कनेक्ट करने का तरीका किसी भी सॉकेट API के लिए मानक है, और एक बार सही करने लायक है:
error फ़्रेम पर retryable: true, Retry-After वाला 503, या 30 सेकंड बिना फ़्रेम के, जो capacity_exceeded के लिए खड़ा है। invalid_token और session_expired का मतलब है आपके सर्वर से नया streamUrl, पुराने से दोबारा कोशिश नहीं।सेशन बनाने वाले रूट को सस्ते में कॉल होने लायक रखें, और कनेक्शन बंद होने को नई शुरुआत मानें। रिज़्यूम न होना कुछ भी न संग्रहीत करने की कीमत है; अगर ट्रांसक्रिप्ट को सॉकेट से ज़्यादा जीना है, तो उसे ख़ुद सहेजें।
जब आप चाहते हैं कि वही स्ट्रीम एक रिकॉर्डिंग के रूप में संग्रहीत हो, बाद में सुधारी जाए, एक्सपोर्ट हो, और अलग-अलग भाषाओं में दर्शक उसे पढ़ें, तो उसे लाइव ट्रांसक्रिप्शन में प्रकाशित करें। लाइव जाने की गाइड वही PCM सॉकेट एक ट्रांसक्रिप्शन और ब्रॉडकास्ट लिंक के ज़रिए दिखाती है, और सुनने की गाइड सब्सक्राइबर वाला पक्ष दिखाती है, जहाँ हर श्रोता एक भाषा चुनता है और देर से जुड़ने वाला lastSeq कर्सर से पकड़ता है। किसी कमरे के लिए यह कैसा दिखता है, इसका विषय एक लिंक, हर भाषा है।
जिस फ़ाइल को आप रखना चाहते हैं, उसके लिए संग्रहीत रास्ता API से फ़ाइल ट्रांसक्रिप्ट करें है। हर फ़ील्ड सहित पूरा फ़्रेम संदर्भ API reference में माइक्रोफ़ोन स्ट्रीम करें गाइड में है।