ರಿಯಲ್-ಟೈಮ್ ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಟ್ ಎರಡು ಬಾರಿ ಬರುತ್ತದೆ: ಮೊದಲು ಬದಲಾಗುತ್ತಲೇ ಇರುವ ಡ್ರಾಫ್ಟ್ ಆಗಿ, ನಂತರ ಬದಲಾಗದ ಅಂತಿಮ ಸಾಲಾಗಿ. ಡ್ರಾಫ್ಟ್ ಅನ್ನು ಬದಲಿಸಬಹುದಾದ ಕೊನೆಯ ಭಾಗವಾಗಿ ಮತ್ತು ಅಂತಿಮ ಸಾಲನ್ನು ಇತಿಹಾಸವಾಗಿ ತೋರಿಸಿ; ಲೈವ್ ಕ್ಯಾಪ್ಶನ್ ವೀಕ್ಷಣೆಯ ಉಳಿದ ಭಾಗ ಕೇವಲ ಸಂಪರ್ಕ ಕೆಲಸ.
ರಿಯಲ್-ಟೈಮ್ ಸೆಷನ್ ಎಂಬುದು VocalStack API ಮಾಡುವ ಅತ್ಯಂತ ಸರಳ ಕೆಲಸ. ನಿಮ್ಮ ಸರ್ವರ್ ಒಂದು HTTP ಕರೆ ಮಾಡಿ ಸಹಿ ಮಾಡಲಾದ streamUrl ಪಡೆಯುತ್ತದೆ. ನಿಮ್ಮ ಕ್ಲೈಂಟ್ ಆ URL ಗೆ WebSocket ತೆರೆಯುತ್ತದೆ, ಸೆರೆಹಿಡಿದಂತೆಯೇ ಕಚ್ಚಾ ಆಡಿಯೊವನ್ನು ಕಳುಹಿಸುತ್ತದೆ ಮತ್ತು ಮಾತನಾಡುವವರಿಗಿಂತ ಕೆಲವೇ ಕ್ಷಣ ಹಿಂದೆ JSON ಟೆಕ್ಸ್ಟ್ ಫ್ರೇಮ್ಗಳಾಗಿ ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಟ್ ಓದುತ್ತದೆ. ಆಡಿಯೊ ಮುಗಿದಾಗ eof ಕಳುಹಿಸಿ; ಸರ್ವರ್ ಉಳಿದಿರುವುದನ್ನು ಫ್ಲಷ್ ಮಾಡಿ, ಸೆಷನ್ ಅಳೆದ ಅಂಕಿಅಂಶಗಳನ್ನು ವರದಿ ಮಾಡಿ ಮುಚ್ಚುತ್ತದೆ.
ಏನನ್ನೂ ಸಂಗ್ರಹಿಸುವುದಿಲ್ಲ. ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಟ್ ಕೇವಲ ವೈರ್ನಲ್ಲಿ ಮಾತ್ರ ಇರುತ್ತದೆ; ಕ್ಯಾಪ್ಶನ್ ವೀಕ್ಷಣೆ, ಮೀಟಿಂಗ್ ಓವರ್ಲೇ ಅಥವಾ ನಿಮ್ಮ ಆ್ಯಪ್ನ ಧ್ವನಿ ವೈಶಿಷ್ಟ್ಯಕ್ಕೆ ಇದೇ ಬೇಕಾಗುತ್ತದೆ. ಪದಗಳನ್ನು ಉಳಿಸಲು, ಹಂಚಿಕೊಳ್ಳಲು ಮತ್ತು ನಂತರ ಎಕ್ಸ್ಪೋರ್ಟ್ ಮಾಡಲು ಬಯಸಿದರೆ, ಬದಲಿಗೆ ಲೈವ್ ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಷನ್ ಮತ್ತು ಬ್ರಾಡ್ಕಾಸ್ಟ್ ಬಳಸಿ; ಕೊನೆಯ ವಿಭಾಗ ಆ ಕಡೆ ಸೂಚಿಸುತ್ತದೆ.
ಹೆಚ್ಚಿನ ಸ್ಟ್ರೀಮಿಂಗ್ ಭಾಷಣ API ಗಳು ಈ ರೂಪವನ್ನೇ ಹಂಚಿಕೊಳ್ಳುತ್ತವೆ: 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 ಮಾತ್ರ ನಿಮ್ಮ ಕೀ ಬಯಸುತ್ತದೆ, ಆದ್ದರಿಂದ ಅದು ನಿಮ್ಮ ಸರ್ವರ್ನಲ್ಲಿ ನಡೆಯುತ್ತದೆ.
Write ಅನುಮತಿಯೊಂದಿಗೆ Developers, API keys ಅಡಿಯಲ್ಲಿ ಕೀ ರಚಿಸಿ. ಕೀ ಸರ್ವರ್ಗಳು ಮತ್ತು ಸ್ಕ್ರಿಪ್ಟ್ಗಳಿಗಾಗಿ: ವೆಬ್ ಪುಟದಿಂದ ಅಥವಾ URL ನಲ್ಲಿ ಕಳುಹಿಸಿದ ಕೀಯನ್ನು API ನಿರಾಕರಿಸುತ್ತದೆ, ಆದ್ದರಿಂದ ನೀವು ನಿಮ್ಮದೇ ಒಂದು ರೂಟ್ ಹಿಂದೆ ಸೆಷನ್ ರಚಿಸುತ್ತೀರಿ, ಮತ್ತು ಪುಟವು streamUrl ಅನ್ನು ಮಾತ್ರ ನೋಡುತ್ತದೆ.
URL ತನ್ನದೇ ಆದ ಟೋಕನ್ ಹೊತ್ತೊಯ್ಯುತ್ತದೆ; ಇದಕ್ಕೆ ಕಾರಣ ಬ್ರೌಸರ್ನ ಮಿತಿ, ಅದನ್ನು ಸಾಮಾನ್ಯವಾಗಿ ತಿಳಿದಿರುವುದು ಉಪಯುಕ್ತ. ಬ್ರೌಸರ್ WebSocket ಕನ್ಸ್ಟ್ರಕ್ಟರ್ ವಿನಂತಿ ಹೆಡರ್ಗಳನ್ನು ಹೊಂದಿಸಲಾಗುವುದಿಲ್ಲ, ಆದ್ದರಿಂದ ಕೀ ಹಾಕಲು Authorization ಹೆಡರ್ ಇಲ್ಲ; ಬ್ರೌಸರ್ ಬಳಸುವ ಪ್ರತಿಯೊಂದು ಭಾಷಣ API ತನ್ನ ಸಾಕೆಟ್ ಅನ್ನು URL ನಲ್ಲಿರುವ ಅಲ್ಪಾವಧಿಯ ಟೋಕನ್ನಿಂದ (ಅಥವಾ ಸಬ್ಪ್ರೋಟೋಕಾಲ್ ಹೆಸರಿನೊಳಗೆ ಅಡಗಿಸಿ) ದೃಢೀಕರಿಸುತ್ತದೆ. ಅಲ್ಪಾವಧಿಯದ್ದೇ ಮುಖ್ಯ: URL ಲಾಗ್ಗಳು ಮತ್ತು ಇತಿಹಾಸದಲ್ಲಿ ಉಳಿಯುತ್ತದೆ, ಆದ್ದರಿಂದ ಅದು ಹೊತ್ತೊಯ್ಯುವುದು ಅವಧಿ ಮೀರಬೇಕು. expiresAt ಮೊದಲು ಸಂಪರ್ಕಿಸಿ.
SDK ಗಳನ್ನು ಪ್ಯಾಕೇಜ್ ರಿಜಿಸ್ಟ್ರಿಯಿಂದಲ್ಲ, Developers ಪ್ರದೇಶದಿಂದ ಡೌನ್ಲೋಡ್ ಮಾಡಲಾಗುತ್ತದೆ; ತ್ವರಿತ ಆರಂಭ ನ 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 ಅನ್ನು ನೀಡಿ; ಇಲ್ಲಿಂದ ಮುಂದೆ ಯಾವುದೇ ಕೀ ಬೇಕಾಗುವುದಿಲ್ಲ.
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, ಅಥವಾ ಗೌರವಿಸಬೇಕಾದ Retry-After ಹೆಡರ್ನೊಂದಿಗೆ 503 unavailable.
ಎರಡನೆಯದು, ಸ್ಯಾಚುರೇಶನ್ ದೋಷದಂತಲ್ಲ, ಮೌನದಂತೆ ಕಾಣುತ್ತದೆ. 30 ಸೆಕೆಂಡುಗಳೊಳಗೆ ಯಾವುದೇ ಫ್ರೇಮ್ ಬರದಿದ್ದರೆ, ಅದನ್ನು capacity_exceeded ಎಂದು ಪರಿಗಣಿಸಿ ಮತ್ತೆ ಸಂಪರ್ಕಿಸಿ; ಹೊಸ ಸೆಷನ್ ಸಾಮಾನ್ಯವಾಗಿ ಹಾದುಹೋಗುತ್ತದೆ. ಲೋಡಿಂಗ್ ಫ್ರೇಮ್ಗಳು ಬರುತ್ತಲೇ ಇದ್ದರೂ session.ready ಎಂದಿಗೂ ಬರದಿದ್ದರೆ, ಅದನ್ನು engine_unavailable ಎಂದು ಪರಿಗಣಿಸಿ; ಅದೂ ಮರುಪ್ರಯತ್ನಿಸಬಹುದಾದದ್ದು. SDK ಗಳಲ್ಲಿ, RealtimeSession.connect session.ready ನಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ ಮತ್ತು readyDeadlineMs ನಂತರ ಸರಿಯಾದ ಕೋಡ್ನೊಂದಿಗೆ ವಿಫಲವಾಗುತ್ತದೆ.
session.ready ಬಂದ ನಂತರ, ಮಾತನಾಡುವ ವೇಗದಲ್ಲಿಯೇ ಆಡಿಯೊ ಬರಬೇಕೆಂದು ಸರ್ವರ್ ನಿರೀಕ್ಷಿಸುತ್ತದೆ.
ಆಡಿಯೊ ಕಚ್ಚಾ PCM ನ ಬೈನರಿ ಫ್ರೇಮ್ಗಳಾಗಿ ಹೋಗುತ್ತದೆ: 32-ಬಿಟ್ ಫ್ಲೋಟ್, ಲಿಟಲ್-ಎಂಡಿಯನ್, 16 kHz, ಮೋನೋ (pcm_f32le, ಸೆಷನ್ನ ಡೀಫಾಲ್ಟ್ audio ಸ್ವರೂಪ). ಸೆರೆಹಿಡಿದಂತೆಯೇ, ಸುಮಾರು 100 ರಿಂದ 250 ms ತುಣುಕುಗಳಲ್ಲಿ ಕಳುಹಿಸಿ; ತುಣುಕುಗಳ ನಡುವೆ ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಟ್ಗಾಗಿ ಕಾಯಬೇಡಿ.
ಬೈಟ್ ಗಣಿತವನ್ನು ಒಮ್ಮೆ ಮಾಡುವುದು ಯೋಗ್ಯ. ಸೆಕೆಂಡಿಗೆ 16,000 ಸ್ಯಾಂಪಲ್ಗಳು, ಪ್ರತಿಯೊಂದೂ 4 ಬೈಟ್ಗಳು, ಅಂದರೆ ಸೆಕೆಂಡಿಗೆ 64,000 ಬೈಟ್ಗಳು; ಆದ್ದರಿಂದ 100 ms ಎಂದರೆ 6,400 ಬೈಟ್ಗಳು ಮತ್ತು 250 ms ಎಂದರೆ 16,000. ಭಾಷಣ ಮಾದರಿಗಳನ್ನು ಸಾಮಾನ್ಯವಾಗಿ 16 kHz ಮೋನೋದಲ್ಲಿ ತರಬೇತಿ ಮಾಡಲಾಗುತ್ತದೆ, ಅದಕ್ಕೇ ಅನೇಕ ರಿಯಲ್-ಟೈಮ್ API ಗಳು ಇದನ್ನು ಕೇಳುತ್ತವೆ; ಆದರೆ ಮೈಕ್ರೊಫೋನ್ಗಳು ಮತ್ತು ಬ್ರೌಸರ್ಗಳು 44.1 ಅಥವಾ 48 kHz ನಲ್ಲಿ, ಆಗಾಗ ಸ್ಟೀರಿಯೊದಲ್ಲಿ ಸೆರೆಹಿಡಿಯುತ್ತವೆ. ಬೈಟ್ಗಳು ಹೊರಹೋಗುವ ಮೊದಲು ಆಡಿಯೊವನ್ನು ಮರುಸ್ಯಾಂಪಲ್ ಮಾಡಿ ಚಾನೆಲ್ಗಳನ್ನು ಒಂದಾಗಿ ಮಡಚುವ ಕೆಲಸ ಯಾರಾದರೂ ಮಾಡಬೇಕು: ಇಲ್ಲಿ ಅದು 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 ಗೆ ಮರುಸ್ಯಾಂಪಲ್ ಮಾಡುತ್ತದೆ ಮತ್ತು ತುಣುಕುಗಳನ್ನು clock ಫ್ರೇಮ್ಗಳೊಂದಿಗೆ ಕಳುಹಿಸುತ್ತದೆ; ಬ್ರೌಸರ್ ಟ್ಯಾಬ್ ಅನ್ನು ನಿಧಾನಗೊಳಿಸಿದಾಗ ಅಂತರಗಳನ್ನು ಸರ್ವರ್ ಗುರುತಿಸಲು ಇವು ಸಹಾಯ ಮಾಡುತ್ತವೆ. ಪುಟವು ನಿಮ್ಮ ರೂಟ್ನಿಂದ ಸೆಷನ್ ಪಡೆಯುತ್ತದೆ ಮತ್ತು ಕೀಯನ್ನು ಎಂದಿಗೂ ನೋಡುವುದಿಲ್ಲ:
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 ಎಂದರೆ ಅಂತಿಮ: seq (0, 1, 2, ಅಂತರವಿಲ್ಲದೆ), start, end, text, speaker ಮತ್ತು ಪ್ರತಿ ಪದದ ಸಮಯದೊಂದಿಗೆ words ಹೊಂದಿರುವ ಒಂದು segment. ಅದನ್ನು ಸೇರಿಸಿ ಮತ್ತು ಅದು ಒಳಗೊಳ್ಳುವ ಡ್ರಾಫ್ಟ್ ಅನ್ನು ಕೈಬಿಡಿ; ಮುಂದಿನ ಡ್ರಾಫ್ಟ್ ಈ ಕಮಿಟ್ ಮುಗಿದ ಸ್ಥಳದಿಂದ ಆರಂಭವಾಗುತ್ತದೆ.
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 ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಕೊನೆಯದಾಗಿ, ನಿಮಗೆ ತಿಳಿಯದ ಫ್ರೇಮ್ ಪ್ರಕಾರಗಳನ್ನು ನಿರ್ಲಕ್ಷಿಸಿ: ಹೊಸವು ಮುನ್ಸೂಚನೆಯಿಲ್ಲದೆ ಸೇರಿಸಲ್ಪಡುತ್ತವೆ.
ಇಲ್ಲಿ ಪೈಥಾನ್ 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 ಫ್ರೇಮ್ ಆಗಿ ಬರುತ್ತದೆ, 24 kHz ಮೋನೋದಲ್ಲಿ base64 Ogg/Opus ಆಗಿ ಕ್ಲಿಪ್ ಹೊತ್ತು. ಉದ್ದ ವಾಕ್ಯ ತುಣುಕುಗಳಲ್ಲಿ (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 ನಂತರದ ಅಂತಿಮ ಫ್ಲಷ್ ಕೂಡ ಸೇರಿದೆ. ಅದಕ್ಕಿಂತ ಗಮನಾರ್ಹವಾಗಿ ಕೆಳಗಿರುವ ಸಂಖ್ಯೆ, ಸೆಷನ್ ಆಡಿಯೊ ಇದ್ದ ಅವಧಿಗಿಂತ ಗಮನಾರ್ಹವಾಗಿ ಹೆಚ್ಚು ಸಮಯ ತೆಗೆದುಕೊಂಡಿದೆ ಎಂದರ್ಥ; ಮೊದಲು ನೋಡಬೇಕಾದದ್ದು ಆಡಿಯೊ ಎಷ್ಟು ವೇಗವಾಗಿ ಬಂತು ಎಂಬುದು. ಫೈಲ್ಗೆ, ಅದೇ ಸಂಖ್ಯೆ ಪ್ಲೇಬ್ಯಾಕ್ಗಿಂತ ಎಷ್ಟು ಪಟ್ಟು ವೇಗವಾಗಿ ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಟ್ ಹಿಂದಿರುಗಿತು ಎಂದು ಹೇಳುತ್ತದೆ.
ಒಂದು ಸೆಷನ್ GET /v1/realtime/capabilities ನಿಂದ limits.maxDurationS ವರೆಗೆ ಗರಿಷ್ಠವಾಗಿ ನಡೆಯುತ್ತದೆ, ಮತ್ತು ಅದರ ಆಡಿಯೊ ನಿಮ್ಮ ಯೋಜನೆಯ ಲೈವ್ ದರದಲ್ಲಿ ಪ್ರತಿ ಸೆಕೆಂಡಿಗೆ ನಿಮ್ಮ ಕ್ರೆಡಿಟ್ ಖರ್ಚು ಮಾಡುತ್ತದೆ, ಯಾವುದೇ ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಷನ್ನಂತೆ ಅದೇ 5-ನಿಮಿಷದ ಕನಿಷ್ಠದೊಂದಿಗೆ: ಆಡಿಯೊ ಸ್ವೀಕರಿಸುವ ಪ್ರತಿ ಸೆಷನ್ಗೆ ಕನಿಷ್ಠ 5 ನಿಮಿಷಗಳ ಬಿಲ್ ಆಗುತ್ತದೆ (ಬೆಲೆಗಳು). ಅದೇ ಸಾಕೆಟ್ ಪೂರ್ಣ ಫೈಲ್ ಅನ್ನೂ ಸ್ವೀಕರಿಸುತ್ತದೆ: input: { kind: "file" } ಜೊತೆ ಸೆಷನ್ ರಚಿಸಿ, ಫೈಲ್ನ ಸ್ವಂತ ಬೈಟ್ಗಳನ್ನು (mp3, wav, flac, ogg, m4a) ಮತ್ತು eof ಕಳುಹಿಸಿ; ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಟ್ ಆಡಿಯೊ ಪ್ಲೇ ಆಗುವುದಕ್ಕಿಂತ ಹಲವು ಪಟ್ಟು ವೇಗವಾಗಿ ಹಿಂದಿರುಗುತ್ತದೆ, ದಾರಿಯುದ್ದಕ್ಕೂ transcript.progress ಫ್ರೇಮ್ಗಳೊಂದಿಗೆ.
ಮುಂದುವರಿಸುವ ಸೌಲಭ್ಯವಿಲ್ಲ. ಸಂಪರ್ಕ ಕಡಿತಗೊಂಡರೆ ಸೆಷನ್ ಕೊನೆಗೊಳ್ಳುತ್ತದೆ; ಮರುಸಂಪರ್ಕ ಎಂದರೆ ಹೊಸದನ್ನು ರಚಿಸುವುದು, ಅದಕ್ಕೆ ತನ್ನದೇ ಕನಿಷ್ಠ ಬಿಲ್ ಆಗುತ್ತದೆ, ಮತ್ತು ನಿಮ್ಮ ಲೈವ್ ಮೂಲದಿಂದ ಮತ್ತೆ ಸ್ಟ್ರೀಮ್ ಮಾಡುವುದು, seq ಸೊನ್ನೆಯಿಂದ ಮರಳಿ. ಮರುಸಂಪರ್ಕ ಯಾವುದೇ ಸಾಕೆಟ್ API ಗೆ ಪ್ರಮಾಣಿತ ಮಾದರಿ, ಮತ್ತು ಒಮ್ಮೆ ಸರಿಯಾಗಿ ಮಾಡುವುದು ಯೋಗ್ಯ:
error ಫ್ರೇಮ್ನಲ್ಲಿ retryable: true, ತನ್ನ Retry-After ಜೊತೆಗೆ 503, ಅಥವಾ ಫ್ರೇಮ್ ಇಲ್ಲದೆ 30 ಸೆಕೆಂಡುಗಳು, ಇದು capacity_exceeded ಅನ್ನು ಸೂಚಿಸುತ್ತದೆ. invalid_token ಮತ್ತು session_expired ಎಂದರೆ ನಿಮ್ಮ ಸರ್ವರ್ನಿಂದ ಹೊಸ streamUrl, ಹಳೆಯದರೊಂದಿಗೆ ಮತ್ತೊಂದು ಪ್ರಯತ್ನವಲ್ಲ.ಸೆಷನ್ ರಚಿಸುವ ರೂಟ್ ಕರೆಯಲು ಅಗ್ಗವಾಗಿರಲಿ, ಮತ್ತು ಮುಚ್ಚುವಿಕೆಯನ್ನು ಹೊಸ ಆರಂಭವೆಂದು ಪರಿಗಣಿಸಿ. ಮುಂದುವರಿಕೆ ಇಲ್ಲದಿರುವುದು ಏನನ್ನೂ ಸಂಗ್ರಹಿಸದ ಬೆಲೆ; ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಟ್ ಸಾಕೆಟ್ಗಿಂತ ಹೆಚ್ಚು ಕಾಲ ಉಳಿಯಬೇಕಾದರೆ, ಅದನ್ನು ಉಳಿಸಿ.
ಅದೇ ಸ್ಟ್ರೀಮ್ ಅನ್ನು ರೆಕಾರ್ಡಿಂಗ್ ಆಗಿ ಉಳಿಸಲು, ನಂತರ ತಿದ್ದಲು, ಎಕ್ಸ್ಪೋರ್ಟ್ ಮಾಡಲು, ಮತ್ತು ಕೇಳುಗರು ತಮ್ಮ ಭಾಷೆಗಳಲ್ಲಿ ಓದಲು ಬಯಸಿದರೆ, ಬದಲಿಗೆ ಅದನ್ನು ಲೈವ್ ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಷನ್ಗೆ ಪ್ರಕಟಿಸಿ. ಲೈವ್ ಹೋಗಿ ಮಾರ್ಗದರ್ಶಿ ಅದೇ PCM ಸಾಕೆಟ್ ಅನ್ನು ಟ್ರಾನ್ಸ್ಕ್ರಿಪ್ಷನ್ ಮತ್ತು ಬ್ರಾಡ್ಕಾಸ್ಟ್ ಲಿಂಕ್ ಮೂಲಕ ಹಾದುಹೋಗಿಸುತ್ತದೆ, ಮತ್ತು ಆಲಿಸಿ ಮಾರ್ಗದರ್ಶಿ ಚಂದಾದಾರರ ಭಾಗವನ್ನು ತೋರಿಸುತ್ತದೆ, ಅಲ್ಲಿ ಪ್ರತಿ ಕೇಳುಗರು ಒಂದು ಭಾಷೆ ಆರಿಸುತ್ತಾರೆ ಮತ್ತು ತಡವಾಗಿ ಸೇರಿದವರು lastSeq ಕರ್ಸರ್ನಿಂದ ಹಿಡಿಯುತ್ತಾರೆ. ಒಂದು ಕೋಣೆಗೆ ಅದು ಹೇಗೆ ಕಾಣುತ್ತದೆ ಎಂಬುದು ಒಂದು ಲಿಂಕ್, ಪ್ರತಿ ಭಾಷೆ ವಿಷಯ.
ಉಳಿಸಬೇಕಾದ ಫೈಲ್ಗೆ, ಸಂಗ್ರಹಿಸಿದ ಮಾರ್ಗ API ಮೂಲಕ ಫೈಲ್ ಟ್ರಾನ್ಸ್ಕ್ರೈಬ್ ಮಾಡಿ. ಪ್ರತಿ ಕ್ಷೇತ್ರವನ್ನೂ ಒಳಗೊಂಡ ಸಂಪೂರ್ಣ ಫ್ರೇಮ್ ಉಲ್ಲೇಖ, API ಉಲ್ಲೇಖದಲ್ಲಿರುವ ಮೈಕ್ರೊಫೋನ್ ಸ್ಟ್ರೀಮ್ ಮಾಡಿ ಮಾರ್ಗದರ್ಶಿ.