

# IVS iOS Broadcast SDK での配信とサブスクライブ \| Real-Time Streaming
<a name="ios-publish-subscribe"></a>

このドキュメントでは、IVS Real-Time Streaming iOS Broadcast SDK を使用してステージに配信とサブスクライブを行うためのステップについて説明します。

## 概念
<a name="ios-publish-subscribe-concepts"></a>

リアルタイム機能には、[ステージ](#ios-publish-subscribe-concepts-stage)、[ストラテジー](#ios-publish-subscribe-concepts-strategy)、[レンダラー](#ios-publish-subscribe-concepts-renderer)という 3 つのコアコンセプトがあります。設計目標は、実際に動作する製品を構築するのに必要となるクライアント側ロジックの量を最小限に抑えることです。

### ステージ
<a name="ios-publish-subscribe-concepts-stage"></a>

`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
```

### 方針
<a name="ios-publish-subscribe-concepts-strategy"></a>

`IVSStageStrategy` プロトコルは、ホストアプリケーションがステージの望ましい状態を SDK に伝達する方法を提供します。`shouldSubscribeToParticipant`、`shouldPublishParticipant`、`streamsToPublishForParticipant` の 3 つの関数を実装する必要があります。以下で、すべて説明します。

#### 参加者へのサブスクライブ
<a name="ios-publish-subscribe-concepts-strategy-participants"></a>

```
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
    }
}
```

これを使用すると、モデレーターは、自身は視聴の対象とならずに、すべてのゲストを監視できるステージを作ることができます。ホストアプリケーションでは、追加のビジネスロジックを使用して、モデレータがお互いを見えるようにしても、ゲストには見えないようにすることができます。

#### 参加者へのサブスクライブの設定
<a name="ios-publish-subscribe-concepts-strategy-participants-config"></a>

```
func stage(_ stage: IVSStage, subscribeConfigurationForParticipant participant: IVSParticipantInfo) -> IVSSubscribeConfiguration
```

リモート参加者がサブスクライブしている場合 (「[参加者へのサブスクライブ](#ios-publish-subscribe-concepts-strategy-participants)」を参照)、SDK はホストアプリケーションにその参加者のカスタムサブスクライブ設定についてクエリします。この設定はオプションであり、ホストアプリケーションがサブスクライバーの動作の特定の側面を制御できるようにします。設定できる内容の詳細については、SDK リファレンスドキュメントの「[SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration)」を参照してください。

次に示すのは実装の例です。

```
func stage(_ stage: IVSStage, subscribeConfigurationForParticipant participant: IVSParticipantInfo) -> IVSSubscribeConfiguration {
    let config = IVSSubscribeConfiguration()

    try! config.jitterBuffer.setMinDelay(.medium())

    return config
}
```

この実装では、サブスクライブしたすべての参加者のジッターバッファ最小遅延を `MEDIUM` のプリセットに更新します。

`shouldSubscribeToParticipant` を使用した、より高度な実装も可能です。指定された `ParticipantInfo` を使用して、特定の参加者のサブスクライブ設定を選択的に更新できます。

デフォルトの動作を使用することをお勧めします。カスタム設定は、特定の動作を変更したい場合にのみ指定します。

#### 配信
<a name="ios-publish-subscribe-concepts-strategy-publishing"></a>

```
func stage(_ stage: IVSStage, shouldPublishParticipant participant: IVSParticipantInfo) -> Bool
```

ステージに接続すると、SDK はホストアプリケーションにクエリを実行し、特定の参加者を配信とすべきかどうかを確認します。これは、提供されたトークンに基づいて配信する権限を持つローカル参加者においてのみ呼び出されます。

次に示すのは実装の例です。

```
func stage(_ stage: IVSStage, shouldPublishParticipant participant: IVSParticipantInfo) -> Bool {
    return true
}
```

これは、ユーザーは常に配信状態としたい標準的なビデオチャットアプリケーション用です。オーディオとビデオをミュートまたはミュート解除して、すぐに不可視または可視にできます。(配信/配信停止も使用できますが、この方法では大幅に遅くなります。可視性を頻繁に変更したいユースケースには、ミュート/ミュート解除が適しています。)

#### 配信するストリームの選択
<a name="ios-publish-subscribe-concepts-strategy-streams"></a>

```
func stage(_ stage: IVSStage, streamsToPublishForParticipant participant: IVSParticipantInfo) -> [IVSLocalStageStream]
```

配信時には、これを使用して配信するオーディオストリームとビデオストリームが決定されます。これについては、後ほど「[メディアストリームの配信](#ios-publish-subscribe-publish-stream)」で詳しく説明します。

#### ストラテジーの更新
<a name="ios-publish-subscribe-concepts-strategy-updates"></a>

このストラテジーは動的であることを意図しており、上記の関数のいずれかから返される値はいつでも変更できます。たとえば、エンドユーザーがボタンをタップするまでホストアプリケーションが配信したくない場合、`shouldPublishParticipant` (`hasUserTappedPublishButton` など) から変数を返すことができます。その変数がエンドユーザーの相互作用に基づいて変更されたら、`stage.refreshStrategy()` を呼び出して、変更されたもののみを適用して、最新の値のストラテジーを照会する必要があることを SDK に通知します。SDK は、`shouldPublishParticipant` 値が変更されたことを検出すると、配信プロセスを開始します。SDK クエリとすべての関数が以前と同じ値を返す場合、`refreshStrategy` 呼び出しによってステージが変更されることはありません。

`shouldSubscribeToParticipant` の戻り値が `.audioVideo` から `.audioOnly` に変更され、以前にビデオストリームが存在していた場合は、戻り値が変更されたすべての参加者のビデオストリームが削除されます。

通常、ホストアプリケーションは、適切に管理するために必要なすべての状態について考慮する必要はありません。ステージは以前のストラテジーと現在のストラテジーの違いを最も効率的に適用するストラテジーを使用します。このため、`stage.refreshStrategy()` の呼び出しはストラテジーが変わらない限り何もしないため、低コストなオペレーションとみなすことができます。

### レンダラー
<a name="ios-publish-subscribe-concepts-renderer"></a>

`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` がトリガーされることに注意してください。

## メディアストリームを配信する
<a name="ios-publish-subscribe-publish-stream"></a>

内蔵マイクやカメラなどのローカルデバイスは、`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]
}
```

## 参加者を表示、削除する
<a name="ios-publish-subscribe-participants"></a>

サブスクライブが完了すると、レンダラーの `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 から参加者を削除するためのカスタムのビジネスロジックは必要ありません。

## メディアストリームをミュート、ミュート解除する
<a name="ios-publish-subscribe-mute-streams"></a>

`IVSLocalStageStream` オブジェクトには、ストリームをミュートするかどうかを制御する `setMuted` 関数があります。この関数は、`streamsToPublishForParticipant` ストラテジー関数から返される前または後にストリームで呼び出すことができます。

**重要**: `refreshStrategy` を呼び出した後に新しい `IVSLocalStageStream` オブジェクトインスタンスが `streamsToPublishForParticipant` によって返された場合、新しいストリームオブジェクトのミュート状態がステージに適用されます。新しい `IVSLocalStageStream` インスタンスを作成するときは、想定どおりのミュート状態を維持するように注意してください。

## リモート参加者のメディアミュート状態の監視
<a name="ios-publish-subscribe-mute-state"></a>

参加者がビデオまたはオーディオストリームのミュート状態を変更すると、変更されたストリームの配列を使用してレンダラー `didChangeMutedStreams` 関数が呼び出されます。`IVSStageStream` の `isMuted` プロパティを使用して、次の UI を適宜更新してください。

```
func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChangeMutedStreams streams: [IVSStageStream]) {
    streams.forEach { stream in 
        /* stream.isMuted */
    }
}
```

## ステージ構成を作成する
<a name="ios-publish-subscribe-stage-config"></a>

ステージの動画設定の値をカスタマイズするには、次の `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 統計を取得する
<a name="ios-publish-subscribe-webrtc-stats"></a>

配信ストリームまたはサブスクライブ中のストリームの最新の 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)")
      }
   }
}
```

## 参加者属性を取得
<a name="ios-publish-subscribe-participant-attributes"></a>

`CreateParticipantToken` オペレーションリクエストで属性を指定した場合、`IVSParticipantInfo` プロパティに属性が表示されます。

```
func stage(_ stage: IVSStage, participantDidJoin participant: IVSParticipantInfo) {
    print("ID: \(participant.participantId)")
    for attribute in participant.attributes {
        print("attribute: \(attribute.key)=\(attribute.value)")
    }
}
```

## メッセージを埋め込む
<a name="ios-publish-subscribe-embed-messages"></a>

IVSImageDevice で `embedMessage` メソッドを使用すると、配信中のビデオフレームにメタデータペイロードを直接挿入できます。これは、リアルタイムアプリケーションのフレーム同期型メッセージングを可能にします。メッセージの埋め込みを使用できるのは、リアルタイム配信(低レイテンシー配信ではない) の SDK を使用している場合のみです。

埋め込みメッセージは、ビデオフレーム内に直接埋め込まれ、パケット配信を保証しない UDP 経由で送信されるため、必ずしもサブスクライバーに届くとは限りません。送信中のパケット損失は、特にネットワーク状態が悪い場合において、メッセージの損失につながる可能性があります。この問題を軽減するため、`embedMessage` メソッドには `repeatCount` パラメータが含まれています。このパラメータは、連続する複数のフレーム全体でメッセージを複製することで、配信信頼性を向上させます。この機能を利用できるのはビデオストリームのみです。

### embedMessage の使用
<a name="ios-embed-messages-using-embedmessage"></a>

配信元のクライアントは、IVSImageDevice で `embedMessage` メソッドを使用して、メッセージペイロードをビデオストリームに埋め込むことができます。ペイロードサイズは 0 KB より大きく 1 KB 未満のサイズにする必要があります。挿入される埋め込みメッセージの 1 秒あたりの数が 10 KB/秒を超えないようにする必要があります。

```
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)")
}
```

### メッセージペイロードの反復
<a name="ios-embed-messages-repeat-payloads"></a>

`repeatCount` を使用して複数のフレーム全体でメッセージを複製し、信頼性を向上させます。この値は 0 ～ 30 の範囲の値にする必要があります。受信クライアントには、メッセージを重複除外するロジックが必要です。

```
try imageDevice.embedMessage(messageData, withRepeatCount: 5)

