

# IVS Broadcast SDK \$1 リアルタイムストリーミング
<a name="broadcast"></a>

Amazon Interractive Video Searvice (IVS) リアルタイムストリーミングブロードキャスト SDK は、Amazon IVS を使用してアプリケーションを構築するデベロッパー向けのものです。この SDK は、Amazon IVS のアーキテクチャを活用するように設計されており、Amazon IVS と共に、継続的に改善され、新機能が提供されます。ネイティブのブロードキャスト SDK として、アプリケーションおよびユーザーがアプリケーションにアクセスするデバイスに対してパフォーマンスへの影響を最小限に抑えるように設計されています。

Broadcast SDK はビデオの送信と受信の両方に使用されることに注意してください。ホストとビューアーには同じ SDK を使用します。個別のプレーヤー SDK は必要ありません。

アプリケーションでは、Amazon IVS Broadcast SDK の主な機能を活用できます。
+ **高品質ストリーミング** — ブロードキャスト SDK は、高品質のストリーミングをサポートします。カメラからビデオをキャプチャし、最大 720p でエンコードします。
+ **自動ビットレート調整** — スマートフォンユーザーは移動するため、ブロードキャストの過程でネットワークの状況が変わることがあります。Amazon IVS Broadcast SDK は、変化するネットワーク状況に対応するために、動画のビットレートを自動的に調整します。
+ **縦向きと横向きのサポート** — ユーザーがデバイスをどのように持っているかに関係なく、画像は正しい向きで適切に拡大縮小されて表示されます。Broadcast SDK は、ポートレートとランドスケープの両方のキャンバスサイズをサポートします。ユーザーが設定した向きからデバイスを回転させると、アスペクト比が自動的に調整されます。
+ **セキュアなストリーミング** — ユーザーのブロードキャストは、 TLS を使用して暗号化されるため、ストリームを安全に保つことができます。
+ **外部オーディオデバイス** — Amazon IVS Broadcast SDK は、オーディオジャック、USB、および Bluetooth SCO 外部マイクをサポートしています。

## プラットフォームの要件
<a name="broadcast-platform-requirements"></a>

### ネイティブプラットフォーム
<a name="broadcast-native-platforms"></a>


| プラットフォーム | サポートされているバージョン | 
| --- | --- | 
| Android |  9.0 以降 -- バージョン 6.0 移行でビルドすることはできますが、リアルタイムストリーミング機能は使用できないことに注意してください。  | 
| iOS |  14 以降  | 

IVS は、少なくとも 4 つの iOS メジャーバージョンと 6 つの Android メジャーバージョンをサポートしています。現在のサポート対象バージョンは、これらの最小値よりも多い可能性があります。メジャーバージョンがサポートされなくなる場合は、少なくとも 3 か月前に SDK リリースノートでお客様にお知らせします。

### デスクトップブラウザ
<a name="browser-desktop"></a>


| ブラウザ | サポートされているプラットフォーム | サポートされているバージョン | 
| --- | --- | --- | 
| Chrome | Windows、macOS | 2 つのメジャーバージョン (最新バージョンと 1 つ前のバージョン) | 
| Firefox | Windows、macOS | 2 つのメジャーバージョン (最新バージョンと 1 つ前のバージョン) | 
| Edge | Windows 8.1 以降 | 2 つのメジャーバージョン (最新バージョンと 1 つ前のバージョン) Edge Legacy は除く | 
| Safari | macOS | 2 つのメジャーバージョン (最新バージョンと 1 つ前のバージョン) | 

### モバイルブラウザ (iOS および Android)
<a name="browser-mobile"></a>


| ブラウザ | サポートされているプラットフォーム | サポートされているバージョン | 
| --- | --- | --- | 
| Chrome | iOS、Android | 2 つのメジャーバージョン (最新バージョンと 1 つ前のバージョン) | 
| Firefox | Android | 2 つのメジャーバージョン (最新バージョンと 1 つ前のバージョン) | 
| Safari | iOS | 2 つのメジャーバージョン (最新バージョンと 1 つ前のバージョン) | 

