

# IVS-Broadcast-SDK \$1 Echtzeit-Streaming
<a name="broadcast"></a>

Das Amazon Interactive Video Services (IVS)-Broadcast-SDK ist für Entwickler gedacht, die Anwendungen mit Amazon IVS erstellen. Dieses SDK wurde entwickelt, um die Amazon-IVS-Architektur zu nutzen und bietet neben Amazon IVS kontinuierliche Verbesserungen und neue Funktionen. Als natives Broadcast-SDK wurde es entwickelt, um die Leistungsauswirkungen auf Ihre Anwendung und auf die Geräte, mit denen Ihre Benutzer auf Ihre Anwendung zugreifen, zu minimieren.

Beachten Sie, dass das Broadcast-SDK sowohl für das Senden als auch für das Empfangen von Videos verwendet wird. Sie verwenden also dasselbe SDK für Hosts und Zuschauer. Kein separates Player-SDK erforderlich.

Ihre Anwendung kann die wichtigsten Funktionen des Amazon-IVS-Broadcast-SDK nutzen:
+ **Hochqualitatives Streaming** – Das Broadcast-SDK unterstützt qualitativ hochwertiges Streaming. Nehmen Sie Videos von Ihrer Kamera auf und kodieren Sie sie mit bis zu 720p.
+ **Automatische Bitratenanpassungen** – Smartphone-Nutzer sind mobil, so dass sich ihre Netzwerkbedingungen im Laufe einer Sendung ändern können. Das Amazon-IVS-Broadcast-SDK passt die Videobitrate automatisch an sich ändernde Netzwerkbedingungen an.
+ **Hoch- und Quer-Support** – Unabhängig davon, wie Ihre Benutzer ihre Geräte halten, wird das Image mit der rechten Seite nach oben und richtig skaliert angezeigt. Das Broadcast-SDK unterstützt sowohl die Leinwandgröße im Hoch- als auch im Querformat. Es verwaltet automatisch das Seitenverhältnis, wenn die Benutzer ihr Gerät von der konfigurierten Ausrichtung weg drehen.
+ **Sicheres Streaming** – Die Übertragungen Ihrer Benutzer werden mit TLS verschlüsselt, sodass sie ihre Streams sicher halten können.
+ **Externe Audiogeräte** – Das Amazon-IVS-Broadcast-SDK unterstützt externe Audiobuchse, USB und Bluetooth-SCO-Mikrofone.

## Plattform-Anforderungen
<a name="broadcast-platform-requirements"></a>

### Native Plattformen
<a name="broadcast-native-platforms"></a>


| Plattform | Unterstützte Versionen | 
| --- | --- | 
| Android |  9.0 und höher – Hinweis: Kunden können mit Version 6.0 und höher entwickeln, können aber die Echtzeit-Streaming-Funktion nicht nutzen.  | 
| iOS |  14 und höher  | 

IVS unterstützt mindestens 4 Hauptversionen von iOS und 6 Hauptversionen von Android. Unsere aktuelle Versionsunterstützung kann über diese Mindestanforderungen hinausgehen. Kunden werden über SDK-Versionshinweise mindestens 3 Monate im Voraus benachrichtigt, wenn eine Hauptversion nicht mehr unterstützt wird.

### Desktop-Browser
<a name="browser-desktop"></a>


| Browser | Unterstützte Plattformen | Unterstützte Versionen | 
| --- | --- | --- | 
| Chrome | Windows, macOS | Zwei Hauptversionen (aktuelle und neueste Vorversion) | 
| Firefox | Windows, macOS | Zwei Hauptversionen (aktuelle und neueste Vorversion) | 
| Edge | Windows 8.1 und höher | Zwei Hauptversionen (aktuelle und neueste Vorversion) Schließt Edge Legacy aus | 
| Safari | macOS | Zwei Hauptversionen (aktuelle und neueste Vorversion) | 

### Mobile Browser (iOS und Android)
<a name="browser-mobile"></a>


| Browser | Unterstützte Plattformen | Unterstützte Versionen | 
| --- | --- | --- | 
| Chrome | iOS, Android | Zwei Hauptversionen (aktuelle und neueste Vorversion) | 
| Firefox | Android | Zwei Hauptversionen (aktuelle und neueste Vorversion) | 
| Safari | iOS | Zwei Hauptversionen (aktuelle und neueste Vorversion) | 

