錄製
使用時機
當你碰觸到錄製器相關功能時,請參考此技能:錄製按鈕、進行中工具列、權限提示、分塊上傳流程或相機氣泡。如果你要新增對新來源的支援(例如分頁擷取、iPhone 連續互通相機),或變更伺服端分塊最終化方式,這份文件就是你的指引地圖。
接觸的資料模型
recordings— 當使用者按下「錄製」或匯入來源時,就會建立該資料列。原生/檔案錄製會依序轉換uploading→processing→ready(或failed)。videoUrl、durationMs、videoSizeBytes、width、height、hasAudio、hasCamera等欄位會隨著上傳串流而填入。Loom 匯入使用import-loom-recording,並以 Clips 代管的 MP4 或 Loom 嵌入(當 MP4 匯出不可用時)轉為 ready 狀態。application_state.record-intent— 代理程式在想要開始錄製時寫入此項目。UI 會讀取並清除它,然後提示權限。application_state.navigation— 當錄製器啟用時,設為{ view: "record" }。
二進位上傳會經由自訂 API 路由(/api/uploads/:id/chunk 和 /api/uploads/:id/abort),而非 action,因為 action 不適合處理二進位串流主體。最後一個區塊會呼叫 finalize-recording。Loom URL 匯入僅包含中繼資料,應透過 import-loom-recording action 進行。
某些錄製會連結到會議 — 當錄製資料列的 meeting_id 不為 null 時,表示它是透過 start-meeting-recording 建立的,此時 recording 和 meetings 技能都適用。請參閱 meetings 技能以了解雙向連結。
生命週期
- 意圖。 使用者點擊「錄製」(全域
Cmd+Shift+L)或代理程式呼叫pnpm action start-recording --mode=screen。代理程式版本會將record-intent寫入應用程式狀態;UI 會擷取它並啟動與使用者點擊相同的流程。 - 權限。 對螢幕呼叫
navigator.mediaDevices.getDisplayMedia({ video, audio }),對相機呼叫getUserMedia({ video, audio })。不要在沒有使用者手勢的情況下提示權限。代理程式路徑依賴 UI 按鈕 — 我們絕不繞過瀏覽器的權限模型。 - 建立資料列。 一旦串流被授予,立即呼叫
create-recording以status: "uploading"和預先產生的 ID 插入資料列。該 ID 用於後續的每個區塊上傳。 - 錄製。 使用
mimeType: "video/webm;codecs=vp9,opus"(備用為 vp8,然後是瀏覽器預設值)啟動MediaRecorder。使用timeslice: 2000讓區塊每 2 秒到達一次。 - 上傳每個區塊。
ondataavailable將位元組串流到/api/uploads/chunk,並帶有標頭X-Recording-Id和X-Chunk-Index,同時獨立地將原始區塊鏡像到IndexedDB作為最佳努力備份。短暫的傳遞失敗會暫停上傳並僅在當前瀏覽器分頁保持開啟時恢復記憶體中的來源;沒有背景重新發送工作者或關閉分頁後的復原保證。IndexedDB寫入失敗不會阻止擷取或上傳。 - 即時轉錄。 與 MediaRecorder 並行,
useLiveTranscription執行 Web Speech API 以即時累積轉錄文字。在停止時,客戶端呼叫save-browser-transcript以立即持久化結果 — 無需 API 金鑰。桌面錄製在可用時優先使用本機 Whisper/macOS 語音,並在非 Mac 機器上的 webview 中回退到 Web Speech,然後再依賴上傳轉錄。 - 最終化。 在停止時,將最後一個區塊傳送至
/api/uploads/:id/chunk?isFinal=1。路由會呼叫finalize-recording,它會拼接區塊、使媒體可定位(見下文)、在配置儲存時上傳完成的媒體、將status轉換為ready,然後啟動request-transcript以獲得更高品質的輸出(參閱ai-video-tools)。 - 立即導航。 桌面錄製器在停止開始最終化時立即開啟
/r/:id。錄製資料列已經存在,因此頁面可以在顯示標題、分享連結和上傳進度的同時,輪詢從uploading或processing到ready的狀態。不要等待上傳/最終化回應才開啟頁面。
行動隨附應用程式生命週期
Agent-Native 行動應用程式使用相同的錄製資料列和二進位上傳路由,但搭配原生擷取原始物件:
- 相機影片/匯入使用
expo-camera/ 系統照片挑選器;會議音訊使用啟用背景錄製功能的expo-audio。 - 原生檔案被複製到文件目錄中,並在上傳開始前寫入一個帶型別的 AsyncStorage 擷取任務。
create-recording接收一個穩定的客戶端產生的 ID、sourceAppName: "Agent-Native Mobile"以及容器 MIME 類型。- 上傳透過 Expo FileHandle 讀取最多 3 MiB,並在每次確認的 POST 後持久化下一個區塊索引。伺服器端的 4 MiB 上限仍然適用。
- 前景/恢復協調輪詢
/api/uploads/:id/status;可重試的失敗會回退,且絕不會丟棄本機檔案。完成時可以發布本機通知,且錄製會出現在所有地方的 Clips 中。
行動音訊 M4A 是一個 MP4 容器,並使用錄製路由的 MP4 MIME 別名。位元組不會被轉換或完整載入到 JavaScript 記憶體中。
因為手機在任何網路工作之前持久化每個音訊/影片檔案,並從其持久佇列恢復有界限的區塊上傳,短暫的上傳失敗絕不會遺失擷取內容。不要要求使用者保持擷取畫面開啟或在上傳失敗後重新錄製;告訴他們重新連線到 Clips 並從行動 Home 重試已儲存的任務。
行動會議擷取僅限麥克風;切勿聲稱它具有桌面麥克風加系統音訊的保真度。請參閱 meetings 技能以了解這對與會者歸屬意味著什麼。
可定位播放(不要交付原始的 MediaRecorder 輸出)
原始的 MediaRecorder 檔案不利於漸進式 HTTP 播放,這表現為「片段需要幾分鐘才能載入」和「每次搜尋都需要重新緩衝」,即使檔案下載正常:
- MP4 的
moov中繼資料原子寫在mdat之後,因此播放器必須先獲取整個檔案才能開始或搜尋。 - WebM 是一個沒有 Cues(搜尋索引)和未知分段持續時間的即時串流,因此 Chrome 不會遵從
currentTime = X,而必須掃描/下載。
finalize-recording 在上傳前修復此問題:MP4 獲得純 TS faststart(server/lib/faststart.ts),WebM 獲得無損的 ffmpeg -c copy remux,它會寫入 SeekHead + Cues + 真實持續時間(server/lib/video-remux.ts)。兩者都是最佳努力 — 失敗時我們上傳原始檔案,絕不阻塞最終化。高於 CLIPS_INLINE_REMUX_MAX_BYTES(預設 200 MB)的錄製會跳過內聯處理,並在背景中修復。
串流/可恢復上傳路徑將原始位元組直接轉發給提供者,且無法內聯重寫它們,因此 finalize-recording 會為這些錄製排程一個背景 ensureRecordingSeekable 處理。
要修復在此功能存在之前(或透過串流)上傳的片段,請呼叫 reprocess-recording action:--id、--ids='[...]' 或 --all --limit=N。它會重新獲取提供者媒體、重寫它、重新上傳,並重新指向資料列。它是冪等的(已可定位的片段會被跳過,除非使用 --force),且僅觸及呼叫者擁有的提供者代管片段。當使用者報告特定慢/緩衝片段時,這是正確的工具。
可定位性 remuxing 無法修復音訊在影片軌道有較大時間戳記間隙時繼續進行的錄製(行動瀏覽器在使用者切換應用程式後暫停相機時常見)。對於凍結或似乎在宣告持續時間之前停止的片段,請使用 --normalizeTimeline=true 呼叫 reprocess-recording。該顯式模式使用相同的擁有者範圍獲取和上傳流程,但會完全轉碼為恆定 30 fps 的 faststart MP4(H.264 + AAC)。它保留音訊,並在缺少影格的間隙中複製最後一個解碼的影片影格。action 會上傳到一個新的媒體物件,並僅在驗證輸出被儲存後才原子地重新指向資料列;任何轉碼、音訊驗證、上傳或併發更新失敗都會保留原始 URL 和格式。
Loom 導入
對 Loom 分享或嵌入 URL 使用 import-loom-recording。action 會驗證 Loom URL、從 Loom 的公開端點讀取 Loom oEmbed 中繼資料,並建立一個帶有 Loom 嵌入 URL、縮圖、標題、持續時間和尺寸的 ready 錄製。當 Loom 在分享頁面上公開一個簽署的公開轉錄 JSON URL 時,action 會將該轉錄導入到 Clips 並儲存標準化的區段;切勿儲存 Loom 簽署的 CDN URL。
當 Loom 公開一個可下載的公開 MP4 時,import-loom-recording 會下載它、將位元組重新上傳到 Clips 儲存,並建立一個 ready、可播放的 Clips 代管錄製,當分享頁面公開一個時,會導入 Loom 的公開轉錄。如果 Loom 允許公開播放但未公開 MP4,action 會保留一個 ready、嵌入支援的錄製,並在可用時仍導入轉錄。
嵌入支援的 Loom 導入會渲染一個 Loom iframe 並隱藏原生 Clips 編輯器。重新上傳的 Loom 導入支援 Clips 原生修剪、匯出、影格提取和基於上傳的轉錄;當 embed 支援的導入需要這些功能時,要求使用者上傳原始影片檔案。
瀏覽器診斷與錄製器安裝選項
瀏覽器錄製可以透過 createBrowserDiagnosticsCapture 儲存有界限、經過編輯的診斷資訊:控制檯訊息加上 fetch/XHR 方法、URL、狀態、持續時間和錯誤。瀏覽器錄製器僅從錄製器頁面本身擷取活動。Clips Chrome 擴充功能是瀏覽器日誌的活動分頁路徑:它使用擴充功能擷取會話啟動 /record 並傳遞 developerLogs=1/0,然後以來源 extension 儲存診斷資訊。
Web Store 列表是即時的,因此公開的 Chrome 擴充功能選項預設會顯示。否則顯示「下載桌面應用程式」的 UI 提示會使用共享的安裝選擇元件(CaptureInstallButton / CaptureInstallInlineLink)來提供兩個選項:用於瀏覽器日誌的 Chrome 擴充功能,以及用於最順暢原生擷取的桌面應用程式。設定 VITE_CLIPS_CHROME_EXTENSION_ENABLED=0 以再次隱藏 Chrome 選項,或 VITE_CLIPS_CHROME_EXTENSION_URL 指向不同的列表。
save-browser-diagnostics 是 UI/內部的。它儲存有界限的控制檯日誌,加上 fetch/XHR 方法、URL 路徑/查詢鍵、狀態和持續時間。它絕不擷取標頭、主體、Cookie 或網路 URL 查詢值。控制檯文字保留有用的非秘密值,同時編輯看起來像憑證的鍵和標頭。當你有編輯器存取權限時,使用 get-recording-player-data 獲取完整的診斷有效載荷;參閱 video-sharing 技能以了解公開代理程式上下文暴露的更窄編輯串流。
Chrome 擴充功能
擴充功能位於 chrome-extension/。它使用 clipsExtensionId 和 clipsCaptureSessionId 啟動 /record,而錄製器將 CLIPS_CAPTURE_START / CLIPS_CAPTURE_STOP / CLIPS_CAPTURE_CANCEL 發送回擴充功能。它僅在使用者啟動的分頁上使用 Chrome 偵錯器 API,僅在錄製啟用時使用,並傳回與 save-browser-diagnostics 儲存的相同編輯過的診斷形狀。
擴充功能也增強了 GitHub 問題和 PR Markdown:一個狹窄的 github.com 內容腳本偵測 Clips /r/、/share/ 和 /embed/ 連結,然後在擴充功能擁有的預覽 iframe 中渲染現有的 /embed/:id 播放器,讓影片無需離開 GitHub 即可播放。除非經過審慎的權限審查,否則請將此範圍限定在 GitHub。
資料夾、空間與批次移動
對於單一和批次資料夾移動,使用 move-recording。對單一片段傳遞 id,或對選取的片段傳遞 ids,並以 folderId: null 將它們移動到媒體庫或空間根目錄。
螢幕截圖
螢幕截圖是一個 recordings 資料列,其 kind = "image"(在 shared/recording-kind.ts 中為 isImageRecording)。它與片段共享資料夾、空間、分享、評論和搜尋,但沒有影片部分:沒有轉錄、章節、底片條、時間軸或音訊。list-recordings --kind=image 會列出它們。
- 擷取、標註和編輯是瀏覽器工作(
create-screenshot、save-screenshot-edits,皆僅限 UI)。代理程式透過get-recording-player-data或/api/agent-frame.jpg讀取螢幕截圖,後者會為任何atMs回傳圖片本身。 imageUrl是服務的圖片;baseImageUrl是編輯器繪製的無標記基底。兩者都透過/api/thumbnail/:id服務,該端點會套用密碼、過期時間和編輯保留。切勿交出儲存 URL。- 編輯分為兩步驟,與影片相同:方框儲存為待處理疊加(對檢視者保留),然後燒入。燒入會先寫入
editsJson.burnInProgress,然後刪除原始檔案,並在之後清除它;unreclaimedUrls列出尚未刪除的較舊副本。兩者都僅限伺服器端(server/lib/screenshot-edits.ts),且絕不能到達檢視者。 - 影片編輯 action 在
assertNativeRecordingMedia拒絕螢幕截圖;只有save-screenshot-edits會寫入螢幕截圖的editsJson。
暫停 / 繼續
所有現代瀏覽器都支援 MediaRecorder.pause() / .resume()。在暫停期間保持單一的 MediaRecorder 實例 — 不要拆解串流,否則權限提示會再次觸發。在暫停時,上傳工作者會繼續清空其緩衝區,以便在使用者停止之前趕上進度。
相機氣泡
當模式為 screen+camera 時,「氣泡」指兩個不同的東西:
- 螢幕上的自檢視。
CameraBubble渲染一個樸素的、未記錄的<video>元素,使用者可以在倒數/錄製期間拖動它來構圖。它從未被擷取 — 它只是本地構圖 UI。 - 記錄的合成畫面。 顯示擷取無法在使用者錄製另一個視窗/應用程式時包含該 DOM 元素,因此儲存的影片必須在
MediaRecorder開始之前將相機圓圈烘焙進去。compositor模組(app/record/compositor/)處理此問題:它在記憶體中建立一個離屏 canvas,將顯示擷取串流繪製到其上,然後使用 canvascaptureStream()作為 MediaRecorder 的來源。相機串流被繪製到 canvas 的右下角(可拖曳位置)。這會產生一個組合後的單一影片串流,其中相機覆疊已燒入。
錯誤復原
- 權限拒絕。 如果使用者拒絕螢幕或相機權限,
create-recording永遠不會被呼叫。UI 應該顯示一條友好的訊息,解釋為什麼需要權限,並引導他們到瀏覽器的權限設定。 - MediaRecorder 錯誤。
MediaRecorder可能會因各種原因觸發error事件(例如,找不到編解碼器、硬體問題)。監聽此事件並優雅地處理:停止錄製,顯示錯誤訊息,並將使用者帶回錄製前狀態。查看記錄的區塊以進行除錯。 - 上傳失敗。 分塊上傳可能會因網路問題、伺服器錯誤等而失敗。該技能實作重試邏輯並將失敗記錄到
IndexedDB。最終的失敗(在所有重試耗盡後)會將錄製狀態設為failed,並在/r/:id頁面上顯示錯誤訊息。使用者應該被允許重試或刪除錄製。 - 最終化失敗。
finalize-recording可能會因影片處理問題(例如,損壞的串流、伺服器資源不足)而失敗。這會使錄製處於processing狀態。提供一個重試按鈕或顯示一個錯誤,指示使用者聯絡支援。
螢幕錄製專用注意事項
- 系統音訊。 在大多數瀏覽器中,
getDisplayMedia不包含系統音訊。若要包含系統音訊,使用者需要安裝一個瀏覽器擴充功能或使用桌面應用程式。該技能會相應地提示。 - 擷取整個螢幕 vs 視窗/分頁。
getDisplayMedia允許使用者在提示時選擇要分享的整個螢幕、視窗或分頁。該技能尊重此選擇;它無法以程式設計方式限制它。 - 瀏覽器分頁錄製。 該技能支援透過
browser來源類型錄製特定的瀏覽器分頁。這需要 Chrome 擴充功能,該擴充功能使用除錯器 API 來擷取分頁內容。
Chrome 擴充功能詳細資訊
Chrome 擴充功能是 Clips 的重要組成部分,為瀏覽器分頁錄製提供與桌面應用程式類似的無縫體驗。其原始碼位於 chrome-extension/ 目錄中。擴充功能的主要功能包括:
- 分頁錄製。 它使用 Chrome 除錯器 API 來擷取特定瀏覽器分頁的內容,包括音訊。
- 診斷。 它記錄瀏覽器活動(網路請求、控制檯日誌)以協助除錯錄製問題。
- 預覽。 它在 GitHub 問題/PR 上下文中渲染 Clips 影片的預覽。
部署擴充功能需要使用 Chrome 開發人員儀表板並建立一個新的 zip 套件。chrome-extension/ 目錄包含所有必需的檔案。