浮動字幕 SSE
概述
浮動字幕 SSE 讓你以獨立、唯讀的連線,即時訂閱「進行中錄音」的逐字稿(來源語言原文+目標語言翻譯),用於桌面浮動字幕視窗、第二螢幕字幕等情境。此 feed 與錄音本身的 WebSocket 連線分離,可在不同裝置/視窗單獨開啟。「進行中錄音」涵蓋一般錄音與廣播直播中——廣播主講者於直播期間亦可用本 feed 訂閱自己的即時逐字稿。
注意:浮動字幕 SSE 的基礎路徑為
https://vas-poc.vurbo.ai(即時服務,與 WebSocket 同源),與一般 SSE API 的https://vas-poc.vurbo.ai/api/v1/sse不同。
連線資訊
| 項目 | 值 |
|---|---|
| 基礎路徑 | https://vas-poc.vurbo.ai |
| 協定 | HTTP + Server-Sent Events (SSE) |
| 資料格式 | text/event-stream |
| 認證方式 | feed_token(綁定該錄音,無需 API Key) |
端點總覽
| 方法 | 端點 | 說明 |
|---|---|---|
| POST | /api/v1/auth/tasks/{taskId}/subtitle-feed-token | 換取浮動字幕 feed_token(詳見 Subtitle Feed Token API) |
| GET | /tasks/{task_id}/subtitle | 即時逐字稿 SSE 串流 |
連線流程
- 錄音擁有者以 API Key 呼叫
POST /api/v1/auth/tasks/{taskId}/subtitle-feed-token換取短效feed_token。 - 浮動字幕視窗以該
feed_token連線GET /tasks/{task_id}/subtitle,接收 SSE 串流。 feed_token在有效期內可重連(每次驗證成功會自動延長有效期);長時間錄音應在到期前重新換取。
現場觀眾亦可由擁有者分享的連結連線:以分享密鑰換取觀眾 Token(見 Subtitle Feed Token API),再以該 Token 連線本 SSE。
GET /tasks/{task_id}/subtitle
功能說明
以唯讀方式訂閱進行中錄音的即時逐字稿,接收原文(含辨識中與已確定)與翻譯結果的 SSE 串流。可跨裝置、跨視窗連線;支援自動重連與斷線補播。
使用場景
- 桌面常駐浮動字幕視窗
- 第二螢幕 / 投影字幕顯示
- 雙語(原文+翻譯)即時字幕
認證方式
feed_token 認證(無需 API Key):透過 Query Parameter feed_token 驗證。Token 綁定該錄音,並採滑動有效期(每次驗證成功自動延長)。
請求參數
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
task_id | path | string | 是 | 錄音 ID |
feed_token | query | string | 是 | 浮動字幕存取 Token(由 token 端點換取) |
lang | query | string | 否 | 篩選目標翻譯語言;逗號分隔(如 en-US,ja-JP)。省略或 * 表示全部 |
請求範例
// 接收全部語言
const eventSource = new EventSource(
'https://vas-poc.vurbo.ai/tasks/3f9a.../subtitle?feed_token=xxx'
);
// 只接收英文翻譯
const eventSource = new EventSource(
'https://vas-poc.vurbo.ai/tasks/3f9a.../subtitle?feed_token=xxx&lang=en-US'
);
連線時序邊界
| 情境 | HTTP 狀態碼 | 處理建議 |
|---|---|---|
| 錄音尚未開始(或已逾時消失) | 425 Too Early | 稍後重試 |
| 錄音已結束 | 410 Gone | 提示使用者錄音結束、關閉視窗 |
| feed_token 無效 / 過期 | 401 Unauthorized | 重新換取 feed_token |
| 連線數或觀眾人數已達上限 | 429 Too Many Requests | 稍後重試或關閉多餘視窗 |
事件類型
| 事件 | 說明 | 備註 |
|---|---|---|
connected | 連線確認 | 含目前狀態與語言資訊 |
result | 原文(STT)或即時翻譯 | 依 payload 帶 origin 或 translations |
translation | 單句重翻結果 | 帶 is_retranslation |
batch_retranslation | 批次重翻結果 | 切換語言時 |
language_switch_start / language_switch_done | 批次重翻進度 | 互譯切語言時 |
language_switched | 互譯語言切換 | 互譯模式 |
translation_language_removed | 翻譯語言已移除 | 多語 switch_language(op=remove)成功後 |
segment_discarded | 句子作廢 | 該 sid 不會再有後續;請從「翻譯中」狀態移除 |
status | 狀態通知 | 暫停 / 恢復 / 停止 |
viewers | 目前觀看人數 | 觀眾分享啟用時 |
subtitle_closed | 分享已關閉、連線結束 | 觀眾端;收到後應停止重連 |
speaker_renamed | 說話者重命名 | 多人模式 |
speaker_reassigned | 單句說話者修改 | 多人模式 |
speakers_merged / speakers_auto_merged | 語者合併 | 多人模式 |
每個事件的
data與錄音主控端 WebSocket 的對應訊息一致,欄位定義詳見 WebSocket 事件。下方僅列浮動字幕常用事件。
事件格式
1. connected - 連線確認
{
"status": "live",
"recognition_mode": "single",
"source_lang": "zh-TW",
"translation_languages": ["en-US", "ja-JP"]
}
| 欄位 | 類型 | 說明 |
|---|---|---|
status | string | 錄音狀態:live / paused / reconnecting / ended |
recognition_mode | string | 辨識模式:single / multi_speaker / multi_language |
source_lang | string | 來源語言(非互譯模式) |
conversation_languages | array | 互譯模式的兩個語言(互譯模式才有,取代 source_lang) |
translation_languages | array | 目標翻譯語言列表(當前權威語言集:反映錄音中途的語言新增/移除,非開始錄音時的初始值)。沒有任何目標語言時不帶此欄位 |
2. result - 原文(STT)
data 帶 origin 時為原文:
{
"action": "result",
"origin": {
"sid": 1,
"language": "zh-TW",
"detected_language": "zh-TW",
"text": "大家好",
"is_final": true,
"speaker_id": "Guest-1",
"speaker_label": "Royx",
"start_time": "00:05"
}
}
| 欄位 | 類型 | 說明 |
|---|---|---|
sid | number | 句子 ID |
language | string | 來源語言。多語轉錄模式下逐句判定,會隨每句實際語言變動 |
detected_language | string | 該句偵測語言(多語/互譯擺位用;多人模式為固定來源語言,不可作語言徽章) |
text | string | 原文內容 |
is_final | boolean | false=辨識中(會被同 sid 的後續結果就地替換);true=已確定 |
speaker_id | string | 原始說話者 ID(多人模式,可選) |
speaker_label | string | 顯示標籤(多人模式,可選) |
start_time | string | 句子開始時間,格式 mm:ss |
3. result - 即時翻譯
data 帶 translations 時為翻譯(即時翻譯為一語言一則):
{
"action": "result",
"translations": {
"en-US": {
"sid": 1,
"text": "Hello everyone",
"is_final": true
}
}
}
| 欄位 | 類型 | 說明 |
|---|---|---|
translations | object | key 為目標語言代碼,value 為翻譯結果 |
translations.<lang>.sid | number | 對應原文句子 ID |
translations.<lang>.text | string | 翻譯內容 |
translations.<lang>.is_final | boolean | 是否為最終結果 |
說話者繼承:翻譯訊息本身不帶
speaker_id/speaker_label。前端應以sid對應到已收到的原文行,沿用其說話者資訊。
4. translation / batch_retranslation - 重翻
使用者在錄音中觸發重翻時,以相同 sid 送出更新後的翻譯,前端應就地替換:
{
"action": "translation",
"sid": 1,
"translations": {
"en-US": {
"sid": 1,
"text": "Hi everyone",
"is_final": true,
"is_retranslation": true
}
}
}
5. status - 狀態通知
{
"action": "status",
"status": "paused",
"message": "語音辨識已暫停"
}
| 欄位 | 類型 | 說明 |
|---|---|---|
status | string | 機器可讀的錄音生命週期狀態:live(恢復)/ paused(暫停)/ ended(停止)。前端應據此欄位動作:paused → 凍結畫面、ended → 關閉浮動字幕視窗、live → 恢復顯示。 |
message | string | 狀態顯示文字(不保證格式,請勿解析判斷狀態,一律以 status 欄位為準)。 |
浮動字幕視窗關閉時機:浮動字幕 SSE 串流在錄音停止後不會自動關閉(設計上維持連線)。主講者本人的浮動字幕視窗必須依此事件的
status: "ended"主動關閉,否則會凍結停留在最後一句。status欄位僅出現於pause/resume/stop三種生命週期轉換;set_name、互譯手動模式的start_speaking等其他status事件不帶此欄位。
6. speaker_renamed / speaker_reassigned / speakers_merged - 語者事件
多人模式專用,欄位與 WebSocket 事件 對應事件相同。前端收到後更新對應句子的顯示標籤。
{
"action": "speaker_renamed",
"speaker_id": "Guest-1",
"new_label": "Royx",
"affected_sids": [1, 3, 5]
}
7. language_switched - 互譯語言切換
互譯模式專用。
{
"action": "language_switched",
"active_lang": "en-US",
"translation_lang": "zh-TW"
}
8. viewers - 觀看人數
當啟用觀眾分享時,feed 會推送目前觀看人數;連線當下發送一次,之後人數變動時更新。
{
"count": 3,
"max": 10
}
| 欄位 | 類型 | 說明 |
|---|---|---|
count | number | 目前觀眾人數(不含擁有者本人) |
max | number | 觀眾人數上限(以伺服器設定為準,預設 10;請以此欄位為準,勿寫死數值) |
9. subtitle_closed - 分享關閉
主講者「關閉分享」或「停止錄音」時,伺服器會主動送此事件並隨即結束觀眾的連線(觀眾端為被動接收)。
{ "action": "subtitle_closed" }
觀眾端收到後應顯示「分享已結束」並停止自動重連(此時分享已關閉,重連也會被拒)。主講者本人的連線不受此事件影響。
補播與重連
- 連線時會先補播最近數句已確定的原文與翻譯(供中途加入 / 重連補回)。
- 語者事件也會納入補播:
speaker_renamed/speaker_reassigned/speakers_merged/speakers_auto_merged依原始時序重放(在其影響的句子之後)。消費端補播時應與即時相同處理——依affected_sids回溯更新既有句子的語者標籤,重連或中途加入才不會顯示改名前的舊語者名。 EventSource會自動重連;只要feed_token仍有效,重連後會再次補播中斷期間的已確定內容。辨識中(is_final:false)的草稿不補播,會由後續結果自然更新。
心跳機制
SSE 連線使用心跳保持連線活躍:
- 間隔:15 秒
- 格式:SSE 註解(以
:開頭) - 前端無需處理,瀏覽器會自動忽略
: heartbeat
前端範例
async function connectSubtitle(taskId, apiKey, lang = null) {
// 1. 換取 feed_token
const res = await fetch(
`https://vas-poc.vurbo.ai/api/v1/auth/tasks/${taskId}/subtitle-feed-token`,
{ method: 'POST', headers: { 'X-API-Key': apiKey } }
);
if (res.status === 425) {
// 錄音尚未就緒,短延遲後重試
return;
}
const { token } = await res.json();
// 2. 連線 SSE
let url = `https://vas-poc.vurbo.ai/tasks/${taskId}/subtitle?feed_token=${token}`;
if (lang) url += `&lang=${lang}`;
const eventSource = new EventSource(url);
eventSource.addEventListener('connected', (e) => {
const data = JSON.parse(e.data);
console.log(`狀態:${data.status},來源:${data.source_lang}`);
});
eventSource.addEventListener('result', (e) => {
const data = JSON.parse(e.data);
if (data.origin) {
// 原文:以 sid + is_final 就地替換
console.log(`[${data.origin.sid}] ${data.origin.text}`);
} else if (data.translations) {
for (const [lang, t] of Object.entries(data.translations)) {
console.log(`翻譯 (${lang}): ${t.text}`);
}
}
});
eventSource.addEventListener('status', (e) => {
console.log(`狀態:${JSON.parse(e.data).message}`);
});
eventSource.onerror = () => {
// 410=結束、401=token 失效;EventSource 會自動重連,必要時重新換 token
};
return eventSource;
}
版本:V1.24.1 最後更新:2026-10-07