發布與訂閱 - Amazon IVS

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

發布與訂閱

概念

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

階段

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

Stage stage = new Stage(context, token, strategy); try { stage.join(); } catch (BroadcastException exception) { // handle join exception } stage.leave();

Stage 類別也可連接 StageRenderer

stage.addRenderer(renderer); // multiple renderers can be added

策略

Stage.Strategy 介面為主持人應用程式提供了將所需階段狀態傳送至 SDK 的管道。您必須實作以下三項函數:shouldSubscribeToParticipantshouldPublishFromParticipant、和 stageStreamsToPublishForParticipant。以下將討論所有內容。

訂閱參與者

Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);

遠端參與者加入階段時,SDK 會向主持人應用程式查詢該參與者所需的訂閱狀態。選項包括 NONEAUDIO_ONLYAUDIO_VIDEO。傳回此函數的值時,主持人應用程式不需要擔心發布狀態、目前的訂閱狀態或階段連線狀態。若傳回 AUDIO_VIDEO,SDK 會等到遠端參與者發布時才會訂閱,然後在整個程序中透過轉譯器更新主持人應用程式。

以下是實作範例:

@Override Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { return Stage.SubscribeType.AUDIO_VIDEO; }

對於一律希望所有參與者互相看到彼此的主持人應用程式 (例如影片聊天應用程式),這是此函數的完整實作程序。

您也可以採用更進階的實作方式。在 ParticipantInfo 上使用 userInfo 屬性,以根據伺服器提供的屬性選擇性訂閱參與者:

@Override Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { switch(participantInfo.userInfo.get(“role”)) { case “moderator”: return Stage.SubscribeType.NONE; case “guest”: return Stage.SubscribeType.AUDIO_VIDEO; default: return Stage.SubscribeType.NONE; } }

這可以用來建立一個階段,版主可以在不會被看到或聽到自己聲音的情況下監控所有訪客。主持人應用程式可以使用其他商業邏輯,讓版主看到彼此,但仍維持訪客看不到他們的狀態。

發布

boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);

連線到階段後,SDK 會查詢主持人應用程式,看看特定參與者是否應發布。系統僅會根據提供的字符為具有發布許可的本機參與者調用此函數。

以下是實作範例:

@Override boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { return true; }

這是針對一個使用者總是想發布內容的標準影片聊天應用程式。他們可以靜音和取消靜音其音訊和影片內容,以立即隱藏起來,或看到/聽見內容。(他們也可以使用發布/取消發布,但這種方式速度較慢。建議在需經常變更可見性的使用案例中使用靜音/取消靜音。)

選擇要發布的串流

@Override List<LocalStageStream> stageStreamsToPublishForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo); }

發布時,這會用來決定應發布哪些音訊和影片串流。稍後會在發布媒體串流中進行詳細說明。

更新策略

策略應處於動態狀態:從上述任何函數返回的值可以隨時進行修改。例如,若主持人應用程式在終端使用者按下按鈕前都不想發布,您可以從 shouldPublishFromParticipant 傳回一個變數 (例如 hasUserTappedPublishButton)。當該變數根據終端使用者的互動而變更時,請呼叫 stage.refreshStrategy() 向 SDK 傳送訊號,表示它應查詢策略中的最新值,並僅套用已變更的項目。若 SDK 發現 shouldPublishFromParticipant 值已變更,它便會開始發布程序。若 SDK 查詢後所有函數傳回與之前相同的值,則 refreshStrategy 呼叫將不會對階段進行任何修改。

shouldSubscribeToParticipant 傳回的值從 AUDIO_VIDEO 變更為 AUDIO_ONLY,則系統將會針對傳回值已變更的所有參與者移除影片串流 (若之前存有影片串流)。

一般而言,階段會採用策略,以最有效率的方式套用先前與目前策略之間的差異,主持人應用程式不必擔心正確進行管理所需的所有狀態。因此,請將呼叫 stage.refreshStrategy() 視為低成本的操作,因為除非策略發生變化,否則它什麼都不會執行。

轉譯器

StageRenderer 介面會將階段狀態傳送給主持人應用程式。主持人應用程式的 UI 更新通常可以完全由轉譯器提供的事件提供支援。轉譯器會提供以下函數:

