

# 使用 IVS Web 廣播 SDK 發布和訂閱 \$1 即時串流
<a name="web-publish-subscribe"></a>

本文件將帶您了解開始使用 IVS 即時串流 Web 廣播 SDK 發布和訂閱階段的相關步驟。

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

以下是三個以即時功能為基礎的核心概念：[階段](#web-publish-subscribe-concepts-stage)、[策略](#web-publish-subscribe-concepts-strategy) 和 [事件](#web-publish-subscribe-concepts-events)。設計目標是盡可能減少打造工作產品所需的用戶端邏輯數量。

### 階段
<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`。以下將討論所有內容。

若要使用已定義的策略，請將其傳送給 `Stage` 建構函數。以下是使用策略將參與者的網路攝影機發布到階段並訂閱所有參與者的完整應用程式範例。後續幾節會詳細說明各項必要策略函數的用途。

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

事件提供的資訊應該不會對策略的傳回值造成影響。例如，呼叫 `STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED` 時，`shouldSubscribeToParticipant` 的傳回值應該不會變更。若主持人應用程式想要訂閱特定參與者，則無論該參與者的發布狀態為何，它都應傳回所需的訂閱類型。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>

除了使用者的網路攝影機外，應用程式通常需要發布螢幕共用。發布螢幕共用需要為此舞台建立額外的權杖，具體而言，是為發布螢幕共用的媒體而建立權杖。使用 `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` 後由 `stageStreamsToPublish` 傳回新的 `LocalStageStream` 物件執行個體，則新串流物件的靜音狀態會套用至階段。建立新 `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 物件上均可用。它會傳回全面的品質指標，包括網路品質、封包統計資料、位元速率資訊和影格相關指標。

這是一種非同步方法，讓您可以透過等待或鏈結承諾來擷取統計資料。當統計資料不可用時，例如串流未啟動或無法取得內部統計資料，此方法會傳回 `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) 物件。

請注意，對於採用 simulcast 技術的視訊串流，陣列包含多個統計物件（每層一個）。

**最佳實務**
+ 輪詢頻率 — 以合理的間隔（1-5 秒）呼叫 `requestQualityStats()`，避免影響效能
+ 錯誤處理 — 處理前務必檢查傳回值是否為 `undefined`
+ 記憶體管理 — 不再需要串流時清除間隔/逾時
+ 網路品質 — 使用 `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 瀏覽器。它可傳送串流的三個轉譯層。
  + 這讓伺服器能夠根據其網路限制，選擇要傳送給其他參與者的轉譯。
  + 連同 `maxBitrate` 及/或 `maxFramerate` 值一起指定 `simulcast` 時，預期最高轉譯層會考慮設定這些值，前提條件是 `maxBitrate` 不低於內部 SDK 第二高層 900 kbps 的預設 `maxBitrate` 值。
  + 如果相較於第二高層的預設值，指定的 `maxBitrate` 太低，則會停用 `simulcast`。
  + 若未透過將 `shouldPublishParticipant` 傳回 `false`、呼叫 `refreshStrategy`、將 `shouldPublishParticipant` 傳回 `true`，以及再次呼叫 `refreshStrategy` 的組合動作來重新發布媒體，則無法開啟和關閉 `simulcast`。

## 取得參與者屬性
<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>

發布用戶端可以透過設定其視訊的 LocalStageStream 以啟用 `inBandMessaging` 與後續調用 `insertSeiMessage` 方法，藉此將 SEI 承載插入正在發布的階段串流。請注意，啟用 `inBandMessaging` 會增加 SDK 記憶體用量。

承載必須是 [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) 類型。承載大小必須大於 0KB，小於 1KB。每秒插入的 SEI 訊息數量不得超過每秒 10KB。

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

## Simulcast 分層編碼
<a name="web-publish-subscribe-layered-encoding-simulcast"></a>

Simulcast 分層編碼是一種 IVS 即時串流功能，可讓發布者傳送多個不同品質的影片層，也可讓訂閱用戶動態或手動變更這些層。[串流最佳化](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/real-time-streaming-optimization.html)文件中詳細介紹了該功能。

### 設定分層編碼 (發布者)
<a name="web-layered-encoding-simulcast-configure-publisher"></a>

若要以發布者身分啟用 Simulcast 分層編碼，請在執行個體化時將下列組態新增至 `LocalStageStream`：

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

根據相機裝置的輸入解析度，系統會依照*串流最佳化*的[預設層、品質和影格率](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers)小節中的定義，來編碼和傳送一定數量的層。

此外，您也可選擇從 Simulcast 組態內設定個別層：

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

或者，您可以建立自訂層組態，最多三層。如果您提供空陣列或未提供任何值，則會使用上述預設值。透過下列必要屬性來描述層：
+ `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>

訂閱用戶無需執行任何操作來啟用分層編碼。如果發布者正在傳送 Simulcast 層，則伺服器預設會在各層之間動態調整，根據訂閱用戶的裝置和網路狀況選擇品質最佳的層。

或者，若要挑選發布者正在傳送的明確層，有幾個選項可供選擇，如下所述。

### 選項 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` 的層物件標籤字串
+ undefined 或 null，這表示不應選取任何層，且偏好動態調整

例如，以下策略將一律讓使用者選取可用的最低品質影片層：

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

若要重設層選擇並返回動態調整，則在策略中傳回 null 或 undefined。在此範例中，`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` 物件自身還有可傳達層和 Simulcast 調整變更的事件：
  + `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)。