#### 既知の制限事項
<a name="browser-mobile-limitations"></a>
+ 動画のアーティファクトやブラックスクリーンの原因となるパフォーマンスの制約により、いずれのモバイルウェブブラウザでも、パブリッシャーで同時にパブリッシュ/サブスクライブする場合は、その数を 3 個までにするようお勧めします。パブリッシャーの数をそれより増やす必要がある場合は、[オーディオのみのパブリッシュとサブスクライブ](web-publish-subscribe.md#web-publish-subscribe-concepts-strategy-updates)を設定します。
+ パフォーマンス上の問題やクラッシュの可能性を考慮して、ステージを合成して Android Mobile Web のチャネルに配信することはお勧めしません。ブロードキャスト機能が必要な場合は、[IVS Real-Time Streaming の Android Broadcast SDK](broadcast-android.md) を統合してください。

## ウェブビュー
<a name="broadcast-webviews"></a>

Web Broadcast SDK は、ウェブビューやウェブライク環境 (テレビ、家庭用ゲーム機など) をサポートしていません。モバイル実装については、[Android](broadcast-android.md) 向けおよび [iOS](broadcast-ios.md) 向けの「IVS Broadcast SDK ガイド (リアルタイムストリーミング)」を参照してください。

## 必要なデバイスのアクセス
<a name="broadcast-device-access"></a>

Broadcast SDK では、デバイス内蔵のカメラとマイクと、Bluetooth、USB、またはオーディオジャックを介して接続されているカメラとマイクにアクセスする必要があります。

## サポート
<a name="broadcast-support"></a>

ブロードキャスト SDK は継続的に改良しています。利用可能なバージョンと修正済みの問題については [Amazon IVS リリースノート](release-notes.md)を参照してください。必要な場合、サポートに連絡する前にお使いのプレイヤーのバージョンを更新し、問題が解決するかどうか確認してください。

### バージョニング
<a name="broadcast-support-versioning"></a>

Amazon IVS Broadcast SDK は、[セマンティックバージョニング](https://semver.org/)を使用しています。

以下の解説は、次を前提としています。
+ 最新リリースは 4.1.3。
+ 1 つ前のメジャーバージョンの最新リリースは 3.2.4。
+ バージョン 1.x の最新リリースは 1.5.6.

最新バージョンのマイナーリリースとして、下位互換性のある新機能が追加されています。この場合、次回の新機能のセットは、バージョン 4.2.0 として追加されます。

下位互換性のあるマイナーなバグ修正が、最新バージョンのパッチリリースとして追加されています。ここでは、次回のマイナーなバグ修正のセットは、バージョン 4.1.4 として追加されます。

下位互換性のあるメジャーなバグ修正は異なる方法で処理されます。これらはいくつかのバージョンに追加されています。
+ 最新バージョンのパッチリリース。こちらは、バージョン 4.1.4 です。
+ 1 つ前のマイナーバージョンのパッチリリース。こちらは、バージョン 3.2.5 です。
+ 最新バージョン 1.x リリースのパッチリリース。こちらは、バージョン 1.5.7 です。

メジャーなバグ修正は、Amazon IVS 製品チームによって定義されています。典型的な例に、重要なセキュリティ更新のほか、お客様に必要な選別された修正があります。

**注:** 上記の例では、リリースされたバージョンの数字は、連番でインクリメントされています(4.1.3 → 4.1.4、など)。実際は、1 つ以上のパッチ番号が内部に残り、リリースされないままになることもあります。そのため、リリースされたバージョンは 4.1.3 から (例えば) 4.1.6 に増えることもあります。

# IVS Broadcast SDK: Web ガイド \$1 リアルタイムストリーミング
<a name="broadcast-web"></a>

IVS Real-Time Streaming Web Broadcast SDK は、デベロッパー向けに、Web 上でインタラクティブかつリアルタイムの体験を構築するためのツールを提供します。この SDK は、Amazon IVS を使用してウェブアプリケーションを構築するデベロッパー向けです。

Web Broadcast SDK を使用して、参加者はビデオを送受信できます。SDK は、次の操作をサポートします。
+ ステージに参加する
+ ステージ内の他の参加者にメディアを配信する
+ ステージ内の他の参加者のメディアをサブスクライブする
+ ステージに配信されたビデオとオーディオを管理および監視する
+ 各ピア接続の WebRTC 統計を取得
+ IVS 低遅延ストリーミング Web Broadcast SDK からのすべての操作

**Web Broadcast SDK の最新バージョン:** 1.33.0 ([リリースノート](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-web-rt)) 

**リファレンスドキュメント:** Amazon IVS Web Broadcast SDK で利用できる最も重要なメソッドについては、[https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference) を参照してください。SDK の最新バージョンが選択されていることを確認してください。

**サンプルコード**: SDK をすぐに使い始めるには、以下のサンプルの利用が適しています。
+ [簡易再生](https://codepen.io/amazon-ivs/pen/RNwVBRK)
+ [簡易配信とサブスクライブ](https://codepen.io/amazon-ivs/pen/ZEqgrpo)
+ [包括的な React リアルタイムコラボレーションデモ](https://github.com/aws-samples/amazon-ivs-real-time-collaboration-web-demo/tree/main)

**プラットフォーム要件**: サポートされているプラットフォームのリストについては、「[Amazon IVS Broadcast SDK](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/broadcast.html)」を参照してください。

**注:** ブラウザからの公開は、追加のソフトウェアのインストールを必要としないため、エンドユーザーにとって便利です。ただし、ブラウザベースの公開は、ブラウザ環境の制約と変動の影響を受けます。安定性を優先する必要がある場合 (イベントストリーミングなど）、通常はシステムリソースに直接アクセスでき、ブラウザの制限を回避できる、ブラウザ以外のソース (OBS Studioやその他の専用エンコーダーなど) から公開することをお勧めします。ブラウザ以外の公開オプションの詳細については、「[ストリームの取り込み](rt-stream-ingest.md)」ドキュメントを参照してください。

# IVS Web Broadcast SDK の開始方法 \$1 Real-Time Streaming
<a name="broadcast-web-getting-started"></a>

このドキュメントでは、IVS Real-Time Streaming Web Broadcast SDK の使用を開始するためのステップについて説明します。

## インポート
<a name="broadcast-web-getting-started-imports"></a>

リアルタイムのビルディングブロックは、ルートブロードキャストモジュールとは別の名前空間に配置されています。

### スクリプトタグを使用する
<a name="broadcast-web-getting-started-imports-script"></a>

Web Broadcast SDK は JavaScript ライブラリとして分散されており、[https://web-broadcast.live-video.net/1.33.0/amazon-ivs-web-broadcast.js](https://web-broadcast.live-video.net/1.33.0/amazon-ivs-web-broadcast.js) で入手できます。

以下の例で定義されているクラスおよび列挙型はグローバルオブジェクト `IVSBroadcastClient` にあります。

```
const { Stage, SubscribeType } = IVSBroadcastClient;
```

### npmを使う
<a name="broadcast-web-getting-started-imports-npm"></a>

`npm` パッケージをインストールするには: 

```
npm install amazon-ivs-web-broadcast
```

クラス、列挙型、型はパッケージモジュールからインポートすることもできます。

```
import { Stage, SubscribeType, LocalStageStream } from 'amazon-ivs-web-broadcast'
```

### サーバーサイドレンダリングのサポート
<a name="broadcast-web-getting-started-imports-server-side-rendering"></a>

Web Broadcast SDK ステージライブラリは、ロード時にライブラリの機能に必要なブラウザプリミティブを参照するため、サーバー側のコンテキストにロードできません。これを回避するには、「[Next と React を使用した Web Broadcast デモ](https://github.com/aws-samples/amazon-ivs-broadcast-web-demo/blob/main/hooks/useBroadcastSDK.js#L26-L31)」で示されているように、ライブラリを動的にロードします。

## 必要なアクセス許可
<a name="broadcast-web-request-permissions"></a>

アプリケーションは、ユーザーのカメラとマイクへのアクセス許可をリクエストする必要があります。また、HTTPS で提供される必要があります。(これは Amazon IVS に特有ではなく、カメラやマイクにアクセスが必要なすべてのウェブサイトに必要です。)

オーディオおよびビデオデバイス両方のアクセス許可をリクエストし、キャプチャする方法を示す関数の例を次に示します。

```
async function handlePermissions() {
   let permissions = {
       audio: false,
       video: false,
   };
   try {
       const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
       for (const track of stream.getTracks()) {
           track.stop();
       }
       permissions = { video: true, audio: true };
   } catch (err) {
       permissions = { video: false, audio: false };
       console.error(err.message);
   }
   // If we still don't have permissions after requesting them display the error message
   if (!permissions.video) {
       console.error('Failed to get video permissions.');
   } else if (!permissions.audio) {
       console.error('Failed to get audio permissions.');
   }
}
```

詳細については、「[[Permissions API](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia)」および「MediaDevices.getUserMedia()](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API)」を参照してください。

## 利用可能なデバイスのリストを表示する
<a name="broadcast-web-request-list-devices"></a>

キャプチャ可能なデバイスを確認するには、ブラウザの [MediaDevices.enumerateDevices()](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices) メソッドにクエリを実行します。

```
const devices = await navigator.mediaDevices.enumerateDevices();
window.videoDevices = devices.filter((d) => d.kind === 'videoinput');
window.audioDevices = devices.filter((d) => d.kind === 'audioinput');
```

## デバイスから MediaStream を取得する
<a name="broadcast-web-retrieve-mediastream"></a>

使用可能なデバイスのリストを獲得すると、任意の数のデバイスからストリームを取得できます。例えば、カメラからストリームを取得する `getUserMedia()` メソッドを利用できます。

ストリームをキャプチャするデバイスを指定する場合は、メディア制約の `audio` または `video` セクションで `deviceId` を明示的に設定できます。または、`deviceId` を省略して、ブラウザのプロンプトからユーザーにデバイスを選択させることもできます。

`width` および `height` の制約を使用して、理想的なカメラの解像度を指定することもできます。(これらの制約について詳しくは、[こちら](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#properties_of_video_tracks)をご覧ください。) SDK では、ブロードキャストの最大解像度に対応する幅および高さの制約が自動的に適用されます。しかし、ソースを SDK に追加した後ソースのアスペクト比が変更されないよう、これらもお客様ご自身で適用することをお勧めします。

リアルタイムストリーミングでは、メディアが 720p 解像度に制限されていることを確認します。具体的には、幅と高さの `getUserMedia` と `getDisplayMedia` の制約値は、乗算時に 921600 (1280 x 720) を超えることはできません。

```
const videoConfiguration = {
  maxWidth: 1280,
  maxHeight: 720,
  maxFramerate: 30,
}

window.cameraStream = await navigator.mediaDevices.getUserMedia({
   video: {
       deviceId: window.videoDevices[0].deviceId,
       width: {
           ideal: videoConfiguration.maxWidth,
       },
       height: {
           ideal:videoConfiguration.maxHeight,
       },
   },
});
window.microphoneStream = await navigator.mediaDevices.getUserMedia({
   audio: { deviceId: window.audioDevices[0].deviceId },
});
```

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

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

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

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

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

`Stage` クラスは、ホストアプリケーションと SDK 間の主要な相互作用のポイントです。これはステージそのものを表し、ステージへの参加とステージからの退出に使用されます。ステージの作成と参加には、コントロールプレーンからの有効で有効期限内のトークン文字列 (`token` として表示) が必要です。ステージへの参加と退出は簡単です。

```
const stage = new Stage(token, strategy)

try {
   await stage.join();
} catch (error) {
   // handle join exception
}

stage.leave();
```

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

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

定義済みのストラテジーを使用するには、それを `Stage` コンストラクターに渡します。以下は、参加者の Web カメラをステージに配信し、すべての参加者にサブスクライブするというストラテジーを使用したアプリケーションの完全な例です。必要な各ストラテジー機能の目的は、以下のセクションで説明します。

```
const devices = await navigator.mediaDevices.getUserMedia({ 
   audio: true,
   video: {
        width: { max: 1280 },
        height: { max: 720 },
    } 
});
const myAudioTrack = new LocalStageStream(devices.getAudioTracks()[0]);
const myVideoTrack = new LocalStageStream(devices.getVideoTracks()[0]);

// Define the stage strategy, implementing required functions
const strategy = {
   audioTrack: myAudioTrack,
   videoTrack: myVideoTrack,

   // optional
   updateTracks(newAudioTrack, newVideoTrack) {
      this.audioTrack = newAudioTrack;
      this.videoTrack = newVideoTrack;
   },

   // required
   stageStreamsToPublish() {
      return [this.audioTrack, this.videoTrack];
   },

   // required
   shouldPublishParticipant(participant) {
      return true;
   },

   // required
   shouldSubscribeToParticipant(participant) {
      return SubscribeType.AUDIO_VIDEO;
   }
};

// Initialize the stage and start publishing
const stage = new Stage(token, strategy);
await stage.join();


// To update later (e.g. in an onClick event handler)
strategy.updateTracks(myNewAudioTrack, myNewVideoTrack);
stage.refreshStrategy();
```

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

```
shouldSubscribeToParticipant(participant: StageParticipantInfo): SubscribeType
```

リモート参加者がステージに参加すると、SDK はその参加者に対して希望するサブスクリプションの状態についてホストアプリケーションに問い合わせます。使用できるオプションは `NONE`、`AUDIO_ONLY`、および `AUDIO_VIDEO` です。この関数の値を返す場合、ホストアプリケーションは配信の状態、現在のサブスクリプションの状態、またはステージ接続の状態を考慮する必要はありません。`AUDIO_VIDEO` が返された場合、SDK はリモート参加者が配信するまで待ってからサブスクライブし、プロセス全体でイベントを作成してホストアプリケーションを更新します。

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

```
const strategy = {
   
   shouldSubscribeToParticipant: (participant) => {
      return SubscribeType.AUDIO_VIDEO;
   }

   // ... other strategy functions
}
```

これは、ビデオチャットアプリケーションなど、すべての参加者が互いに常に可視化されているホストアプリケーション向けのの完全な実装です。

より高度な実装も可能です。例えば、CreateParticipantToken でトークンを作成するときに、アプリケーションが `role` 属性を提供しているとします。アプリケーションは、`StageParticipantInfo` の `attributes` プロパティを使用して、サーバーが提供する属性に基づいて、参加者に対して選択的にサブスクライブできます。

```
const strategy = {
   
   shouldSubscribeToParticipant(participant) {
      switch (participant.attributes.role) {
         case 'moderator':
            return SubscribeType.NONE;
         case 'guest':
            return SubscribeType.AUDIO_VIDEO;
         default:
            return SubscribeType.NONE;
      }
   }
   // . . . other strategies properties
}
```

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

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

```
subscribeConfiguration(participant: StageParticipantInfo): SubscribeConfiguration
```

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

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

```
const strategy = {
   
   subscribeConfiguration: (participant) => {
      return {
         jitterBuffer: {
            minDelay: JitterBufferMinDelay.MEDIUM
         }  
      }

   // ... other strategy functions
}
```

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

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

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

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

```
shouldPublishParticipant(participant: StageParticipantInfo): boolean
```

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

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

```
const strategy = {
   
   shouldPublishParticipant: (participant) => {
      return true;
   }

   // . . . other strategies properties
}
```

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

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

```
stageStreamsToPublish(): LocalStageStream[];
```

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

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

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

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

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

### Events
<a name="web-publish-subscribe-concepts-events"></a>

`Stage` インスタンスはイベントエミッターです。`stage.on()` を使用して、ステージの状態がホストアプリケーションに伝達されます。ホストアプリケーションの UI の更新は、通常、イベントによって完全にサポートされます。イベントは次のとおりです。

```
stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {})
stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {})
stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {})
stage.on(StageEvents.STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED, (participant, state) => {})
stage.on(StageEvents.STAGE_PARTICIPANT_SUBSCRIBE_STATE_CHANGED, (participant, state) => {})
stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => {})
stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_REMOVED, (participant, streams) => {})
stage.on(StageEvents.STAGE_STREAM_ADAPTION_CHANGED, (participant, stream, isAdapting) => ())
stage.on(StageEvents.STAGE_STREAM_LAYERS_CHANGED, (participant, stream, layers) => ())
stage.on(StageEvents.STAGE_STREAM_LAYER_SELECTED, (participant, stream, layer, reason) => ())
stage.on(StageEvents.STAGE_STREAM_MUTE_CHANGED, (participant, stream) => {})
stage.on(StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED, (participant, stream) => {})
```

これらのイベントのほとんどには、対応する `ParticipantInfo` が用意されています。

イベントによって提供される情報がストラテジーの戻り値に影響することは想定されていません。たとえば、`shouldSubscribeToParticipant` の戻り値は、`STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED` が呼び出されても変化しない想定です。ホストアプリケーションが特定の参加者をサブスクライブする場合は、その参加者の配信状態に関係なく、目的のサブスクリプションタイプを返す必要があります。SDK は、ステージの状態に基づいて、望ましいストラテジーの状態が適切なタイミングで実行されるようにする役目を担います。

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

マイクやカメラなどのローカルデバイスは、上記の「[デバイスからの MediaStream の取得](broadcast-web-getting-started.md#broadcast-web-retrieve-mediastream)」で説明したのと同じ手順で取得されます。この例では、`MediaStream` を使用して SDK による配信に使用される `LocalStageStream` オブジェクトのリストを作成しています。

```
try {
    // Get stream using steps outlined in document above
    const stream = await getMediaStreamFromDevice();

    let streamsToPublish = stream.getTracks().map(track => {
        new LocalStageStream(track)
    });

    // Create stage with strategy, or update existing strategy
    const strategy = {
        stageStreamsToPublish: () => streamsToPublish
    }
}
```

## スクリーン共有を配信する
<a name="web-publish-subscribe-publish-screenshare"></a>

アプリケーションでは、多くの場合、ユーザーの Web カメラに加えてスクリーン共有を配信する必要があります。スクリーン共有を配信するには、特にスクリーン共有のメディアを公開するためには、ステージ用の追加のトークンを作成する必要があります。`getDisplayMedia` を使用し、解像度を最大 720p に制限します。その後、ステップはカメラをステージに配信するのと似ています。

```
// Invoke the following lines to get the screenshare's tracks
const media = await navigator.mediaDevices.getDisplayMedia({
   video: {
      width: {
         max: 1280,
      },
      height: {
         max: 720,
      }
   }
});
const screenshare = { videoStream: new LocalStageStream(media.getVideoTracks()[0]) };
const screenshareStrategy = {
   stageStreamsToPublish: () => {
      return [screenshare.videoStream];
   },
   shouldPublishParticipant: (participant) => {
      return true;
   },
   shouldSubscribeToParticipant: (participant) => {
      return SubscribeType.AUDIO_VIDEO;
   }
}
const screenshareStage = new Stage(screenshareToken, screenshareStrategy);
await screenshareStage.join();
```

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

サブスクライブが完了すると、`STAGE_PARTICIPANT_STREAMS_ADDED` イベントを通じて `StageStream` オブジェクトの配列を受け取ります。このイベントでは、メディアストリームを表示する際に役立つ参加者情報も提供します。

```
stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => {
    let streamsToDisplay = streams;

    if (participant.isLocal) {
        // Ensure to exclude local audio streams, otherwise echo will occur
        streamsToDisplay = streams.filter(stream => stream.streamType === StreamType.VIDEO)
    }

    // Create or find video element already available in your application
    const videoEl = getParticipantVideoElement(participant.id);

    // Attach the participants streams
    videoEl.srcObject = new MediaStream();
    streamsToDisplay.forEach(stream => videoEl.srcObject.addTrack(stream.mediaStreamTrack));
})
```

参加者がストリームの配信を停止したり、配信登録を解除したりすると、削除されたストリームを使用して `STAGE_PARTICIPANT_STREAMS_REMOVED` 関数が呼び出されます。ホストアプリケーションは、これをシグナルとして使用して、参加者のビデオストリームを DOM から削除する必要があります。

`STAGE_PARTICIPANT_STREAMS_REMOVED` は、以下を含む、ストリームが削除される可能性のあるすべてのシナリオで呼び出されます。
+ リモート参加者は配信を停止します。
+ ローカルデバイスがサブスクリプションを解除するか、サブスクリプションを `AUDIO_VIDEO` から `AUDIO_ONLY` に変更します。
+ リモート参加者がステージを退出します。
+ ローカルの参加者がステージを退出します。

`STAGE_PARTICIPANT_STREAMS_REMOVED` はすべてのシナリオで呼び出されるため、リモートまたはローカルの離脱操作中、 UI から参加者を削除するためのカスタムのビジネスロジックは必要ありません。

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

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

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

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

参加者がビデオまたはオーディオのミュート状態を変更すると、変更されたストリームのリストで `STAGE_STREAM_MUTE_CHANGED` イベントがトリガーされます。`StageStream` の `isMuted` プロパティを使用して、次の UI を適宜更新してください。

```
stage.on(StageEvents.STAGE_STREAM_MUTE_CHANGED, (participant, stream) => {
   if (stream.streamType === 'video' && stream.isMuted) {
       // handle UI changes for video track getting muted
   }
})
```

また、[StageParticipantInfo](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference#stageparticipantinfo) でオーディオまたはビデオがミュートされているかどうか関する状態情報を参照することもできます。

```
stage.on(StageEvents.STAGE_STREAM_MUTE_CHANGED, (participant, stream) => {
   if (participant.videoStopped || participant.audioMuted) {
       // handle UI changes for either video or audio
   }
})
```

## WebRTC 統計を取得する
<a name="web-publish-subscribe-webrtc-stats"></a>

`requestQualityStats()` メソッドは、ローカルストリームとリモートストリームの両方に関する詳細な WebRTC 統計情報へのアクセスを提供します。これは LocalStageStream および RemoteStageStream オブジェクトの両方で使用できます。ネットワーク品質、パケット統計、ビットレート情報、フレーム関連のメトリクスなどの包括的な品質メトリクスを返します。

これは、await または promise の連鎖によって統計を取得できる非同期メソッドです。ストリームがアクティブでない場合 (内部統計が利用できない、または統計が利用できない場合) は `undefined` が返されます。統計情報が利用可能な場合、ストリーム (リモートまたはローカル、ビデオまたはオーディオ) に応じて、メソッドは [LocalVideoStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/LocalVideoStats)、[LocalAudioStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/LocalAudioStats)、[RemoteVideoStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/RemoteVideoStats)、または [RemoteAudioStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/RemoteAudioStats) オブジェクトを返します。

サイマルキャストを含むビデオストリームの場合、配列には複数の統計オブジェクト (レイヤーごとに 1 つ) が含まれていることに留意してください。

**ベストプラクティス**
+ ポーリング頻度 — パフォーマンスへの影響を避けるため、妥当な間隔 (1～5 秒) で `requestQualityStats()` を呼び出します
+ エラー処理 — 処理する前に、返された値が `undefined` であるかどうかを必ず確認してください
+ メモリ管理 — ストリームが不要になったときに間隔/タイムアウトをクリアします
+ ネットワーク品質 — ネットワークによる劣化の可能性に関するユーザーフィードバックには `networkQuality` を使用します 詳細については、「[NetworkQuality](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/enumerations/NetworkQuality)」を参照してください。

**使用例**

```
// For local streams
const localStats = await localVideoStream.requestQualityStats();
const audioStats = await localAudioStream.requestQualityStats();

// For remote streams
const remoteVideoStats = await remoteVideoStream.requestQualityStats();
const remoteAudioStats = await remoteAudioStream.requestQualityStats();

// Example: Monitor stats every 10 seconds
const statsInterval = setInterval(async () => {
   const stats = await localVideoStream.requestQualityStats();
   if (stats) {
      // Note: If simulcast is enabled, you may receive multiple 
      // stats records for each layer
      stats.forEach(layer => {
         const rid = layer.rid || 'default';
         console.log(`Layer ${rid}:`, {
            active: layer.active,
            networkQuality: layer.networkQuality,
            packetsSent: layer.packetsSent,
            bytesSent: layer.bytesSent,
            resolution: `${layer.frameWidth}x${layer.frameHeight}`,
            fps: layer.framesPerSecond
         });
      });
   }
}, 10000);
```

## メディアの最適化
<a name="web-publish-subscribe-optimizing-media"></a>

最高のパフォーマンスを得るには、`getUserMedia` および `getDisplayMedia` を制限して呼び出すことをお勧めします。

```
const CONSTRAINTS = {
    video: {
        width: { ideal: 1280 }, // Note: flip width and height values if portrait is desired
        height: { ideal: 720 },
        framerate: { ideal: 30 },
    },
};
```

次の `LocalStageStream` コンストラクターに渡される追加オプションを使用して、メディアをさらに制限できます。

```
const localStreamOptions = {
    minBitrate?: number;
    maxBitrate?: number;
    maxFramerate?: number;
    simulcast: {
        enabled: boolean
    }
}
const localStream = new LocalStageStream(track, localStreamOptions)
```

上記のコードについて
+ `minBitrate` はブラウザが使用することが予想される最小ビットレートを設定します。ただし、ビデオストリームの複雑度が低いと、エンコーダがこのビットレートよりも低くなる可能性があります。
+ `maxBitrate` はこのストリームでブラウザが超えないと予想される最大ビットレートを設定します。
+ `maxFramerate` はこのストリームでブラウザが超えないと予想される最大フレームレートを設定します。
+ `simulcast` オプションは Chromium ベースのブラウザでのみ使用できます。これにより、ストリームの 3 つのレンディションレイヤーを送信できます。
  + これにより、サーバーは、ネットワークの制限に基づいて、他の参加者に送信するレンディションを選択できます。
  + `simulcast` が `maxBitrate` および/または `maxFramerate` の値とともに指定された場合、`maxBitrate` が内部 SDK の 2 番目に高レイヤーのデフォルト `maxBitrate` 値である 900 kbps を下回らない限り、これらの値を念頭に置いて最上位のレンディションレイヤーが設定されることが想定されています。
  + `maxBitrate` が 2 番目に高いレイヤーのデフォルト値と比較して低すぎるように指定された場合は、`simulcast` は無効になります。
  + `simulcast` のオンとオフを切り替えるには、`shouldPublishParticipant` が `false` を返し、`refreshStrategy` を呼び出し、`shouldPublishParticipant` が `true` を返し、`refreshStrategy` をもう一度呼ぶ組み合わせにより、メディアを再配信する必要があります。

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

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

```
stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
   console.log(`Participant ${participant.id} info:`, participant.attributes);
})
```

## 補足拡張情報 (SEI)
<a name="web-publish-subscribe-sei-attributes"></a>

補足拡張情報 (SEI) NAL ユニットは、フレーム整列メタデータを動画と一緒に保存するために使用されます。H.264 ビデオストリームに配信およびサブスクライブするときに使用できます。SEI ペイロードがサブスクライバーに届くという保証はありません (特にネットワーク状況が悪い場合)。SEI ペイロードは H.264 フレーム構造内にデータを直接保存するため、この機能はオーディオのみのストリームでは利用できません。

### SEI ペイロードの挿入
<a name="sei-attributes-inserting-sei-payloads"></a>

配信元クライアントは、`inBandMessaging` を有効化するようにビデオの LocalStageStream を設定し、その後に `insertSeiMessage` メソッド呼び出すことで、配信されているステージストリームに SEI ペイロードを挿入できます。`inBandMessaging` を有効にすると、SDK のメモリ使用量が増加することに注意してください。

ペイロードは [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) タイプである必要があります。ペイロードサイズは 0 KB より大きく 1 KB 未満のサイズにする必要があります。挿入される SEI メッセージの 1 秒あたりの数が 10 KB/秒を超えない必要があります。

```
const config = {
    inBandMessaging: { enabled: true }
};
const vidStream = new LocalStageStream(videoTrack, config);
const payload = new TextEncoder().encode('hello world').buffer;
vidStream.insertSeiMessage(payload);
```

#### SEI ペイロードの反復
<a name="sei-attributes-repeating-sei-payloads"></a>

オプションで `repeatCount` を指定して、送信される次の N 個のフレームで SEI ペイロードの挿入を反復します。これは、ビデオの送信に使用される下層 UDP トランスポートプロトコルが原因で発生する可能性のある特有の損失を軽減するために役立つ場合があります。この値は 0～30 の値にする必要があることに注意してください。受信クライアントには、メッセージを重複除外するロジックが必要です。

```
vidStream.insertSeiMessage(payload, { repeatCount: 5 }); // Optional config, repeatCount must be between 0 and 30
```

### SEI ペイロードの読み取り
<a name="sei-attributes-reading-sei-payloads"></a>

サブスクライブするクライアントは、次の例に示すように、サブスクライバー (複数可) `SubscribeConfiguration` を設定して `inBandMessaging` を有効にし、`StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED` イベントをリッスンすることで、H.264 動画を配信しているパブリッシャー (存在する場合) から SEI ペイロードを読み取ることができます。

```
const strategy = {
    subscribeConfiguration: (participant) => {
        return {
            inBandMessaging: {
                enabled: true
            }
        }
    }
    // ... other strategy functions
}

stage.on(StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED, (participant, seiMessage) => {
    console.log(seiMessage.payload, seiMessage.uuid);
});
```

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

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

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

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

```
// Enable Simulcast
let cameraStream = new LocalStageStream(cameraDevice, {
   simulcast: { enabled: true }
})
```

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

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

```
import { SimulcastLayerPresets } from ‘amazon-ivs-web-broadcast’

// Enable Simulcast
let cameraStream = new LocalStageStream(cameraDevice, {
   simulcast: {
      enabled: true,
      layers: [
         SimulcastLayerPresets.DEFAULT_720,
          SimulcastLayerPresets.DEFAULT_360,
          SimulcastLayerPresets.DEFAULT_180, 
   }
})
```

または、最大で 3 つのレイヤー用に独自のカスタムレイヤー設定を作成することもできます。空のアレイを指定するか、値を指定しない場合、上記のデフォルトが使用されます。レイヤーは、次の必須プロパティで説明されています。
+ `height: number;`
+ `width: number;`
+ `maxBitrateKbps: number;`
+ `maxFramerate: number;`

プリセットから、個々のプロパティを上書きするか、まったく新しい設定を作成できます。

```
import { SimulcastLayerPresets } from ‘amazon-ivs-web-broadcast’

const custom720pLayer = {
   ...SimulcastLayerPresets.DEFAULT_720,
   maxFramerate: 15,
}

const custom360pLayer = {
       maxBitrateKbps: 600,
       maxFramerate: 15,
       width: 640,
       height: 360,
}

// Enable Simulcast
let cameraStream = new LocalStageStream(cameraDevice, {
   simulcast: {
      enabled: true,
      layers: [
         custom720pLayer,
         custom360pLayer, 
   }
})
```

個々のレイヤーを設定するときにトリガーできる最大値、制限、エラーについては、SDK リファレンスドキュメントを参照してください。

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

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

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

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

`subscribeConfiguration` 戦略を使用すると、サブスクライバーとして受信する初期レイヤーを選択できます。

```
const strategy = {
    subscribeConfiguration: (participant) => {
        return {
            simulcast: {
                initialLayerPreference: InitialLayerPreference.LOWEST_QUALITY
            }
        }
    }
    // ... other strategy functions
}
```

デフォルトでは、サブスクライバーは常に最初に最低品質のレイヤーが送信されます。これにより、徐々に最高品質のレイヤーにまで拡大します。エンドユーザーの帯域幅の消費量が最適化され、ビデオ再生に最適な時間が実現されるため、より貧弱なネットワーク上のユーザーに対して初期ビデオフリーズが軽減されます。

これらのオプションは `InitialLayerPreference` で利用できます。
+ `LOWEST_QUALITY` — サーバーは、最初に最低品質のビデオレイヤーを配信します。帯域幅の消費とメディアの時間が最適化されます。品質はビデオのサイズ、ビットレート、フレームレートの組み合わせとして定義されます。例えば、720p ビデオは 1080p ビデオよりも品質が低くなります。
+ `HIGHEST_QUALITY` — サーバーは、最初に最高品質のビデオレイヤーを配信します。品質が最適化されますが、メディアの時間が長くなる場合があります。品質はビデオのサイズ、ビットレート、フレームレートの組み合わせとして定義されます。例えば、1080p ビデオは 720p ビデオよりも高品質です。

**注:** 初期レイヤー設定 (`initialLayerPreference` の呼び出し) を反映させるには、再サブスクライブが必要です。これらの更新はアクティブなサブスクリプションに適用されないためです。



### オプション 2: ストリームに優先されるレイヤー
<a name="web-layered-encoding-simulcast-preferred-layer"></a>

ストリームが開始されたら、`preferredLayerForStream ` 戦略メソッドを使用できます。この戦略メソッドは、参加者およびストリーム情報を公開します。

戦略メソッドは、次の内容として返すことができます。
+ `RemoteStageStream.getLayers` が返す内容に直接的に基づくレイヤーオブジェクト 
+ `StageStreamLayer.label` に基づく、レイヤーオブジェクトのラベル文字列
+ レイヤーを選択せず、動的適応が優先されることを示す未定義または null

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

```
const strategy = {
    preferredLayerForStream: (participant, stream) => {
        return stream.getLowestQualityLayer();
    }
    // ... other strategy functions
}
```

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

```
const strategy = {
    preferredLayerForStream: (participant, stream) => {
        if (appState.isAutoMode) {
            return null;
        } else {
            return appState.layerChoice
        }
    }
    // ... other strategy functions
}
```

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

`RemoteStageStream` には、レイヤーの選択について決定し、対応する選択をエンドユーザーに表示するために使用できるいくつかのヘルパーがあります。
+ **レイヤーイベント** - `StageEvents` に加え、`RemoteStageStream` オブジェクト自体にはレイヤーおよびサイマルキャストの適応変更を伝えるイベントがあります。
  + `stream.on(RemoteStageStreamEvents.ADAPTION_CHANGED, (isAdapting) => {})`
  + `stream.on(RemoteStageStreamEvents.LAYERS_CHANGED, (layers) => {})`
  + `stream.on(RemoteStageStreamEvents.LAYER_SELECTED, (layer, reason) => {})`
+ **レイヤーメソッド** — `RemoteStageStream` には、ストリームおよび提示されるレイヤーに関する情報を取得するために使用できるいくつかのヘルパーメソッドがあります。これらのメソッドは、`preferredLayerForStream ` 戦略で提供されるリモートストリームに加え、`StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED` を介して配信されるリモートストリームで利用できます。
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`

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

## ネットワーク問題の処理
<a name="web-publish-subscribe-network-issues"></a>

ローカルデバイスのネットワーク接続が失われると、SDK はユーザーアクションなしで内部で再接続を試みます。場合によっては、SDK が正常に動作せず、ユーザーアクションが必要なる可能性があります。

大まかに、ステージの状態は `STAGE_CONNECTION_STATE_CHANGED` イベントを介して処理できます。

```
stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
   switch (state) {
      case StageConnectionState.DISCONNECTED:
         // handle disconnected UI
         return;
      case StageConnectionState.CONNECTING:
         // handle establishing connection UI
         return;
      case StageConnectionState.CONNECTED:
         // SDK is connected to the Stage
         return;
      case StageConnectionState.ERRORED:
         // SDK encountered an error and lost its connection to the stage. Wait for CONNECTED.
         return;
    }
})
```

通常、SDK は内部的に復旧しようとするため、ステージに正常に参加した後に発生するエラー状態を無視できます。SDK が `ERRORED` 状態を報告し、ステージが長時間 (30 秒以上) `CONNECTING` 状態のままである場合、おそらくネットワークから切断されています。

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

ステージをブロードキャストするには、`IVSBroadcastClient` セッションを作成してから、前述の SDK による通常のブロードキャスト手順に従います。次のように、`STAGE_PARTICIPANT_STREAMS_ADDED` を介して公開された `StageStream` のリストを使用して、ブロードキャストストリーム構成に適用できる参加者メディアストリームを取得できます。

```
// Setup client with preferred settings
const broadcastClient = getIvsBroadcastClient();

stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => {
    streams.forEach(stream => {
        const inputStream = new MediaStream([stream.mediaStreamTrack]);
        switch (stream.streamType) {
            case StreamType.VIDEO:
                broadcastClient.addVideoInputDevice(inputStream, `video-${participant.id}`, {
                    index: DESIRED_LAYER,
                    width: MAX_WIDTH,
                    height: MAX_HEIGHT
                });
                break;
            case StreamType.AUDIO:
                broadcastClient.addAudioInputDevice(inputStream, `audio-${participant.id}`);
                break;
        }
    })
})
```

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

# IVS Web Broadcast SDK の既知の問題と回避策 \$1 Real-Time Streaming
<a name="broadcast-web-known-issues"></a>

このドキュメントでは、Amazon IVS Real-Time Streaming Web Broadcast SDK を使用する際に発生する可能性のある既知の問題の一覧を示し、可能な回避策を提案します。
+ `stage.leave()` を呼び出さずにブラウザのタブを閉じたり、ブラウザを終了したりしても、ユーザーは最大 10 秒間フレームがフリーズしたり、画面が真っ暗になったりしてセッションに表示されることがあります。

  **回避策:** 該当なし。
+ セッションの開始後、Safari セッションに参加しているユーザーには、断続的に黒い画面が表示されます。

  **回避策:** ブラウザを更新して、セッションに再接続します。
+ Safari は、ネットワークを切り替えても正常に回復しません。

  **回避策:** ブラウザを更新して、セッションに再接続します。
+ 開発者コンソールは `Error: UnintentionalError at StageSocket.onClose` エラーを繰り返します。

  **回避策:** 参加者トークンごとに作成できるステージは 1 つだけです。このエラーは、`Stage` インスタンスが 1 つのデバイス上にあるか複数のデバイスにあるかに関係なく、同じ参加者トークンで複数のインスタンスが作成された場合に発生します。
+ `StageParticipantPublishState.PUBLISHED` 状態の維持に問題がある可能性があり、`StageEvents.STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED` イベントを聞くと`StageParticipantPublishState.ATTEMPTING_PUBLISH` 状態が繰り返される可能性があります。

  **回避策:** `getUserMedia` または `getDisplayMedia` を呼び出すときは、ビデオ解像度を 720p に制限してください。具体的には、幅と高さの `getUserMedia` と `getDisplayMedia` の制約値は、乗算時に 921600 (1280 x 720) を超えることはできません。
+ `stage.leave()` が呼び出されるか、リモート参加者が退出すると、ブラウザのデバッグコンソールに 404 DELETE エラーが表示されます。

  **回避策:** 該当なし。これは無害なエラーです。

## Safari での制限事項
<a name="broadcast-web-safari-limitations"></a>
+ アクセス許可のプロンプトを拒否するには、Safari でのウェブサイト設定のアクセス許可を OS レベルでリセットする必要があります。
+ Safari は、Firefox や Chrome ほど効果的にすべてのデバイスをネイティブに検出されません。例えば、OBS 仮想カメラは検知されません。

## Firefox での制限事項
<a name="broadcast-web-firefox-limitations"></a>
+ Firefox で画面を共有するには、システムのアクセス許可を有効にする必要があります。アクセス許可を有効にした後、正常に動作するために Firefox を再起動する必要があります。再起動しないと、アクセス許可がブロックされていると認識され、ブラウザで [NotFoundError](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#exceptions) 例外がスローされます。
+ `getCapabilities` メソッドがありません。これは、ユーザーがメディアトラックの解像度やアスペクト比を取得できないことを意味します。こちらの [Bugzilla のスレッド](https://bugzilla.mozilla.org/show_bug.cgi?id=1179084)を参照してください。
+ レイテンシーやチャネル数などの、いくつかの `AudioContext` プロパティがありません。これは、オーディオトラックを操作する上級ユーザーにとって問題になる可能性があります。
+ `getUserMedia` からのカメラフィードは、MacOS でのアスペクト比が 4:3 に制限されています。「[Bugzilla のスレッド 1](https://bugzilla.mozilla.org/show_bug.cgi?id=1193640)」および「[Bugzilla のスレッド 2](https://bugzilla.mozilla.org/show_bug.cgi?id=1306034)」を参照してください。
+ オーディオキャプチャが `getDisplayMedia` でサポートされていません。こちらの [Bugzilla のスレッド](https://bugzilla.mozilla.org/show_bug.cgi?id=1541425)を参照してください。
+ スクリーンキャプチャのフレームレートが最適ではありません (約 15 fps?)。こちらの [Bugzilla のスレッド](https://bugzilla.mozilla.org/show_bug.cgi?id=1703522)を参照してください。

## モバイルウェブの制限事項
<a name="broadcast-web-mobile-web-limitations"></a>
+ [getDisplayMedia](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#browser_compatibility) の画面共有はモバイルデバイスではサポートされていません。

  **回避策:** 該当なし。
+ `leave()` を呼び出さずにブラウザを閉じると、参加者が退出するまでに 15～30 秒かかります。

  **回避策**: ユーザーが正しく接続を解除するように促す UI を追加します。
+ バックグラウンドで動くアプリケーションが原因で動画の配信が停止します。

  **回避策**: パブリッシャーが一時停止しているときに UI スレートを表示します。
+ Android デバイスでカメラのミュートを解除すると、動画のフレームレートが約 5 秒間低下します。

  **回避策:** 該当なし。
+ iOS 16.0 では、ビデオフィードはローテーション時にストレッチされます。

  **回避策**: OS の既知の問題の概要を示す UI を表示します。
+ オーディオ入力デバイスを切り替えると、オーディオ出力デバイスも自動的に切り替わります。

  **回避策:** 該当なし。
+ ブラウザのバックグラウンド設定を行うと、配信ストリームが真っ暗になり、音声のみが生成されます。

  **回避策:** 該当なし。これはセキュリティ上の理由からです。

# IVS Web Broadcast SDK でのエラー処理 \$1 Real-Time Streaming
<a name="broadcast-web-error-handling"></a>

このセクションでは、エラー状態の概要、Web Broadcast SDK がエラー状態をアプリケーションに報告する方法、およびエラー発生時にアプリケーションが実行する処理について説明します。エラーは SDK によって `StageEvents.ERROR` イベントのリスナーに報告されます。

```
stage.on(StageEvents.ERROR, (error: StageError) => {
    // log or handle errors here
    console.log(`${error.code}, ${error.category}, ${error.message}`);
});
```

## ステージエラー
<a name="web-error-handling-stage-errors"></a>

StageError は、SDK が復旧できない問題に遭遇したときに報告され、通常、アプリケーションの介入やネットワークの再接続が必要になります。

報告されたそれぞれの `StageError` には、コード (または `StageErrorCode`)、メッセージ (文字列)、カテゴリ (`StageErrorCategory`) があります。それぞれが基盤となるオペレーションカテゴリに関連しています。

エラーのオペレーションカテゴリは、ステージへの接続 (`JOIN_ERROR`)、ステージへのメディアの送信 (`PUBLISH_ERROR`)、またはステージからの受信メディアストリームの受信 (`SUBSCRIBE_ERROR`) のどちらに関連しているかに基づいて決定されます。

`StageError` のコードプロパティは、特定の問題をレポートします。


| 名前 | コード | [Recommended Action] (推奨されるアクション) | 
| --- | --- | --- | 
| TOKEN\$1MALFORMED | 1 | 有効なトークンを作成し、ステージのインスタンス化を再試行してください。 | 
| TOKEN\$1EXPIRED | 2 | 有効期限が切れていないトークンを作成し、ステージのインスタンス化を再試行します。 | 
| タイムアウト | 3 | オペレーションがタイムアウトしました。ステージが存在し、トークンが有効である場合、この障害はネットワークの問題である可能性があります。この場合は、デバイスの接続が回復するまでお待ちください。 | 
| FAILED | 4 | オペレーションの試行中に致命的な状態が発生しました。エラーの詳細を確認してください。 ステージが存在し、トークンが有効である場合、この障害はネットワークの問題である可能性があります。この場合は、デバイスの接続が回復するまでお待ちください。 ネットワークの安定性に関連するほとんどの障害の場合、SDK は FAILED エラーを出力する前に最大 30 秒間内部で再試行します。 | 
| CANCELED | 5 | アプリケーションコードをチェックし、繰り返し `join`、`refreshStrategy`、または `replaceStrategy` 呼び出しがないことを確認します。これにより、完了前に繰り返しオペレーションが開始され、キャンセルされる可能性があります。 | 
| STAGE\$1AT\$1CAPACITY | 6 | このエラーは、ステージまたはアカウントのキャパシティに達していることを示します。ステージが参加者の制限に達した場合は、戦略を更新して、ステージのキャパシティが解放されたときにオペレーションを再試行してください。アカウントが同時サブスクリプションまたは同時パブリッシャーのクォータに達した場合は、[AWS Service Quotas コンソール](https://console.aws.amazon.com/servicequotas/)を使用して使用量を減らすか、クォータの引き上げをリクエストしてください。 | 
| CODEC\$1MISMATCH | 7 | コーデックはステージではサポートされていません。コーデックのサポートについては、ブラウザとプラットフォームを確認してください。IVS Real-Time Streaming の場合、ブラウザはビデオ用の H.264 コーデックとオーディオ用の Opus コーデックをサポートしている必要があります。 | 
| TOKEN\$1NOT\$1ALLOWED | 8 | トークンにはオペレーションに対するアクセス許可がありません。トークンを正しいアクセス許可で再作成し、再試行してください。 | 
| STAGE\$1DELETED | 9 | なし。削除されたステージに参加しようとすると、このエラーがトリガーされます。 | 
| PARTICIPANT\$1DISCONNECTED | 10 | なし。切断された参加者のトークンを使用して参加しようとすると、このエラーがトリガーされます。 | 

### StageError の処理の例
<a name="web-error-handling-stage-errors-example"></a>

StageError コードを使用して、エラーが期限切れのトークンによるものかどうかを判断します。

```
stage.on(StageEvents.ERROR, (error: StageError) => {
    if (error.code === StageError.TOKEN_EXPIRED) {
        // recreate the token and stage instance and re-join
    }
});
```

### 既に参加している場合のネットワークエラー
<a name="web-error-handling-stage-errors-network"></a>

デバイスのネットワーク接続が切断されると、SDK と Stages サーバーとの接続が失われる可能性があります。SDK がバックエンドサービスにアクセスできなくなるため、コンソールにエラーが表示される場合があります。https://broadcast.stats.live-video.net への POST は失敗します。

配信中/サブスクライブ中の場合は、配信/サブスクライブの試行に関連するエラーがコンソールに表示されます。

SDK は、エクスポネンシャルバックオフストラテジーを使用して内部で再接続を試みます。

**アクション**: デバイスの接続が回復するまでお待ちください。

## エラー状態
<a name="web-error-handling-errored-states"></a>

これらの状態は、アプリケーションのログ記録に使用し、特定の参加者のステージへの接続の問題を通知するメッセージをユーザーに表示することをお勧めします。

### 配信
<a name="errored-states-publish"></a>

SDK は配信が失敗したときに `ERRORED` を報告します。

```
stage.on(StageEvents.STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED, (participantInfo, state) => {
  if (state === StageParticipantPublishState.ERRORED) {
      // Log and/or display message to user
  }
});
```

### Subscribe
<a name="errored-states-subscribe"></a>

SDK はサブスクライブが失敗したときに `ERRORED` を報告します。これは、ネットワークの状態が原因で発生する可能性がありますが、ステージがサブスクライバーのキャパシティに達した場合にも発生することがあります。

```
stage.on(StageEvents.STAGE_PARTICIPANT_SUBSCRIBE_STATE_CHANGED, (participantInfo, state) => {
  if (state === StageParticipantSubscribeState.ERRORED) {
    // Log and/or display message to user
  }
});
```

# IVS Broadcast SDK: Android ガイド \$1 リアルタイムストリーミング
<a name="broadcast-android"></a>

IVS Real-Time Streaming の Android Broadcast SDK を使用して、参加者は Android 上でビデオを送受信できます。

`com.amazonaws.ivs.broadcast` パッケージは、このドキュメントで説明されているインターフェイスを実装します。SDK は、次の操作をサポートします。
+ ステージに参加する 
+ ステージ内の他の参加者にメディアを配信する
+ ステージ内の他の参加者のメディアをサブスクライブする
+ ステージに配信されたビデオとオーディオを管理および監視する
+ 各ピア接続の WebRTC 統計を取得
+ IVS 低レイテンシーストリーミング Android Broadcast SDK からのすべての操作

**Android Broadcast SDK の最新バージョン:** 1.40.0 ([リリースノート](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-android-rt)) 

**リファレンスドキュメント:** Amazon IVS Android プレイヤーで利用可能な最も重要な方法については、 [https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/android/](https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/android/) にあるリファレンスドキュメントを参照してください。

**サンプルコード:** GitHub の Android サンプルリポジトリ（[https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples)）を参照してください。

**プラットフォーム要件:** Android 9.0 以降

# IVS Android Broadcast SDK の開始方法 \$1 Real-Time Streaming
<a name="broadcast-android-getting-started"></a>

このドキュメントでは、IVS Real-Time Streaming Android Broadcast SDK の使用を開始するためのステップについて説明します。

## ライブラリのインストール
<a name="broadcast-android-install"></a>

Amazon IVS Android ブロードキャストライブラリを Android 開発環境に追加する方法は以下のようにいくつかあります。Gradle を直接使用する、Gradle バージョンカタログを使用する、または SDK を手動でインストールする。

**Gradle を直接使用する**: 次のように、ライブラリをモジュールの `build.gradle` ファイルに追加します (IVS Broadcast SDK の最新バージョンの場合)。

```
repositories {
    mavenCentral()
}
 
dependencies {
     implementation 'com.amazonaws:ivs-broadcast:1.40.0:stages@aar'
}
```

**Gradle バージョンカタログを使用する**: まず、以下をモジュールの `build.gradle` ファイルに含めます。

```
implementation(libs.ivs){
   artifact {
      classifier = "stages"
      type = "aar"
   }
}
```

次に、`libs.version.toml` ファイルに以下を含めます (IVS Broadcast SDK の最新バージョンの場合)。

```
[versions]
ivs="1.40.0"

[libraries]
ivs = {module = "com.amazonaws:ivs-broadcast", version.ref = "ivs"}
```

**SDK を手動でインストールする**: 次の場所から最新バージョンをダウンロードします。

[https://search.maven.org/artifact/com.amazonaws/ivs-broadcast](https://search.maven.org/artifact/com.amazonaws/ivs-broadcast)

必ず `-stages` が付いた `aar` をダウンロードしてください。

**また、スピーカーフォンに対する SDK コントロールも許可**します。どのインストール方法を選択しても、次のアクセス許可をマニフェストに追加して、SDK がスピーカーフォンを有効または無効にできるようにします。

```
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
```

## デバッグ情報を含む SDK の使用
<a name="broadcast-android-using-debug-symbols-rt"></a>

また、デバッグ情報を含む Android Broadcast SDK のバージョンも公開しています。このバージョンを使用すると、IVS Broadcast SDK (つまり、`libbroadcastcore.so` ) でクラッシュした場合、Firebase Crashlytics のデバッグレポート (スタックトレース) の品質を向上させることができます。これらのクラッシュを IVS SDK チームに報告すると、高品質のスタックトレースにより、問題の修正が容易になります。

このバージョンの SDK を使用するには、Gradle ビルドファイルに以下を含めます。

```
implementation "com.amazonaws:ivs-broadcast:$version:stages-unstripped@aar"
```

以下の代わりに上記の行を使用します。

```
implementation "com.amazonaws:ivs-broadcast:$version:stages@aar"
```

### Firebase Crashlytics へのシンボルのアップロード
<a name="android-debug-symbols-rt-firebase-crashlytics"></a>

Gradle ビルドファイルが Firebase Crashlytics 用に設定されていることを確認します。以下の Google の指示に従ってください。

[https://firebase.google.com/docs/crashlytics/ndk-reports](https://firebase.google.com/docs/crashlytics/ndk-reports)

`com.google.firebase:firebase-crashlytics-ndk` を依存関係として必ず含めてください。

リリース用にアプリを構築する場合、Firebase Crashlytics プラグインは情報を自動的にアップロードするはずです。情報を手動でアップロードするには、次のいずれかを実行します。

```
gradle uploadCrashlyticsSymbolFileRelease
```

```
./gradlew uploadCrashlyticsSymbolFileRelease
```

(情報が自動と手動の両方で 2 回アップロードされても問題ありません)。

### Release .apk の肥大化を防ぐ
<a name="android-debug-symbols-rt-sizing-apk"></a>

リリース `.apk` ファイルをパッケージ化する前に、Android Gradle Plugin は共有ライブラリ (IVS Broadcast SDK の `libbroadcastcore.so` ライブラリを含む) からデバッグ情報を自動的に削除しようとします。ただし、これはときに動作しないことがあります。その結果、`.apk` ファイルが肥大化し、Android Gradle プラグインから、「デバッグ情報を削除できず、そのまま `.so` ファイルをパッケージ化している」という警告メッセージが表示される可能性があります。このような場合は、以下のことを試してみます。
+ Android NDK をインストールします。すべての最新バージョンが機能します。
+ `ndkVersion <your_installed_ndk_version_number>` をアプリケーションの `build.gradle` ファイルに追加します。アプリケーション自体にネイティブコードが含まれていない場合でも、これを行います。

詳細については、「[問題レポート](https://issuetracker.google.com/issues/353554169)」を参照してください。

## 必要なアクセス許可
<a name="broadcast-android-permissions"></a>

アプリはユーザーのカメラとマイクへのアクセス許可を要求する必要があります。(これは、Amazon IVS に特有なものではなく、カメラやマイクにアクセスする必要があるアプリケーションには必須です。)

ここでは、ユーザーがすでにアクセス許可を付与しているかどうかを確認し、付与していない場合は、許可を求めます。

```
final String[] requiredPermissions =
         { Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO };

for (String permission : requiredPermissions) {
    if (ContextCompat.checkSelfPermission(this, permission) 
                != PackageManager.PERMISSION_GRANTED) {
        // If any permissions are missing we want to just request them all.
        ActivityCompat.requestPermissions(this, requiredPermissions, 0x100);
        break;
    }
}
```

ここでは、ユーザーの応答を取得します。

```
@Override
public void onRequestPermissionsResult(int requestCode, 
                                      @NonNull String[] permissions,
                                      @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode,
               permissions, grantResults);
    if (requestCode == 0x100) {
        for (int result : grantResults) {
            if (result == PackageManager.PERMISSION_DENIED) {
                return;
            }
        }
        setupBroadcastSession();
    }
}
```

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

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

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

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

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

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

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

`Stage.Strategy` インターフェースは、ホストアプリケーションがステージの望ましい状態を SDK に伝える方法を提供します。`shouldSubscribeToParticipant`、`shouldPublishFromParticipant`、`stageStreamsToPublishForParticipant` の 3 つの関数を実装する必要があります。以下で、すべて説明します。

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

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

リモート参加者がステージに参加すると、SDK はその参加者に対して希望するサブスクリプションの状態についてホストアプリケーションに問い合わせます。使用できるオプションは `NONE`、`AUDIO_ONLY`、および `AUDIO_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;
	}
}
```

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

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

```
SubscribeConfiguration subscribeConfigurationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);
```

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

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

```
@Override
public SubscribeConfiguration subscribeConfigrationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
    SubscribeConfiguration config = new SubscribeConfiguration();

    config.jitterBuffer.setMinDelay(JitterBufferConfiguration.JitterBufferDelay.MEDIUM());

    return config;
}
```

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

`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);
                
void onStreamAdaptionChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream, boolean adaption);

void onStreamLayersChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream, @NonNull List<RemoteStageStream.Layer> layers);

void onStreamLayerSelected(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream, @Nullable RemoteStageStream.Layer layer, @NonNull RemoteStageStream.LayerSelectedReason reason);
```

これらのメソッドのほとんどには、対応する `Stage` および `ParticipantInfo` が用意されています。

レンダラーから提供された情報がストラテジーの戻り値に影響することは想定されていません。たとえば、`shouldSubscribeToParticipant` の戻り値は、`onParticipantPublishStateChanged` が呼び出されても変化しない想定です。ホストアプリケーションが特定の参加者をサブスクライブする場合は、その参加者の配信状態に関係なく、目的のサブスクリプションタイプを返す必要があります。SDK は、ステージの状態に基づいて、望ましいストラテジーの状態が適切なタイミングで実行されるようにする役目を担います。

`StageRenderer` は次のステージクラスにアタッチできます。

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

配信参加者のみが `onParticipantJoined` をトリガーし、参加者が配信を停止するか、ステージ セッションを終了すると、`onParticipantLeft` がトリガーされることに注意してください。

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

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

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

サブスクライブが完了すると、レンダラーの `StageStream` 関数を介して `onStreamsAdded` オブジェクトの配列を受け取ります。`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 から参加者を削除するためのカスタムのビジネスロジックは必要ありません。

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

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

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

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

参加者がビデオまたはオーディオストリームのミュート状態を変更すると、変更されたストリームのリストとともにレンダラー `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 統計を取得する
<a name="android-publish-subscribe-webrtc-stats"></a>

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

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

`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());
	}
}
```

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

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

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

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

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

```
val surfaceSource: SurfaceSource = imageStream.device as SurfaceSource
val message = "hello world"
val messageBytes = message.toByteArray(StandardCharsets.UTF_8)

