使用 IVS iOS 廣播 SDK 發布和訂閱 | 即時串流
本文件將帶您了解開始使用 IVS 即時串流 iOS 廣播 SDK 發布和訂閱階段的相關步驟。
概念
以下是三個以即時功能為基礎的核心概念:階段、策略和轉譯器。設計目標是盡可能減少打造工作產品所需的用戶端邏輯數量。
階段
IVSStage 類別是主持人應用程式和 SDK 之間的主要交互點。該類別代表階段本身,用於加入和離開階段。建立或加入階段需有效且未過期的控制平面字符字串 (表示為 token)。加入和離開階段並不難。
let stage = try IVSStage(token: token, strategy: self) try stage.join() stage.leave()
IVSStage 類別也可連接 IVSStageRenderer 和 IVSErrorDelegate:
let stage = try IVSStage(token: token, strategy: self) stage.errorDelegate = self stage.addRenderer(self) // multiple renderers can be added
策略
IVSStageStrategy 協定為主持人應用程式提供了將所需階段狀態傳送至 SDK 的管道。您必須實作以下三項函數:shouldSubscribeToParticipant、shouldPublishParticipant、和 streamsToPublishForParticipant。以下將討論所有內容。
訂閱參與者
func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType
遠端參與者加入階段時,SDK 會向主持人應用程式查詢該參與者所需的訂閱狀態。選項包括 .none、.audioOnly 和 .audioVideo。傳回此函數的值時,主持人應用程式不需要擔心發布狀態、目前的訂閱狀態或階段連線狀態。若傳回 .audioVideo,SDK 會等到遠端參與者發布時才會訂閱,然後在整個程序中透過轉譯器更新主機應用程式。
以下是實作範例:
func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType { return .audioVideo }
對於一律希望所有參與者互相看到彼此的主持人應用程式 (例如影片聊天應用程式),這是此函數的完整實作程序。
您也可以採用更進階的實作方式。在 IVSParticipantInfo 上使用 attributes 屬性,以根據伺服器提供的屬性選擇性訂閱參與者:
func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType { switch participant.attributes["role"] { case "moderator": return .none case "guest": return .audioVideo default: return .none } }
這可以用來建立一個階段,版主可以在不會被看到或聽到自己聲音的情況下監控所有訪客。主持人應用程式可以使用其他商業邏輯,讓版主看到彼此,但仍維持訪客看不到他們的狀態。
參與者訂閱組態
func stage(_ stage: IVSStage, subscribeConfigurationForParticipant participant: IVSParticipantInfo) -> IVSSubscribeConfiguration
如果正在訂閱遠端參與者 (請參閱訂閱參與者),則 SDK 會查詢主機應用程式關於該參與者的自訂訂閱組態。此組態為選用功能,允許主機應用程式控制某些層面的訂閱用戶行為。如需有關可設定內容的詳細資訊,請參閱 SDK 參考文件中的 SubscribeConfiguration
以下是實作範例:
func stage(_ stage: IVSStage, subscribeConfigurationForParticipant participant: IVSParticipantInfo) -> IVSSubscribeConfiguration { let config = IVSSubscribeConfiguration() try! config.jitterBuffer.setMinDelay(.medium()) return config }
此實作會將所有訂閱參與者的抖動緩衝區最低延遲更新為預設的 MEDIUM。
您也可以透過 shouldSubscribeToParticipant 採用更進階的實作方式。指定的 ParticipantInfo 可用來專門更新特定參與者的訂閱組態。
我們建議您使用預設行為。只有在您想要變更特定行為時,才指定自訂組態。
發布
func stage(_ stage: IVSStage, shouldPublishParticipant participant: IVSParticipantInfo) -> Bool
連線到階段後,SDK 會查詢主持人應用程式,看看特定參與者是否應發布。系統僅會根據提供的字符為具有發布許可的本機參與者調用此函數。
以下是實作範例:
func stage(_ stage: IVSStage, shouldPublishParticipant participant: IVSParticipantInfo) -> Bool { return true }
這是針對一個使用者總是想發布內容的標準影片聊天應用程式。他們可以靜音和取消靜音其音訊和影片內容,以立即隱藏起來,或看到/聽見內容。(他們也可以使用發布/取消發布,但這種方式速度較慢。建議在需經常變更可見性的使用案例中使用靜音/取消靜音。)
選擇要發布的串流
func stage(_ stage: IVSStage, streamsToPublishForParticipant participant: IVSParticipantInfo) -> [IVSLocalStageStream]
發布時,這會用來決定應發布哪些音訊和影片串流。稍後會在發布媒體串流中進行詳細說明。
更新策略
策略應處於動態狀態:從上述任何函數返回的值可以隨時進行修改。例如,若主機應用程式在終端使用者按下按鈕前都不想發布,您可以從 shouldPublishParticipant 傳回一個變數 (例如 hasUserTappedPublishButton)。當該變數根據終端使用者的互動而變更時,請呼叫 stage.refreshStrategy() 向 SDK 傳送訊號,表示它應查詢策略中的最新值,並僅套用已變更的項目。若 SDK 發現 shouldPublishParticipant 值已變更,它便會開始發布程序。若 SDK 查詢後所有函數傳回與之前相同的值,則 refreshStrategy 呼叫將不會對階段進行任何修改。
若 shouldSubscribeToParticipant 傳回的值從 .audioVideo 變更為 .audioOnly,則系統將會針對傳回值已變更的所有參與者移除影片串流 (若之前存有影片串流)。
一般而言,階段會採用策略,以最有效率的方式套用先前與目前策略之間的差異,主持人應用程式不必擔心正確進行管理所需的所有狀態。因此,請將呼叫 stage.refreshStrategy() 視為低成本的操作,因為除非策略發生變化,否則它什麼都不會執行。
轉譯器
IVSStageRenderer 協定會將階段狀態傳送給主持人應用程式。主機應用程式的 UI 更新通常可以完全由轉譯器提供的事件提供支援。轉譯器會提供以下函數:
func stage(_ stage: IVSStage, participantDidJoin participant: IVSParticipantInfo) func stage(_ stage: IVSStage, participantDidLeave participant: IVSParticipantInfo) func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChange publishState: IVSParticipantPublishState) func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChange subscribeState: IVSParticipantSubscribeState) func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didAdd streams: [IVSStageStream]) func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didRemove streams: [IVSStageStream]) func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChangeMutedStreams streams: [IVSStageStream]) func stage(_ stage: IVSStage, didChange connectionState: IVSStageConnectionState, withError error: Error?) func stage(_ stage: IVSStage, participant: IVSParticipantInfo, stream: IVSRemoteStageStream, didChangeStreamAdaption adaption: Bool) func stage(_ stage: IVSStage, participant: IVSParticipantInfo, stream: IVSRemoteStageStream, didChange layers: [IVSRemoteStageStreamLayer]) func stage(_ stage: IVSStage, participant: IVSParticipantInfo, stream: IVSRemoteStageStream, didSelect layer: IVSRemoteStageStreamLayer?, reason: IVSRemoteStageStream.LayerSelectedReason)
轉譯器提供的資訊應該不會對策略的傳回值造成影響。例如,呼叫 participant:didChangePublishState 時,shouldSubscribeToParticipant 的傳回值應該不會變更。若主持人應用程式想要訂閱特定參與者,則無論該參與者的發布狀態為何,它都應傳回所需的訂閱類型。SDK 負責確保根據階段狀態,在正確的時間點執行策略的所需狀態。
請注意,當發布參與者觸發 participantDidJoin,且參與者停止發布或離開階段工作階段時,participantDidLeave 才會觸發。
發布媒體串流
您可以透過 IVSDeviceDiscovery 找到本地裝置 (例如內建的麥克風和攝影機)。以下是選擇前置攝影機和預設麥克風,然後將其以 IVSLocalStageStreams 返回並由 SDK 發布的範例:
let devices = IVSDeviceDiscovery().listLocalDevices() // Find the camera virtual device, choose the front source, and create a stream let camera = devices.compactMap({ $0 as? IVSCamera }).first! let frontSource = camera.listAvailableInputSources().first(where: { $0.position == .front })! camera.setPreferredInputSource(frontSource) let cameraStream = IVSLocalStageStream(device: camera) // Find the microphone virtual device and create a stream let microphone = devices.compactMap({ $0 as? IVSMicrophone }).first! let microphoneStream = IVSLocalStageStream(device: microphone) // Configure the audio manager to use the videoChat preset, which is optimized for bi-directional communication, including echo cancellation. IVSStageAudioManager.sharedInstance().setPreset(.videoChat) // This is a function on IVSStageStrategy func stage(_ stage: IVSStage, streamsToPublishForParticipant participant: IVSParticipantInfo) -> [IVSLocalStageStream] { return [cameraStream, microphoneStream] }
顯示和移除參與者
訂閱完成後,您會透過轉譯器的 didAddStreams 函數收到 IVSStageStream 物件陣列。若要預覽或接收有關此參與者的音訊層級統計資料,您可以從串流中存取基礎 IVSDevice 物件:
if let imageDevice = stream.device as? IVSImageDevice { let preview = imageDevice.previewView() /* attach this UIView subclass to your view */ } else if let audioDevice = stream.device as? IVSAudioDevice { audioDevice.setStatsCallback( { stats in /* process stats.peak and stats.rms */ }) }
當參與者停止發布或取消訂閱時,系統會呼叫 didRemoveStreams 函數,並傳回遭移除的串流。主持人應用程式應將此視為從檢視階層中移除參與者影片串流的信號。
didRemoveStreams 會在串流可能遭移除的所有情況下調用,其中包括:
-
遠端參與者停止發布。
-
本機裝置取消訂閱,或將訂閱從
.audioVideo變更為.audioOnly。 -
遠端參與者離開階段。
-
本機參與者離開階段。
由於 didRemoveStreams 會在所有情況下調用,因此在遠端或本機離開操作期間,不需要使用自訂商業邏輯從 UI 移除參與者。
靜音和取消靜音媒體串流
IVSLocalStageStream 物件具備控制是否將串流靜音的 setMuted 函數。此函數可以在從 streamsToPublishForParticipant 策略函數傳回之前或之後在串流上呼叫。
重要:如果呼叫 refreshStrategy 後由 streamsToPublishForParticipant 傳回新的 IVSLocalStageStream 物件執行個體,則新串流物件的靜音狀態會套用至階段。建立新 IVSLocalStageStream 執行個體時請務必小心,以確保維持預期的靜音狀態。
監控遠端參與者媒體靜音狀態
當參與者變更其影片或音訊串流的靜音狀態時,會以已變更的串流陣列調用轉譯器 didChangeMutedStreams 函數。使用 IVSStageStream 上的 isMuted 屬性來據此更新您的 UI:
func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChangeMutedStreams streams: [IVSStageStream]) { streams.forEach { stream in /* stream.isMuted */ } }
建立階段組態
若要自訂階段影片組態的值,請使用 IVSLocalStageStreamVideoConfiguration:
let config = IVSLocalStageStreamVideoConfiguration() try config.setMaxBitrate(900_000) try config.setMinBitrate(100_000) try config.setTargetFramerate(30) try config.setSize(CGSize(width: 360, height: 640)) config.degradationPreference = .balanced
取得 WebRTC 統計資料
若要取得發布串流或訂閱串流的最新 WebRTC 統計資料,請在 IVSStageStream 上使用 requestRTCStats。收集完成後,您將透過可以在上 IVSStageStream 設定的 IVSStageStreamDelegate 收到統計資料。若要持續收集 WebRTC 統計資料,請透過 Timer 呼叫此函數。
func stream(_ stream: IVSStageStream, didGenerateRTCStats stats: [String : [String : String]]) { for stat in stats { for member in stat.value { print("stat \(stat.key) has member \(member.key) with value \(member.value)") } } }
取得參與者屬性
如果在 CreateParticipantToken 操作請求中指定屬性,您可以在 IVSParticipantInfo 屬性中看到屬性:
func stage(_ stage: IVSStage, participantDidJoin participant: IVSParticipantInfo) { print("ID: \(participant.participantId)") for attribute in participant.attributes { print("attribute: \(attribute.key)=\(attribute.value)") } }
嵌入訊息
IVSImageDevice 上的 embedMessage 方法可讓您在發布期間將中繼資料承載直接插入影片影格。這可為即時應用程式啟用影格同步訊息。僅在使用 SDK 進行即時發布 (非低延遲發布) 時,才能使用訊息嵌入。
嵌入訊息不保證會送達訂閱者,因為其直接嵌入在影片影格中並透過 UDP 傳輸,這樣並不能保證封包交付。傳輸期間封包遺失可能會導致訊息遺失,尤其是在網路狀況不佳的情況下。為了緩解這種情況,embedMessage 方法包含一個 repeatCount 參數,可在多個連續影格間複製訊息,從而提高交付可靠性。此功能僅適用於影片串流。
使用 embedMessage
發布用戶端可以使用 IVSImageDevice 上的 embedMessage 方法,將訊息承載嵌入至其影片串流。承載大小必須大於 0KB,小於 1KB。每秒插入的嵌入訊息數量不得超過每秒 10KB。
let imageDevice: IVSImageDevice = imageStream.device as! IVSImageDevice let messageData = Data("hello world".utf8) do { try imageDevice.embedMessage(messageData, withRepeatCount: 0) } catch { print("Failed to embed message: \(error)") }
重複訊息承載
使用 repeatCount 在多個影格之間複製訊息,以提高可靠性。此值必須介於 0 到 30。接收用戶端必須具有邏輯才能取消複製此訊息。
try imageDevice.embedMessage(messageData, withRepeatCount: 5) // repeatCount: 0-30, receiving clients should handle duplicates
讀取嵌入訊息
如需如何從傳入串流讀取嵌入訊息,請參閱下方的「取得補充增強資訊 (SEI)」。
取得補充增強資訊 (SEI)
補充增強資訊 (SEI) NAL 單元用於同時儲存與影格相符的中繼資料和視訊。訂閱用戶端可以透過檢查來自發布者 IVSImageDevice 的 IVSImageDeviceFrame 物件上的 embeddedMessages 屬性,讀取發布 H.264 影片之發布者的 SEI 承載。若要這樣做,請取得發布者的 IVSImageDevice,然後透過提供給 setOnFrameCallback 的回呼觀察每個影格,如下列範例所示:
// in an IVSStageRenderer’s stage:participant:didAddStreams: function, after acquiring the new IVSImageStream let imageDevice: IVSImageDevice? = imageStream.device as? IVSImageDevice imageDevice?.setOnFrameCallback { frame in for message in frame.embeddedMessages { if let seiMessage = message as? IVSUserDataUnregisteredSEIMessage { let seiMessageData = seiMessage.data let seiMessageUUID = seiMessage.UUID // interpret the message's data based on the UUID } } }
在背景繼續工作階段
當應用程式進入背景時,您可以在聽到遠端音訊的同時繼續待在階段,不過無法繼續傳送自己的影像和音訊。您必須更新您的 IVSStrategy 實作以停止發布,並訂閱 .audioOnly (或 .none,如適用)。
func stage(_ stage: IVSStage, shouldPublishParticipant participant: IVSParticipantInfo) -> Bool { return false } func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType { return .audioOnly }
然後呼叫 stage.refreshStrategy()。
Simulcast 分層編碼
Simulcast 分層編碼是一種 IVS 即時串流功能,可讓發布者傳送多個不同品質的影片層,也可讓訂閱用戶動態或手動設定這些層。串流最佳化文件中詳細介紹了該功能。
設定分層編碼 (發布者)
若要以發布者身分啟用 Simulcast 分層編碼,請在執行個體化時將下列組態新增至 IVSLocalStageStream:
// Enable Simulcast let config = IVSLocalStageStreamVideoConfiguration() config.simulcast.enabled = true let cameraStream = IVSLocalStageStream(device: camera, configuration: config) // Other Stage implementation code
根據在影片組態上設定的解析度,系統會依照串流最佳化的預設層、品質和影格率小節中的定義,來編碼和傳送一定數量的層。
此外,您也可選擇從 Simulcast 組態內設定個別層:
// Enable Simulcast let config = IVSLocalStageStreamVideoConfiguration() config.simulcast.enabled = true let layers = [ IVSStagePresets.simulcastLocalLayer().default720(), IVSStagePresets.simulcastLocalLayer().default180() ] try config.simulcast.setLayers(layers) let cameraStream = IVSLocalStageStream(device: camera, configuration: config) // Other Stage implementation code
或者,您可以建立自訂層組態,最多三層。如果您提供空陣列或未提供任何值,則會使用上述預設值。透過下列必要屬性設定來描述層:
-
setSize: CGSize; -
setMaxBitrate: integer; -
setMinBitrate: integer; -
setTargetFramerate: float;
從預設集開始,可以覆寫個別屬性或建立全新的組態:
// Enable Simulcast let config = IVSLocalStageStreamVideoConfiguration() config.simulcast.enabled = true let customHiLayer = IVSStagePresets.simulcastLocalLayer().default720() try customHiLayer.setTargetFramerate(15) let layers = [ customHiLayer, IVSStagePresets.simulcastLocalLayer().default180() ] try config.simulcast.setLayers(layers) let cameraStream = IVSLocalStageStream(device: camera, configuration: config) // Other Stage implementation code
如需設定個別層時可觸發的最大值、限制和錯誤,請參閱 SDK 參考文件。
設定分層編碼 (訂閱用戶)
訂閱用戶無需執行任何操作來啟用分層編碼。如果發布者正在傳送 Simulcast 層,則伺服器預設會在各層之間動態調整,根據訂閱用戶的裝置和網路狀況選擇品質最佳的層。
或者,若要挑選發布者正在傳送的明確層,有幾個選項可供選擇,如下所述。
選項 1:初始層品質偏好設定
使用 subscribeConfigurationForParticipant 策略可以選擇想要以訂閱用戶身分接收的初始層:
func stage(_ stage: IVSStage, subscribeConfigurationForParticipant participant: IVSParticipantInfo) -> IVSSubscribeConfiguration { let config = IVSSubscribeConfiguration() config.simulcast.initialLayerPreference = .lowestQuality return config }
依預設,訂閱用戶一律會先收到最低品質的層,而後緩慢地提升至最高品質的層。此舉可最佳化終端使用者頻寬消耗量,提供最佳的影片播放時間,減少較弱網路上使用者的初始影片凍結。
這些選項都適用於 InitialLayerPreference:
-
lowestQuality:伺服器會先提供最低品質的影片層。此舉會最佳化頻寬消耗量以及媒體播放時間。品質定義為影片大小、位元速率和影格率的組合。例如,720p 影片的品質低於 1080p 影片的品質。 -
highestQuality:伺服器會先提供最高品質的影片層。此舉會最佳化品質,也可能會增加媒體播放時間。品質定義為影片大小、位元速率和影格率的組合。例如,1080p 影片的品質高於 720p 影片的品質。
注意:若要讓初始圖層偏好設定 (initialLayerPreference 呼叫) 生效,必須重新訂閱,因為這些更新不適用於作用中訂閱。
選項 2:偏好的串流層
preferredLayerForStream 策略方法可讓您在串流開始後選取圖層。此策略方法會接收參與者和串流資訊,因此您可以依參與者逐一選取圖層。此 SDK 會呼叫此方法以回應特定事件,例如串流層變更時、參與者狀態變更時或主機應用程式重新整理策略時。
此策略方法會傳回 IVSRemoteStageStreamLayer 物件,可能是下列其中一項:
-
圖層物件,例如
IVSRemoteStageStream.layers傳回的圖層物件。 -
null,這表示不應選取任何層,且偏好動態調整。
例如,以下策略將一律讓使用者選取可用的最低品質影片層:
func stage(_ stage: IVSStage, participant: IVSParticipantInfo, preferredLayerFor stream: IVSRemoteStageStream) -> IVSRemoteStageStreamLayer? { return stream.lowestQualityLayer }
若要重設層選擇並返回動態調整,則在策略中傳回 null 或 undefined。在此範例中,appState 是代表主機應用程式狀態的預留位置變數。
func stage(_ stage: IVSStage, participant: IVSParticipantInfo, preferredLayerFor stream: IVSRemoteStageStream) -> IVSRemoteStageStreamLayer? { If appState.isAutoMode { return nil } else { return appState.layerChoice } }
選項 3:RemoteStageStream 層協助程式
IVSRemoteStageStream 有多個協助程式,可用來做出有關層選擇的決定,並向終端使用者顯示對應的選擇:
-
層事件:除了
IVSStageRenderer之外,IVSRemoteStageStreamDelegate還有可傳達層和 Simulcast 調整變更的事件:-
func stream(_ stream: IVSRemoteStageStream, didChangeAdaption adaption: Bool) -
func stream(_ stream: IVSRemoteStageStream, didChange layers: [IVSRemoteStageStreamLayer]) -
func stream(_ stream: IVSRemoteStageStream, didSelect layer: IVSRemoteStageStreamLayer?, reason: IVSRemoteStageStream.LayerSelectedReason)
-
-
層方法:
IVSRemoteStageStream有多種協助程式方法,可用來取得有關串流和所呈現層的資訊。這些方法可在preferredLayerForStream策略中提供的遠端串流,以及透過func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didAdd streams: [IVSStageStream])公開的遠端串流上使用。-
stream.layers -
stream.selectedLayer -
stream.lowestQualityLayer -
stream.highestQualityLayer -
stream.layers(with: IVSRemoteStageStreamLayerConstraints)
-
如需詳細資訊,請參閱 SDK 參考文件IVSRemoteStageStream 類別。對於 LayerSelected 原因,如果傳回UNAVAILABLE,則表示無法選取請求的圖層。將盡力選擇其位置,通常是較低品質的圖層以保持串流穩定性。
將階段廣播到 IVS 頻道
若要廣播階段,請建立一個獨立 IVSBroadcastSession,然後按照使用 SDK 進行廣播的一般指示操作 (如上所述)。IVSStageStream 的 device 屬性會是 IVSImageDevice 或 IVSAudioDevice,如上述程式碼片段所示;這些屬性可以連線至 IVSBroadcastSession.mixer,以可自訂的版面配置廣播整個階段。
或者,您可以複合階段並將其廣播到 IVS 低延遲通道,以吸引更多受眾。請參閱《IVS 低延遲串流使用者指南》中的在 Amazon IVS 串流上啟用多位主持人。