void onParticipantJoined(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo); void onParticipantLeft(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo); void onParticipantPublishStateChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull Stage.PublishState publishState); void onParticipantSubscribeStateChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull Stage.SubscribeState subscribeState); void onStreamsAdded(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams); void onStreamsRemoved(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams); void onStreamsMutedChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams); void onError(@NonNull BroadcastException exception); void onConnectionStateChanged(@NonNull Stage stage, @NonNull Stage.ConnectionState state, @Nullable BroadcastException exception);

針對大多數這些方法提供了對應的 StageParticipantInfo

轉譯器提供的資訊應該不會對策略的傳回值造成影響。例如,呼叫 onParticipantPublishStateChanged 時,shouldSubscribeToParticipant 的傳回值應該不會變更。若主持人應用程式想要訂閱特定參與者,則無論該參與者的發布狀態為何,它都應傳回所需的訂閱類型。SDK 負責確保根據階段狀態,在正確的時間點執行策略的所需狀態。

您可以將 StageRenderer 連接至階段類別:

stage.addRenderer(renderer); // multiple renderers can be added

請注意,當發布參與者觸發 onParticipantJoined,且參與者停止發布或離開階段工作階段時,onParticipantLeft 才會觸發。

發布媒體串流

您可以透過 DeviceDiscovery 找到本地裝置 (例如內建的麥克風和攝影機)。以下是選擇前置攝影機和預設麥克風,然後將其以 LocalStageStreams 傳回並由 SDK 發布的範例:

DeviceDiscovery deviceDiscovery = new DeviceDiscovery(context); List<Device> devices = deviceDiscovery.listLocalDevices(); List<LocalStageStream> publishStreams = new ArrayList<LocalStageStream>(); Device frontCamera = null; Device microphone = null; // Create streams using the front camera, first microphone for (Device device : devices) { Device.Descriptor descriptor = device.getDescriptor(); if (!frontCamera && descriptor.type == Device.Descriptor.DeviceType.Camera && descriptor.position = Device.Descriptor.Position.FRONT) { front Camera = device; } if (!microphone && descriptor.type == Device.Descriptor.DeviceType.Microphone) { microphone = device; } } ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera); AudioLocalStageStream microphoneStream = new AudioLocalStageStream(microphoneDevice); publishStreams.add(cameraStream); publishStreams.add(microphoneStream); // Provide the streams in Stage.Strategy @Override @NonNull List<LocalStageStream> stageStreamsToPublishForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { return publishStreams; }

顯示和移除參與者

訂閱完成後,您會透過轉譯器的 onStreamsAdded 函數收到 StageStream 物件陣列。您可以透過 ImageStageStream 擷取預覽畫面:

ImagePreviewView preview = ((ImageStageStream)stream).getPreview(); // Add the view to your view hierarchy LinearLayout previewHolder = findViewById(R.id.previewHolder); preview.setLayoutParams(new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)); previewHolder.addView(preview);

您可以透過 AudioStageStream 擷取音訊層級的統計資料:

((AudioStageStream)stream).setStatsCallback((peak, rms) -> { // handle statistics });

當參與者停止發布或取消訂閱時,系統會呼叫 onStreamsRemoved 函數,並傳回遭移除的串流。主持人應用程式應將此視為從檢視階層中移除參與者影片串流的信號。

onStreamsRemoved 會在串流可能遭移除的所有情況下調用,其中包括:

  • 遠端參與者停止發布。

  • 本機裝置取消訂閱,或將訂閱從 AUDIO_VIDEO 變更為 AUDIO_ONLY

  • 遠端參與者離開階段。

  • 本機參與者離開階段。

由於 onStreamsRemoved 會在所有情況下調用,因此在遠端或本機離開操作期間,不需要使用自訂商業邏輯從 UI 移除參與者。

靜音和取消靜音媒體串流

LocalStageStream 物件具備控制是否將串流靜音的 setMuted 函數。此函數可以在從 streamsToPublishForParticipant 策略函數傳回之前或之後在串流上呼叫。

重要:如果呼叫 refreshStrategy 後由 streamsToPublishForParticipant 傳回新的 LocalStageStream 物件執行個體,則新串流物件的靜音狀態會套用至階段。建立新 LocalStageStream 執行個體時請務必小心,以確保維持預期的靜音狀態。

監控遠端參與者媒體靜音狀態

當參與者變更其影片或音訊串流的靜音狀態時,會以已變更的串流清單調用轉譯器 onStreamMutedChanged 函數。使用 StageStream 上的 getMuted 方法來據此更新您的 UI。

@Override void onStreamsMutedChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams) { for (StageStream stream : streams) { boolean muted = stream.getMuted(); // handle UI changes } }

取得 WebRTC 統計資料

若要取得發布串流或訂閱串流的最新 WebRTC 統計資料,請在 StageStream 上使用 requestRTCStats。收集完成後,您將透過可以在上 StageStream 設定的 StageStream.Listener 收到統計資料。

stream.requestRTCStats(); @Override void onRTCStats(Map<String, Map<String, String>> statsMap) { for (Map.Entry<String, Map<String, string>> stat : statsMap.entrySet()) { for(Map.Entry<String, String> member : stat.getValue().entrySet()) { Log.i(TAG, stat.getKey() + “ has member “ + member.getKey() + “ with value “ + member.getValue()); } } }

取得參與者屬性

如果您在 CreateParticipantToken 端點要求中指定屬性,您可以在 ParticipantInfo 屬性中看到屬性:

@Override void onParticipantJoined(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { for (Map.Entry<String, String> entry : participantInfo.userInfo.entrySet()) { Log.i(TAG, “attribute: “ + entry.getKey() + “ = “ + entry.getValue()); } }

在背景繼續工作階段

當應用程式進入後台時,建議您停止發布或僅訂閱其他遠程參與者的音訊。若要完成此操作,請更新您的 Strategy 實作以停止發布,並訂閱 AUDIO_ONLY (或 NONE,如適用)。

// Local variables before going into the background boolean shouldPublish = true; Stage.SubscribeType subscribeType = Stage.SubscribeType.AUDIO_VIDEO; // Stage.Strategy implementation @Override boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { return shouldPublish; } @Override Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) { return subscribeType; } // In our Activity, modify desired publish/subscribe when we go to background, then call refreshStrategy to update the stage @Override void onStop() { super.onStop(); shouldPublish = false; subscribeTpye = Stage.SubscribeType.AUDIO_ONLY; stage.refreshStrategy(); }

啟用/停用使用 Simulcast 進行分層編碼

發布媒體串流時,SDK 會傳輸高品質和低品質的影片串流,因此即使下行頻寬有限,遠端參與者也可訂閱串流。預設會啟用使用 Simulcast 進行分層編碼。您可以使用 StageVideoConfiguration.Simulcast 類別將其停用:

// Disable Simulcast StageVideoConfiguration config = new StageVideoConfiguration(); config.simulcast.setEnabled(false); ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config); // Other Stage implementation code

影片組態限制

SDK 不支援使用 StageVideoConfiguration.setSize(BroadcastConfiguration.Vec2 size) 強制執行縱向模式或橫向模式。在縱向模式中,較小的空間為寬度;在橫向模式中,較小的空間則為高度。這表示以下兩次 setSize 呼叫會對影片組態產生一樣的效果:

StageVideo Configuration config = new StageVideo Configuration(); config.setSize(BroadcastConfiguration.Vec2(720f, 1280f); config.setSize(BroadcastConfiguration.Vec2(1280f, 720f);

處理網路問題

當本機裝置的網路連線中斷時,SDK 會在內部嘗試重新連線,無需使用者採取任何動作。SDK 在部分情況下會執行失敗,這時就需要使用者採取動作。以下是兩個有關網路連線中斷的主要錯誤:

  • 錯誤代碼 1400,訊息:「PeerConnection 因為未知的網路錯誤而遺失」

  • 錯誤代碼 1300,訊息:「已用盡重試嘗試次數」

若收到第一種錯誤,但未收到第二種錯誤,SDK 仍會連線至階段,並嘗試自動重新建立連線。保險起見,您可以在不對策略方法的傳回值進行任何更改的情況下呼叫 refreshStrategy,以觸發手動重新連線嘗試。

若收到第二種錯誤,則表示 SDK 的重新連線嘗試失敗,且本機裝置已中斷與階段的連線。在此情況下,請嘗試在重新建立網路連線後,呼叫 join 來重新加入階段。

一般而言,若成功加入階段後遇到錯誤,則表示 SDK 並沒有成功重新建立連線。建立新的 Stage 物件,並在網路情況改善時嘗試加入。

使用藍牙麥克風

若要使用藍牙麥克風裝置發布,您必須啟動藍牙 SCO 連線:

Bluetooth.startBluetoothSco(context); // Now bluetooth microphones can be used … // Must also stop bluetooth SCO Bluetooth.stopBluetoothSco(context);