try {
    surfaceSource.embedMessage(messageBytes, 0)
} catch (e: BroadcastException) {
    Log.e("EmbedMessage", "Failed to embed message: ${e.message}")
}
```

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

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

```
try {
    surfaceSource.embedMessage(messageBytes, 5)
    // repeatCount: 0-30, receiving clients should handle duplicates
} catch (e: BroadcastException) {
    Log.e("EmbedMessage", "Failed to embed message: ${e.message}")
}
```

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

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

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

補足拡張情報 (SEI) NAL ユニットは、フレーム整列メタデータを動画と一緒に保存するために使用されます。パブリッシャーの `ImageDevice` から送信される `ImageDeviceFrame` オブジェクトの `embeddedMessages` プロパティを調べることにより、サブスクライブしているクライアントは H.264 ビデオを配信しているパブリッシャーから SEI ペイロードを読み取ることができます。これを行うには、次の例で示すように、パブリッシャーの `ImageDevice` を取得し、`setOnFrameCallback` に提供されるコールバックを介して各フレームを確認します。

```
// in a StageRenderer’s onStreamsAdded function, after acquiring the new ImageStream

val imageDevice = imageStream.device as ImageDevice
imageDevice.setOnFrameCallback(object : ImageDevice.FrameCallback {
	override fun onFrame(frame: ImageDeviceFrame) {
    		for (message in frame.embeddedMessages) {
        		if (message is UserDataUnregisteredSeiMessage) {
            		val seiMessageBytes = message.data
            		val seiMessageUUID = message.uuid
           	 
            		// interpret the message's data based on the UUID
        		}
    		}
	}
})
```

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

アプリがバックグラウンドに入ると、配信を停止するか、他のリモート参加者の音声のみをサブスクライブすることができます。そのためには、`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();
}
```

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

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

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

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

```
// Enable Simulcast
StageVideoConfiguration config = new StageVideoConfiguration();
config.simulcast.setEnabled(true);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

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

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

```
// Enable Simulcast
StageVideoConfiguration config = new StageVideoConfiguration();
config.simulcast.setEnabled(true);

List<StageVideoConfiguration.Simulcast.Layer> simulcastLayers = new ArrayList<>();
simulcastLayers.add(StagePresets.SimulcastLocalLayer.DEFAULT_720);
simulcastLayers.add(StagePresets.SimulcastLocalLayer.DEFAULT_180);

config.simulcast.setLayers(simulcastLayers);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

または、最大で 3 つのレイヤー用に独自のカスタムレイヤー設定を作成することもできます。空のアレイを指定するか、値を指定しない場合、上記のデフォルトが使用されます。レイヤーは、次の必須プロパティセッターで説明されています。
+ `setSize: Vec2;`
+ `setMaxBitrate: integer;`
+ `setMinBitrate: integer;`
+ `setTargetFramerate: integer;`

プリセットから、個々のプロパティを上書きするか、まったく新しい設定を作成できます。

```
// Enable Simulcast
StageVideoConfiguration config = new StageVideoConfiguration();
config.simulcast.setEnabled(true);

List<StageVideoConfiguration.Simulcast.Layer> simulcastLayers = new ArrayList<>();

// Configure high quality layer with custom framerate
StageVideoConfiguration.Simulcast.Layer customHiLayer = StagePresets.SimulcastLocalLayer.DEFAULT_720;
customHiLayer.setTargetFramerate(15);

// Add layers to the list
simulcastLayers.add(customHiLayer);
simulcastLayers.add(StagePresets.SimulcastLocalLayer.DEFAULT_180);

config.simulcast.setLayers(simulcastLayers);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

個々のレイヤーを設定するときにトリガーできる最大値、制限、エラーについては、SDK リファレンスドキュメントを参照してください。

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

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

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

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

`subscribeConfigurationForParticipant` 戦略を使用すると、サブスクライバーとして受信する初期レイヤーを選択できます。

```
@Override
public SubscribeConfiguration subscribeConfigrationForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
    SubscribeConfiguration config = new SubscribeConfiguration();

    config.simulcast.setInitialLayerPreference(SubscribeSimulcastConfiguration.InitialLayerPreference.LOWEST_QUALITY);

    return config;
}
```

デフォルトでは、サブスクライバーは常に最初に最低品質のレイヤーが送信されます。これにより、徐々に最高品質のレイヤーにまで拡大します。エンドユーザーの帯域幅の消費量が最適化され、ビデオ再生に最適な時間が実現されるため、より貧弱なネットワーク上のユーザーに対して初期ビデオフリーズが軽減されます。

これらのオプションは `InitialLayerPreference` で利用できます。
+ `LOWEST_QUALITY` — サーバーは、最初に最低品質のビデオレイヤーを配信します。帯域幅の消費とメディアの時間が最適化されます。品質はビデオのサイズ、ビットレート、フレームレートの組み合わせとして定義されます。例えば、720p ビデオは 1080p ビデオよりも品質が低くなります。
+ `HIGHEST_QUALITY` — サーバーは、最初に最高品質のビデオレイヤーを配信します。品質が最適化されますが、メディアの時間が長くなる場合があります。品質はビデオのサイズ、ビットレート、フレームレートの組み合わせとして定義されます。例えば、1080p ビデオは 720p ビデオよりも高品質です。

**注:** 初期レイヤー設定 (`setInitialLayerPreference` の呼び出し) を反映させるには、再サブスクライブが必要です。これらの更新はアクティブなサブスクリプションに適用されないためです。

### オプション 2: ストリームに優先されるレイヤー
<a name="android-layered-encoding-simulcast-preferred-layer"></a>

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

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

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

```
@Nullable
@Override
public RemoteStageStream.Layer preferredLayerForStream(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream) {
    return stream.getLowestQualityLayer();
}
```

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

```
@Nullable
@Override
public RemoteStageStream.Layer preferredLayerForStream(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream) {
    if (appState.isAutoMode) {
        return null;
    } else {
        return appState.layerChoice;
    }
}
```

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

`RemoteStageStream` には、レイヤーの選択について決定し、対応する選択をエンドユーザーに表示するために使用できるいくつかのヘルパーがあります。
+ **レイヤーイベント** — `StageRenderer` に加え、`RemoteStageStream.Listener` にはレイヤーおよびサイマルキャストの適応変更を伝えるイベントがあります。
  + `void onAdaptionChanged(boolean adaption)`
  + `void onLayersChanged(@NonNull List<Layer> layers)`
  + `void onLayerSelected(@Nullable Layer layer, @NonNull LayerSelectedReason reason)`
+ **レイヤーメソッド** — `RemoteStageStream` には、ストリームおよび提示されるレイヤーに関する情報を取得するために使用できるいくつかのヘルパーメソッドがあります。これらのメソッドは、`preferredLayerForStream` 戦略で提供されるリモートストリームに加え、`StageRenderer.onStreamsAdded` を介して配信されるリモートストリームで利用できます。
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`
  + `stream.getLayersWithConstraints`

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

## ビデオ設定の制限
<a name="android-publish-subscribe-video-limits"></a>

SDK は、`StageVideoConfiguration.setSize(BroadcastConfiguration.Vec2 size)` を使用したポートレートモードまたはランドスケープモードの強制をサポートしていません。縦の向きでは、小さい方の寸法が幅として使用され、横の向きでは高さとして使用されます。つまり、次の 2 つの `setSize` への呼び出しが動画の設定に同じ効果をもたらすということです。

```
StageVideo Configuration config = new StageVideo Configuration();

