Real vaxt transkripti iki dəfə gəlir: əvvəlcə dəyişən qaralama kimi, sonra dəyişməyən son sətir kimi. Qaralamanı əvəz olunan quyruq kimi, son sətri isə tarixçə kimi göstərin; canlı altyazı görünüşünün qalan hissəsi isə sadəcə texniki təminatdır.
Real vaxt sessiyası VocalStack API-nin ən kiçik əməliyyatıdır. Serveriniz bir HTTP çağırışı edir və imzalanmış streamUrl alır. Müştəriniz həmin URL-ə WebSocket açır, tutulan xam audionu göndərir və transkripti danışandan bir neçə an sonra JSON mətn çərçivələri şəklində geri oxuyur. Audio bitəndə eof göndərirsiniz, server buferi boşaldır, sessiyanın ölçdüyü nəticələri bildirir və bağlanır.
Heç nə saxlanılmır. Transkript yalnız şəbəkə üzərində mövcud olur — altyazı görünüşü, görüş üzərinə qoyulan qat və ya tətbiqinizdəki səs funksiyası məhz bunu tələb edir. Əgər sözlərin sonradan saxlanmasını, paylaşılmasını və ixrac edilməsini istəyirsinizsə, bunun əvəzinə canlı transkripsiya və yayım lazımdır; son bölmə bu istiqamətə yönləndirir.
Quruluş əksər axın nitq API-lərinin paylaşdığı quruluşdur: HTTP üzərindən etimadnamənin mübadiləsi, içəri audio, xaricə JSON axan uzunömürlü socket və müvəqqəti mətni son mətndən ayırmağın bir yolu. Adlar bir API-dən digərinə fərqlənir; hərəkət edən hissələr isə nadir hallarda. Budur, ardıcıllıqla:
POST /v1/realtime/sessions çağırışı, API açarınızla, serveriniz üzərində. Cavab { sessionId, streamUrl, expiresAt } obyektidir.streamUrl-ə WebSocket açın.session.ready gələnə qədər session.loading ürək döyüntülərini gözləyin.transcript.draft (quyruğu əvəz edin) və transcript.committed (son sətri əlavə edin) oxuyun.{"type":"eof"} göndərin; session.done oxuyun.Yalnız birinci addım açarınızı tələb edir, ona görə də o, serveriniz üzərində baş verir.
Developers, API keys bölməsində Write icazəsi ilə açar yaradın. Açar serverlər və skriptlər üçündür: API veb səhifədən və ya URL daxilində göndərilən açarı qəbul etmir, ona görə də sessiyanı öz marşrutunuzun arxasında yaradırsınız və səhifə yalnız streamUrl-i görür.
URL öz tokenini daşıyır; bunun səbəbi ümumi olaraq bilmək dəyər olan brauzer məhdudiyyətidir. Brauzerin WebSocket konstruktoru sorğu başlıqları təyin edə bilmir, ona görə də açarı qoymaq üçün Authorization başlığı yoxdur; brauzerlə işləyən hər nitq API-si socketini URL-dəki qısamüddətli token ilə (və ya alt-protokol adına gizlədilmiş şəkildə) autentifikasiya edir. Qısamüddətlilik məqsədin özüdür: URL jurnallara və tarixçəyə düşür, ona görə də daşıdığı şey müddəti bitməlidir. expiresAt-dən əvvəl qoşulun.
SDK-lar paket reyestrindən deyil, Developers bölməsindən endirilir; sürətli başlanğıc səhifəsinin Install bölməsi bu mühitin öz əmrlərini göstərir. TypeScript SDK ilə server marşrutu belə görünür:
// 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 olmadan bu, bir fetch çağırışıdır. Bu nümunə də ispan dili tələb edir və cümlə yığımını söndürür; aşağıdakı şəbəkə nümunəsi ilə üst-üstə düşür:
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 };
Hər seçimin standart dəyəri var. Başlanğıcda bilmək dəyər olanlar bunlardır: spokenLanguage (buraxsanız, dil avtomatik təyin olunur), translate (hədəf dillərin siyahısı), speakerLabels və latencyMode; qalanları aşağıda lazım olduqca gələcək.
streamUrl-i audionu tutan hansı mənbə varsa ona ötürün; bundan sonra heç bir açar iştirak etmir.
streamUrl-ə olduğu kimi, başlıq olmadan qoşulun. Sessiya başlayarkən server session.loading ürək döyüntülərini göndərir. Sonra session.ready gəlir. Ondan əvvəl heç bir audio göndərməyin. Vaxtından əvvəl göndərilən audio sonrakı üçün saxlanılmır; server bunu xəta hesab edir, error çərçivəsi göndərir və socketi bağlayır.
Socket açıldıqdan sonra hər xəta həmin çərçivənin formasını alır: {"type":"error","code":"…","message":"…","retryable":true|false}. code qapalı siyahıdır (invalid_audio, invalid_options, invalid_token, capacity_exceeded, session_expired, slow_consumer, engine_unavailable, internal), message loglarınız üçündür, retryable isə yeni sessiyanın cəhd etməyə dəyib-dəymədiyini bildirir. Bunu ilk çərçivədən etibarən idarə edin:
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;
}
});
Xam müştəri iki şeyi özü idarə etməlidir; SDK-lar bunu artıq edir. Birincisi, rədd edilmiş yeniləmə API-nin adi HTTP xəta zərfini daşıyır: 401 invalid_token, 410 session_expired və ya Retry-After başlığı ilə 503 unavailable — buna riayət etmək lazımdır.
İkincisi, doyma xəta kimi deyil, sükut kimi görünür. Əgər 30 saniyə ərzində heç bir çərçivə gəlmirsə, bunu capacity_exceeded kimi qəbul edin və yenidən qoşulun; yeni sessiya adətən keçir. Əgər yükləmə çərçivələri gəlməkdə davam edir, amma session.ready heç gəlmirsə, bunu engine_unavailable kimi qəbul edin, bu da yenidən cəhd edilə bilər. SDK-larda RealtimeSession.connect session.ready-də tamamlanır və readyDeadlineMs-dən sonra düzgün kodla uğursuz olur.
session.ready gəldikdən sonra server audionun danışıldığı tempdə gözləyir.
Audio xam PCM-in ikilik çərçivələri şəklində daxil olur: 32-bit float, little-endian, 16 kHz, mono (pcm_f32le, sessiyanın standart audio formatı). Onu tutulduğu kimi, təxminən 100–250 ms-lik hissələrlə göndərin, hissələr arasında transkripti gözləmədən.
Bayt hesabını bir dəfə etmək dəyər. Saniyədə 16,000 nümunə, hər biri 4 bayt, saniyədə 64,000 bayt edir; ona görə də 100 ms 6,400 bayt, 250 ms isə 16,000 bayt olur. Nitq modelləri adətən 16 kHz mono üzərində öyrədilir, buna görə də bir çox real vaxt API-si məhz bunu tələb edir; mikrofonlar və brauzerlər isə 44,1 və ya 48 kHz-də, çox vaxt stereo şəkildə tutur. Baytlar göndərilməzdən əvvəl audionun yenidən nümunələnməsi və kanalların birə bükülməsi lazımdır: burada bu, ffmpeg-dir, brauzerdə isə SDK-nın mikrofon köməkçisidir. 48 kHz audio göndərib 16 kHz elan etmək səhv vermir; lakin nitqi yavaş çəkilmiş kimi, pis transkripsiya edir.
Node-da ffmpeg ən sadə tutma qurğusudur:
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 heç vaxt bloklanmır; o, növbəyə qoyur. bufferedAmount həmin növbənin ölçüsüdür və sağlam əlaqədə sıfıra yaxın olur. Canlı audio dayandırıla bilməz, ona görə də böyüyən növbəyə faydalı cavab çərçivələri atmaq deyil, onu fərq etməkdir. Server də geri təzyiq tətbiq edir. O, audionu real vaxtdan bir neçə saniyə qabaqcadan qəbul edir (limits.audioBufferS), sonra yetişənə qədər oxumağı dayandırır. Geriyə gedən yolda təsdiqlər itkisiz növbəyə qoyulur, qaralamalar isə birləşdirilir; beləliklə, yavaş oxuyan tərəf daha az, daha təzə qaralama görür. Təsdiqlərlə belə ayaqlaya bilməyən müştəri slow_consumer və retryable: false ilə error çərçivəsi alır və bağlanır; bunu görsəniz, mesaj işləyicinizin çox iş gördüyü deməkdir.
Brauzerdə @vocalstack/sdk/mic-dən olan streamMicrophone mikrofona icazə istəyir, 16 kHz-ə yenidən nümunələyir və hissələri clock çərçivələri ilə birlikdə göndərir; bu çərçivələr brauzer sekmesini məhdudlaşdırdıqda serverə boşluqları qeyd etməyə imkan verir. Səhifə sessiyanı sizin marşrutunuzdan alır və açarı heç vaxt görmür:
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) imzası bütün UI nümunəsidir; növbəti bölmə bunun səbəbini izah edir.
Transkripti iki çərçivə tipi qurur və onları qarışdırmaq ən çox rast gəlinən səhvdir.
Əvvəlcə, niyə ümumiyyətlə ikisi var. Nitq modeli sözün nə olduğunu qismən ondan sonra gələn sözlərdən müəyyən edir: “I scream” və “ice cream” cümlə davam edənə qədər eyni səslənir. Ona görə də real vaxt tanıyıcısı əvvəlcə bir təxmin verir, daha çox audio gəldikcə onu düzəldir və yalnız kifayət qədər eşitdikdən sonra sabitləşir. Hər axın nitq API-si bunu bir şəkildə açır. Bəziləri təxminləri “interim” və ya “partial” nəticələr adlandırır və sabitləşmişləri is_final ilə işarələyir; terminologiya dəyişir, prinsip dəyişmir. Burada təxmin qaralamadır, sabitləşmiş mətn isə təsdiqdir.
transcript.draft canlı quyruqdur: son təsdiqdən bəri eşidilən, hələ dəyişən sözlər. Hər qaralama əvvəlkinin tam əvəzidir. Quyruğu onun segments-ləri ilə yenidən çəkin; heç vaxt əlavə etməyin. Qaralamalar layihəyə görə seq daşımır, ona görə də onların çevriləcəyi təsdiqlərlə uyğunlaşdırmağa çalışmayın.
transcript.committed sabitləşmişdir: seq (0, 1, 2, boşluqsuz), start, end, text, speaker və hər sözün vaxt işarələri ilə words olan bir segment. Onu əlavə edin və əhatə etdiyi qaralamanı atın; növbəti qaralama bu təsdiqin bitdiyi yerdən başlayır.
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
Qaralamaları əlavə edən UI təkrarlanan, titrəyən mətn göstərir. Altyazı görünüşünüzdə danışanın heç vaxt olmayan pozulma yaranır. Bütün təsdiqlənmiş seqmentləri, sonra onlardan sonrakı ən son qaralamanı göstərin və təsdiq gələndə qaralamanı təmizləyin. Təsdiqdə heç bir qaralamada görünməmiş sözlər ola bilər.
Budur, yuxarıdakı fetch nümunəsindən şəbəkədə bir neçə saniyəlik nitq (translate: ["es"], cümlə yığımı söndürülüb):
{"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}]}
Bunların ətrafında daha iki çərçivə var. transcript.amended təsdiqlənmiş seqmenti yerində düzəldir (eyni seq, yeni text və ya speaker), içindəki bir söz düzəldiləndə; onu artıq çəkdiyiniz sətrə tətbiq edin. transcript.words təsdiqlənməzdən dərhal əvvəl quyruq üçün söz vaxt işarələrini axıdır. Bu, standart olaraq aktiv olan events.words tələb edir; təsdiq hadisə seçimlərindən asılı olmayaraq həmişə öz words-lərini daxil edir. Nəhayət, tanımadığınız çərçivə tiplərini nəzərə almayın: yenilər xəbərdarlıq olmadan əlavə olunur.
Budur, terminalı altyazı görünüşü kimi götürən Python SDK-nın oxuma dövrü. Burada da cümlə yığımı söndürülüb, ona görə tərcümələr transcript.translated kimi gəlir və error çərçivəsi dövrədən qaldırılan VocalStackError kimi üzə çıxır:
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())
Yuxarıdakı kimi, seqment üzrə tərcümə sadə formadır. Oxucuların üstün tutduğu forma üçün ayrıca bölmə var.
translate: ["es", "fr"] sessiya davam etdikcə tərcümələr tələb edir. Onların necə gəlməsi cümlə yığımından (sentences: { enabled: true }) asılıdır, bu da standart olaraq aktivdir.
Cümlələr söndürülü olduqda, hər təsdiqdən bir an sonra onun seq-ini göstərən və dillərə görə açarlanmış translations daşıyan bir transcript.translated çərçivəsi gəlir; onları həmin seqmentə əlavə edin. İndiyə qədərki nümunələr məhz bunu istifadə edib. Bu sadədir və təsdiq jurnalı kimi oxunur: bir seqment, bir tərcümə, cümlə sərhədi isə danışanın nəfəs aldığı yerdədir.
Cümlələr açıq olduqda server təsdiqləri tam cümlələrə yığır və onları tərcümə edir; bu, xeyli daha yaxşı oxunur. Hər transcript.sentence çərçivəsi sentenceId ilə açarlanmış bir cümlənin tam əvəzidir. Onun status-u open-dən closed-a keçir (və ya dropped, yəni atılmalıdır), translations isə açıq olduqda müvəqqətidir, bağlandıqdan sonra isə sabitdir. sentenceId üzrə yeniləyin (upsert), heç vaxt əlavə etməyin. TypeScript SDK məhz bunun üçün applySentence təqdim edir:
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}`);
}
Tərcümələr transkriptin bir-iki saniyə arxasınca gəlir. Onların səslənməsini də istəyirsinizsə, voice: ["es"] əlavə edin (cümlələr tələb olunur). Hər bağlı cümlənin ispan dili tərcüməsi sonra 24 kHz mono-da base64 Ogg/Opus klipi daşıyan transcript.voice çərçivəsi kimi gəlir. Uzun cümlə hissələrlə (parts-ın part-ı) gəlir ki, arasıkəsilmədən ifadə edilsin. Müvəqqəti mətn heç vaxt səsləndirilmir. Canlı tərcümə bələdçisi səsləri ətraflı əhatə edir, saniyələrin hara getdiyi isə gecikmə zəncirini izah edir.
Hədəf dillər yaradılarkən sabitlənmir.
session.update mətn çərçivəsi həmin andan etibarən spokenLanguage, translate, speakerLabels, latencyMode, voice və ya voiceByLang-ı dəyişir; buraxdığınız sahələr dəyişmir.
{"type":"session.update","translate":["es","fr"],"latencyMode":"fast"}
SDK-lar bunu session.update(…) kimi təqdim edir. translate bütün siyahını əvəz edir, [] isə tərcüməni söndürür. Artıq çatdırılmış seqmentlər yenidən transkripsiya və ya tərcümə olunmur; danışan etiketlərini yandırsanız, danışanlar yalnız həmin andan etiketlənir. Deyilən söz deyilmiş qalır; parametrlər yalnız bundan sonra gələnə təsir edir.
Audio bitəndə {"type":"eof"} göndərin (SDK-larda session.eof()). Server hələ saxladığı hissəni transkripsiya edir, son təsdiqləri göndərir, sonra session.done göndərib bağlanır. Əvəzinə cancel göndərsəniz, dərhal dayanır və qalanını atır.
{"type":"session.done","reason":"eof","stats":{"audioS":30.0,"processingS":30.6,"realtimeFactor":0.98}}
reason eof, max_duration (sessiya planın maksimum uzunluğuna çatdı və eyni buferi boşaltma işini gördü) və ya cancelled ola bilər. stats serverin ölçdüyü göstəricilərdir: audioS göndərdiyiniz audiodur, processingS sessiyanın işlədiyi divar saatı vaxtıdır, realtimeFactor isə onların nisbətidir. Canlı sessiya 1-in bir az altında nəticələnir, çünki divar saatı vaxtına eof-dan sonrakı son buferin boşaldılması da daxildir. Bundan xeyli aşağı rəqəm sessiyanın audionun davamından nəzərəçarpacaq dərəcədə uzun çəkdiyi deməkdir; ilk baxılacaq yer audionun nə qədər sürətlə gəldiyidir. Fayl üçün eyni rəqəm transkriptin oxunma sürətindən neçə dəfə tez qayıtdığını göstərir.
Canlı sessiya GET /v1/realtime/capabilities-dən alınan limits.maxDurationS-dən çox davam etmir və onun audiosu planınızın canlı tarifi ilə hər saniyə kreditinizi xərcləyir; hər hansı transkripsiya ilə eyni 5 dəqiqəlik minimum tətbiq olunur: audio alan hər sessiya ən azı 5 dəqiqə üçün hesablanır (qiymətlər). Eyni socket tam fayl da qəbul edir: sessiyanı input: { kind: "file" } ilə yaradın, faylın öz baytlarını (mp3, wav, flac, ogg, m4a) və eof göndərin; transkript audionun səslənməsindən dəfələrlə sürətli qayıdır, yolboyu transcript.progress çərçivələri ilə.
Davam etdirmə yoxdur. Kəsilmiş əlaqə sessiyanı bitirir; yenidən qoşulmaq yeni sessiya yaratmaq deməkdir, onun öz minimumu ilə hesablanır və canlı mənbənizdən yenidən axın, seq 0-dan başlayır. Yenidən qoşulma özü istənilən socket API-si üçün standart nümunədir və bir dəfə düzgün qurmağa dəyər:
error çərçivəsində retryable: true, Retry-After ilə 503, və ya heç bir çərçivə olmadan 30 saniyə ki, bu capacity_exceeded deməkdir. invalid_token və session_expired serverinizdən yeni streamUrl deməkdir, köhnəsi ilə başqa cəhd yox.Sessiya yaradan marşrutu çağırmaq üçün ucuz saxlayın və bağlanmanı təzə başlanğıc kimi qəbul edin. Davam etdirmənin olmaması heç nə saxlamamağın qiymətidir; əgər transkript socketdən çox yaşamalıdırsa, onu özünüz saxlayın.
Eyni axının qeyd kimi saxlanmasını, sonradan düzəldilməsini, ixrac edilməsini və auditoriyanın öz dillərində oxumasını istəyirsinizsə, onu bunun əvəzinə canlı transkripsiyaya yerləşdirin. Canlı yayıma çıxış bələdçisi eyni PCM socketini transkripsiya və yayım keçidi vasitəsilə izah edir, dinləmə bələdçisi isə abunəçi tərəfini göstərir: hər dinləyici dil seçir, gec qoşulan isə lastSeq kursoru ilə yetişir. Bunun otaq üçün necə göründüyü bir keçid, bütün dillər mövzusudur.
Saxlanılmasını istədiyiniz fayl üçün saxlanılan marşrut API ilə faylın transkripsiyası-dır. Bütün sahələr daxil olmaqla tam çərçivə istinadı API istinadında Mikrofonun yayımlanması bələdçisidir.