

# 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()` の呼び出しはストラテジーが変わらない限り何もしないため、低コストなオペレーションとみなすことができます。

### イベント
<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)」を参照してください。