config.setSize(BroadcastConfiguration.Vec2(720f, 1280f);
config.setSize(BroadcastConfiguration.Vec2(1280f, 720f);
```

## ネットワーク問題の処理
<a name="android-publish-subscribe-network-issues"></a>

ローカルデバイスのネットワーク接続が失われると、SDK はユーザーアクションなしで内部で再接続を試みます。場合によっては、SDK が正常に動作せず、ユーザーアクションが必要なる可能性があります。ネットワーク接続の切断に関連する主なエラーは 2 つあります。
+ エラーコード 1400、メッセージ:「不明なネットワークエラーにより PeerConnection が失われました」
+ エラーコード 1300、メッセージ:「再試行の試行回数制限に達しました」

最初のエラーを受け取って 2 番目のエラーを受け取らない場合、SDK はまだステージに接続されており、自動的に接続を再確立しようとします。安全対策として、ストラテジーメソッドの戻り値を変更せずに `refreshStrategy` を呼び出すと、手動で再接続を試みることができます。

2 番目のエラーを受け取った場合、SDK の再接続は失敗し、ローカルデバイスはステージに接続されていません。この場合は、ネットワーク接続が再確立された後に `join` を呼び出してステージに再び参加してみてください。

一般に、ステージに正常に参加した後にエラーが発生した場合は、SDK が接続を再確立できなかったことを示します。新しい `Stage` オブジェクトを作成し、ネットワークの状態が向上したら参加してみます。

## Bluetooth マイクの使用
<a name="android-publish-subscribe-bluetooth-microphones"></a>

Bluetooth マイクデバイスを使用して配信するには、Bluetooth SCO 接続を開始する必要があります。

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

# IVS Android Broadcast SDK の既知の問題と回避策 \$1 Real-Time Streaming
<a name="broadcast-android-known-issues"></a>

このドキュメントでは、Amazon IVS Real-Time Streaming Android Broadcast SDK を使用する際に発生する可能性のある既知の問題の一覧を示し、可能な回避策を提案します。
+ Android デバイスがスリープ状態になってから起動すると、プレビューがフリーズ状態になる場合があります。

  **回避策:** 新しい `Stage` を作成して使用します。
+ 参加者が別の参加者が使用しているトークンを使用して参加すると、最初の接続は特別なエラーなしで切断されます。

  **回避策:** 該当なし。
+ まれにパブリッシャーが配信中でも、サブスクライバーが受け取る配信状態が `inactive` であるという問題が発生します。

  **回避策:**セッションを終了してから再度参加してみてください。問題が解決しない場合は、パブリッシャー用に新しいトークンを作成してください。
+ ステージセッション中、通常は通話時間が長くなると、まれにオーディオディストーションの問題が断続的に発生することがあります。

  **回避策:** オーディオディストーションの問題が起きる参加者は、セッションを終了してから再度参加するか、音声を配信停止にしてから再配信して問題を解決できます。
+ ステージに配信する場合、外部マイクはサポートされていません。

  **回避策:** USB で接続された外部マイクをステージへの配信に使用しないでください。
+ `createSystemCaptureSources` を使用して画面共有を使用したステージへの配信はサポートされていません。

  **回避策:** カスタムの画像入力ソースとカスタムオーディオ入力ソースを使用して、システムキャプチャを手動で管理します。
+ `ImagePreviewView` が親から削除された場合 (例えば、`removeView()` が親側で呼び出された場合)、`ImagePreviewView` はすぐにリリースされます。`ImagePreviewView` を別の親ビューに追加しても、フレームは表示されません。

  **回避策:** `getPreview` を使用して別のプレビューをリクエストします。
+ Android 12 搭載の Samsung Galaxy S22/\$1 でステージに参加すると、1401 エラーが発生し、ローカルデバイスがステージに参加できない、または参加しても音声が聞こえない場合があります。

  **回避策:** Android 13 にアップグレードします。
+ Android 13 搭載の Nokia X20 でステージに参加すると、カメラが開かず、例外が発生する場合があります。

  **回避策:** 該当なし。
+ MediaTek Helio チップセットを搭載したデバイスでは、リモート参加者のビデオが正しくレンダリングされない場合があります。

  **回避策:** 該当なし。
+ 一部のデバイスでは、デバイスの OS が SDK で選択したものとは異なるマイクを選択する場合があります。これは、`VOICE_COMMUNICATION` オーディオルートの定義方法はデバイスメーカーによって異なるため、Amazon IVS Broadcast SDK では制御できないためです。

  **回避策:** 該当なし。
+ 一部の Android ビデオエンコーダーは、176x176 未満のビデオサイズで設定することはできません。サイズを小さく設定するとエラーが発生し、ストリーミングが停止します。

  **回避策：** ビデオサイズを 176x176 未満に設定しないでください。

# IVS Android Broadcast SDK でのエラー処理 \$1 Real-Time Streaming
<a name="broadcast-android-error-handling"></a>

このセクションでは、エラー状態の概要、IVS Real-Time Streaming Android Broadcast SDK がエラー状態をアプリケーションに報告する方法、およびエラー発生時にアプリケーションが実行する処理について説明します。

## 致命的なエラーと致命的でないエラー
<a name="broadcast-android-fatal-vs-nonfatal-errors"></a>

エラーオブジェクトには、`BroadcastException` の「is fatal」ブール値フィールドが含まれています。

一般的に、致命的なエラーは Stages サーバーへの接続に関連しています (接続を確立できないか、接続が失われて回復できないかのいずれか)。アプリケーションはステージを再作成し、(場合によっては新しいトークンを使って、あるいはデバイスの接続が回復したときに) 再参加する必要があります。

致命的でないエラーは通常、配信/サブスクライブ状態に関連しており、配信/サブスクライブ操作を再試行する SDK によって処理されます。

次のプロパティを確認できます。

```
try {
  stage.join(...)
} catch (e: BroadcastException) {
  If (e.isFatal) { 
    // the error is fatal
```

## 参加エラー
<a name="broadcast-android-stage-join-errors"></a>

### 不正な形式のトークン
<a name="broadcast-android-stage-join-errors-malformed-token"></a>

これは、ステージトークンの形式が不正な場合に発生します。

SDK は、`stage.join` への呼び出しで Java 例外 (error code = 1000 および fatal = true) をスローします。

**アクション**: 有効なトークンを作成し、参加を再試行してください。

### 期限切れのトークン
<a name="broadcast-android-stage-join-errors-expired-token"></a>

これは、ステージトークンの有効期限が切れた場合に発生します。

SDK は、`stage.join` への呼び出しで Java 例外 (error code = 1001 および fatal = true) をスローします。

**アクション**: 新しいトークンを作成し、参加を再試行してください。

### 無効または取り消されたトークン
<a name="broadcast-android-stage-join-errors-invalid-token"></a>

これは、ステージトークンの形式が不正でないにもかかわらず、Stages サーバーによって拒否された場合に発生します。これは、アプリケーションに搭載されているステージレンダラーを介して非同期的に報告されます。

SDK が `onConnectionStateChanged` を呼び出すと、error code = 1026 および fatal = true の例外が発生します。

**アクション**: 有効なトークンを作成し、参加を再試行してください。

### 初期参加におけるネットワークエラー
<a name="broadcast-android-stage-join-errors-network-initial-join"></a>

これは、SDK が Stages サーバーにアクセスしたにもかかわらず、接続を確立できなかった場合に発生します。これは、アプリケーションに搭載されているステージレンダラーを介して非同期的に報告されます。

SDK が `onConnectionStateChanged` を呼び出すと、error code = 1300 および fatal = true の例外が発生します。

**アクション**: デバイスの接続が回復するのを待ってから、参加を再試行してください。

### 既に参加している場合のネットワークエラー
<a name="broadcast-android-stage-join-errors-network-already-joined"></a>

デバイスのネットワーク接続が切断されると、SDK と Stages サーバーとの接続が失われる可能性があります。これは、アプリケーションに搭載されているステージレンダラーを介して非同期的に報告されます。

SDK が `onConnectionStateChanged` を呼び出すと、error code = 1300 および fatal = true の例外が発生します。

**アクション**: デバイスの接続が回復するのを待ってから、参加を再試行してください。

## 配信/サブスクライブエラー
<a name="broadcast-android-publish-subscribe-errors"></a>

### 初期
<a name="broadcast-android-publish-subscribe-errors-initial"></a>

次のようなエラーがあります。
+ MultihostSessionOfferCreationFailPublish (1020)
+ MultihostSessionOfferCreationFailSubscribe (1021)
+ MultihostSessionNoIceCandidates (1022)
+ MultihostSessionStageAtCapacity (1024)
+ SignallingSessionCannotRead (1201)
+ SignallingSessionCannotSend (1202)
+ SignallingSessionBadResponse (1203)

これらは、アプリケーションに搭載されているステージレンダラーを介して非同期的に報告されます。

SDK は限られた回数だけ操作を再試行します。再試行中の配信/サブスクライブ状態は `ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE` です。再試行が成功すると、状態は `PUBLISHED`/`SUBSCRIBED` に変更されます。

SDK が `onError` を呼び出すと、関連するエラーコードおよび fatal = true が発生します。

**アクション**: SDK は自動的に再試行するため、アクションは不要です。オプションで、アプリケーションはストラテジーを更新して再試行回数を増やすことができます。

### 確立済みにもかかわらず失敗する
<a name="broadcast-android-publish-subscribe-errors-established"></a>

配信またはサブスクライブは、確立された後に失敗することがあります (ネットワークエラーが原因である可能性が高い)。「ネットワークエラーによりピア接続が失われた」ことを示すエラーコードは 1400 です。

これは、アプリケーションに搭載されているステージレンダラーを介して非同期的に報告されます。

SDK は、配信/サブスクライブ操作を再試行します。再試行中の配信/サブスクライブ状態は `ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE` です。再試行が成功すると、状態は `PUBLISHED`/`SUBSCRIBED` に変更されます。

SDK が `onError` を呼び出すと、error code = 1400 および fatal = false が発生します。

**アクション**: SDK は自動的に再試行するため、アクションは不要です。オプションで、アプリケーションはストラテジーを更新して再試行回数を増やすことができます。接続が完全に失われた場合、Stages への接続も失敗する可能性があります。

# IVS Broadcast SDK: iOS ガイド \$1 リアルタイムストリーミング
<a name="broadcast-ios"></a>

IVS Real-Time Streaming iOS Broadcast SDK を使用すると、参加者は iOS でビデオを送受信できます。

`AmazonIVSBroadcast` モジュールは、このドキュメントで説明されているインターフェイスを実装します。以下のオペレーションがサポートされています。
+ ステージに参加する 
+ ステージ内の他の参加者にメディアを配信する
+ ステージ内の他の参加者のメディアをサブスクライブする
+ ステージに配信されたビデオとオーディオを管理および監視する
+ 各ピア接続の WebRTC 統計を取得
+ IVS 低レイテンシーストリーミング iOS Broadcast SDK からのすべての操作

**iOS Broadcast SDK の最新バージョン:** 1.40.0 ([リリースノート](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-ios-rt)) 

**リファレンスドキュメント:** Amazon IVS iOS プレイヤーで利用できる最も重要な方法については、[https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/ios/](https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/ios/) にあるリファレンスドキュメントを参照してください。

**サンプルコード:** GitHub の iOS サンプルリポジトリ（ [https://github.com/aws-samples/amazon-ivs-real-time-streaming-ios-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-ios-samples)）を参照してください。

**プラットフォームの要件:** iOS 14 以降

# IVS iOS Broadcast SDK の開始方法 \$1 Real-Time Streaming
<a name="broadcast-ios-getting-started"></a>

このドキュメントでは、IVS Real-Time Streaming iOS Broadcast SDK の使用を開始するためのステップについて説明します。

## ライブラリのインストール
<a name="broadcast-ios-install"></a>

Swift Package Manager を介して Broadcast SDK を統合することをお勧めします。(代わりに、フレームワークを手動でプロジェクトに追加することも可能です)。

### 推奨: Broadcast SDK の統合 (Swift Package Manager)
<a name="broadcast-ios-install-swift"></a>

1. Package.swift ファイルを「[https://broadcast.live-video.net/1.40.0/Package.swift](https://broadcast.live-video.net/1.40.0/Package.swift)」からダウンロードしてください。

1. プロジェクトで AmazonIVSBroadcast という名前の新しいディレクトリを作成し、バージョン管理に追加します。

1. ダウンロードした Package.swift ファイルを新しいディレクトリに配置します。

1. Xcode で **[ファイル] > [パッケージの依存関係を追加]** に移動し、**[ローカルに追加]** を選択します。

1. 作成した AmazonIVSBroadcast ディレクトリに移動して選択たら、**パッケージの追加**を選択します。

1. **AmazonIVSBroadcast のパッケージ製品の選択**を求められたら、「**ターゲットに追加**」セクションでアプリケーションターゲットを設定し、**パッケージ製品**として **[AmazonIVSBroadcastStages]** を選択します。

1. **パッケージの追加**を選択します。

**重要**: IVS リアルタイムストリーミングの Broadcast SDK には、IVS 低レイテンシーストリーミング Broadcast SDK のすべての機能が含まれます。両方の SDK を同じプロジェクトに統合することはできません。

### 代替方法: フレームワークを手動でインストールする
<a name="broadcast-ios-install-manual"></a>

1. 次のリンクから最新バージョンをダウンロードします。[ https://broadcast.live-video.net/1.40.0/AmazonIVSBroadcast-Stages.xcframework.zip](https://broadcast.live-video.net/1.40.0/AmazonIVSBroadcast-Stages.xcframework.zip).

1. アーカイブの内容を抽出します。`AmazonIVSBroadcast.xcframework` には、デバイスとシミュレータの両方の SDK が含まれています。

1. アプリケーションターゲットの [**全般**] タブの、[**Frameworks, Libraries, and Embedded Content (フレームワーク、ライブラリ、埋め込みコンテンツ)**] のセクションに `AmazonIVSBroadcast.xcframework` をドラッグして埋め込みます。  
![\[アプリケーションターゲットの [全般] タブの[Frameworks, Libraries, and Embedded Content (フレームワーク、ライブラリ、埋め込みコンテンツ)] セクション。\]](http://docs.aws.amazon.com/ja_jp/ivs/latest/RealTimeUserGuide/images/iOS_Broadcast_SDK_Guide_xcframework.png)

## 必要なアクセス許可
<a name="broadcast-ios-permissions"></a>

アプリはユーザーのカメラとマイクへのアクセス許可を要求する必要があります。(これは、Amazon IVS に特有なものではなく、カメラやマイクにアクセスする必要があるアプリケーションには必須です。)

ここでは、ユーザーがすでにアクセス許可を付与しているかどうかを確認し、付与していない場合は、許可を求めます。

```
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized: // permission already granted.
case .notDetermined:
   AVCaptureDevice.requestAccess(for: .video) { granted in
       // permission granted based on granted bool.
   }
case .denied, .restricted: // permission denied.
@unknown default: // permissions unknown.
}
```

カメラやマイクにアクセスするには、`.video` と `.audio` の両方のメディアタイプに対してこれを行う必要があります。

また、`NSCameraUsageDescription` と `NSMicrophoneUsageDescription` のエントリを `Info.plist` に追加する必要があります。これを行わずにアクセス許可をリクエストすると、アプリがクラッシュします。

## アプリケーションアイドルタイマーの無効化
<a name="broadcast-ios-disable-idle-timer"></a>

これはオプションですが推奨されます。Broadcast SDK の使用中にデバイスがスリープ状態になり、ブロードキャストが中断されるのを防ぎます。

```
override func viewDidAppear(_ animated: Bool) {
   super.viewDidAppear(animated)
   UIApplication.shared.isIdleTimerDisabled = true
}
override func viewDidDisappear(_ animated: Bool) {
   super.viewDidDisappear(animated)
   UIApplication.shared.isIdleTimerDisabled = false
}
```

# IVS iOS Broadcast SDK での配信とサブスクライブ \$1 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)」を参照してください。

# iOS がカメラの解像度とフレームレートを選択する方法
<a name="ios-publish-subscribe-resolution-framerate"></a>

Broadcast SDK が管理するカメラは、熱の発生とエネルギー消費量を最小限に抑えるために、その解像度とフレームレート (1 秒あたりフレーム数、または FPS) を最適化します。このセクションでは、ホストアプリケーションがそのユースケースに合わせて最適化できるように、解像度とフレームレートがどのように選択されるかについて説明します。

`IVSCamera` を使用して `IVSLocalStageStream` を作成するときは、フレームレート `IVSLocalStageStreamVideoConfiguration.targetFramerate`、および解像度 `IVSLocalStageStreamVideoConfiguration.size` 向けにカメラが最適化されます。`IVSLocalStageStream.setConfiguration` を呼び出すと、カメラが新しい値で更新されます。

## カメラプレビュー
<a name="resolution-framerate-camera-preview"></a>

`IVSCamera` を `IVSBroadcastSession` または `IVSStage` にアタッチせずにそのプレビューを作成すると、デフォルト解像度の 1080p とデフォルトフレームレートの 60 fps が使用されます。

## ステージのブロードキャスト
<a name="resolution-framerate-broadcast-stage"></a>

`IVSBroadcastSession` を使用して `IVSStage` をブロードキャストする場合、SDK は、両方のセッションの基準を満たす解像度とフレームレートでカメラを最適化しようとします。

例えば、ブロードキャストがフレームレート 15 FPS、解像度 1080p に設定されているときに、ステージのフレームレートが 30 FPS、解像度が 720p になっているという場合、SDK はフレームレートが 30 FPS、解像度が 1080p のカメラ設定を選択します。`IVSBroadcastSession` はカメラからのフレームを 1 個おきにドロップし、`IVSStage` は 1080p の画像を 720p に縮小します。

ホストアプリケーションが、カメラで `IVSBroadcastSession` と `IVSStage` の両方を一緒に使用する予定の場合は、それぞれの設定の `targetFramerate` および `size` プロパティを一致させることが推奨されます。一致しないプロパティは、動画のキャプチャ中にカメラがそれ自体を再設定する原因となる場合があり、これによって動画サンプルの配信がわずかに遅れます。

同一の値を設定しておくことがホストアプリケーションのユースケースに適さない場合は、最初から高品質のカメラを作成しておくことで、低品質のセッションが追加されたときにカメラがそれ自体を再設定することを防ぎます。例えば、1080p および 30 FPS でブロードキャストしてから、後ほど 720p および 30 FPS に設定されたステージに参加する場合でも、カメラはそれ自体を再設定せず、動画も中断されることなく継続します。これは、720p が 1080p 以下で、30 FPS が 30 FPS以下であるためです。

## 任意のフレームレート、解像度、およびアスペクト比
<a name="resolution-framerate-arbitrary"></a>

ほとんどのカメラハードウェアは、30 FPS で 720p、60 FPS で 1080p などの一般的なフォーマットと完全に一致します。ただし、すべてのフォーマットに完全に一致することは不可能です。Broadcast SDK は、次のルール (優先度順) に基づいてカメラ設定を選択します。

1. 解像度の幅と高さが目的の解像度以上でも、この制約内では幅と高さが可能な限り小さい。

1. フレームレートが目的のフレームレート以上でも、この制約内ではフレームレートが可能な限り低い。

1. アスペクト比は目的のアスペクト比に一致します。

1. 一致するフォーマットが複数ある場合は、視野角が最も広いフォーマットが使用されます。

ここでは、以下の 2 つの例を示します。
+ ホストアプリケーションが、120 FPS、4K でブロードキャストしようとしています。選択されたカメラは、60 FPS で 4k、または 120 FPS で 1080p しかサポートしていません。フレームレートルールよりも解像度ルールが優先されるため、選択されるフォーマットは 60 FPS で 4K になります。
+ 1910 x 1070 という規格外の解像度がリクエストされました。カメラは 1920 x 1080 を使用します。*ご注意ください: 1921 x 1080 のような解像度を選択すると、カメラが次に利用可能な解像度 (2592 x 1944 など) にスケールアップするため、CPU およびメモリ帯域幅ペナルティが発生します*。

## Android の場合
<a name="resolution-framerate-android"></a>

Android は iOS のように解像度やフレームレートを臨機応変に調整しないため、これは Android broadcast SDK には影響しません。

# IVS iOS Broadcast SDK の既知の問題と回避策 \$1 Real-Time Streaming
<a name="broadcast-ios-known-issues"></a>

このドキュメントでは、Amazon IVS Real-Time Streaming iOS Broadcast SDK を使用する際に発生する可能性のある既知の問題の一覧を示し、可能な回避策を提案します。
+ Bluetooth オーディオルートの変更は予測できない場合があります。セッション中に新しいデバイスを接続すると、iOS が入力ルートを自動的に変更する場合があります。また、同時に接続されている複数の Bluetooth ヘッドセットから選択することはできません。これは通常のブロードキャストとステージセッションの両方で起こります。

  **回避策:** Bluetooth ヘッドセットを使用する場合は、ブロードキャストまたはステージを開始する前に接続し、セッション全体を通して接続したままにします。
+ 参加者が iPhone 14、iPhone 14 Plus、iPhone 14 Pro、または iPhone 14 Pro Max を使用していると、他の参加者にオーディオエコーの問題が発生する可能性があります。

  **回避策:** 該当するデバイスを使用している参加者は、ヘッドフォンを使用して他の参加者のエコーの問題を防ぐことができます。
+ 参加者が別の参加者が使用しているトークンを使用して参加すると、最初の接続は特別なエラーなしで切断されます。

  **回避策:** 該当なし。
+ まれにパブリッシャーが配信中でも、サブスクライバーが受け取る配信状態が `inactive` であるという問題が発生します。

  **回避策:**セッションを終了してから再度参加してみてください。問題が解決しない場合は、パブリッシャー用に新しいトークンを作成してください。
+ 参加者が配信またはサブスクライブしている際、ネットワークが安定している場合でも、ネットワークの問題により接続が切断されたことを示すコード 1400 のエラーが表示されることがあります。

  **回避策:** 再配信/再サブスクライブを試してください。
+ ステージセッション中、通常は通話時間が長くなると、まれにオーディオディストーションの問題が断続的に発生することがあります。

  **回避策:** オーディオディストーションの問題が起きる参加者は、セッションを終了してから再度参加するか、音声を配信停止にしてから再配信して問題を解決できます。

# IVS iOS Broadcast SDK でのエラー処理 \$1 Real-Time Streaming
<a name="broadcast-ios-error-handling"></a>

このセクションでは、エラー状態の概要、IVS Real-Time Streaming iOS Broadcast SDK がエラー状態をアプリケーションに報告する方法、およびエラー発生時にアプリケーションが実行する処理について説明します。

## 致命的なエラーと致命的でないエラー
<a name="broadcast-ios-fatal-vs-nonfatal-errors"></a>

エラーオブジェクトには、「is fatal」ブール値が含まれています。これはブール値を含む `IVSBroadcastErrorIsFatalKey` 内のディクショナリエントリです。

一般的に、致命的なエラーは Stages サーバーへの接続に関連しています (接続を確立できないか、接続が失われて回復できないかのいずれか)。アプリケーションはステージを再作成し、(場合によっては新しいトークンを使って、あるいはデバイスの接続が回復したときに) 再参加する必要があります。

致命的でないエラーは通常、配信/サブスクライブ状態に関連しており、配信/サブスクライブ操作を再試行する SDK によって処理されます。

次のプロパティを確認できます。

```
let nsError = error as NSError
if nsError.userInfo[IVSBroadcastErrorIsFatalKey] as? Bool == true {
  // the error is fatal
}
```

## 参加エラー
<a name="broadcast-ios-stage-join-errors"></a>

### 不正な形式のトークン
<a name="broadcast-ios-stage-join-errors-malformed-token"></a>

これは、ステージトークンの形式が不正な場合に発生します。

SDK は、Swift 例外 (error code = 1000 および IVSBroadcastErrorIsFatalKey = YES) をスローします。

**アクション**: 有効なトークンを作成し、参加を再試行してください。

### 期限切れのトークン
<a name="broadcast-ios-stage-join-errors-expired-token"></a>

これは、ステージトークンの有効期限が切れた場合に発生します。

SDK は、Swift 例外 (error code = 1001 および IVSBroadcastErrorIsFatalKey = YES) をスローします。

**アクション**: 新しいトークンを作成し、参加を再試行してください。

### 無効または取り消されたトークン
<a name="broadcast-ios-stage-join-errors-invalid-token"></a>

これは、ステージトークンの形式が不正でないにもかかわらず、Stages サーバーによって拒否された場合に発生します。これは、アプリケーションに搭載されているステージレンダラーを介して非同期的に報告されます。

SDK が `stage(didChange connectionState, withError error)` を呼び出すと、error code = 1026 および IVSBroadcastErrorIsFatalKey = YES が発生します。

**アクション**: 有効なトークンを作成し、参加を再試行してください。

### 初期参加におけるネットワークエラー
<a name="broadcast-ios-stage-join-errors-network-initial-join"></a>

これは、SDK が Stages サーバーにアクセスしたにもかかわらず、接続を確立できなかった場合に発生します。これは、アプリケーションに搭載されているステージレンダラーを介して非同期的に報告されます。

SDK が `stage(didChange connectionState, withError error)` を呼び出すと、error code = 1300 および IVSBroadcastErrorIsFatalKey = YES が発生します。

**アクション**: デバイスの接続が回復するのを待ってから、参加を再試行してください。

### 既に参加している場合のネットワークエラー
<a name="broadcast-ios-stage-join-errors-network-already-joined"></a>

デバイスのネットワーク接続が切断されると、SDK と Stages サーバーとの接続が失われる可能性があります。これは、アプリケーションに搭載されているステージレンダラーを介して非同期的に報告されます。

SDK が `stage(didChange connectionState, withError error)` を呼び出すと、error code = 1300 および IVSBroadcastErrorIsFatalKey 値= YES が発生します。

**アクション**: デバイスの接続が回復するのを待ってから、参加を再試行してください。

## 配信/サブスクライブエラー
<a name="broadcast-ios-publish-subscribe-errors"></a>

### 初期
<a name="broadcast-ios-publish-subscribe-errors-initial"></a>

次のようなエラーがあります。
+ MultihostSessionOfferCreationFailPublish (1020)
+ MultihostSessionOfferCreationFailSubscribe (1021)
+ MultihostSessionNoIceCandidates (1022)
+ MultihostSessionStageAtCapacity (1024)
+ SignallingSessionCannotRead (1201)
+ SignallingSessionCannotSend (1202)
+ SignallingSessionBadResponse (1203)

これらは、アプリケーションに搭載されているステージレンダラーを介して非同期的に報告されます。

SDK は限られた回数だけ操作を再試行します。再試行中の配信/サブスクライブ状態は `ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE` です。再試行が成功すると、状態は `PUBLISHED`/`SUBSCRIBED` に変更されます。

SDK は `IVSErrorDelegate:didEmitError` を呼び出し、関連するエラーコードおよび `IVSBroadcastErrorIsFatalKey == NO` を出力します。

**アクション**: SDK は自動的に再試行するため、アクションは不要です。オプションで、アプリケーションはストラテジーを更新して再試行回数を増やすことができます。

### 確立済みにもかかわらず失敗する
<a name="broadcast-ios-publish-subscribe-errors-established"></a>

配信またはサブスクライブは、確立された後に失敗することがあります (ネットワークエラーが原因である可能性が高い)。「ネットワークエラーによりピア接続が失われた」ことを示すエラーコードは 1400 です。

これは、アプリケーションに搭載されているステージレンダラーを介して非同期的に報告されます。

SDK は、配信/サブスクライブ操作を再試行します。再試行中の配信/サブスクライブ状態は `ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE` です。再試行が成功すると、状態は `PUBLISHED`/`SUBSCRIBED` に変更されます。

SDK が `didEmitError` を呼び出すと、error code = 1400 および IVSBroadcastErrorIsFatalKey = NO が発生します。

**アクション**: SDK は自動的に再試行するため、アクションは不要です。オプションで、アプリケーションはストラテジーを更新して再試行回数を増やすことができます。接続が完全に失われた場合、Stages への接続も失敗する可能性があります。

# IVS Broadcast SDK: 混合デバイス
<a name="broadcast-mixed-devices"></a>

混合デバイスは、複数の入力ソースを取得して単一の出力を生成するオーディオおよびビデオデバイスです。混合デバイスは、複数の画面上 (ビデオ) の要素およびオーディオトラックを定義および管理できる強力な機能です。カメラ、マイク、スクリーンキャプチャ、アプリで生成されたオーディオとビデオなど、複数のソースからのビデオとオーディオを組み合わせることができます。移行機能を使用して IVS にストリーミングするビデオ周辺にこれらのソースを移動させて、ストリーミングの途中でソースに追加およびソースを削除することができます。

混合デバイスは画像およびオーディオの形式で存在します。混合イメージデバイスを作成するには、次のものを呼び出します。

Android の`DeviceDiscovery.createMixedImageDevice()` 

iOS の `IVSDeviceDiscovery.createMixedImageDevice()`

返されたデバイスは、他のデバイスと同様に `BroadcastSession` (低レイテンシーストリーミング) または `Stage` (リアルタイムストリーミング) にアタッチできます。

## 用語
<a name="broadcast-mixed-devices-terminology"></a>

![\[IVS Broadcast 混合デバイスの用語。\]](http://docs.aws.amazon.com/ja_jp/ivs/latest/RealTimeUserGuide/images/Broadcast_SDK_Mixer_Glossary.png)



| 言葉 | 説明 | 
| --- | --- | 
| デバイス | オーディオまたは画像入力を生成するハードウェアまたはソフトウェアコンポーネント。デバイスの例としては、マイク、カメラ、Bluetooth ヘッドセット、画面キャプチャやカスタム画像入力などの仮想デバイスがあります。 | 
| 混合デバイス | 他の `Device` と同様に、`Device` は `BroadcastSession` にアタッチできるますが、`Source` オブジェクトの追加を可能にする追加の API を使用します。混合デバイスにはオーディオまたは画像を複合する内部ミキサーがあり、単一の出力オーディオおよびイメージストリームを生成します。 混合デバイスはオーディオまたは画像のいずれかの形式で存在します。  | 
| 混合デバイスの設定 | 混合デバイスの設定オブジェクト。混合イメージデバイスの場合、寸法やフレームレートなどのプロパティが設定されます。混合オーディオデバイスの場合、チャンネル数が設定されます。 | 
|  ソース | 画面上のビジュアルエレメントの位置と、オーディオミックス内のオーディオトラックのプロパティを定義するコンテナです。混合デバイスは、ゼロ以上のソースで設定できます。ソースには、ソースのメディアの使用方法に影響する設定が与えられます。上のイメージには、4 つのイメージソースが示されています。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ja_jp/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html)  | 
| ソース設定 |  混合デバイスに入るソースの設定オブジェクト。完全な設定オブジェクトは以下のように表記されています。  | 
| トランジション | スロットを新しい位置に移動したり、そのプロパティの一部を変更するには、`MixedDevice.transitionToConfiguration()` を使用します。このメソッドには次のものがあります。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ja_jp/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html) | 

## 混合オーディオデバイス
<a name="broadcast-mixed-audio-device"></a>

### 設定
<a name="broadcast-mixed-audio-device-configuration"></a>

Android の`MixedAudioDeviceConfiguration` 

iOS の `IVSMixedAudioDeviceConfiguration`


| 名前 | 型 | 説明 | 
| --- | --- | --- | 
| `channels` | 整数 | オーディオミキサーからの出力チャネルの数。有効な値:1、2。1 はモノラルオーディオで、2 はステレオオーディオです。デフォルト: 2。 | 

### ソース設定
<a name="broadcast-mixed-audio-device-source-configuration"></a>

Android の`MixedAudioDeviceSourceConfiguration` 

iOS の `IVSMixedAudioDeviceSourceConfiguration`


| 名前 | 型 | 説明 | 
| --- | --- | --- | 
| `gain` | 浮動小数点数 | オーディオゲイン。これは乗数であるため、1 を超える値であればゲインが増加し、1 より小さい値であれば減少します。有効な値: 0 ～ 2。デフォルト: 1。 | 

## 混合イメージデバイス
<a name="broadcast-mixed-image-device"></a>

### 設定
<a name="broadcast-mixed-image-device-configuration"></a>

Android の`MixedImageDeviceConfiguration` 

iOS の `IVSMixedImageDeviceConfiguration`


| 名前 | 型 | 説明 | 
| --- | --- | --- | 
| `size` | Vec2 | ビデオキャンバスのサイズ。 | 
| `targetFramerate` | 整数 | 混合デバイスの 1 秒あたりのターゲットフレーム数。平均として、この値を満たす必要がありますが、特定の状況 (CPU や GPU の負荷の高さなど) でシステムのフレーム数が低下する場合があります。 | 
| `transparencyEnabled` | ブール値 | イメージソース設定で `alpha` プロパティを使用してブレンドできます。これを `true` に設定すると、メモリおよび CPU の消費量が増加します。デフォルト: `false`。 | 

### ソース設定
<a name="broadcast-mixed-image-device-source-configuration"></a>

Android の`MixedImageDeviceSourceConfiguration` 

iOS の `IVSMixedImageDeviceSourceConfiguration`


| 名前 | 型 | 説明 | 
| --- | --- | --- | 
| `alpha` | 浮動小数点数 | スロットのアルファ。これは、画像内のアルファ値との乗法です。有効な値: 0 〜 1。0 は完全に透明で、1 は完全に不透明です。デフォルト: 1。 | 
| `aspect` | AspectMode | スロットでレンダリングされたイメージのアスペクト比モード。有効な値: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ja_jp/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html) デフォルト: `Fit`  | 
| `fillColor` | Vec4 | スロットおよび画像のアスペクト比が一致しない場合に `aspect Fit` と使用する塗りつぶしカラー。フォーマットは (赤、緑、青、アルファ) です。有効な値 (チャネルごとに): 0 〜 1。デフォルト: (0、0、0、0)。 | 
| `position` | Vec2 | キャンバスの左上隅からの相対で、スロット位置 (ピクセル) です。スロットの原点も左上です。 | 
| `size` | Vec2 | スロットのサイズ (ピクセル単位)。この値を設定すると、`matchCanvasSize` も `false` に設定されます。デフォルト: (0、0)。ただし、`matchCanvasSize` のデフォルトは `true` であるため、スロットのレンダリングサイズはキャンバスサイズであり、(0、0) ではありません。 | 
| `zIndex` | 浮動小数点数 | スロットの相対的な順序。`zIndex` 値が高いスロットは、`zIndex` 値が低いスロットの上に描画されます。 | 

## 混合イメージデバイスの作成と設定
<a name="broadcast-mixed-image-device-creating-configuring"></a>

![\[ミキシング用のブロードキャストセッションの設定。\]](http://docs.aws.amazon.com/ja_jp/ivs/latest/RealTimeUserGuide/images/Broadcast_SDK_Mixer_Configuring.png)


ここでは、このガイドの冒頭にあるシーンに似たシーンを作成し、次の 3 つの画面上の要素を使用します。
+ カメラの左下のスロット。
+ ロゴオーバーレイ用の右下のスロット。
+ 映画の右上のスロット。

キャンバスの原点は左上隅で、これはスロットでも同じであることに注意してください。したがって、スロットを (0、0) に配置すると、スロット全体が見えるように左上隅に配置されます。

### iOS
<a name="broadcast-mixed-image-device-creating-configuring-ios"></a>

```
let deviceDiscovery = IVSDeviceDiscovery()
let mixedImageConfig = IVSMixedImageDeviceConfiguration()
mixedImageConfig.size = CGSize(width: 1280, height: 720)
try mixedImageConfig.setTargetFramerate(60)
mixedImageConfig.isTransparencyEnabled = true
let mixedImageDevice = deviceDiscovery.createMixedImageDevice(with: mixedImageConfig)

// Bottom Left
let cameraConfig = IVSMixedImageDeviceSourceConfiguration()
cameraConfig.size = CGSize(width: 320, height: 180)
cameraConfig.position = CGPoint(x: 20, y: mixedImageConfig.size.height - cameraConfig.size.height - 20)
cameraConfig.zIndex = 2
let camera = deviceDiscovery.listLocalDevices().first(where: { $0 is IVSCamera }) as? IVSCamera
let cameraSource = IVSMixedImageDeviceSource(configuration: cameraConfig, device: camera)
mixedImageDevice.add(cameraSource)

// Top Right
let streamConfig = IVSMixedImageDeviceSourceConfiguration()
streamConfig.size = CGSize(width: 640, height: 320)
streamConfig.position = CGPoint(x: mixedImageConfig.size.width - streamConfig.size.width - 20, y: 20)
streamConfig.zIndex = 1
let streamDevice = deviceDiscovery.createImageSource(withName: "stream")
let streamSource = IVSMixedImageDeviceSource(configuration: streamConfig, device: streamDevice)
mixedImageDevice.add(streamSource)

// Bottom Right
let logoConfig = IVSMixedImageDeviceSourceConfiguration()
logoConfig.size = CGSize(width: 320, height: 180)
logoConfig.position = CGPoint(x: mixedImageConfig.size.width - logoConfig.size.width - 20,
                              y: mixedImageConfig.size.height - logoConfig.size.height - 20)
logoConfig.zIndex = 3
let logoDevice = deviceDiscovery.createImageSource(withName: "logo")
let logoSource = IVSMixedImageDeviceSource(configuration: logoConfig, device: logoDevice)
mixedImageDevice.add(logoSource)
```

### Android
<a name="broadcast-mixed-image-device-creating-configuring-android"></a>

```
val deviceDiscovery = DeviceDiscovery(this /* context */)
val mixedImageConfig = MixedImageDeviceConfiguration().apply {
    setSize(BroadcastConfiguration.Vec2(1280f, 720f))
    setTargetFramerate(60)
    setEnableTransparency(true)
}
val mixedImageDevice = deviceDiscovery.createMixedImageDevice(mixedImageConfig)

// Bottom Left
val cameraConfig = MixedImageDeviceSourceConfiguration().apply {
    setSize(BroadcastConfiguration.Vec2(320f, 180f))
    setPosition(BroadcastConfiguration.Vec2(20f, mixedImageConfig.size.y - size.y - 20))
    setZIndex(2)
}
val camera = deviceDiscovery.listLocalDevices().firstNotNullOf { it as? CameraSource }
val cameraSource = MixedImageDeviceSource(cameraConfig, camera)
mixedImageDevice.addSource(cameraSource)

// Top Right
val streamConfig = MixedImageDeviceSourceConfiguration().apply {
    setSize(BroadcastConfiguration.Vec2(640f, 320f))
    setPosition(BroadcastConfiguration.Vec2(mixedImageConfig.size.x - size.x - 20, 20f))
    setZIndex(1)
}
val streamDevice = deviceDiscovery.createImageInputSource(streamConfig.size)
val streamSource = MixedImageDeviceSource(streamConfig, streamDevice)
mixedImageDevice.addSource(streamSource)

// Bottom Right
val logoConfig = MixedImageDeviceSourceConfiguration().apply {
    setSize(BroadcastConfiguration.Vec2(320f, 180f))
    setPosition(BroadcastConfiguration.Vec2(mixedImageConfig.size.x - size.x - 20, mixedImageConfig.size.y - size.y - 20))
    setZIndex(1)
}
val logoDevice = deviceDiscovery.createImageInputSource(logoConfig.size)
val logoSource = MixedImageDeviceSource(logoConfig, logoDevice)
mixedImageDevice.addSource(logoSource)
```

## ソースの削除
<a name="broadcast-mixed-devices-removing-sources"></a>

ソースを削除するには、削除する `Source` オブジェクトで `MixedDevice.remove` を呼び出します。

## トランジションのあるアニメーション
<a name="broadcast-mixed-devices-animations-transitions"></a>

トランジション方法では、ソースの構成が新しい構成に置き換えられます。この置換は、デュレーションを 0 より高く秒単位で設定することで、時間の経過とともにアニメートできます。

### アニメーション化できるプロパティ
<a name="broadcast-mixed-devices-animations-properties"></a>

スロット構造のすべてのプロパティをアニメートできるわけではありません。Float タイプに基づくすべてのプロパティはアニメーション化できます。その他のプロパティは、アニメーションの開始時または終了時に有効になります。


| 名前 | アニメーション化の可否 | インパクトポイント | 
| --- | --- | --- | 
| `Audio.gain` | はい | Interpolated | 
| `Image.alpha` | はい | Interpolated | 
| `Image.aspect` | いいえ | 修了 | 
| `Image.fillColor` | はい | Interpolated | 
| `Image.position` | はい | Interpolated | 
| `Image.size` | はい | Interpolated | 
| `Image.zIndex` 注: `zIndex` は 2D 平面を 3D 空間内で移動するため、アニメーションの途中で 2 つの平面が交差したときにトランジションが発生します。これは計算できますが、開始A値と終了 `zIndex` 値によって異なります。トランジションをよりスムーズにするために、これを `alpha` と組み合わせます。  | はい | 不明 | 

### シンプルな例
<a name="broadcast-mixed-devices-animations-examples"></a>

以下では、上記の[混合イメージデバイスの作成と設定](#broadcast-mixed-image-device-creating-configuring)で定義された設定を使用したフルスクリーンのカメラテイクオーバーの例が示されます。これは 0.5 秒でアニメーション化されます。

#### iOS
<a name="broadcast-mixed-devices-animations-examples-ios"></a>

```
// Continuing the example from above, modifying the existing cameraConfig object.
cameraConfig.size = CGSize(width: 1280, height: 720)
cameraConfig.position = CGPoint.zero
cameraSource.transition(to: cameraConfig, duration: 0.5) { completed in
    if completed {
        print("Animation completed")
    } else {
        print("Animation interrupted")
    }
}
```

#### Android
<a name="broadcast-mixed-devices-animations-examples-android"></a>

```
// Continuing the example from above, modifying the existing cameraConfig object.
cameraConfig.setSize(BroadcastConfiguration.Vec2(1280f, 720f))
cameraConfig.setPosition(BroadcastConfiguration.Vec2(0f, 0f))
cameraSource.transitionToConfiguration(cameraConfig, 500) { completed ->
    if (completed) {
        print("Animation completed")
    } else {
        print("Animation interrupted")
    }
}
```

## ブロードキャストのミラーリング
<a name="broadcast-mixed-devices-mirroring"></a>


| ブロードキャストでアタッチされた画像デバイスを以下の方向にミラーリングする場合 | 以下の条件には負の値を使用します | 
| --- | --- | 
| 水平方向 | スロットの幅 | 
| 垂直方向 | スロットの高さ | 
| 水平方向と垂直方向の両方 | スロットの幅と高さの両方 | 

ミラーリング時にスロットを正しい位置に配置するには、同じ値で位置を調整する必要があります。

以下は、ブロードキャストを水平方向と垂直方向にミラーリングする例です。

### iOS
<a name="broadcast-mixed-devices-mirroring-ios"></a>

水平ミラーリング:

```
let cameraSource = IVSMixedImageDeviceSourceConfiguration()
cameraSource.size = CGSize(width: -320, height: 720)
// Add 320 to position x since our width is -320
cameraSource.position = CGPoint(x: 320, y: 0)
```

垂直ミラーリング:

```
let cameraSource = IVSMixedImageDeviceSourceConfiguration()
cameraSource.size = CGSize(width: 320, height: -720)
// Add 720 to position y since our height is -720
cameraSource.position = CGPoint(x: 0, y: 720)
```

### Android
<a name="broadcast-mixed-devices-mirroring-android"></a>

水平ミラーリング:

```
val cameraConfig = MixedImageDeviceSourceConfiguration().apply {
    setSize(BroadcastConfiguration.Vec2(-320f, 180f))
   // Add 320f to position x since our width is -320f
    setPosition(BroadcastConfiguration.Vec2(320f, 0f))
}
```

垂直ミラーリング:

```
val cameraConfig = MixedImageDeviceSourceConfiguration().apply {
    setSize(BroadcastConfiguration.Vec2(320f, -180f))
    // Add 180f to position y since our height is -180f
    setPosition(BroadcastConfiguration.Vec2(0f, 180f))
}
```

注: このミラーリングは `ImagePreviewView` (Android) や `IVSImagePreviewView` (iOS) の `setMirrored` 方法とは異なります。このメソッドはデバイス上のローカルプレビューにのみ影響し、ブロードキャストには影響しません。

# IVS Broadcast SDK: トークン交換 \$1 リアルタイムストリーミング
<a name="broadcast-mobile-token-exchange"></a>

トークン交換を使用すると、参加者が再接続することなく、参加者トークン機能をアップグレードまたはダウングレードし、モバイルブロードキャスト SDK 内のトークン属性を更新できます。これは、参加者がサブスクライブ専用機能から開始し、後でパブリッシュ機能が必要になる共同ホスティングなどのシナリオで便利です。

制限:
+ トークン交換は、[キーペア](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-distribute-tokens.html#getting-started-distribute-tokens-self-signed)を使用してサーバーで作成されたトークンでのみ機能します。[CreateParticipantToken API](https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/API_CreateParticipantToken.html) を介して作成されたトークンでは機能しません。
+ トークン交換を使用して、サーバーサイドコンポジションレイアウト (featuredParticipantAttribute や participantOrderAttribute など) を駆動する属性を変更した場合、アクティブなコンポジションのレイアウトは、参加者が再接続するまで更新されません。

## トークンの交換
<a name="broadcast-mobile-token-exchange-exchanging-tokens"></a>

トークンの交換は簡単です。`Stage` / `IVSStage` オブジェクト上の `exchangeToken` API を呼び出して新しいトークンを提供します。新しいトークンの `capabilities` が以前のトークンのものと異なる場合、新しいトークンの機能は直ちに評価されます。例えば、前のトークンに `publish` 機能がなく、新しいトークンにその機能がある場合、公開用のステージ戦略関数が呼び出され、ホストアプリケーションが新しい機能を使用してすぐに公開するか、待機するかを決定できるようになります。削除された機能も同様です。前のトークンに `publish` 機能があり、新しいトークンにその機能がない場合、参加者は公開用のステージ戦略関数を呼び出さずにすぐに公開を解除します。

トークンを交換する場合、以前のトークンと新しいトークンのペイロードフィールドの値が同じである必要があります。
+ `topic`
+ `resource`
+ `jti`
+ `whip_url`
+ `events_url`

これらのフィールドはイミュータブルです。イミュータブルなフィールドを変更するトークンを交換すると、SDK は直ちに交換を拒否します。

残りのフィールドは、次のように変更できます。
+ `attributes`
+ `capabilities`
+ `user`
+ `_id`
+ `iat`
+ `exp`

### iOS
<a name="broadcast-mobile-token-exchange-exchanging-tokens-ios"></a>



```
let stage = try IVSStage(token: originalToken, strategy: self)
stage.join()
stage.exchangeToken(newToken)
```

### Android
<a name="broadcast-mobile-token-exchange-exchanging-tokens-android"></a>



```
val stage = Stage(context, originalToken, strategy)
stage.join()
stage.exchangeToken(newToken)
```

## 更新の受信
<a name="broadcast-mobile-token-exchange-receiving-updates"></a>

`StageRenderer` / `IVSStageRenderer` の関数は、`userId` または `attributes` を更新するトークンを交換する既に公開済みのリモート参加者に関する更新を受信します。まだ公開していないリモート参加者は、最終的に公開された場合、既存の `onParticipantJoined` / `participantDidJoin` レンダラー関数を介して更新済みの `userId` と `attributes` を持ちます

### iOS
<a name="broadcast-mobile-token-exchange-receiving-updates-ios"></a>



```
class MyStageRenderer: NSObject, IVSStageRenderer {
    func stage(_ stage: IVSStage, participantMetadataDidUpdate participant: IVSParticipantInfo) {
        // participant will be a new IVSParticipantInfo instance with updated properties.
    }
}
```

### Android
<a name="broadcast-mobile-token-exchange-receiving-updates-android"></a>



```
private val stageRenderer = object : StageRenderer {
    override fun onParticipantMetadataUpdated(stage: Stage, participantInfo: ParticipantInfo) {
        // participantInfo will be a new ParticipantInfo instance with updated properties.
    }
}
```

## 更新の可視性
<a name="broadcast-mobile-token-exchange-visibility"></a>

参加者がトークンを交換して `userId` または `attributes` を更新する場合、これらの変更の可視性は現在の公開状態によって異なります。
+ **参加者が公開**していない場合:** 更新はサイレントに処理されます。最終的に公開されると、すべての SDK は最初の公開イベントの一部として更新済みの `userId` と `attributes` を受け取ります。
+ **参加者が**既に公開している場合:** 更新は直ちにブロードキャストされます。ただし、通知を受け取るのはモバイル SDK v1.37.0 以降のみです。ウェブ SDK、古いモバイル SDK、サーバーサイドコンポジションの参加者は、参加者が公開を解除して再公開するまで変更を表示されません。

次の表はサポートのマトリックスを示します。


| 参加者の状態 | オブザーバー: モバイル SDK 1.37.0 以降 | オブザーバー: 古いモバイル SDK、ウェブ SDK、サーバーサイドコンポジション | 
| --- | --- | --- | 
| 未発行 (その後開始) | ✅ 表示可能 (参加者が参加したイベントを通じて公開) | ✅ 表示可能 (参加者が参加したイベントを通じて公開) | 
| 発行済み (再発行なし) | ✅ 表示可能 (参加者メタデータ更新済みイベントを介して即時) | ❌ 表示不可 | 
| 発行済み (発行解除および再発行) | ✅ 表示可能 (参加者メタデータ更新済みイベントを介して即時) | ⚠️ 最終的に表示可能 (参加者が参加したイベントを介して再公開) | 

# IVS Broadcast SDK: カスタムイメージソース \$1 リアルタイムストリーミング
<a name="broadcast-custom-image-sources"></a>

カスタム画像入力ソースを使用することで、プリセットされたカメラに限定されるのではなく、アプリケーションが独自の画像入力を Broadcast SDK に提供できます。カスタム画像ソースは、半透明の透かしや静的な「be right back」(すぐに戻ります) シーンのようにシンプルにすることや、カメラに加工フィルターを追加するなど、アプリケーションが追加のカスタム処理を実行できるようにすることもできます。

カメラのカスタムコントロールにカスタム画像入力ソースを使用する場合 (カメラアクセスを必要とするビューティーフィルターライブラリを使用するなど)、Broadcast SDK はカメラの管理をしなくなります。代わりに、アプリケーションはカメラのライフサイクルを正しく処理する責任があります。アプリケーションがカメラをどのように管理すべきかについては、プラットフォームの公式ドキュメントを参照してください。

## Android
<a name="custom-image-sources-android"></a>

`DeviceDiscovery` セッションを作成したら、画像入力ソースを作成します。

```
CustomImageSource imageSource = deviceDiscovery.createImageInputSource(new BroadcastConfiguration.Vec2(1280, 720));
```

このメソッドは、標準の Android [Surface](https://developer.android.com/reference/android/view/Surface) に基づく画像ソースである `CustomImageSource` を返します。サブクラス `SurfaceSource` のサイズを変更したり、回転したりできます。`ImagePreviewView` を作成して、その内容のプレビューを表示することもできます。

基盤の `Surface` を取得します。

```
Surface surface = surfaceSource.getInputSurface();
```

この `Surface` は、Camera2、OpenGL ES、その他のライブラリなどの画像プロデューサーの出力バッファとして使用できます。最も簡単なユースケースは、静的なビットマップまたは色を Surface のキャンバスに直接描画することです。ただし、多くのライブラリ (加工フィルターライブラリなど) には、レンダリングする外部 `Surface` をアプリケーションで指定できるメソッドが用意されています。このようなメソッドを使用して、この `Surface` をフィルターライブラリに渡すことができます。これによりライブラリは、ストリーミングするブロードキャストセッションに、処理されたフレームを出力できます。

この `CustomImageSource`は `LocalStageStream` でラップすることができ、`StageStrategy` によって返されて `Stage` に公開されます。

## iOS
<a name="custom-image-sources-ios"></a>

`DeviceDiscovery` セッションを作成したら、画像入力ソースを作成します。

```
let customSource = broadcastSession.createImageSource(withName: "customSourceName")
```

このメソッドは、アプリケーションが手動で `CMSampleBuffers` を送信できるようにする画像ソースである `IVSCustomImageSource` を返します。サポートされているピクセル形式については、「iOS ブロードキャスト SDK リファレンス」を参照してください。最新バージョンへのリンクは、最新のブロードキャスト SDK リリースの「[Amazon IVS リリースノート](release-notes.md)」にあります。

カスタムソースに送信されたサンプルは、次のステージでストリーミングされます。

```
customSource.onSampleBuffer(sampleBuffer)
```

動画のストリーミングには、このメソッドをコールバックで使用します。例えば、カメラを使用している場合、`AVCaptureSession` から新しいサンプルバッファを受信するたびに、アプリケーションはサンプルバッファをカスタム画像ソースに転送できます。必要に応じて、カスタム画像ソースにサンプルを送信する前に、アプリケーションでさらなる処理 (加工フィルターなど) を適用できます。

`IVSCustomImageSource` は `IVSLocalStageStream` でラップすることができ、`IVSStageStrategy` によって返されて `Stage` に公開されます。

# IVS Broadcast SDK: カスタムオーディオソース \$1 リアルタイムストリーミング
<a name="broadcast-custom-audio-sources"></a>

**注:** このガイドは、IVS リアルタイムストリーミング Android Broadcast SDK にのみ適用されます。iOS とウェブの SDK に関する情報は、今後公開される予定です。

カスタム音声入力ソースを使用すると、アプリケーションはデバイスの内蔵マイクに制限されるのではなく、ブロードキャスト SDK に独自の音声入力を提供できます。カスタムオーディオソースを使用すると、アプリケーションは処理された音声へのエフェクトの適用とストリーミング、複数のオーディオストリームのミックス、サードパーティーのオーディオ処理ライブラリとの統合を行うことができます。

カスタムオーディオ入力ソースを使用する場合、ブロードキャスト SDK ではマイクが直接管理されなくなります。代わりに、アプリケーションがオーディオデータをキャプチャ、処理し、カスタムソースに送信します。

カスタムオーディオソースのワークフローは、以下のステップに従います。

1. オーディオ入力 — 指定されたオーディオ形式 (サンプルレート、チャネル、形式) でカスタムオーディオソースを作成します。

1. 処理 — オーディオ処理パイプラインからオーディオデータをキャプチャまたは生成します。

1. カスタムオーディオソース — `appendBuffer()` を使用してカスタムソースにオーディオバッファを送信します。

1. ステージ — `LocalStageStream` にラップして `StageStrategy` 経由でステージに公開します。

1. 参加者 — ステージ参加者は、処理された音声をリアルタイムで受け取ります。

## Android
<a name="custom-audio-sources-android"></a>

### カスタムオーディオソースの作成
<a name="custom-audio-sources-android-creating-a-custom-audio-source"></a>

`DeviceDiscovery` セッションを作成した後、カスタム音声入力ソースを作成します。

```
DeviceDiscovery deviceDiscovery = new DeviceDiscovery(context); 
 
// Create custom audio source with specific format 
CustomAudioSource customAudioSource = deviceDiscovery.createAudioInputSource( 
   2,  // Number of channels (1 = mono, 2 = stereo) 
   BroadcastConfiguration.AudioSampleRate.RATE_48000,  // Sample rate 
   AudioDevice.Format.INT16  // Audio format (16-bit PCM) 
);
```

このメソッドは、未加工の PCM オーディオデータを受け入れる `CustomAudioSource` を返します。カスタムオーディオソースは、オーディオ処理パイプラインが生成するのと同じオーディオ形式で設定する必要があります。

#### サポートされるオーディオ形式
<a name="custom-audio-sources-android-submitting-audio-data-supportedi-audio-formats"></a>


| パラメータ | オプション | 説明 | 
| --- | --- | --- | 
| チャンネル | 1 (モノラル)、2 (ステレオ) | オーディオチャネルの数。 | 
| サンプルレート | RATE\$116000、RATE\$144100、RATE\$148000 | オーディオサンプルレート (Hz)。高品質には 48kHz が推奨されます。 | 
| 形式 | INT16、FLOAT32 | オーディオサンプル形式。INT16 は 16 ビットの固定ポイント PCM です。FLOAT32 は 32 ビットの浮動小数点 PCM です。インターリーブ形式と Planar 平面形式の両方を使用できます。 | 

### オーディオデータの送信
<a name="custom-audio-sources-android-submitting-audio-data"></a>

オーディオデータをカスタムソースに送信するには、`appendBuffer()` メソッドを使用します。

```
// Prepare audio data in a ByteBuffer 
ByteBuffer audioBuffer = ByteBuffer.allocateDirect(bufferSize); 
audioBuffer.put(pcmAudioData);  // Your processed audio data 
 
// Calculate the number of bytes 
long byteCount = pcmAudioData.length; 
 
// Submit audio to the custom source 
// presentationTimeUs should be generated by and come from your audio source
int samplesProcessed = customAudioSource.appendBuffer( 
   audioBuffer, 
   byteCount, 
   presentationTimeUs 
); 
 
if (samplesProcessed > 0) { 
   Log.d(TAG, "Successfully submitted " + samplesProcessed + " samples"); 
} else { 
   Log.w(TAG, "Failed to submit audio samples"); 
} 
 
// Clear buffer for reuse 
audioBuffer.clear();
```

**重要な考慮事項:**
+ オーディオデータは、カスタムソースの作成時に指定された形式である必要があります。
+ スムーズなオーディオ再生のために、タイムスタンプは一定間隔で増加し、オーディオソースによって提供される必要があります。
+ ストリームのギャップを避けるため、オーディオを定期的に送信します。
+ メソッドは、処理されたサンプルの数を返します (0 は失敗を示します）。

### ステージへの発行
<a name="custom-audio-sources-android-publishing-to-a-stage"></a>

`CustomAudioSource` を `AudioLocalStageStream` でラップし、`StageStrategy` から返します。

```
// Create the audio stream from custom source 
AudioLocalStageStream audioStream = new AudioLocalStageStream(customAudioSource); 
 
// Define your stage strategy 
Strategy stageStrategy = new Strategy() { 
   @NonNull 
   @Override 
   public List<LocalStageStream> stageStreamsToPublishForParticipant( 
         @NonNull Stage stage, 
         @NonNull ParticipantInfo participantInfo) { 
      List<LocalStageStream> streams = new ArrayList<>(); 
      streams.add(audioStream);  // Publish custom audio 
      return streams; 
   } 
 
   @Override 
   public boolean shouldPublishFromParticipant( 
         @NonNull Stage stage, 
         @NonNull ParticipantInfo participantInfo) { 
      return true;  // Control when to publish 
   } 
 
   @Override 
   public Stage.SubscribeType shouldSubscribeToParticipant( 
         @NonNull Stage stage, 
         @NonNull ParticipantInfo participantInfo) { 
      return Stage.SubscribeType.AUDIO_VIDEO; 
   } 
}; 
 
// Create and join the stage 
Stage stage = new Stage(context, stageToken, stageStrategy);
```

### 完全な例: オーディオ処理の統合
<a name="custom-audio-sources-android-complete-example"></a>

オーディオ処理 SDK との統合を示す完全な例を次に示します。

```
public class AudioStreamingActivity extends AppCompatActivity { 
   private DeviceDiscovery deviceDiscovery; 
   private CustomAudioSource customAudioSource; 
   private AudioLocalStageStream audioStream; 
   private Stage stage; 
 
   @Override 
   protected void onCreate(Bundle savedInstanceState) { 
      super.onCreate(savedInstanceState); 
 
      // Configure audio manager 
      StageAudioManager.getInstance(this) 
         .setPreset(StageAudioManager.UseCasePreset.VIDEO_CHAT); 
 
      // Initialize IVS components 
      initializeIVSStage(); 
 
      // Initialize your audio processing SDK 
      initializeAudioProcessing(); 
   } 
 
   private void initializeIVSStage() { 
      deviceDiscovery = new DeviceDiscovery(this); 
 
      // Create custom audio source (48kHz stereo, 16-bit) 
      customAudioSource = deviceDiscovery.createAudioInputSource( 
         2,  // Stereo 
         BroadcastConfiguration.AudioSampleRate.RATE_48000, 
         AudioDevice.Format.INT16 
      ); 
 
      // Create audio stream 
      audioStream = new AudioLocalStageStream(customAudioSource); 
 
      // Create stage with strategy 
      Strategy strategy = new Strategy() { 
         @NonNull 
         @Override 
         public List<LocalStageStream> stageStreamsToPublishForParticipant( 
               @NonNull Stage stage, 
               @NonNull ParticipantInfo participantInfo) { 
            return Collections.singletonList(audioStream); 
         } 
 
         @Override 
         public boolean shouldPublishFromParticipant( 
               @NonNull Stage stage, 
               @NonNull ParticipantInfo participantInfo) { 
            return true; 
         } 
 
         @Override 
         public Stage.SubscribeType shouldSubscribeToParticipant( 
               @NonNull Stage stage, 
               @NonNull ParticipantInfo participantInfo) { 
            return Stage.SubscribeType.AUDIO_VIDEO; 
         } 
      }; 
 
      stage = new Stage(this, getStageToken(), strategy); 
   } 
 
   private void initializeAudioProcessing() { 
      // Initialize your audio processing SDK 
      // Set up callback to receive processed audio 
      yourAudioSDK.setAudioCallback(new AudioCallback() { 
         @Override 
         public void onProcessedAudio(byte[] audioData, int sampleRate, 
                                     int channels, long timestamp) { 
            // Submit processed audio to IVS Stage 
            submitAudioToStage(audioData, timestamp); 
         } 
      }); 
   } 
 
   // The timestamp is required to come from your audio source and you  
   // should not be generating one on your own, unless your audio source 
   // does not provide one. If that is the case, create your own epoch  
   // timestamp and manually calculate the duration between each sample  
   // using the number of frames and frame size. 

   private void submitAudioToStage(byte[] audioData, long timestamp) { 
      try { 
         // Allocate direct buffer 
         ByteBuffer buffer = ByteBuffer.allocateDirect(audioData.length); 
         buffer.put(audioData); 
 
         // Submit to custom audio source 
         int samplesProcessed = customAudioSource.appendBuffer( 
            buffer, 
            audioData.length, 
            timestamp > 0 ? timestamp : System.nanoTime() / 1000 
         ); 
 
         if (samplesProcessed <= 0) { 
            Log.w(TAG, "Failed to submit audio samples"); 
         } 
 
         buffer.clear(); 
      } catch (Exception e) { 
         Log.e(TAG, "Error submitting audio: " + e.getMessage(), e); 
      } 
   } 
 
   @Override 
   protected void onDestroy() { 
      super.onDestroy(); 
      if (stage != null) { 
          stage.release(); 
      } 
   } 
}
```

### ベストプラクティス
<a name="custom-audio-sources-android-best-practices"></a>

#### オーディオ形式の整合性
<a name="custom-audio-sources-android-best-practices-audio-format-consistency"></a>

送信するオーディオ形式が、カスタムソースの作成時に指定された形式と一致していることを確認します。

```
// If you create with 48kHz stereo INT16 
customAudioSource = deviceDiscovery.createAudioInputSource( 
   2, RATE_48000, INT16 
); 
 
// Your audio data must be: 
// - 2 channels (stereo) 
// - 48000 Hz sample rate 
// - 16-bit interleaved PCM format
```

#### バッファ管理
<a name="custom-audio-sources-android-best-practices-buffer-managemetn"></a>

ガベージコレクションを最小限に抑えるために `ByteBuffers` を直接使用して再利用します。

```
// Allocate once 
private ByteBuffer audioBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE); 
 
// Reuse in callback 
public void onAudioData(byte[] data) { 
   audioBuffer.clear(); 
   audioBuffer.put(data); 
   customAudioSource.appendBuffer(audioBuffer, data.length, getTimestamp()); 
   audioBuffer.clear(); 
}
```

#### タイミングと同期
<a name="custom-audio-sources-android-best-practices-timing-and-synchronization"></a>

オーディオをスムーズに再生するには、オーディオソースが提供するタイムスタンプを使用する必要があります。オーディオソースに独自のタイムスタンプがない場合は、独自のエポックタイムスタンプを作成し、フレーム数とフレームサイズを使用して各サンプル間の期間を手動で計算します。

```
// "audioFrameTimestamp" should be generated by your audio source
// Consult your audio source’s documentation for information on how to get this 
long timestamp = audioFrameTimestamp;
```

#### エラー処理
<a name="custom-audio-sources-android-best-practices-error-handling"></a>

必ず `appendBuffer()` の戻り値を確認してください。

```
int samplesProcessed = customAudioSource.appendBuffer(buffer, count, timestamp); 
 
if (samplesProcessed <= 0) { 
   Log.w(TAG, "Audio submission failed - buffer may be full or format mismatch"); 
   // Handle error: check format, reduce submission rate, etc. 
}
```

# IVS Broadcast SDK: サードパーティーのカメラフィルター \$1 リアルタイムストリーミング
<a name="broadcast-3p-camera-filters"></a>

このガイドは、読者が既に[カスタム画像](broadcast-custom-image-sources.md)ソースに慣れていて [IVS Broadcast SDK (リアルタイムストリーミング)](broadcast.md) をアプリケーションに統合していることを前提としています。

カメラフィルターを使うと、ライブストリームのクリエイターは顔や背景の見た目を補強したり変更したりできます。これにより、視聴者の注目度を高め、視聴者を引き付け、ライブストリーミングに好感を持たせることができる可能性があります。

# サードパーティーのカメラフィルターを統合する
<a name="broadcast-3p-camera-filters-integrating"></a>

フィルター SDK の出力を[カスタム画像入力ソース](broadcast-custom-image-sources.md)にフィードすることで、サードパーティーのカメラフィルター SDK を IVS ブロードキャスト SDK と統合できます。カスタム画像入力ソースを使用することで、アプリケーションが独自の画像入力を Broadcast SDK に提供できます。サードパーティーのフィルタープロバイダーの SDK がカメラのライフサイクルを管理して、カメラからの画像の処理、フィルター効果の適用、カスタム画像ソースに渡せる形式で出力する場合があります。

![\[フィルター SDK の出力をカスタム画像入力ソースにフィードすることで、サードパーティーのカメラフィルター SDK を IVS ブロードキャスト SDK と統合する。\]](http://docs.aws.amazon.com/ja_jp/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Integrating.png)


カメラフレームにフィルター効果を適用して[カスタム画像入力ソース](broadcast-custom-image-sources.md)に渡せる形式に変換する組み込みメソッドについては、サードパーティーのフィルタープロバイダーのドキュメントを参照してください。この処理は、使用している IVS ブロードキャスト SDK のバージョンによって異なります。
+ **ウェブ** – フィルタープロバイダーは、出力をキャンバス要素にレンダリングできる必要があります。そうすると、[captureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) メソッドを使用して、キャンバスのコンテンツの MediaStream を返すことができます。そして、MediaStream を [LocalStageStream](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/classes/LocalStageStream) のインスタンスに変換して、ステージに公開できます。
+ **Android** – フィルタープロバイダーの SDK は、IVS Broadcast SDK が提供する Android `Surface` にフレームをレンダリングすることも、フレームをビットマップに変換することもできます。ビットマップを使用する場合は、ロックを解除してキャンバスに書き込むことで、カスタム画像ソースが提供する基盤 `Surface` にレンダリングできます。
+ **iOS** – サードパーティーのフィルタープロバイダーの SDK は、フィルター効果を適用したカメラフレームを `CMSampleBuffer` として提供する必要があります。カメラ画像の処理後に `CMSampleBuffer` を最終出力として使用する方法については、サードパーティーのフィルターベンダーの SDK のドキュメントを参照してください。

# IVS Broadcast SDK で BytePlus を使用する
<a name="broadcast-3p-camera-filters-integrating-byteplus"></a>

このドキュメントでは、IVS Broadcast SDK で BytePlus Effects SDK を使用する方法について説明します。

## Android
<a name="integrating-byteplus-android"></a>

### BytePlus Effects SDK のインストールと設定
<a name="integrating-byteplus-android-install-effects-sdk"></a>

BytePlus Effects SDK のインストール、初期化、設定方法の詳細については、BytePlus「[Android アクセスガイド](https://docs.byteplus.com/en/effects/docs/android-v4101-access-guide)」を参照してください。

### カスタム画像ソースを設定する
<a name="integrating-byteplus-android-setup-image-source"></a>

SDK を初期化した後、フィルター効果を適用した処理済みのカメラフレームをカスタム画像入力ソースにフィードします。そのためには、`DeviceDiscovery` オブジェクトのインスタンスを作成し、カスタム画像ソースを作成します。カメラのカスタムコントロールにカスタム画像入力ソースを使用する場合、Broadcast SDK はカメラの管理をしなくなることに注意してください。代わりに、アプリケーションはカメラのライフサイクルを正しく処理する責任があります。

#### Java
<a name="integrating-byteplus-android-setup-image-source-code"></a>

```
var deviceDiscovery = DeviceDiscovery(applicationContext)
var customSource = deviceDiscovery.createImageInputSource( BroadcastConfiguration.Vec2(
720F, 1280F
))
var surface: Surface = customSource.inputSurface
var filterStream = ImageLocalStageStream(customSource)
```

### 出力をビットマップに変換し、カスタム画像入力ソースにフィードする
<a name="integrating-byteplus-android-convert-to-bitmap"></a>

BytePlus Effects SDK からフィルター効果が適用されたカメラフレームを IVS Broadcast SDK に直接転送できるようにするには、BytePlus Effects SDK のテクスチャ出力をビットマップに変換します。画像が処理されると、SDK によって `onDrawFrame()` メソッドが呼び出されます。`onDrawFrame()` メソッドは Android の [GLSurfaceView.Renderer](https://developer.android.com/reference/android/opengl/GLSurfaceView.Renderer) インターフェイスのパブリックメソッドです。BytePlus が提供する Android サンプルアプリケーションでは、このメソッドはカメラフレームごとに呼び出され、テクスチャを出力します。同時に、このテクスチャをビットマップに変換してカスタム画像入力ソースにフィードするロジックを `onDrawFrame()` メソッドに追加できます。次のサンプルコードに示すように、BytePlus SDK が提供する `transferTextureToBitmap` メソッドを使用してこの変換を行います。このメソッドは、次のサンプルコードに示すように、BytePlus Effects SDK から [com.bytedance.labcv.core.util.ImageUtil](https://docs.byteplus.com/en/effects/docs/android-v4101-access-guide#Appendix:%20convert%20input%20texture%20to%202D%20texture%20with%20upright%20face) ライブラリで提供されます。次に、結果のビットマップを Surface のキャンバスに書き込むことで、`CustomImageSource` の基盤 Android `Surface` にレンダリングできます。`onDrawFrame()` を何度も連続して呼び出すと、ビットマップのシーケンスが生成され、組み合わせると動画のストリームが作成されます。

#### Java
<a name="integrating-byteplus-android-convert-to-bitmap-code"></a>

```
import com.bytedance.labcv.core.util.ImageUtil;
...
protected ImageUtil imageUtility;
...


@Override
public void onDrawFrame(GL10 gl10) {
  ...	
  // Convert BytePlus output to a Bitmap
  Bitmap outputBt = imageUtility.transferTextureToBitmap(output.getTexture(),ByteEffect     
  Constants.TextureFormat.Texture2D,output.getWidth(), output.getHeight());

  canvas = surface.lockCanvas(null);
  canvas.drawBitmap(outputBt, 0f, 0f, null);
  surface.unlockCanvasAndPost(canvas);
```

# IVS Broadcast SDK で DeepAR を使用する
<a name="broadcast-3p-camera-filters-integrating-deepar"></a>

このドキュメントでは、IVS Broadcast SDK で DeepAR SDK を使用する方法について説明します。

## Android
<a name="integrating-deepar-android"></a>

DeepAR SDK を Android IVS Broadcast SDK と統合する方法の詳細については、[DeepAR の Android 統合ガイド](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/android/)を参照してください。

## iOS
<a name="integrating-deepar-ios"></a>

DeepAR SDK を iOS IVS Broadcast SDK と統合する方法の詳細については、[DeepAR の iOS 統合ガイド](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/ios/)を参照してください。

# VS Broadcast SDK で Snap を使用する
<a name="broadcast-3p-camera-filters-integrating-snap"></a>

このドキュメントでは、IVS Broadcast SDK で Snap の Camera Kit SDK を使用する方法について説明します。

## Web
<a name="integrating-snap-web"></a>

このセクションは、読者が既に [Web Broadcast SDK を使用した動画の公開とサブスクリプション](getting-started-pub-sub-web.md)に慣れていることを前提としています。

Snap の Camera Kit SDK を IVS Real-Time Streaming Web Broadcast SDK と統合するには、以下が必要です。

1. Camera Kit SDK と Webpack をインストールします。(この例では Webpack をバンドラーとして使用していますが、任意のバンドラーを使用できます)

1. `index.html` を作成します。

1. セットアップ要素を追加します。

1. `index.css` を作成します。

1. 参加者を表示して設定します。

1. 接続されているカメラとマイクを表示します。

1. Camera Kit セッションを作成します。

1. レンズを取得し、レンズセレクターに入力します。

1. Camera Kit セッションの出力をキャンバスにレンダリングします。

1. [レンズ] ドロップダウンに入力する関数を作成します。

1. Camera Kit にレンダリング用のメディアソースを供給し、`LocalStageStream` を公開します。

1. `package.json` を作成します。

1. Webpack 設定ファイルを作成します。

1. HTTPS サーバーをセットアップしてテストします。

各ステップの詳細を以下に示します。

### Camera Kit SDK と Webpack をインストールする
<a name="integrating-snap-web-install-camera-kit"></a>

この例では Webpack をバンドラーとして使用していますが、任意のバンドラーを使用できます。

```
npm i @snap/camera-kit webpack webpack-cli
```

### index.html を作成する
<a name="integrating-snap-web-create-index"></a>

次に、HTML 共通スクリプトを作成し、Web Broadcast SDK をスクリプトタグとしてインポートします。次のコードでは、`<SDK version>` を、使用している Broadcast SDK のバージョンに必ず置き換えてください。

#### HTML
<a name="integrating-snap-web-create-index-code"></a>

```
<!--
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */
-->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <title>Amazon IVS Real-Time Streaming Web Sample (HTML and JavaScript)</title>

  <!-- Fonts and Styling -->
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.4.1/milligram.css" />
  <link rel="stylesheet" href="./index.css" />

  <!-- Stages in Broadcast SDK -->
  <script src="https://web-broadcast.live-video.net/<SDK version>/amazon-ivs-web-broadcast.js"></script>
</head>

<body>
  <!-- Introduction -->
  <header>
    <h1>Amazon IVS Real-Time Streaming Web Sample (HTML and JavaScript)</h1>

    <p>This sample is used to demonstrate basic HTML / JS usage. <b><a href="https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/multiple-hosts.html">Use the AWS CLI</a></b> to create a <b>Stage</b> and a corresponding <b>ParticipantToken</b>. Multiple participants can load this page and put in their own tokens. You can <b><a href="https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#glossary" target="_blank">read more about stages in our public docs.</a></b></p>
  </header>
  <hr />
  
  <!-- Setup Controls -->
 
  <!-- Display Local Participants -->
  
  <!-- Lens Selector -->

  <!-- Display Remote Participants -->

  <!-- Load All Desired Scripts -->
```

### セットアップ要素を追加する
<a name="integrating-snap-web-add-setup-elements"></a>

カメラ、マイク、およびレンズを選択し、参加者トークンを指定するための HTML を次のように作成します。

#### HTML
<a name="integrating-snap-web-setup-controls-code"></a>

```
<!-- Setup Controls -->
  <div class="row">
    <div class="column">
      <label for="video-devices">Select Camera</label>
      <select disabled id="video-devices">
        <option selected disabled>Choose Option</option>
      </select>
    </div>
    <div class="column">
      <label for="audio-devices">Select Microphone</label>
      <select disabled id="audio-devices">
        <option selected disabled>Choose Option</option>
      </select>
    </div>
    <div class="column">
      <label for="token">Participant Token</label>
      <input type="text" id="token" name="token" />
    </div>
    <div class="column" style="display: flex; margin-top: 1.5rem">
      <button class="button" style="margin: auto; width: 100%" id="join-button">Join Stage</button>
    </div>
    <div class="column" style="display: flex; margin-top: 1.5rem">
      <button class="button" style="margin: auto; width: 100%" id="leave-button">Leave Stage</button>
    </div>
  </div>
```

その下に HTML を追加して、ローカルおよびリモートの参加者からのカメラフィードを表示します。

#### HTML
<a name="integrating-snap-web-local-remote-participants-code"></a>

```
 <!-- Local Participant -->
<div class="row local-container">
    <canvas id="canvas"></canvas>

    <div class="column" id="local-media"></div>
    <div class="static-controls hidden" id="local-controls">
      <button class="button" id="mic-control">Mute Mic</button>
      <button class="button" id="camera-control">Mute Camera</button>
    </div>
  </div>

  
  <hr style="margin-top: 5rem"/>
  
  <!-- Remote Participants -->
  <div class="row">
    <div id="remote-media"></div>
  </div>
```

カメラをセットアップするためのヘルパーメソッドなどの追加のロジックやバンドルされている JavaScript ファイルをロードします。(このセクションの後半では、これらの JavaScript ファイルを作成して 1 つのファイルにバンドルします。これにより、Camera Kit をモジュールとしてインポートできます。バンドルされている JavaScript ファイルには、Camera Kit の設定、Lens の適用、およびステージにレンズを適用したカメラフィードの公開を行うためのロジックが含まれます) `body` および `html` 要素の終了タグを追加して、`index.html` の作成を完了します。

#### HTML
<a name="integrating-snap-web-load-all-scripts-code"></a>

```
<!-- Load all Desired Scripts -->
  <script src="./helpers.js"></script>
  <script src="./media-devices.js"></script>
  <!-- <script type="module" src="./stages-simple.js"></script> -->
  <script src="./dist/bundle.js"></script>
</body>
</html>
```

### index.css を作成する
<a name="integrating-snap-web-create-index-css"></a>

ページのスタイルを設定するための CSS ソースファイルを作成します。ステージを管理し、Snap の Camera Kit SDK と統合するためのロジックに焦点を当てるため、このコードについては説明しません。

#### CSS
<a name="integrating-snap-web-create-index-css-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

html,
body {
  margin: 2rem;
  box-sizing: border-box;
  height: 100vh;
  max-height: 100vh;
  display: flex;
  flex-direction: column;
}

hr {
  margin: 1rem 0;
}

table {
  display: table;
}

canvas {
  margin-bottom: 1rem;
  background: green;
}

video {
  margin-bottom: 1rem;
  background: black;
  max-width: 100%;
  max-height: 150px;
}

.log {
  flex: none;
  height: 300px;
}

.content {
  flex: 1 0 auto;
}

.button {
  display: block;
  margin: 0 auto;
}

.local-container {
  position: relative;
}

.static-controls {
  position: absolute;
  margin-left: auto;
  margin-right: auto;
  left: 0;
  right: 0;
  bottom: -4rem;
  text-align: center;
}

.static-controls button {
  display: inline-block;
}

.hidden {
  display: none;
}

.participant-container {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  margin: 1rem;
}

video {
  border: 0.5rem solid #555;
  border-radius: 0.5rem;
}
.placeholder {
  background-color: #333333;
  display: flex;
  text-align: center;
  margin-bottom: 1rem;
}
.placeholder span {
  margin: auto;
  color: white;
}
#local-media {
  display: inline-block;
  width: 100vw;
}

#local-media video {
  max-height: 300px;
}

#remote-media {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: row;
  width: 100%;
}

#lens-selector {
  width: 100%;
  margin-bottom: 1rem;
}
```

### 参加者を表示および設定する
<a name="integrating-snap-web-setup-participants"></a>

次に `helpers.js` を作成します。これには、参加者の表示と設定に使用するヘルパーメソッドが含まれます。

#### JavaScript
<a name="integrating-snap-web-setup-participants-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

function setupParticipant({ isLocal, id }) {
  const groupId = isLocal ? 'local-media' : 'remote-media';
  const groupContainer = document.getElementById(groupId);

  const participantContainerId = isLocal ? 'local' : id;
  const participantContainer = createContainer(participantContainerId);
  const videoEl = createVideoEl(participantContainerId);

  participantContainer.appendChild(videoEl);
  groupContainer.appendChild(participantContainer);

  return videoEl;
}

function teardownParticipant({ isLocal, id }) {
  const groupId = isLocal ? 'local-media' : 'remote-media';
  const groupContainer = document.getElementById(groupId);
  const participantContainerId = isLocal ? 'local' : id;

  const participantDiv = document.getElementById(
    participantContainerId + '-container'
  );
  if (!participantDiv) {
    return;
  }
  groupContainer.removeChild(participantDiv);
}

function createVideoEl(id) {
  const videoEl = document.createElement('video');
  videoEl.id = id;
  videoEl.autoplay = true;
  videoEl.playsInline = true;
  videoEl.srcObject = new MediaStream();
  return videoEl;
}

function createContainer(id) {
  const participantContainer = document.createElement('div');
  participantContainer.classList = 'participant-container';
  participantContainer.id = id + '-container';

  return participantContainer;
}
```

### 接続されたカメラとマイクを表示する
<a name="integrating-snap-web-display-cameras-microphones"></a>

次に `media-devices.js` を作成します。これには、デバイスに接続されているカメラとマイクを表示するためのヘルパーメソッドが含まれます。

#### JavaScript
<a name="integrating-snap-web-display-cameras-microphones-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

/**
 * Returns an initial list of devices populated on the page selects
 */
async function initializeDeviceSelect() {
  const videoSelectEl = document.getElementById('video-devices');
  videoSelectEl.disabled = false;

  const { videoDevices, audioDevices } = await getDevices();
  videoDevices.forEach((device, index) => {
    videoSelectEl.options[index] = new Option(device.label, device.deviceId);
  });

  const audioSelectEl = document.getElementById('audio-devices');

  audioSelectEl.disabled = false;
  audioDevices.forEach((device, index) => {
    audioSelectEl.options[index] = new Option(device.label, device.deviceId);
  });
}

/**
 * Returns all devices available on the current device
 */
async function getDevices() {
  // Prevents issues on Safari/FF so devices are not blank
  await navigator.mediaDevices.getUserMedia({ video: true, audio: true });

  const devices = await navigator.mediaDevices.enumerateDevices();
  // Get all video devices
  const videoDevices = devices.filter((d) => d.kind === 'videoinput');
  if (!videoDevices.length) {
    console.error('No video devices found.');
  }

  // Get all audio devices
  const audioDevices = devices.filter((d) => d.kind === 'audioinput');
  if (!audioDevices.length) {
    console.error('No audio devices found.');
  }

  return { videoDevices, audioDevices };
}

async function getCamera(deviceId) {
  // Use Max Width and Height
  return navigator.mediaDevices.getUserMedia({
    video: {
      deviceId: deviceId ? { exact: deviceId } : null,
    },
    audio: false,
  });
}

async function getMic(deviceId) {
  return navigator.mediaDevices.getUserMedia({
    video: false,
    audio: {
      deviceId: deviceId ? { exact: deviceId } : null,
    },
  });
}
```

### Camera Kit セッションを作成する
<a name="integrating-snap-web-camera-kit-session"></a>

`stages.js` を作成します。これには、カメラフィードに Lens を適用し、そのフィードをステージに公開するためのロジックが含まれます。次のコードブロックをコピーして `stages.js` に貼り付けることをお勧めします。その後、コードの内容を 1 つずつ確認すると、後続のセクションで何が起こっているか把握できます。

#### JavaScript
<a name="integrating-snap-web-camera-kit-session-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

const {
  Stage,
  LocalStageStream,
  SubscribeType,
  StageEvents,
  ConnectionState,
  StreamType,
} = IVSBroadcastClient;

import {
  bootstrapCameraKit,
  createMediaStreamSource,
  Transform2D,
} from '@snap/camera-kit';

let cameraButton = document.getElementById('camera-control');
let micButton = document.getElementById('mic-control');
let joinButton = document.getElementById('join-button');
let leaveButton = document.getElementById('leave-button');

let controls = document.getElementById('local-controls');
let videoDevicesList = document.getElementById('video-devices');
let audioDevicesList = document.getElementById('audio-devices');

let lensSelector = document.getElementById('lens-selector');
let session;
let availableLenses = [];

// Stage management
let stage;
let joining = false;
let connected = false;
let localCamera;
let localMic;
let cameraStageStream;
let micStageStream;

const liveRenderTarget = document.getElementById('canvas');

const init = async () => {
  await initializeDeviceSelect();

  const cameraKit = await bootstrapCameraKit({
    apiToken: 'INSERT_YOUR_API_TOKEN_HERE',
  });

  session = await cameraKit.createSession({ liveRenderTarget });
  const { lenses } = await cameraKit.lensRepository.loadLensGroups([
    'INSERT_YOUR_LENS_GROUP_ID_HERE',
  ]);

  availableLenses = lenses;
  populateLensSelector(lenses);

  const snapStream = liveRenderTarget.captureStream();

  lensSelector.addEventListener('change', handleLensChange);
  lensSelector.disabled = true;
  cameraButton.addEventListener('click', () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? 'Show Camera' : 'Hide Camera';
  });

  micButton.addEventListener('click', () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? 'Unmute Mic' : 'Mute Mic';
  });

  joinButton.addEventListener('click', () => {
    joinStage(session, snapStream);
  });

  leaveButton.addEventListener('click', () => {
    leaveStage();
  });
};

async function setCameraKitSource(session, mediaStream) {
  const source = createMediaStreamSource(mediaStream);
  await session.setSource(source);
  source.setTransform(Transform2D.MirrorX);
  session.play();
}

const populateLensSelector = (lenses) => {
  lensSelector.innerHTML = '<option selected disabled>Choose Lens</option>';

  lenses.forEach((lens, index) => {
    const option = document.createElement('option');
    option.value = index;
    option.text = lens.name || `Lens ${index + 1}`;
    lensSelector.appendChild(option);
  });
};

const handleLensChange = (event) => {
  const selectedIndex = parseInt(event.target.value);
  if (session && availableLenses[selectedIndex]) {
    session.applyLens(availableLenses[selectedIndex]);
  }
};

const joinStage = async (session, snapStream) => {
  if (connected || joining) {
    return;
  }
  joining = true;

  const token = document.getElementById('token').value;

  if (!token) {
    window.alert('Please enter a participant token');
    joining = false;
    return;
  }

  // Retrieve the User Media currently set on the page
  localCamera = await getCamera(videoDevicesList.value);
  localMic = await getMic(audioDevicesList.value);
  await setCameraKitSource(session, localCamera);

  // Create StageStreams for Audio and Video
  cameraStageStream = new LocalStageStream(snapStream.getVideoTracks()[0]);
  micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

  const strategy = {
    stageStreamsToPublish() {
      return [cameraStageStream, micStageStream];
    },
    shouldPublishParticipant() {
      return true;
    },
    shouldSubscribeToParticipant() {
      return SubscribeType.AUDIO_VIDEO;
    },
  };

  stage = new Stage(token, strategy);

  // Other available events:
  // https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events
  stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
    connected = state === ConnectionState.CONNECTED;

    if (connected) {
      joining = false;
      controls.classList.remove('hidden');
      lensSelector.disabled = false;
    } else {
      controls.classList.add('hidden');
      lensSelector.disabled = true;
    }
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
    console.log('Participant Joined:', participant);
  });

  stage.on(
    StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED,
    (participant, streams) => {
      console.log('Participant Media Added: ', participant, streams);

      let streamsToDisplay = streams;

      if (participant.isLocal) {
        // Ensure to exclude local audio streams, otherwise echo will occur
        streamsToDisplay = streams.filter(
          (stream) => stream.streamType === StreamType.VIDEO
        );
      }

      const videoEl = setupParticipant(participant);
      streamsToDisplay.forEach((stream) =>
        videoEl.srcObject.addTrack(stream.mediaStreamTrack)
      );
    }
  );

  stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
    console.log('Participant Left: ', participant);
    teardownParticipant(participant);
  });

  try {
    await stage.join();
  } catch (err) {
    joining = false;
    connected = false;
    console.error(err.message);
  }
};

