使用 IVS iOS 廣播 SDK 發布和訂閱 | 即時串流 - Amazon IVS

使用 IVS iOS 廣播 SDK 發布和訂閱 | 即時串流

本文件將帶您了解開始使用 IVS 即時串流 iOS 廣播 SDK 發布和訂閱階段的相關步驟。

概念

以下是三個以即時功能為基礎的核心概念:階段策略轉譯器。設計目標是盡可能減少打造工作產品所需的用戶端邏輯數量。

階段

IVSStage 類別是主持人應用程式和 SDK 之間的主要交互點。該類別代表階段本身,用於加入和離開階段。建立或加入階段需有效且未過期的控制平面字符字串 (表示為 token)。加入和離開階段並不難。

let stage = try IVSStage(token: token, strategy: self) try stage.join() stage.leave()

IVSStage 類別也可連接 IVSStageRendererIVSErrorDelegate

let stage = try IVSStage(token: token, strategy: self) stage.errorDelegate = self stage.addRenderer(self) // multiple renderers can be added

策略

IVSStageStrategy 協定為主持人應用程式提供了將所需階段狀態傳送至 SDK 的管道。您必須實作以下三項函數:shouldSubscribeToParticipantshouldPublishParticipant、和 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)") } }

取得補充增強資訊 (SEI)

補充增強資訊 (SEI) NAL 單元用於同時儲存與影格相符的中繼資料和視訊。訂閱用戶端可以透過檢查來自發布者 IVSImageDeviceIVSImageDeviceFrame 物件上的 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 層,則伺服器預設會在各層之間動態調整,根據訂閱用戶的裝置和網路狀況選擇品質最佳的層。

或者,若要挑選發布者正在傳送的明確層,有幾個選項可供選擇,如下所述。

選項 1:初始層品質偏好設定

使用 subscribeConfiguration 策略可以選擇想要以訂閱用戶身分接收的初始層:

func stage(_ stage: IVSStage, subscribeConfigurationForParticipant participant: IVSParticipantInfo) -> IVSSubscribeConfiguration { let config = IVSSubscribeConfiguration() config.simulcast.initialLayerPreference = .lowestQuality return config }

依預設,訂閱用戶一律會先收到最低品質的層,而後緩慢地提升至最高品質的層。此舉可最佳化終端使用者頻寬消耗量,提供最佳的影片播放時間,減少較弱網路上使用者的初始影片凍結。

這些選項都適用於 InitialLayerPreference

  • lowestQuality:伺服器會先提供最低品質的影片層。此舉會最佳化頻寬消耗量以及媒體播放時間。品質定義為影片大小、位元速率和影格率的組合。例如,720p 影片的品質低於 1080p 影片的品質。

  • highestQuality:伺服器會先提供最高品質的影片層。此舉會最佳化品質,也可能會增加媒體播放時間。品質定義為影片大小、位元速率和影格率的組合。例如,1080p 影片的品質高於 720p 影片的品質。

選項 2:偏好的串流層

串流開始後,即可使用 preferredLayerForStream 策略方法。此策略方法會公開參與者和串流資訊。

策略方法可以傳回下列項目:

  • 直接依據 IVSRemoteStageStream.layers 所傳回內容的層物件。

  • nil,這表示不應選取任何層,且偏好動態調整。

例如,以下策略將一律讓使用者選取可用的最低品質影片層:

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, preferredLayerFor stream: IVSRemoteStageStream) -> IVSRemoteStageStreamLayer? { return stream.lowestQualityLayer }

若要重設層選擇並返回動態調整,則在策略中傳回 nil。在此範例中,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 類別。

將階段廣播到 IVS 頻道

若要廣播階段,請建立一個獨立 IVSBroadcastSession,然後按照使用 SDK 進行廣播的一般指示操作 (如上所述)。IVSStageStreamdevice 屬性會是 IVSImageDeviceIVSAudioDevice,如上述程式碼片段所示;這些屬性可以連線至 IVSBroadcastSession.mixer,以可自訂的版面配置廣播整個階段。

或者,您可以複合階段並將其廣播到 IVS 低延遲通道,以吸引更多受眾。請參閱《IVS 低延遲串流使用者指南》中的在 Amazon IVS 串流上啟用多位主持人