#### Bekannte Beschränkungen
<a name="browser-mobile-limitations"></a>
+ Auf allen mobilen Webbrowsern empfehlen wir, nicht mehr als drei Publisher gleichzeitig zu veröffentlichen/zu abonnieren, weil es sonst zu Leistungsproblemen kommen kann, die Videoartefakte und schwarze Bildschirme verursachen. Wenn Sie mehr Publisher benötigen, konfigurieren Sie die [reine Audio-Veröffentlichung und -Abonnierung](web-publish-subscribe.md#web-publish-subscribe-concepts-strategy-updates).
+ Aus Gründen der Leistung und möglicher Abstürze raten wir davon ab, eine Stufe zusammenzustellen und an einen Kanal im Android Mobile Web zu übertragen. Wenn Broadcast-Funktionalität erforderlich ist, integrieren Sie das [Android-Broadcast-SDK für IVS-Echtzeit-Streaming](broadcast-android.md).

## Webansichten
<a name="broadcast-webviews"></a>

Das Web-Broadcast-SDK bietet keine Unterstützung für Webviews oder webähnliche Umgebungen (TV, Konsolen usw.). Informationen zu mobilen Implementierungen finden Sie im Broadcast-SDK-Handbuch für Echtzeit-Streaming mit niedriger Latenz für [Android](broadcast-android.md) und [iOS](broadcast-ios.md).

## Erforderlicher Gerätezugriff
<a name="broadcast-device-access"></a>

Das Broadcast-SDK erfordert Zugriff auf die Kameras und Mikrofone des Geräts, sowohl auf die im Gerät integrierten als auch auf die über Bluetooth, USB oder eine Audiobuchse angeschlossenen.

## Support
<a name="broadcast-support"></a>

Hinweis: Das Broadcast-SDK wird ständig verbessert. Siehe [Versionshinweise zu Amazon IVS](release-notes.md) für verfügbare Versionen und behobene Probleme. Aktualisieren Sie gegebenenfalls Ihre Version des Broadcast-SDK, bevor Sie sich an den Support wenden und prüfen Sie, ob das Problem dadurch behoben wird.

### Versionsverwaltung
<a name="broadcast-support-versioning"></a>

Die Amazon-IVS-Broadcast-SDKs verwenden [Semantisches Versioning](https://semver.org/).

Nehmen Sie für diese Diskussion an:
+ Die neueste Version ist 4.1.3.
+ Die neueste Version der vorherigen Hauptversion ist 3.2.4.
+ Die neueste Version 1.x ist 1.5.6.

Rückwärtskompatible neue Funktionen werden als Nebenversionen der neuesten Version hinzugefügt. In diesem Fall wird der nächste Satz neuer Funktionen als Version 4.2.0 hinzugefügt.

Rückwärtskompatible, kleinere Fehlerbehebungen werden als Patch-Releases der neuesten Version hinzugefügt. Hier wird der nächste Satz von kleineren Fehlerbehebungen als Version 4.1.4 hinzugefügt.

Rückwärtskompatible, große Fehlerbehebungen werden unterschiedlich behandelt; diese werden zu mehreren Versionen hinzugefügt:
+ Patch-Version der neuesten Version. Hier ist das Version 4.1.4.
+ Patch-Version der vorherigen Nebenversion. Hier ist das Version 3.2.5.
+ Patch-Version der neuesten Version 1.x. Hier ist das Version 1.5.7.

Wichtige Fehlerbehebungen werden vom Amazon IVS-Produktteam definiert. Typische Beispiele sind kritische Sicherheitsupdates und ausgewählte andere Korrekturen, die für Kunden erforderlich sind.

**Hinweis:** In den obigen Beispielen werden freigegebene Versionen inkrementiert, ohne dass Zahlen übersprungen werden (z. B. von 4.1.3 auf 4.1.4). In Wirklichkeit können eine oder mehrere Patch-Nummern intern bleiben und nicht veröffentlicht werden, so dass die freigegebene Version von 4.1.3 auf, sagen wir, 4.1.6 steigen könnte.

# IVS-Broadcast-SDK: Web-Handbuch \$1 Echtzeit-Streaming
<a name="broadcast-web"></a>

Das Web-Broadcast-SDK von IVS gibt Entwicklern die Werkzeuge an die Hand, um interaktive Echtzeit-Erlebnisse im Web zu schaffen. Dieses SDK ist für Entwickler gedacht, die Webanwendungen mit Amazon IVS erstellen.

Das Web-Broadcast-SDK ermöglicht es den Teilnehmern, Videos zu senden und zu empfangen. Das SDK unterstützt die folgenden Vorgänge:
+ Einer Stage beitreten
+ Medien für andere Teilnehmer auf der Stage veröffentlichen
+ Medien anderer Teilnehmer auf der Stage abonnieren
+ Auf der Stage veröffentlichte Videos und Audios verwalten und überwachen
+ WebRTC-Statistiken für jede Peer-Verbindung beziehen
+ Alle Operationen aus dem Web-Broadcast-SDK für IVS-Streaming mit niedriger Latenz

**Aktuelle Version des Web-Broadcast-SDK:** 1.33.0 ([Versionshinweise](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-web-rt)) 

**Referenzdokumentation:** Informationen zu den wichtigsten Methoden, die im Amazon IVS Web Broadcast SDK verfügbar sind, finden Sie unter [https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference). Stellen Sie sicher, dass die neueste Version des SDK ausgewählt ist.

**Beispielcode**: Die folgenden Beispiele sind ein guter Ausgangspunkt, um schnell mit dem SDK loszulegen:
+ [Einfache Wiedergabe](https://codepen.io/amazon-ivs/pen/RNwVBRK)
+ [Einfaches Publishing und Abonnieren](https://codepen.io/amazon-ivs/pen/ZEqgrpo)
+ [Umfassende Demo zur Echtzeit-Zusammenarbeit in React](https://github.com/aws-samples/amazon-ivs-real-time-collaboration-web-demo/tree/main)

**Plattformanforderungen**: Eine Liste der unterstützten Plattformen finden Sie unter [Amazon IVS Broadcast SDK](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/broadcast.html).

**Hinweis:** Die Veröffentlichung über einen Browser ist für Endbenutzer praktisch, da keine zusätzliche Software installiert werden muss. Die browserbasierte Veröffentlichung unterliegt jedoch den Einschränkungen und Schwankungen der Browser-Umgebungen. Wenn Stabilität für Sie Priorität hat (beispielsweise für Ereignis-Streaming) empfehlen wir generell die Veröffentlichung über eine Quelle außerhalb des Browsers (z. B. OBS Studio oder andere dedizierte Encoder), die oft direkten Zugriff auf Systemressourcen haben und Browser-Beschränkungen umgehen. Weitere Informationen zu Optionen der Veröffentlichung außerhalb des Browsers finden Sie in der Dokumentation zu [Stream Ingest](rt-stream-ingest.md).

# Erste Schritte mit dem IVS Web Broadcast SDK \$1 Streaming in Echtzeit
<a name="broadcast-web-getting-started"></a>

Dieses Dokument führt Sie durch die Schritte zum Einstieg in das Web Broadcast SDK von IVS-Streaming in Echtzeit.

## Importe
<a name="broadcast-web-getting-started-imports"></a>

Die Bausteine für Echtzeit befinden sich in einem anderen Namespace als die Root-Broadcasting-Module.

### Verwenden eines Skript-Tags
<a name="broadcast-web-getting-started-imports-script"></a>

Das Web Broadcast SDK wird als JavaScript-Bibliothek verteilt und kann unter [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) abgerufen werden.

Die in den folgenden Beispielen definierten Klassen und Aufzählungen befinden sich im globalen Objekt `IVSBroadcastClient`:

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

### Verwenden von npm
<a name="broadcast-web-getting-started-imports-npm"></a>

So installieren Sie das `npm`-Paket: 

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

Die Klassen, Enums und Typen können auch aus dem Paketmodul importiert werden:

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

### Serverseitige Rendering-Unterstützung
<a name="broadcast-web-getting-started-imports-server-side-rendering"></a>

Die Stagebibliothek des Web-Broadcast-SDK kann nicht in einem serverseitigen Kontext geladen werden, da sie auf Browser-Primitive verweist, die für das Funktionieren der Bibliothek beim Laden erforderlich sind. Um dieses Problem zu umgehen, laden Sie die Bibliothek dynamisch, wie in der [Web-Broadcast-Demo mit „Next“ und „React“](https://github.com/aws-samples/amazon-ivs-broadcast-web-demo/blob/main/hooks/useBroadcastSDK.js#L26-L31) gezeigt.

## Berechtigungen anfordern
<a name="broadcast-web-request-permissions"></a>

Ihre App muss die Berechtigung für den Zugriff auf die Kamera und das Mikrofon des Benutzers anfordern und muss über HTTPS bereitgestellt werden. (Das gilt nicht nur für Amazon IVS, sondern für alle Websites, die Zugriff auf Kameras und Mikrofone benötigen.)

Die folgende Beispielfunktion zeigt, wie Sie Berechtigungen für Audio- und Videogeräte anfordern und erfassen können:

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

Weitere Informationen finden Sie in der [Berechtigungs-API](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API) und unter [MediaDevices.getUserMedia()](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia).

## Auflisten der verfügbaren Geräte
<a name="broadcast-web-request-list-devices"></a>

Um festzustellen, welche Geräte für die Erfassung verfügbar sind, fragen Sie die Methode [MediaDevices.enumerateDevices()](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices) des Browsers ab:

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

## Abrufen eines MediaStream von einem Gerät
<a name="broadcast-web-retrieve-mediastream"></a>

Nachdem Sie die Liste der verfügbaren Geräte erfasst haben, können Sie einen Stream von einer beliebigen Anzahl von Geräten abrufen. Sie können zum Beispiel mit der Methode `getUserMedia()` einen Stream von einer Kamera abrufen.

Wenn Sie angeben möchten, von welchem Gerät der Stream erfasst werden soll, können Sie die `deviceId` im Bereich `audio` oder `video` der Medieneinschränkungen explizit festlegen. Alternativ können Sie die `deviceId` weglassen und Benutzer ihre Geräte über die Eingabeaufforderung des Browsers auswählen lassen.

Zudem können Sie mithilfe der Einschränkungen `width` und `height` eine ideale Kameraauflösung angeben. (Mehr über diese Einschränkungen erfahren Sie [hier](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#properties_of_video_tracks).) Das SDK wendet automatisch die Einschränkungen für die Breite und Höhe an, die Ihrer maximalen Übertragungsauflösung entsprechen. Es empfiehlt sich jedoch, diese auch selbst anzuwenden, damit das Seitenverhältnis der Quelle nicht geändert wird, nachdem Sie sie dem SDK hinzugefügt haben.

Stellen Sie für Echtzeit-Streaming sicher, dass die Medienauflösung auf 720p beschränkt ist. Insbesondere dürfen Ihre `getUserMedia`- und `getDisplayMedia`-Beschränkungswerte für Breite und Höhe 921 600 (1280\$1720) nicht überschreiten, wenn sie miteinander multipliziert werden. 

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

# Veröffentlichen und Abonnieren mit dem IVS Web Broadcast SDK \$1 Streaming in Echtzeit
<a name="web-publish-subscribe"></a>

Dieses Dokument führt Sie durch die Schritte zum Veröffentlichen und Abonnieren einer Stage mit dem Web Broadcast SDK von IVS-Streaming in Echtzeit.

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

Drei Kernkonzepte liegen der Echtzeit-Funktionalität zugrunde: [Stage](#web-publish-subscribe-concepts-stage), [Strategie](#web-publish-subscribe-concepts-strategy) und [Ereignisse](#web-publish-subscribe-concepts-events). Das Designziel besteht in der Minimierung der Menge an clientseitiger Logik, die für die Entwicklung eines funktionierenden Produkts erforderlich ist.

### Stage
<a name="web-publish-subscribe-concepts-stage"></a>

Die Klasse `Stage` ist der Hauptinteraktionspunkt zwischen der Hostanwendung und dem SDK. Sie stellt die Stage selbst dar und dient dazu, der Stage beizutreten und sie zu verlassen. Für das Erstellen einer Stage und das Beitreten ist eine gültige, noch nicht abgelaufene Token-Zeichenfolge aus der Steuerebene erforderlich (dargestellt als `token`). Einer Stage beizutreten und sie zu verlassen, ist ganz einfach:

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

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

stage.leave();
```

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

Über die Schnittstelle `StageStrategy` kann die Hostanwendung dem SDK den gewünschten Status der Stage mitteilen. Drei Funktionen müssen implementiert werden: `shouldSubscribeToParticipant`, `shouldPublishParticipant` und `stageStreamsToPublish`. Alle werden im Folgenden behandelt.

Um eine definierte Strategie zu verwenden, übergeben Sie sie an den `Stage`-Konstruktor. Im Folgenden finden Sie ein vollständiges Beispiel für eine Anwendung, die mithilfe einer Strategie die Webcam eines Teilnehmers auf der Stage veröffentlicht und alle Teilnehmer abonniert. Der Zweck der einzelnen erforderlichen Strategiefunktionen wird in den folgenden Abschnitten ausführlich erläutert.

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

#### Abonnieren von Teilnehmern
<a name="web-publish-subscribe-concepts-strategy-participants"></a>

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

Wenn ein Remote-Teilnehmer der Stage beitritt, fragt das SDK die Hostanwendung nach dessen gewünschtem Abonnementstatus. Die Optionen lauten `NONE`, `AUDIO_ONLY` und `AUDIO_VIDEO`. Wenn ein Wert für diese Funktion zurückgegeben wird, muss sich die Hostanwendung nicht um den Veröffentlichungs-, den aktuellen Abonnement- oder den Verbindungsstatus des Stage kümmern. Bei Rückgabe von `AUDIO_VIDEO` wartet das SDK mit dem Abonnieren, bis der Remote-Teilnehmer etwas veröffentlicht. Außerdem aktualisiert es die Hostanwendung, indem es während des gesamten Prozesses Ereignisse ausgibt.

Hier folgt ein Beispiel für eine Implementierung:

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

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

Hierbei handelt es sich um die vollständige Implementierung dieser Funktion für eine Hostanwendung, bei der sich alle Teilnehmer stets gegenseitig sehen sollen; z. B. eine Video-Chat-Anwendung.

Weitergehende Implementierungen sind ebenfalls möglich. Beispiel: Angenommen, die Anwendung stellt bei der Erstellung des Tokens mit CreateParticipantToken ein `role`-Attribut bereit. Die Anwendung könnte die `attributes`-Eigenschaft für `StageParticipantInfo` nutzen, um Teilnehmer anhand der vom Server bereitgestellten Attribute selektiv zu abonnieren:

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

Hiermit kann eine Stage erstellt werden, auf der Moderatoren alle Gäste überwachen können, ohne selbst gesehen oder gehört zu werden. Die Hostanwendung könnte eine zusätzliche Geschäftslogik nutzen, damit Moderatoren sich gegenseitig sehen können, für Gäste aber unsichtbar bleiben.

#### Konfiguration für das Abonnieren von Teilnehmern
<a name="web-publish-subscribe-concepts-strategy-participants-config"></a>

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

Wenn ein Remote-Teilnehmer abonniert wird (siehe [Teilnehmer abonnieren](#web-publish-subscribe-concepts-strategy-participants)), fragt das SDK die Host-Anwendung nach einer benutzerdefinierten Abonnementkonfiguration für diesen Teilnehmer ab. Diese Konfiguration ist optional und ermöglicht es der Hostanwendung, bestimmte Aspekte des Subscriber-Verhaltens zu steuern. Informationen darüber, was konfiguriert werden kann, finden Sie unter [SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration) in der SDK-Referenzdokumentation.

Hier folgt ein Beispiel für eine Implementierung:

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

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

Diese Implementierung aktualisiert die Mindestverzögerung für den Jitter-Buffer für alle abonnierten Teilnehmer auf die Voreinstellung `MEDIUM`.

Wie bei `shouldSubscribeToParticipant` sind auch hier weitergehende Implementierungen möglich. Die `ParticipantInfo`-Angaben können verwendet werden, um die Abonnementkonfiguration für bestimmte Teilnehmer selektiv zu aktualisieren.

Wir empfehlen die Verwendung der Standardverhaltensweisen. Geben Sie die benutzerdefinierte Konfiguration nur an, wenn Sie ein bestimmtes Verhalten ändern möchten.

#### Veröffentlichen
<a name="web-publish-subscribe-concepts-strategy-publishing"></a>

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

Sobald die Verbindung zur Stage hergestellt ist, überprüft das SDK per Anfrage an die Hostanwendung, ob ein bestimmter Teilnehmer etwas veröffentlichen soll. Dies wird nur bei lokalen Teilnehmern aufgerufen, die auf Grundlage des bereitgestellten Tokens zur Veröffentlichung berechtigt sind.

Hier folgt ein Beispiel für eine Implementierung:

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

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

Sie ist für eine normale Video-Chat-Anwendung gedacht, bei der Benutzer immer etwas veröffentlichen möchten. Sie können die Audio- und Videowiedergabe stummschalten und die Stummschaltung aufheben, um umgehend ausgeblendet oder gesehen/gehört zu werden. (Sie können auch „Veröffentlichen/Veröffentlichung aufheben“ verwenden, was aber viel langsamer ist. „Stummschalten/Stummschalten aufheben“ ist für Anwendungsfälle vorzuziehen, in denen eine häufige Änderung der Sichtbarkeit wünschenswert ist.)

#### Auswählen von Streams zur Veröffentlichung
<a name="web-publish-subscribe-concepts-strategy-streams"></a>

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

Beim Veröffentlichen wird hiermit bestimmt, welche Audio- und Videostreams veröffentlicht werden sollen. Dieser Punkt wird später unter [Veröffentlichen eines Medienstreams](#web-publish-subscribe-publish-stream) ausführlicher behandelt.

#### Aktualisieren der Strategie
<a name="web-publish-subscribe-concepts-strategy-updates"></a>

Die Strategie soll dynamisch sein: Die von einer der oben genannten Funktionen zurückgegebenen Werte lassen sich jederzeit ändern. Wenn die Hostanwendung beispielsweise erst veröffentlichen soll, wenn der Endbenutzer auf eine Schaltfläche tippt, können Sie eine Variable aus `shouldPublishParticipant` zurückgeben (zum Beispiel `hasUserTappedPublishButton`). Wenn sich diese Variable aufgrund einer Interaktion des Endbenutzers ändert, signalisieren Sie dem SDK per Aufruf von `stage.refreshStrategy()`, dass es die Strategie nach den neuesten Werten abfragen und nur Dinge anwenden soll, die sich geändert haben. Wenn das SDK feststellt, dass sich der Wert `shouldPublishParticipant` geändert hat, startet es den Veröffentlichungsprozess. Wenn alle Funktionen bei einer SDK-Abfrage den gleichen Wert zurückgeben wie zuvor, wird die Stage mit dem Aufruf von `refreshStrategy` nicht geändert.

Ändert sich der Rückgabewert von `shouldSubscribeToParticipant` von `AUDIO_VIDEO` in `AUDIO_ONLY`, wird der Videostream für alle Teilnehmer mit geänderten Rückgabewerten entfernt, sofern zuvor ein Videostream vorhanden war.

Im Allgemeinen nutzt die Stage die Strategie, um den Unterschied zwischen der vorherigen und der aktuellen Strategie am effizientesten anzuwenden. Dabei muss sich die Hostanwendung nicht um die ganzen Status kümmern, die für eine ordnungsgemäße Verwaltung erforderlich sind. Stellen Sie sich den Aufruf von `stage.refreshStrategy()` daher als einen ressourcenschonenden Vorgang vor, da nur bei einer Änderung der Strategie etwas unternommen wird.

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

Eine `Stage`-Instance ist ein Ereignis-Emitter. Mit `stage.on()` wird der Hostanwendung der Status der Stage mitgeteilt. Aktualisierungen in der Benutzeroberfläche der Hostanwendung können in der Regel vollständig durch die Ereignisse unterstützt werden. Folgende Ereignisse werden unterstützt:

```
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) => {})
```

Für die meisten dieser Ereignisse wird die entsprechende `ParticipantInfo` bereitgestellt.

Es wird nicht erwartet, dass sich die von den Ereignissen bereitgestellten Informationen auf die Rückgabewerte der Strategie auswirken. Es wird beispielsweise nicht erwartet, dass sich der Rückgabewert von `shouldSubscribeToParticipant` beim Aufruf von `STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED` ändert. Wenn die Hostanwendung einen bestimmten Teilnehmer abonnieren möchte, muss sie unabhängig von dessen Veröffentlichungsstatus den gewünschten Abonnementtyp zurückgeben. Das SDK muss dafür sorgen, dass entsprechend dem Status der Stage und dem gewünschten Status der Strategie zum richtigen Zeitpunkt gehandelt wird.

## Veröffentlichen eines Medienstreams
<a name="web-publish-subscribe-publish-stream"></a>

Lokale Geräte wie Mikrofone und Kameras werden mit den gleichen Schritten abgerufen, die oben unter [Abrufen eines MediaStream von einem Gerät](broadcast-web-getting-started.md#broadcast-web-retrieve-mediastream) beschrieben sind. In dem Beispiel erstellen wir mit `MediaStream` eine Liste von `LocalStageStream`-Objekten, die für die Veröffentlichung durch das SDK verwendet werden:

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

## Veröffentlichen einer Bildschirmfreigabe
<a name="web-publish-subscribe-publish-screenshare"></a>

Häufig müssen Anwendungen zusätzlich zur Webkamera des Benutzers eine Bildschirmfreigabe veröffentlichen. Das Veröffentlichen einer Bildschirmfreigabe erfordert die Erstellung eines zusätzlichen Tokens für die Stage, insbesonder für die Veröffentlichung der Medien der Bildschirmfreigabe. Verwenden Sie `getDisplayMedia` und beschränken Sie die Auflösung auf maximal 720p. Danach sind die Schritte ähnlich wie beim Veröffentlichen einer Kamera für die Stage.

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

## Anzeigen und Entfernen von Teilnehmern
<a name="web-publish-subscribe-participants"></a>

Nach Abschluss von Abonnements erhalten Sie über das Ereignis `STAGE_PARTICIPANT_STREAMS_ADDED` eine Reihe von `StageStream`-Objekten. Zudem stellt das Ereignis Teilnehmerinformationen bereit, die Ihnen beim Anzeigen von Medienstreams helfen:

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

Wenn ein Teilnehmer die Veröffentlichung beendet oder dessen Abonnement eines Streams beendet wird, wird die Funktion `STAGE_PARTICIPANT_STREAMS_REMOVED` mit den Streams aufgerufen, die entfernt wurden. Hostanwendungen sollten dies als Signal nutzen, um den Videostream des Teilnehmers aus dem DOM zu entfernen.

`STAGE_PARTICIPANT_STREAMS_REMOVED` wird für alle Szenarien aufgerufen, in denen ein Stream entfernt werden könnte, darunter:
+ Der Remote-Teilnehmer beendet die Veröffentlichung.
+ Ein lokales Gerät beendet das Abonnement oder ändert das Abonnement von `AUDIO_VIDEO` in `AUDIO_ONLY`.
+ Der Remote-Teilnehmer verlässt die Stage.
+ Der lokale Teilnehmer verlässt die Stage.

Da `STAGE_PARTICIPANT_STREAMS_REMOVED` bei allen Szenarien aufgerufen wird, ist keine benutzerdefinierte Geschäftslogik erforderlich, um Teilnehmer beim remoten oder lokalen Verlassen aus der Benutzeroberfläche zu entfernen.

## Stummschalten von Medienstreams und Aufheben der Stummschaltung
<a name="web-publish-subscribe-mute-streams"></a>

`LocalStageStream`-Objekte verfügen über eine `setMuted`-Funktion, die das Stummschalten des Streams steuert. Diese Funktion kann für den Stream aufgerufen werden, bevor oder nachdem er von der Strategiefunktion `stageStreamsToPublish` zurückgegeben wird.

**Wichtig**: Wenn nach einem Aufruf von `refreshStrategy` eine neue `LocalStageStream`-Objekt-Instance von `stageStreamsToPublish` zurückgegeben wird, wird der Stummschaltungsstatus des neuen Streamobjekts auf die Stage angewendet. Seien Sie vorsichtig beim Erstellen neuer `LocalStageStream`-Instances, um sicherzustellen, dass der erwartete Stummschaltungsstatus beibehalten wird.

## Überwachen des Medien-Stummschaltungsstatus von Remote-Teilnehmern
<a name="web-publish-subscribe-mute-state"></a>

Wenn Teilnehmer den Stummschaltungsstatus ihres Videos oder Audios ändern, wird das Ereignis `STAGE_STREAM_MUTE_CHANGED` mit einer Liste der Streams ausgelöst, die sich geändert haben. Verwenden Sie die Eigenschaft `isMuted` für `StageStream`, um die Benutzeroberfläche entsprechend zu aktualisieren:

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

Sie können auch unter [StageParticipantInfo](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference#stageparticipantinfo) nach Statusinformationen darüber suchen, ob Audio oder Video stummgeschaltet sind:

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

## Abrufen von WebRTC-Statistiken
<a name="web-publish-subscribe-webrtc-stats"></a>

Die Methode `requestQualityStats()` bietet Zugriff auf detaillierte WebRTC-Statistiken für lokale und Remote-Streams. Sie stehen für LocalStageStream- und für RemoteStageStream-Objekte zur Verfügung. Die Methode gibt umfassende Qualitätsmetriken zurück, darunter Netzwerkqualität, Paketstatistiken, Informationen zur Bitrate und einzelbildbezogene Metriken.

Hierbei handelt es sich um eine asynchrone Methode, mit der Sie Statistiken entweder über await oder durch Verkettung eines Promise abrufen können. Sind keine Statistiken verfügbar, gibt sie `undefined` zurück, z. B. wenn der Stream nicht aktiv ist oder interne Statistiken nicht verfügbar sind. Wenn Statistiken verfügbar sind, gibt die Methode je nach Stream (remote oder lokal, Video oder Audio) das Objekt [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) oder [RemoteAudioStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/RemoteAudioStats) zurück.

Beachten Sie, dass das Array bei Videostreams mit Simulcast mehrere Statistikobjekte enthält (eines pro Schicht).

**Bewährte Methoden**
+ Abfragehäufigkeit – Rufen Sie `requestQualityStats()` in angemessenen Intervallen (1 bis 5 Sekunden) auf, um Leistungseinbußen zu vermeiden.
+ Fehlerbehandlung – Prüfen Sie vor der Verarbeitung stets, ob der zurückgegebene Wert `undefined` lautet.
+ Verwaltung des Arbeitsspeichers – Löschen Sie Intervalle/Timeouts, wenn Streams nicht mehr benötigt werden.
+ Netzwerkqualität – Nutzen Sie `networkQuality` für Benutzerfeedback zu möglichen Beeinträchtigungen, die vom Netzwerk verursacht werden. Einzelheiten finden Sie unter [NetworkQuality](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/enumerations/NetworkQuality).

**Beispielverwendung**

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

## Optimieren von Medien
<a name="web-publish-subscribe-optimizing-media"></a>

Für eine optimale Leistung wird empfohlen, Aufrufe von `getUserMedia` und `getDisplayMedia` entsprechend den folgenden Einschränkungen zu begrenzen:

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

Sie können die Medien durch zusätzliche Optionen, die an den `LocalStageStream`-Konstruktor übergeben werden, weiter einschränken:

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

Im obigen Code:
+ `minBitrate` legt eine Mindestbitrate fest, die der Browser voraussichtlich verwenden sollte. Ein Videostream mit geringer Komplexität kann jedoch dazu führen, dass der Encoder diese Bitrate unterschreitet.
+ `maxBitrate` legt eine maximale Bitrate fest, von der erwartet werden sollte, dass sie vom Browser für diesen Stream nicht überschritten wird.
+ `maxFramerate` legt eine maximale Framerate fest, von der erwartet werden sollte, dass sie vom Browser für diesen Stream nicht überschritten wird.
+ Die Option `simulcast` ist nur in Chromium-basierten Browsern verwendbar. Sie ermöglicht das Senden von drei Wiedergabeebenen des Streams.
  + Auf diese Weise kann der Server anhand ihrer Netzwerkbeschränkungen auswählen, welche Wiedergabeversion an andere Teilnehmer gesendet werden soll.
  + Wenn `simulcast` zusammen mit einem `maxBitrate` und/oder `maxFramerate` Wert angegeben wird, wird erwartet, dass die höchste Wiedergabe-Ebene unter Berücksichtigung dieser Werte konfiguriert wird, vorausgesetzt, `maxBitrate` unterschreitet nicht die Standardeinstellung der zweithöchsten Ebene des internen SDK-Standardwerts `maxBitrate` von 900 kbps.
  + Wenn `maxBitrate` im Vergleich zum Standardwert der zweithöchsten Ebene als zu niedrig angegeben wird, wird `simulcast` deaktiviert.
  + `simulcast` kann nicht ein- und ausgeschaltet werden, ohne die Medien erneut zu veröffentlichen, indem `shouldPublishParticipant` `false` zurückgibt, `refreshStrategy` aufruft, `shouldPublishParticipant` `true` zurückgibt, und `refreshStrategy` wieder aufruft.

## Abrufen von Teilnehmerattributen
<a name="web-publish-subscribe-participant-attributes"></a>

Wenn Sie Attribute in der Vorgangsanfrage `CreateParticipantToken` angeben, können Sie die Attribute in den Eigenschaften von `StageParticipantInfo` einsehen:

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

## SEI (Supplemental Enhancement Information, Ergänzende Informationen zur Verbesserung)
<a name="web-publish-subscribe-sei-attributes"></a>

Die NAL-Einheit für Supplemental Enhancement Information (SEI) wird verwendet, um Frame-orientierte Metadaten zusammen mit dem Video zu speichern. Sie können beim Veröffentlichen und Abonnieren von H.264-Videostreams verwendet werden. Es kann nicht garantiert werden, dass SEI-Nutzdaten bei Subscribern ankommen, insbesondere bei schlechten Netzwerkbedingungen. Da die SEI-Nutzdaten direkt in der H.264-Frame-Struktur gespeichert werden, kann diese Funktion nicht für reine Audio-Streams genutzt werden.

### Einfügen von SEI-Nutzdaten
<a name="sei-attributes-inserting-sei-payloads"></a>

Veröffentlichende Clients können SEI-Nutzdaten in einen Stage-Stream einfügen, der gerade veröffentlicht wird, indem sie den LocalStageStream ihres Videos so konfigurieren, dass `inBandMessaging` aktiviert wird, und anschließend die Methode `insertSeiMessage` aufrufen. Beachten Sie, dass die Aktivierung von `inBandMessaging` die SDK-Speichernutzung erhöht.

[Nutzdaten müssen vom Typ ArrayBuffer sein.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) Die Nutzdaten müssen größer als 0 KB und kleiner als 1 KB sein. Die Anzahl der pro Sekunde eingefügten SEI-Nachrichten darf 10 KB pro Sekunde nicht überschreiten.

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

#### Sich wiederholende SEI-Nutzdaten
<a name="sei-attributes-repeating-sei-payloads"></a>

Geben Sie optional eine `repeatCount` an, um das Einfügen von SEI-Nutzdaten für die nächsten N gesendeten Frames zu wiederholen. Dies könnte hilfreich sein, um den inhärenten Verlust zu verringern, der aufgrund des zugrunde liegenden UDP-Transportprotokolls entstehen kann, das zum Senden von Videos verwendet wird. Beachten Sie, dass dieser Wert zwischen 0 und 30 liegen muss. Empfangende Clients müssen über eine Logik verfügen, um die Nachricht zu deduplizieren.

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

### Lesen von SEI-Nutzdaten
<a name="sei-attributes-reading-sei-payloads"></a>

Abonnierende Clients können SEI-Nutzdaten von einem Publisher lesen, der H.264-Videos veröffentlicht, sofern vorhanden. Dazu wird das `SubscribeConfiguration`-Element der Subscriber für die Aktivierung von `inBandMessaging` konfiguriert und auf das `StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED`-Ereignis gelauscht, wie im folgenden Beispiel gezeigt:

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

## Mehrschichtige Kodierung mit Simulcast
<a name="web-publish-subscribe-layered-encoding-simulcast"></a>

Bei der mehrschichtigen Kodierung mit Simulcast handelt es sich um ein Feature für IVS-Echtzeit-Streaming, mit dessen Hilfe Publisher mehrere Videoschichten unterschiedlicher Qualität senden können. Subscriber können diese Schichten dynamisch oder manuell ändern. Das Feature wird im Dokument [Streaming-Optimierungen](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/real-time-streaming-optimization.html) ausführlicher beschrieben.

### Konfigurieren mehrschichtiger Kodierung (Publisher)
<a name="web-layered-encoding-simulcast-configure-publisher"></a>

Um als Publisher die mehrschichtige Kodierung mit Simulcast zu aktivieren, fügen Sie dem `LocalStageStream` bei der Instanziierung die folgende Konfiguration hinzu:

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

Je nach Eingangsauflösung der Kamera wird eine festgelegte Anzahl von Schichten kodiert und gesendet, wie im Abschnitt [Standardmäßige Schichten, Qualitäten und Bildraten](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) von *Streaming-Optimierungen* definiert.

Außerdem können Sie optional einzelne Ebenen innerhalb der Simulcast-Konfiguration konfigurieren:

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

Alternativ können Sie eigene benutzerdefinierte Ebenenkonfigurationen für bis zu drei Ebenen erstellen. Wenn Sie ein leeres Array oder keinen Wert angeben, werden die oben beschriebenen Standardwerte verwendet. Ebenen werden mit den folgenden erforderlichen Eigenschaften beschrieben:
+ `height: number;`
+ `width: number;`
+ `maxBitrateKbps: number;`
+ `maxFramerate: number;`

Ausgehend von den Voreinstellungen können Sie entweder einzelne Eigenschaften überschreiben oder eine völlig neue Konfiguration erstellen:

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

Informationen zu Höchstwerten, Grenzwerten und Fehlern, die bei der Konfiguration einzelner Ebenen ausgelöst werden können, finden Sie in der SDK-Referenzdokumentation.

### Konfigurieren mehrschichtiger Kodierung (Subscriber)
<a name="web-layered-encoding-simulcast-configure-subscriber"></a>

Subscriber müssen nichts unternehmen, um die mehrschichtige Kodierung zu aktivieren. Wenn ein Publisher Simulcast-Schichten sendet, passt sich der Server standardmäßig dynamisch den Schichten an, um je nach Gerät und Netzwerkbedingungen des Subscribers die optimale Qualität auszuwählen.

Alternativ gibt es mehrere nachfolgend beschriebene Optionen, um explizite Schichten auszuwählen, die der Publisher sendet.

### Option 1: Einstellung für die Qualität der Anfangsschicht
<a name="web-layered-encoding-simulcast-layer-quality-preference"></a>

Mit der Strategie `subscribeConfiguration` können Sie auswählen, welche Anfangsschicht Sie als Subscriber erhalten möchten:

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

Standardmäßig wird Subscribern zunächst immer die Schicht mit der niedrigsten Qualität gesendet. Nach und nach wird die Qualität gesteigert, bis die Schicht mit der höchsten Qualität erreicht ist. Das optimiert den Bandbreitenverbrauch der Endbenutzer, verkürzt die Zeit bis zum Abspielen des Videos und verringert das anfängliche Einfrieren von Videos bei Benutzern in Netzwerken mit geringerer Bandbreite.

Folgende Optionen sind für `InitialLayerPreference` verfügbar:
+ `LOWEST_QUALITY` – Der Server stellt zuerst die Videoschicht mit der niedrigsten Qualität bereit. Dadurch werden der Bandbreitenverbrauch und die Zeit bis zum Abspielen von Medien optimiert. Die Qualität ist definiert als die Kombination aus Größe, Bitrate und Bildrate des Videos. Beispielsweise weisen 720p-Videos eine geringere Qualität auf als 1080p-Videos.
+ `HIGHEST_QUALITY` – Der Server stellt zuerst die Videoschicht mit der höchsten Qualität bereit. Das optimiert die Qualität, kann aber die Zeit bis zum Abspielen von Medien verlängern. Die Qualität ist definiert als die Kombination aus Größe, Bitrate und Bildrate des Videos. Beispielsweise weisen 1080p-Videos eine höhere Qualität auf als 720p-Videos.

**Hinweis:** Damit die anfänglichen Schichteinstellungen (der Aufruf `initialLayerPreference`) wirksam werden, muss ein neues Abonnement abgeschlossen werden, da diese Updates für das aktive Abonnement nicht gelten.



### Option 2: Bevorzugte Schicht für Streams
<a name="web-layered-encoding-simulcast-preferred-layer"></a>

Sobald ein Stream gestartet wurde, können Sie die Strategiemethode `preferredLayerForStream ` nutzen. Diese Strategiemethode legt den Teilnehmer und die Stream-Informationen offen.

Die Strategiemethode kann mit folgenden Elementen zurückgegeben werden:
+ dem Schichtobjekt direkt, basierend auf der Rückgabe von `RemoteStageStream.getLayers` 
+ der Bezeichnung des Schichtobjekts, basierend auf `StageStreamLayer.label`
+ undefiniert oder null, was bedeutet, dass keine Schicht ausgewählt werden sollte und eine dynamische Anpassung bevorzugt wird

Bei der folgenden Strategie wählen die Benutzer beispielsweise immer die Videoschicht mit der niedrigsten verfügbaren Qualität aus:

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

Um die Schichtauswahl zurückzusetzen und zur dynamischen Anpassung zurückzukehren, geben Sie in der Strategie null oder undefiniert zurück. In diesem Beispiel ist `appState` eine Dummy-Variable, die den möglichen Anwendungsstatus darstellt.

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

### Option 3: Helferobjekte für RemoteStageStream-Schicht
<a name="web-layered-encoding-simulcast-remotestagestream-helpers"></a>

`RemoteStageStream` weist mehrere Helferobjekte auf, mit deren Hilfe Entscheidungen über die Schichtauswahl getroffen und Endbenutzern die entsprechende Auswahl angezeigt werden kann:
+ **Schichtereignisse** – Neben `StageEvents` verfügt das Objekt `RemoteStageStream` selbst über Ereignisse, die Änderungen bei der Schicht- und Simulcast-Anpassung kommunizieren:
  + `stream.on(RemoteStageStreamEvents.ADAPTION_CHANGED, (isAdapting) => {})`
  + `stream.on(RemoteStageStreamEvents.LAYERS_CHANGED, (layers) => {})`
  + `stream.on(RemoteStageStreamEvents.LAYER_SELECTED, (layer, reason) => {})`
+ **Schichtmethoden** – `RemoteStageStream` verfügt über mehrere Helfermethoden, mit denen Informationen über den Stream und die präsentierten Schichten abgerufen werden können. Diese Methoden sind sowohl für den in der Strategie `preferredLayerForStream ` bereitgestellten Remote-Stream als auch für Remote-Streams verfügbar, die über `StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED` verfügbar gemacht werden.
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`

Einzelheiten finden Sie im Abschnitt zur Klasse `RemoteStageStream` in der [SDK-Referenzdokumentation](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference). Falls als Grund für `LAYER_SELECTED` `UNAVAILABLE` zurückgegeben wird, bedeutet das, dass die angeforderte Schicht nicht ausgewählt werden konnte. Stattdessen wird eine bestmögliche Auswahl getroffen. Dabei handelt es sich in der Regel um eine Schicht mit niedrigerer Qualität, um die Stabilität des Streams zu gewährleisten.

## Umgang mit Netzwerkproblemen
<a name="web-publish-subscribe-network-issues"></a>

Bei Unterbrechung der Netzwerkverbindung des lokalen Geräts versucht das SDK intern, die Verbindung ohne Benutzeraktion wiederherzustellen. In einigen Fällen ist das SDK nicht erfolgreich, weshalb eine Benutzeraktion erforderlich ist.

Generell kann der Status der Stage über das Ereignis `STAGE_CONNECTION_STATE_CHANGED` gesteuert werden:

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

Im Allgemeinen können Sie einen fehlerhaften Status ignorieren, der nach dem erfolgreichen Beitritt zu einer Stage auftritt, da das SDK versuchen wird, die Verbindung intern wiederherzustellen. Wenn das SDK einen `ERRORED`-Status meldet und die Stage über einen längeren Zeitraum (z. B. 30 Sekunden oder länger) im `CONNECTING`-Status verbleibt, sind Sie wahrscheinlich vom Netzwerk getrennt worden.

## Übertragung der Stage auf einen IVS-Kanal
<a name="web-publish-subscribe-broadcast-stage"></a>

Zum Übertragen einer Stage erstellen Sie eine separate `IVSBroadcastClient`-Sitzung und folgen Sie dann den oben beschriebenen üblichen Anweisungen für die Übertragung mit dem SDK. Mithilfe der Liste der über `STAGE_PARTICIPANT_STREAMS_ADDED` offengelegten `StageStream` können die Medienstreams der Teilnehmer abgerufen werden, die wie folgt auf die Zusammensetzung der übertragenen Streams angewendet werden können:

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

Optional können Sie eine Stage zusammenstellen und sie auf einen IVS-Kanal mit niedriger Latenz übertragen, um ein größeres Publikum zu erreichen. Sehen Sie [Aktivierung mehrerer Hosts in einem Amazon-IVS-Stream](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/multiple-hosts.html) im Benutzerhandbuch für IVS-Streaming mit niedriger Latenz.

# Bekannte Probleme und Behelfslösungen im IVS Web Broadcast SDK \$1 Streaming in Echtzeit
<a name="broadcast-web-known-issues"></a>

In diesem Dokument werden bekannte Probleme aufgeführt, die bei der Verwendung des Web Broadcast SDK von Amazon-IVS-Streaming in Echtzeit auftreten können, und es werden mögliche Problemumgehungen vorgeschlagen.
+ Wenn Browser-Tabs oder Browser ohne Aufruf von `stage.leave()` geschlossen werden, können Benutzer noch bis zu 10 Sekunden lang mit einem eingefrorenen Frame oder einem schwarzen Bildschirm in der Sitzung zu sehen sein.

  **Problemumgehung:** Keine.
+ Safari-Sitzungen werden für Benutzer, die nach Beginn einer Sitzung beitreten, mitunter mit einem schwarzen Bildschirm angezeigt.

  **Problemumgehung:** Aktualisieren Sie den Browser und stellen Sie die Verbindung zur Sitzung erneut her.
+ Safari stellt Sitzungen bei einem Netzwerkwechsel nicht ordnungsgemäß wieder her.

  **Problemumgehung:** Aktualisieren Sie den Browser und stellen Sie die Verbindung zur Sitzung erneut her.
+ Die Entwicklerkonsole wiederholt den Fehler `Error: UnintentionalError at StageSocket.onClose`.

  **Problemumgehung:** Pro Teilnehmertoken kann nur eine Stage erstellt werden. Dieser Fehler tritt auf, wenn mehr als eine `Stage`-Instance mit demselben Teilnehmertoken erstellt wird, unabhängig davon, ob sich die Instance auf einem oder mehreren Geräten befindet.
+ Es kann zu Problemen bei der Aufrechterhaltung eines `StageParticipantPublishState.PUBLISHED`-Status kommen und Sie können wiederholte `StageParticipantPublishState.ATTEMPTING_PUBLISH`-Status erhalten, wenn Sie das Ereignis `StageEvents.STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED` abhören.

  **Umgehung:** Beschränken Sie die Videoauflösung auf 720p, wenn Sie `getUserMedia` oder `getDisplayMedia` aufrufen. Insbesondere dürfen Ihre `getUserMedia`- und `getDisplayMedia`-Beschränkungswerte für Breite und Höhe 921 600 (1280\$1720) nicht überschreiten, wenn sie miteinander multipliziert werden.
+ Wenn `stage.leave()` aufgerufen wird oder ein Remote-Teilnehmer die Stage verlässt, wird in der Debug-Konsole des Browsers der Fehler „404 DELETE“ angezeigt.

  **Problemumgehung:** Keine. Hierbei handelt es sich um einen harmlosen Fehler.

## Einschränkungen von Safari
<a name="broadcast-web-safari-limitations"></a>
+ Wenn bei einer entsprechenden Aufforderung die Erteilung einer Berechtigung verweigert wird, muss die Berechtigung in den Einstellungen auf der Safari-Website auf Betriebssystemebene zurückgesetzt werden.
+ Safari erkennt nicht alle Geräte nativ so effektiv wie Firefox oder Chrome. OBS Virtual Camera wird beispielsweise nicht erkannt.

## Einschränkungen von Firefox
<a name="broadcast-web-firefox-limitations"></a>
+ Damit Firefox den Bildschirm freigeben kann, müssen Systemberechtigungen aktiviert sein. Nach deren Aktivierung muss Firefox neu gestartet werden, damit es ordnungsgemäß funktioniert. Andernfalls löst der Browser eine [NotFoundError](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#exceptions)-Ausnahme aus, wenn Berechtigungen als gesperrt betrachtet werden.
+ Die Methode `getCapabilities` fehlt. Das bedeutet, dass Benutzer die Auflösung oder das Seitenverhältnis der Medienspur nicht abrufen können. Weitere Informationen finden Sie in diesem [Bugzilla-Thread](https://bugzilla.mozilla.org/show_bug.cgi?id=1179084).
+ Es fehlen mehrere `AudioContext`-Eigenschaften, z. B. die Latenz und die Kanalanzahl. Dies könnte für erfahrene Benutzer, die die Audiospuren bearbeiten möchten, ein Problem darstellen.
+ Kamera-Feeds von `getUserMedia` sind unter macOS auf ein Seitenverhältnis von 4:3 beschränkt. Weitere Informationen finden Sie im [Bugzilla-Thread 1](https://bugzilla.mozilla.org/show_bug.cgi?id=1193640) und im [Bugzilla-Thread 2](https://bugzilla.mozilla.org/show_bug.cgi?id=1306034).
+ Die Audioerfassung wird mit `getDisplayMedia` nicht unterstützt. Weitere Informationen finden Sie in diesem [Bugzilla-Thread](https://bugzilla.mozilla.org/show_bug.cgi?id=1541425).
+ Die Framerate bei der Bildschirmerfassung ist suboptimal (ungefähr 15 Bilder pro Sekunde?). Weitere Informationen finden Sie in diesem [Bugzilla-Thread](https://bugzilla.mozilla.org/show_bug.cgi?id=1703522).

## Einschränkungen im mobilen Web
<a name="broadcast-web-mobile-web-limitations"></a>
+ Die Bildschirmfreigabe von [getDisplayMedia](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#browser_compatibility) wird auf Mobilgeräten nicht unterstützt.

  **Problemumgehung:** Keine.
+ Beim Schließen eines Browsers dauert es 15 bis 30 Sekunden, bis der Teilnehmer den Browser verlässt, ohne `leave()` aufzurufen.

  **Problemumgehung**: Fügen Sie eine Benutzeroberfläche hinzu, die Benutzer dazu ermutigt, die Verbindung ordnungsgemäß zu trennen.
+ Die Hintergrund-App führt dazu, dass die Veröffentlichung von Videos beendet wird.

  **Problemumgehung**: Zeigen Sie ein UI-Slate an, wenn der Publisher angehalten ist.
+ Nach dem Aufheben der Stummschaltung einer Kamera auf Android-Geräten sinkt die Video-Framerate für etwa 5 Sekunden.

  **Problemumgehung:** Keine.
+ Der Video-Feed wird bei der Rotation für iOS 16.0 gestreckt.

  **Problemumgehung**: Zeigen Sie eine Benutzeroberfläche an, die dieses bekannte Betriebssystemproblem beschreibt.
+ Beim Wechseln des Audio-Eingabegeräts wird automatisch auch das Audio-Ausgabegerät umgeschaltet.

  **Problemumgehung:** Keine.
+ Wenn der Browser in den Hintergrund gestellt wird, wird der Veröffentlichungsstream schwarz und es wird nur Audio erzeugt.

  **Problemumgehung:** Keine. Dies geschieht aus Sicherheitsgründen.

# Fehlerbehandlung im IVS Web Broadcast SDK \$1 Streaming in Echtzeit
<a name="broadcast-web-error-handling"></a>

Dieser Abschnitt gibt einen Überblick über die Fehlerbedingungen, wie das Web-Broadcast-SDK sie an die Anwendung meldet und wie eine Anwendung reagieren sollte, wenn diese Fehler auftreten. Fehler werden vom SDK an die Listener des `StageEvents.ERROR`-Ereignisses gemeldet:

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

## Stagefehler
<a name="web-error-handling-stage-errors"></a>

Ein StageError-Fehler wird gemeldet, wenn das SDK auf ein Problem stößt, das nicht behoben werden kann und für dessen Behebung im Allgemeinen ein Eingreifen der App und/oder eine erneute Netzwerkverbindung erforderlich ist.

Jeder gemeldete `StageError` hat einen Code (oder `StageErrorCode`), eine Meldung (Zeichenfolge) und eine Kategorie (`StageErrorCategory`). Alles davon bezieht sich auf eine zugrunde liegende Vorgangskategorie.

Die Vorgangskategorie des Fehlers wird danach bestimmt, ob er mit der Verbindung zur Stage (`JOIN_ERROR`), dem Senden von Medien an die Stage (`PUBLISH_ERROR`) oder dem Empfangen eines eingehenden Medienstreams von der Stage (`SUBSCRIBE_ERROR`) zusammenhängt.

Die Codeeigenschaft eines `StageError` meldet das spezifische Problem:


| Name | Code | Empfohlene Aktion | 
| --- | --- | --- | 
| TOKEN\$1MALFORMED | 1 | Erstellen Sie ein gültiges Token und versuchen Sie erneut, die Stage zu instanziieren. | 
| TOKEN\$1EXPIRED | 2 | Erstellen Sie ein noch nicht abgelaufenes Token und versuchen Sie erneut, die Stage zu instanziieren. | 
| TIMEOUT | 3 | Bei der Operation ist eine Zeitüberschreitung aufgetreten. Wenn die Stage vorhanden und das Token gültig ist, handelt es sich bei diesem Fehler wahrscheinlich um ein Netzwerkproblem. Warten Sie in diesem Fall, bis die Konnektivität des Geräts wiederhergestellt ist. | 
| FEHLGESCHLAGEN | 4 | Beim Versuch, einen Vorgang auszuführen, ist ein schwerwiegender Fehler aufgetreten. Überprüfen Sie Fehlerdetails. Wenn die Stage vorhanden und das Token gültig ist, handelt es sich bei diesem Fehler wahrscheinlich um ein Netzwerkproblem. Warten Sie in diesem Fall, bis die Konnektivität des Geräts wiederhergestellt ist. Bei den meisten Fehlern im Zusammenhang mit der Netzwerkstabilität versucht das SDK intern für einen Zeitraum von bis zu 30 Sekunden erneut, eine Verbindung herzustellen, bevor ein FAILED-Fehler ausgegeben wird.  | 
| CANCELED | 5 | Überprüfen Sie den Anwendungscode und stellen Sie sicher, dass es keine wiederholten `join`-, `refreshStrategy`- oder `replaceStrategy`-Aufrufe gibt, die dazu führen könnten, dass wiederholte Vorgänge gestartet und vor Abschluss abgebrochen werden. | 
| STAGE\$1AT\$1CAPACITY | 6 | Dieser Fehler weist darauf hin, dass die Stage oder Ihr Konto voll ausgelastet ist. Falls die Stage das Teilnehmerlimit erreicht hat, wiederholen Sie den Vorgang, wenn die Stage nicht mehr voll ausgelastet ist, indem Sie die Strategie aktualisieren. Wenn Ihr Konto das Kontingent für gleichzeitige Abonnements oder Publisher erreicht hat, reduzieren Sie die Nutzung oder fordern Sie über die [AWS-Service-Quotas-Konsole](https://console.aws.amazon.com/servicequotas/) eine Erhöhung des Kontingents an.  | 
| CODEC\$1MISMATCH | 7 | Der Codec wird von der Stage nicht unterstützt. Überprüfen Sie den Browser und die Plattform auf Codec-Unterstützung. Für IVS-Echtzeit-Streaming müssen Browser den H.264-Codec für Video und den Opus-Codec für Audio unterstützen. | 
| TOKEN\$1NOT\$1ALLOWED | 8 | Das Token hat keine Berechtigung für den Vorgang. Erstellen Sie das Token mit den richtigen Berechtigungen neu und versuchen Sie es erneut. | 
| STAGE\$1DELETED | 9 | Keine; der Versuch, einer gelöschten Stage beizutreten, löst diesen Fehler aus. | 
| PARTICIPANT\$1DISCONNECTED | 10 | Keine; der Versuch, mit dem Token eines Teilnehmers beizutreten, dessen Verbindung getrennt wurde, löst diesen Fehler aus. | 

### Beispiel für die Behandlung von StageError
<a name="web-error-handling-stage-errors-example"></a>

Verwenden Sie den StageError-Code, um festzustellen, ob der Fehler auf ein abgelaufenes Token zurückzuführen ist:

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

### Netzwerkfehler, wenn bereits eine Verbindung hergestellt wurde
<a name="web-error-handling-stage-errors-network"></a>

Wenn die Netzwerkverbindung des Geräts ausfällt, verliert das SDK möglicherweise die Verbindung zu den Stageservern. Möglicherweise werden in der Konsole Fehler angezeigt, da das SDK die Backend-Dienste nicht mehr erreichen kann. POSTs auf https://broadcast.stats.live-video.net schlagen fehl.

Wenn Sie etwas veröffentlichen und/oder abonnieren, werden in der Konsole Fehler angezeigt, die sich auf Versuche beziehen, etwas zu veröffentlichen/zu abonnieren.

Intern versucht das SDK, die Verbindung mithilfe einer exponentiellen Backoff-Strategie wiederherzustellen.

**Aktion**: Warten Sie, bis die Konnektivität des Geräts wiederhergestellt ist.

## Fehlerstatus
<a name="web-error-handling-errored-states"></a>

Es wird empfohlen, diese Status für die Anwendungsprotokollierung zu verwenden und Benutzern Nachrichten anzuzeigen, die sie über Verbindungsprobleme mit der Stage für einen bestimmten Teilnehmer informieren.

### Veröffentlichen
<a name="errored-states-publish"></a>

Das SDK meldet `ERRORED`, wenn eine Veröffentlichung fehlschlägt.

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

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

Das SDK meldet `ERRORED`, wenn ein Abonnement fehlschlägt. Dies kann auf Netzwerkbedingungen zurückzuführen sein oder wenn eine Stage für Abonnenten ausgelastet ist.

```
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-Handbuch \$1 Echtzeit-Streaming
<a name="broadcast-android"></a>

Das Android-Broadcast-SDK für IVS-Echtzeit-Streaming ermöglicht es den Teilnehmern, Videos auf Android zu senden und zu empfangen.

Das Paket `com.amazonaws.ivs.broadcast` implementiert die in diesem Dokument beschriebene Schnittstelle. Das SDK unterstützt die folgenden Vorgänge:
+ Einer Stage beitreten 
+ Medien für andere Teilnehmer auf der Stage veröffentlichen
+ Medien anderer Teilnehmer auf der Stage abonnieren
+ Auf der Stage veröffentlichte Videos und Audios verwalten und überwachen
+ WebRTC-Statistiken für jede Peer-Verbindung beziehen
+ Alle Vorgänge aus dem IVS-Streaming-SDK für Android-Übertragungen mit niedriger Latenz

**Aktuelle Version des Broadcast-SDK für Android:** 1.40.0 ([Versionshinweise](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-android-rt)) 

Informationen zu den wichtigsten Methoden, die im Amazon-IVS-Android-Broadcast-SDK verfügbar sind, finden Sie in der **Referenzdokumentation** unter [https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/android/](https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/android/).

**Beispiel-Code:** Siehe das Android-Beispiel-Repository auf GitHub: [https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples).

**Plattformanforderungen:** Android 9.0 und höher

# Erste Schritte mit dem IVS Android Broadcast SDK \$1 Streaming in Echtzeit
<a name="broadcast-android-getting-started"></a>

Dieses Dokument führt Sie durch die Schritte zum Einstieg in das Android Broadcast SDK von IVS-Streaming in Echtzeit.

## Bibliothek installieren
<a name="broadcast-android-install"></a>

Es gibt mehrere Möglichkeiten, die Android-Broadcast-Bibliothek von Amazon IVS Ihrer Android-Entwicklungsumgebung hinzuzufügen: direkte Verwendung von Gradle, Verwendung von Gradle-Versionskatalogen oder manuelle Installation des SDK.

**Direkte Verwendung von Gradle**: Fügen Sie die Bibliothek zur `build.gradle`-Datei Ihres Moduls hinzu, wie hier gezeigt (für die aktuelle Version des IVS-Broadcast-SDK):

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

**Verwendung von Gradle-Versionskatalogen**: Fügen Sie zunächst Folgendes in die `build.gradle`-Datei Ihres Moduls ein:

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

Fügen Sie anschließend Folgendes in die `libs.version.toml`-Datei ein (für die aktuelle Version des IVS-Broadcast-SDK):

```
[versions]
ivs="1.40.0"

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

**Manuelle Installation des SDK**: Laden Sie die aktuelle Version von diesem Speicherort herunter:

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

Laden Sie unbedingt die `aar` mit `-stages` angehängt herunter.

**Außerdem die Kontrolle der Freisprecheinrichtung durch das SDK zulassen**: Unabhängig davon, welche Installationsmethode Sie wählen, fügen Sie Ihrem Manifest auch die folgende Berechtigung hinzu, damit das SDK die Freisprecheinrichtung aktivieren und deaktivieren kann:

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

## Verwenden des SDK mit Debug-Symbolen
<a name="broadcast-android-using-debug-symbols-rt"></a>

Wir veröffentlichen auch eine Version des Android-Broadcast-SDK, die Debug-Symbole enthält. Sie können diese Version verwenden, um die Qualität von Debug-Berichten (Stack-Traces) in Firebase Crashlytics zu verbessern, falls im IVS-Broadcast-SDK Abstürze auftreten, d. h. `libbroadcastcore.so`. Wenn Sie diese Abstürze dem SDK-Team von IVS melden, erleichtern die qualitativ hochwertigeren Stack-Traces die Behebung der Probleme.

Um diese Version des SDK zu verwenden, fügen Sie Folgendes in Ihre Gradle-Build-Dateien ein:

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

Verwenden Sie die obige Zeile anstelle von:

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

### Hochladen von Symbolen zu Firebase Crashlytics
<a name="android-debug-symbols-rt-firebase-crashlytics"></a>

Stellen Sie sicher, dass Ihre Gradle-Build-Dateien für Firebase Crashlytics eingerichtet sind. Folgen Sie den Anweisungen von Google hier:

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

Achten Sie darauf, `com.google.firebase:firebase-crashlytics-ndk` als Abhängigkeit anzugeben.

Wenn Sie Ihre App für die Veröffentlichung erstellen, muss das Firebase-Crashlytics-Plugin Symbole automatisch hochladen. Führen Sie einen der folgenden Befehle aus, um Symbole manuell hochzuladen:

```
gradle uploadCrashlyticsSymbolFileRelease
```

```
./gradlew uploadCrashlyticsSymbolFileRelease
```

(Es schadet nicht, wenn Symbole zweimal hochgeladen werden, sowohl automatisch als auch manuell.)

### Verhindern, dass Ihre APK-Version größer wird
<a name="android-debug-symbols-rt-sizing-apk"></a>

Vor dem Verpacken der `.apk`-Release-Datei versucht das Android-Gradle-Plugin automatisch, Debug-Informationen aus gemeinsam genutzten Bibliotheken (einschließlich der `libbroadcastcore.so`-Bibliothek des IVS-Broadcast-SDK) zu entfernen. Manchmal geschieht dies jedoch nicht. Infolgedessen könnte Ihre `.apk`-Datei größer werden und Sie könnten vom Android-Gradle-Plugin eine Warnmeldung erhalten, dass es Debug-Symbole nicht entfernen kann und die `.so`-Dateien unverändert verpackt. Wenn dies passiert, gehen Sie wie folgt vor:
+ Installieren Sie ein Android-NDK. Jede aktuelle Version funktioniert.
+ Fügen Sie `ndkVersion <your_installed_ndk_version_number>` zur `build.gradle`-Datei Ihrer Anwendung hinzu. Tun Sie dies auch dann, wenn Ihre Anwendung selbst keinen nativen Code enthält.

Weitere Informationen finden Sie in diesem [Problembericht](https://issuetracker.google.com/issues/353554169).

## Berechtigungen anfordern
<a name="broadcast-android-permissions"></a>

Ihre App muss die Berechtigung für den Zugriff auf die Kamera und das Mikrofon des Benutzers anfordern. (Dies ist nicht spezifisch für Amazon IVS; es ist für alle Anwendungen erforderlich, die Zugriff auf Kameras und Mikrofone benötigen.)

Hier prüfen wir, ob der Benutzer bereits Berechtigungen erteilt hat und fragen, wenn nicht, nach ihnen:

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

Hier erhalten wir die Antwort des Benutzers:

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

# Veröffentlichen und Abonnieren mit dem IVS Android Broadcast SDK \$1 Streaming in Echtzeit
<a name="android-publish-subscribe"></a>

Dieses Dokument führt Sie durch die Schritte zum Veröffentlichen und Abonnieren einer Stufe mit dem Android Broadcast SDK von IVS-Streaming in Echtzeit.

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

Drei Kernkonzepte liegen der Echtzeit-Funktionalität zugrunde: [Stage](#android-publish-subscribe-concepts-stage), [Strategie](#android-publish-subscribe-concepts-strategy) und [Renderer](#android-publish-subscribe-concepts-renderer). Das Designziel besteht in der Minimierung der Menge an clientseitiger Logik, die für die Entwicklung eines funktionierenden Produkts erforderlich ist.

### Stage
<a name="android-publish-subscribe-concepts-stage"></a>

Die Klasse `Stage` ist der Hauptinteraktionspunkt zwischen der Hostanwendung und dem SDK. Sie stellt die Stage selbst dar und dient dazu, der Stage beizutreten und sie zu verlassen. Für das Erstellen einer Bühne und das Beitreten ist eine gültige, noch nicht abgelaufene Token-Zeichenfolge aus der Steuerebene erforderlich (dargestellt als `token`). Einer Bühne beizutreten und sie zu verlassen, ist ganz einfach. 

```
Stage stage = new Stage(context, token, strategy);

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

stage.leave();
```

In der Klasse `Stage` erfolgt auch das Anhängen des `StageRenderer`:

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

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

Über die Schnittstelle `Stage.Strategy` kann die Hostanwendung dem SDK den gewünschten Status der Bühne mitteilen. Drei Funktionen müssen implementiert werden: `shouldSubscribeToParticipant`, `shouldPublishFromParticipant` und `stageStreamsToPublishForParticipant`. Alle werden im Folgenden behandelt.

#### Abonnieren von Teilnehmern
<a name="android-publish-subscribe-concepts-strategy-participants"></a>

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

Wenn ein Remote-Teilnehmer der Stage beitritt, fragt das SDK die Hostanwendung nach dessen gewünschtem Abonnementstatus. Die Optionen lauten `NONE`, `AUDIO_ONLY` und `AUDIO_VIDEO`. Wenn ein Wert für diese Funktion zurückgegeben wird, muss sich die Hostanwendung nicht um den Veröffentlichungs-, den aktuellen Abonnement- oder den Verbindungsstatus des Stage kümmern. Bei Rückgabe von `AUDIO_VIDEO` wartet das SDK mit dem Abonnieren, bis der Remote-Teilnehmer etwas veröffentlicht. Außerdem aktualisiert das SDK die Hostanwendung während des gesamten Prozesses über den Renderer.

Hier folgt ein Beispiel für eine Implementierung:

```
@Override
Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	return Stage.SubscribeType.AUDIO_VIDEO;
}
```

Hierbei handelt es sich um die vollständige Implementierung dieser Funktion für eine Hostanwendung, bei der sich alle Teilnehmer stets gegenseitig sehen sollen; z. B. eine Video-Chat-Anwendung.

Weitergehende Implementierungen sind ebenfalls möglich. Nutzen Sie die Eigenschaft `userInfo` für `ParticipantInfo`, um Teilnehmer anhand der vom Server bereitgestellten Attribute selektiv zu abonnieren:

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

Hiermit kann eine Bühne erstellt werden, auf der Moderatoren alle Gäste überwachen können, ohne selbst gesehen oder gehört zu werden. Die Hostanwendung könnte eine zusätzliche Geschäftslogik nutzen, damit Moderatoren sich gegenseitig sehen können, für Gäste aber unsichtbar bleiben.

#### Konfiguration für das Abonnieren von Teilnehmern
<a name="android-publish-subscribe-concepts-strategy-participants-config"></a>

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

Wenn ein Remote-Teilnehmer abonniert wird (siehe [Teilnehmer abonnieren](#android-publish-subscribe-concepts-strategy-participants)), fragt das SDK die Host-Anwendung nach einer benutzerdefinierten Abonnementkonfiguration für diesen Teilnehmer ab. Diese Konfiguration ist optional und ermöglicht es der Hostanwendung, bestimmte Aspekte des Subscriber-Verhaltens zu steuern. Informationen darüber, was konfiguriert werden kann, finden Sie unter [SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration) in der SDK-Referenzdokumentation.

Hier folgt ein Beispiel für eine Implementierung:

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

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

    return config;
}
```

Diese Implementierung aktualisiert die Mindestverzögerung für den Jitter-Buffer für alle abonnierten Teilnehmer auf die Voreinstellung `MEDIUM`.

Wie bei `shouldSubscribeToParticipant` sind auch hier weitergehende Implementierungen möglich. Die `ParticipantInfo`-Angaben können verwendet werden, um die Abonnementkonfiguration für bestimmte Teilnehmer selektiv zu aktualisieren.

Wir empfehlen die Verwendung der Standardverhaltensweisen. Geben Sie die benutzerdefinierte Konfiguration nur an, wenn Sie ein bestimmtes Verhalten ändern möchten.

#### Veröffentlichen
<a name="android-publish-subscribe-concepts-strategy-publishing"></a>

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

Sobald die Verbindung zur Stage hergestellt ist, überprüft das SDK per Anfrage an die Hostanwendung, ob ein bestimmter Teilnehmer etwas veröffentlichen soll. Dies wird nur bei lokalen Teilnehmern aufgerufen, die auf Grundlage des bereitgestellten Tokens zur Veröffentlichung berechtigt sind.

Hier folgt ein Beispiel für eine Implementierung:

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

Sie ist für eine normale Video-Chat-Anwendung gedacht, bei der Benutzer immer etwas veröffentlichen möchten. Sie können die Audio- und Videowiedergabe stummschalten und die Stummschaltung aufheben, um umgehend ausgeblendet oder gesehen/gehört zu werden. (Sie können auch „Veröffentlichen/Veröffentlichung aufheben“ verwenden, was aber viel langsamer ist. „Stummschalten/Stummschalten aufheben“ ist für Anwendungsfälle vorzuziehen, in denen eine häufige Änderung der Sichtbarkeit wünschenswert ist.)

#### Auswählen von Streams zur Veröffentlichung
<a name="android-publish-subscribe-concepts-strategy-streams"></a>

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

Beim Veröffentlichen wird hiermit bestimmt, welche Audio- und Videostreams veröffentlicht werden sollen. Dieser Punkt wird später unter [Veröffentlichen eines Medienstreams](#android-publish-subscribe-publish-stream) ausführlicher behandelt.

#### Aktualisieren der Strategie
<a name="android-publish-subscribe-concepts-strategy-updates"></a>

Die Strategie soll dynamisch sein: Die von einer der oben genannten Funktionen zurückgegebenen Werte lassen sich jederzeit ändern. Wenn die Hostanwendung beispielsweise erst veröffentlichen soll, wenn der Endbenutzer auf eine Schaltfläche tippt, können Sie eine Variable aus `shouldPublishFromParticipant` zurückgeben (zum Beispiel `hasUserTappedPublishButton`). Wenn sich diese Variable aufgrund einer Interaktion des Endbenutzers ändert, signalisieren Sie dem SDK per Aufruf von `stage.refreshStrategy()`, dass es die Strategie nach den neuesten Werten abfragen und nur Dinge anwenden soll, die sich geändert haben. Wenn das SDK feststellt, dass sich der Wert `shouldPublishFromParticipant` geändert hat, startet es den Veröffentlichungsprozess. Wenn alle Funktionen bei einer SDK-Abfrage den gleichen Wert zurückgeben wie zuvor, werden mit dem Aufruf von `refreshStrategy` keine Änderungen an der Bühne durchgeführt.

Ändert sich der Rückgabewert von `shouldSubscribeToParticipant` von `AUDIO_VIDEO` in `AUDIO_ONLY`, wird der Videostream für alle Teilnehmer mit geänderten Rückgabewerten entfernt, sofern zuvor ein Videostream vorhanden war.

Im Allgemeinen nutzt die Stage die Strategie, um den Unterschied zwischen der vorherigen und der aktuellen Strategie am effizientesten anzuwenden. Dabei muss sich die Hostanwendung nicht um die ganzen Status kümmern, die für eine ordnungsgemäße Verwaltung erforderlich sind. Stellen Sie sich den Aufruf von `stage.refreshStrategy()` daher als einen ressourcenschonenden Vorgang vor, da nur bei einer Änderung der Strategie etwas unternommen wird.

### Renderer
<a name="android-publish-subscribe-concepts-renderer"></a>

Die Schnittstelle `StageRenderer` teilt der Hostanwendung den Status der Bühne mit. Aktualisierungen in der Benutzeroberfläche der Hostanwendung können in der Regel vollständig über die vom Renderer bereitgestellten Ereignisse gesteuert werden. Der Renderer stellt die folgenden Funktionen bereit:

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

Für die meisten dieser Methoden werden die entsprechende `Stage` und `ParticipantInfo` bereitgestellt.

Es wird nicht erwartet, dass sich die vom Renderer bereitgestellten Informationen auf die Rückgabewerte der Strategie auswirken. Es wird beispielsweise nicht erwartet, dass sich der Rückgabewert von `shouldSubscribeToParticipant` beim Aufruf von `onParticipantPublishStateChanged` ändert. Wenn die Hostanwendung einen bestimmten Teilnehmer abonnieren möchte, muss sie unabhängig von dessen Veröffentlichungsstatus den gewünschten Abonnementtyp zurückgeben. Das SDK muss dafür sorgen, dass entsprechend dem Status der Bühne und dem gewünschten Status der Strategie zum richtigen Zeitpunkt gehandelt wird.

Der `StageRenderer` kann der Bühnenklasse angefügt werden:

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

Hinweis: Nur veröffentlichende Teilnehmer lösen `onParticipantJoined` aus. Wenn Teilnehmer die Veröffentlichung beenden oder die Bühnensitzung verlassen, wird `onParticipantLeft` ausgelöst.

## Veröffentlichen eines Medienstreams
<a name="android-publish-subscribe-publish-stream"></a>

Lokale Geräte wie eingebaute Mikrofone und Kameras werden über `DeviceDiscovery` erkannt. Hier folgt ein Beispiel für die Auswahl der nach vorne gerichteten Kamera und des Standardmikrofons und deren anschließende Rückgabe als `LocalStageStreams` zur Veröffentlichung durch das 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;
}
```

## Anzeigen und Entfernen von Teilnehmern
<a name="android-publish-subscribe-participants"></a>

Nach Abschluss von Abonnements erhalten Sie über die Funktion `onStreamsAdded` des Renderers eine Reihe von `StageStream`-Objekten. Sie können die Vorschau von einem `ImageStageStream` abrufen:

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

Außerdem können Sie die Statistiken des Audiolevels von einem `AudioStageStream` abrufen:

```
((AudioStageStream)stream).setStatsCallback((peak, rms) -> {
	// handle statistics
});
```

Wenn ein Teilnehmer die Veröffentlichung beendet oder dessen Abonnement beendet wird, wird die Funktion `onStreamsRemoved` mit den Streams aufgerufen, die entfernt wurden. Hostanwendungen sollten dies als Signal nutzen, um den Videostream des Teilnehmers aus der Ansichtshierarchie zu entfernen.

`onStreamsRemoved` wird für alle Szenarien aufgerufen, in denen ein Stream entfernt werden könnte, darunter: 
+ Der Remote-Teilnehmer beendet die Veröffentlichung.
+ Ein lokales Gerät beendet das Abonnement oder ändert das Abonnement von `AUDIO_VIDEO` in `AUDIO_ONLY`.
+ Der Remote-Teilnehmer verlässt die Stage.
+ Der lokale Teilnehmer verlässt die Stage.

Da `onStreamsRemoved` bei allen Szenarien aufgerufen wird, ist keine benutzerdefinierte Geschäftslogik erforderlich, um Teilnehmer beim remoten oder lokalen Verlassen aus der Benutzeroberfläche zu entfernen.

## Stummschalten von Medienstreams und Aufheben der Stummschaltung
<a name="android-publish-subscribe-mute-streams"></a>

`LocalStageStream`-Objekte verfügen über eine `setMuted`-Funktion, die das Stummschalten des Streams steuert. Diese Funktion kann für den Stream aufgerufen werden, bevor oder nachdem er von der Strategiefunktion `streamsToPublishForParticipant` zurückgegeben wird.

**Wichtig**: Wenn nach einem Aufruf von `refreshStrategy` eine neue `LocalStageStream`-Objekt-Instance von `streamsToPublishForParticipant` zurückgegeben wird, wird der Stummschaltungsstatus des neuen Streamobjekts auf die Bühne angewendet. Seien Sie vorsichtig beim Erstellen neuer `LocalStageStream`-Instances, um sicherzustellen, dass der erwartete Stummschaltungsstatus beibehalten wird.

## Überwachen des Medien-Stummschaltungsstatus von Remote-Teilnehmern
<a name="android-publish-subscribe-mute-state"></a>

Wenn ein Teilnehmer den Stummschaltungsstatus seines Video- oder Audiostreams ändert, wird die Funktion `onStreamMutedChanged` des Renderers mit einer Liste der Streams aufgerufen, die sich geändert haben. Verwenden Sie die Methode `getMuted` für `StageStream`, um die Benutzeroberfläche entsprechend zu aktualisieren. 

```
@Override
void onStreamsMutedChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams) {
	for (StageStream stream : streams) {
		boolean muted = stream.getMuted();
		// handle UI changes
	}
}
```

## Abrufen von WebRTC-Statistiken
<a name="android-publish-subscribe-webrtc-stats"></a>

Um die neuesten WebRTC-Statistiken für einen veröffentlichten oder abonnierten Stream abzurufen, verwenden Sie `requestRTCStats` für `StageStream`. Nach Abschluss einer Erfassung erhalten Sie Statistiken über den `StageStream.Listener`, der für `StageStream` eingestellt werden kann.

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

## Abrufen von Teilnehmerattributen
<a name="android-publish-subscribe-participant-attributes"></a>

Wenn Sie Attribute in der Vorgangsanfrage `CreateParticipantToken` angeben, können Sie die Attribute in den Eigenschaften von `ParticipantInfo` einsehen:

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

## Eingebettete Nachrichten
<a name="android-publish-subscribe-embed-messages"></a>

Die `embedMessage`-Methode auf ImageDevice ermöglicht es Ihnen, Metadaten-Nutzdaten während der Veröffentlichung direkt in Videoframes einzufügen. Dies ermöglicht framesynchronisiertes Messaging für Echtzeitanwendungen. Das Einbetten von Nachrichten ist nur verfügbar, wenn das SDK für die Veröffentlichung in Echtzeit verwendet wird (nicht für Veröffentlichungen mit niedriger Latenz).

Es kann nicht garantiert werden, dass eingebettete Nachrichten bei Abonnenten ankommen, da sie direkt in Videoframes eingebettet und über UDP übertragen werden, wodurch die Paketzustellung nicht garantiert wird. Der Verlust von Paketen während der Übertragung kann zum Verlust von Nachrichten führen, insbesondere bei schlechten Netzwerkbedingungen. Um dem entgegenzuwirken, beinhaltet die `embedMessage`-Methode einen `repeatCount`-Parameter, der die Nachricht über mehrere aufeinanderfolgende Frames dupliziert und so die Zuverlässigkeit der Zustellung erhöht. Diese Funktion ist nur für Videostreams verfügbar.

### Verwenden von embedMessage
<a name="android-embed-messages-using-embedmessage"></a>

Publishing-Clients können Nachrichten-Nutzdaten mithilfe der `embedMessage`-Methode auf ImageDevice in ihren Videostream einbetten. Die Nutzdaten müssen größer als 0 KB und kleiner als 1 KB sein. Die Anzahl der pro Sekunde eingebetteten Nachrichten darf 10 KB pro Sekunde nicht überschreiten. 

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

### Nachrichten-Nutzdaten
<a name="android-embed-messages-repeat-payloads"></a>

Verwenden Sie `repeatCount`, um die Nachricht über mehrere Frames hinweg zu duplizieren, um die Zuverlässigkeit zu erhöhen. Dieser Wert muss zwischen 0 und 30 liegen. Empfangende Clients müssen über eine Logik verfügen, um die Nachricht zu deduplizieren.

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

### Lesen eingebetteter Nachrichten
<a name="android-embed-messages-read-messages"></a>

Informationen zum Lesen eingebetteter Nachrichten aus eingehenden Streams finden Sie weiter unten unter „Zusätzliche Erweiterungsinformationen (SEI) abrufen“.

## Abrufen von SEI-Daten (Supplemental Enhancement Information)
<a name="android-publish-subscribe-sei-attributes"></a>

Die NAL-Einheit für Supplemental Enhancement Information (SEI) wird verwendet, um Frame-orientierte Metadaten zusammen mit dem Video zu speichern. Subscriber können SEI-Nutzlasten von einem Publisher lesen, der H.264-Video veröffentlicht, indem sie die Eigenschaften `embeddedMessages` der `ImageDeviceFrame`-Objekte aus dem `ImageDevice` des Publishers überprüfen. Erlangen Sie dazu das `ImageDevice` eines Publishers und beobachten Sie dann jeden Frame über einen Callback an `setOnFrameCallback`, wie im folgenden Beispiel gezeigt:

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

## Fortsetzen der Sitzung im Hintergrund
<a name="android-publish-subscribe-background-session"></a>

Wenn die App in den Hintergrund wechselt, können Sie die Veröffentlichung beenden oder das Abonnement auf das Audio anderer Remote-Teilnehmer beschränken. Dazu aktualisieren Sie die Implementierung Ihrer `Strategy`, um die Veröffentlichung zu beenden und `AUDIO_ONLY` zu abonnieren (oder gegebenenfalls `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();
}
```

## Mehrschichtige Kodierung mit Simulcast
<a name="android-publish-subscribe-layered-encoding-simulcast"></a>

Bei der mehrschichtigen Kodierung mit Simulcast handelt es sich um ein Feature für IVS-Echtzeit-Streaming, mit dessen Hilfe Publisher mehrere Videoschichten unterschiedlicher Qualität senden können. Subscriber können diese Schichten dynamisch oder manuell konfigurieren. Das Feature wird im Dokument [Streaming-Optimierungen](real-time-streaming-optimization.md) ausführlicher beschrieben.

### Konfigurieren mehrschichtiger Kodierung (Publisher)
<a name="android-layered-encoding-simulcast-configure-publisher"></a>

Um als Publisher die mehrschichtige Kodierung mit Simulcast zu aktivieren, fügen Sie dem `LocalStageStream` bei der Instanziierung die folgende Konfiguration hinzu:

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

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

Je nach der in der Videokonfiguration eingestellten Auflösung wird eine festgelegte Anzahl von Schichten kodiert und gesendet, wie im Abschnitt [Standardmäßige Schichten, Qualitäten und Bildraten](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) von *Streaming-Optimierungen* definiert.

Außerdem können Sie optional einzelne Ebenen innerhalb der Simulcast-Konfiguration konfigurieren: 

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

Alternativ können Sie eigene benutzerdefinierte Ebenenkonfigurationen für bis zu drei Ebenen erstellen. Wenn Sie ein leeres Array oder keinen Wert angeben, werden die oben beschriebenen Standardwerte verwendet. Ebenen werden mit den folgenden erforderlichen Eigenschaftssetzern beschrieben:
+ `setSize: Vec2;`
+ `setMaxBitrate: integer;`
+ `setMinBitrate: integer;`
+ `setTargetFramerate: integer;`

Ausgehend von den Voreinstellungen können Sie entweder einzelne Eigenschaften überschreiben oder eine völlig neue Konfiguration erstellen:

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

Informationen zu Höchstwerten, Grenzwerten und Fehlern, die bei der Konfiguration einzelner Ebenen ausgelöst werden können, finden Sie in der SDK-Referenzdokumentation.

### Konfigurieren mehrschichtiger Kodierung (Subscriber)
<a name="android-layered-encoding-simulcast-configure-subscriber"></a>

Subscriber müssen nichts unternehmen, um die mehrschichtige Kodierung zu aktivieren. Wenn ein Publisher Simulcast-Schichten sendet, passt sich der Server standardmäßig dynamisch den Schichten an, um je nach Gerät und Netzwerkbedingungen des Subscribers die optimale Qualität auszuwählen.

Alternativ gibt es mehrere nachfolgend beschriebene Optionen, um explizite Schichten auszuwählen, die der Publisher sendet.

### Option 1: Einstellung für die Qualität der Anfangsschicht
<a name="android-layered-encoding-simulcast-layer-quality-preference"></a>

Mit der Strategie `subscribeConfigurationForParticipant` können Sie auswählen, welche Anfangsschicht Sie als Subscriber erhalten möchten:

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

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

    return config;
}
```

Standardmäßig wird Subscribern zunächst immer die Schicht mit der niedrigsten Qualität gesendet. Nach und nach wird die Qualität gesteigert, bis die Schicht mit der höchsten Qualität erreicht ist. Das optimiert den Bandbreitenverbrauch der Endbenutzer, verkürzt die Zeit bis zum Abspielen des Videos und verringert das anfängliche Einfrieren von Videos bei Benutzern in Netzwerken mit geringerer Bandbreite.

Folgende Optionen sind für `InitialLayerPreference` verfügbar:
+ `LOWEST_QUALITY` – Der Server stellt zuerst die Videoschicht mit der niedrigsten Qualität bereit. Dadurch werden der Bandbreitenverbrauch und die Zeit bis zum Abspielen von Medien optimiert. Die Qualität ist definiert als die Kombination aus Größe, Bitrate und Bildrate des Videos. Beispielsweise weisen 720p-Videos eine geringere Qualität auf als 1080p-Videos.
+ `HIGHEST_QUALITY` – Der Server stellt zuerst die Videoschicht mit der höchsten Qualität bereit. Das optimiert die Qualität, kann aber die Zeit bis zum Abspielen von Medien verlängern. Die Qualität ist definiert als die Kombination aus Größe, Bitrate und Bildrate des Videos. Beispielsweise weisen 1080p-Videos eine höhere Qualität auf als 720p-Videos.

**Hinweis:** Damit die anfänglichen Schichteinstellungen (der Aufruf `setInitialLayerPreference`) wirksam werden, muss ein neues Abonnement abgeschlossen werden, da diese Updates für das aktive Abonnement nicht gelten.

### Option 2: Bevorzugte Schicht für Streams
<a name="android-layered-encoding-simulcast-preferred-layer"></a>

Mit der Strategiemethode `preferredLayerForStream` können Sie eine Schicht auswählen, nachdem der Stream gestartet wurde. Diese Strategiemethode erhält die Teilnehmer- und Streaminformationen, sodass Sie eine Schicht für jeden Teilnehmer auswählen können. Das SDK ruft diese Methode als Reaktion auf bestimmte Ereignisse auf, z. B. wenn sich die Streamschichten ändern, sich der Teilnehmerstatus ändert oder die Hostanwendung die Strategie aktualisiert.

Der Strategiemodus gibt ein `RemoteStageStream.Layer`-Objekt zurück, wobei es sich um Folgendes handeln kann:
+ ein Schichtobjekt, z. B. eines, das von `RemoteStageStream.getLayers` zurückgegeben wird
+ null, was bedeutet, dass keine Schicht ausgewählt werden sollte und eine dynamische Anpassung bevorzugt wird

Bei der folgenden Strategie wählen die Benutzer beispielsweise immer die Videoschicht mit der niedrigsten verfügbaren Qualität aus:

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

Um die Schichtauswahl zurückzusetzen und zur dynamischen Anpassung zurückzukehren, geben Sie in der Strategie null oder undefiniert zurück. In diesem Beispiel ist `appState` eine Platzhaltervariable, die den Status der Hostanwendung darstellt.

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

### Option 3: Helferobjekte für RemoteStageStream-Schicht
<a name="android-layered-encoding-simulcast-remotestagestream-helpers"></a>

`RemoteStageStream` weist mehrere Helferobjekte auf, mit deren Hilfe Entscheidungen über die Schichtauswahl getroffen und Endbenutzern die entsprechende Auswahl angezeigt werden kann:
+ **Schichtereignisse** – Neben `StageRenderer` verfügt der `RemoteStageStream.Listener` über Ereignisse, die Änderungen bei der Schicht- und Simulcast-Anpassung kommunizieren:
  + `void onAdaptionChanged(boolean adaption)`
  + `void onLayersChanged(@NonNull List<Layer> layers)`
  + `void onLayerSelected(@Nullable Layer layer, @NonNull LayerSelectedReason reason)`
+ **Schichtmethoden** – `RemoteStageStream` verfügt über mehrere Helfermethoden, mit denen Informationen über den Stream und die präsentierten Schichten abgerufen werden können. Diese Methoden sind sowohl für den in der Strategie `preferredLayerForStream` bereitgestellten Remote-Stream als auch für Remote-Streams verfügbar, die über `StageRenderer.onStreamsAdded` verfügbar gemacht werden.
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`
  + `stream.getLayersWithConstraints`

Einzelheiten finden Sie im Abschnitt zur Klasse `RemoteStageStream` in der [SDK-Referenzdokumentation](https://aws.github.io/amazon-ivs-broadcast-docs/latest/android/). Falls als Grund für `LayerSelected` `UNAVAILABLE` zurückgegeben wird, bedeutet das, dass die angeforderte Schicht nicht ausgewählt werden konnte. Stattdessen wird eine bestmögliche Auswahl getroffen. Dabei handelt es sich in der Regel um eine Schicht mit niedrigerer Qualität, um die Stabilität des Streams zu gewährleisten.

## Einschränkungen der Videokonfiguration
<a name="android-publish-subscribe-video-limits"></a>

Das SDK unterstützt kein Erzwingen des Hoch- oder Querformats mit `StageVideoConfiguration.setSize(BroadcastConfiguration.Vec2 size)`. Im Hochformat wird die kleinere Dimension als Breite verwendet, im Querformat als Höhe. Das bedeutet, dass die folgenden beiden Aufrufe von `setSize` die gleiche Auswirkung auf die Videokonfiguration haben:

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

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

## Umgang mit Netzwerkproblemen
<a name="android-publish-subscribe-network-issues"></a>

Bei Unterbrechung der Netzwerkverbindung des lokalen Geräts versucht das SDK intern, die Verbindung ohne Benutzeraktion wiederherzustellen. In einigen Fällen ist das SDK nicht erfolgreich, weshalb eine Benutzeraktion erforderlich ist. Es gibt zwei Hauptfehler im Zusammenhang mit der Unterbrechung der Netzwerkverbindung:
+ Fehlercode 1400, Meldung: „Die PeerConnection wurde aufgrund eines unbekannten Netzwerkfehlers unterbrochen.“
+ Fehlercode 1300, Meldung: „Die Zahl der Wiederholungsversuche ist ausgeschöpft.“

Wenn der erste Fehler empfangen wird, der zweite jedoch nicht, ist das SDK immer noch mit der Bühne verbunden und versucht, die Verbindungen automatisch wiederherzustellen. Zur Sicherheit können Sie `refreshStrategy` ohne Änderungen an den Rückgabewerten der Strategiemethode aufrufen, um einen manuellen Neuverbindungsversuch auszulösen.

Wenn der zweite Fehler empfangen wird, sind die Neuverbindungsversuche des SDK fehlgeschlagen und das lokale Gerät ist nicht mehr mit der Bühne verbunden. Versuchen Sie in diesem Fall, der Bühne erneut beizutreten, indem Sie `join` aufrufen, nachdem die Netzwerkverbindung wiederhergestellt wurde.

Im Allgemeinen deutet das Auftreten von Fehlern nach dem erfolgreichen Beitritt zu einer Bühne darauf hin, dass das SDK beim Wiederherstellen einer Verbindung nicht erfolgreich war. Erstellen Sie ein neues `Stage`-Objekt und versuchen Sie, der Bühne beizutreten, wenn sich die Netzwerkbedingungen verbessern.

## Verwenden von Bluetooth-Mikrofonen
<a name="android-publish-subscribe-bluetooth-microphones"></a>

Um mit Bluetooth-Mikrofongeräten zu veröffentlichen, müssen Sie eine Bluetooth-SCO-Verbindung herstellen:

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

# Bekannte Probleme und Behelfslösungen im IVS Android Broadcast SDK \$1 Streaming in Echtzeit
<a name="broadcast-android-known-issues"></a>

In diesem Dokument werden bekannte Probleme aufgeführt, die bei der Verwendung des Android Broadcast SDK von Amazon-IVS-Streaming in Echtzeit auftreten können, und es werden mögliche Problemumgehungen vorgeschlagen.
+ Wenn ein Android-Gerät in den Ruhezustand wechselt und aufwacht, befindet sich die Vorschau möglicherweise in einem eingefrorenen Zustand.

  **Problemumgehung:** Erstellen und nutzen Sie eine neue `Stage`.
+ Wenn ein Teilnehmer mit einem Token beitritt, das von einem anderen Teilnehmer verwendet wird, wird die erste Verbindung ohne einen bestimmten Fehler getrennt.

  **Problemumgehung:** Keine. 
+ Es gibt ein seltenes Problem, bei dem der Publisher etwas veröffentlicht, der Veröffentlichungsstatus, den Subscriber erhalten, jedoch `inactive` lautet.

  **Problemumgehung:** Versuchen Sie, die Sitzung zu verlassen und ihr wieder beizutreten. Wenn das Problem weiterhin besteht, erstellen Sie ein neues Token für den Publisher.
+ Während einer Bühnensitzung kann zeitweise ein seltenes Problem mit Tonverzerrungen auftreten, in der Regel bei längeren Anrufen.

  **Problemumgehung:** Der Teilnehmer mit dem verzerrtem Ton kann die Sitzung entweder verlassen und erneut beitreten oder die Veröffentlichung des Audios aufheben und dann erneut veröffentlichen.
+ Externe Mikrofone werden bei der Veröffentlichung auf einer Bühne nicht unterstützt.

  **Problemumgehung:** Verwenden Sie kein über USB angeschlossenes externes Mikrofon, um etwas auf einer Bühne zu veröffentlichen.
+ Das Veröffentlichen auf einer Bühne mit der Bildschirmfreigabe über `createSystemCaptureSources` wird nicht unterstützt.

  **Problemumgehung:** Verwalten Sie die Systemerfassung manuell, indem Sie benutzerdefinierte Bild- und Audioeingangsquellen verwenden.
+ Wenn eine `ImagePreviewView` in einem übergeordneten Element entfernt wird (`removeView()` wird z. B. im übergeordneten Element aufgerufen), wird die `ImagePreviewView` sofort freigegeben. Die `ImagePreviewView` zeigt keine Frames an, wenn sie einer anderen übergeordneten Ansicht hinzugefügt wird.

  **Problemumgehung:** Fordern Sie mit `getPreview` eine andere Vorschau an.
+ Beim Beitritt zu einer Bühne mit einem Samsung Galaxy S22/\$1 mit Android 12 tritt möglicherweise ein 1401-Fehler auf. Das lokale Gerät kann der Bühne nicht beitreten oder tritt ihr bei, hat aber keinen Ton.

  **Problemumgehung:** Führen Sie ein Upgrade auf Android 13 durch.
+ Beim Beitritt zu einer Bühne mit einem Nokia X20 unter Android 13 lässt sich die Kamera möglicherweise nicht öffnen und es wird eine Ausnahme ausgelöst.

  **Problemumgehung:** Keine.
+ Geräte mit dem MediaTek-Helio-Chipsatz können Videos von Remote-Teilnehmern nicht richtig wiedergeben.

  **Problemumgehung:** Keine.
+ Auf einigen Geräten wählt das Betriebssystem möglicherweise ein anderes Mikrofon als das, das im SDK ausgewählt wurde. Das liegt daran, dass das Amazon IVS Broadcast SDK nicht steuern kann, wie die Audioroute `VOICE_COMMUNICATION` definiert wird, da sie je nach Gerätehersteller unterschiedlich ist.

  **Problemumgehung:** Keine.
+ Einige Android-Videoencoder können nicht mit einer Videogröße von weniger als 176 × 176 konfiguriert werden. Die Konfiguration einer kleineren Größe verursacht einen Fehler und verhindert das Streaming.

  **Problemumgehung:** Konfigurieren Sie die Videogröße nicht auf weniger als 176 × 176.

# Fehlerbehandlung im IVS Android Broadcast SDK \$1 Streaming in Echtzeit
<a name="broadcast-android-error-handling"></a>

Dieser Abschnitt gibt einen Überblick über die Fehlerbedingungen, wie das Android Broadcast SDK von IVS-Streaming in Echtzeit sie an die Anwendung meldet und wie eine Anwendung reagieren sollte, wenn diese Fehler auftreten.

## Schwerwiegende und nicht schwerwiegende Fehler
<a name="broadcast-android-fatal-vs-nonfatal-errors"></a>

Das Fehlerobjekt hat das boolesche Feld „ist fatal“ von `BroadcastException`.

Im Allgemeinen hängen schwerwiegende Fehler mit der Verbindung zum Stages-Server zusammen (entweder kann eine Verbindung nicht hergestellt werden oder sie ist verloren gegangen und kann nicht wiederhergestellt werden). Die Anwendung sollte die Stage neu erstellen und erneut beitreten, ggf. mit einem neuen Token oder wenn die Konnektivität des Geräts wiederhergestellt ist.

Fehler, die nicht schwerwiegend sind, hängen in der Regel mit dem Status „Veröffentlichen/Abonnieren“ zusammen und werden vom SDK behandelt, das den Vorgang zum Veröffentlichen/Abonnieren erneut versucht.

Sie können diese Eigenschaft überprüfen:

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

## Beitrittsfehler
<a name="broadcast-android-stage-join-errors"></a>

### Fehlerhaft formatiertes Token
<a name="broadcast-android-stage-join-errors-malformed-token"></a>

Dies passiert, wenn das Phasen-Token falsch formatiert ist.

Das SDK löst bei einem Aufruf von eine Java-Ausnahme mit dem Fehlercode = 1000 und fatal = true aus. `stage.join`

**Aktion**: Erstellen Sie ein gültiges Token und versuchen Sie erneut, Mitglied zu werden.

### Abgelaufenes Token
<a name="broadcast-android-stage-join-errors-expired-token"></a>

Dies passiert, wenn das Phasen-Token abgelaufen ist.

Das SDK löst bei einem Aufruf von eine Java-Ausnahme mit dem Fehlercode = 1001 und fatal = true aus. `stage.join`

**Aktion**: Erstellen Sie ein neues Token und versuchen Sie erneut, Mitglied zu werden.

### Ungültiges oder widerrufenes Token
<a name="broadcast-android-stage-join-errors-invalid-token"></a>

Dies passiert, wenn das Stage-Token nicht falsch formatiert ist, sondern vom Stages-Server zurückgewiesen wird. Dies wird asynchron über den von der Anwendung bereitgestellten Phasen-Renderer gemeldet.

Das SDK ruft `onConnectionStateChanged` mit einer Ausnahme auf, mit dem Fehlercode = 1026 und fatal = true.

**Aktion**: Erstellen Sie ein gültiges Token und versuchen Sie erneut beizutreten.

### Netzwerkfehler beim ersten Beitritt
<a name="broadcast-android-stage-join-errors-network-initial-join"></a>

Dies passiert, wenn das SDK den Stages-Server nicht kontaktieren kann, um eine Verbindung herzustellen. Dies wird asynchron über den von der Anwendung bereitgestellten Phasen-Renderer gemeldet.

Das SDK ruft `onConnectionStateChanged` mit einer Ausnahme auf, mit dem Fehlercode = 1300 und fatal = true.

**Handlung**: Warten Sie, bis die Konnektivität des Geräts wiederhergestellt ist, und versuchen Sie erneut, eine Verbindung herzustellen.

### Netzwerkfehler, wenn bereits eine Verbindung hergestellt wurde
<a name="broadcast-android-stage-join-errors-network-already-joined"></a>

Wenn die Netzwerkverbindung des Geräts ausfällt, verliert das SDK möglicherweise die Verbindung zu den Stage-Servern. Dies wird asynchron über den von der Anwendung bereitgestellten Phasen-Renderer gemeldet.

Das SDK ruft `onConnectionStateChanged` mit einer Ausnahme auf, mit dem Fehlercode = 1300 und fatal = true.

**Handlung**: Warten Sie, bis die Konnektivität des Geräts wiederhergestellt ist, und versuchen Sie erneut, eine Verbindung herzustellen.

## Fehler beim Veröffentlichen/Abonnieren
<a name="broadcast-android-publish-subscribe-errors"></a>

### Anfänglich
<a name="broadcast-android-publish-subscribe-errors-initial"></a>

Es gibt mehrere Arten von Fehlern:
+ MultihostSessionOfferCreationFailPublish (1.020)
+ MultihostSessionOfferCreationFailSubscribe (1.021)
+ MultihostSessionNoIceCandidates (1.022)
+ MultihostSessionStageAtCapacity (1.024)
+ SignallingSessionCannotRead (1.201)
+ SignallingSessionCannotSend (1.202)
+ SignallingSessionBadResponse (1.203)

Diese werden asynchron über den von der Anwendung bereitgestellten Stage-Renderer gemeldet.

Das SDK wiederholt den Vorgang für eine begrenzte Anzahl von Malen. Bei Wiederholungen ist der Status „Veröffentlichen/Abonnieren“ `ATTEMPTING_PUBLISH` / `ATTEMPTING_SUBSCRIBE`. Wenn die Wiederholungsversuche erfolgreich sind, ändert sich der Status auf `PUBLISHED` / `SUBSCRIBED`.

Das SDK ruft `onError` mit dem entsprechenden Fehlercode und fatal = false auf.

**Aktion**: Es ist keine Aktion erforderlich, da das SDK es automatisch wiederholt. Optional kann die Anwendung die Strategie aktualisieren, um weitere Wiederholungsversuche zu erzwingen.

### Bereits eingerichtet, dann gescheitert
<a name="broadcast-android-publish-subscribe-errors-established"></a>

Eine Veröffentlichung oder ein Abonnement kann nach der Einrichtung fehlschlagen, was höchstwahrscheinlich auf einen Netzwerkfehler zurückzuführen ist. Fehlercode 1400, Meldung: „Die Peer-Verbindung wurde aufgrund eines unbekannten Netzwerkfehlers unterbrochen.“

Dies wird asynchron über den von der Anwendung bereitgestellten Stage-Renderer gemeldet.

Das SDK versucht erneut, den Vorgang zu veröffentlichen/abonnieren. Bei Wiederholungen ist der Status „Veröffentlichen/Abonnieren“ `ATTEMPTING_PUBLISH` / `ATTEMPTING_SUBSCRIBE`. Wenn die Wiederholungsversuche erfolgreich sind, ändert sich der Status auf `PUBLISHED` / `SUBSCRIBED`.

Das SDK ruft `onError` mit dem Fehlercode = 1400 und fatal = false auf.

**Aktion**: Es ist keine Aktion erforderlich, da das SDK es automatisch wiederholt. Optional kann die Anwendung die Strategie aktualisieren, um weitere Wiederholungsversuche zu erzwingen. Im Falle eines vollständigen Verbindungsverlusts ist es wahrscheinlich, dass auch die Verbindung zu Stages fehlschlägt.

# IVS-Broadcast-SDK: iOS-Handbuch \$1 Echtzeit-Streaming
<a name="broadcast-ios"></a>

Das iOS-Broadcast-SDK für IVS Echtzeit-Streaming ermöglicht es den Teilnehmern, Videos auf iOS zu senden und zu empfangen.

Das Modul `AmazonIVSBroadcast` implementiert die in diesem Dokument beschriebene Schnittstelle. Folgende Operationen werden unterstützt:
+ Einer Stage beitreten 
+ Medien für andere Teilnehmer auf der Stage veröffentlichen
+ Medien anderer Teilnehmer auf der Stage abonnieren
+ Auf der Stage veröffentlichte Videos und Audios verwalten und überwachen
+ WebRTC-Statistiken für jede Peer-Verbindung beziehen
+ Alle Vorgänge aus dem IVS-Streaming-SDK für iOS-Übertragungen mit niedriger Latenz

**Aktuelle Version des Broadcast-SDK für iOS:** 1.40.0 ([Versionshinweise](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#mar12-26-broadcast-ios-rt)) 

Informationen zu den wichtigsten Methoden, die im Amazon-IVS-iOs-Broadcast-SDK verfügbar sind, finden Sie in der **Referenzdokumentation** unter [https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/ios/](https://aws.github.io/amazon-ivs-broadcast-docs/1.40.0/ios/).

**Beispiel-Code:** Siehe das iOS-Beispiel-Repository auf GitHub: [https://github.com/aws-samples/amazon-ivs-real-time-streaming-ios-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-ios-samples).

**Plattformanforderungen:** iOS 14 und höher

# Erste Schritte mit dem IVS iOS Broadcast SDK \$1 Streaming in Echtzeit
<a name="broadcast-ios-getting-started"></a>

Dieses Dokument führt Sie durch die Schritte zum Einstieg in das iOS Broadcast SDK von IVS-Streaming in Echtzeit.

## Bibliothek installieren
<a name="broadcast-ios-install"></a>

Wir empfehlen Ihnen, das SDK über Swift Package Manager zu integrieren. (Alternativ können Sie die Framework manuell zu Ihrem Projekt hinzufügen.)

### Empfohlen: Integrieren Sie das Broadcast-SDK (Swift Package Manager)
<a name="broadcast-ios-install-swift"></a>

1. Laden Sie die Datei Package.swift von [https://broadcast.live-video.net/1.40.0/Package.swift](https://broadcast.live-video.net/1.40.0/Package.swift) herunter.

1. Erstellen Sie in Ihrem Projekt ein neues Verzeichnis mit dem Namen AmazonIVSBroadcast und fügen Sie es der Versionskontrolle hinzu.

1. Platzieren Sie die heruntergeladene Datei Package.swift im neuen Verzeichnis.

1. Gehen Sie in Xcode zu **Datei > Paketabhängigkeiten hinzufügen** und wählen Sie **Lokal hinzufügen …**

1. Navigieren Sie zu dem von Ihnen erstellten AmazonIVSBroadcast-Verzeichnis, wählen Sie es aus und wählen Sie **Paket hinzufügen** aus.

1. Wenn Sie aufgefordert werden, **Paketprodukte für AmazonIVSBroadcast** auszuwählen, wählen Sie **AmazonIVSBroadcastStages** als Ihr **Paketprodukt** aus, indem Sie Ihr Anwendungsziel im Abschnitt **Zum Ziel hinzufügen** festlegen.

1. Wählen Sie **Paket hinzufügen** aus.

**Wichtig:** Das IVS-Echtzeit-Streaming-Broadcast-SDK beinhaltet alle Feature des IVS-Streaming-Broadcast-SDK mit niedriger Latenz. Es ist nicht möglich, beide SDKs in dasselbe Projekt zu integrieren.

### Manuelles Installieren der Framework
<a name="broadcast-ios-install-manual"></a>

1. Laden Sie die neueste Version von [https://broadcast.live-video.net/1.40.0/AmazonIVSBroadcast-Stages.xcframework.zip](https://broadcast.live-video.net/1.40.0/AmazonIVSBroadcast-Stages.xcframework.zip) herunter.

1. Extrahieren Sie den Inhalt des Archivs. `AmazonIVSBroadcast.xcframework` enthält das SDK für Gerät und Simulator.

1. Betten Sie `AmazonIVSBroadcast.xcframework` ein, indem Sie es in den Abschnitt **Frameworks, Bibliotheken und eingebettete Inhalte** auf der Registerkarte **Allgemein** für Ihr Anwendungsziel ziehen.  
![\[Der Abschnitt Rahmenbedingungen, Bibliotheken und eingebettete Inhalte auf der Registerkarte Allgemein für Ihr Anwendungsziel.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/iOS_Broadcast_SDK_Guide_xcframework.png)

## Berechtigungen anfordern
<a name="broadcast-ios-permissions"></a>

Ihre App muss die Berechtigung für den Zugriff auf die Kamera und das Mikrofon des Benutzers anfordern. (Dies ist nicht spezifisch für Amazon IVS; es ist für jede Anwendung erforderlich, die Zugriff auf Kameras und Mikrofone benötigt.)

Hier prüfen wir, ob der Benutzer bereits Berechtigungen erteilt hat und wenn nicht, fragen wir nach ihnen:

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

Sie müssen dies sowohl für `.video`- als auch für `.audio`-Medientypen tun, wenn Sie auf Kameras bzw. Mikrofone zugreifen möchten.

Sie müssen außerdem Einträge für `NSCameraUsageDescription` und `NSMicrophoneUsageDescription` zu Ihrem `Info.plist` hinzufügen. Andernfalls stürzt Ihre App ab, wenn Sie versuchen, Berechtigungen anzufordern.

## Deaktivieren des Idle-Timers der Anwendung
<a name="broadcast-ios-disable-idle-timer"></a>

Dies ist zwar optional, wird aber empfohlen. Es verhindert, dass Ihr Gerät in den Ruhezustand versetzt, während Sie das Broadcast-SDK verwenden, was die Übertragung unterbrechen würde.

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

# Veröffentlichen und Abonnieren mit dem IVS iOS Broadcast SDK \$1 Streaming in Echtzeit
<a name="ios-publish-subscribe"></a>

Dieses Dokument führt Sie durch die Schritte zum Veröffentlichen und Abonnieren einer Stage mit dem iOS Broadcast SDK von IVS-Streaming in Echtzeit.

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

Drei Kernkonzepte liegen der Echtzeit-Funktionalität zugrunde: [Stage](#ios-publish-subscribe-concepts-stage), [Strategie](#ios-publish-subscribe-concepts-strategy) und [Renderer](#ios-publish-subscribe-concepts-renderer). Das Designziel besteht in der Minimierung der Menge an clientseitiger Logik, die für die Entwicklung eines funktionierenden Produkts erforderlich ist.

### Stage
<a name="ios-publish-subscribe-concepts-stage"></a>

Die Klasse `IVSStage` ist der Hauptinteraktionspunkt zwischen der Hostanwendung und dem SDK. Die Klasse stellt die Stage selbst dar und dient dazu, der Stage beizutreten und sie zu verlassen. Für das Erstellen einer Stage oder das Beitreten ist eine gültige, noch nicht abgelaufene Token-Zeichenfolge aus der Steuerebene erforderlich (dargestellt als `token`). Einer Stage beizutreten und sie zu verlassen, ist ganz einfach.

```
let stage = try IVSStage(token: token, strategy: self)

try stage.join()

stage.leave()
```

In der Klasse `IVSStage` erfolgt auch das Anhängen des `IVSStageRenderer` und `IVSErrorDelegate`:

```
let stage = try IVSStage(token: token, strategy: self)
stage.errorDelegate = self
stage.addRenderer(self) // multiple renderers can be added
```

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

Über das Protokoll `IVSStageStrategy` kann die Hostanwendung dem SDK den gewünschten Status der Stage mitteilen. Drei Funktionen müssen implementiert werden: `shouldSubscribeToParticipant`, `shouldPublishParticipant` und `streamsToPublishForParticipant`. Alle werden im Folgenden behandelt.

#### Abonnieren von Teilnehmern
<a name="ios-publish-subscribe-concepts-strategy-participants"></a>

```
func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType
```

Wenn ein Remote-Teilnehmer einer Stage beitritt, fragt das SDK die Hostanwendung nach dessen gewünschtem Abonnementstatus. Die Optionen lauten `.none`, `.audioOnly` und `.audioVideo`. Wenn ein Wert für diese Funktion zurückgegeben wird, muss sich die Hostanwendung nicht um den Veröffentlichungs-, den aktuellen Abonnement- oder den Verbindungsstatus des Stage kümmern. Bei Rückgabe von `.audioVideo` wartet das SDK mit dem Abonnieren, bis der Remote-Teilnehmer etwas veröffentlicht. Außerdem aktualisiert das SDK die Hostanwendung während des gesamten Prozesses über den Renderer.

Hier folgt ein Beispiel für eine Implementierung:

```
func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType {
    return .audioVideo
}
```

Hierbei handelt es sich um die vollständige Implementierung dieser Funktion für eine Hostanwendung, bei der sich alle Teilnehmer stets gegenseitig sehen sollen; z. B. eine Video-Chat-Anwendung.

Weitergehende Implementierungen sind ebenfalls möglich. Nutzen Sie die Eigenschaft `attributes` für `IVSParticipantInfo`, um Teilnehmer anhand der vom Server bereitgestellten Attribute selektiv zu abonnieren:

```
func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType {
    switch participant.attributes["role"] {
    case "moderator": return .none
    case "guest": return .audioVideo
    default: return .none
    }
}
```

Hiermit kann eine Stage erstellt werden, auf der Moderatoren alle Gäste überwachen können, ohne selbst gesehen oder gehört zu werden. Die Hostanwendung könnte eine zusätzliche Geschäftslogik nutzen, damit Moderatoren sich gegenseitig sehen können, für Gäste aber unsichtbar bleiben.

#### Konfiguration für das Abonnieren von Teilnehmern
<a name="ios-publish-subscribe-concepts-strategy-participants-config"></a>

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

Wenn ein Remote-Teilnehmer abonniert wird (siehe [Teilnehmer abonnieren](#ios-publish-subscribe-concepts-strategy-participants)), fragt das SDK die Host-Anwendung nach einer benutzerdefinierten Abonnementkonfiguration für diesen Teilnehmer ab. Diese Konfiguration ist optional und ermöglicht es der Hostanwendung, bestimmte Aspekte des Subscriber-Verhaltens zu steuern. Informationen darüber, was konfiguriert werden kann, finden Sie unter [SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration) in der SDK-Referenzdokumentation.

Hier folgt ein Beispiel für eine Implementierung:

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

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

    return config
}
```

Diese Implementierung aktualisiert die Mindestverzögerung für den Jitter-Buffer für alle abonnierten Teilnehmer auf die Voreinstellung `MEDIUM`.

Wie bei `shouldSubscribeToParticipant` sind auch hier weitergehende Implementierungen möglich. Die `ParticipantInfo`-Angaben können verwendet werden, um die Abonnementkonfiguration für bestimmte Teilnehmer selektiv zu aktualisieren.

Wir empfehlen die Verwendung der Standardverhaltensweisen. Geben Sie die benutzerdefinierte Konfiguration nur an, wenn Sie ein bestimmtes Verhalten ändern möchten.

#### Veröffentlichen
<a name="ios-publish-subscribe-concepts-strategy-publishing"></a>

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

Sobald die Verbindung zur Stage hergestellt ist, überprüft das SDK per Anfrage an die Hostanwendung, ob ein bestimmter Teilnehmer etwas veröffentlichen soll. Dies wird nur bei lokalen Teilnehmern aufgerufen, die auf Grundlage des bereitgestellten Tokens zur Veröffentlichung berechtigt sind.

Hier folgt ein Beispiel für eine Implementierung:

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

Sie ist für eine normale Video-Chat-Anwendung gedacht, bei der Benutzer immer etwas veröffentlichen möchten. Sie können die Audio- und Videowiedergabe stummschalten und die Stummschaltung aufheben, um umgehend ausgeblendet oder gesehen/gehört zu werden. (Sie können auch „Veröffentlichen/Veröffentlichung aufheben“ verwenden, was aber viel langsamer ist. „Stummschalten/Stummschalten aufheben“ ist für Anwendungsfälle vorzuziehen, in denen eine häufige Änderung der Sichtbarkeit wünschenswert ist.)

#### Auswählen von Streams zur Veröffentlichung
<a name="ios-publish-subscribe-concepts-strategy-streams"></a>

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

Beim Veröffentlichen wird hiermit bestimmt, welche Audio- und Videostreams veröffentlicht werden sollen. Dieser Punkt wird später unter [Veröffentlichen eines Medienstreams](#ios-publish-subscribe-publish-stream) ausführlicher behandelt.

#### Aktualisieren der Strategie
<a name="ios-publish-subscribe-concepts-strategy-updates"></a>

Die Strategie soll dynamisch sein: Die von einer der oben genannten Funktionen zurückgegebenen Werte lassen sich jederzeit ändern. Wenn die Hostanwendung beispielsweise erst veröffentlichen soll, wenn der Endbenutzer auf eine Schaltfläche tippt, können Sie eine Variable aus `shouldPublishParticipant` zurückgeben (zum Beispiel `hasUserTappedPublishButton`). Wenn sich diese Variable aufgrund einer Interaktion des Endbenutzers ändert, signalisieren Sie dem SDK per Aufruf von `stage.refreshStrategy()`, dass es die Strategie nach den neuesten Werten abfragen und nur Dinge anwenden soll, die sich geändert haben. Wenn das SDK feststellt, dass sich der Wert `shouldPublishParticipant` geändert hat, startet es den Veröffentlichungsprozess. Wenn alle Funktionen bei einer SDK-Abfrage den gleichen Wert zurückgeben wie zuvor, werden mit dem Aufruf von `refreshStrategy` keine Änderungen an der Stage durchgeführt.

Ändert sich der Rückgabewert von `shouldSubscribeToParticipant` von `.audioVideo` in `.audioOnly`, wird der Videostream für alle Teilnehmer mit geänderten Rückgabewerten entfernt, sofern zuvor ein Videostream vorhanden war.

Im Allgemeinen nutzt die Stage die Strategie, um den Unterschied zwischen der vorherigen und der aktuellen Strategie am effizientesten anzuwenden. Dabei muss sich die Hostanwendung nicht um die ganzen Status kümmern, die für eine ordnungsgemäße Verwaltung erforderlich sind. Stellen Sie sich den Aufruf von `stage.refreshStrategy()` daher als einen ressourcenschonenden Vorgang vor, da nur bei einer Änderung der Strategie etwas unternommen wird.

### Renderer
<a name="ios-publish-subscribe-concepts-renderer"></a>

Das Protokoll `IVSStageRenderer` teilt der Hostanwendung den Status der Stage mit. Aktualisierungen in der Benutzeroberfläche der Hostanwendung können in der Regel vollständig über die vom Renderer bereitgestellten Ereignisse gesteuert werden. Der Renderer stellt die folgenden Funktionen bereit:

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

Es wird nicht erwartet, dass sich die vom Renderer bereitgestellten Informationen auf die Rückgabewerte der Strategie auswirken. Es wird beispielsweise nicht erwartet, dass sich der Rückgabewert von `shouldSubscribeToParticipant` beim Aufruf von `participant:didChangePublishState` ändert. Wenn die Hostanwendung einen bestimmten Teilnehmer abonnieren möchte, muss sie unabhängig von dessen Veröffentlichungsstatus den gewünschten Abonnementtyp zurückgeben. Das SDK muss dafür sorgen, dass entsprechend dem Status der Stage und dem gewünschten Status der Strategie zum richtigen Zeitpunkt gehandelt wird.

Hinweis: Nur veröffentlichende Teilnehmer lösen `participantDidJoin` aus. Wenn Teilnehmer die Veröffentlichung beenden oder die Stagesitzung verlassen, wird `participantDidLeave` ausgelöst.

## Veröffentlichen eines Medienstreams
<a name="ios-publish-subscribe-publish-stream"></a>

Lokale Geräte wie eingebaute Mikrofone und Kameras werden über `IVSDeviceDiscovery` erkannt. Hier folgt ein Beispiel für die Auswahl der nach vorne gerichteten Kamera und des Standardmikrofons und deren anschließende Rückgabe als `IVSLocalStageStreams` zur Veröffentlichung durch das 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]
}
```

## Anzeigen und Entfernen von Teilnehmern
<a name="ios-publish-subscribe-participants"></a>

Nach Abschluss von Abonnements erhalten Sie über die Funktion `didAddStreams` des Renderers eine Reihe von `IVSStageStream`-Objekten. Um Statistiken des Audiolevels zu diesem Teilnehmer in der Vorschau anzuzeigen oder abzurufen, können Sie über den Stream auf das zugrunde liegende `IVSDevice`-Objekt zugreifen:

```
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 */
    })
}
```

Wenn ein Teilnehmer die Veröffentlichung beendet oder dessen Abonnement beendet wird, wird die Funktion `didRemoveStreams` mit den Streams aufgerufen, die entfernt wurden. Hostanwendungen sollten dies als Signal nutzen, um den Videostream des Teilnehmers aus der Ansichtshierarchie zu entfernen.

`didRemoveStreams` wird für alle Szenarien aufgerufen, in denen ein Stream entfernt werden könnte, darunter:
+ Der Remote-Teilnehmer beendet die Veröffentlichung.
+ Ein lokales Gerät beendet das Abonnement oder ändert das Abonnement von `.audioVideo` in `.audioOnly`.
+ Der Remote-Teilnehmer verlässt die Stage.
+ Der lokale Teilnehmer verlässt die Stage.

Da `didRemoveStreams` bei allen Szenarien aufgerufen wird, ist keine benutzerdefinierte Geschäftslogik erforderlich, um Teilnehmer beim remoten oder lokalen Verlassen aus der Benutzeroberfläche zu entfernen.

## Stummschalten von Medienstreams und Aufheben der Stummschaltung
<a name="ios-publish-subscribe-mute-streams"></a>

`IVSLocalStageStream`-Objekte verfügen über eine `setMuted`-Funktion, die das Stummschalten des Streams steuert. Diese Funktion kann für den Stream aufgerufen werden, bevor oder nachdem er von der Strategiefunktion `streamsToPublishForParticipant` zurückgegeben wird.

**Wichtig**: Wenn nach einem Aufruf von `refreshStrategy` eine neue `IVSLocalStageStream`-Objekt-Instance von `streamsToPublishForParticipant` zurückgegeben wird, wird der Stummschaltungsstatus des neuen Streamobjekts auf die Stage angewendet. Seien Sie vorsichtig beim Erstellen neuer `IVSLocalStageStream`-Instances, um sicherzustellen, dass der erwartete Stummschaltungsstatus beibehalten wird.

## Überwachen des Medien-Stummschaltungsstatus von Remote-Teilnehmern
<a name="ios-publish-subscribe-mute-state"></a>

Wenn ein Teilnehmer den Stummschaltungsstatus seines Video- oder Audiostreams ändert, wird die Funktion `didChangeMutedStreams` des Renderers mit einer Gruppe der Streams aufgerufen, die sich geändert haben. Verwenden Sie die Eigenschaft `isMuted` für `IVSStageStream`, um die Benutzeroberfläche entsprechend zu aktualisieren:

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

## Erstellen einer Stagekonfiguration
<a name="ios-publish-subscribe-stage-config"></a>

Die Werte der Videokonfiguration einer Stage passen Sie mit `IVSLocalStageStreamVideoConfiguration` an:

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

## Abrufen von WebRTC-Statistiken
<a name="ios-publish-subscribe-webrtc-stats"></a>

Um die neuesten WebRTC-Statistiken für einen veröffentlichten oder abonnierten Stream abzurufen, verwenden Sie `requestRTCStats` für `IVSStageStream`. Nach Abschluss einer Erfassung erhalten Sie Statistiken über den `IVSStageStreamDelegate`, der für `IVSStageStream` eingestellt werden kann. Um WebRTC-Statistiken kontinuierlich zu erfassen, rufen Sie diese Funktion für einen `Timer` auf.

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

## Abrufen von Teilnehmerattributen
<a name="ios-publish-subscribe-participant-attributes"></a>

Wenn Sie Attribute in der Vorgangsanfrage `CreateParticipantToken` angeben, können Sie die Attribute in den Eigenschaften von `IVSParticipantInfo` einsehen:

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

## Eingebettete Nachrichten
<a name="ios-publish-subscribe-embed-messages"></a>

Die `embedMessage` Methode auf IVSImageDevice ermöglicht es Ihnen, Metadaten-Nutzdaten während der Veröffentlichung direkt in Videoframes einzufügen. Dies ermöglicht framesynchronisiertes Messaging für Echtzeitanwendungen. Das Einbetten von Nachrichten ist nur verfügbar, wenn das SDK für die Veröffentlichung in Echtzeit verwendet wird (nicht für Veröffentlichungen mit niedriger Latenz).

Es kann nicht garantiert werden, dass eingebettete Nachrichten bei Abonnenten ankommen, da sie direkt in Videoframes eingebettet und über UDP übertragen werden, wodurch die Paketzustellung nicht garantiert wird. Der Verlust von Paketen während der Übertragung kann zum Verlust von Nachrichten führen, insbesondere bei schlechten Netzwerkbedingungen. Um dem entgegenzuwirken, beinhaltet die `embedMessage`-Methode einen `repeatCount`-Parameter, der die Nachricht über mehrere aufeinanderfolgende Frames dupliziert und so die Zuverlässigkeit der Zustellung erhöht. Diese Funktion ist nur für Videostreams verfügbar.

### Verwenden von embedMessage
<a name="ios-embed-messages-using-embedmessage"></a>

Publishing-Clients können Nachrichten-Nutzdaten mithilfe der `embedMessage`-Methode auf IVSImageDevice in ihren Videostream einbetten. Die Nutzdaten müssen größer als 0 KB und kleiner als 1 KB sein. Die Anzahl der pro Sekunde eingebetteten Nachrichten darf 10 KB pro Sekunde nicht überschreiten.

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

### Nachrichten-Nutzdaten
<a name="ios-embed-messages-repeat-payloads"></a>

Verwenden Sie `repeatCount`, um die Nachricht über mehrere Frames hinweg zu duplizieren, um die Zuverlässigkeit zu erhöhen. Dieser Wert muss zwischen 0 und 30 liegen. Empfangende Clients müssen über eine Logik verfügen, um die Nachricht zu deduplizieren.

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

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

### Lesen eingebetteter Nachrichten
<a name="ios-embed-messages-read-messages"></a>

Informationen zum Lesen eingebetteter Nachrichten aus eingehenden Streams finden Sie weiter unten unter „Zusätzliche Erweiterungsinformationen (SEI) abrufen“. 

## Abrufen von SEI-Daten (Supplemental Enhancement Information)
<a name="ios-publish-subscribe-sei-attributes"></a>

Die NAL-Einheit für Supplemental Enhancement Information (SEI) wird verwendet, um Frame-orientierte Metadaten zusammen mit dem Video zu speichern. Subscriber können SEI-Nutzlasten von einem Publisher lesen, der H.264-Video veröffentlicht, indem sie die Eigenschaften `embeddedMessages` der `IVSImageDeviceFrame`-Objekte aus dem `IVSImageDevice` des Publishers überprüfen. Erlangen Sie dazu das `IVSImageDevice` eines Publishers und beobachten Sie dann jeden Frame über einen Callback an `setOnFrameCallback`, wie im folgenden Beispiel gezeigt:

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

## Fortsetzen der Sitzung im Hintergrund
<a name="ios-publish-subscribe-background-session"></a>

Wenn die App in den Hintergrund wechselt, können Sie weiterhin auf der Stage präsent sein, während Sie Remote-Ton hören. Es ist jedoch nicht möglich, das eigene Bild und den eigenen Ton weiterhin zu senden. Sie müssen die Implementierung Ihrer `IVSStrategy` aktualisieren, um die Veröffentlichung zu beenden und `.audioOnly` zu abonnieren (oder gegebenenfalls `.none`):

```
func stage(_ stage: IVSStage, shouldPublishParticipant participant: IVSParticipantInfo) -> Bool {
    return false
}
func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType {
    return .audioOnly
}
```

Anschließend rufen Sie `stage.refreshStrategy()` auf.

## Mehrschichtige Kodierung mit Simulcast
<a name="ios-publish-subscribe-layered-encoding-simulcast"></a>

Bei der mehrschichtigen Kodierung mit Simulcast handelt es sich um ein Feature für IVS-Echtzeit-Streaming, mit dessen Hilfe Publisher mehrere Videoschichten unterschiedlicher Qualität senden können. Subscriber können diese Schichten dynamisch oder manuell konfigurieren. Das Feature wird im Dokument [Streaming-Optimierungen](real-time-streaming-optimization.md) ausführlicher beschrieben.

### Konfigurieren mehrschichtiger Kodierung (Publisher)
<a name="ios-layered-encoding-simulcast-configure-publisher"></a>

Um als Publisher die mehrschichtige Kodierung mit Simulcast zu aktivieren, fügen Sie dem `IVSLocalStageStream` bei der Instanziierung die folgende Konfiguration hinzu:

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

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

// Other Stage implementation code
```

Je nach der in der Videokonfiguration eingestellten Auflösung wird eine festgelegte Anzahl von Schichten kodiert und gesendet, wie im Abschnitt [Standardmäßige Schichten, Qualitäten und Bildraten](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) von *Streaming-Optimierungen* definiert.

Außerdem können Sie optional einzelne Ebenen innerhalb der Simulcast-Konfiguration konfigurieren:

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

Alternativ können Sie eigene benutzerdefinierte Ebenenkonfigurationen für bis zu drei Ebenen erstellen. Wenn Sie ein leeres Array oder keinen Wert angeben, werden die oben beschriebenen Standardwerte verwendet. Ebenen werden mit den folgenden erforderlichen Eigenschaftssetzern beschrieben:
+ `setSize: CGSize;`
+ `setMaxBitrate: integer;`
+ `setMinBitrate: integer;`
+ `setTargetFramerate: float;`

Ausgehend von den Voreinstellungen können Sie entweder einzelne Eigenschaften überschreiben oder eine völlig neue Konfiguration erstellen:

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

Informationen zu Höchstwerten, Grenzwerten und Fehlern, die bei der Konfiguration einzelner Ebenen ausgelöst werden können, finden Sie in der SDK-Referenzdokumentation.

### Konfigurieren mehrschichtiger Kodierung (Subscriber)
<a name="ios-layered-encoding-simulcast-configure-subscriber"></a>

Subscriber müssen nichts unternehmen, um die mehrschichtige Kodierung zu aktivieren. Wenn ein Publisher Simulcast-Schichten sendet, passt sich der Server standardmäßig dynamisch den Schichten an, um je nach Gerät und Netzwerkbedingungen des Subscribers die optimale Qualität auszuwählen.

Alternativ gibt es mehrere nachfolgend beschriebene Optionen, um explizite Schichten auszuwählen, die der Publisher sendet.

### Option 1: Einstellung für die Qualität der Anfangsschicht
<a name="ios-layered-encoding-simulcast-layer-quality-preference"></a>

Mit der Strategie `subscribeConfigurationForParticipant` können Sie auswählen, welche Anfangsschicht Sie als Subscriber erhalten möchten:

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

    config.simulcast.initialLayerPreference = .lowestQuality

    return config
}
```

Standardmäßig wird Subscribern zunächst immer die Schicht mit der niedrigsten Qualität gesendet. Nach und nach wird die Qualität gesteigert, bis die Schicht mit der höchsten Qualität erreicht ist. Das optimiert den Bandbreitenverbrauch der Endbenutzer, verkürzt die Zeit bis zum Abspielen des Videos und verringert das anfängliche Einfrieren von Videos bei Benutzern in Netzwerken mit geringerer Bandbreite.

Folgende Optionen sind für `InitialLayerPreference` verfügbar:
+ `lowestQuality` – Der Server stellt zuerst die Videoschicht mit der niedrigsten Qualität bereit. Dadurch werden der Bandbreitenverbrauch und die Zeit bis zum Abspielen von Medien optimiert. Die Qualität ist definiert als die Kombination aus Größe, Bitrate und Bildrate des Videos. Beispielsweise weisen 720p-Videos eine geringere Qualität auf als 1080p-Videos.
+ `highestQuality` – Der Server stellt zuerst die Videoschicht mit der höchsten Qualität bereit. Das optimiert die Qualität, kann aber die Zeit bis zum Abspielen von Medien verlängern. Die Qualität ist definiert als die Kombination aus Größe, Bitrate und Bildrate des Videos. Beispielsweise weisen 1080p-Videos eine höhere Qualität auf als 720p-Videos.

**Hinweis:** Damit die anfänglichen Schichteinstellungen (der Aufruf `initialLayerPreference`) wirksam werden, muss ein neues Abonnement abgeschlossen werden, da diese Updates für das aktive Abonnement nicht gelten.

### Option 2: Bevorzugte Schicht für Streams
<a name="ios-layered-encoding-simulcast-preferred-layer"></a>

Mit der Strategiemethode `preferredLayerForStream` können Sie eine Schicht auswählen, nachdem der Stream gestartet wurde. Diese Strategiemethode erhält die Teilnehmer- und Streaminformationen, sodass Sie eine Schicht für jeden Teilnehmer auswählen können. Das SDK ruft diese Methode als Reaktion auf bestimmte Ereignisse auf, z. B. wenn sich die Streamschichten ändern, sich der Teilnehmerstatus ändert oder die Hostanwendung die Strategie aktualisiert.

Der Strategiemodus gibt ein `IVSRemoteStageStreamLayer`-Objekt zurück, wobei es sich um Folgendes handeln kann:
+ ein Schichtobjekt, z. B. eines, das von `IVSRemoteStageStream.layers` zurückgegeben wird
+ null, was bedeutet, dass keine Schicht ausgewählt werden sollte und eine dynamische Anpassung bevorzugt wird

Bei der folgenden Strategie wählen die Benutzer beispielsweise immer die Videoschicht mit der niedrigsten verfügbaren Qualität aus:

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

Um die Schichtauswahl zurückzusetzen und zur dynamischen Anpassung zurückzukehren, geben Sie in der Strategie null oder undefiniert zurück. In diesem Beispiel ist `appState` eine Platzhaltervariable, die den Status der Hostanwendung darstellt.

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

### Option 3: Helferobjekte für RemoteStageStream-Schicht
<a name="ios-layered-encoding-simulcast-remotestagestream-helpers"></a>

`IVSRemoteStageStream` weist mehrere Helferobjekte auf, mit deren Hilfe Entscheidungen über die Schichtauswahl getroffen und Endbenutzern die entsprechende Auswahl angezeigt werden kann:
+ **Schichtereignisse** – Neben `IVSStageRenderer` verfügt der `IVSRemoteStageStreamDelegate` über Ereignisse, die Änderungen bei der Schicht- und Simulcast-Anpassung kommunizieren:
  + `func stream(_ stream: IVSRemoteStageStream, didChangeAdaption adaption: Bool)`
  + `func stream(_ stream: IVSRemoteStageStream, didChange layers: [IVSRemoteStageStreamLayer])`
  + `func stream(_ stream: IVSRemoteStageStream, didSelect layer: IVSRemoteStageStreamLayer?, reason: IVSRemoteStageStream.LayerSelectedReason)`
+ **Schichtmethoden** – `IVSRemoteStageStream` verfügt über mehrere Helfermethoden, mit denen Informationen über den Stream und die präsentierten Schichten abgerufen werden können. Diese Methoden sind sowohl für den in der Strategie `preferredLayerForStream` bereitgestellten Remote-Stream als auch für Remote-Streams verfügbar, die über `func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didAdd streams: [IVSStageStream])` verfügbar gemacht werden.
  + `stream.layers`
  + `stream.selectedLayer`
  + `stream.lowestQualityLayer`
  + `stream.highestQualityLayer`
  + `stream.layers(with: IVSRemoteStageStreamLayerConstraints)`

Einzelheiten finden Sie im Abschnitt zur Klasse `IVSRemoteStageStream` in der [SDK-Referenzdokumentation](https://aws.github.io/amazon-ivs-broadcast-docs/latest/ios/). Falls als Grund für `LayerSelected` `UNAVAILABLE` zurückgegeben wird, bedeutet das, dass die angeforderte Schicht nicht ausgewählt werden konnte. Stattdessen wird eine bestmögliche Auswahl getroffen. Dabei handelt es sich in der Regel um eine Schicht mit niedrigerer Qualität, um die Stabilität des Streams zu gewährleisten.

## Übertragung der Stage auf einen IVS-Kanal
<a name="ios-publish-subscribe-broadcast-stage"></a>

Zum Übertragen einer Stage erstellen Sie eine separate `IVSBroadcastSession` und folgen Sie dann den oben beschriebenen üblichen Anweisungen für die Übertragung mit dem SDK. Die Eigenschaft `device` für `IVSStageStream` ist entweder ein `IVSImageDevice` oder ein `IVSAudioDevice`, wie im obigen Snippet veranschaulicht. Diese können mit dem `IVSBroadcastSession.mixer` verbunden werden, um die gesamte Stage in einem individuell anpassbaren Layout zu übertragen.

Optional können Sie eine Stage zusammenstellen und sie auf einen IVS-Kanal mit niedriger Latenz übertragen, um ein größeres Publikum zu erreichen. Sehen Sie [Aktivierung mehrerer Hosts in einem Amazon-IVS-Stream](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/multiple-hosts.html) im Benutzerhandbuch für IVS-Streaming mit niedriger Latenz.

# So wählt iOS Kameraauflösung und Bildrate
<a name="ios-publish-subscribe-resolution-framerate"></a>

Die vom Broadcast-SDK verwaltete Kamera optimiert ihre Auflösung und Bildrate (Bilder pro Sekunde oder FPS), um die Wärmeentwicklung und den Energieverbrauch zu minimieren. In diesem Abschnitt wird erläutert, wie Auflösung und Bildrate ausgewählt werden, um Hostanwendungen bei der Optimierung für ihre Anwendungsfälle zu unterstützen.

Wenn ein `IVSLocalStageStream` mit einer `IVSCamera` erstellt wird, ist die Kamera für eine Bildrate von `IVSLocalStageStreamVideoConfiguration.targetFramerate` und eine Auflösung von `IVSLocalStageStreamVideoConfiguration.size` optimiert. Das Aufrufen von `IVSLocalStageStream.setConfiguration` aktualisiert die Kamera mit neueren Werten. 

## Kameravorschau
<a name="resolution-framerate-camera-preview"></a>

Wenn Sie eine Vorschau einer `IVSCamera` erstellen, ohne sie mit einer `IVSBroadcastSession` oder `IVSStage` zu verbinden, wird standardmäßig eine Auflösung von 1080p und eine Bildrate von 60 Bildern pro Sekunde verwendet.

## Übertragen einer Stage
<a name="resolution-framerate-broadcast-stage"></a>

Bei der Verwendung einer `IVSBroadcastSession` zu Übertragung einer `IVSStage` versucht das SDK, die Kamera mit einer Auflösung und Bildrate zu optimieren, die die Kriterien beider Sitzungen erfüllen.

Wenn die Broadcast-Konfiguration beispielsweise auf eine Bildrate von 15 FPS und eine Auflösung von 1080p eingestellt ist, während die Stage eine Bildrate von 30 FPS und eine Auflösung von 720p hat, wählt das SDK eine Kamerakonfiguration mit einer Bildrate von 30 FPS und einer Auflösung von 1080p aus. Bei der `IVSBroadcastSession` fehlt jedes zweite Bild von der Kamera und die `IVSStage` skaliert das 1080p-Bild auf 720p herunter.

Wenn eine Host-Anwendung plant, sowohl eine `IVSBroadcastSession` als auch eine `IVSStage` zusammen mit einer Kamera zu verwenden, empfehlen wir, dass die Eigenschaften `targetFramerate` und `size` der jeweiligen Konfigurationen übereinstimmen. Eine Nichtübereinstimmung kann dazu führen, dass sich die Kamera während der Videoaufnahme selbst neu konfiguriert, was zu einer kurzen Verzögerung bei der Übertragung von Videos führt.

Wenn identische Werte nicht dem Anwendungsfall der Hostanwendung entsprechen, verhindert das Erstellen der Kamera mit höherer Qualität, dass sich die Kamera selbst neu konfiguriert, wenn die Sitzung mit niedrigerer Qualität hinzugefügt wird. Wenn Sie zum Beispiel mit 1080p und 30 Bildern pro Sekunde übertragen und später einer Stage beitreten, die auf 720p und 30 FPS eingestellt ist, konfiguriert sich die Kamera nicht selbst neu und die Videowiedergabe läuft ohne Unterbrechung weiter. Dies liegt daran, dass 720p kleiner oder gleich 1080p und 30 FPS kleiner oder gleich 30 FPS sind.

## Beliebige Bildraten, Auflösungen und Seitenverhältnisse
<a name="resolution-framerate-arbitrary"></a>

Die meisten Kameras können gängige Formate wie 720p bei 30 FPS oder 1080p bei 60 FPS exakt wiedergeben. Es ist jedoch unmöglich, alle Formate exakt wiederzugeben. Das Broadcast-SDK wählt die Kamerakonfiguration auf der Grundlage der folgenden Regeln aus (in der Reihenfolge ihrer Priorität):

1. Breite und Höhe der Auflösung sind größer oder gleich der gewünschten Auflösung, aber innerhalb dieser Beschränkung sind Breite und Höhe so klein wie möglich.

1. Die Bildrate ist größer oder gleich der gewünschten Bildrate, aber innerhalb dieser Beschränkung ist die Bildrate so niedrig wie möglich.

1. Das Seitenverhältnis entspricht dem gewünschten Seitenverhältnis.

1. Wenn es mehrere passende Formate gibt, wird das Format mit dem größten Sichtfeld verwendet.

Nachfolgend finden Sie zwei Beispiele:
+ Die Host-Anwendung versucht, in 4K mit 120 FPS zu übertragen. Die ausgewählte Kamera unterstützt nur 4K bei 60 FPS oder 1080p bei 120 FPS. Das gewählte Format ist 4K bei 60 FPS, da die Auflösungsregel eine höhere Priorität hat als die Bildratenregel.
+ Eine unregelmäßige Auflösung wird angefordert, 1910x1070. Die Kamera wird 1920x1080 verwenden. *Vorsicht: Wenn Sie eine Auflösung wie 1921x1080 wählen, skaliert die Kamera auf die nächste verfügbare Auflösung (z. B. 2592x1944) hoch, was sich nachteilig auf die CPU- und Speicherbandbreite auswirkt*.

## Was ist mit Android?
<a name="resolution-framerate-android"></a>

Android passt seine Auflösung oder Bildrate nicht wie iOS im laufenden Betrieb an, sodass dies keine Auswirkungen auf das Android-Broadcast-SDK hat.

# Bekannte Probleme und Behelfslösungen im IVS iOS Broadcast SDK \$1 Streaming in Echtzeit
<a name="broadcast-ios-known-issues"></a>

In diesem Dokument werden bekannte Probleme aufgeführt, die bei der Verwendung des iOS Broadcast SDK von Amazon-IVS-Streaming in Echtzeit auftreten können, und es werden mögliche Problemumgehungen vorgeschlagen.
+ Das Ändern von Bluetooth-Audiorouten kann unvorhersehbar sein. Wenn Sie ein neues Gerät in der Mitte der Sitzung verbinden, kann iOS die Eingaberoute automatisch ändern oder nicht. Es ist auch nicht möglich, zwischen mehreren Bluetooth-Headsets zu wählen, die gleichzeitig verbunden sind. Dies geschieht sowohl bei normalen Broadcast- als auch bei Stagesitzungen.

  **Problemumgehung:** Wenn Sie ein Bluetooth-Headset verwenden möchten, verbinden Sie es, bevor Sie den Broadcast oder die Stage starten und lassen Sie es während der gesamten Sitzung verbunden.
+ Teilnehmer, die ein iPhone 14, iPhone 14 Plus, iPhone 14 Pro oder iPhone 14 Pro Max nutzen, können bei anderen Teilnehmern ein Echo verursachen.

  **Problemumgehung:** Teilnehmer, die die betroffenen Geräte nutzen, können Kopfhörer verwenden, um das Echo bei anderen Teilnehmern zu vermeiden.
+ Wenn ein Teilnehmer mit einem Token beitritt, das von einem anderen Teilnehmer verwendet wird, wird die erste Verbindung ohne einen bestimmten Fehler getrennt.

  **Problemumgehung:** Keine.
+ Es gibt ein seltenes Problem, bei dem der Publisher etwas veröffentlicht, der Veröffentlichungsstatus, den Subscriber erhalten, jedoch `inactive` lautet.

  **Problemumgehung:** Versuchen Sie, die Sitzung zu verlassen und ihr wieder beizutreten. Wenn das Problem weiterhin besteht, erstellen Sie ein neues Token für den Publisher.
+ Wenn ein Teilnehmer etwas veröffentlicht oder abonniert, kann selbst bei einem stabilen Netzwerk eine Fehlermeldung mit dem Code 1400 angezeigt werden. Sie weist darauf hin, dass die Verbindung aufgrund eines Netzwerkproblems unterbrochen wurde.

  **Problemumgehung:** Versuchen Sie, erneut zu veröffentlichen bzw. erneut zu abonnieren.
+ Während einer Stagesitzung kann zeitweise ein seltenes Problem mit Tonverzerrungen auftreten, in der Regel bei längeren Anrufen.

  **Problemumgehung:** Der Teilnehmer mit dem verzerrtem Ton kann die Sitzung entweder verlassen und erneut beitreten oder die Veröffentlichung des Audios aufheben und dann erneut veröffentlichen.

# Fehlerbehandlung im IVS iOS Broadcast SDK \$1 Streaming in Echtzeit
<a name="broadcast-ios-error-handling"></a>

Dieser Abschnitt gibt einen Überblick über die Fehlerbedingungen, wie das iOS Broadcast SDK von IVS-Streaming in Echtzeit sie an die Anwendung meldet und wie eine Anwendung reagieren sollte, wenn diese Fehler auftreten.

## Schwerwiegende und nicht schwerwiegende Fehler
<a name="broadcast-ios-fatal-vs-nonfatal-errors"></a>

Das Fehlerobjekt hat den booleschen Wert „ist fatal“. Dies ist ein Wörterbucheintrag unter `IVSBroadcastErrorIsFatalKey`, der einen booleschen Wert enthält.

Im Allgemeinen hängen schwerwiegende Fehler mit der Verbindung zum Stages-Server zusammen (entweder kann eine Verbindung nicht hergestellt werden oder sie ist verloren gegangen und kann nicht wiederhergestellt werden). Die Anwendung sollte die Stage neu erstellen und erneut beitreten, ggf. mit einem neuen Token oder wenn die Konnektivität des Geräts wiederhergestellt ist.

Fehler, die nicht schwerwiegend sind, hängen in der Regel mit dem Status „Veröffentlichen/Abonnieren“ zusammen und werden vom SDK behandelt, das den Vorgang zum Veröffentlichen/Abonnieren erneut versucht.

Sie können diese Eigenschaft überprüfen:

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

## Beitrittsfehler
<a name="broadcast-ios-stage-join-errors"></a>

### Fehlerhaft formatiertes Token
<a name="broadcast-ios-stage-join-errors-malformed-token"></a>

Dies passiert, wenn das Stage-Token falsch formatiert ist.

Das SDK löst eine Swift-Ausnahme mit dem Fehlercode = 1000 und IVSBroadcastErrorIsFatalKey = YES aus.

**Aktion**: Erstellen Sie ein gültiges Token und versuchen Sie erneut beizutreten.

### Abgelaufenes Token
<a name="broadcast-ios-stage-join-errors-expired-token"></a>

Dies passiert, wenn das Stage-Token abgelaufen ist.

Das SDK löst eine Swift-Ausnahme mit dem Fehlercode = 1001 und IVSBroadcastErrorIsFatalKey = YES aus.

**Aktion**: Erstellen Sie ein neues Token und versuchen Sie erneut beizutreten.

### Ungültiges oder widerrufenes Token
<a name="broadcast-ios-stage-join-errors-invalid-token"></a>

Dies passiert, wenn das Stage-Token nicht falsch formatiert ist, sondern vom Stages-Server zurückgewiesen wird. Dies wird asynchron über den von der Anwendung bereitgestellten Stage-Renderer gemeldet.

Das SDK ruft `stage(didChange connectionState, withError error)` mit dem Fehlercode = 1026 und IVSBroadcastErrorIsFatalKey = YES auf.

**Aktion**: Erstellen Sie ein gültiges Token und versuchen Sie erneut beizutreten.

### Netzwerkfehler beim ersten Beitritt
<a name="broadcast-ios-stage-join-errors-network-initial-join"></a>

Dies passiert, wenn das SDK den Stages-Server nicht kontaktieren kann, um eine Verbindung herzustellen. Dies wird asynchron über den von der Anwendung bereitgestellten Stage-Renderer gemeldet.

Das SDK ruft `stage(didChange connectionState, withError error)` mit dem Fehlercode = 1300 und IVSBroadcastErrorIsFatalKey = YES auf.

**Handlung**: Warten Sie, bis die Konnektivität des Geräts wiederhergestellt ist, und versuchen Sie erneut, eine Verbindung herzustellen.

### Netzwerkfehler, wenn bereits eine Verbindung hergestellt wurde
<a name="broadcast-ios-stage-join-errors-network-already-joined"></a>

Wenn die Netzwerkverbindung des Geräts ausfällt, verliert das SDK möglicherweise die Verbindung zu den Stage-Servern. Dies wird asynchron über den von der Anwendung bereitgestellten Stage-Renderer gemeldet.

Das SDK ruft `stage(didChange connectionState, withError error)` mit dem Fehlercode = 1300 und dem Wert IVSBroadcastErrorIsFatalKey = YES auf.

**Handlung**: Warten Sie, bis die Konnektivität des Geräts wiederhergestellt ist, und versuchen Sie erneut, eine Verbindung herzustellen.

## Fehler beim Veröffentlichen/Abonnieren
<a name="broadcast-ios-publish-subscribe-errors"></a>

### Anfänglich
<a name="broadcast-ios-publish-subscribe-errors-initial"></a>

Es gibt mehrere Arten von Fehlern:
+ MultihostSessionOfferCreationFailPublish (1.020)
+ MultihostSessionOfferCreationFailSubscribe (1.021)
+ MultihostSessionNoIceCandidates (1.022)
+ MultihostSessionStageAtCapacity (1.024)
+ SignallingSessionCannotRead (1.201)
+ SignallingSessionCannotSend (1.202)
+ SignallingSessionBadResponse (1.203)

Diese werden asynchron über den von der Anwendung bereitgestellten Stage-Renderer gemeldet.

Das SDK wiederholt den Vorgang für eine begrenzte Anzahl von Malen. Bei Wiederholungen ist der Status „Veröffentlichen/Abonnieren“ `ATTEMPTING_PUBLISH` / `ATTEMPTING_SUBSCRIBE`. Wenn die Wiederholungsversuche erfolgreich sind, ändert sich der Status auf `PUBLISHED` / `SUBSCRIBED`.

Das SDK ruft `IVSErrorDelegate:didEmitError` mit dem entsprechenden Fehlercode und `IVSBroadcastErrorIsFatalKey == NO` auf.

**Aktion**: Es ist keine Aktion erforderlich, da das SDK es automatisch wiederholt. Optional kann die Anwendung die Strategie aktualisieren, um weitere Wiederholungsversuche zu erzwingen.

### Bereits eingerichtet, dann gescheitert
<a name="broadcast-ios-publish-subscribe-errors-established"></a>

Eine Veröffentlichung oder ein Abonnement kann nach der Einrichtung fehlschlagen, was höchstwahrscheinlich auf einen Netzwerkfehler zurückzuführen ist. Fehlercode 1400, Meldung: „Die Peer-Verbindung wurde aufgrund eines unbekannten Netzwerkfehlers unterbrochen.“

Dies wird asynchron über den von der Anwendung bereitgestellten Stage-Renderer gemeldet.

Das SDK versucht erneut, den Vorgang zu veröffentlichen/abonnieren. Bei Wiederholungen ist der Status „Veröffentlichen/Abonnieren“ `ATTEMPTING_PUBLISH` / `ATTEMPTING_SUBSCRIBE`. Wenn die Wiederholungsversuche erfolgreich sind, ändert sich der Status auf `PUBLISHED` / `SUBSCRIBED`.

Das SDK ruft `didEmitError` mit dem Fehlercode = 1400 und IVSBroadcastErrorIsFatalKey = NO auf.

**Aktion**: Es ist keine Aktion erforderlich, da das SDK es automatisch wiederholt. Optional kann die Anwendung die Strategie aktualisieren, um weitere Wiederholungsversuche zu erzwingen. Im Falle eines vollständigen Verbindungsverlusts ist es wahrscheinlich, dass auch die Verbindung zu Stages fehlschlägt.

# IVS-Broadcast-SDK: Gemischte Geräte
<a name="broadcast-mixed-devices"></a>

Gemischte Geräte sind Audio- und Videogeräte, die mehrere Eingangsquellen auf einen einzigen Ausgang kombinieren. Geräte mischen ist ein leistungsstarkes Feature, mit dem Sie mehrere Bildschirmelemente (Video) und Audiospuren definieren und verwalten können. Sie können Video und Audio aus mehreren Quellen wie Kameras, Mikrofonen, Bildschirmaufnahmen sowie von Ihrer App generiertes Audio und Video kombinieren. Sie können mit Übergängen diese Quellen im Video, das Sie an IVS streamen, verschieben und während des Streamings Quellen hinzufügen oder entfernen.

Gemischte Geräte gibt es in Bild- und Audioausführungen. Um ein Gemischtes-Bild-Gerät zu erstellen, rufen Sie auf:

`DeviceDiscovery.createMixedImageDevice()` auf Android

`IVSDeviceDiscovery.createMixedImageDevice()` auf iOS

Das zurückgegebene Gerät kann wie jedes andere Gerät an eine `BroadcastSession` (Streaming mit niedriger Latenz) oder `Stage` (Echtzeit-Streaming) angeschlossen werden.

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

![\[Terminologie für gemischte Geräte mit IVS-Broadcasting.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Broadcast_SDK_Mixer_Glossary.png)



| Begriff | Beschreibung | 
| --- | --- | 
| Gerät | Eine Hardware- oder Softwarekomponente, die Audio- oder Bild-Eingaben produziert. Beispiele für Geräte sind Mikrofone, Kameras, Bluetooth-Headsets und virtuelle Geräte wie Bildschirmaufnahmen oder benutzerdefinierte Image-Eingänge. | 
| Gemischtes Gerät | Ein `Device`, das wie jedes andere `Device` an ein `BroadcastSession` angehängt werden kann, jedoch mit zusätzlichen APIs, die das Hinzufügen von `Source`-Objekten ermöglichen. Gemischte Geräte verfügen über interne Mischer, die Audio- oder Bilddateien zusammensetzen und so einen einzigen Audio- und Bildausgangsstream erzeugen. Gemischte Geräte gibt es in Bild- und Audioausführungen.  | 
| Konfiguration gemischter Geräte | Ein Konfigurationsobjekt für das gemischte Gerät. Bei gemischten Geräten mit Bildern werden dadurch Eigenschaften wie Abmessungen und Bildrate konfiguriert. Bei gemischten Geräten mit Audiodateien wird dadurch die Kanalanzahl konfiguriert. | 
|  Quelle | Ein Container, der die Position eines visuellen Elements auf dem Bildschirm und die Eigenschaften einer Audiospur im Audiomix definiert. Ein gemischtes Gerät kann mit null oder mehr Quellen konfiguriert werden. Quellen erhalten eine Konfiguration, die sich darauf auswirkt, wie die Medien der Quelle verwendet werden. Das obige Image zeigt vier Bildquellen: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html)  | 
| Konfiguration der Quelle |  Ein Konfigurationsobjekt für die Quelle, welche in ein gemischtes Gerät geht. Die vollständigen Konfigurationsobjekte werden unten beschrieben.   | 
| Übergang | Um einen Slot an eine neue Position zu verschieben oder einige seiner Eigenschaften zu ändern, verwenden Sie `MixedDevice.transitionToConfiguration()`. Diese Methode verwendet: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html) | 

## Gemischtes Audio-Gerät
<a name="broadcast-mixed-audio-device"></a>

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

`MixedAudioDeviceConfiguration` auf Android

`IVSMixedAudioDeviceConfiguration` auf iOS


| Name | Typ | Beschreibung | 
| --- | --- | --- | 
| `channels` | Ganzzahl | Anzahl der Ausgangskanäle des Audiomischers. Gültige Werte: 1 und 2. 1 ist Mono-Audio; 2 Stereo-Audio. Standard: 2 | 

### Konfiguration der Quelle
<a name="broadcast-mixed-audio-device-source-configuration"></a>

`MixedAudioDeviceSourceConfiguration` auf Android

`IVSMixedAudioDeviceSourceConfiguration` auf iOS


| Name | Typ | Beschreibung | 
| --- | --- | --- | 
| `gain` | Gleitkommazahl | Audio-Gain. Dies ist ein Multiplikator, daher erhöht jeder Wert über 1 den Gain; jeder Wert unter 1 verringert ihn. Zulässige Werte: 0 bis 2. Standard: 1  | 

## Gemischtes Bild-Gerät
<a name="broadcast-mixed-image-device"></a>

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

`MixedImageDeviceConfiguration` auf Android

`IVSMixedImageDeviceConfiguration` auf iOS


| Name | Typ | Beschreibung | 
| --- | --- | --- | 
| `size` | Vec2 | Größe der Video-Bildfläche. | 
| `targetFramerate` | Ganzzahl | Anzahl der Ziel-Frames pro Sekunde für das gemischte Gerät. Im Durchschnitt sollte dieser Wert erreicht werden, aber das System kann unter bestimmten Umständen Frames auslassen (z. B. hohe CPU- oder GPU-Auslastung). | 
| `transparencyEnabled` | Boolesch | Dies ermöglicht das Mischen mithilfe der `alpha`-Eigenschaft in Bildquellenkonfigurationen. Wenn Sie diese Einstellung auf `true` einstellen, erhöht sich der Speicher- und CPU-Verbrauch. Standardwert: `false`. | 

### Konfiguration der Quelle
<a name="broadcast-mixed-image-device-source-configuration"></a>

`MixedImageDeviceSourceConfiguration` auf Android

`IVSMixedImageDeviceSourceConfiguration` auf iOS


| Name | Typ | Beschreibung | 
| --- | --- | --- | 
| `alpha` | Gleitkommazahl | Alpha des Slots. Dies ist multiplikativ mit allen Alpha-Werten im Image. Gültige Werte: 0 bis 1, wobei 0 vollständig undurchsichtig und 1 vollständig transparent ist. Standard: 1 | 
| `aspect` | AspectMode | Aspekt-Ratio-Modus für jedes Image, das im Slot gerendert wird. Zulässige Werte: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html) Standardwert: `Fit`  | 
| `fillColor` | Vec4 | Füllfarbe, die mit `aspect Fit` verwendet wird, wenn die Seitenverhältnisse von Slot und Bild nicht übereinstimmen. Das Format ist (rot, grün, blau, alpha). Gültiger Wert (für jeden Kanal): 0 bis 1. Standard: 0.0.0.0 | 
| `position` | Vec2 | Position des Slots (in Pixel) relativ zur linken oberen Ecke der Bildfläche. Der Ursprung des Slots ist ebenfalls oben links. | 
| `size` | Vec2 | Größe des Slots in Pixel. Ein Festlegen dieses Wertes setzt `matchCanvasSize` außerdem auf `false`. Standard: (0, 0); weil `matchCanvasSize` jedoch standardmäßig `true` ist, ist die gerenderte Slotgröße gleich der Bildflächengröße, nicht (0, 0). | 
| `zIndex` | Gleitkommazahl | Relative Reihenfolge der Slots. Slots mit höherem `zIndex`-Wert werden über Slots mit niedrigerem `zIndex`-Wert gelegt. | 

## Erstellen und Konfigurieren eines gemischten Bildgerätes
<a name="broadcast-mixed-image-device-creating-configuring"></a>

![\[Konfigurieren einer Broadcast-Sitzung zum Mischen\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Broadcast_SDK_Mixer_Configuring.png)


Hier erstellen wir eine Szene, die der am Anfang dieses Handbuchs ähnelt, mit drei Bildschirmelementen:
+ Slot unten links für eine Kamera.
+ Slot unten rechts für eine Logo-Überlagerung.
+ Slot oben rechts für einen Film.

Beachten Sie, dass der Ursprung für die Bildfläche die obere linke Ecke ist und dies für alle Slots gilt. Wenn Sie also einen Slot an (0, 0) positionieren, wird er in die obere linke Ecke gesetzt, wobei der gesamte Slot sichtbar ist.

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

## Entfernen von Quellen
<a name="broadcast-mixed-devices-removing-sources"></a>

Um eine Quelle zu entfernen, rufen Sie `MixedDevice.remove` mit dem `Source`-Objekt auf, das Sie entfernen möchten.

## Animationen mit Übergängen
<a name="broadcast-mixed-devices-animations-transitions"></a>

Die Übergangsmethode ersetzt die Konfiguration einesr Quelle durch eine neue Konfiguration. Dieser Ersatz kann zeitlich animiert werden, indem eine Dauer von mehr als 0 Sekunden festgelegt wird. 

### Welche Eigenschaften können animiert werden?
<a name="broadcast-mixed-devices-animations-properties"></a>

Nicht alle Eigenschaften in der Slot-Struktur können animiert werden. Alle Eigenschaften, die auf Float-Typen basieren, können animiert werden; andere Eigenschaften werden entweder am Anfang oder am Ende der Animation wirksam.


| Name | Kann es animiert werden? | Auswirkungspunkt | 
| --- | --- | --- | 
| `Audio.gain` | Ja | Interpoliert | 
| `Image.alpha` | Ja | Interpoliert | 
| `Image.aspect` | Nein | Ende | 
| `Image.fillColor` | Ja | Interpoliert | 
| `Image.position` | Ja | Interpoliert | 
| `Image.size` | Ja | Interpoliert | 
| `Image.zIndex` Hinweis: `zIndex` verschiebt 2D-Ebenen durch den 3D-Raum, so dass der Übergang stattfindet, wenn sich die beiden Ebenen irgendwann in der Mitte der Animation kreuzen. Dies könnte berechnet werden, hängt aber des `zIndex`-Start- und -Endwertes ab. Kombinieren Sie dies für einen reibungsloseren Übergang mit `alpha`.  | Ja | Unbekannt | 

### Einfache Beispiele:
<a name="broadcast-mixed-devices-animations-examples"></a>

Im Folgenden finden Sie Beispiele für eine Vollbild-Kameraübernahme unter Verwendung der oben unter [Erstellen und Konfigurieren eines gemischten Bildgeräts definierten Konfiguration](#broadcast-mixed-image-device-creating-configuring). Dies wird 0,5 Sekunden lang animiert.

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

## Spiegelung der Übertragung
<a name="broadcast-mixed-devices-mirroring"></a>


| Um ein angeschlossenes Bildgerät in der Übertragung in diese Richtung zu spiegeln ... | Verwenden Sie einen negativen Wert für … | 
| --- | --- | 
| Horizontal | Die Breite des Slots | 
| Vertikal | Die Höhe des Slots | 
| Sowohl horizontal als auch vertikal | Die Slot-Breite und -Höhe | 

Die Position muss um denselben Wert angepasst werden, damit der Slot beim Spiegeln in die richtige Position gebracht wird.

Im Folgenden finden Sie Beispiele für die horizontale und vertikale Spiegelung der Übertragung.

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

Horizontale Spiegelung:

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

Vertikale Spiegelung:

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

Horizontale Spiegelung:

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

Vertikale Spiegelung:

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

Hinweis: Diese Spiegelung unterscheidet sich von der `setMirrored`-Methode in `ImagePreviewView` (Android) und `IVSImagePreviewView` (iOS). Diese Methode wirkt sich nur auf die lokale Vorschauansicht auf dem Gerät aus und hat keine Auswirkungen auf die Übertragung.

# IVS-Broadcast-SDK: Token-Austausch \$1 Echtzeit-Streaming
<a name="broadcast-mobile-token-exchange"></a>

Der Token-Austausch ermöglicht es Ihnen, die Funktionen für Teilnehmer-Tokens zu aktualisieren oder herabzustufen und Token-Attribute innerhalb des Mobile Broadcast SDK zu aktualisieren, ohne dass die Teilnehmer erneut eine Verbindung herstellen müssen. Dies ist nützlich für Szenarien wie Co-Hosting, bei denen Teilnehmer zunächst nur über Subscribe-Funktionen verfügen und später Veröffentlichungsfunktionen benötigen.

Einschränkungen:
+ Der Token-Austausch funktioniert nur mit Token, die auf Ihrem Server mit einem [Schlüsselpaar](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-distribute-tokens.html#getting-started-distribute-tokens-self-signed) erstellt wurden. Er funktioniert nicht mit Token, die über die [CreateParticipantToken-API](https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/API_CreateParticipantToken.html) erstellt wurden. 
+ Wenn Sie den Token-Austausch verwenden, um Attribute zu ändern, die serverseitige Zusammensetzungslayouts steuern (wie featuredParticipantAttribute und participantOrderAttribute), wird das Layout einer aktiven Zusammensetzung erst aktualisiert, wenn der Teilnehmer wieder eine Verbindung herstellt. 

## Teilnehmer-Token
<a name="broadcast-mobile-token-exchange-exchanging-tokens"></a>

Der Token-Austausch ist unkompliziert: Rufen Sie die `exchangeToken`-API für das `Stage`/`IVSStage`-Objekt auf und stellen Sie das neue Token bereit. Wenn sich die `capabilities` des neuen Tokens von denen des vorherigen Tokens unterscheiden, werden die Fähigkeiten des neuen Tokens sofort bewertet. Wenn das vorherige Token beispielsweise nicht über die Fähigkeit `publish` verfügte, das neue Token jedoch schon, werden die Stage-Strategy-Funktionen für die Veröffentlichung aufgerufen, sodass die Hostanwendung entscheiden kann, ob sie mit der neuen Funktion sofort veröffentlichen oder warten möchte. Das Gleiche gilt für entfernte Funktionen: Wenn das vorherige Token über die Fähigkeit `publish` verfügte und das neue Token nicht, macht der Teilnehmer die Veröffentlichung sofort rückgängig, ohne die Funktionen der Phasenstrategie für die Veröffentlichung aufzurufen.

Beim Token-Austausch müssen das vorherige und das neue Token dieselben Werte für die folgenden Nutzdaten-Felder haben: 
+ `topic`
+ `resource`
+ `jti`
+ `whip_url`
+ `events_url`

Diese Felder sind unveränderlich. Der Austausch eines Tokens, das ein unveränderliches Feld ändert, führt dazu, dass das SDK den Austausch sofort ablehnt.

Die übrigen Felder können geändert werden, darunter:
+ `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)
```

## Empfangen von Updates
<a name="broadcast-mobile-token-exchange-receiving-updates"></a>

Eine Funktion in `StageRenderer`/`IVSStageRenderer` empfängt Updates über bereits veröffentlichte Remote-Teilnehmer, die ihre Token austauschen, um ihre `userId` oder `attributes` zu aktualisieren. Fernteilnehmer, die noch nicht veröffentlichen, erhalten ihre `userId` und `attributes` aktualisiert und über die vorhandenen `onParticipantJoined`/`participantDidJoin`-Renderer-Funktionen angezeigt, wenn sie schließlich veröffentlichen.

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

## Sichtbarkeitsstatus
<a name="broadcast-mobile-token-exchange-visibility"></a>

Wenn ein Teilnehmer ein Token austauscht, um seine `userId` oder `attributes` zu aktualisieren, hängt die Sichtbarkeit dieser Änderungen von seinem aktuellen Veröffentlichungsstatus ab: 
+ **Wenn der Teilnehmer *nicht* veröffentlicht:** Die Aktualisierung wird im Hintergrund verarbeitet. Wenn er irgendwann veröffentlicht, erhalten alle SDKs die aktualisierte `userId` und `attributes` als Teil der ersten Veröffentlichung.
+ **Falls der Teilnehmer *bereits* veröffentlicht:** Das Update wird sofort übertragen. Allerdings erhalten nur mobile SDKs v1.37.0\$1 die Benachrichtigung. Teilnehmer mit dem Web-SDK, älteren mobilen SDKs und der serverseitigen Zusammensetzung sehen die Änderung erst, wenn der Teilnehmer die Veröffentlichung rückgängig macht und sie erneut veröffentlicht.

In dieser Tabelle wird die Unterstützungsmatrix verdeutlicht:


| Teilnehmerstatus | Beobachter: Mobile SDK 1.37.0\$1 | Beobachter: Ältere mobile SDKs, Web-SDK, serverseitige Zusammensetzung | 
| --- | --- | --- | 
| Wird nicht veröffentlicht (startet dann) | ✅ Sichtbar (bei Veröffentlichung über eine Veranstaltung, der ein Teilnehmer beigetreten ist) | ✅ Sichtbar (bei Veröffentlichung über eine Veranstaltung, der ein Teilnehmer beigetreten ist) | 
| Bereits veröffentlicht (wird nie erneut veröffentlicht) | ✅ Sichtbar (sofort über ein durch Metadaten der Teilnehmer aktualisiertes Ereignis) | ❌ Nicht sichtbar | 
| Bereits veröffentlicht (Veröffentlichung rückgängig gemacht und erneut veröffentlicht) | ✅ Sichtbar (sofort über ein durch Metadaten der Teilnehmer aktualisiertes Ereignis) | ⚠️ Letztendlich sichtbar (bei erneuten Veröffentlichung über eine Veranstaltung, der ein Teilnehmer beigetreten ist) | 

# IVS-Broadcast-SDK: Benutzerdefinierte Image-Quellen \$1 Echtzeit-Streaming
<a name="broadcast-custom-image-sources"></a>

Benutzerdefinierte Bildeingabequellen ermöglichen es einer Anwendung, eine eigene Bildeingabe für das Broadcast-SDK bereitzustellen, anstatt sich auf die voreingestellten Kameras oder die Bildschirmfreigabe zu beschränken. Eine benutzerdefinierte Bildquelle kann so einfach sein wie ein halbtransparentes Wasserzeichen oder eine statische „Bin gleich zurück“-Szene, oder es kann der Anwendung ermöglichen, zusätzliche benutzerdefinierte Verarbeitungen wie das Hinzufügen von Schönheitsfiltern zur Kamera durchzuführen.

Wenn Sie eine benutzerdefinierte Image-Eingangsquelle zur benutzerdefinierten Steuerung der Kamera verwenden (z. B. die Verwendung von Schönheitsfilter-Bibliotheken, die Kamerazugriff erfordern), ist das Broadcast-SDK nicht mehr für die Verwaltung der Kamera verantwortlich. Stattdessen ist die Anwendung dafür verantwortlich, den Lebenszyklus der Kamera korrekt zu handhaben. Lesen Sie die offizielle Plattformdokumentation darüber, wie Ihre Anwendung die Kamera verwalten soll.

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

Erstellen Sie nach dem Erstellen einer `DeviceDiscovery`-Sitzung eine Bildeingabequelle:

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

Diese Methode gibt ein `CustomImageSource` zurück, welche eine Image-Quelle ist, die von einem Standard-Android-[Surface](https://developer.android.com/reference/android/view/Surface) unterstützt wird. Die Unterklasse `SurfaceSource` kann in der Größe geändert und gedreht werden. Sie können auch ein `ImagePreviewView` erstellen, um eine Vorschau seines Inhalts anzuzeigen.

So rufen Sie das zugrundeliegende ab `Surface`:

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

Dieses `Surface` kann als Ausgabepuffer für Image-Produzenten wie Camera2, OpenGL ES und andere Bibliotheken verwendet werden. Der einfachste Anwendungsfall ist das direkte Zeichnen einer statischen Bitmap oder Farbe in den Canvas des Surface. Viele Bibliotheken (wie Schönheitsfilter-Bibliotheken) bieten jedoch eine Methode, mit der eine Anwendung ein externes `Surface` zum Rendern angeben kann. Sie können eine solche Methode verwenden, um dieses `Surface` an die Filterbibliothek zu übergeben, die es der Bibliothek ermöglicht, verarbeitete Frames für das Streamen der Broadcast-Sitzung auszugeben.

Dieses `CustomImageSource` kann in einen `LocalStageStream` verpackt und von der `StageStrategy` zurückgegeben werden, um es in einer `Stage` zu veröffentlichen.

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

Erstellen Sie nach dem Erstellen einer `DeviceDiscovery`-Sitzung eine Bildeingabequelle:

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

Diese Methode gibt eine `IVSCustomImageSource` zurück, welche eine Image-Quelle ist, die es der Anwendung ermöglicht, `CMSampleBuffers` manuell abzusenden. Informationen zu unterstützten Pixelformaten finden Sie in der iOS-Broadcast-SDK-Referenz; ein Link zur aktuellsten Version befindet sich in den [Versionshinweisen zu Amazon IVS](release-notes.md) für die neueste Broadcast-SDK-Version.

Die an die benutzerdefinierte Quelle gesendeten Proben werden auf die Stage gestreamt:

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

Verwenden Sie diese Methode zum Streamen von Videos in einem Rückruf. Wenn Sie beispielsweise die Kamera verwenden, kann die Anwendung jedes Mal, wenn ein neuer Beispielpuffer von einer `AVCaptureSession` erhalten wird, den Beispielpuffer an die benutzerdefinierte Image-Quelle weiterleiten. Falls gewünscht, kann die Anwendung eine weitere Verarbeitung (wie einen Schönheitsfilter) anwenden, bevor sie das Beispiel an die benutzerdefinierte Image-Quelle absendet.

Dieses `IVSCustomImageSource` kann in einen `IVSLocalStageStream` verpackt und von der `IVSStageStrategy` zurückgegeben werden, um es in einer `Stage` zu veröffentlichen.

# IVS-Broadcast-SDK: Benutzerdefinierte Audio-Quellen \$1 Echtzeit-Streaming
<a name="broadcast-custom-audio-sources"></a>

**Hinweis:** Diese Anleitung führt Sie durch die Schritte zum Einstieg in das Android Broadcast-SDK für IVS-Streaming. Informationen für die iOS- und Web-SDKs werden zukünftig veröffentlicht.

Benutzerdefinierte Audioeingangsquellen ermöglichen es einer Anwendung, ihre eigenen Audioeingänge an das Broadcast-SDK zu liefern, anstatt auf das integrierte Mikrofon des Geräts beschränkt zu sein. Eine benutzerdefinierte Audioquelle ermöglicht es Anwendungen, verarbeitetes Audio mit Effekten zu streamen, mehrere Audiostreams zu mischen oder in Audioverarbeitungsbibliotheken von Drittanbietern zu integrieren.

Beachten Sie, dass das Broadcast-SDK nicht mehr für die Verwaltung der Kamera verantwortlich ist, wenn Sie eine benutzerdefinierte Bildeingabequelle zur benutzerdefinierten Steuerung der Kamera verwenden. Stattdessen ist Ihre Anwendung für die Erfassung, Verarbeitung und Übertragung von Audiodaten an die benutzerdefinierte Quelle verantwortlich.

Der Arbeitsablauf für benutzerdefinierte Audioquellen folgt diesen Schritten:

1. Audioeingang – Erstellen Sie eine benutzerdefinierte Audioquelle mit einem bestimmten Audioformat (Samplerate, Kanäle, Format). 

1. Ihre Verarbeitung – Erfassen oder generieren Sie Audiodaten aus Ihrer Audioverarbeitungspipeline.

1. Benutzerdefinierte Audioquelle – Senden Sie Audiopuffer an die benutzerdefinierte Quelle mithilfe von`appendBuffer()`.

1. Stage – In `LocalStageStream` abschließen und über Ihre `StageStrategy` in der Stage veröffentlichen. 

1. Teilnehmer – Die Teilnehmer der Phase erhalten das bearbeitete Audio in Echtzeit.

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

### Erstellen einer benutzerdefinierten Audioquelle
<a name="custom-audio-sources-android-creating-a-custom-audio-source"></a>

Erstellen Sie nach dem Erstellen einer `DeviceDiscovery`-Sitzung eine Audioeingabequelle:

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

Diese Methode gibt eine `CustomAudioSource` zurück, die unformatierte PCM-Audiodaten akzeptiert. Die benutzerdefinierte Audioquelle muss mit demselben Audioformat konfiguriert sein, das Ihre Audioverarbeitungspipeline erzeugt.

#### Unterstützte Formate
<a name="custom-audio-sources-android-submitting-audio-data-supportedi-audio-formats"></a>


| Parameter | Optionen | Beschreibung | 
| --- | --- | --- | 
| Kanäle | 1 (Mono), 2 (Stereo) | Die Anzahl der Audiokanäle. | 
| Abtastrate | RATE\$116000, RATE\$144100, RATE\$148000 | Audio-Abtastrate in Hz. Für hohe Qualität werden 48 kHz empfohlen. | 
| Format | INT16, FLOAT32 | Format der Hörprobe. INT16 ist 16-Bit-Festkomma-PCM, FLOAT32 ist 32-Bit-Gleitkomma-PCM. Es sind sowohl verschachtelte als auch planare Formate verfügbar. | 

### Audioeinstellungen
<a name="custom-audio-sources-android-submitting-audio-data"></a>

Verwenden Sie die Methode `appendBuffer()`, um Audiodaten an die benutzerdefinierte Quelle zu senden:

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

**Wichtige Überlegungen:**
+ Audiodaten müssen in dem Format vorliegen, das bei der Erstellung der benutzerdefinierten Quelle angegeben wurde.
+ Die Zeitstempel sollten monoton ansteigend sein und von Ihrer Audioquelle bereitgestellt werden, um eine reibungslose Audiowiedergabe zu gewährleisten.
+ Reichen Sie regelmäßig Audiodateien ein, um Lücken im Stream zu vermeiden.
+ Die Methode gibt die Anzahl der verarbeiteten Samples zurück (0 bedeutet Fehler). 

### In einer Stage veröffentlichen
<a name="custom-audio-sources-android-publishing-to-a-stage"></a>

Die `CustomAudioSource` in einem `AudioLocalStageStream` abschließen und von Ihrer `StageStrategy` zurücksenden:

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

### Vollständiges Beispiel: Integration der Audioverarbeitung
<a name="custom-audio-sources-android-complete-example"></a>

Hier ist ein vollständiges Beispiel, das die Integration mit einem Audioverarbeitungs-SDK zeigt:

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

### Bewährte Methoden
<a name="custom-audio-sources-android-best-practices"></a>

#### Audioformatkonsistenz
<a name="custom-audio-sources-android-best-practices-audio-format-consistency"></a>

Stellen Sie sicher, dass das von Ihnen eingereichte Audioformat dem Format entspricht, das bei der Erstellung der benutzerdefinierten Quelle angegeben wurde:

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

#### Pufferverwaltung
<a name="custom-audio-sources-android-best-practices-buffer-managemetn"></a>

Verwenden Sie direkte `ByteBuffers` und verwenden Sie sie erneut, um die Garbage Collection zu minimieren: 

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

#### Timing und Synchronisation
<a name="custom-audio-sources-android-best-practices-timing-and-synchronization"></a>

Für eine reibungslose Audiowiedergabe müssen Sie die von Ihrer Audioquelle bereitgestellten Zeitstempel verwenden. Wenn Ihre Audioquelle keinen eigenen Zeitstempel hat, erstellen Sie Ihren eigenen Epochenzeitstempel und berechnen Sie die Dauer zwischen den einzelnen Samples manuell anhand der Anzahl der Frames und der Framegröße. 

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

#### Fehlerbehandlung
<a name="custom-audio-sources-android-best-practices-error-handling"></a>

Überprüfen Sie immer den Rückgabewert von `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: Kamerafilter von Drittanbietern \$1 Echtzeit-Streaming
<a name="broadcast-3p-camera-filters"></a>

In diesem Handbuch wird davon ausgegangen, dass Sie bereits mit [benutzerdefinierten Bild](broadcast-custom-image-sources.md)-Quellen vertraut sind und das [Broadcast-SDK zu IVS-Echtzeit-Streaming](broadcast.md) in Ihre Anwendung integrieren.

Kamerafilter ermöglichen es Livestream-Erstellern, ihr Gesichts- oder Hintergrundbild zu verbessern oder zu ändern. Dies kann möglicherweise das Engagement der Zuschauer steigern, Zuschauer anlocken und das Live-Streaming-Erlebnis verbessern.

# Integration von Kamerafiltern von Drittanbietern
<a name="broadcast-3p-camera-filters-integrating"></a>

Sie können Kamera-Filter-SDKs von Drittanbietern in das IVS-Broadcast-SDK integrieren, indem Sie die Ausgabe des Filter-SDKs einer [benutzerdefinierten Bildeingabequelle](broadcast-custom-image-sources.md) zuführen. Mit einer benutzerdefinierten Bildeingabequelle kann eine Anwendung ihre eigene Bildereingabe für das Broadcast SDK bereitstellen. Das SDK eines Drittanbieters von Filtern kann möglicherweise den Lebenszyklus der Kamera verwalten, um Bilder von der Kamera zu verarbeiten, einen Filtereffekt anzuwenden und in einem Format auszugeben, das an eine benutzerdefinierte Bildquelle übergeben werden kann.

![\[Integration von Kamera-Filter-SDKs von Drittanbietern in das IVS-Broadcast-SDK, indem Sie die Ausgabe des Filter-SDKs einer benutzerdefinierten Bildeingabequelle zuführen.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Integrating.png)


Informationen zu integrierten Methoden zum Konvertieren eines Kamerarahmens mit angewendetem Filtereffekt in ein Format, das an eine [benutzerdefinierte Bildeingabequelle](broadcast-custom-image-sources.md) übergeben werden kann, finden Sie in der Dokumentation Ihres Drittanbieters von Filtern. Der Prozess variiert, je nachdem, welche Version des IVS Broadcast SDK verwendet wird:
+ **Web** – Der Filteranbieter muss in der Lage sein, seine Ausgabe auf einem Bildflächenelement zu rendern. Anschließend kann die Methode [captureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) verwendet werden, um einen MediaStream des Bildflächeninhalts zurückzugeben. Der MediaStream kann dann in eine Instance eines [LocalStageStream](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/classes/LocalStageStream) umgewandelt und auf einer Stage veröffentlicht werden.
+ **Android** – Das SDK des Filteranbieters kann entweder einen Frame auf einem vom IVS-Broadcast-SDK bereitgestellten Android `Surface` rendern oder den Frame in eine Bitmap konvertieren. Wenn Sie eine Bitmap verwenden, kann diese dann durch Entsperren und Schreiben in eine Bildfläche in der zugrunde liegenden `Surface` gerendert werden, das von der benutzerdefinierten Bildquelle bereitgestellt wird.
+ **iOS** – Das SDK eines Drittanbieter für Filter muss einen Kamerarahmen mit einem als `CMSampleBuffer` angewendeten Filtereffekt bereitstellen. Informationen dazu, wie Sie nach der Verarbeitung eines Kamerabilds ein `CMSampleBuffer` als endgültige Ausgabe erhalten, finden Sie in der SDK-Dokumentation Ihres Drittanbieters für Filter.

# Verwendung von BytePlus mit dem IVS Broadcast SDK
<a name="broadcast-3p-camera-filters-integrating-byteplus"></a>

In diesem Dokument wird erklärt, wie Sie das BytePlus Effects SDK mit dem IVS Broadcast SDK verwenden.

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

### Installieren und Einrichten des BytePlus-Effects-SDK
<a name="integrating-byteplus-android-install-effects-sdk"></a>

Einzelheiten zur Installation, Initialisierung und Einrichtung des BytePlus-Effects-SDK finden Sie im [Android-Zugriffsleitfaden](https://docs.byteplus.com/en/effects/docs/android-v4101-access-guide) von BytePlus.

### Einrichten der benutzerdefinierten Bildquelle
<a name="integrating-byteplus-android-setup-image-source"></a>

Führen Sie nach der Initialisierung des SDK verarbeitete Kamerarahmen mit einem Filtereffekt ein, der auf eine benutzerdefinierte Bildeingabequelle angewendet wird. Erstellen Sie dazu eine Instance eines `DeviceDiscovery`-Objekts sowie eine benutzerdefinierte Bildquelle. Beachten Sie, dass das Broadcast-SDK nicht mehr für die Verwaltung der Kamera verantwortlich ist, wenn Sie eine benutzerdefinierte Bildeingabequelle zur benutzerdefinierten Steuerung der Kamera verwenden. Stattdessen ist die Anwendung dafür verantwortlich, den Lebenszyklus der Kamera korrekt zu handhaben.

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

### Konvertieren der Ausgabe in ein Bitmap und Zuführen zu einer benutzerdefinierten Bildeingabequelle
<a name="integrating-byteplus-android-convert-to-bitmap"></a>

Damit Kamerarahmen, auf die ein vom BytePlus-Effects-SDK angewendeter Filtereffekt angewendet wurde, direkt an das IVS-Broadcast-SDK weitergeleitet werden können, konvertieren Sie die Texturausgabe des BytePlus-Effects-SDK in eine Bitmap. Bei der Verarbeitung eines Bildes wird die `onDrawFrame()`-Methode vom SDK aufgerufen. Die `onDrawFrame()`-Methode ist eine öffentliche Methode der [GLSurfaceView.Renderer](https://developer.android.com/reference/android/opengl/GLSurfaceView.Renderer)-Schnittstelle von Android. In der von BytePlus bereitgestellten Android-Beispielanwendung wird diese Methode bei jedem Kamerarahmen aufgerufen. Diese gibt eine Textur aus. Gleichzeitig können Sie die `onDrawFrame()`-Methode mit Logik ergänzen, um diese Textur in eine Bitmap umzuwandeln und sie einer benutzerdefinierten Bildeingabequelle zuzuführen. Verwenden Sie für diese Konvertierung, wie im folgenden Codebeispiel gezeigt, die vom BytePlus-SDK bereitgestellte `transferTextureToBitmap`-Methode. Diese Methode wird von der [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)-Bibliothek aus dem BytePlus Effects SDK bereitgestellt, wie im folgenden Codebeispiel gezeigt. Anschließend können Sie auf das zugrunde liegende Android `Surface` des `CustomImageSource` rendern, indem Sie die resultierende Bitmap auf die Bildfläche einer Oberfläche schreiben. Viele aufeinanderfolgende Aufrufe von `onDrawFrame()` führen zu einer Folge von Bitmaps und erzeugen bei der Kombination einen Videostream.

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

# Verwendung von DeepAR mit dem IVS Broadcast SDK
<a name="broadcast-3p-camera-filters-integrating-deepar"></a>

In diesem Dokument wird erklärt, wie Sie das DeepAR SDK mit dem IVS Broadcast SDK verwenden.

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

Einzelheiten zur Integration des DeepAR-SDK in das Android-IVS-Broadcast-SDK finden Sie im [Android-Integrationshandbuch von DeepAR](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/android/).

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

Einzelheiten zur Integration des DeepAR-SDK in das iOS IVS-Broadcast-SDK finden Sie im [iOS-Integrationshandbuch von DeepAR](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/ios/).

# Verwendung von Snap mit dem IVS Broadcast SDK
<a name="broadcast-3p-camera-filters-integrating-snap"></a>

In diesem Dokument wird erklärt, wie Sie das Camera Kit SDK von Snap mit dem IVS Broadcast SDK verwenden.

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

In diesem Abschnitt wird davon ausgegangen, dass Sie bereits mit dem [Veröffentlichen und Abonnieren von Videos mithilfe des Web-Broadcast-SDK](getting-started-pub-sub-web.md) vertraut sind.

Um das Camera-Kit-SDK von Snap mit dem Web-Broadcast-SDK des IVS-Echtzeit-Streaming zu integrieren, müssen Sie Folgendes tun:

1. Installieren Sie das Camera-Kit-SDK und das Webpack. (In unserem Beispiel wird Webpack als Bundler verwendet, Sie können jedoch jeden Bundler Ihrer Wahl verwenden.)

1. Erstellen von `index.html`.

1. Fügen Sie Einrichtungselemente hinzu.

1. Erstellen von `index.css`.

1. Zeigen Sie Teilnehmer an und legen Sie sie fest.

1. Zeigen Sie die angeschlossenen Kameras und Mikrofone an.

1. Erstellen Sie eine Camera-Kit-Sitzung.

1. Rufen Sie Objektive ab und füllen Sie die Objektivauswahl.

1. Rendern Sie die Ausgabe einer Camera-Kit-Sitzung in einer Bildfläche.

1. Erstellen Sie eine Funktion, um das Dropdown-Menü „Objektiv“ zu füllen.

1. Stellen Sie Camera Kit eine Medienquelle zum Rendern und Veröffentlichen eines `LocalStageStream` zur Verfügung.

1. Erstellen von `package.json`.

1. Erstellen Sie eine Webpack-Konfigurationsdatei.

1. Richten Sie einen HTTPS-Server ein und testen Sie ihn.

Jeder dieser Schritte wird nachfolgend beschrieben.

### Installieren des Camera-Kit-SDK und des Webpacks
<a name="integrating-snap-web-install-camera-kit"></a>

In diesem Beispiel verwenden wir Webpack als Bundler, Sie können jedoch auch einen beliebigen anderen Bundler nutzen.

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

### index.html erstellen
<a name="integrating-snap-web-create-index"></a>

Erstellen Sie als Nächstes das HTML-Boilerplate und importieren Sie das Web-Broadcast-SDK als Skript-Tag. Stellen Sie im folgenden Code sicher, dass Sie `<SDK version>` durch die von Ihnen verwendete Broadcast-SDK-Version ersetzen.

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

### Einrichtungselemente hinzufügen
<a name="integrating-snap-web-add-setup-elements"></a>

Erstellen Sie den HTML-Code für die Auswahl einer Kamera, eines Mikrofons und eines Objektivs sowie für die Angabe eines Teilnehmer-Tokens:

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

Fügen Sie darunter zusätzlichen HTML-Code hinzu, um Kamera-Feeds von lokalen und entfernten Teilnehmern anzuzeigen:

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

Laden Sie zusätzliche Logik, einschließlich Hilfsmethoden zum Einrichten der Kamera und der gebündelten JavaScript-Datei. (Später in diesem Abschnitt werden Sie diese JavaScript-Dateien erstellen und sie in einer einzigen Datei bündeln, damit Sie Camera Kit als Modul importieren können. Die gebündelte JavaScript-Datei enthält die Logik, mit der Sie Camera Kit festlegen können, ein Objektiv anwenden und den Kamera-Feed mit einem Objektiv in einer Stage veröffentlichen). Fügen Sie schließende Tags für die Elemente `body` und `html` hinzu, um die Erstellung von `index.html` abzuschließen.

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

### Erstellen einer Datei vom Typ „index.css“
<a name="integrating-snap-web-create-index-css"></a>

Erstellen Sie eine CSS-Quelldatei, um die Seite zu formatieren. Wir erläutern diesen Code nicht näher, sondern konzentrieren uns auf die Logik für die Verwaltung einer Stage und die Integration in das Camera-Kit-SDK von Snap.

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

### Teilnehmer anzeigen und einrichten
<a name="integrating-snap-web-setup-participants"></a>

Erstellen Sie als Nächstes `helpers.js`, das Hilfsmethoden zum Anzeigen und Einrichten von Teilnehmern enthält:

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

### Angeschlossene Kameras und Mikrofone anzeigen
<a name="integrating-snap-web-display-cameras-microphones"></a>

Erstellen Sie als Nächstes `media-devices.js`, das Hilfsmethoden zum Anzeigen von Kameras und Mikrofonen enthält, die mit Ihrem Gerät verbunden sind:

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

### Erstellen einer Camera-Kit-Sitzung
<a name="integrating-snap-web-camera-kit-session"></a>

Erstellen Sie `stages.js`, das die Logik zum Anwenden eines Objektivs auf den Kamera-Feed und zum Veröffentlichen des Feeds in einer Stage enthält. Es wird empfohlen, den folgenden Codeblock zu kopieren und in `stages.js` einzufügen. Sie können den Code dann Stück für Stück überprüfen, um zu verstehen, was in den folgenden Abschnitten geschieht.

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

Im ersten Teil dieser Datei wird das Broadcast-SDK und das Camera-Kit-Web-SDK importiert und die Variablen, die mit jedem SDK verwenden werden, initialisiert. Es wird eine Camera-Kit-Sitzung erstellt , durch Aufrufen von `createSession` nach dem [Bootstrapping das Camera-Kit-Web-SDK](https://kit.snapchat.com/reference/CameraKit/web/0.7.0/index.html#bootstrapping-the-sdk). Beachten Sie, dass ein Elementobjekt einer Bildfläche an eine Sitzung übergeben wird. Dies weist Camera Kit an, in dieser Bildfläche zu rendern.

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

### Abrufen von Objektiven und Auffüllen der Objektivauswahl
<a name="integrating-snap-web-fetch-apply-lens"></a>

Um Ihre Objektive abzurufen, ersetzen Sie den Platzhalter für die Objektivgruppen-ID durch Ihre eigene ID, die Sie im [Camera-Kit-Entwicklerportal](https://camera-kit.snapchat.com/) finden. Füllen Sie das Dropdown-Menü für die Objektivauswahl mithilfe der Funktion `populateLensSelector()` aus, die wir später erstellen.

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

### Rendern der Ausgabe einer Camera-Kit-Sitzung in einer Bildfläche
<a name="integrating-snap-web-render-output-to-canvas"></a>

Verwenden Sie die Methode [captureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream), um einen `MediaStream` des Bildflächeninhalts zurückzugeben. Die Bildfläche enthält einen Videostream des Kamera-Feeds mit angewendetem Objektiv. Fügen Sie außerdem Ereignis-Listener für Schaltflächen zum Stummschalten von Kamera und Mikrofon sowie Ereignis-Listener für das Betreten und Verlassen einer Stage hinzu. Im Ereignis-Listener für den Beitritt zu einer Stage übergeben wir eine Camera-Kit-Sitzung und das `MediaStream` aus der Bildfläche, damit es in einer Stage veröffentlicht werden kann.

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

### Erstellen einer Funktion zum Auffüllen des Dropdown-Menüs „Objektiv“
<a name="integrating-snap-web-populate-lens-dropdown"></a>

Erstellen Sie die folgende Funktion, um die **Objektivauswahl** mit den zuvor abgerufenen Objektiven zu füllen. Die **Objektivauswahl** ist ein Benutzeroberflächenelement auf der Seite, mit dem Sie aus einer Liste von Objektiven auswählen können, die auf den Kamera-Feed angewendet werden sollen. Erstellen Sie außerdem die Rückruffunktion `handleLensChange`, um das angegebene Objektiv anzuwenden, wenn es im Dropdown-Menü **Objektiv** ausgewählt wird.

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

### Stellen Sie dem Camera Kit eine Medienquelle zum Rendern zur Verfügung und veröffentlichen Sie einen LocalStageStream
<a name="integrating-snap-web-publish-localstagestream"></a>

Um einen Videostream mit einem angewandten Objektiv zu veröffentlichen, rufen Sie eine Funktion mit dem Namen `setCameraKitSource` auf, um das zuvor von der Bildfläche erfasste `MediaStream` zu übergeben. Der `MediaStream` von der Bildfläche tut im Moment nichts, weil wir unseren lokalen Kamera-Feed noch nicht integriert haben. Wir können unseren lokalen Kamera-Feed integrieren, indem wir die Hilfsmethode `getCamera` aufrufen und sie `localCamera` zuweisen. Wir können dann unseren lokalen Kamera-Feed (über `localCamera`) und das Sitzungsobjekt an `setCameraKitSource` übergeben. Die `setCameraKitSource`-Funktion konvertiert unseren lokalen Kamera-Feed in eine [Medienquelle für CameraKit](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#creating-a-camerakitsource), indem sie `createMediaStreamSource` aufruft. Die Medienquelle für `CameraKit` wird dann [umgewandelt](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#2d-transforms), um die nach vorne gerichtete Kamera zu spiegeln. Der Objektiveffekt wird dann auf die Medienquelle angewendet und durch Aufrufen von `session.play()` auf die Ausgabe-Bildfläche gerendert.

Nachdem das Objektiv nun auf das von der Bildfläche erfasste `MediaStream` angewendet wurde, können wir es in einer Stage veröffentlichen. Wir machen das, indem wir einen `LocalStageStream` mit den Videospuren des `MediaStream` erstellen. Eine Instance von `LocalStageStream` kann dann zur Veröffentlichung an eine `StageStrategy` übergeben werden.

#### 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;
    },
  };
```

Der verbleibende Code unten dient der Erstellung und Verwaltung der Stage:

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

### Erstellen einer Datei vom Typ „package.json“
<a name="integrating-snap-web-package-json"></a>

Erstellen Sie `package.json` und fügen Sie die folgende JSON-Konfiguration hinzu. Diese Datei definiert die Abhängigkeiten und enthält einen Skriptbefehl zum Bündeln des Codes.

#### JSON-Konfiguration
<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"
  }
}
```

### Erstellen einer Webpack-Konfigurationsdatei
<a name="integrating-snap-web-webpack-config"></a>

Erstellen Sie `webpack.config.js` und fügen Sie den folgenden Code hinzu. Dadurch wird der bisher erstellte Code gebündelt, sodass wir die Importanweisung zur Verwendung von Camera Kit verwenden können.

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

Führen Sie schließlich `npm run build` aus, um Ihr JavaScript nach den Vorgaben in der Webpack-Konfigurationsdatei zu bündeln. Zu Testzwecken können Sie dann HTML und JavaScript über den lokalen Computer bereitstellen. In diesem Beispiel verwenden wir das Python-Modul `http.server`. 

### Einrichten eines HTTPS-Servers und Ausführen von Tests
<a name="integrating-snap-web-https-server-test"></a>

Zum Testen des Codes müssen Sie einen HTTPS-Server einrichten. Wenn Sie einen HTTPS-Server für die lokale Entwicklung verwenden und die Integration Ihrer Web-App mit dem Camera-Kit-SDK von Snap testen, können Sie CORS-Probleme (Cross-Origin Resource Sharing) vermeiden.

Öffnen Sie ein Terminal und navigieren Sie zu dem Verzeichnis, in dem Sie den gesamten Code bis zu diesem Zeitpunkt erstellt haben. Führen Sie den folgenden Befehl aus, um ein selbstsigniertes SSL-/TLS-Zertifikat und einen privaten Schlüssel zu generieren:

```
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
```

Dadurch werden zwei Dateien erstellt: `key.pem` (der private Schlüssel) und `cert.pem` (das selbstsignierte Zertifikat). Erstellen Sie eine neue Python-Datei mit dem Namen `https_server.py` und fügen Sie den folgenden Code hinzu:

#### 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()
```

Öffnen Sie ein Terminal, navigieren Sie zu dem Verzeichnis, in dem Sie die Datei `https_server.py` erstellt haben, und führen Sie den folgenden Befehl aus:

```
python3 https_server.py
```

Dadurch wird der HTTPS-Server unter „https://localhost:4443“ gestartet und Dateien werden aus dem aktuellen Verzeichnis bereitgestellt. Stellen Sie sicher, dass sich die Dateien `cert.pem` und `key.pem` im selben Verzeichnis wie die Datei `https_server.py` befinden.

Öffnen Sie Ihren Browser und navigieren Sie zu „https://localhost:4443“. Da es sich um ein selbstsigniertes SSL-/TLS-Zertifikat handelt, wird es von Ihrem Webbrowser nicht als vertrauenswürdig eingestuft, sodass Sie eine Warnung erhalten. Sie können die Warnung umgehen, da dieser Schritt nur zu Testzwecken dient. Sie sollten dann sehen, wie der AR-Effekt für das zuvor angegebene Snap-Objektiv auf Ihren Kamera-Feed auf dem Bildschirm angewendet wird.

Beachten Sie, dass dieses Setup, das die integrierten Python-Module `http.server` und `ssl` verwendet, für lokale Entwicklungs- und Testzwecke geeignet ist, für eine Produktionsumgebung jedoch nicht empfohlen wird. Das in diesem Setup verwendete selbstsignierte SSL-/TLS-Zertifikat wird von Webbrowsern und anderen Clients nicht als vertrauenswürdig eingestuft. Das bedeutet, dass Benutzern beim Zugriff auf den Server Sicherheitswarnungen angezeigt werden. In diesem Beispiel verwenden Sie zwar die integrierten Python-Module „http.server“ und „ssl“, Sie können aber auch eine andere HTTPS-Serverlösung nutzen.

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

Um das Camera-Kit-SDK von Snap mit dem IVS-Android-Broadcast-SDK zu integrieren, müssen Sie das Camera-Kit-SDK installieren, eine Camera-Kit-Sitzung initialisieren, ein Objektiv anwenden und die Ausgabe der Camera-Kit-Sitzung an die benutzerdefinierte Bildeingabequelle weiterleiten.

Um das Camera Kit SDK zu installieren, fügen Sie Folgendes zur Datei Ihres `build.gradle`-Moduls hinzu. Ersetzen Sie `$cameraKitVersion` durch die [neueste Version des 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"
```

Initialisieren und beziehen Sie eine `cameraKitSession`. Camera Kit bietet auch einen praktischen Wrapper für die APIs des [CameraX](https://developer.android.com/media/camera/camerax) von Android, sodass Sie keine komplizierte Logik schreiben müssen, um CameraX mit Camera Kit zu verwenden. Sie können das `CameraXImageProcessorSource`-Objekt als [Quelle](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-source/index.html) für [ImageProcessor](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-image-processor/index.html) verwenden, wodurch Sie Streaming-Frames für die Kameravorschau starten können.

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

### Objektive abrufen und anwenden
<a name="integrating-snap-android-fetch-apply-lenses"></a>

Sie können Objektive und ihre Reihenfolge im Karussell im Entwickler-Portal des [Camera Kit](https://camera-kit.snapchat.com/) konfigurieren:

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

Senden Sie zur Übertragung verarbeitete Bilder an die zugrunde liegende `Surface` einer benutzerdefinierten Bildquelle. Verwenden Sie ein `DeviceDiscovery`-Objekt und erstellen Sie eine `CustomImageSource`, um ein `SurfaceSource` zurückzugeben. Anschließend können Sie die Ausgabe einer `CameraKit`-Sitzung in der `Surface` rendern die von der `SurfaceSource` bereitgestellt wird.

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

# Verwenden von Ersetzen des Hintergrunds mit dem IVS Broadcast SDK
<a name="broadcast-3p-camera-filters-background-replacement"></a>

Beim Ersetzen des Hintergrunds handelt es sich um eine Art Kamerafilter, mit dem Livestream-Ersteller ihren Hintergrund ändern können. Wie im folgenden Diagramm dargestellt, umfasst das Ersetzen Ihres Hintergrunds Folgendes:

1. Abrufen eines Kamerabildes aus dem Live-Kamera-Feed.

1. Segmentierung in Vordergrund- und Hintergrundkomponenten mithilfe des Google-ML-Sets.

1. Kombinieren der resultierenden Segmentierungsmaske mit einem benutzerdefinierten Hintergrundbild.

1. Weitergabe an eine benutzerdefinierte Bildquelle zur Übertragung.

![\[Workflow für die Implementierung der Ersetzung des Hintergrunds.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Background_Replacement.png)


## Web
<a name="background-replacement-web"></a>

In diesem Abschnitt wird davon ausgegangen, dass Sie bereits mit dem [Veröffentlichen und Abonnieren von Videos mithilfe des Web-Broadcast-SDK](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-pub-sub-web.html) vertraut sind.

Um den Hintergrund eines Live-Streams durch ein benutzerdefiniertes Bild zu ersetzen, verwenden Sie das [Selfie-Segmentierungsmodell](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model) mit [MediaPipe Image Segmenter](https://developers.google.com/mediapipe/solutions/vision/image_segmenter). Hierbei handelt es sich um ein Machine Learning-Modell, das identifiziert, welche Pixel im Video-Frame im Vorder- oder Hintergrund liegen. Anschließend können Sie die Ergebnisse des Modells verwenden, um den Hintergrund eines Live-Streams zu ersetzen, indem Sie Vordergrundpixel aus dem Video-Feed in ein benutzerdefiniertes Bild kopieren, das den neuen Hintergrund darstellt.

Um die Ersetzung des Hintergrunds in das Web-Broadcast-SDK für IVS-Echtzeit-Streaming zu integrieren, müssen Sie Folgendes tun:

1. Installieren Sie MediaPipe und Webpack. (In unserem Beispiel wird Webpack als Bundler verwendet, Sie können jedoch jeden Bundler Ihrer Wahl verwenden.)

1. Erstellen von `index.html`.

1. Fügen Sie Medienelemente hinzu.

1. Fügen Sie ein Skript-Tag hinzu.

1. Erstellen von `app.js`.

1. Lädt ein benutzerdefiniertes Hintergrundbild.

1. Erstellen Sie eine Instance von `ImageSegmenter`.

1. Rendern Sie den Video-Feed in eine Bildfläche.

1. Erstellen Sie eine Logik zum Ersetzen des Hintergrunds.

1. Erstellen Sie eine Webpack-Konfigurationsdatei.

1. Bündeln Sie Ihre JavaScript-Datei.

### MediaPipe und Webpack installieren
<a name="background-replacement-web-install-mediapipe-webpack"></a>

Installieren Sie zunächst die npm-Pakete `@mediapipe/tasks-vision` und `webpack`. Das folgende Beispiel verwendet Webpack als JavaScript-Bundler. Sie können bei Bedarf einen anderen Bundler verwenden.

#### JavaScript
<a name="background-replacement-web-install-mediapipe-webpack-code"></a>

```
npm i @mediapipe/tasks-vision webpack webpack-cli
```

Stellen Sie sicher, dass Sie auch Ihr `package.json` aktualisieren, um `webpack` als Entwickler-Skript anzugeben:

#### JavaScript
<a name="background-replacement-web-update-package-json-code"></a>

```
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
```

### index.html erstellen
<a name="background-replacement-web-create-index"></a>

Erstellen Sie als Nächstes das HTML-Boilerplate und importieren Sie das Web-Broadcast-SDK als Skript-Tag. Stellen Sie im folgenden Code sicher, dass Sie `<SDK version>` durch die von Ihnen verwendete Broadcast-SDK-Version ersetzen.

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

### Medienelemente hinzufügen
<a name="background-replacement-web-add-media-elements"></a>

Fügen Sie als Nächstes ein Videoelement und zwei Bildflächen-Elemente innerhalb des Tags des Hauptteils hinzu. Das Videoelement enthält Ihren Live-Kamera-Feed und wird als Eingabe für den MediaPipe Image Segmenter verwendet. Das erste Bildflächenelement wird verwendet, um eine Vorschau des zu übertragenden Feeds zu rendern. Das zweite Bildflächenelement wird zum Rendern des benutzerdefinierten Bildes verwendet, das als Hintergrund verwendet wird. Da die zweite Bildfläche mit dem benutzerdefinierten Bild nur als Quelle zum programmgesteuerten Kopieren von Pixeln von dort auf die endgültige Bildfläche verwendet wird, ist sie nicht sichtbar.

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

### Hinzufügen eines Skript-Tags
<a name="background-replacement-web-add-script-tag"></a>

Fügen Sie ein Skript-Tag hinzu, um eine gebündelte JavaScript-Datei zu laden, die den Code für die Ersetzung des Hintergrunds enthält, und veröffentlichen Sie sie in einer Stage:

```
<script src="./dist/bundle.js"></script>
```

### Erstellen von app.js
<a name="background-replacement-web-create-appjs"></a>

Erstellen Sie als Nächstes eine JavaScript-Datei, um die Elementobjekte für die Bildfläche und Videoelemente abzurufen, die auf der HTML-Seite erstellt wurden. Importieren Sie die Module `ImageSegmenter` und `FilesetResolver`. Das Modul `ImageSegmenter` wird zur Durchführung der Segmentierungsaufgabe verwendet.

#### 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";
```

Erstellen Sie als Nächstes eine Funktion mit dem Namen `init()`, um den MediaStream von der Kamera des Benutzers abzurufen und jedes Mal eine Rückruffunktion aufzurufen, wenn ein Kamerarahmen vollständig geladen ist. Fügen Sie Ereignis-Listener für die Schaltflächen zum Beitreten und Verlassen einer Stage hinzu.

Beachten Sie, dass wir beim Beitritt zu einer Stage eine Variable mit dem Namen `segmentationStream` übergeben. Hierbei handelt es sich um einen Video-Stream, der von einem Bildflächenelement erfasst wird und ein Vordergrundbild enthält, das dem benutzerdefinierten Bild, das den Hintergrund darstellt, überlagert ist. Später wird dieser benutzerdefinierte Stream zum Erstellen einer Instance eines `LocalStageStream` verwendet, die in einer Stage veröffentlicht werden kann.

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

### Ein benutzerdefiniertes Hintergrundbild laden
<a name="background-replacement-web-background-image"></a>

Fügen Sie unten in der `init`-Funktion Code hinzu, um eine Funktion mit dem Namen `initBackgroundCanvas` aufzurufen, die ein benutzerdefiniertes Bild aus einer lokalen Datei lädt und es in einer Bildfläche rendert. Diese Funktion wird im nächsten Schritt definiert. Ordnen Sie das von der Kamera des Benutzers abgerufene `MediaStream` dem Videoobjekt zu. Später wird dieses Videoobjekt an den Image Segmenter übergeben. Legen Sie außerdem eine Funktion mit dem Namen `renderVideoToCanvas` als Rückruffunktion fest, die immer dann aufgerufen wird, wenn ein Videobild vollständig geladen wurde. Diese Funktion wird in einem späteren Schritt definiert.

#### JavaScript
<a name="background-replacement-web-load-background-image-code"></a>

```
initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
```

Wir implementieren die `initBackgroundCanvas`-Funktion, die ein Bild aus einer lokalen Datei lädt. In diesem Beispiel wird das Bild von einem Strandes als benutzerdefinierter Hintergrund verwendet. Die Bildfläche mit dem benutzerdefinierten Bild wird nicht angezeigt, da Sie sie mit den Vordergrundpixeln aus dem Bildflächenelement mit dem Kamera-Feed zusammenführen.

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

### Erstellen einer Instance von ImageSegmenter
<a name="background-replacement-web-imagesegmenter"></a>

Erstellen Sie als Nächstes eine Instance von `ImageSegmenter`, die das Bild segmentiert und das Ergebnis als Maske zurückgibt. Verwenden Sie beim Erstellen einer Instance eines `ImageSegmenter` das [Selfie-Segmentierungsmodell](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,
  });
};
```

### Rendern des Video-Feeds auf eine Bildfläche
<a name="background-replacement-web-render-video-to-canvas"></a>

Erstellen Sie als Nächstes die Funktion, die den Video-Feed auf das andere Bildflächenelement rendert. Das Video-Feed muss auf einer Bildfläche gerendert werden, damit mithilfe der Bildflächen-2D-API die Vordergrundpixel daraus extrahiert werden können. Dabei übergeben wir auch einen Videoframe an unsere `ImageSegmenter`-Instance und verwenden dabei die Methode [segmentforVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo), um den Vordergrund vom Hintergrund im Videoframe zu segmentieren. Wenn die Methode [segmentforVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) zurückgegeben wird, ruft sie die benutzerdefinierte Rückruffunktion `replaceBackground` auf, um den Hintergrund zu ersetzen.

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

### Logik zum Ersetzen des Hintergrunds erstellen
<a name="background-replacement-web-logic"></a>

Erstellen Sie die `replaceBackground`-Funktion, die das benutzerdefinierte Hintergrundbild mit dem Vordergrund aus dem Kamera-Feed zusammenführt, um den Hintergrund zu ersetzen. Die Funktion ruft zunächst die zugrunde liegenden Pixeldaten des benutzerdefinierten Hintergrundbilds und des Video-Feeds von den beiden zuvor erstellten Bildflächenelementen ab. Anschließend durchläuft es die von `ImageSegmenter` bereitgestellte Maske, die angibt, welche Pixel im Vordergrund stehen. Beim Durchlaufen der Maske kopiert es selektiv Pixel, die den Kamera-Feed des Benutzers enthalten, in die entsprechenden Hintergrund-Pixeldaten. Sobald das erledigt ist, konvertiert es die endgültigen Pixeldaten, wobei der Vordergrund in den Hintergrund kopiert wird, und auf eine Bildfläche dargestellt wird.

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

Als Referenz finden Sie hier die vollständige `app.js`-Datei, die die gesamte obige Logik enthält:

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

### Erstellen einer Webpack-Konfigurationsdatei
<a name="background-replacement-web-webpack-config"></a>

Fügen Sie diese Konfiguration zu Ihrer Webpack-Konfigurationsdatei hinzu, um `app.js` zu bündeln, damit die Importaufrufe funktionieren:

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

### Bündeln Ihrer JavaScript-Dateien
<a name="background-replacement-web-bundle-javascript"></a>

```
npm run build
```

Starten Sie einen einfachen HTTP-Server aus dem Verzeichnis, das `index.html` enthält, und öffnen Sie `localhost:8000`, um das Ergebnis anzuzeigen:

```
python3 -m http.server -d ./
```

## Android
<a name="background-replacement-android"></a>

Um den Hintergrund in Ihrem Livestream zu ersetzen, können Sie die Selfie-Segmentierungs-API von [Google ML Kit](https://developers.google.com/ml-kit/vision/selfie-segmentation) verwenden. Die Selfie-Segmentierungs-API akzeptiert ein Kamerabild als Eingabe und gibt eine Maske zurück. Diese liefert für jedes Pixel des Bildes einen Konfidenzwert, der angibt, ob es sich im Vordergrund oder im Hintergrund befand. Basierend auf dem Konfidenzwert können Sie dann die entsprechende Pixelfarbe entweder aus dem Hintergrundbild oder dem Vordergrundbild abrufen. Dieser Vorgang wird fortgesetzt, bis alle Konfidenzwerte in der Maske überprüft wurden. Das Ergebnis ist ein neues Array von Pixelfarben, das Vordergrundpixel kombiniert mit Pixeln aus dem Hintergrundbild enthält.

Um die Ersetzung im Hintergrund mit dem Android-Broadcast-SDK für ISV-Echtzeit-Streaming zu integrieren, müssen Sie wie folgt vorgehen:

1. Installieren Sie die CameraX-Bibliotheken und das Google ML-Kit.

1. Initialisieren Sie Boilerplate-Variablen.

1. Erstellen Sie eine benutzerdefinierte Bildquelle.

1. Verwalten Sie Kamerarahmen.

1. Übergeben Sie Kamerarahmen an Google ML Kit.

1. Überlagern Sie den Vordergrund des Kamerarahmens mit Ihrem benutzerdefinierten Hintergrund.

1. Führen Sie das neue Bild einer benutzerdefinierten Bildquelle zu.

### CameraX-Bibliotheken und Google ML Kit installieren
<a name="background-replacement-android-install-camerax-googleml"></a>

Verwenden Sie die CameraX-Bibliothek von Android, um Bilder aus dem Live-Kamera-Feed zu extrahieren. Um die CameraX-Bibliothek und das Google ML-Kit zu installieren, fügen Sie Folgendes zur `build.gradle`-Datei Ihres Moduls hinzu. Ersetzen Sie `${camerax_version}` und `${google_ml_kit_version}` durch die neueste Version der [CameraX](https://developer.android.com/jetpack/androidx/releases/camera)- bzw. [Google-ML-Kit-Bibliotheken](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}"
```

Importieren Sie die folgenden Bibliotheken:

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

### Initialisieren von Boilerplate-Variablen
<a name="background-replacement-android-initialize-variables"></a>

Initialisieren Sie eine Instance von `ImageAnalysis` und eine Instance eines `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
```

Initialisieren Sie eine Segmenter-Instance im [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)
```

### Erstellen einer benutzerdefinierten Image-Quelle
<a name="background-replacement-android-create-image-source"></a>

Erstellen Sie in der `onCreate`-Methode Ihrer Aktivität eine Instance eines `DeviceDiscovery`-Objekts sowie eine benutzerdefinierte Bildquelle. Die von der benutzerdefinierten Bildquelle bereitgestellte `Surface` erhält das endgültige Bild, wobei der Vordergrund über einem benutzerdefinierten Hintergrundbild liegt. Anschließend erstellen Sie mithilfe der benutzerdefinierten Bildquelle eine Instance von einem `ImageLocalStageStream`. Die Instance von einem `ImageLocalStageStream` (in diesem Beispiel `filterStream` benannt) kann dann in einer Stage veröffentlicht werden. Anweisungen zum Einrichten einer Stage finden Sie im [Handbuch des IVS-Android-Broadcast-SDK](broadcast-android.md). Erstellen Sie abschließend auch einen Thread, der zur Verwaltung der Kamera verwendet wird.

#### 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()
```

### Verwalten von Kamerarahmen
<a name="background-replacement-android-camera-frames"></a>

Erstellen Sie als Nächstes eine Funktion zum Initialisieren der Kamera. Diese Funktion nutzt die CameraX-Bibliothek, um Bilder aus dem Live-Kamera-Feed zu extrahieren. Zunächst erstellen Sie eine Instance eines `ProcessCameraProvider` mit dem Namen `cameraProviderFuture`. Dieses Objekt stellt ein zukünftiges Ergebnis der Beschaffung eines Kameraanbieters dar. Anschließend laden Sie ein Bild aus Ihrem Projekt als Bitmap. In diesem Beispiel wird ein Bild von einem Strand als Hintergrund verwendet, es kann sich aber auch um ein beliebiges Bild handeln.

Anschließend fügen Sie einen Listener zu `cameraProviderFuture` hinzu. Dieser Listener wird benachrichtigt, wenn die Kamera verfügbar wird oder wenn beim Abrufen eines Kameraanbieters ein Fehler auftritt.

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

Erstellen Sie im Listener `ImageAnalysis.Builder`, um über den Live-Kamera-Feed auf jedes einzelne Bild zuzugreifen. Legen Sie die Gegendruckstrategie auf `STRATEGY_KEEP_ONLY_LATEST` fest. Dadurch wird sichergestellt, dass jeweils nur ein Kamerarahmen zur Verarbeitung geliefert wird. Konvertieren Sie jedes einzelne Kamerarahmen in eine Bitmap, sodass Sie dessen Pixel extrahieren und später mit dem benutzerdefinierten Hintergrundbild kombinieren können.

#### 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())
```

### Übergabe von Kamerarahmen an Google ML Kit
<a name="background-replacement-android-frames-to-mlkit"></a>

Erstellen Sie als Nächstes ein `InputImage` und übergeben Sie es zur Verarbeitung an die Segmenter-Instance. Ein `InputImage` kann aus einem `ImageProxy` erstellt werden, der von der Instance von `ImageAnalysis` bereitgestellt wird. Sobald dem Segmenter ein `InputImage` zur Verfügung gestellt wird, gibt er eine Maske mit Konfidenzwerten zurück, die die Wahrscheinlichkeit angeben, dass sich ein Pixel im Vordergrund oder Hintergrund befindet. Diese Maske bietet auch Breiten- und Höheneigenschaften, mit denen Sie ein neues Array erstellen, das die Hintergrundpixel des zuvor geladenen benutzerdefinierten Hintergrundbilds enthält.

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

### Überlagern des Vordergrunds des Kamerarahmens mit Ihrem benutzerdefinierten Hintergrund
<a name="background-replacement-android-overlay-frame-foreground"></a>

Mit der Maske, die die Konfidenzwerte enthält, dem Kamerarahmen als Bitmap und den Farbpixeln aus dem benutzerdefinierten Hintergrundbild haben Sie alles, was Sie brauchen, um den Vordergrund über Ihren benutzerdefinierten Hintergrund zu legen. Die `overlayForeground`-Funktion wird dann mit folgenden Parametern aufgerufen:

#### Java
<a name="background-replacement-android-call-overlayforeground-code"></a>

```
resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
```

Diese Funktion durchläuft die Maske und prüft die Konfidenzwerte, um festzustellen, ob die entsprechende Pixelfarbe aus dem Hintergrundbild oder dem Kamerarahmen abgerufen werden soll. Wenn der Konfidenzwert angibt, dass sich ein Pixel in der Maske höchstwahrscheinlich im Hintergrund befindet, erhält er die entsprechende Pixelfarbe aus dem Hintergrundbild. Andernfalls wird die entsprechende Pixelfarbe vom Kamerarahmen abgerufen, um den Vordergrund zu erstellen. Sobald die Funktion die Iteration durch die Maske abgeschlossen hat, wird unter Verwendung des neuen Arrays von Farbpixeln eine neue Bitmap erstellt und zurückgegeben. Diese neue Bitmap enthält den Vordergrund, der sich mit dem benutzerdefinierten Hintergrund überlagert.

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

### Das neue Bild einer benutzerdefinierten Bildquelle zuführen
<a name="background-replacement-android-custom-image-source"></a>

Anschließend können Sie die neue Bitmap in die `Surface` schreiben, das von einer benutzerdefinierten Bildquelle bereitgestellt wird. Dadurch wird es in Ihre Stage übertragen.

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

Hier ist die vollständige Funktion zum Abrufen der Kamerarahmen, zum Übergeben an Segmenter und zum Überlagern mit dem Hintergrund:

#### 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: Mobile Audiomodi \$1 Echtzeit-Streaming
<a name="broadcast-mobile-audio-modes"></a>

Die Audioqualität ist ein wichtiger Bestandteil jedes Medienerlebnisses im echten Team, und es gibt keine einheitliche Audiokonfiguration, die für jeden Anwendungsfall am besten funktioniert. Um sicherzustellen, dass Ihre Benutzer beim Anhören eines IVS-Echtzeit-Streams über das beste Erlebnis verfügen, bieten unsere mobilen SDKs mehrere voreingestellte Audiokonfigurationen sowie bei Bedarf leistungsstärkere Anpassungen.

## Einführung
<a name="broadcast-mobile-audio-modes-introduction"></a>

Die IVS-SDKs für mobile Übertragungen stellen eine `StageAudioManager`-Klasse bereit. Diese Klasse ist als zentraler Ansprechpartner für die Steuerung der zugrunde liegenden Audiomodi auf beiden Plattformen konzipiert. Unter Android steuert dies den [AudioManager](https://developer.android.com/reference/android/media/AudioManager), einschließlich Audiomodus, Audioquelle, Inhaltstyp, Nutzung und Kommunikationsgeräte. Unter iOS steuert es die Anwendung [AVAudioSession](https://developer.apple.com/documentation/avfaudio/avaudiosession) und ob [VoiceProcessing](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) aktiviert ist.

**Wichtig**: Interagieren Sie nicht mit `AVAudioSession` oder `AudioManager` direkt, während das IVS-Echtzeit-Broadcast-SDK aktiv ist. Dies kann dazu führen, dass das Audio verloren geht oder Audio vom falschen Gerät aufgenommen oder wiedergegeben wird.

Bevor Sie Ihr erstes `DeviceDiscovery`- oder `Stage`-Objekt erstellen, muss die `StageAudioManager`-Klasse konfiguriert werden.

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

------

Wenn nichts auf `StageAudioManager` festgelegt wird, bevor eine `DeviceDiscovery`- oder `Stage`-Instance initialisiert wird, wird automatisch die Voreinstellung `VideoChat` angewendet.

## Voreinstellungen für den Audio-Modus
<a name="broadcast-mobile-audio-modes-presets"></a>

Das Echtzeit-Broadcast-SDK bietet drei Voreinstellungen, die jeweils auf gängige Anwendungsfälle zugeschnitten sind, wie unten beschrieben. Für jede Voreinstellung werden fünf Hauptkategorien behandelt, die die Voreinstellungen voneinander unterscheiden.

Die Kategorie **Lautstärkeregler** bezieht sich auf die Art der Lautstärke (Medienlautstärke oder Anruflautstärke), die über die physischen Lautstärkeregler am Gerät verwendet oder geändert wird. Beachten Sie, dass dies Auswirkungen auf die Lautstärke hat, wenn Sie den Audio-Modus wechseln. Beispiel: Angenommen, die Lautstärke des Geräts ist auf den maximalen Wert eingestellt, während Sie die Voreinstellung „Videochat“ verwenden. Das Umschalten auf die Voreinstellung „Nur Abonnieren“ bewirkt eine andere Lautstärke als die des Betriebssystems, was zu einer erheblichen Änderung der Lautstärke auf dem Gerät führen kann.

### Video-Chat
<a name="audio-modes-presets-video-chat"></a>

Dies ist die Standardvoreinstellung, die für den Fall konzipiert ist, dass das lokale Gerät ein Echtzeitgespräch mit anderen Teilnehmern führen soll.

**Bekanntes Problem unter iOS**: Wenn Sie diese Voreinstellung verwenden und kein Mikrofon anschließen, wird der Ton über den Ohrhörer und nicht über den Gerätelautsprecher wiedergegeben. Verwenden Sie diese Voreinstellung nur in Kombination mit einem Mikrofon.


| Kategorie | Android | iOS | 
| --- | --- | --- | 
| Echounterdrückung | Aktiviert | Aktiviert | 
| Lautstärkenregler | Anruflautstärke | Anruflautstärke | 
| Auswahl des Mikrofons | Je nach Betriebssystem begrenzt. USB-Mikrofone sind möglicherweise nicht verfügbar. | Je nach Betriebssystem begrenzt. USB- und Bluetooth-Mikrofone sind möglicherweise nicht verfügbar. Bluetooth-Headsets, die sowohl die Ein- als auch die Ausgabe gemeinsam verarbeiten, sollten funktionieren; z. B. AirPods. | 
| Audioausgabe | Jedes Ausgabegerät sollte funktionieren. | Je nach Betriebssystem begrenzt. Kabelgebundene Headsets sind möglicherweise nicht verfügbar. | 
| Audioqualität | Mittel/Niedrig. Es hört sich wie ein Telefonanruf an, nicht wie die Medienwiedergabe. | Mittel/Niedrig. Es hört sich wie ein Telefonanruf an, nicht wie die Medienwiedergabe. | 

### Nur Abonnement
<a name="audio-modes-presets-subscribe-only"></a>

Diese Voreinstellung ist für den Fall konzipiert, dass Sie andere Veröffentlichungsteilnehmer abonnieren, aber nicht selbst veröffentlichen möchten. Der Schwerpunkt liegt auf der Audioqualität und der Unterstützung aller verfügbaren Ausgabegeräte.


| Kategorie | Android | iOS | 
| --- | --- | --- | 
| Echounterdrückung | Disabled | Disabled | 
| Lautstärkenregler | Medienlautstärke | Medienlautstärke | 
| Auswahl des Mikrofons | Nicht verfügbar, diese Voreinstellung ist nicht für die Veröffentlichung konzipiert. | Nicht verfügbar, diese Voreinstellung ist nicht für die Veröffentlichung konzipiert. | 
| Audioausgabe | Jedes Ausgabegerät sollte funktionieren. | Jedes Ausgabegerät sollte funktionieren. | 
| Audioqualität | Hoch. Jeder Medientyp sollte klar zu hören sein, auch Musik. | Hoch. Jeder Medientyp sollte klar zu hören sein, auch Musik. | 

### Studio
<a name="audio-modes-presets-studio"></a>

Diese Voreinstellung ist für qualitativ hochwertige Abonnements bei gleichzeitiger Beibehaltung der Möglichkeit zur Veröffentlichung konzipiert. Es erfordert, dass die Aufnahme- und Wiedergabe-Hardware eine Echounterdrückung ermöglicht. Ein Anwendungsfall hierfür wäre die Verwendung eines USB-Mikrofons und eines kabelgebundenen Headsets. Das SDK sorgt für die höchste Audioqualität und verlässt sich dabei auf die physische Trennung dieser Geräte, um Echos zu vermeiden.


| Kategorie | Android | iOS | 
| --- | --- | --- | 
| Echounterdrückung | Die Plattform-Echounterdrückung ist deaktiviert, doch wenn `StageAudioConfiguration.enableEchoCancellation` wahr ist, kann die Software-Echounterdrückung trotzdem erfolgen. | Disabled | 
| Lautstärkenregler | Medienlautstärke in den meisten Fällen. Anruflautstärke, wenn ein Bluetooth-Mikrofon angeschlossen ist.  | Medienlautstärke | 
| Auswahl des Mikrofons | Jedes Mikrofon sollte funktionieren. | Jedes Mikrofon sollte funktionieren. | 
| Audioausgabe | Jedes Ausgabegerät sollte funktionieren. | Jedes Ausgabegerät sollte funktionieren. | 
| Audioqualität | Hoch. Beide Seiten sollten in der Lage sein, Musik zu senden und sie auf der anderen Seite klar zu hören. Wenn ein Bluetooth-Headset angeschlossen ist, nimmt die Audioqualität ab, da der Bluetooth-SCO-Modus aktiviert ist. | Hoch. Beide Seiten sollten in der Lage sein, Musik zu senden und sie auf der anderen Seite klar zu hören. Wenn ein Bluetooth-Headset angeschlossen ist, kann es je nach Headset aufgrund der Aktivierung des Bluetooth SCO-Modus zu einer Verschlechterung der Audioqualität kommen.  | 

## Fortschrittliche Anwendungsfälle
<a name="broadcast-mobile-audio-modes-advanced-use-cases"></a>

Über die Voreinstellungen hinaus ermöglichen sowohl die Echtzeit-Streaming-Broadcast-SDKs für iOS als auch Android die Konfiguration der zugrunde liegenden Audiomodi der Plattform:
+ Legen Sie unter Android [AudioSource](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource), [Usage](https://developer.android.com/reference/android/media/AudioAttributes#USAGE_ALARM) und [ContentType](https://developer.android.com/reference/android/media/AudioAttributes#CONTENT_TYPE_MOVIE) fest.
+ Verwenden Sie unter 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) und die Möglichkeit, die [Sprachverarbeitung](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) während der Veröffentlichung zu aktivieren oder zu deaktivieren.

Hinweis: Bei Verwendung dieser Audio-SDK-Methoden ist es möglich, dass die zugrunde liegende Audiositzung falsch konfiguriert wird. Wenn Sie z. B. die Option `.allowBluetooth` unter iOS in Kombination mit der Kategorie `.playback` verwenden, entsteht eine ungültige Audiokonfiguration und das SDK kann kein Audio aufnehmen oder wiedergeben. Diese Methoden sollten nur verwendet werden, wenn eine Anwendung spezifische Anforderungen an eine Audiositzung hat, die validiert wurden.

------
#### [ 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-Echounterdrückung
<a name="advanced-use-cases-ios_echo_cancellation"></a>

Die Echounterdrückung unter iOS kann auch unabhängig über `IVSStageAudioManager` mit der zugehörigen Methode `echoCancellationEnabled` gesteuert werden. Diese Methode steuert, ob die [Sprachverarbeitung](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) auf den Ein- und Ausgangsknoten des vom SDK verwendeten zugrunde liegenden `AVAudioEngine` aktiviert ist. Es ist wichtig, die Auswirkungen einer manuellen Änderung dieser Eigenschaft zu verstehen:
+ Die `AVAudioEngine`-Eigenschaft wird nur berücksichtigt, wenn das Mikrofon des SDK aktiv ist. Dies ist aufgrund der iOS-Anforderung erforderlich, dass die Sprachverarbeitung auf den Ein- und Ausgangsknoten gleichzeitig aktiviert sein muss. Normalerweise erfolgt dies mithilfe des Mikrofons, das von `IVSDeviceDiscovery` zurückgegeben wurde, um einen zu veröffentlichenden `IVSLocalStageStream` zu erstellen. Alternativ kann das Mikrofon aktiviert werden, ohne dass es für die Veröffentlichung verwendet wird, indem ein `IVSAudioDeviceStatsCallback` an das Mikrofon selbst angehängt wird. Dieser alternative Ansatz ist nützlich, wenn Sie Echounterdrückung benötigen und statt des Mikrofons des IVS-SDK ein benutzerdefiniertes Mikrofon mit Audioquelle verwenden.
+ Zum Aktivieren der `AVAudioEngine`-Eigenschaft ist der Modus `.videoChat` oder `.voiceChat` erforderlich. Das Anfordern eines anderen Modus führt dazu, dass das zugrunde liegende Audio-Framework von iOS das SDK bekämpft, was zu Audioverlust führt.
+ Durch die Aktivierung von `AVAudioEngine` wird die Option `.allowBluetooth ` automatisch aktiviert.

Das Verhalten kann je nach Gerät und iOS-Version unterschiedlich sein.

### Benutzerdefinierte iOS-Audioquellen
<a name="advanced-use-cases-ios_custom_audio_sources"></a>

Benutzerdefinierte Audioquellen können mit dem SDK mithilfe von `IVSDeviceDiscovery.createAudioSource` verwendet werden. Bei der Verbindung mit einer Bühne verwaltet das IVS-Broadcast-SDK für Echtzeit-Streaming auch dann eine interne `AVAudioEngine`-Instance für die Audiowiedergabe, wenn das Mikrofon des SDK nicht verwendet wird. Daher müssen die an `IVSStageAudioManager` übergebenen Werte mit dem von der benutzerdefinierten Audioquelle bereitgestellten Audio kompatibel sein.

Wenn die benutzerdefinierte Audioquelle, die für die Veröffentlichung verwendet wird, zwar über das Mikrofon aufzeichnet, aber von der Hostanwendung verwaltet wird, funktioniert das oben genannte SDK zur Echounterdrückung nur, wenn das vom SDK verwaltete Mikrofon aktiviert ist. Informationen zur Umgehung dieser Anforderung finden Sie unter [iOS-Echounterdrückung](#advanced-use-cases-ios_echo_cancellation).

### Veröffentlichen mit Bluetooth unter Android
<a name="advanced-use-cases-bluetooth-android"></a>

Das SDK kehrt automatisch zur Voreinstellung `VIDEO_CHAT` unter Android zurück, wenn die folgenden Bedingungen erfüllt sind:
+ Die zugewiesene Konfiguration verwendet den Nutzungswert `VOICE_COMMUNICATION` nicht.
+ Ein Bluetooth-Mikrofon ist mit dem Gerät verbunden.
+ Der lokale Teilnehmer veröffentlicht in einer Stufe.

Hierbei handelt es sich um eine Einschränkung des Android-Betriebssystems hinsichtlich der Verwendung von Bluetooth-Headsets zur Audioaufzeichnung.

## Integration mit anderen SDKs
<a name="broadcast-mobile-audio-modes-integrating-other-sdks"></a>

Da sowohl iOS als auch Android nur einen aktiven Audiomodus pro Anwendung unterstützen, kommt es häufig zu Konflikten, wenn Ihre Anwendung mehrere SDKs verwendet, die die Steuerung des Audiomodus erfordern. Wenn Sie auf solche Konflikte stoßen, können Sie einige gängige Lösungsstrategien ausprobieren, die nachfolgend erläutert werden.

### Werte im Audiomodus anpassen
<a name="integrating-other-sdks-match-values"></a>

Mithilfe der erweiterten Audiokonfigurationsoptionen des IVS-SDK oder der Funktionalität des anderen SDK können Sie die beiden SDKs auf die zugrunde liegenden Werte ausrichten.

### Agora
<a name="integrating-other-sdks-agora"></a>

#### iOS
<a name="integrating-other-sdks-agora-ios"></a>

Wenn Sie unter iOS dem Agora SDK mitteilen, dass die `AVAudioSession` aktiv bleiben soll, wird verhindert, dass es deaktiviert wird, während das Broadcast-SDK für IVS-Echtzeit-Streaming es verwendet.

```
myRtcEngine.SetParameters("{\"che.audio.keep.audiosession\":true}");
```

#### Android
<a name="integrating-other-sdks-agora-android"></a>

Vermeiden Sie den Aufruf von `setEnableSpeakerphone` in `RtcEngine` und rufen Sie `enableLocalAudio(false)` während der Veröffentlichung mit dem Broadcast-SDK für IVS-Echtzeit-Streaming auf. Sie können `enableLocalAudio(true)` erneut aufrufen, wenn das IVS-SDK keine Veröffentlichung durchführt.