const leaveStage = async () => {
  stage.leave();

  joining = false;
  connected = false;

  cameraButton.innerText = 'Hide Camera';
  micButton.innerText = 'Mute Mic';
  controls.classList.add('hidden');
};

init();
```

このファイルの最初の部分では、Broadcast SDK と Camera Kit Web SDK をインポートし、各 SDK で使用する変数を初期化します。[Camera Kit Web SDK をブートストラップ](https://kit.snapchat.com/reference/CameraKit/web/0.7.0/index.html#bootstrapping-the-sdk)した後に `createSession` を呼び出すことで、Camera Kit セッションを作成します。キャンバス要素オブジェクトがセッションに渡されることに注意してください。これにより、Camera Kit はそのキャンバスにレンダリングするよう指示されます。

#### JavaScript
<a name="integrating-snap-web-camera-kit-session-code-2"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

const {
  Stage,
  LocalStageStream,
  SubscribeType,
  StageEvents,
  ConnectionState,
  StreamType,
} = IVSBroadcastClient;

import {
  bootstrapCameraKit,
  createMediaStreamSource,
  Transform2D,
} from '@snap/camera-kit';

let cameraButton = document.getElementById('camera-control');
let micButton = document.getElementById('mic-control');
let joinButton = document.getElementById('join-button');
let leaveButton = document.getElementById('leave-button');

let controls = document.getElementById('local-controls');
let videoDevicesList = document.getElementById('video-devices');
let audioDevicesList = document.getElementById('audio-devices');

let lensSelector = document.getElementById('lens-selector');
let session;
let availableLenses = [];

// Stage management
let stage;
let joining = false;
let connected = false;
let localCamera;
let localMic;
let cameraStageStream;
let micStageStream;

const liveRenderTarget = document.getElementById('canvas');

const init = async () => {
  await initializeDeviceSelect();

  const cameraKit = await bootstrapCameraKit({
    apiToken: 'INSERT_YOUR_API_TOKEN_HERE',
  });

  session = await cameraKit.createSession({ liveRenderTarget });
```

