使用 IVS Web 廣播 SDK 發布和訂閱 | 即時串流
本文件將帶您了解開始使用 IVS 即時串流 Web 廣播 SDK 發布和訂閱階段的相關步驟。
概念
以下是三個以即時功能為基礎的核心概念:階段、策略 和 事件。設計目標是盡可能減少打造工作產品所需的用戶端邏輯數量。
階段
Stage
類別是主持人應用程式和 SDK 之間的主要交互點。它代表階段本身,用於加入和離開階段。建立和加入階段需有效且未過期的控制平面權杖字串 (表示為 token
)。加入和離開階段並不難:
const stage = new Stage(token, strategy) try { await stage.join(); } catch (error) { // handle join exception } stage.leave();
策略
StageStrategy
介面為主持人應用程式提供了將所需階段狀態傳送至 SDK 的管道。您必須實作以下三項函數:shouldSubscribeToParticipant
、shouldPublishParticipant
、和 stageStreamsToPublish
。以下將討論所有內容。
若要使用已定義的策略,請將其傳送給 Stage
建構函數。以下是使用策略將參與者的網路攝影機發布到階段並訂閱所有參與者的完整應用程式範例。後續幾節會詳細說明各項必要策略函數的用途。
const devices = await navigator.mediaDevices.getUserMedia({ audio: true, video: { width: { max: 1280 }, height: { max: 720 }, } }); const myAudioTrack = new LocalStageStream(devices.getAudioTracks()[0]); const myVideoTrack = new LocalStageStream(devices.getVideoTracks()[0]); // Define the stage strategy, implementing required functions const strategy = { audioTrack: myAudioTrack, videoTrack: myVideoTrack, // optional updateTracks(newAudioTrack, newVideoTrack) { this.audioTrack = newAudioTrack; this.videoTrack = newVideoTrack; }, // required stageStreamsToPublish() { return [this.audioTrack, this.videoTrack]; }, // required shouldPublishParticipant(participant) { return true; }, // required shouldSubscribeToParticipant(participant) { return SubscribeType.AUDIO_VIDEO; } }; // Initialize the stage and start publishing const stage = new Stage(token, strategy); await stage.join(); // To update later (e.g. in an onClick event handler) strategy.updateTracks(myNewAudioTrack, myNewVideoTrack); stage.refreshStrategy();
訂閱參與者
shouldSubscribeToParticipant(participant: StageParticipantInfo): SubscribeType
遠端參與者加入階段時,SDK 會向主持人應用程式查詢該參與者所需的訂閱狀態。選項包括 NONE
、AUDIO_ONLY
和 AUDIO_VIDEO
。傳回此函數的值時,主持人應用程式不需要擔心發布狀態、目前的訂閱狀態或階段連線狀態。若傳回 AUDIO_VIDEO
,SDK 會等到遠端參與者發布時才會訂閱,然後在整個程序中透過發出事件來更新主持人應用程式。
以下是實作範例:
const strategy = { shouldSubscribeToParticipant: (participant) => { return SubscribeType.AUDIO_VIDEO; } // ... other strategy functions }
對於一律希望所有參與者互相看到彼此的主持人應用程式 (例如影片聊天應用程式),這是此函數的完整實作程序。
您也可以採用更進階的實作方式。例如,假設應用程式在使用 CreateParticipantToken 建立權杖時提供了 role
屬性。此應用程式可能會使用 StageParticipantInfo
的 attributes
屬性,以根據伺服器提供的屬性選擇性訂閱參與者:
const strategy = { shouldSubscribeToParticipant(participant) { switch (participant.attributes.role) { case 'moderator': return SubscribeType.NONE; case 'guest': return SubscribeType.AUDIO_VIDEO; default: return SubscribeType.NONE; } } // . . . other strategies properties }
這可以用來建立一個階段,版主可以在不會被看到或聽到自己聲音的情況下監控所有訪客。主持人應用程式可以使用其他商業邏輯,讓版主看到彼此,但仍維持訪客看不到他們的狀態。
參與者訂閱組態
subscribeConfiguration(participant: StageParticipantInfo): SubscribeConfiguration
如果正在訂閱遠端參與者 (請參閱訂閱參與者),則 SDK 會查詢主機應用程式關於該參與者的自訂訂閱組態。此組態為選用功能,允許主機應用程式控制某些層面的訂閱用戶行為。如需有關可設定內容的詳細資訊,請參閱 SDK 參考文件中的 SubscribeConfiguration
以下是實作範例:
const strategy = { subscribeConfiguration: (participant) => { return { jitterBuffer: { minDelay: JitterBufferMinDelay.MEDIUM } } // ... other strategy functions }
此實作會將所有訂閱參與者的抖動緩衝區最低延遲更新為預設的 MEDIUM
。
您也可以透過 shouldSubscribeToParticipant
採用更進階的實作方式。指定的 ParticipantInfo
可用來專門更新特定參與者的訂閱組態。
我們建議您使用預設行為。只有在您想要變更特定行為時,才指定自訂組態。
發布
shouldPublishParticipant(participant: StageParticipantInfo): boolean
連線到階段後,SDK 會查詢主持人應用程式,看看特定參與者是否應發布。系統僅會根據提供的字符為具有發布許可的本機參與者調用此函數。
以下是實作範例:
const strategy = { shouldPublishParticipant: (participant) => { return true; } // . . . other strategies properties }
這是針對一個使用者總是想發布內容的標準影片聊天應用程式。他們可以靜音和取消靜音其音訊和影片內容,以立即隱藏起來,或看到/聽見內容。(他們也可以使用發布/取消發布,但這種方式速度較慢。建議在需經常變更可見性的使用案例中使用靜音/取消靜音。)
選擇要發布的串流
stageStreamsToPublish(): LocalStageStream[];
發布時,這會用來決定應發布哪些音訊和影片串流。稍後會在發布媒體串流中進行詳細說明。
更新策略
策略應處於動態狀態:從上述任何函數返回的值可以隨時進行修改。例如,若主持人應用程式在終端使用者按下按鈕前都不想發布,您可以從 shouldPublishParticipant
傳回一個變數 (例如 hasUserTappedPublishButton
)。當該變數根據終端使用者的互動而變更時,請呼叫 stage.refreshStrategy()
向 SDK 傳送信號,表示它應查詢策略中的最新值,並僅套用已變更的項目。若 SDK 發現 shouldPublishParticipant
值已變更,它便會開始進行發布。若 SDK 查詢後所有函數傳回與之前相同的值,則 refreshStrategy
呼叫將不會對階段進行修改。
若 shouldSubscribeToParticipant
傳回的值從 AUDIO_VIDEO
變更為 AUDIO_ONLY
,則系統會針對傳回值已變更的所有參與者移除影片串流 (若之前存有影片串流)。
一般而言,階段會採用策略,以最有效率的方式套用先前與目前策略之間的差異,主持人應用程式不必擔心正確進行管理所需的所有狀態。因此,請將呼叫 stage.refreshStrategy()
視為低成本的操作,因為除非策略發生變化,否則它什麼都不會執行。
事件
Stage
執行個體是一個事件觸發器。使用 stage.on()
時,系統會將階段的狀態傳送給主持人應用程式。主持人應用程式的 UI 更新通常可以完全由事件提供支援。事件如下所示:
stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {}) stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {}) stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {}) stage.on(StageEvents.STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED, (participant, state) => {}) stage.on(StageEvents.STAGE_PARTICIPANT_SUBSCRIBE_STATE_CHANGED, (participant, state) => {}) stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => {}) stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_REMOVED, (participant, streams) => {}) stage.on(StageEvents.STAGE_STREAM_ADAPTION_CHANGED, (participant, stream, isAdapting) => ()) stage.on(StageEvents.STAGE_STREAM_LAYERS_CHANGED, (participant, stream, layers) => ()) stage.on(StageEvents.STAGE_STREAM_LAYER_SELECTED, (participant, stream, layer, reason) => ()) stage.on(StageEvents.STAGE_STREAM_MUTE_CHANGED, (participant, stream) => {}) stage.on(StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED, (participant, stream) => {})
針對大多數這些事件提供了相應的 ParticipantInfo
。
事件提供的資訊應該不會對策略的傳回值造成影響。例如,呼叫 STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED
時,shouldSubscribeToParticipant
的傳回值應該不會變更。若主持人應用程式想要訂閱特定參與者,則無論該參與者的發布狀態為何,它都應傳回所需的訂閱類型。SDK 負責確保根據階段狀態,在正確的時間點執行策略的所需狀態。
發布媒體串流
使用與上述 從裝置擷取 MediaStream 中相同的步驟來擷取麥克風和攝影機等本機裝置。在範例中,我們會使用 MediaStream
來建立 SDK 用來發布的 LocalStageStream
物件清單:
try { // Get stream using steps outlined in document above const stream = await getMediaStreamFromDevice(); let streamsToPublish = stream.getTracks().map(track => { new LocalStageStream(track) }); // Create stage with strategy, or update existing strategy const strategy = { stageStreamsToPublish: () => streamsToPublish } }
發布螢幕共用
除了使用者的網路攝影機外,應用程式通常需要發布螢幕共用。發布螢幕共用需要為此舞台建立額外的權杖,具體而言,是為發布螢幕共用的媒體而建立權杖。使用 getDisplayMedia
並將解析度限制為最高 720p。之後的步驟就與將攝影機發布到此舞台類似。
// Invoke the following lines to get the screenshare's tracks const media = await navigator.mediaDevices.getDisplayMedia({ video: { width: { max: 1280, }, height: { max: 720, } } }); const screenshare = { videoStream: new LocalStageStream(media.getVideoTracks()[0]) }; const screenshareStrategy = { stageStreamsToPublish: () => { return [screenshare.videoStream]; }, shouldPublishParticipant: (participant) => { return true; }, shouldSubscribeToParticipant: (participant) => { return SubscribeType.AUDIO_VIDEO; } } const screenshareStage = new Stage(screenshareToken, screenshareStrategy); await screenshareStage.join();
顯示和移除參與者
訂閱完成後,您會透過 STAGE_PARTICIPANT_STREAMS_ADDED
事件收到 StageStream
物件陣列。該事件還會為您提供參與者的資訊,在您顯示媒體串流時提供協助:
stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => { const streamsToDisplay = streams; if (participant.isLocal) { // Ensure to exclude local audio streams, otherwise echo will occur streamsToDisplay = streams.filter(stream => stream.streamType === StreamType.VIDEO) } // Create or find video element already available in your application const videoEl = getParticipantVideoElement(participant.id); // Attach the participants streams videoEl.srcObject = new MediaStream(); streamsToDisplay.forEach(stream => videoEl.srcObject.addTrack(stream.mediaStreamTrack)); })
當參與者停止發布或取消訂閱串流時,系統會呼叫 STAGE_PARTICIPANT_STREAMS_REMOVED
函數,並傳回遭移除的串流。主持人應用程式應將此視為從 DOM 中移除參與者影片串流的信號。
STAGE_PARTICIPANT_STREAMS_REMOVED
會在串流可能遭移除的所有情況下調用,其中包括:
-
遠端參與者停止發布。
-
本機裝置取消訂閱,或將訂閱從
AUDIO_VIDEO
變更為AUDIO_ONLY
。 -
遠端參與者離開階段。
-
本機參與者離開階段。
由於 STAGE_PARTICIPANT_STREAMS_REMOVED
會在所有情況下調用,因此在遠端或本機離開操作期間,不需要使用自訂商業邏輯從 UI 移除參與者。
靜音和取消靜音媒體串流
LocalStageStream
物件具備控制是否將串流靜音的 setMuted
函數。此函數可以在從 stageStreamsToPublish
策略函數傳回之前或之後在串流上呼叫。
重要:如果呼叫 refreshStrategy
後由 stageStreamsToPublish
傳回新的 LocalStageStream
物件執行個體,則新串流物件的靜音狀態會套用至階段。建立新 LocalStageStream
執行個體時請務必小心,以確保維持預期的靜音狀態。
監控遠端參與者媒體靜音狀態
當參加者變更其影片或音訊的靜音狀態時,會以已變更的串流清單觸發 STAGE_STREAM_MUTE_CHANGED
事件。使用 StageStream
上的 isMuted
屬性來據此更新您的 UI:
stage.on(StageEvents.STAGE_STREAM_MUTE_CHANGED, (participant, stream) => { if (stream.streamType === 'video' && stream.isMuted) { // handle UI changes for video track getting muted } })
此外,您可以查看 StageParticipantInfo
stage.on(StageEvents.STAGE_STREAM_MUTE_CHANGED, (participant, stream) => { if (participant.videoStopped || participant.audioMuted) { // handle UI changes for either video or audio } })
取得 WebRTC 統計資料
若要取得發布串流或訂閱串流的最新 WebRTC 統計資料,請在 StageStream
上使用 getStats
。這是一種非同步方法,讓您可以透過等待或鏈結承諾來擷取統計資料。結果為含有所有標準統計資料的字典 RTCStatsReport
。
try { const stats = await stream.getStats(); } catch (error) { // Unable to retrieve stats }
最佳化媒體
建議您為 getUserMedia
和 getDisplayMedia
呼叫設下以下限制,以獲得最佳效能:
const CONSTRAINTS = { video: { width: { ideal: 1280 }, // Note: flip width and height values if portrait is desired height: { ideal: 720 }, framerate: { ideal: 30 }, }, };
您可以透過傳遞至 LocalStageStream
建構函數的其他選項進一步限制媒體:
const localStreamOptions = { minBitrate?: number; maxBitrate?: number; maxFramerate?: number; simulcast: { enabled: boolean } } const localStream = new LocalStageStream(track, localStreamOptions)
在上述程式碼中:
-
minBitrate
設定瀏覽器預期應使用的最小位元速率。然而,低複雜度影片串流可能會使編碼器低於此位元速率。 -
maxBitrate
設定瀏覽器預期應不超過此串流的最大位元速率。 -
maxFramerate
設定瀏覽器預期應不超過此串流的最大影格率。 -
simulcast
選項僅適用於 Chromium 瀏覽器。它可傳送串流的三個轉譯層。-
這讓伺服器能夠根據其網路限制,選擇要傳送給其他參與者的轉譯。
-
連同
maxBitrate
及/或maxFramerate
值一起指定simulcast
時,預期最高轉譯層會考慮設定這些值,前提條件是maxBitrate
不低於內部 SDK 第二高層 900 kbps 的預設maxBitrate
值。 -
如果相較於第二高層的預設值,指定的
maxBitrate
太低,則會停用simulcast
。 -
若未透過將
shouldPublishParticipant
傳回false
、呼叫refreshStrategy
、將shouldPublishParticipant
傳回true
,以及再次呼叫refreshStrategy
的組合動作來重新發布媒體,則無法開啟和關閉simulcast
。
-
取得參與者屬性
如果在 CreateParticipantToken
操作請求中指定屬性,您可以在 StageParticipantInfo
屬性中看到屬性:
stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => { console.log(`Participant ${participant.id} info:`, participant.attributes); })
補充增強資訊 (SEI)
補充增強資訊 (SEI) NAL 單元用於同時儲存與影格相符的中繼資料和視訊。發布和訂閱 H.264 影片串流時,可加以使用。SEI 承載不保證會送達訂閱用戶,特別是在網路狀況不佳時。
插入 SEI 承載
發布用戶端可以透過設定其視訊的 LocalStageStream 以啟用 inBandMessaging
與後續調用 insertSeiMessage
方法,藉此將 SEI 承載插入正在發布的階段串流。
承載必須是 ArrayBuffer
const config = { inBandMessaging: { enabled: true } }; const vidStream = new LocalStageStream(videoTrack, config); const payload = new TextEncoder().encode('hello world').buffer; vidStream.insertSeiMessage(payload);
重複 SEI 承載
選擇性地提供 repeatCount
,以針對接下來傳送的 N 個影格重複插入 SEI 承載。此舉有助於減輕因使用基礎 UDP 傳輸通訊協定來傳送視訊而可能發生的固有損失。請注意,此值必須介於 0 到 30 之間。接收用戶端必須具有邏輯才能取消複製此訊息。
vidStream.insertSeiMessage(payload, { repeatCount: 5 }); // Optional config, repeatCount must be between 0 and 30
讀取 SEI 承載
訂閱用戶端可透過設定訂閱用戶 SubscribeConfiguration
啟用inBandMessaging
和接聽 StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED
事件,從發布 H.264 視訊的發布者 (如果存在) 讀取 SEI 承載 (如下列範例所示):
const strategy = { subscribeConfiguration: (participant) => { return { inBandMessaging: { enabled: true } } } // ... other strategy functions } stage.on(StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED, (participant, seiMessage) => { console.log(seiMessage.payload, seiMessage.uuid); });
Simulcast 分層編碼
Simulcast 分層編碼是一種 IVS 即時串流功能,可讓發布者傳送多個不同品質的影片層,也可讓訂閱用戶動態或手動變更這些層。串流最佳化文件中詳細介紹了該功能。
設定分層編碼 (發布者)
若要以發布者身分啟用 Simulcast 分層編碼,請在執行個體化時將下列組態新增至 LocalStageStream
:
// Enable Simulcast let cameraStream = new LocalStageStream(cameraDevice, { simulcast: { enabled: true } })
根據相機裝置的輸入解析度,系統會依照串流最佳化的預設層、品質和影格率小節中的定義,來編碼和傳送一定數量的層。
設定分層編碼 (訂閱用戶)
訂閱用戶無需執行任何操作來啟用分層編碼。如果發布者正在傳送 Simulcast 層,則伺服器預設會在各層之間動態調整,根據訂閱用戶的裝置和網路狀況選擇品質最佳的層。
或者,若要挑選發布者正在傳送的明確層,有幾個選項可供選擇,如下所述。
選項 1:初始層品質偏好設定
使用 subscribeConfiguration
策略可以選擇想要以訂閱用戶身分接收的初始層:
const strategy = { subscribeConfiguration: (participant) => { return { simulcast: { initialLayerPreference: InitialLayerPreference.LOWEST_QUALITY } } } // ... other strategy functions }
依預設,訂閱用戶一律會先收到最低品質的層,而後緩慢地提升至最高品質的層。此舉可最佳化終端使用者頻寬消耗量,提供最佳的影片播放時間,減少較弱網路上使用者的初始影片凍結。
這些選項都適用於 InitialLayerPreference
:
LOWEST_QUALITY
:伺服器會先提供最低品質的影片層。此舉會最佳化頻寬消耗量以及媒體播放時間。品質定義為影片大小、位元速率和影格率的組合。例如,720p 影片的品質低於 1080p 影片的品質。HIGHEST_QUALITY
:伺服器會先提供最高品質的影片層。此舉會最佳化品質,也可能會增加媒體播放時間。品質定義為影片大小、位元速率和影格率的組合。例如,1080p 影片的品質高於 720p 影片的品質。
注意:若要讓初始圖層偏好設定生效,必須重新訂閱,因為這些更新不適用於作用中訂閱。
選項 2:偏好的串流層
串流開始後,即可使用 preferredLayerForStream
策略方法。此策略方法會公開參與者和串流資訊。
策略方法可以傳回下列項目:
直接依據
RemoteStageStream.getLayers
所傳回內容的層物件依據
StageStreamLayer.label
的層物件標籤字串undefined 或 null,這表示不應選取任何層,且偏好動態調整
例如,以下策略將一律讓使用者選取可用的最低品質影片層:
const strategy = { preferredLayerForStream: (participant, stream) => { return stream.getLowestQualityLayer(); } // ... other strategy functions }
若要重設層選擇並返回動態調整,則在策略中傳回 null 或 undefined。在此範例中,appState
是代表可能應用程式狀態的虛擬變數。
const strategy = { preferredLayerForStream: (participant, stream) => { if (appState.isAutoMode) { return null; } else { return appState.layerChoice } } // ... other strategy functions }
選項 3:RemoteStageStream 層協助程式
RemoteStageStream
有多個協助程式,可用來做出有關層選擇的決定,並向終端使用者顯示對應的選擇:
-
層事件:除了
StageEvents
之外,RemoteStageStream
物件自身還有可傳達層和 Simulcast 調整變更的事件:-
stream.on(RemoteStageStreamEvents.ADAPTION_CHANGED, (isAdapting) => {})
stream.on(RemoteStageStreamEvents.LAYERS_CHANGED, (layers) => {})
stream.on(RemoteStageStreamEvents.LAYER_SELECTED, (layer, reason) => {})
-
-
層方法:
RemoteStageStream
有多種協助程式方法,可用來取得有關串流和所呈現層的資訊。這些方法可在preferredLayerForStream
策略中提供的遠端串流,以及透過StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED
公開的遠端串流上使用。stream.getLayers
stream.getSelectedLayer
stream.getLowestQualityLayer
stream.getHighestQualityLayer
如需詳細資訊,請參閱 SDK 參考文件RemoteStageStream
類別。對於 LAYER_SELECTED
原因,如果傳回UNAVAILABLE
,則表示無法選取請求的圖層。將盡力選擇其位置,通常是較低品質的圖層以保持串流穩定性。
處理網路問題
當本機裝置的網路連線中斷時,SDK 會在內部嘗試重新連線,無需使用者採取任何動作。SDK 在部分情況下會執行失敗,這時就需要使用者採取動作。
階段的狀態大致上可以透過 STAGE_CONNECTION_STATE_CHANGED
事件來進行處理:
stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => { switch (state) { case StageConnectionState.DISCONNECTED: // handle disconnected UI break; case StageConnectionState.CONNECTING: // handle establishing connection UI break; case StageConnectionState.CONNECTED: // SDK is connected to the Stage break; case StageConnectionState.ERRORED: // SDK encountered an error and lost its connection to the stage. Wait for CONNECTED. break; })
一般而言,您可以忽略成功加入舞台後遇到的錯誤狀態,因為 SDK 會嘗試在內部復原。如果 SDK 報告 ERRORED
狀態,且舞台長時間 (例如 30 秒或更久) 保持在 CONNECTING
狀態,則表示網路的連線可能已中斷。
將階段廣播到 IVS 頻道
若要廣播階段,請建立一個獨立 IVSBroadcastClient
工作階段,然後按照使用 SDK 進行廣播的一般指示操作 (如上所述)。透過 STAGE_PARTICIPANT_STREAMS_ADDED
公開的 StageStream
清單可用於擷取可應用於廣播串流組合的參與者媒體串流,如下所示:
// Setup client with preferred settings const broadcastClient = getIvsBroadcastClient(); stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => { streams.forEach(stream => { const inputStream = new MediaStream([stream.mediaStreamTrack]); switch (stream.streamType) { case StreamType.VIDEO: broadcastClient.addVideoInputDevice(inputStream, `video-${participant.id}`, { index: DESIRED_LAYER, width: MAX_WIDTH, height: MAX_HEIGHT }); break; case StreamType.AUDIO: broadcastClient.addAudioInputDevice(inputStream, `audio-${participant.id}`); break; } }) })
或者,您可以複合階段並將其廣播到 IVS 低延遲通道,以吸引更多受眾。請參閱《IVS 低延遲串流使用者指南》中的在 Amazon IVS 串流上啟用多位主持人。