IVS iOS Broadcast SDK での発行とサブスクライブ | Real-Time Streaming
このドキュメントでは、IVS Real-Time Streaming iOS Broadcast SDK を使用してステージに発行とサブスクライブを行うためのステップについて説明します。
概念
リアルタイム機能には、ステージ、ストラテジー、レンダラーという 3 つのコアコンセプトがあります。設計目標は、実際に動作する製品を構築するのに必要となるクライアント側ロジックの量を最小限に抑えることです。
ステージ
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
の 3 つの関数を実装する必要があります。以下で、すべて説明します。
参加者へのサブスクライブ
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)
レンダラーから提供された情報がストラテジーの戻り値に影響することは想定されていません。たとえば、shouldSubscribeToParticipant
の戻り値は、participant:didChangePublishState
が呼び出されても変化しない想定です。ホストアプリケーションが特定の参加者をサブスクライブする場合は、その参加者の公開状態に関係なく、目的のサブスクリプションタイプを返す必要があります。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] }
参加者を表示、削除する
サブスクライブが完了すると、レンダラーの IVSStageStream
関数を介して didAddStreams
オブジェクトの配列を受け取ります。この参加者のオーディオレベル統計をプレビューまたは受信するには、ストリームから基になる 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
を呼び出した後に新しい IVSLocalStageStream
オブジェクトインスタンスが streamsToPublishForParticipant
によって返された場合、新しいストリームオブジェクトのミュート状態がステージに適用されます。新しい 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
を使用してください。収集が完了すると、IVSStageStreamDelegate
に設定できる IVSStageStream
から統計を受け取ります。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、Supplemental Enhancement Information) を取得する
補足拡張情報 (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()
に電話をかけます。
サイマルキャストによるレイヤードエンコーディング
サイマルキャストによるレイヤードエンコーディングは、パブリッシャーが複数の異なるビデオの品質レイヤーを送信し、サブスクライバーがそれらのレイヤーを動的または手動で変更できるようにする IVS リアルタイムのストリーミング機能です。この機能は、「ストリーミング最適化」ドキュメントで詳しく説明されています。
レイヤードエンコーディングの設定 (パブリッシャー)
パブリッシャーとしてサイマルキャストによるレイヤードエンコーディングを有効にするには、インスタンス化時に IVSLocalStageStream
に次の設定を追加します。
// Enable Simulcast let config = IVSLocalStageStreamVideoConfiguration() config.simulcast.enabled = true let cameraStream = IVSLocalStageStream(device: camera, configuration: config) // Other Stage implementation code
ビデオ設定で設定した解像度に応じて、「ストリーミングの最適化」の「デフォルトレイヤー、品質、フレームレート」セクションで定義されているように、設定された数のレイヤーがエンコードされて送信されます。
レイヤードエンコーディングの設定 (サブスクライバー)
サブスクライバーとして、レイヤードエンコーディングを有効にするために必要なものはありません。パブリッシャーがサイマルキャストレイヤーを送信している場合、デフォルトでサーバーによってレイヤー間で動的に適応され、サブスクライバーのデバイスおよびネットワークの状態に基づいて最適な品質が選択されます。
あるいは、パブリッシャーが送信している明示的なレイヤーを選択するには、以下に説明するいくつかのオプションがあります。
オプション 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
にはレイヤーおよびサイマルキャストの適応変更を伝えるイベントがあります。-
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 ストリームでの複数のホストの有効化」を参照してください。