### レンズの取得とレンズセレクターへの入力
<a name="integrating-snap-web-fetch-apply-lens"></a>

レンズを取得するには、Lens グループ ID のプレースホルダーを、[Camera Kit デベロッパーポータル](https://camera-kit.snapchat.com/)にある独自の ID に置き換えます。後で作成する `populateLensSelector()` 関数を使用して、レンズ選択ドロップダウンに入力します。

#### JavaScript
<a name="integrating-snap-web-fetch-apply-lens-code"></a>

```
session = await cameraKit.createSession({ liveRenderTarget });
  const { lenses } = await cameraKit.lensRepository.loadLensGroups([
    'INSERT_YOUR_LENS_GROUP_ID_HERE',
  ]);

  availableLenses = lenses;
  populateLensSelector(lenses);
```

### Camera Kit セッションからの出力をキャンバスにレンダリングする
<a name="integrating-snap-web-render-output-to-canvas"></a>

[captureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) メソッドを使用して、キャンバスのコンテンツの `MediaStream` を返します。キャンバスには、Lens が適用されたカメラフィードのビデオストリームが含まれます。また、カメラとマイクをミュートするボタン用のイベントリスナーと、ステージに参加したりステージから退出したりするためのイベントリスナーも追加します。ステージに参加するためのイベントリスナーでは、Camera Kit セッションとキャンバスからの `MediaStream` を渡して、ステージに公開できるようにします。

#### JavaScript
<a name="integrating-snap-web-render-output-to-canvas-code"></a>

```
const snapStream = liveRenderTarget.captureStream();

  lensSelector.addEventListener('change', handleLensChange);
  lensSelector.disabled = true;
  cameraButton.addEventListener('click', () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? 'Show Camera' : 'Hide Camera';
  });

  micButton.addEventListener('click', () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? 'Unmute Mic' : 'Mute Mic';
  });

  joinButton.addEventListener('click', () => {
    joinStage(session, snapStream);
  });

  leaveButton.addEventListener('click', () => {
    leaveStage();
  });
};
```

### [レンズ] ドロップダウンに入力する関数を作成する
<a name="integrating-snap-web-populate-lens-dropdown"></a>

次の関数を作成し、先ほど取得したレンズを**レンズ**セレクターに入力します。**レンズ**セレクターは、カメラフィードに適用するレンズをリストから選択できるページ上の UI 要素です。また、`handleLensChange` コールバック関数を作成して、**[レンズ]** ドロップダウンから選択したときに指定したレンズを適用することもできます。

#### JavaScript
<a name="integrating-snap-web-populate-lens-dropdown-code"></a>

```
const populateLensSelector = (lenses) => {
  lensSelector.innerHTML = '<option selected disabled>Choose Lens</option>';

  lenses.forEach((lens, index) => {
    const option = document.createElement('option');
    option.value = index;
    option.text = lens.name || `Lens ${index + 1}`;
    lensSelector.appendChild(option);
  });
};

const handleLensChange = (event) => {
  const selectedIndex = parseInt(event.target.value);
  if (session && availableLenses[selectedIndex]) {
    session.applyLens(availableLenses[selectedIndex]);
  }
};
```

### Camera Kit にレンダリング用のメディアソースを供給し、LocalStageStream を公開します。
<a name="integrating-snap-web-publish-localstagestream"></a>

Lens を適用したビデオストリームを公開するには、以前にキャンバスからキャプチャした `MediaStream` を渡す `setCameraKitSource` という名前の関数を作成します。ローカルカメラフィードをまだ組み込んでいないので、キャンバスからの `MediaStream` は今のところ何もしていません。`getCamera` ヘルパーメソッドを呼び出して `localCamera` に割り当てることで、ローカルカメラフィードを組み込むことができます。その後、ローカルカメラフィード (`localCamera` 経由) とセッションオブジェクトを `setCameraKitSource` に渡すことができます。`setCameraKitSource` 関数は `createMediaStreamSource` の呼び出しによってローカルカメラフィードを [CameraKit のメディアソース](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#creating-a-camerakitsource)に変換します。次に、`CameraKit` のメディアソースは前面カメラをミラーリングするように[変換](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#2d-transforms)されます。次に、Lens 効果がメディアソースに適用され、`session.play()` の呼び出しによって出力キャンバスにレンダリングされます。

キャンバスからキャプチャした `MediaStream` に Lens が適用されたので、ステージへの公開に進むことができます。そのためには、`MediaStream` のビデオトラックを使用して `LocalStageStream` を作成します。その後、`LocalStageStream` のインスタンスを `StageStrategy` に渡して公開できます。

#### JavaScript
<a name="integrating-snap-web-publish-localstagestream-code"></a>

```
async function setCameraKitSource(session, mediaStream) {
  const source = createMediaStreamSource(mediaStream);
  await session.setSource(source);
  source.setTransform(Transform2D.MirrorX);
  session.play();
}

const joinStage = async (session, snapStream) => {
  if (connected || joining) {
    return;
  }
  joining = true;

  const token = document.getElementById('token').value;

  if (!token) {
    window.alert('Please enter a participant token');
    joining = false;
    return;
  }

  // Retrieve the User Media currently set on the page
  localCamera = await getCamera(videoDevicesList.value);
  localMic = await getMic(audioDevicesList.value);
  await setCameraKitSource(session, localCamera);
  // Create StageStreams for Audio and Video
  // cameraStageStream = new LocalStageStream(localCamera.getVideoTracks()[0]);
  cameraStageStream = new LocalStageStream(snapStream.getVideoTracks()[0]);
  micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

  const strategy = {
    stageStreamsToPublish() {
      return [cameraStageStream, micStageStream];
    },
    shouldPublishParticipant() {
      return true;
    },
    shouldSubscribeToParticipant() {
      return SubscribeType.AUDIO_VIDEO;
    },
  };
```

以下の残りのコードは、ステージを作成および管理するためのものです。

#### JavaScript
<a name="integrating-snap-web-create-manage-stage-code"></a>

```
stage = new Stage(token, strategy);

  // Other available events:
  // https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events

  stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
    connected = state === ConnectionState.CONNECTED;

    if (connected) {
      joining = false;
      controls.classList.remove('hidden');
    } else {
      controls.classList.add('hidden');
    }
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
    console.log('Participant Joined:', participant);
  });

  stage.on(
    StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED,
    (participant, streams) => {
      console.log('Participant Media Added: ', participant, streams);

      let streamsToDisplay = streams;

      if (participant.isLocal) {
        // Ensure to exclude local audio streams, otherwise echo will occur
        streamsToDisplay = streams.filter(
          (stream) => stream.streamType === StreamType.VIDEO
        );
      }

      const videoEl = setupParticipant(participant);
      streamsToDisplay.forEach((stream) =>
        videoEl.srcObject.addTrack(stream.mediaStreamTrack)
      );
    }
  );

  stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
    console.log('Participant Left: ', participant);
    teardownParticipant(participant);
  });

  try {
    await stage.join();
  } catch (err) {
    joining = false;
    connected = false;
    console.error(err.message);
  }
};

const leaveStage = async () => {
  stage.leave();

  joining = false;
  connected = false;

  cameraButton.innerText = 'Hide Camera';
  micButton.innerText = 'Mute Mic';
  controls.classList.add('hidden');
};

init();
```

### package.json を作成する
<a name="integrating-snap-web-package-json"></a>

`package.json` を作成して、次の JSON 設定を追加します。このファイルは依存関係を定義するもので、コードをバンドルするためのスクリプトコマンドが含まれています。

#### JSON 設定
<a name="integrating-snap-web-package-json-code"></a>

```
{
  "dependencies": {
    "@snap/camera-kit": "^0.10.0"
  },
  "name": "ivs-stages-with-snap-camerakit",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "webpack": "^5.95.0",
    "webpack-cli": "^5.1.4"
  }
}
```

### Webpack 設定ファイルを作成する
<a name="integrating-snap-web-webpack-config"></a>

`webpack.config.js` を作成して次のコードを追加します。これにより、これまでに作成したコードがバンドルされ、import ステートメントを使用して Camera Kit を使用できるようになります。

#### JavaScript
<a name="integrating-snap-web-webpack-config-code"></a>

```
const path = require('path');
module.exports = {
  entry: ['./stage.js'],
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};
```

最後に、`npm run build` を実行して、Webpack 設定ファイルで定義されている JavaScript をバンドルします。テスト目的であれば、ローカルコンピュータから HTML と JavaScript を提供することができます。この例では、Python の `http.server` モジュールを使用します。

### HTTPS サーバーのセットアップとテスト
<a name="integrating-snap-web-https-server-test"></a>

コードをテストするには、HTTPS サーバーをセットアップする必要があります。ウェブアプリケーションの Snap Camera Kit SDK との統合をローカルで開発およびテストするために HTTPS サーバーを使用すると、オリジン間リソース共有 (CORS、Cross-Origin Resource Sharing) の問題を回避できます。

ターミナルを開き、この時点までのすべてのコードを作成したディレクトリに移動します。次のコマンドを実行して、自己署名 SSL/TLS 証明書とプライベートキーを生成します。

```
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
```

これにより、`key.pem` (プライベートキー) と `cert.pem` (自己署名証明書) の 2 つのファイルが作成されます。`https_server.py` という名前の新しい Python ファイルを作成し、次のコードを追加します。

#### Python
<a name="integrating-snap-web-https-server-test-code"></a>

```
import http.server
import ssl

# Set the directory to serve files from
DIRECTORY = '.'

# Create the HTTPS server
server_address = ('', 4443)
httpd = http.server.HTTPServer(
    server_address, http.server.SimpleHTTPRequestHandler)

# Wrap the socket with SSL/TLS
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain('cert.pem', 'key.pem')
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)

print(f'Starting HTTPS server on https://localhost:4443, serving {DIRECTORY}')
httpd.serve_forever()
```

ターミナルを開き、`https_server.py` ファイルを作成したディレクトリに移動し、次のコマンドを実行します。

```
python3 https_server.py
```

これにより、https://localhost:4443 で HTTPS サーバーが起動し、現在のディレクトリからファイルが提供されます。`cert.pem` および `key.pem` ファイルが `https_server.py` ファイルと同じディレクトリにあることを確認します。

ブラウザを開き、https://localhost:4443 に移動します。これは自己署名 SSL/TLS 証明書であるため、お使いのウェブブラウザでは信頼されず、警告が表示されます。これはテスト目的のみであるため、警告をバイパスできます。次に、前に指定したスナップレンズの AR 効果が、カメラフィードに画面に表示されます。

Python の組み込み `http.server` モジュールと `ssl` モジュールを使用したこの設定は、ローカルでの開発およびテスト目的には適していますが、本番環境には推奨されません。この設定で使用される自己署名 SSL/TLS 証明書は、ウェブブラウザやその他のクライアントで信頼されていないため、ユーザーがサーバーにアクセスするとセキュリティ警告が表示されます。また、この例では Python の組み込み http.server モジュールと ssl モジュールを使用していますが、別の HTTPS サーバーソリューションを使用することもできます。

## Android
<a name="integrating-snap-android"></a>

Snap の Camera Kit SDK を IVS Android Broadcast SDK と統合するには、Camera Kit SDK をインストールし、Camera Kit セッションを初期化し、Lens を適用して、Camera Kit セッションの出力をカスタム画像入力ソースに送る必要があります。

Camera Kit SDK をインストールするには、モジュールの `build.gradle` ファイルに以下を追加します。`$cameraKitVersion` を [Camera Kit SDK の最新バージョン](https://docs.snap.com/camera-kit/integrate-sdk/mobile/changelog-mobile)に置き換えます。

### Java
<a name="integrating-snap-android-install-camerakit-sdk-code"></a>

```
implementation "com.snap.camerakit:camerakit:$cameraKitVersion"
```

`cameraKitSession` を初期化および取得します。Camera Kit には Android の [CameraX](https://developer.android.com/media/camera/camerax) API 用の便利なラッパーも用意されているため、Camera Kit で CameraX を使用する際に複雑なロジックを記述する必要はありません。`CameraXImageProcessorSource` オブジェクトを [ImageProcessor](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-image-processor/index.html) の[ソース](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-source/index.html)として使用して、カメラプレビューストリーミングフレームを開始できます。

### Java
<a name="integrating-snap-android-initialize-camerakitsession-code"></a>

```
 protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        // Camera Kit support implementation of ImageProcessor that is backed by CameraX library:
        // https://developer.android.com/training/camerax
        CameraXImageProcessorSource imageProcessorSource = new CameraXImageProcessorSource( 
            this /*context*/, this /*lifecycleOwner*/
        );
        imageProcessorSource.startPreview(true /*cameraFacingFront*/);

        cameraKitSession = Sessions.newBuilder(this)
                .imageProcessorSource(imageProcessorSource)
                .attachTo(findViewById(R.id.camerakit_stub))
                .build();
    }
```

### Lens を取得して適用する
<a name="integrating-snap-android-fetch-apply-lenses"></a>

[Camera Kit デベロッパーポータル](https://camera-kit.snapchat.com/)で、Lens とそのカルーセル内での順序を設定できます。

#### Java
<a name="integrating-snap-android-configure-lenses-code"></a>

```
// Fetch lenses from repository and apply them
 // Replace LENS_GROUP_ID with Lens Group ID from https://camera-kit.snapchat.com
cameraKitSession.getLenses().getRepository().get(new Available(LENS_GROUP_ID), available -> {
     Log.d(TAG, "Available lenses: " + available);
     Lenses.whenHasFirst(available, lens -> cameraKitSession.getLenses().getProcessor().apply(lens, result -> {
          Log.d(TAG,  "Apply lens [" + lens + "] success: " + result);
      }));
});
```

ブロードキャストするには、処理済みのフレームをカスタム画像ソースの基盤 `Surface` に送信します。`DeviceDiscovery` オブジェクトを使用して `CustomImageSource` を作成し、`SurfaceSource` を返します。その後、`CameraKit` セッションからの出力を `SurfaceSource` が提供する基盤 `Surface` にレンダリングできます。

#### Java
<a name="integrating-snap-android-broadcast-code"></a>

```
val publishStreams = ArrayList<LocalStageStream>()

val deviceDiscovery = DeviceDiscovery(applicationContext)
val customSource = deviceDiscovery.createImageInputSource(BroadcastConfiguration.Vec2(720f, 1280f))

cameraKitSession.processor.connectOutput(outputFrom(customSource.inputSurface))
val customStream = ImageLocalStageStream(customSource)

// After rendering the output from a Camera Kit session to the Surface, you can 
// then return it as a LocalStageStream to be published by the Broadcast SDK
val customStream: ImageLocalStageStream = ImageLocalStageStream(surfaceSource)
publishStreams.add(customStream)

@Override
fun stageStreamsToPublishForParticipant(stage: Stage, participantInfo: ParticipantInfo): List<LocalStageStream> = publishStreams
```

# IVS Broadcast SDK で背景の置換を使用する
<a name="broadcast-3p-camera-filters-background-replacement"></a>

背景の置換は、ライブストリームのクリエイターが背景を変更できるようにするカメラフィルターの一種です。次の図に示すように、背景の置換には以下が必要です。

1. ライブカメラフィードからカメラ画像を取得します。

1. Google ML Kit を使用して前景コンポーネントと背景コンポーネントに分割します。

1. 生成された分割マスクをカスタムの背景画像と組み合わせます。

1. それをカスタム画像ソースに渡してブロードキャストします。

![\[背景の置換を実装するためのワークフロー。\]](http://docs.aws.amazon.com/ja_jp/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Background_Replacement.png)


## Web
<a name="background-replacement-web"></a>

このセクションは、読者が既に [Web Broadcast SDK を使用した動画の公開とサブスクリプション](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-pub-sub-web.html)に慣れていることを前提としています。

ライブストリームの背景をカスタム画像に置き換えるには、[MediaPipe Image Segmenter](https://developers.google.com/mediapipe/solutions/vision/image_segmenter) で [selfie segmentation model](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model) を使用します。これは動画フレーム内のどのピクセルが前景か背景かを識別する機械学習モデルです。その後、ビデオフィードの前景ピクセルを新しい背景を表すカスタム画像にコピーすることで、モデルの結果を使用してライブストリームの背景を置き換えることができます。

背景の置換を IVS Real-Time Streaming Web Broadcast SDK と統合するには、以下が必要です。

1. MediaPipe と Webpack をインストールします。(この例では Webpack をバンドラーとして使用していますが、任意のバンドラーを使用できます)

1. `index.html` を作成します。

1. メディア要素を追加します。

1. スクリプトタグを追加します。

1. `app.js` を作成します。

1. カスタム背景画像を読み込みます。

1. `ImageSegmenter` のインスタンスを作成します。

1. ビデオフィードをキャンバスにレンダリングします。

1. 背景置換ロジックを作成します。

1. Webpack 設定ファイルを作成します。

1. JavaScript ファイルをバンドルします。

### MediaPipe と Webpack をインストールする
<a name="background-replacement-web-install-mediapipe-webpack"></a>

まず、`@mediapipe/tasks-vision` と `webpack` npm パッケージをインストールします。以下の例では、Webpack を JavaScript バンドラーとして使用しています。必要に応じて別のバンドラーを使用できます。

#### JavaScript
<a name="background-replacement-web-install-mediapipe-webpack-code"></a>

```
npm i @mediapipe/tasks-vision webpack webpack-cli
```

また、次のように、ビルドスクリプトとして `webpack` を指定するように `package.json` も必ず更新します。

#### JavaScript
<a name="background-replacement-web-update-package-json-code"></a>

```
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
```

### index.html を作成する
<a name="background-replacement-web-create-index"></a>

次に、HTML 共通スクリプトを作成し、Web Broadcast SDK をスクリプトタグとしてインポートします。次のコードでは、`<SDK version>` を、使用している Broadcast SDK のバージョンに必ず置き換えてください。

#### JavaScript
<a name="background-replacement-web-create-index-code"></a>

```
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <!-- Import the SDK -->
  <script src="https://web-broadcast.live-video.net/<SDK version>/amazon-ivs-web-broadcast.js"></script>
</head>

<body>

</body>
</html>
```

### メディア要素を追加する
<a name="background-replacement-web-add-media-elements"></a>

次に、ボディタグ内にビデオ要素と 2 つのキャンバス要素を追加します。ビデオ要素にはライブカメラフィードが含まれ、MediaPipe Image Segmenter への入力として使用されます。最初のキャンバス要素は、ブロードキャストされるフィードのプレビューをレンダリングするために使用されます。2 番目のキャンバス要素は、背景として使用されるカスタム画像のレンダリングに使用されます。カスタム画像を含む 2 番目のキャンバスは、そこから最終的なキャンバスにピクセルをプログラムでコピーするためのソースとしてのみ使用されるため、表示されなくなります。

#### JavaScript
<a name="background-replacement-web-add-media-elements-code"></a>

```
<div class="row local-container">
      <video id="webcam" autoplay style="display: none"></video>
    </div>
    <div class="row local-container">
      <canvas id="canvas" width="640px" height="480px"></canvas>

      <div class="column" id="local-media"></div>
      <div class="static-controls hidden" id="local-controls">
        <button class="button" id="mic-control">Mute Mic</button>
        <button class="button" id="camera-control">Mute Camera</button>
      </div>
    </div>
    <div class="row local-container">
      <canvas id="background" width="640px" height="480px" style="display: none"></canvas>
    </div>
```

### スクリプトタグを追加する
<a name="background-replacement-web-add-script-tag"></a>

スクリプトタグを追加して、背景の置換を行うコードを含むバンドルされた JavaScript ファイルをロードし、ステージに公開します。

```
<script src="./dist/bundle.js"></script>
```

### app.js の作成
<a name="background-replacement-web-create-appjs"></a>

次に、JavaScript ファイルを作成して、HTML ページに作成されたキャンバス要素とビデオ要素の要素オブジェクトを取得します。`ImageSegmenter` モジュールと `FilesetResolver` モジュールをインポートします。`ImageSegmenter` モジュールは分割タスクの実行に使用されます。

#### JavaScript
<a name="create-appjs-import-imagesegmenter-fileresolver-code"></a>

```
const canvasElement = document.getElementById("canvas");
const background = document.getElementById("background");
const canvasCtx = canvasElement.getContext("2d");
const backgroundCtx = background.getContext("2d");
const video = document.getElementById("webcam");

import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision";
```

次に、ユーザーのカメラから MediaStream を取得する `init()` という関数を作成し、カメラフレームの読み込みが完了するたびにコールバック関数を呼び出します。ステージに参加したりステージから退出したりするボタンのイベントリスナーを追加します。

ステージに参加するときは、`segmentationStream` という名前の変数を渡すことに注意してください。これはキャンバス要素からキャプチャされたビデオストリームで、背景を表すカスタム画像に前景画像が重なっています。その後、このカスタムストリームを使用して `LocalStageStream` のインスタンスを作成し、ステージに公開できます。

#### JavaScript
<a name="create-appjs-create-init-code"></a>

```
const init = async () => {
  await initializeDeviceSelect();

  cameraButton.addEventListener("click", () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? "Show Camera" : "Hide Camera";
  });

  micButton.addEventListener("click", () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? "Unmute Mic" : "Mute Mic";
  });

  localCamera = await getCamera(videoDevicesList.value);
  const segmentationStream = canvasElement.captureStream();

  joinButton.addEventListener("click", () => {
    joinStage(segmentationStream);
  });

  leaveButton.addEventListener("click", () => {
    leaveStage();
  });
};
```

### カスタム背景画像を読み込む
<a name="background-replacement-web-background-image"></a>

`init` 関数の下部に、`initBackgroundCanvas` という名前の関数を呼び出すコードを追加します。これにより、ローカルファイルからカスタム画像が読み込まれ、キャンバスにレンダリングされます。この関数は次のステップで定義します。ユーザーのカメラから取得した `MediaStream` をビデオオブジェクトに割り当てます。その後、このビデオオブジェクトは Image Segmenter に渡されます。また、`renderVideoToCanvas` という名前の関数をビデオフレームの読み込みが完了するたびに呼び出されるコールバック関数として設定します。この関数は後のステップで定義します。

#### JavaScript
<a name="background-replacement-web-load-background-image-code"></a>

```
initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
```

ローカルファイルから画像を読み込む `initBackgroundCanvas` 関数を実装しましょう。この例では、カスタム背景としてビーチの画像を使用します。カスタム画像を含むキャンバスは、カメラフィードを含むキャンバス要素の前景ピクセルとマージされるため、表示されなくなります。

#### JavaScript
<a name="background-replacement-web-implement-initBackgroundCanvas-code"></a>

```
const initBackgroundCanvas = () => {
  let img = new Image();
  img.src = "beach.jpg";

  img.onload = () => {
    backgroundCtx.clearRect(0, 0, canvas.width, canvas.height);
    backgroundCtx.drawImage(img, 0, 0);
  };
};
```

### ImageSegmenter のインスタンスを作成する
<a name="background-replacement-web-imagesegmenter"></a>

次に、`ImageSegmenter` のインスタンスを作成します。これにより、画像が分割され、その結果がマスクとして返されます。`ImageSegmenter` のインスタンスを作成するときは、[selfie segmentation model](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model) を使用します。

#### JavaScript
<a name="background-replacement-web-imagesegmenter-code"></a>

```
const createImageSegmenter = async () => {
  const audio = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm");

  imageSegmenter = await ImageSegmenter.createFromOptions(audio, {
    baseOptions: {
      modelAssetPath: "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
      delegate: "GPU",
    },
    runningMode: "VIDEO",
    outputCategoryMask: true,
  });
};
```

### ビデオフィードをキャンバスにレンダリングする
<a name="background-replacement-web-render-video-to-canvas"></a>

次に、ビデオフィードを他のキャンバス要素にレンダリングする関数を作成します。ビデオフィードをキャンバスにレンダリングする必要があります。そうすると、Canvas 2D API を使用してそこから前景ピクセルを抽出できるようになります。また、その際、[segmentForVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) メソッドを使用してビデオフレームを `ImageSegmenter` のインスタンスに渡し、ビデオフレーム内の前景と背景を分割します。[segmentForVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) メソッドが戻ると、背景の置換を行うためのカスタムコールバック関数である `replaceBackground` が呼び出されます。

#### JavaScript
<a name="background-replacement-web-render-video-to-canvas-code"></a>

```
const renderVideoToCanvas = async () => {
  if (video.currentTime === lastWebcamTime) {
    window.requestAnimationFrame(renderVideoToCanvas);
    return;
  }
  lastWebcamTime = video.currentTime;
  canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);

  if (imageSegmenter === undefined) {
    return;
  }

  let startTimeMs = performance.now();

  imageSegmenter.segmentForVideo(video, startTimeMs, replaceBackground);
};
```

### 背景の置換ロジックを作成する
<a name="background-replacement-web-logic"></a>

カスタム背景画像をカメラフィードの前景と結合して背景を置き換える `replaceBackground` 関数を作成します。この関数はまず、先に作成した 2 つのキャンバス要素から、カスタム背景画像とビデオフィードの基盤ピクセルデータを取得します。次に、`ImageSegmenter` から提供されたマスクを繰り返し適用します。これにより、どのピクセルが前景にあるかがわかります。マスクを繰り返し適用しながら、ユーザーのカメラフィードを含むピクセルを、対応する背景ピクセルデータに選択的にコピーします。これが完了すると、前景がコピーされた最終的なピクセルデータを背景に変換し、キャンバスに描画します。

#### JavaScript
<a name="background-replacement-web-logic-create-replacebackground-code"></a>

```
function replaceBackground(result) {
  let imageData = canvasCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  let backgroundData = backgroundCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  const mask = result.categoryMask.getAsFloat32Array();
  let j = 0;

  for (let i = 0; i < mask.length; ++i) {
    const maskVal = Math.round(mask[i] * 255.0);

    j += 4;
  // Only copy pixels on to the background image if the mask indicates they are in the foreground
    if (maskVal < 255) {
      backgroundData[j] = imageData[j];
      backgroundData[j + 1] = imageData[j + 1];
      backgroundData[j + 2] = imageData[j + 2];
      backgroundData[j + 3] = imageData[j + 3];
    }
  }

 // Convert the pixel data to a format suitable to be drawn to a canvas
  const uint8Array = new Uint8ClampedArray(backgroundData.buffer);
  const dataNew = new ImageData(uint8Array, video.videoWidth, video.videoHeight);
  canvasCtx.putImageData(dataNew, 0, 0);
  window.requestAnimationFrame(renderVideoToCanvas);
}
```

参考までに、上記のすべてのロジックを含む完全な `app.js` ファイルを次に示します。

#### JavaScript
<a name="background-replacement-web-logic-app-js-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

// All helpers are expose on 'media-devices.js' and 'dom.js'
const { setupParticipant } = window;

const { Stage, LocalStageStream, SubscribeType, StageEvents, ConnectionState, StreamType } = IVSBroadcastClient;
const canvasElement = document.getElementById("canvas");
const background = document.getElementById("background");
const canvasCtx = canvasElement.getContext("2d");
const backgroundCtx = background.getContext("2d");
const video = document.getElementById("webcam");

import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision";

let cameraButton = document.getElementById("camera-control");
let micButton = document.getElementById("mic-control");
let joinButton = document.getElementById("join-button");
let leaveButton = document.getElementById("leave-button");

let controls = document.getElementById("local-controls");
let audioDevicesList = document.getElementById("audio-devices");
let videoDevicesList = document.getElementById("video-devices");

// Stage management
let stage;
let joining = false;
let connected = false;
let localCamera;
let localMic;
let cameraStageStream;
let micStageStream;
let imageSegmenter;
let lastWebcamTime = -1;

const init = async () => {
  await initializeDeviceSelect();

  cameraButton.addEventListener("click", () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? "Show Camera" : "Hide Camera";
  });

  micButton.addEventListener("click", () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? "Unmute Mic" : "Mute Mic";
  });

  localCamera = await getCamera(videoDevicesList.value);
  const segmentationStream = canvasElement.captureStream();

  joinButton.addEventListener("click", () => {
    joinStage(segmentationStream);
  });

  leaveButton.addEventListener("click", () => {
    leaveStage();
  });

  initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
};

const joinStage = async (segmentationStream) => {
  if (connected || joining) {
    return;
  }
  joining = true;

  const token = document.getElementById("token").value;

  if (!token) {
    window.alert("Please enter a participant token");
    joining = false;
    return;
  }

  // Retrieve the User Media currently set on the page
  localMic = await getMic(audioDevicesList.value);

  cameraStageStream = new LocalStageStream(segmentationStream.getVideoTracks()[0]);
  micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

  const strategy = {
    stageStreamsToPublish() {
      return [cameraStageStream, micStageStream];
    },
    shouldPublishParticipant() {
      return true;
    },
    shouldSubscribeToParticipant() {
      return SubscribeType.AUDIO_VIDEO;
    },
  };

  stage = new Stage(token, strategy);

  // Other available events:
  // https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events
  stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
    connected = state === ConnectionState.CONNECTED;

    if (connected) {
      joining = false;
      controls.classList.remove("hidden");
    } else {
      controls.classList.add("hidden");
    }
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
    console.log("Participant Joined:", participant);
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => {
    console.log("Participant Media Added: ", participant, streams);

    let streamsToDisplay = streams;

    if (participant.isLocal) {
      // Ensure to exclude local audio streams, otherwise echo will occur
      streamsToDisplay = streams.filter((stream) => stream.streamType === StreamType.VIDEO);
    }

    const videoEl = setupParticipant(participant);
    streamsToDisplay.forEach((stream) => videoEl.srcObject.addTrack(stream.mediaStreamTrack));
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
    console.log("Participant Left: ", participant);
    teardownParticipant(participant);
  });

  try {
    await stage.join();
  } catch (err) {
    joining = false;
    connected = false;
    console.error(err.message);
  }
};

const leaveStage = async () => {
  stage.leave();

  joining = false;
  connected = false;

  cameraButton.innerText = "Hide Camera";
  micButton.innerText = "Mute Mic";
  controls.classList.add("hidden");
};

function replaceBackground(result) {
  let imageData = canvasCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  let backgroundData = backgroundCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  const mask = result.categoryMask.getAsFloat32Array();
  let j = 0;

  for (let i = 0; i < mask.length; ++i) {
    const maskVal = Math.round(mask[i] * 255.0);

    j += 4;
    if (maskVal < 255) {
      backgroundData[j] = imageData[j];
      backgroundData[j + 1] = imageData[j + 1];
      backgroundData[j + 2] = imageData[j + 2];
      backgroundData[j + 3] = imageData[j + 3];
    }
  }
  const uint8Array = new Uint8ClampedArray(backgroundData.buffer);
  const dataNew = new ImageData(uint8Array, video.videoWidth, video.videoHeight);
  canvasCtx.putImageData(dataNew, 0, 0);
  window.requestAnimationFrame(renderVideoToCanvas);
}

const createImageSegmenter = async () => {
  const audio = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm");

  imageSegmenter = await ImageSegmenter.createFromOptions(audio, {
    baseOptions: {
      modelAssetPath: "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
      delegate: "GPU",
    },
    runningMode: "VIDEO",
    outputCategoryMask: true,
  });
};

const renderVideoToCanvas = async () => {
  if (video.currentTime === lastWebcamTime) {
    window.requestAnimationFrame(renderVideoToCanvas);
    return;
  }
  lastWebcamTime = video.currentTime;
  canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);

  if (imageSegmenter === undefined) {
    return;
  }

  let startTimeMs = performance.now();

  imageSegmenter.segmentForVideo(video, startTimeMs, replaceBackground);
};

const initBackgroundCanvas = () => {
  let img = new Image();
  img.src = "beach.jpg";

  img.onload = () => {
    backgroundCtx.clearRect(0, 0, canvas.width, canvas.height);
    backgroundCtx.drawImage(img, 0, 0);
  };
};

createImageSegmenter();
init();
```

### Webpack 設定ファイルを作成する
<a name="background-replacement-web-webpack-config"></a>

次の設定を Webpack 設定ファイルに追加して `app.js` をバンドルすると、インポート呼び出しが動作するようになります。

#### JavaScript
<a name="background-replacement-web-webpack-config-code"></a>

```
const path = require("path");
module.exports = {
  entry: ["./app.js"],
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
};
```

### JavaScript ファイルをバンドルする
<a name="background-replacement-web-bundle-javascript"></a>

```
npm run build
```

`index.html` を含むディレクトリから単純な HTTP サーバーを起動し、`localhost:8000` を開いて結果を確認します。

```
python3 -m http.server -d ./
```

## Android
<a name="background-replacement-android"></a>

ライブストリームの背景を置換するには、[Google ML Kit](https://developers.google.com/ml-kit/vision/selfie-segmentation) の selfie segmentation API を使用できます。selfie segmentation API はカメラ画像を入力として受け入れ、画像の各ピクセルの信頼度スコアを示すマスクを返します。これにより、画像のピクセルが前景にあったか背景にあったかがわかります。信頼度スコアに基づいて、背景画像または前景画像から対応するピクセルの色を取得できます。この処理は、マスク内のすべての信頼度スコアが検証されるまで続きます。その結果、背景画像のピクセルと前景のピクセルを組み合わせた新しいピクセルの色の配列が生成されます。

背景の置換を IVS Real-Time Streaming Android Broadcast SDK と統合するには、以下が必要です。

1. CameraX ライブラリと Google ML Kit をインストールします。

1. 共通スクリプト変数を初期化します。

1. カスタム画像ソースを作成します。

1. カメラフレームを管理します。

1. カメラフレームを Google ML Kit に渡します。

1. カメラフレームの前景をカスタム背景に重ねます。

1. 新しい画像をカスタム画像ソースにフィードします。

### CameraX ライブラリと Google ML Kit をインストールする
<a name="background-replacement-android-install-camerax-googleml"></a>

ライブカメラフィードから画像を抽出するには、Android の CameraX ライブラリを使用します。CameraX ライブラリと Google ML Kit をインストールするには、モジュールの `build.gradle` ファイルに以下を追加します。`${camerax_version}` と `${google_ml_kit_version}` をそれぞれ [CameraX](https://developer.android.com/jetpack/androidx/releases/camera) ライブラリと [Google ML Kit](https://developers.google.com/ml-kit/vision/selfie-segmentation/android) ライブラリの最新バージョンに置き換えます。

#### Java
<a name="background-replacement-android-install-camerax-googleml-code"></a>

```
implementation "com.google.mlkit:segmentation-selfie:${google_ml_kit_version}"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
```

以下のライブラリをインポートします。

#### Java
<a name="background-replacement-android-import-libraries-code"></a>

```
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.lifecycle.ProcessCameraProvider
import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions
```

### 共通スクリプト変数を初期化する
<a name="background-replacement-android-initialize-variables"></a>

`ImageAnalysis` のインスタンスと `ExecutorService` のインスタンスを初期化します。

#### Java
<a name="background-replacement-android-initialize-imageanalysis-executorservice-code"></a>

```
private lateinit var binding: ActivityMainBinding
private lateinit var cameraExecutor: ExecutorService
private var analysisUseCase: ImageAnalysis? = null
```

Segmenter インスタンスを [STREAM\$1MODE](https://developers.google.com/ml-kit/vision/selfie-segmentation/android#detector_mode) に初期化します。

#### Java
<a name="background-replacement-android-initialize-segmenter-code"></a>

```
private val options =
        SelfieSegmenterOptions.Builder()
            .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE)
            .build()

private val segmenter = Segmentation.getClient(options)
```

### カスタム画像ソースを作成する
<a name="background-replacement-android-create-image-source"></a>

アクティビティの `onCreate` メソッドで、`DeviceDiscovery` オブジェクトのインスタンスを作成し、カスタム画像ソースを作成します。カスタム画像ソースから提供された `Surface` が、カスタム背景画像に前景が重なった最終イメージを受け取ります。次に、カスタム画像ソースを使用して `ImageLocalStageStream` のインスタンスを作成します。その後、`ImageLocalStageStream` のインスタンス (この例では名前は `filterStream`) をステージに公開できます。ステージの設定方法については、「[IVS Broadcast SDK: Android ガイド](broadcast-android.md)」を参照してください。最後に、カメラの管理に使用するスレッドも作成します。

#### Java
<a name="background-replacement-android-create-image-source-code"></a>

```
var deviceDiscovery = DeviceDiscovery(applicationContext)
var customSource = deviceDiscovery.createImageInputSource( BroadcastConfiguration.Vec2(
720F, 1280F
))
var surface: Surface = customSource.inputSurface
var filterStream = ImageLocalStageStream(customSource)

cameraExecutor = Executors.newSingleThreadExecutor()
```

### カメラフレームを管理する
<a name="background-replacement-android-camera-frames"></a>

次に、カメラを初期化する関数を作成します。この関数は CameraX ライブラリを使用して、ライブカメラフィードから画像を抽出します。まず、`cameraProviderFuture` という `ProcessCameraProvider` のインスタンスを作成します。このオブジェクトは、カメラプロバイダーを取得した将来の結果を表します。次に、プロジェクトから画像をビットマップとして読み込みます。この例では、ビーチの画像を背景として使用していますが、どのような画像でもかまいません。

次に、`cameraProviderFuture` にリスナーを追加します。このリスナーには、カメラが使用可能になったとき、またはカメラプロバイダーの取得中にエラーが発生した場合に通知されます。

#### Java
<a name="background-replacement-android-initialize-camera-code"></a>

```
private fun startCamera(surface: Surface) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        val imageResource = R.drawable.beach
        val bgBitmap: Bitmap = BitmapFactory.decodeResource(resources, imageResource)
        var resultBitmap: Bitmap;


        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            
                if (mediaImage != null) {
                    val inputImage =
                        InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                            resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
                            canvas = surface.lockCanvas(null);
                            canvas.drawBitmap(resultBitmap, 0f, 0f, null)

                            surface.unlockCanvasAndPost(canvas);

                        }
                        .addOnFailureListener { exception ->
                            Log.d("App", exception.message!!)
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }

                }
            };

            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }
