SSE API

浮動字幕 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 串流

連線流程

  1. 錄音擁有者以 API Key 呼叫 POST /api/v1/auth/tasks/{taskId}/subtitle-feed-token 換取短效 feed_token。
  2. 浮動字幕視窗以該 feed_token 連線 GET /tasks/{task_id}/subtitle,接收 SSE 串流。
  3. 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_idpathstring是錄音 ID
feed_tokenquerystring是浮動字幕存取 Token(由 token 端點換取)
langquerystring否篩選目標翻譯語言;逗號分隔(如 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"]
}
欄位類型說明
statusstring錄音狀態:live / paused / reconnecting / ended
recognition_modestring辨識模式:single / multi_speaker / multi_language
source_langstring來源語言(非互譯模式)
conversation_languagesarray互譯模式的兩個語言(互譯模式才有,取代 source_lang)
translation_languagesarray目標翻譯語言列表(當前權威語言集:反映錄音中途的語言新增/移除,非開始錄音時的初始值)。沒有任何目標語言時不帶此欄位

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"
  }
}
欄位類型說明
sidnumber句子 ID
languagestring來源語言。多語轉錄模式下逐句判定,會隨每句實際語言變動
detected_languagestring該句偵測語言(多語/互譯擺位用;多人模式為固定來源語言,不可作語言徽章)
textstring原文內容
is_finalbooleanfalse=辨識中(會被同 sid 的後續結果就地替換);true=已確定
speaker_idstring原始說話者 ID(多人模式,可選)
speaker_labelstring顯示標籤(多人模式,可選)
start_timestring句子開始時間,格式 mm:ss

3. result - 即時翻譯

data 帶 translations 時為翻譯(即時翻譯為一語言一則):

{
  "action": "result",
  "translations": {
    "en-US": {
      "sid": 1,
      "text": "Hello everyone",
      "is_final": true
    }
  }
}
欄位類型說明
translationsobjectkey 為目標語言代碼,value 為翻譯結果
translations.<lang>.sidnumber對應原文句子 ID
translations.<lang>.textstring翻譯內容
translations.<lang>.is_finalboolean是否為最終結果

說話者繼承:翻譯訊息本身不帶 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": "語音辨識已暫停"
}
欄位類型說明
statusstring機器可讀的錄音生命週期狀態:live(恢復)/ paused(暫停)/ ended(停止)。前端應據此欄位動作:paused → 凍結畫面、ended → 關閉浮動字幕視窗、live → 恢復顯示。
messagestring狀態顯示文字(不保證格式,請勿解析判斷狀態,一律以 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
}
欄位類型說明
countnumber目前觀眾人數(不含擁有者本人)
maxnumber觀眾人數上限(以伺服器設定為準,預設 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

Copyright © 2026