// repeatCount: 0-30, receiving clients should handle duplicates
```

### 埋め込みメッセージの読み取り
<a name="ios-embed-messages-read-messages"></a>

受信ストリームから埋め込みメッセージを読み取る方法については、以下の「補足拡張情報 (SEI) を取得する」を参照してください。

## 補足拡張情報 (SEI、Supplemental Enhancement Information) を取得する
<a name="ios-publish-subscribe-sei-attributes"></a>

補足拡張情報 (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
    		}
	}
}
```

## セッションをバックグラウンドで続行
<a name="ios-publish-subscribe-background-session"></a>

アプリがバックグラウンドに入っても、リモート音声を聞きながらステージにい続けることはできますが、自分の画像や音声を送信し続けることはできません。`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()` に電話をかけます。

## サイマルキャストによるレイヤードエンコーディング
<a name="ios-publish-subscribe-layered-encoding-simulcast"></a>

サイマルキャストによるレイヤードエンコーディングは、パブリッシャーが複数の異なるビデオの品質レイヤーを送信し、サブスクライバーがそれらのレイヤーを動的または手動で設定できるようにする IVS リアルタイムのストリーミング機能です。この機能は、「[ストリーミング最適化](real-time-streaming-optimization.md)」ドキュメントで詳しく説明されています。