```

リスナー内で、ライブカメラフィードから個々のフレームにアクセスするように `ImageAnalysis.Builder` を作成します。バックプレッシャーストラテジーを `STRATEGY_KEEP_ONLY_LATEST` に設定します。これにより、一度に 1 つのカメラフレームだけが処理に送られることが保証されます。個々のカメラフレームをビットマップに変換すると、そのピクセルを抽出して後でカスタム背景画像と組み合わせることができます。

#### Java
<a name="background-replacement-android-create-imageanalysisbuilder-code"></a>

```
val imageAnalyzer = ImageAnalysis.Builder()
analysisUseCase = imageAnalyzer
    .setTargetResolution(Size(360, 640))
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()

analysisUseCase?.setAnalyzer(cameraExecutor) { imageProxy: ImageProxy ->
    val mediaImage = imageProxy.image
    val tempBitmap = imageProxy.toBitmap();
    val inputBitmap = tempBitmap.rotate(imageProxy.imageInfo.rotationDegrees.toFloat())
```

### カメラフレームを Google ML Kit に渡す
<a name="background-replacement-android-frames-to-mlkit"></a>

次に、`InputImage` を作成して Segmenter のインスタンスに渡して処理します。`InputImage` は、`ImageAnalysis` のインスタンスから提供された `ImageProxy` から作成できます。Segmenter に `InputImage` が提供されると、ピクセルが前景または背景にある可能性を示す信頼度スコア付きのマスクが返されます。このマスクには幅と高さのプロパティもあります。これを使用して、前に読み込んだカスタム背景画像の背景ピクセルを含む新しい配列を作成します。

#### Java
<a name="background-replacement-android-frames-to-mlkit-code"></a>

```
if (mediaImage != null) {
        val inputImage =
            InputImage.fromMediaImag


segmenter.process(inputImage)
    .addOnSuccessListener { segmentationMask ->
        val mask = segmentationMask.buffer
        val maskWidth = segmentationMask.width
        val maskHeight = segmentationMask.height
        val backgroundPixels = IntArray(maskWidth * maskHeight)
        bgBitmap.getPixels(backgroundPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)
```

### カメラフレームの前景をカスタム背景に重ねる
<a name="background-replacement-android-overlay-frame-foreground"></a>

信頼度スコアを含むマスク、ビットマップとしてのカメラフレーム、カスタム背景画像のカラーピクセルがあれば、前景をカスタム背景に重ねるのに必要なものがすべて揃っています。次に、`overlayForeground` 関数が以下のパラメータで呼び出されます。

#### Java
<a name="background-replacement-android-call-overlayforeground-code"></a>

```
resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
```

この関数はマスクを繰り返し適用し、信頼度の値をチェックして、対応するピクセルの色を背景画像から取得するのか、カメラフレームから取得するのかを決定します。マスク内のピクセルが背景にある可能性が高いことを信頼度の値が示している場合、対応するピクセルの色を背景画像から取得します。それ以外の場合は、カメラフレームから対応するピクセルの色を取得して前景を構築します。関数がマスクの反復適用を終了すると、新しいカラーピクセルの配列を使用して新しいビットマップが作成され、返されます。この新しいビットマップには、カスタム背景に重なった前景が含まれています。

#### Java
<a name="background-replacement-android-run-overlayforeground-code"></a>

```
private fun overlayForeground(
        byteBuffer: ByteBuffer,
        maskWidth: Int,
        maskHeight: Int,
        cameraBitmap: Bitmap,
        backgroundPixels: IntArray
    ): Bitmap {
        @ColorInt val colors = IntArray(maskWidth * maskHeight)
        val cameraPixels = IntArray(maskWidth * maskHeight)

        cameraBitmap.getPixels(cameraPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)

        for (i in 0 until maskWidth * maskHeight) {
            val backgroundLikelihood: Float = 1 - byteBuffer.getFloat()

            // Apply the virtual background to the color if it's not part of the foreground
            if (backgroundLikelihood > 0.9) {
                // Get the corresponding pixel color from the background image
                // Set the color in the mask based on the background image pixel color
                colors[i] = backgroundPixels.get(i)
            } else {
                // Get the corresponding pixel color from the camera frame
                // Set the color in the mask based on the camera image pixel color
                colors[i] = cameraPixels.get(i)
            }
        }

        return Bitmap.createBitmap(
            colors, maskWidth, maskHeight, Bitmap.Config.ARGB_8888
        )
    }
```

### 新しい画像をカスタム画像ソースにフィードする
<a name="background-replacement-android-custom-image-source"></a>

その後、カスタム画像ソースから提供された `Surface` に新しいビットマップを書き込むことができます。これでステージにブロードキャストされます。

#### Java
<a name="background-replacement-android-custom-image-source-code"></a>

```
resultBitmap = overlayForeground(mask, inputBitmap, mutableBitmap, bgBitmap)
canvas = surface.lockCanvas(null);
canvas.drawBitmap(resultBitmap, 0f, 0f, null)
```

カメラフレームを取得して Segmenter に渡し、背景に重ねる関数をすべて次に示します。

#### Java
<a name="background-replacement-android-custom-image-source-startcamera-code"></a>

```
@androidx.annotation.OptIn(androidx.camera.core.ExperimentalGetImage::class)
    private fun startCamera(surface: Surface) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        val imageResource = R.drawable.clouds
        val bgBitmap: Bitmap = BitmapFactory.decodeResource(resources, imageResource)
        var resultBitmap: Bitmap;

        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            val imageAnalyzer = ImageAnalysis.Builder()
            analysisUseCase = imageAnalyzer
                .setTargetResolution(Size(720, 1280))
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()

            analysisUseCase!!.setAnalyzer(cameraExecutor) { imageProxy: ImageProxy ->
                val mediaImage = imageProxy.image
                val tempBitmap = imageProxy.toBitmap();
                val inputBitmap = tempBitmap.rotate(imageProxy.imageInfo.rotationDegrees.toFloat())

                if (mediaImage != null) {
                    val inputImage =
                        InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                    segmenter.process(inputImage)
                        .addOnSuccessListener { segmentationMask ->
                            val mask = segmentationMask.buffer
                            val maskWidth = segmentationMask.width
                            val maskHeight = segmentationMask.height
                            val backgroundPixels = IntArray(maskWidth * maskHeight)
                            bgBitmap.getPixels(backgroundPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)

                            resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
                            canvas = surface.lockCanvas(null);
                            canvas.drawBitmap(resultBitmap, 0f, 0f, null)

                            surface.unlockCanvasAndPost(canvas);

                        }
                        .addOnFailureListener { exception ->
                            Log.d("App", exception.message!!)
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }

                }
            };

            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }
```

# IVS Broadcast SDK: モバイルオーディオモード \$1 リアルタイムストリーミング
<a name="broadcast-mobile-audio-modes"></a>

オーディオ品質はリアルチームのメディアエクスペリエンスにとって重要な部分であり、あらゆるユースケースに最適な万能のオーディオ構成はありません。IVS リアルタイムストリームを聴くときにユーザーが最高の体験を得られるように、モバイル SDK には複数のプリセットオーディオ構成が用意されているほか、必要に応じてさらに強力なカスタマイズも可能です。

## 序章
<a name="broadcast-mobile-audio-modes-introduction"></a>

IVS モバイル Broadcast SDK には `StageAudioManager` クラスが用意されています。このクラスは、2 つのプラットフォームの基盤オーディオモードを一元的に制御できるように設計されています。Android では、オーディオモード、オーディオソース、コンテンツタイプ、使用方法、通信デバイスなど、[AudioManager](https://developer.android.com/reference/android/media/AudioManager) を制御します。iOS では、アプリケーションの [AVAudioSession](https://developer.apple.com/documentation/avfaudio/avaudiosession) を制御し、[voiceProcessing](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) を有効にするかどうかも制御します。

**重要**: IVS リアルタイム Broadcast SDK がアクティブな間は、`AVAudioSession` や `AudioManager` を直接操作しないでください。これを行うと、オーディオが失われたり、間違ったデバイスから録音されたり、間違ったデバイスで再生されたりする可能性があります。

最初の `DeviceDiscovery` オブジェクトまたは `Stage` オブジェクトを作成する前に、`StageAudioManager` クラスを設定する必要があります。

------
#### [ Android (Kotlin) ]

```
StageAudioManager.getInstance(context).setPreset(StageAudioManager.UseCasePreset.VIDEO_CHAT) // The default value

val deviceDiscovery = DeviceDiscovery(context)
val stage = Stage(context, token, this)

// Other Stage implementation code
```

------
#### [ iOS (Swift) ]

```
IVSStageAudioManager.sharedInstance().setPreset(.videoChat) // The default value

let deviceDiscovery = IVSDeviceDiscovery()
let stage = try? IVSStage(token: token, strategy: self)

// Other Stage implementation code
```

------

`DeviceDiscovery` インスタンスや `Stage` インスタンスの初期化前に `StageAudioManager` に何も設定されていない場合、`VideoChat` プリセットが自動的に適用されます。

## オーディオモードプリセット
<a name="broadcast-mobile-audio-modes-presets"></a>

リアルタイム Broadcast SDK には 3 つのプリセットがあり、以下で説明するように、それぞれ一般的なユースケースに合わせて調整されています。各プリセットについて、プリセットを互いに区別する 5 つの主要カテゴリについて説明します。

**Volume Rocker** カテゴリは、デバイスの物理ボリュームロッカーを介して使用または変更されたボリュームのタイプ (メディアボリュームまたはコールボリューム) を指します。これは、オーディオモードを切り替えるときにボリュームに影響することに注意してください。例えば、Video Chat プリセットの使用中にデバイスボリュームが最大値に設定されているとします。Subscribe Only プリセットに切り替えると、オペレーティングシステムとは異なるボリュームレベルになり、デバイスで大幅なボリューム変更が発生する可能性があります。

### ビデオチャット
<a name="audio-modes-presets-video-chat"></a>

これはデフォルトのプリセットで、ローカルデバイスが他の参加者とリアルタイムで会話する場合に適しています。

**iOS の既知の問題**: このプリセットを使用してマイクをアタッチしないと、デバイススピーカーではなくイヤホンでオーディオが再生されます。このプリセットは、マイクと組み合わせてのみ使用してください。


| Category | Android | iOS | 
| --- | --- | --- | 
| エコーキャンセレーション | 有効 | 有効 | 
| Volume Rocker | 通話ボリューム | 通話ボリューム | 
| マイク選択 | OS によって制限されます。USB マイクを使用できない場合があります。 | OS によって制限されます。USB マイクと Bluetooth マイクを使用できない場合があります。 AirPods のように、入力と出力の両方を同時に処理する Bluetooth ヘッドセットは動作するはずです。 | 
| オーディオ出力 | どの出力デバイスでも動作するはずです。 | OS によって制限されます。有線ヘッドセットを使用できない場合があります。 | 
| オーディオ品質 | 中/低 メディア再生とは違い、電話のように聞こえます。 | 中/低 メディア再生とは違い、電話のように聞こえます。 | 

### サブスクライブのみ
<a name="audio-modes-presets-subscribe-only"></a>

このプリセットは、公開している他の参加者をサブスクライブする予定があっても自分では公開しない場合向けに設計されています。オーディオの品質に重点を置き、利用可能なすべての出力デバイスをサポートしています。


| Category | Android | iOS | 
| --- | --- | --- | 
| エコーキャンセレーション | Disabled | Disabled | 
| Volume Rocker | メディアボリューム | メディアボリューム | 
| マイク選択 | 非該当。このプリセットは公開用ではありません。 | 非該当。このプリセットは公開用ではありません。 | 
| オーディオ出力 | どの出力デバイスでも動作するはずです。 | どの出力デバイスでも動作するはずです。 | 
| オーディオ品質 | 高 音楽を含め、どんな種類のメディアでもクリアに聞こえるはずです。 | 高 音楽を含め、どんな種類のメディアでもクリアに聞こえるはずです。 | 

### Studio
<a name="audio-modes-presets-studio"></a>

このプリセットは、公開機能を維持したまま、質の高いサブスクライブができるように設計されています。エコーキャンセレーション機能付きの録音再生ハードウェアが必要です。この場合のユースケースは、USB マイクと有線ヘッドセットの使用です。エコーの原因とならないようにデバイスの物理的な分離を確保しながら、SDK は最高品質のオーディオを維持します。


| Category | Android | iOS | 
| --- | --- | --- | 
| エコーキャンセレーション | プラットフォームエコーキャンセレーションは無効になっていますが、 `StageAudioConfiguration.enableEchoCancellation` が true の場合、ソフトウェアエコーキャンセレーションが発生する可能性があります。 | Disabled | 
| Volume Rocker | ほとんどの場合、メディアボリューム。Bluetooth マイクが接続されている場合は、通話ボリューム。 | メディアボリューム | 
| マイク選択 | どのマイクでも動作するはずです。 | どのマイクでも動作するはずです。 | 
| オーディオ出力 | どの出力デバイスでも動作するはずです。 | どの出力デバイスでも動作するはずです。 | 
| オーディオ品質 | 高 双方が音楽を送信でき、反対側でもクリアに聞こえるはずです。 Bluetooth ヘッドセットが接続されている場合、Bluetooth SCO モードが有効になっているため、音質が低下します。 | 高 双方が音楽を送信でき、反対側でもクリアに聞こえるはずです。 Bluetooth ヘッドセットが接続されている場合、ヘッドセットによっては Bluetooth SCO モードが有効になっているため、音質が低下する場合があります。 | 

## 高度なユースケース
<a name="broadcast-mobile-audio-modes-advanced-use-cases"></a>

プリセット以外にも、iOS と Android のリアルタイムストリーミング Broadcast SDK では、基盤となるプラットフォームのオーディオモードを次のように設定できます。
+ Android では、[AudioSource](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource)、[Usage](https://developer.android.com/reference/android/media/AudioAttributes#USAGE_ALARM)、[ContentType](https://developer.android.com/reference/android/media/AudioAttributes#CONTENT_TYPE_MOVIE) を設定します。
+ iOS では、[AVAudioSession.Category](https://developer.apple.com/documentation/avfaudio/avaudiosession/category)、[AVAudioSession.CategoryOptions](https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions)、[AVAudioSession.Mode](https://developer.apple.com/documentation/avfaudio/avaudiosession/mode)、および公開中に[音声処理](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc)を有効にするかどうかを切り替える機能を使用します。

注: これらのオーディオ SDK メソッドを使用する場合、基盤となるオーディオセッションを誤って設定する可能性があります。例えば、iOS で `.allowBluetooth` オプションを `.playback` カテゴリと組み合わせて使用すると、無効なオーディオ設定が作成され、SDK はオーディオを録音または再生できません。これらのメソッドは、アプリケーションに特定のオーディオセッション要件が検証されている場合にのみ使用されるように設計されています。

------
#### [ Android (Kotlin) ]

```
// This would act similar to the Subscribe Only preset, but it uses a different ContentType.
StageAudioManager.getInstance(context)
    .setConfiguration(StageAudioManager.Source.GENERIC,
                      StageAudioManager.ContentType.MOVIE,
                      StageAudioManager.Usage.MEDIA);

val stage = Stage(context, token, this)

// Other Stage implementation code
```

------
#### [ iOS (Swift) ]

```
// This would act similar to the Subscribe Only preset, but it uses a different mode and options.
IVSStageAudioManager.sharedInstance()
    .setCategory(.playback,
                 options: [.duckOthers, .mixWithOthers],
                 mode: .default)

let stage = try? IVSStage(token: token, strategy: self)

// Other Stage implementation code
```

------

### iOS エコーキャンセレーション
<a name="advanced-use-cases-ios_echo_cancellation"></a>

iOS でのエコーキャンセレーションは、`echoCancellationEnabled` メソッドを使用して `IVSStageAudioManager` を介して個別に制御することもできます。このメソッドは、SDK が使用し、基盤となっている `AVAudioEngine` の入出力ノードで[音声処理](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc)が有効になっているかどうかを制御します。このプロパティを手動で変更した場合の以下の影響を理解することは重要です。
+ `AVAudioEngine` プロパティは、SDK のマイクがアクティブな場合にのみ使用されます。これは、入力ノードと出力ノードの両方で音声処理を同時に有効にするという iOS の要件のために必要です。通常、これは、`IVSDeviceDiscovery` から返されたマイクを使用して行われ、公開用に `IVSLocalStageStream` が作成されます。または、マイク自体に `IVSAudioDeviceStatsCallback` をアタッチすることで、公開に使用することなくマイクを有効にすることもできます。この代替アプローチは、IVS SDK のマイクの代わりにカスタムオーディオソースベースのマイクを使用しているときにエコーキャンセレーションが必要な場合に役立ちます。
+ `AVAudioEngine` プロパティを有効にするには、`.videoChat` または `.voiceChat` のモードが必要です。別のモードをリクエストすると、iOS の基盤となるオーディオフレームワークが SDK と戦い、オーディオが失われます。
+ `AVAudioEngine` を自動的に有効にすると、`.allowBluetooth ` オプションが有効になります。

デバイスと iOS のバージョンによって動作が異なる場合があります。

### iOS カスタムオーディオソース
<a name="advanced-use-cases-ios_custom_audio_sources"></a>

カスタムオーディオソースは、`IVSDeviceDiscovery.createAudioSource` を使用して SDK で使用できます。ステージに接続する場合、IVS Real-Time Streaming Broadcast SDK は、SDK のマイクが使用されていなくても、音声再生用の内部 `AVAudioEngine` インスタンスを管理します。そのため、`IVSStageAudioManager` に提供される値は、カスタムオーディオソースによって提供されるオーディオと互換性がある必要があります。

公開に使用されるカスタムオーディオソースがマイクからの録音でホストアプリケーションで管理されている場合、上記のエコーキャンセレーション SDK は、SDK 管理のマイクがアクティブ化されない限り機能しません。この要件を回避する方法については、[「iOS エコーキャンセレーション](#advanced-use-cases-ios_echo_cancellation)」を参照してください。

### Android で Bluetooth を使用して公開する
<a name="advanced-use-cases-bluetooth-android"></a>

以下の条件が満たされると、SDK は Android の `VIDEO_CHAT` プリセットに自動的に戻ります。
+ 割り当てられた設定には、`VOICE_COMMUNICATION` 使用状況の値は使用されていない。
+ Bluetooth マイクがデバイスに接続されている。
+ ローカル参加者がステージに公開している。

これは Bluetooth ヘッドセットを使用してオーディオを録音する方法に関する Android オペレーティングシステムの制限です。

## 他の SDK との統合
<a name="broadcast-mobile-audio-modes-integrating-other-sdks"></a>

iOS と Android はどちらもアプリケーションごとにアクティブなオーディオモードを 1 つしかサポートしていないため、オーディオモードの制御を必要とする複数の SDK をアプリケーションで使用していると、競合が発生することがよくあります。このような競合が発生した場合は、以下で説明する一般的な解決方法をいくつか試してみてください。

### オーディオモードの値に合わせる
<a name="integrating-other-sdks-match-values"></a>

IVS SDK の高度なオーディオ設定オプションまたは他の SDK の機能を使用して、2 つの SDK を基本となる値に合わせます。

### Agora
<a name="integrating-other-sdks-agora"></a>

#### iOS
<a name="integrating-other-sdks-agora-ios"></a>

iOS では、Agora SDK に `AVAudioSession` をアクティブのままにしておくように指示すると、IVS Real-Time Streaming Broadcast SDK が使用している間は非アクティブ化されなくなります。

```
myRtcEngine.SetParameters("{\"che.audio.keep.audiosession\":true}");
```

#### Android
<a name="integrating-other-sdks-agora-android"></a>

IVS Real-Time Streaming Broadcast SDK で公開中に `RtcEngine` で `setEnableSpeakerphone` を呼び出すことや `enableLocalAudio(false)` を呼び出すことは避けてください。IVS SDK で公開していないときに再度 `enableLocalAudio(true)` を呼び出す機会があります。