### レイヤードエンコーディングの設定 (パブリッシャー）
<a name="ios-layered-encoding-simulcast-configure-publisher"></a>

パブリッシャーとしてサイマルキャストによるレイヤードエンコーディングを有効にするには、インスタンス化時に `IVSLocalStageStream` に次の設定を追加します。

```
// Enable Simulcast
let config = IVSLocalStageStreamVideoConfiguration()
config.simulcast.enabled = true

let cameraStream = IVSLocalStageStream(device: camera, configuration: config)

// Other Stage implementation code
```

ビデオ設定で設定した解像度に応じて、「*ストリーミングの最適化*」の「[デフォルトレイヤー、品質、フレームレート](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers)」セクションで定義されているように、設定された数のレイヤーがエンコードされて送信されます。

また、必要に応じて、サイマルキャスト設定内から個々のレイヤーを設定できます。

```
// 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
```

または、最大で 3 つのレイヤー用に独自のカスタムレイヤー設定を作成することもできます。空のアレイを指定するか、値を指定しない場合、上記のデフォルトが使用されます。レイヤーは、次の必須プロパティセッターで説明されています。
+ `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 リファレンスドキュメントを参照してください。

### レイヤードエンコーディングの設定 (サブスクライバー）
<a name="ios-layered-encoding-simulcast-configure-subscriber"></a>

サブスクライバーとして、レイヤードエンコーディングを有効にするために必要なものはありません。パブリッシャーがサイマルキャストレイヤーを送信している場合、デフォルトでサーバーによってレイヤー間で動的に適応され、サブスクライバーのデバイスおよびネットワークの状態に基づいて最適な品質が選択されます。

あるいは、パブリッシャーが送信している明示的なレイヤーを選択するには、以下に説明するいくつかのオプションがあります。

### オプション 1: 初期レイヤー品質の選択
<a name="ios-layered-encoding-simulcast-layer-quality-preference"></a>

`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: ストリームに優先されるレイヤー
<a name="ios-layered-encoding-simulcast-preferred-layer"></a>

`preferredLayerForStream` 戦略メソッドを使用すると、ストリームの開始後にレイヤーを選択できます。この戦略メソッドは参加者とストリーム情報を受け取るため、参加者ごとにレイヤーを選択できます。このメソッドは、ストリームのレイヤーが変化したとき、参加者の状態が変化したとき、またはホストアプリケーションが戦略を更新したときなど、特定のイベントに応じて SDK が呼び出します。

この戦略メソッドは `IVSRemoteStageStreamLayer` オブジェクトを返します。これは次のいずれかになります。
+ `IVSRemoteStageStream.layers` によって返されるレイヤーオブジェクトなど。
+ レイヤーを選択せず、動的適応が優先されることを示す null。

例えば、次の戦略ではユーザーが常に最低品質のビデオレイヤーを選択するようにします。

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

レイヤーの選択をリセットして動的適応に戻るには、戦略で null または未定義を返します。この例では、`appState` はホストアプリケーションの状態を表すプレースホルダー変数です。

```
func stage(_ stage: IVSStage, participant: IVSParticipantInfo, preferredLayerFor stream: IVSRemoteStageStream) -> IVSRemoteStageStreamLayer? {
    If appState.isAutoMode {
        return nil
    } else {
        return appState.layerChoice
    }
}
```

### オプション 3: RemoteStageStream レイヤーヘルパー
<a name="ios-layered-encoding-simulcast-remotestagestream-helpers"></a>

`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 リファレンスドキュメント](https://aws.github.io/amazon-ivs-broadcast-docs/latest/ios/)」の「`IVSRemoteStageStream`」クラスを参照してください。`LayerSelected` の理由として `UNAVAILABLE` が返された場合、これはリクエストされたレイヤーが選択できなかったことを示します。代わりにベストエフォートの選択が行われ、ストリームの安定性を維持するために、通常は低品質のレイヤーが選択されます。

## IVS チャネルにステージをブロードキャストする
<a name="ios-publish-subscribe-broadcast-stage"></a>

ステージをブロードキャストするには、別の `IVSBroadcastSession` を作成してから、前述の SDK による通常のブロードキャスト手順に従います。`IVSStageStream` の `device` プロパティは、上のスニペットで示すように、`IVSImageDevice` または `IVSAudioDevice` のいずれかになります。これらを `IVSBroadcastSession.mixer` に接続すると、カスタマイズ可能なレイアウトでステージ全体をブロードキャストできます。

オプションで、ステージを合成して IVS 低レイテンシーチャネルにブロードキャストすることで、より多くの視聴者に届けることもできます。「IVS 低レイテンシーストリーミングユーザーガイド」の「[Amazon IVS ストリームでの複数のホストの有効化](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/multiple-hosts.html)」を参照してください。