

# Erste Schritte mit IVS-Echtzeit-Streaming
<a name="getting-started"></a>

Dieses Dokument führt Sie durch die Schritte zur Integration von Amazon-IVS-Echtzeit-Streaming in Ihre Anwendung.

**Topics**
+ [Einführung in IVS-Streaming in Echtzeit](getting-started-introduction.md)
+ [Schritt 1: IAM-Berechtigungen einrichten](getting-started-iam-permissions.md)
+ [Schritt 2: Erstellen einer Stage mit optionaler Teilnehmeraufzeichnung](getting-started-create-stage.md)
+ [Schritt 3: Teilnehmertoken verteilen](getting-started-distribute-tokens.md)
+ [Schritt 4: Das IVS Broadcast SDK integrieren](getting-started-broadcast-sdk.md)
+ [Schritt 5: Video veröffentlichen und abonnieren](getting-started-pub-sub.md)

# Einführung in IVS-Streaming in Echtzeit
<a name="getting-started-introduction"></a>

In diesem Abschnitt werden die Voraussetzungen für die Verwendung von Streaming in Echtzeit aufgeführt und wichtige Begriffe vorgestellt.

## Voraussetzungen
<a name="getting-started-introduction-prereq"></a>

Bevor Sie Echtzeit-Streaming zum ersten Mal verwenden können, müssen Sie die folgenden Aufgaben erledigen. Anleitungen finden Sie unter [Erste Schritte mit IVS-Streaming mit niedriger Latenz](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/getting-started.html).
+ Erstellen eines AWS-Kontos
+ Richten Sie Root-Benutzer und Administratoren ein.

## Andere Referenzen:
<a name="getting-started-introduction-extref"></a>
+ [Referenz zum IVS-Web-Broadcast-SDK](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference)
+ [Referenz zum IVS-Android-Broadcast-SDK](https://aws.github.io/amazon-ivs-broadcast-docs/latest/android/)
+ [Referenz zum IVS-iOS-Broadcast-SDK](https://aws.github.io/amazon-ivs-broadcast-docs/latest/ios/)
+ [Referenz zur API von IVS-Echtzeit-Streaming](https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/Welcome.html)

## Terminologie für Echtzeit-Streaming
<a name="getting-started-introduction-terminology"></a>


| Begriff | Beschreibung | 
| --- | --- | 
| Stage | Ein virtueller Raum, in dem die Teilnehmer Videos in Echtzeit austauschen können. | 
| Host | Ein Teilnehmer, der ein lokales Video auf die Stage sendet. | 
| Zuschauer | Ein Teilnehmer, der ein Video der Hosts erhält. | 
| Teilnehmer | Ein Benutzer, der als Host oder Zuschauer mit der Stage verbunden ist. | 
| Teilnehmer-Token | Ein Token, das einen Teilnehmer authentifiziert, wenn er einer Stage beitritt. | 
| Broadcast-SDK | Eine Clientbibliothek, die es den Teilnehmern ermöglicht, Videos zu senden und zu empfangen. | 

## Übersicht über die Schritte
<a name="getting-started-introduction-steps"></a>

1. [IAM-Berechtigungen einrichten](getting-started-iam-permissions.md) – Erstellen Sie eine AWS-Richtlinie für Identity and Access Management (IAM), die Benutzern grundlegende Berechtigungen gewährt, und weisen Sie diese Richtlinie Benutzern zu.

1. [Schaffen Sie eine Stage](getting-started-create-stage.md) – Schaffen Sie eine virtuelle Umgebung, in der die Teilnehmer Videos in Echtzeit austauschen können.

1. [Verteilen Sie Teilnehmer-Token](getting-started-distribute-tokens.md) – Senden Sie Tokens an die Teilnehmer, damit sie Ihrer Stage beitreten können.

1. [Integrieren Sie das IVS-Broadcast-SDK](getting-started-broadcast-sdk.md) – Fügen Sie das Broadcast-SDK zu Ihrer Anwendung hinzu, damit die Teilnehmer Videos senden und empfangen können: [Web](getting-started-broadcast-sdk.md#getting-started-broadcast-sdk-web), [Android](getting-started-broadcast-sdk.md#getting-started-broadcast-sdk-android) und [iOS](getting-started-broadcast-sdk.md#getting-started-broadcast-sdk-ios).

1. [Video veröffentlichen und abonnieren](getting-started-pub-sub.md) – Senden Sie Ihr Video an die Stage und erhalten Sie Videos von anderen Hosts: [IVS-Konsole](getting-started-pub-sub.md#getting-started-pub-sub-console), [Mit dem IVS Web Broadcast SDK veröffentlichen und abonnieren](getting-started-pub-sub-web.md), [Mit dem IVS Android Broadcast SDK veröffentlichen und abonnieren](getting-started-pub-sub-android.md) uand [Mit dem IVS iOS Broadcast SDK veröffentlichen und abonnieren](getting-started-pub-sub-ios.md).

# Schritt 1: IAM-Berechtigungen einrichten
<a name="getting-started-iam-permissions"></a>

Als Nächstes müssen Sie eine AWS Identity and Access Management (IAM)-Richtlinie erstellen, die Benutzern einen grundlegenden Satz an Berechtigungen gewährt (z. B. zum Erstellen einer Amazon IVS-Stage und zum Erstellen von Teilnehmer-Tokens) und diese Richtlinie den Benutzern zuweisen. Die Berechtigungen können Sie beim Erstellen eines [neuen Benutzers](#iam-permissions-new-user) zuweisen oder einem [vorhandenen Benutzer](#iam-permissions-existing-user) hinzufügen. Im Folgenden sind beide Verfahren angegeben.

Weitere Informationen (z. B. Informationen zu IAM-Benutzern und -Richtlinien, zum Anhängen einer Richtlinie an einen Benutzer und zum Beschränken der Möglichkeiten von Benutzern mit Amazon IVS) finden Sie unter:
+ [Erstellen eines IAM-Benutzers](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html#Using_CreateUser_console) im *IAM-Benutzerhandbuch*
+ Die Informationen in [Amazon-IVS-Sicherheit](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/security.html) zu IAM und „Verwaltete Richtlinien für IVS“. 
+ Die IAM-Informationen in [Amazon-IVS-Sicherheit](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/security.html)

Sie können entweder eine vorhandene von AWS verwaltete Richtlinie für Amazon IVS verwenden oder eine neue Richtlinie erstellen, die die Berechtigungen anpasst, die Sie einer Reihe von Benutzern, Gruppen oder Rollen gewähren möchten. Beide Vorgehensweisen werden nachfolgend beschrieben.

## Verwenden einer vorhandenen Richtlinie für IVS-Berechtigungen
<a name="iam-permissions-existing-policy"></a>

In den meisten Fällen möchten Sie für Amazon IVS eine von AWS verwaltete Richtlinie verwenden. Diese werden ausführlich im Abschnitt [Verwaltete Richtlinien für IVS](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/security-iam-awsmanpol.html) von *IVS-Sicherheit* beschrieben.
+ Mit der von AWS verwalteten Richtlinie `IVSReadOnlyAccess` gewähren Sie den Anwendungsentwicklern Zugriff auf alle Get- und List-API-Vorgänge von IVS (sowohl für Streaming mit niedriger Latenz als auch für Echtzeit-Streaming).
+ Mit der von AWS verwalteten Richtlinie `IVSFullAccess` gewähren Sie den Anwendungsentwicklern Zugriff auf alle API-Vorgänge von IVS (sowohl für Streaming mit niedriger Latenz als auch für Echtzeit-Streaming).

## Optional: Eine benutzerdefinierte Richtlinie für Amazon-IVS-Berechtigungen erstellen
<a name="iam-permissions-new-policy"></a>

Dazu gehen Sie wie folgt vor:

1. Melden Sie sich bei der AWS-Managementkonsole an und öffnen Sie die IAM-Konsole unter [https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/).

1. Wählen Sie im Navigationsbereich **Policies** und dann **Create policy**. Das Fenster **Berechtigungen angeben** wird geöffnet.

1. Wählen Sie im Fenster **Berechtigungen angeben** die Registerkarte **JSON**. Kopieren Sie die folgende IVS-Richtlinie und fügen Sie sie in den Textbereich **Richtlinien-Editor** ein. (Die Richtlinie umfasst nicht alle Aktionen von Amazon IVS. Je nach Bedarf können Sie Zugriffsberechtigungen für Vorgänge hinzufügen/löschen (Zulassen/Verweigern). Einzelheiten zu den IVS-Vorgängen finden Sie in der [Referenz zur API von IVS-Echtzeit-Streaming](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/Welcome.html).)

------
#### [ JSON ]

****  

   ```
   {
      "Version":"2012-10-17",		 	 	 
      "Statement": [
         {
            "Effect": "Allow",
            "Action": [
               "ivs:CreateStage",
               "ivs:CreateParticipantToken",
               "ivs:GetStage",
               "ivs:GetStageSession",
               "ivs:ListStages",
               "ivs:ListStageSessions",
               "ivs:CreateEncoderConfiguration",
               "ivs:GetEncoderConfiguration",
               "ivs:ListEncoderConfigurations",
               "ivs:GetComposition",
               "ivs:ListCompositions",
               "ivs:StartComposition",
               "ivs:StopComposition"
             ],
             "Resource": "*"
         },
         {
            "Effect": "Allow",
            "Action": [
               "cloudwatch:DescribeAlarms",
               "cloudwatch:GetMetricData",
               "s3:DeleteBucketPolicy",
               "s3:GetBucketLocation",
               "s3:GetBucketPolicy",
               "s3:PutBucketPolicy",
               "servicequotas:ListAWSDefaultServiceQuotas",
               "servicequotas:ListRequestedServiceQuotaChangeHistoryByQuota",
               "servicequotas:ListServiceQuotas",
               "servicequotas:ListServices",
               "servicequotas:ListTagsForResource"
            ],
            "Resource": "*"
         }
      ]
   }
   ```

------

1. Wählen Sie im Fenster **Berechtigungen angeben** die Option **Weiter** aus (scrollen Sie zum unteren Ende des Fensters, um diese Option anzuzeigen). Das Fenster **Überprüfen und erstellen** wird geöffnet. 

1. Geben Sie im Fenster **Überprüfen und erstellen** einen **Richtliniennamen** ein und fügen Sie optional eine **Beschreibung** hinzu. Notieren Sie sich den Namen der Richtlinie, da Sie ihn beim Erstellen von Benutzern (weiter unten) benötigen. Wählen Sie **Create policy** (Richtlinie erstellen) aus (am unteren Ende des Fensters).

1. Sie gelangen zurück zum IAM-Konsolenfenster, in dem per Banner bestätigt werden sollte, dass die neue Richtlinie erstellt wurde.

## Erstellen Sie einen neuen Benutzer und fügen Sie Berechtigungen hinzu
<a name="iam-permissions-new-user"></a>

### IAM-Benutzer-Zugriffsschlüssel
<a name="iam-permissions-new-user-access-keys"></a>

IAM-Zugriffsschlüssel bestehen aus einer Zugriffsschlüssel-ID und einem geheimen Zugriffsschlüssel. Sie dienen zum Signieren Ihrer programmgesteuerten Anforderungen an AWS. Wenn Sie noch keine Zugriffsschlüssel besitzen, können Sie diese über die AWS-Managementkonsole erstellen. Verwenden Sie als bewährte Methode keine Zugriffsschlüssel für Root-Benutzer.

*Einen geheimen Zugriffsschlüssel können Sie nur beim Erstellen von Zugriffsschlüsseln anzeigen oder herunterladen. Später kann er nicht mehr wiederhergestellt werden.* Sie können jedoch jederzeit neue Zugriffsschlüssel erstellen. Dazu benötigen Sie die Berechtigungen zum Ausführen der erforderlichen IAM-Aktionen.

Bewahren Sie Zugriffsschlüssel stets sicher auf. Geben Sie sie niemals an Dritte weiter (selbst wenn eine Anfrage von Amazon zu stammen scheint). Weitere Informationen finden Sie unter [Verwalten der Zugriffsschlüssel für IAM-Benutzer](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) im *IAM-Benutzerhandbuch*.

### Verfahren
<a name="iam-permissions-new-user-procedure"></a>

Dazu gehen Sie wie folgt vor:

1. Wählen Sie im Navigationsbereich die Option **Benutzer** und dann **Benutzer erstellen**. Das Fenster **Benutzerdetails angeben** wird geöffnet. 

1. Gehen Sie im Fenster **Benutzerdetails angeben** wie folgt vor:

   1. Geben Sie unter **Benutzerdetails** den neuen **Benutzernamen** ein, der erstellt werden soll.

   1. Aktivieren Sie **Benutzerzugriff auf AWS-Managementkonsole bereitstellen**.

   1. Wählen Sie unter **Console password (Konsolenpasswort)** **Autogenerated password (Automatisch generiertes Passwort)**.

   1. Aktivieren Sie **Benutzer muss bei der nächsten Anmeldung ein neues Passwort erstellen**.

   1. Wählen Sie **Weiter** aus. Das Fenster **Berechtigungen festlegen** wird geöffnet.

1. Wählen Sie unter **Berechtigungen festlegen** die Option **Richtlinien direkt zuweisen** aus. Das Fenster **Berechtigungsrichtlinien** wird geöffnet.

1. Geben Sie im Suchfeld einen IVS-Richtliniennamen ein (entweder eine von AWS verwaltete Richtlinie oder Ihre zuvor erstellte benutzerdefinierte Richtlinie). Wenn sie gefunden wurde, aktivieren Sie das Kästchen, um die Richtlinie auszuwählen.

1. Wählen Sie **Weiter** (unten im Fenster). Das Fenster **Überprüfen und erstellen** wird geöffnet.

1. Vergewissern Sie sich im Fenster **Überprüfen und erstellen**, ob alle Benutzerdetails korrekt sind, und wählen Sie dann **Benutzer erstellen** aus (unten im Fenster).

1. Das Fenster **Passwort abrufen** wird geöffnet, das Ihre **Details zur Anmeldung an der Konsole** enthält. *Bewahren Sie diese Informationen sicher auf, damit Sie in Zukunft darauf zurückgreifen können*. Klicken Sie abschließend auf **Zurück zur Benutzerliste**.

## Hinzufügen von Berechtigungen zu einem vorhandenen Benutzer
<a name="iam-permissions-existing-user"></a>

Dazu gehen Sie wie folgt vor:

1. Melden Sie sich bei der AWS-Managementkonsole an und öffnen Sie die IAM-Konsole unter [https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/).

1. Wählen Sie im Navigationsbereich **Benutzer** aus und wählen Sie dann einen vorhandenen Benutzernamen aus, der aktualisiert werden soll. (Wählen Sie den Namen aus, indem Sie darauf klicken. Aktivieren Sie nicht das Auswahlfeld.)

1. Wählen Sie auf der Seite **Zusammenfassung** in der Registerkarte **Berechtigungen** die Option **Berechtigungen hinzufügen** aus. Das Fenster **Berechtigungen hinzufügen** wird geöffnet.

1. Wählen Sie die Option **Attach existing policies directly (Vorhandene Richtlinien direkt anfügen)** aus. Das Fenster **Berechtigungsrichtlinien** wird geöffnet.

1. Geben Sie im Suchfeld einen IVS-Richtliniennamen ein (entweder eine von AWS verwaltete Richtlinie oder Ihre zuvor erstellte benutzerdefinierte Richtlinie). Wenn die Richtlinie gefunden wurde, aktivieren Sie das Kästchen, um die Richtlinie auszuwählen.

1. Wählen Sie **Weiter** (unten im Fenster). Das Fenster **Überprüfung** wird geöffnet.

1. Wählen Sie im Fenster **Überprüfung** die Option **Berechtigungen hinzufügen** aus (unten im Fenster).

1. Vergewissern Sie sich auf der Seite **Summary** (Zusammenfassung), dass die IVS-Richtlinie hinzugefügt wurde.

# Schritt 2: Erstellen einer Stage mit optionaler Teilnehmeraufzeichnung
<a name="getting-started-create-stage"></a>

Bei einer Stage handelt es sich um eine virtuellen Umgebung, in der die Teilnehmer Audio und Video in Echtzeit austauschen können. Sie ist die grundlegende Ressource der Echtzeit-Streaming-API. Sie können eine Stage entweder über die Konsole oder den Vorgang CreateStage erstellen.

Wir empfehlen, dass Sie nach Möglichkeit für jede logische Sitzung eine neue Stage erstellen und diese löschen, wenn Sie fertig sind, anstatt alte Stages für eine mögliche Wiederverwendung beizubehalten. Wenn veraltete Ressourcen (alte Stages, die nicht wiederverwendet werden sollen) nicht bereinigt werden, erreichen Sie wahrscheinlich schneller das Limit der maximalen Anzahl an Stages.

Stage – mit oder ohne Aufzeichnung einzelner Teilnehmer – können Sie über die Amazon-IVS-Konsole oder die AWS-CLI erstellen. Die Erstellung und Aufzeichnung von Stage werden nachfolgend erörtert.

## Aufzeichnung einzelner Teilnehmer
<a name="getting-started-create-stage-ipr-overview"></a>

Sie haben die Möglichkeit, die Aufzeichnung einzelner Teilnehmer für eine Stage zu aktivieren. Wenn das Feature zur Aufzeichnung einzelner Teilnehmer in S3 aktiviert ist, werden alle Broadcasts einzelner Teilnehmer an die Stage aufgezeichnet und in einem Ihnen gehörenden Amazon-S3-Speicher-Bucket gespeichert. Anschließend ist die Aufnahme für die On-Demand-Wiedergabe verfügbar.

*Das Einrichten hiervon ist eine erweiterte Option.* Beim Erstellen einer Stage ist die Aufzeichnung standardmäßig deaktiviert.

Ehe Sie eine Stage für die Aufzeichnung einrichten können, müssen Sie eine *Speicherkonfiguration* erstellen. Dabei handelt es sich um eine Ressource, die einen Amazon-S3-Speicherort angibt, an dem die aufgezeichneten Streams für die Stage gespeichert werden. Speicherkonfiguration können Sie über die Konsole oder CLI erstellen und verwalten. Beide Verfahren sind unten aufgeführt. Nachdem Sie die Speicherkonfiguration erstellt haben, ordnen Sie sie einer Stage zu, entweder beim Erstellen der Stage (wie unten beschrieben) oder später durch Aktualisierung einer vorhandenen Stage. (Siehe [CreateStage](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/API_CreateStage.html) und [UpdateStage](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/API_UpdateStage.html) in der API.) Sie können mehrere Stages derselben Speicherkonfiguration zuordnen. Speicherkonfigurationen, die keiner Stage mehr zugeordnet sind, können gelöscht werden.

Beachten Sie folgende Einschränkungen:
+ Sie müssen Eigentümer des S3-Buckets sein. Das heißt, der S3-Bucket, in dem die Aufzeichnungen gespeichert werden, muss dem Konto gehören, über das eine aufzuzeichnende Stage eingerichtet wird.
+ Die Stage, die Speicherkonfiguration und der S3-Speicher-Bucket müssen sich in derselben AWS-Region befinden. Wenn Sie Stages in anderen Regionen erstellen und diese aufzeichnen möchten, müssen Sie in diesen Regionen ebenfalls Speicherkonfigurationen und S3-Buckets einrichten.

Die Aufnahme in Ihrem S3-Bucket erfordert eine Autorisierung mit Ihren AWS-Anmeldeinformationen. Um IVS den erforderlichen Zugriff zu gewähren, wird beim Erstellen der Aufzeichnungskonfiguration automatisch eine [serviceverknüpfte Rolle](https://docs.aws.amazon.com/IAM/latest/UserGuide/using-service-linked-roles.html) von AWS IAM erstellt. Diese Rolle erteilt IVS nur die Schreibberechtigung für den jeweiligen Bucket.

Beachten Sie, dass Netzwerkprobleme zwischen dem Streaming-Standort und AWS oder in AWS zu Datenverlusten bei der Aufzeichnung von Streams führen können. In diesen Fällen priorisiert Amazon IVS den Livestream gegenüber der Aufzeichnung. Für Redundanz nehmen Sie lokal über Ihr Streaming-Tool auf.

Weitere Informationen (darunter zum Einrichten der Nachbearbeitung oder der VOD-Wiedergabe für aufgezeichnete Dateien) finden Sie unter [Aufzeichnung einzelner Teilnehmer](rt-individual-participant-recording.md).

### Aufnahme deaktivieren
<a name="getting-started-disable-recording"></a>

So deaktivieren Sie die Amazon-S3-Aufzeichnung für eine vorhandene Stage:
+ Konsole – Deaktivieren Sie auf der Detailseite der entsprechenden Stage im Streams-Abschnitt **Einzelne Teilnehmer aufzeichnen** unter **Automatisch in S3 aufzeichnen** die Option **Automatische Aufzeichnung aktivieren**. Wählen Sie dann **Änderungen speichern**. Dadurch wird die Zuordnung der Speicherkonfiguration zur Stage entfernt. Streams der betreffenden Stage werden dann nicht mehr aufgezeichnet.
+ CLI – Führen Sie den Befehl `update-stage` aus und übergeben Sie den ARN für die Aufzeichnungskonfiguration als leere Zeichenfolge:

  ```
  aws ivs-realtime update-stage --arn arn:aws:ivs:us-west-2:123456789012:stage/abcdABCDefgh --auto-participant-recording-configuration storageConfigurationArn=""
  ```

  Hierdurch wird ein Stageobjekt mit einer leeren Zeichenfolge für `storageConfigurationArn` zurückgegeben, was anzeigt, dass die Aufzeichnung deaktiviert ist.

## Anleitung zum Erstellen einer IVS-Stage über die Konsole
<a name="getting-started-create-stage-console"></a>

1. Öffnen Sie die [Amazon-IVS-Konsole](https://console.aws.amazon.com/ivs).

   (Sie können auf die Amazon-IVS-Konsole auch über die [AWS-Managementkonsole](https://console.aws.amazon.com/) zugreifen.)

1. Wählen Sie im linken Navigationsbereich **Stage** und dann **Stage erstellen** aus. Das Fenster **Stage erstellen** wird angezeigt.  
![\[Verwenden Sie das Fenster „Stage erstellen“, um eine neue Stage und ein Teilnehmer-Token dafür zu erstellen.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Create_Stage_Console_IPR.png)

1. Geben Sie optional einen **Stagenamen** ein.

1. Wenn Sie die Aufzeichnung einzelner Teilnehmer aktivieren möchten, führen Sie die nachfolgenden Schritte unter [Einrichten der automatischen Aufzeichnung einzelner Teilnehmer in Amazon S3 (optional)](#getting-started-create-stage-ipr) durch.

1. Wählen Sie **Stage erstellen** aus, um die Stage zu erstellen. Die Seite mit den Stagedetails für die neue Stage wird angezeigt.

### Einrichten der automatischen Aufzeichnung einzelner Teilnehmer in Amazon S3 (optional)
<a name="getting-started-create-stage-ipr"></a>

Gehen Sie wie folgt vor, um die Aufzeichnung beim Erstellen einer neuen Stage zu aktivieren:

1. Aktivieren Sie auf der Seite **Stage erstellen** unter **Einzelne Teilnehmer aufzeichnen** die Option **Automatische Aufzeichnung aktivieren**. Daraufhin werden zusätzliche Felder angezeigt, in denen Sie die **aufgezeichneten Medientypen** auswählen, eine vorhandene **Speicherkonfiguration** auswählen oder eine neue Konfiguration erstellen können. Außerdem können Sie dort auswählen, ob in einem bestimmten Intervall Miniaturansichten aufgezeichnet werden sollen.  
![\[Im Dialogfeld „Einzelne Teilnehmer aufzeichnen“ können Sie die Aufzeichnung einzelner Teilnehmer für eine Stage konfigurieren.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Create_Stage_Console_enable_IPR.png)

1. Wählen Sie aus, welche Medientypen aufgezeichnet werden sollen.

1. Wählen Sie **Speicherkonfiguration erstellen**. Ein neues Fenster wird mit Optionen geöffnet, um einen Amazon S3 Bucket zu erstellen und ihn an die neue Aufzeichnungskonfiguration anzuhängen.  
![\[Erstellen Sie im Fenster „Speicherkonfiguration erstellen“ eine neue Speicherkonfiguration für eine Stage.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Create_Storage_Configuration_IPR.png)

1. Füllen Sie die Felder aus:

   1. Geben Sie optional einen **Speicherkonfigurationsnamen** ein.

   1. Geben Sie einen **Bucket name (Bucket-Namen)** ein.

1. Wählen Sie **Speicherkonfiguration erstellen**, um eine neue Speicherkonfiguration mit einem eindeutigen ARN zu erstellen. In der Regel dauert die Erstellung der Aufnahmekonfiguration einige Sekunden, aber es kann bis zu 20 Sekunden dauern. Wenn die Speicherkonfiguration erstellt ist, kehren Sie zum Fenster **Stage erstellen** zurück. Dort werden im Bereich **Einzelne Teilnehmer aufzeichnen** die neue **Speicherkonfiguration** und der von Ihnen erstellte S3-Bucket (**Speicher**) angezeigt.  
![\[Erstellen einer Stage über die IVS-Konsole: neue Speicherkonfiguration erstellt.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Create_Stage_Console_Storage_Configuration.png)

1. Optional können Sie weitere Optionen aktivieren, die nicht dem Standard entsprechen, z. B. die Aufzeichnung von Teilnehmerreplikaten, die Zusammenführung der Aufzeichnung einzelner Teilnehmer und die Aufzeichnung von Miniaturansichten.  
![\[Erstellen einer Stage über die IVS-Konsole: Aktivieren erweiterter Optionen wie die Aufzeichnung von Miniaturansichten und das Zusammenfügen der Aufzeichnungen einzelner Teilnehmer.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Create_Stage_Console_IPR_Stitching.png)

## Anleitung zum Erstellen einer IVS-Stage über die CLI
<a name="getting-started-create-stage-cli"></a>

Informationen zum Installieren der AWS-CLI finden Sie unter [Installieren oder Aktualisieren auf die neueste Version der AWS-CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html).

Ab sofort können Sie Ressourcen über die CLI erstellen und verwalten. Dazu wenden Sie je nachdem, ob Sie eine Stage mit oder ohne aktivierte Aufzeichnung einzelner Teilnehmer erstellen möchten, eines der beiden folgenden Verfahren an.

### Erstellen einer Stage ohne Aufzeichnung einzelner Teilnehmer
<a name="getting-started-create-stage-cli-without-ipr"></a>

Die Stage-API befindet sich unter dem Namespace ivs-realtime. Beispiel zum Erstellen einer Stage:

```
aws ivs-realtime create-stage --name "test-stage"
```

Die Antwort ist:

```
{
   "stage": {
      "arn": "arn:aws:ivs:us-west-2:376666121854:stage/VSWjvX5XOkU3",
      "name": "test-stage"
   }
}
```

### Erstellen einer Stage mit Aufzeichnung einzelner Teilnehmer
<a name="getting-started-create-stage-cli-with-ipr"></a>

So erstellen Sie eine Stage mit aktivierter Aufzeichnung einzelner Teilnehmer:

```
aws ivs-realtime create-stage --name "test-stage-participant-recording" --auto-participant-recording-configuration storageConfigurationArn=arn:aws:ivs:us-west-2:123456789012:storage-configuration/LKZ6QR7r55c2,mediaTypes=AUDIO_VIDEO
```

Übergeben Sie optional den Parameter `thumbnailConfiguration`, um den Speicher, den Modus und das Intervall (in Sekunden) für die Aufzeichnung von Miniaturansichten manuell einzustellen:

```
aws ivs-realtime create-stage --name "test-stage-participant-recording" --auto-participant-recording-configuration storageConfigurationArn=arn:aws:ivs:us-west-2:123456789012:storage-configuration/LKZ6QR7r55c2,mediaTypes=AUDIO_VIDEO,thumbnailConfiguration="{targetIntervalSeconds=10,storage=[SEQUENTIAL,LATEST],recordingMode=INTERVAL}"
```

Übergeben Sie optional den Parameter `recordingReconnectWindowSeconds`, um das Zusammenführen fragmentierter Aufzeichnungen einzelner Teilnehmer zu aktivieren:

```
aws ivs-realtime create-stage --name "test-stage-participant-recording" --auto-participant-recording-configuration "storageConfigurationArn=arn:aws:ivs:us-west-2:123456789012:storage-configuration/LKZ6QR7r55c2,mediaTypes=AUDIO_VIDEO,thumbnailConfiguration="{targetIntervalSeconds=10,storage=[SEQUENTIAL,LATEST],recordingMode=INTERVAL}",recordingReconnectWindowSeconds=60"
```

Die Antwort ist:

```
{
   "stage": {
      "arn": "arn:aws:ivs:us-west-2:123456789012:stage/VSWjvX5XOkU3",
      "autoParticipantRecordingConfiguration": {
         "hlsConfiguration": {
             "targetSegmentDurationSeconds": 6
         },
         "mediaTypes": [
            "AUDIO_VIDEO"
         ],
         "recordingReconnectWindowSeconds": 60,
         "recordParticipantReplicas": true,
         "storageConfigurationArn": "arn:aws:ivs:us-west-2:123456789012:storage-configuration/LKZ6QR7r55c2",
         "thumbnailConfiguration": {
            "recordingMode": "INTERVAL",
            "storage": [
               "SEQUENTIAL",
               "LATEST"
            ],
            "targetIntervalSeconds": 10
         }
      },
      "endpoints": {
         "events": "<events-endpoint>",
         "rtmp": "<rtmp-endpoint>",
         "rtmps": "<rtmps-endpoint>",
         "whip": "<whip-endpoint>"
      },
      "name": "test-stage-participant-recording"
   }
}
```

# Schritt 3: Teilnehmertoken verteilen
<a name="getting-started-distribute-tokens"></a>

Jetzt, da Sie eine Stage haben, müssen Sie Token erstellen und an die Teilnehmer verteilen, damit sie der Stage beitreten und mit dem Senden und Empfangen von Videos beginnen können. Für die Generierung von Token gibt es zwei Ansätze:
+ [Erstellen](#getting-started-distribute-tokens-self-signed) von Token mit einem Schlüsselpaar
+ [Erstellen von Token mit der IVS-Echtzeit-Streaming-API](#getting-started-distribute-tokens-api).

Beide Vorgehensweisen werden nachfolgend beschrieben.

## Erstellen von Token mit einem Schlüsselpaar
<a name="getting-started-distribute-tokens-self-signed"></a>

Sie können Token in Ihrer Serveranwendung erstellen und sie an die Teilnehmer verteilen, damit diese an einer Stage teilnehmen können. Sie müssen ein öffentliches/privates ECDSA-Schlüsselpaar generieren, um die JWTs zu signieren und den öffentlichen Schlüssel in IVS zu importieren. Dann kann IVS die Token zum Zeitpunkt des Beitritts zur Stage verifizieren. 

Bei IVS gibt es kein Ablaufdatum für Schlüssel. Wenn Ihr privater Schlüssel gefährdet ist, müssen Sie den alten öffentlichen Schlüssel löschen.

### Erstellen eines neuen Schlüsselpaares
<a name="getting-started-distribute-tokens-self-signed-create-key-pair"></a>

Es gibt verschiedene Methoden, um ein Schlüsselpaar zu erstellen. Im Folgenden geben wir zwei Beispiele.

Führen Sie die folgenden Schritte aus, um in der Konsole ein neues Schlüsselpaar zu erstellen:

1. [Öffnen Sie die Amazon-IVS-Konsole.](https://console.aws.amazon.com/ivs) Wählen Sie die Region Ihrer Stage aus, wenn Sie sich nicht bereits darin befinden.

1. Wählen Sie im linken Navigationsmenü **Echtzeit-Streaming > Öffentliche Schlüssel** aus.

1. Wählen Sie **Öffentlichen Schlüssel erstellen**. Das Dialogfenster **Öffentlichen Schlüssel erstellen** wird angezeigt.

1. Folgen Sie den Eingabeaufforderungen und wählen Sie **Erstellen**.

1. Amazon IVS generiert ein neues Schlüsselpaar. Der öffentliche Schlüssel wird als öffentliche Schlüsselressource importiert und der private Schlüssel wird sofort zum Download zur Verfügung gestellt. Der öffentliche Schlüssel kann bei Bedarf auch später heruntergeladen werden.

   Amazon IVS generiert den Schlüssel auf der Clientseite und speichert den privaten Schlüssel nicht. ***Speichern Sie den Schlüssel unbedingt. Sie können ihn später nicht abrufen.***

Um ein neues P384-EC-Schlüsselpaar mit OpenSSL zu erstellen, gehen Sie wie folgt vor (möglicherweise müssen Sie dazu zunächst [OpenSSL](https://www.openssl.org/source/) installieren). Mit diesem Verfahren können Sie sowohl auf die privaten als auch auf die öffentlichen Schlüssel zugreifen. Den öffentlichen Schlüssel benötigen Sie nur, wenn Sie die Verifizierung Ihrer Token testen möchten.

```
openssl ecparam -name secp384r1 -genkey -noout -out priv.pem
openssl ec -in priv.pem -pubout -out public.pem
```

Importieren Sie nun den neuen öffentlichen Schlüssel. Nutzen Sie dazu die untenstehende Anleitung.

### Importieren des öffentlichen Schlüssels
<a name="getting-started-distribute-tokens-import-public-key"></a>

Sobald Sie über ein Schlüsselpaar verfügen, können Sie den öffentlichen Schlüssel in IVS importieren. Der private Schlüssel wird von unserem System nicht benötigt; er wird von Ihnen zum Signieren von Token verwendet.

So importieren Sie einen vorhandenen öffentlichen Schlüssel mit der Konsole:

1. [Öffnen Sie die Amazon-IVS-Konsole.](https://console.aws.amazon.com/ivs) Wählen Sie die Region Ihrer Stage aus, wenn Sie sich nicht bereits darin befinden.

1. Wählen Sie im linken Navigationsmenü **Echtzeit-Streaming > Öffentliche Schlüssel** aus.

1. Wählen Sie **Importieren** aus. Das Dialogfenster **Öffentlichen Schlüssel importieren** wird angezeigt.

1. Folgen Sie den Eingabeaufforderungen und wählen Sie **Importieren**.

1. Amazon IVS importiert Ihren öffentlichen Schlüssel und generiert eine öffentliche Schlüsselressource.

So importieren Sie einen vorhandenen öffentlichen Schlüssel mit der CLI:

```
aws ivs-realtime import-public-key --public-key-material "`cat public.pem`" --region <aws-region>
```

Sie können `--region <aws-region>` auslassen, wenn sich die Region in Ihrer lokalen AWS Konfigurationsdatei befindet.

Hier ist ein Beispiel für eine Antwort:

```
{
    "publicKey": {
        "arn": "arn:aws:ivs:us-west-2:123456789012:public-key/f99cde61-c2b0-4df3-8941-ca7d38acca1a",
        "fingerprint": "98:0d:1a:a0:19:96:1e:ea:0a:0a:2c:9a:42:19:2b:e7",
        "publicKeyMaterial": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEVjYMV+P4ML6xemanCrtse/FDwsNnpYmS\nS6vRV9Wx37mjwi02hObKuCJqpj7x0lpz0bHm5v1JBvdZYAd/r2LR5aChK+/GM2Wj\nl8MG9NJIVFaw1u3bvjEjzTASSfS1BDX1\n-----END PUBLIC KEY-----\n",
        "tags": {}
    }
}
```

### API-Anforderungen
<a name="getting-started-distribute-tokens-create-api"></a>

```
POST /ImportPublicKey HTTP/1.1
{
  "publicKeyMaterial": "<pem file contents>"
}
```

### Generieren und Signieren des Tokens
<a name="getting-started-distribute-tokens-self-signed-generate-sign"></a>

Weitere Informationen zum Arbeiten mit JWTs und den unterstützten Bibliotheken zum Signieren von Token finden Sie unter [jwt.io](https://jwt.io/). In der jwt.io-Oberfläche müssen Sie Ihren privaten Schlüssel eingeben, um Token zu signieren. Der öffentliche Schlüssel wird nur benötigt, wenn Sie Token verifizieren möchten.

Alle JWTs haben drei Felder: Header, Nutzlast und Signatur.

Die JSON-Schemas für den Header und die Nutzdaten des JWT werden unten beschrieben. Alternativ können Sie ein Beispiel-JSON von der IVS-Konsole kopieren. Abrufen des Headers und der Nutzdaten-JSON von der IVS-Konsole:

1. [Öffnen Sie die Amazon-IVS-Konsole.](https://console.aws.amazon.com/ivs) Wählen Sie die Region Ihrer Stage aus, wenn Sie sich nicht bereits darin befinden.

1. Wählen Sie im linken Navigationsbereich **Echtzeit-Streaming > Stage** aus.

1. Wählen Sie die Stage aus, die Sie verwenden möchten. Wählen Sie **Details anzeigen** aus.

1. Wählen Sie im Abschnitt **Teilnehmer-Token** das Drop-down-Menü neben **Token erstellen** aus.

1. Wählen Sie **Token-Header und Nutzdaten erstellen** aus.

1. Füllen Sie das Formular aus und kopieren Sie den JWT-Header und die Nutzdaten, die unten im Popup angezeigt werden.

#### Token-Schema: Header
<a name="getting-started-distribute-tokens-self-signed-generate-sign-header"></a>

Der Header gibt Folgendes an:
+ `alg` ist der Signaturalgorithmus. Dies ist ES384, ein ECDSA-Signaturalgorithmus, der den SHA-384-Hash-Algorithmus verwendet.
+ `typ` ist der Tokentyp, JWT.
+ `kid` ist der ARN des öffentlichen Schlüssels, der zum Signieren des Tokens verwendet wird. Es muss derselbe ARN sein, der von der API-Anforderung [GetPublicKey](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/API_GetPublicKey.html) zurückgegeben wurde.

```
{
  "alg": "ES384",
  "typ": "JWT"
  “kid”: “arn:aws:ivs:123456789012:us-east-1:public-key/abcdefg12345”
}
```

#### Token-Schema: Nutzdaten
<a name="getting-started-distribute-tokens-self-signed-generate-sign-payload"></a>

Die Nutzdaten enthalten spezifische Daten für IVS. Alle Felder außer `user_id` sind Pflichtfelder.
+ `RegisteredClaims` in der JWT-Spezifikation sind reservierte Ansprüche, die angegeben werden müssen, damit das Stage-Token gültig ist: 
  + `exp` (Ablaufzeit) ist ein Unix-UTC-Zeitstempel für den Zeitpunkt, an dem das Token abläuft. (Ein Unix-Zeitstempel ist ein numerischer Wert, der die Anzahl der Sekunden von 1970-01-01T00:00:00Z UTC bis zum angegebenen UTC-Datum/Uhrzeit angibt, wobei Schaltsekunden ignoriert werden.) Das Token wird validiert, wenn der Teilnehmer einer Stage beitritt. IVS stellt Token standardmäßig mit einer Gültigkeitsdauer von 12 Stunden bereit, die wir empfehlen. Diese Frist kann auf maximal 14 Tage ab dem Zeitpunkt der Ausstellung (iat) verlängert werden. Dieser Wert muss eine Ganzzahl sein.
  + `iat` (Ausstellungszeit) ist ein Unix-UTC-Zeitstempel für den Zeitpunkt, an dem das JWT ausgestellt wurde. (Informationen zu Unix-Zeitstempeln finden Sie in der Anmerkung für `exp`.) Dieser Wert muss eine Ganzzahl sein.
  + `jti` (JWT-ID) ist die Teilnehmer-ID, die zur Nachverfolgung verwendet wird und auf den Teilnehmer verweist, dem das Token gewährt wird. Jedes Token muss eine eindeutige Teilnehmer-ID haben. Sie muss eine Zeichenfolge sein, die Groß- und Kleinschreibung berücksichtigt, bis zu 64 Zeichen lang ist und nur alphanumerische Zeichen, Bindestrich (-) und Unterstrich (\$1) enthält. Andere Sonderzeichen sind nicht zulässig. 
+ `user_id` ist ein optionaler, vom Kunden zugewiesener Name, der die Identifizierung des Tokens erleichtert. Dieser kann verwendet werden, um einen Teilnehmer mit einem Benutzer in den eigenen Systemen des Kunden zu verknüpfen. Dieser Wert muss mit dem Feld `userId` in der API-Anforderung [CreateParticipantToken](https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/API_CreateParticipantToken.html) übereinstimmen. Er kann jeder UTF-8-codierte Text sein und ist eine Zeichenfolge mit bis zu 128 Zeichen. *Dieses Feld ist für alle Stageteilnehmer sichtbar und darf daher nicht für personenbezogene, vertrauliche oder sensible Informationen verwendet werden.*
+ `resource` ist der ARN der Stage, z. B. `arn:aws:ivs:us-east-1:123456789012:stage/oRmLNwuCeMlQ`.
+ `topic` ist die ID der Stage, die aus dem Stage-ARN extrahiert werden kann. Wenn der Stage-ARN beispielsweise `arn:aws:ivs:us-east-1:123456789012:stage/oRmLNwuCeMlQ` lautet, ist die Stage-ID `oRmLNwuCeMlQ`.
+ `events_url` muss der Endpunkt für Ereignisse sein, der vom CreateStage- oder GetStage-Vorgang zurückgegeben wird. Es wird empfohlen, diesen Wert bei der Stageerstellung zwischenzuspeichern. Er kann bis zu 14 Tage lang zwischengespeichert werden. Ein Beispielwert ist `wss://global.events.live-video.net`.
+ `whip_url` muss der WHIP-Endpunkt sein, der vom CreateStage- oder GetStage-Vorgang zurückgegeben wird. Es wird empfohlen, diesen Wert bei der Stageerstellung zwischenzuspeichern. Er kann bis zu 14 Tage lang zwischengespeichert werden. Ein Beispielwert ist `https://453fdfd2ad24df.global-bm.whip.live-video.net`.
+ `capabilities` gibt die Fähigkeiten des Tokens an. Gültige Werte sind `allow_publish` und `allow_subscribe`. Für Token, die nur für Abonnements bestimmt sind, legen Sie `allow_subscribe` auf `true` fest.
+ `attributes` ist ein optionales Feld, in dem Sie von der Anwendung bereitgestellte Attribute angeben können, die in das Token codiert und an eine Stage angehängt werden sollen. Zuordnungsschlüssel und -werte können UTF-8-codierten Text enthalten. Die maximale Länge dieses Felds beträgt insgesamt 1 KB. *Dieses Feld ist für alle Stageteilnehmer sichtbar und darf daher nicht für personenbezogene, vertrauliche oder sensible Informationen verwendet werden.*
+ `version` muss sein `1.0`.

  ```
  {
    "exp": 1697322063,
    "iat": 1697149263,
    "jti": "Mx6clRRHODPy",
    "user_id": "<optional_customer_assigned_name>",
    "resource": "<stage_arn>",
    "topic": "<stage_id>",
    "events_url": "wss://global.events.live-video.net",
    "whip_url": "https://114ddfabadaf.global-bm.whip.live-video.net",
    "capabilities": {
      "allow_publish": true,
      "allow_subscribe": true
    },
    "attributes": {
      "optional_field_1": "abcd1234",
      "optional_field_2": "false"
    },
    "version": "1.0"
  }
  ```

#### Token-Schema: Signatur
<a name="getting-started-distribute-tokens-self-signed-generate-sign-signature"></a>

Zum Erstellen der Signatur verwenden Sie den privaten Schlüssel im Header (ES384) mit dem angegebenen Algorithmus, um den codierten Header, die codierte Nutzlast und den privaten Schlüssel zu signieren.

```
ECDSASHA384(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  <private-key>
)
```

#### Anleitungen
<a name="getting-started-distribute-tokens-self-signed-generate-sign-instructions"></a>

1. Generieren Sie die Signatur des Tokens mit einem ES384-Signaturalgorithmus und einem privaten Schlüssel, der dem öffentlichen Schlüssel zugeordnet ist, der an IVS übergeben wird.

1. Montieren des Token.

   ```
   base64UrlEncode(header) + "." +
   base64UrlEncode(payload) + "." +
   base64UrlEncode(signature)
   ```

## Erstellen von Token mit der IVS-Echtzeit-Streaming-API
<a name="getting-started-distribute-tokens-api"></a>

![\[Teilnehmer-Token verteilen: Stage-Token-Workflow\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Distribute_Participant_Tokens.png)


Wie oben gezeigt, fragt eine Client-Anwendung Ihre serverseitige Anwendung nach einem Token, und die serverseitige Anwendung ruft CreateParticipantToken mithilfe eines AWS-SDK oder signierter Sigv4-Anfragen auf. Da AWS-Anmeldeinformationen zum Aufrufen der API verwendet werden, sollte das Token in einer sicheren serverseitigen Anwendung generiert werden, nicht in der clientseitigen Anwendung.

Beim Erstellen eines Teilnehmer-Tokens können Sie optional Attribute und/oder Fähigkeiten angeben:
+ Sie können von der Anwendung bereitgestellte Attribute angeben, die in das Token codiert und an eine Stage angehängt werden sollen. Zuordnungsschlüssel und -werte können UTF-8-codierten Text enthalten. Die maximale Länge dieses Felds beträgt insgesamt 1 KB. *Dieses Feld ist für alle Stageteilnehmer sichtbar und darf daher nicht für personenbezogene, vertrauliche oder sensible Informationen verwendet werden.*
+ Sie können Fähigkeiten angeben, die durch das Token aktiviert werden. Die Standardeinstellung ist `PUBLISH` und `SUBSCRIBE`, was es dem Teilnehmer ermöglicht, Audio und Video zu senden und zu empfangen, aber Sie können Tokens mit einer Teilmenge von Funktionen ausgeben. Sie könnten zum Beispiel einen Token ausgeben, der nur die Fähigkeit `SUBSCRIBE` für Moderatoren enthält. In diesem Fall könnten die Moderatoren die Teilnehmer sehen, die ein Video senden, aber kein eigenes Video senden.

Einzelheiten finden Sie unter [CreateParticipantToken](https://docs.aws.amazon.com//ivs/latest/RealTimeAPIReference/API_CreateParticipantToken.html).

Sie können Teilnehmer-Token über die Konsole oder CLI zu Test- und Entwicklungszwecken erstellen. Höchstwahrscheinlich möchten Sie sie jedoch mit dem AWS-SDK in Ihrer Produktionsumgebung erstellen.

Sie benötigen eine Möglichkeit, um Token von Ihrem Server an alle Clients zu verteilen (z. B. über eine API-Anforderung). Diese Funktionalität wird von uns nicht bereitgestellt. Für diese Anleitung können Sie die Token einfach kopieren und in den folgenden Schritten in den Client-Code einfügen.

**Wichtig**: Behandeln Sie Token als nicht transparent, d. h. entwickeln Sie keine Funktionen, die auf Tokeninhalten basieren. Das Format von Token könnte sich in Zukunft ändern.

### Anleitung für die Konsole
<a name="getting-started-distribute-tokens-console"></a>

1. Navigieren Sie zu der Stage, die Sie im vorherigen Schritt erstellt haben.

1. Wählen Sie **Token erstellen** aus. Das Fenster **Token erstellen** wird angezeigt.

1. Geben Sie eine Benutzer-ID ein, die dem Token zugeordnet werden soll. Dies kann jeder UTF-8-kodierte Text sein. 

1. Wählen Sie **Erstellen** aus.

1. Kopieren Sie das Token. *Wichtig: Achten Sie darauf, das Token zu speichern. IVS speichert es nicht und Sie können es später nicht abrufen*.

### CLI-Anweisungen
<a name="getting-started-distribute-tokens-cli"></a>

Das Erstellen eines Chat-Tokens mit der AWS CLI ist eine erweiterte Option und erfordert, dass Sie zuerst die CLI auf Ihrem Computer herunterladen und konfigurieren. Informationen zu den ersten Schritten finden Sie im [Benutzerhandbuch für die AWS-Befehlszeilenschnittstelle](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html). Beachten Sie, dass die Generierung von Token mit der AWS-CLI für Testzwecke gut geeignet ist. Für den produktiven Einsatz empfehlen wir jedoch, Token auf der Serverseite mit dem AWS-SDK zu generieren (siehe Anweisungen unten).

1. Führen Sie den `create-participant-token`-Befehl mit dem Stage-ARN aus. Fügen Sie eine der folgenden Funktionen ein: `"PUBLISH"`, `"SUBSCRIBE"`.

   ```
   aws ivs-realtime create-participant-token --stage-arn arn:aws:ivs:us-west-2:376666121854:stage/VSWjvX5XOkU3 --capabilities '["PUBLISH", "SUBSCRIBE"]'
   ```

1. Dies gibt ein Teilnehmer-Token zurück:

   ```
   {
       "participantToken": {
           "capabilities": [
               "PUBLISH",
               "SUBSCRIBE"
           ],
           "expirationTime": "2023-06-03T07:04:31+00:00",
           "participantId": "tU06DT5jCJeb",
           "token": "eyJhbGciOiJLTVMiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjE2NjE1NDE0MjAsImp0aSI6ImpGcFdtdmVFTm9sUyIsInJlc291cmNlIjoiYXJuOmF3czppdnM6dXMtd2VzdC0yOjM3NjY2NjEyMTg1NDpzdGFnZS9NbzhPUWJ0RGpSIiwiZXZlbnRzX3VybCI6IndzczovL3VzLXdlc3QtMi5ldmVudHMubGl2ZS12aWRlby5uZXQiLCJ3aGlwX3VybCI6Imh0dHBzOi8vNjZmNzY1YWM4Mzc3Lmdsb2JhbC53aGlwLmxpdmUtdmlkZW8ubmV0IiwiY2FwYWJpbGl0aWVzIjp7ImFsbG93X3B1Ymxpc2giOnRydWUsImFsbG93X3N1YnNjcmliZSI6dHJ1ZX19.MGQCMGm9affqE3B2MAb_DSpEm0XEv25hfNNhYn5Um4U37FTpmdc3QzQKTKGF90swHqVrDgIwcHHHIDY3c9eanHyQmcKskR1hobD0Q9QK_GQETMQS54S-TaKjllW9Qac6c5xBrdAk"
       }
   }
   ```

1. Speichern Sie dieses Token. Sie benötigen dies, um der Stage beizutreten und Videos zu senden und zu empfangen.

### AWS-SDK-Anweisungen
<a name="getting-started-distribute-tokens-sdk"></a>

Sie können das AWS-SDK für die Erstellung von Tokens verwenden. Im Folgenden finden Sie Anweisungen für das AWS-SDK, das JavaScript verwendet. 

**Wichtig:** Dieser Code muss serverseitig ausgeführt und seine Ausgabe an den Client übergeben werden.

**Voraussetzung:** Um das folgende Codebeispiel verwenden zu können, müssen Sie das Paket aws-sdk/client-ivs-realtime installieren. Weitere Informationen dazu finden Sie unter [Erste Schritte mit AWS-SDK für JavaScript](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/getting-started.html).

```
import { IVSRealTimeClient, CreateParticipantTokenCommand } from "@aws-sdk/client-ivs-realtime";

const ivsRealtimeClient = new IVSRealTimeClient({ region: 'us-west-2' });
const stageArn = 'arn:aws:ivs:us-west-2:123456789012:stage/L210UYabcdef';
const createStageTokenRequest = new CreateParticipantTokenCommand({
  stageArn,
});
const response = await ivsRealtimeClient.send(createStageTokenRequest);
console.log('token', response.participantToken.token);
```

# Schritt 4: Das IVS Broadcast SDK integrieren
<a name="getting-started-broadcast-sdk"></a>

IVS bietet ein Broadcast-SDK für Web, Android und iOS, das Sie in Ihre Anwendung integrieren können. Das Broadcast-SDK wird sowohl zum Senden als auch zum Empfangen von Videos verwendet. Wenn Sie [RTMP-Erfassung für Ihre Stage konfiguriert](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/rt-stream-ingest.html) haben, können Sie jeden Encoder verwenden, der an einen RTMP-Endpunkt senden kann (z. B. OBS oder ffmpeg).

In diesem Abschnitt schreiben wir eine einfache Anwendung, mit der zwei oder mehr Teilnehmer in Echtzeit interagieren können. Die folgenden Schritte führen Sie durch die Erstellung einer Anwendung namens BasicRealTime. Der vollständige Anwendungscode befindet sich auf CodePen und GitHub:
+  Web: [https://codepen.io/amazon-ivs/pen/ZEqgrpo](https://codepen.io/amazon-ivs/pen/ZEqgrpo) 
+  Android: [https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples) 
+  iOS: [https://github.com/aws-samples/amazon-ivs-real-time-streaming-ios-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-ios-samples) 

## Web
<a name="getting-started-broadcast-sdk-web"></a>

### Dateien einrichten
<a name="getting-started-broadcast-sdk-web-setup"></a>

Richten Sie zunächst Ihre Dateien ein, indem Sie einen Ordner und eine erste HTML- und JS-Datei erstellen:

```
mkdir realtime-web-example
cd realtime-web-example
touch index.html
touch app.js
```

Sie können das Broadcast-SDK mit einem Script-Tag oder npm installieren. Unser Beispiel verwendet der Einfachheit halber das Script-Tag, kann aber leicht geändert werden, wenn Sie npm später verwenden möchten.

### Verwenden eines Skript-Tags
<a name="getting-started-broadcast-sdk-web-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.

Wenn sie per `<script>`-Tag geladen wird, stellt die Bibliothek eine globale Variable im Fensterbereich namens `IVSBroadcastClient` bereit.

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

So installieren Sie das npm-Paket:

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

Sie können jetzt auf das IVSBroadcastClient-Objekt zugreifen:

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

## Android
<a name="getting-started-broadcast-sdk-android"></a>

### Erstellen Sie das Android-Projekt
<a name="getting-started-broadcast-sdk-android-project"></a>

1. Erstellen Sie ein **Neues Projekt** mit Android Studio.

1. Wählen Sie **Aktivität „Leere Ansichten“**.

   Hinweis: In einigen älteren Versionen von Android Studio heißt die ansichtsbasierte Aktivität **Leere Aktivität**. Wenn Ihr Android-Studio-Fenster **Leere Aktivität** und *nicht* **Leere Ansichten**-Aktivität anzeigt, wählen Sie **Leere Aktivität**. Andernfalls wählen Sie nicht **Leere Aktivität**, da wir View-APIs verwenden werden (nicht Jetpack Compose).

1. Geben Sie Ihrem Projekt einen **Namen**, wählen Sie dann **Fertig**.

### Installieren Sie das Broadcast-SDK
<a name="getting-started-broadcast-sdk-android-install"></a>

Wenn Sie der Android-Entwicklungsumgebung die Amazon-IVS-Android-Broadcast-Bibliothek hinzufügen möchten, fügen Sie die Bibliothek der `build.gradle` – wie hier gezeigt – (für die neueste Version des Amazon IVS Broadcast SDK) zu Ihren Modulen hinzu. In neueren Projekten ist `mavenCentral` das Repository ist möglicherweise bereits in Ihrer `settings.gradle`-Datei. Wenn das der Fall ist, können Sie den `repositories`-Block weglassen. Für unser Beispiel müssen wir auch die Datenbindung im Block `android` aktivieren.

```
android {
    dataBinding.enabled true
}

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

Um das SDK manuell zu installieren, laden Sie alternativ die neueste Version von diesem Speicherort herunter:

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

## iOS
<a name="getting-started-broadcast-sdk-ios"></a>

### Erstellen des iOS-Projekts
<a name="getting-started-broadcast-sdk-ios-project"></a>

1. Erstellen eines neuen Xcode-Projekts.

1. Für **Plattform** wählen Sie **iOS**.

1. Für **Anwendung** wählen Sie **App**.

1. Geben Sie den **Namen des Produkts** Ihrer Anwendung ein und wählen Sie **Weiter**.

1. Wählen (navigieren Sie zu) einem Verzeichnis, in dem das Projekt gespeichert werden soll, und wählen Sie dann **Erstellen**.

Als Nächstes müssen Sie das SDK einbringen. Anweisungen finden Sie unter [Installieren der Bibliothek](broadcast-ios-getting-started.md#broadcast-ios-install) im *Handbuch zum iOS-Broadcast-SDK*.

### So konfigurieren Sie Berechtigungen
<a name="getting-started-broadcast-sdk-ios-config"></a>

Sie müssen die `Info.plist` Ihres Projekts aktualisieren, um zwei neue Einträge hinzuzufügen für `NSCameraUsageDescription` und `NSMicrophoneUsageDescription`. Geben Sie für die Werte benutzerfreundliche Erklärungen an, warum Ihre Anwendung nach Kamera- und Mikrofonzugriff fragt.

![\[Konfigurieren von iOS-Berechtigungen.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/iOS_Configure.png)


# Schritt 5: Video veröffentlichen und abonnieren
<a name="getting-started-pub-sub"></a>

Sie können Folgendes nutzen, um in IVS zu veröffentlichen/abonnieren (in Echtzeit):
+ Die nativen [IVS-Broadcast-SDKs](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/getting-started-set-up-streaming.html#broadcast-sdk), die WebRTC und RTMPS unterstützen. Wir empfehlen dies insbesondere für Produktionsszenarien. Nachfolgend finden Sie die Details für [Web](getting-started-pub-sub-web.md), [Android](getting-started-pub-sub-android.md) und [iOS](getting-started-pub-sub-ios.md).
+ Die Amazon IVS-Konsole – Diese eignet sich zum Testen von Streams. Weitere Informationen finden Sie unter weiter unten in diesem Dokument.
+ Andere Software- und Hardware-Encoder für das Streaming – Sie können alle Streaming-Encoder verwenden, die die Protokolle RTMP, RTMPS oder WHIP unterstützen. Weitere Informationen finden Sie unter [Stream-Erfassung](rt-stream-ingest.md).

## IVS-Konsole
<a name="getting-started-pub-sub-console"></a>

1. Öffnen Sie die [Amazon-IVS-Konsole](https://console.aws.amazon.com/ivs).

   (Sie können auf die Amazon IVS Konsole auch über die [AWS-Managementkonsole](https://console.aws.amazon.com/) zugreifen.)

1. Wählen Sie im Navigationsbereich die Option **Stage** aus. (Wenn der Navigationsbereich ausgeblendet ist, erweitern Sie ihn über das Hamburger-Symbol.)

1. Wählen Sie die Stage aus, die Sie abonnieren oder veröffentlichen möchten, um ihre Detailseite aufzurufen.

1. Abonnieren: Wenn die Stage einen oder mehrere Publisher hat, können Sie sie abonnieren, indem Sie auf der Registerkarte **Abonnieren** auf die Schaltfläche **Abonnieren** klicken. Die Registerkarte wird unter dem Abschnitt **Allgemeine Konfiguration** angezeigt.

1. Zum Veröffentlichen:

   1. Wählen Sie die Registerkarte **Veröffentlichen** aus.

   1. Sie werden aufgefordert, der IVS-Konsole Zugriff auf Ihre Kamera und Ihr Mikrofon zu gewähren. **Erlauben** Sie diese Berechtigungen.

   1. Wählen Sie unten auf der Registerkarte **Veröffentlichen** mit den Dropdown-Feldern die Eingabegeräte für das Mikrofon und die Kamera aus.

   1. Um mit der Veröffentlichung zu beginnen, wählen Sie **Veröffentlichung starten** aus.

   1. Um Ihre veröffentlichten Inhalte anzuzeigen, kehren Sie zur Registerkarte **Abonnieren** zurück.

   1. Um die Veröffentlichung zu beenden, klicken Sie auf der Registerkarte **Veröffentlichen** unten auf die Schaltfläche **Veröffentlichung beenden**.

**Hinweis**: Das Abonnieren und Veröffentlichen verbraucht Ressourcen, und für die Zeit, in der Sie mit der Stage verbunden sind, wird ein Stundensatz berechnet. Weitere Informationen finden Sie auf der Seite mit den IVS-Preisen unter [Echtzeit-Streaming](https://aws.amazon.com/ivs/pricing/#Real-Time_Streaming).

# Mit dem IVS Web Broadcast SDK veröffentlichen und abonnieren
<a name="getting-started-pub-sub-web"></a>

Dieser Abschnitt führt Sie durch die Schritte zur Veröffentlichung und zum Abonnieren einer Stage mithilfe Ihrer Web-App.

## HTML-Boilerplate erstellen
<a name="getting-started-pub-sub-web-html"></a>

Lassen Sie uns zunächst das HTML-Boilerplate erstellen und die Bibliothek als Script-Tag importieren:

```
<!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/1.33.0/amazon-ivs-web-broadcast.js"></script>
</head>

<body>

<!-- TODO - fill in with next sections -->
<script src="./app.js"></script>

</body>
</html>
```

## Token-Eingabe akzeptieren und Schaltflächen zum Beitritten/Verlassen hinzufügen
<a name="getting-started-pub-sub-web-join"></a>

Hier füllen wir den Hauptteil mit unseren Eingabekontrollen aus. Diese nehmen das Token als Eingabe und richten Schaltflächen für **Beitreten** und **Verlassen** ein. Normalerweise fordern Anwendungen das Token von der API Ihrer Anwendung an, aber in diesem Beispiel kopieren Sie das Token und fügen es in die Token-Eingabe ein.

```
<h1>IVS Real-Time Streaming</h1>
<hr />

<label for="token">Token</label>
<input type="text" id="token" name="token" />
<button class="button" id="join-button">Join</button>
<button class="button" id="leave-button" style="display: none;">Leave</button>
<hr />
```

## Mediencontainer-Elemente hinzufügen
<a name="getting-started-pub-sub-web-media"></a>

Diese Elemente werden die Medien für unsere lokalen und externen Teilnehmer bereitstellen. Wir fügen ein Script-Tag hinzu, um die in `app.js` definierte Logik unserer Anwendung zu laden.

```
<!-- Local Participant -->
<div id="local-media"></div>

<!-- Remote Participants -->
<div id="remote-media"></div>

<!-- Load Script -->
<script src="./app.js"></script>
```

Damit ist die HTML-Seite fertig. Sie sollten sie sehen, wenn Sie `index.html` in einem Browser laden:

![\[Echtzeit-Streaming in einem Browser anzeigen: Die HTML-Setup ist abgeschlossen.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/RT_Browser_View.png)


## Erstellen von app.js
<a name="getting-started-pub-sub-web-appjs"></a>

Gehen wir zur Definition des Inhalts unserer `app.js`-Datei. Importieren Sie zunächst alle erforderlichen Eigenschaften aus der globalen Version des SDK:

```
const {
  Stage,
  LocalStageStream,
  SubscribeType,
  StageEvents,
  ConnectionState,
  StreamType
} = IVSBroadcastClient;
```

## Anwendungsvariablen erstellen
<a name="getting-started-pub-sub-web-vars"></a>

Erstellen Sie Variablen, um Verweise auf unsere HTML-Elemente für die Schaltflächen **Beitreten** und **Verlassen** zu speichern und den Status für die Anwendung zu speichern:

```
let joinButton = document.getElementById("join-button");
let leaveButton = document.getElementById("leave-button");

// Stage management
let stage;
let joining = false;
let connected = false;
let localCamera;
let localMic;
let cameraStageStream;
let micStageStream;
```

## JoinStage 1 erstellen: Definieren Sie die Funktion und validieren Sie die Eingabe
<a name="getting-started-pub-sub-web-joinstage1"></a>

Die Funktion `joinStage` nimmt das Eingabe-Token, stellt eine Verbindung zur Stage her und beginnt mit der Veröffentlichung von Video- und Audiodaten, die von `getUserMedia` empfangen werden.

Zu Beginn definieren wir die Funktion und validieren den Status und die Token-Eingabe. Wir werden diese Funktion in den nächsten Abschnitten näher erläutern.

```
const joinStage = async () => {
  if (connected || joining) {
    return;
  }
  joining = true;

  const token = document.getElementById("token").value;

  if (!token) {
    window.alert("Please enter a participant token");
    joining = false;
    return;
  }

  // Fill in with the next sections
};
```

## JoinStage 2 erstellen: Medien zum Veröffentlichen abrufen
<a name="getting-started-pub-sub-web-joinstage2"></a>

Hier sind die Medien, die auf der Stage veröffentlicht werden:

```
async function getCamera() {
  // Use Max Width and Height
  return navigator.mediaDevices.getUserMedia({
    video: true,
    audio: false
  });
}

async function getMic() {
  return navigator.mediaDevices.getUserMedia({
    video: false,
    audio: true
  });
}

// Retrieve the User Media currently set on the page
localCamera = await getCamera();
localMic = await getMic();

// Create StageStreams for Audio and Video
cameraStageStream = new LocalStageStream(localCamera.getVideoTracks()[0]);
micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);
```

## JoinStage 3 erstellen: Definieren Sie die Stagestrategie und erstellen Sie die Stage
<a name="getting-started-pub-sub-web-joinstage3"></a>

Diese Stagestrategie ist das Herzstück der Entscheidungslogik, anhand derer das SDK entscheidet, was veröffentlicht und welche Teilnehmer abonniert werden sollen. Weitere Informationen zum Zweck der Funktion finden Sie unter [Strategie](web-publish-subscribe.md#web-publish-subscribe-concepts-strategy).

Diese Strategie ist einfach. Nachdem Sie die Stage betreten haben, veröffentlichen Sie die soeben abgerufenen Streams und abonnieren die Audio- und Videodaten aller Remote-Teilnehmer:

```
const strategy = {
  stageStreamsToPublish() {
    return [cameraStageStream, micStageStream];
  },
  shouldPublishParticipant() {
    return true;
  },
  shouldSubscribeToParticipant() {
    return SubscribeType.AUDIO_VIDEO;
  }
};

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

## JoinStage 4 erstellen: Stageereignisse verarbeiten und Medien rendern
<a name="getting-started-pub-sub-web-joinstage4"></a>

Stages geben viele Ereignisse ab. Wir müssen auf die `STAGE_PARTICIPANT_STREAMS_ADDED` und `STAGE_PARTICIPANT_LEFT` hören, um Medien auf und von der Seite zu rendern und zu entfernen. Eine umfassendere Reihe von Ereignissen finden Sie unter [Ereignisse](web-publish-subscribe.md#web-publish-subscribe-concepts-events).

Beachten Sie, dass wir hier vier Hilfsfunktionen erstellen, die uns bei der Verwaltung der erforderlichen DOM-Elemente unterstützen:`setupParticipant`, `teardownParticipant`, `createVideoEl` und `createContainer`.

```
stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
  connected = state === ConnectionState.CONNECTED;

  if (connected) {
    joining = false;
    joinButton.style = "display: none";
    leaveButton.style = "display: inline-block";
  }
});

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


// Helper functions for managing DOM

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

## JoinStage 5 erstellen: Treten Sie der Stage bei
<a name="getting-started-pub-sub-web-joinstage5"></a>

Vervollständigen wir unsere Funktion `joinStage` , indem Sie endlich die Stage betreten\$1

```
try {
  await stage.join();
} catch (err) {
  joining = false;
  connected = false;
  console.error(err.message);
}
```

## LeaveStage erstellen
<a name="getting-started-pub-sub-web-leavestage"></a>

Definieren Sie die `leaveStage`-Funktion, die die Verlassen-Schaltfläche aufrufen wird.

```
const leaveStage = async () => {
  stage.leave();

  joining = false;
  connected = false;
};
```

## Input-Event-Handler initialisieren
<a name="getting-started-pub-sub-web-handlers"></a>

Wir fügen eine letzte Funktion zu unserer `app.js`-Datei hinzu. Diese Funktion wird sofort aufgerufen, wenn die Seite geladen wird, und richtet Event-Handler für den Beitritt und das Verlassen der Stage ein.

```
const init = async () => {
  try {
    // Prevents issues on Safari/FF so devices are not blank
    await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
  } catch (e) {
    alert(
      "Problem retrieving media! Enable camera and microphone permissions."
    );
  }

  joinButton.addEventListener("click", () => {
    joinStage();
  });

  leaveButton.addEventListener("click", () => {
    leaveStage();
    joinButton.style = "display: inline-block";
    leaveButton.style = "display: none";
  });
};

init(); // call the function
```

## Führen Sie die Anwendung aus und geben Sie ein Token an
<a name="getting-started-pub-sub-run-app"></a>

Jetzt können Sie die Webseite lokal oder mit anderen teilen, [die Seite öffnen](#getting-started-pub-sub-web-media), ein Teilnehmer-Token eingeben und der Stage beitreten.

## Die nächsten Themen
<a name="getting-started-pub-sub-next"></a>

Ausführlichere Beispiele für npm, React und mehr finden Sie in [IVS-Broadcast-SDK: Web-Leitfaden (Anleitung zu Echtzeit-Streaming)](broadcast-web.md).

# Mit dem IVS Android Broadcast SDK veröffentlichen und abonnieren
<a name="getting-started-pub-sub-android"></a>

Dieser Abschnitt führt Sie durch die Schritte zur Veröffentlichung und zum Abonnieren einer Stage mithilfe Ihrer Android-App.

## Ansichten erstellen
<a name="getting-started-pub-sub-android-views"></a>

Wir beginnen mit der Erstellung eines einfachen Layouts für unsere Anwendung mithilfe der automatisch erstellten `activity_main.xml`-Datei. Das Layout enthält einen `EditText`, um ein Token hinzuzufügen, einen Beitreten-`Button`, eine `TextView`, um den Status der Stage anzuzeigen, und eine `CheckBox`, um die Veröffentlichung umzuschalten.

![\[Richten Sie das Veröffentlichungslayout für Ihre Android-Anwendung ein.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_Android_1.png)


Hier ist das XML hinter der Ansicht:

```
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:keepScreenOn="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".BasicActivity">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/main_controls_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/cardview_dark_background"
            android:padding="12dp"
            app:layout_constraintTop_toTopOf="parent">

            <EditText
                android:id="@+id/main_token"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:autofillHints="@null"
                android:backgroundTint="@color/white"
                android:hint="@string/token"
                android:imeOptions="actionDone"
                android:inputType="text"
                android:textColor="@color/white"
                app:layout_constraintEnd_toStartOf="@id/main_join"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <Button
                android:id="@+id/main_join"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:backgroundTint="@color/black"
                android:text="@string/join"
                android:textAllCaps="true"
                android:textColor="@color/white"
                android:textSize="16sp"
                app:layout_constraintBottom_toBottomOf="@+id/main_token"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@id/main_token" />

            <TextView
                android:id="@+id/main_state"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/state"
                android:textColor="@color/white"
                android:textSize="18sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/main_token" />

            <TextView
                android:id="@+id/main_publish_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/publish"
                android:textColor="@color/white"
                android:textSize="18sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@id/main_publish_checkbox"
                app:layout_constraintTop_toBottomOf="@id/main_token" />

            <CheckBox
                android:id="@+id/main_publish_checkbox"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:buttonTint="@color/white"
                android:checked="true"
                app:layout_constraintBottom_toBottomOf="@id/main_publish_text"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="@id/main_publish_text" />

        </androidx.constraintlayout.widget.ConstraintLayout>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/main_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintTop_toBottomOf="@+id/main_controls_container"
            app:layout_constraintBottom_toBottomOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
<layout>
```

Wir haben hier auf ein paar String-IDs verwiesen, also erstellen wir unsere gesamte `strings.xml`-Datei jetzt:

```
<resources>
    <string name="app_name">BasicRealTime</string>
    <string name="join">Join</string>
    <string name="leave">Leave</string>
    <string name="token">Participant Token</string>
    <string name="publish">Publish</string>
    <string name="state">State: %1$s</string>
</resources>
```

Lassen Sie uns diese Ansichten im XML mit unseren `MainActivity.kt` verknüpfen:

```
import android.widget.Button
import android.widget.CheckBox
import android.widget.EditText
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

private lateinit var checkboxPublish: CheckBox
private lateinit var recyclerView: RecyclerView
private lateinit var buttonJoin: Button
private lateinit var textViewState: TextView
private lateinit var editTextToken: EditText

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    checkboxPublish = findViewById(R.id.main_publish_checkbox)
    recyclerView = findViewById(R.id.main_recycler_view)
    buttonJoin = findViewById(R.id.main_join)
    textViewState = findViewById(R.id.main_state)
    editTextToken = findViewById(R.id.main_token)
}
```

Jetzt erstellen wir eine Elementansicht für unsere `RecyclerView`. Klicken Sie dazu mit der rechten Maustaste auf `res/layout`-Verzeichnis und wählen Sie **Neu > Layout-Ressourcendatei**. Benennen Sie diese Datei `item_stage_participant.xml`.

![\[Erstellen Sie eine Elementansicht für Ihre Android-App-RecyclerView.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_Android_2.png)


Das Layout für dieses Element ist einfach: Es enthält eine Ansicht zum Rendern des Videostreams eines Teilnehmers und eine Liste von Labels zur Anzeige von Informationen über den Teilnehmer:

![\[Erstellen Sie eine Elementansicht für Ihre Android-App-RecyclerView – Label.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_Android_3.png)


Hier ist das XML:

```
<?xml version="1.0" encoding="utf-8"?>
<com.amazonaws.ivs.realtime.basicrealtime.ParticipantItem xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/participant_preview_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:background="@android:color/darker_gray" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:background="#50000000"
        android:orientation="vertical"
        android:paddingLeft="4dp"
        android:paddingTop="2dp"
        android:paddingRight="4dp"
        android:paddingBottom="2dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/participant_participant_id"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            tools:text="You (Disconnected)" />

        <TextView
            android:id="@+id/participant_publishing"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            tools:text="NOT_PUBLISHED" />

        <TextView
            android:id="@+id/participant_subscribed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            tools:text="NOT_SUBSCRIBED" />

        <TextView
            android:id="@+id/participant_video_muted"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            tools:text="Video Muted: false" />

        <TextView
            android:id="@+id/participant_audio_muted"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            tools:text="Audio Muted: false" />

        <TextView
            android:id="@+id/participant_audio_level"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            tools:text="Audio Level: -100 dB" />

    </LinearLayout>

</com.amazonaws.ivs.realtime.basicrealtime.ParticipantItem>
```

Diese XML-Datei generiert eine Klasse, die wir noch nicht erstellt haben, `ParticipantItem`. Da das XML den vollständigen Namespace enthält, sollten Sie diese XML-Datei unbedingt in Ihren Namespace aktualisieren. Lassen Sie uns diese Klasse erstellen und die Ansichten einrichten, aber ansonsten lassen wir das Feld vorerst leer.

Erstellen Sie eine neue Kotlin-Klasse, `ParticipantItem`:

```
package com.amazonaws.ivs.realtime.basicrealtime

import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import android.widget.TextView
import kotlin.math.roundToInt

class ParticipantItem @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0,
    defStyleRes: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {

    private lateinit var previewContainer: FrameLayout
    private lateinit var textViewParticipantId: TextView
    private lateinit var textViewPublish: TextView
    private lateinit var textViewSubscribe: TextView
    private lateinit var textViewVideoMuted: TextView
    private lateinit var textViewAudioMuted: TextView
    private lateinit var textViewAudioLevel: TextView

    override fun onFinishInflate() {
        super.onFinishInflate()
        previewContainer = findViewById(R.id.participant_preview_container)
        textViewParticipantId = findViewById(R.id.participant_participant_id)
        textViewPublish = findViewById(R.id.participant_publishing)
        textViewSubscribe = findViewById(R.id.participant_subscribed)
        textViewVideoMuted = findViewById(R.id.participant_video_muted)
        textViewAudioMuted = findViewById(R.id.participant_audio_muted)
        textViewAudioLevel = findViewById(R.id.participant_audio_level)
    }
}
```

## Berechtigungen
<a name="getting-started-pub-sub-android-perms"></a>

Um die Kamera und das Mikrofon verwenden zu können, müssen Sie vom Benutzer Berechtigungen anfordern. Dafür folgen wir einem standardmäßigen Berechtigungsablauf:

```
override fun onStart() {
    super.onStart()
    requestPermission()
}

private val requestPermissionLauncher =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
        if (permissions[Manifest.permission.CAMERA] == true && permissions[Manifest.permission.RECORD_AUDIO] == true) {
            viewModel.permissionGranted() // we will add this later
        }
    }

private val permissions = listOf(
    Manifest.permission.CAMERA,
    Manifest.permission.RECORD_AUDIO,
)

private fun requestPermission() {
    when {
        this.hasPermissions(permissions) -> viewModel.permissionGranted() // we will add this later
        else -> requestPermissionLauncher.launch(permissions.toTypedArray())
    }
}

private fun Context.hasPermissions(permissions: List<String>): Boolean {
    return permissions.all {
        ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
    }
}
```

## Anwendungsstatus
<a name="getting-started-pub-sub-android-app-state"></a>

Unsere Anwendung verfolgt die Teilnehmer vor Ort in einer `MainViewModel.kt` und der Status wird an die `MainActivity` zurückgemeldet mit Kotlins [StateFlow](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/).

Erstellen Sie eine neue Kotlin-Klasse `MainViewModel`:

```
package com.amazonaws.ivs.realtime.basicrealtime

import android.app.Application
import androidx.lifecycle.AndroidViewModel

class MainViewModel(application: Application) : AndroidViewModel(application), Stage.Strategy, StageRenderer {

}
```

In `MainActivity.kt` verwalten wir unser Ansicht-Modell:

```
import androidx.activity.viewModels

private val viewModel: MainViewModel by viewModels()
```

Um `AndroidViewModel` und diese Kotlin-`ViewModel`-Erweiterungen zu verwenden, müssen Sie Folgendes zur `build.gradle`-Datei Ihres Moduls hinzufügen:

```
implementation 'androidx.core:core-ktx:1.10.1'
implementation "androidx.activity:activity-ktx:1.7.2"
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.10.0'
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"

def lifecycle_version = "2.6.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
```

### RecyclerView-Adapter
<a name="getting-started-pub-sub-android-app-state-recycler"></a>

Wir werden eine einfache `RecyclerView.Adapter`-Unterklasse erstellen, um unsere Teilnehmer zu verfolgen und unsere `RecyclerView` auf Stageereignisse zu aktualisieren. Aber zuerst brauchen wir eine Klasse, die einen Teilnehmer repräsentiert. Erstellen Sie eine neue Kotlin-Klasse `StageParticipant`:

```
package com.amazonaws.ivs.realtime.basicrealtime

import com.amazonaws.ivs.broadcast.Stage
import com.amazonaws.ivs.broadcast.StageStream

class StageParticipant(val isLocal: Boolean, var participantId: String?) {
    var publishState = Stage.PublishState.NOT_PUBLISHED
    var subscribeState = Stage.SubscribeState.NOT_SUBSCRIBED
    var streams = mutableListOf<StageStream>()

    val stableID: String
        get() {
            return if (isLocal) {
                "LocalUser"
            } else {
                requireNotNull(participantId)
            }
        }
}
```

Wir verwenden diese Klasse in der `ParticipantAdapter`-Klasse, die wir als Nächstes erstellen werden. Wir beginnen damit, die Klasse zu definieren und eine Variable zu erstellen, um die Teilnehmer zu verfolgen:

```
package com.amazonaws.ivs.realtime.basicrealtime

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

class ParticipantAdapter : RecyclerView.Adapter<ParticipantAdapter.ViewHolder>() {

    private val participants = mutableListOf<StageParticipant>()
```

Wir müssen auch unsere `RecyclerView.ViewHolder` definieren bevor Sie die restlichen Überschreibungen implementieren:

```
class ViewHolder(val participantItem: ParticipantItem) : RecyclerView.ViewHolder(participantItem)
```

Damit können wir die Standard-`RecyclerView.Adapter`-Überschreibung implementieren:

```
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val item = LayoutInflater.from(parent.context)
        .inflate(R.layout.item_stage_participant, parent, false) as ParticipantItem
    return ViewHolder(item)
}

override fun getItemCount(): Int {
    return participants.size
}

override fun getItemId(position: Int): Long =
    participants[position]
        .stableID
        .hashCode()
        .toLong()

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    return holder.participantItem.bind(participants[position])
}

override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
    val updates = payloads.filterIsInstance<StageParticipant>()
    if (updates.isNotEmpty()) {
        updates.forEach { holder.participantItem.bind(it) // implemented later }
    } else {
        super.onBindViewHolder(holder, position, payloads)
    }
}
```

Schließlich fügen wir neue Methoden hinzu, die wir von unserem `MainViewModel` abrufen, wenn Änderungen an den Teilnehmern vorgenommen werden. Bei diesen Methoden handelt es sich um Standard-CRUD-Operationen auf dem Adapter.

```
fun participantJoined(participant: StageParticipant) {
    participants.add(participant)
    notifyItemInserted(participants.size - 1)
}

fun participantLeft(participantId: String) {
    val index = participants.indexOfFirst { it.participantId == participantId }
    if (index != -1) {
        participants.removeAt(index)
        notifyItemRemoved(index)
    }
}

fun participantUpdated(participantId: String?, update: (participant: StageParticipant) -> Unit) {
    val index = participants.indexOfFirst { it.participantId == participantId }
    if (index != -1) {
        update(participants[index])
        notifyItemChanged(index, participants[index])
    }
}
```

Wieder in `MainViewModel` müssen wir einen Verweis auf diesen Adapter erstellen und speichern:

```
internal val participantAdapter = ParticipantAdapter()
```

## Stage-Status
<a name="getting-started-pub-sub-android-views-stage-state"></a>

Wir müssen auch einige Stagestatus innerhalb `MainViewModel` verfolgen. Definieren wir jetzt diese Eigenschaften:

```
private val _connectionState = MutableStateFlow(Stage.ConnectionState.DISCONNECTED)
val connectionState = _connectionState.asStateFlow()

private var publishEnabled: Boolean = false
    set(value) {
        field = value
        // Because the strategy returns the value of `checkboxPublish.isChecked`, just call `refreshStrategy`.
        stage?.refreshStrategy()
    }

private var deviceDiscovery: DeviceDiscovery? = null
private var stage: Stage? = null
private var streams = mutableListOf<LocalStageStream>()
```

Um Ihre eigene Vorschau zu sehen, bevor Sie eine Stage betreten, erstellen wir sofort einen lokalen Teilnehmer:

```
init {
    deviceDiscovery = DeviceDiscovery(application)

    // Create a local participant immediately to render our camera preview and microphone stats
    val localParticipant = StageParticipant(true, null)
    participantAdapter.participantJoined(localParticipant)
}
```

Wir wollen sicherstellen, dass wir diese Ressourcen bereinigen, wenn unsere `ViewModel` bereinigt ist. Wir überschreiben `onCleared()` sofort, damit wir nicht vergessen, diese Ressourcen zu reinigen.

```
override fun onCleared() {
    stage?.release()
    deviceDiscovery?.release()
    deviceDiscovery = null
    super.onCleared()
}
```

Jetzt füllen wir unsere lokale `streams`-Eigenschaft auf, sobald die Berechtigungen erteilt sind, und implementieren die `permissionsGranted`-Methode, die wir zuvor aufgerufen haben:

```
internal fun permissionGranted() {
    val deviceDiscovery = deviceDiscovery ?: return
    streams.clear()
    val devices = deviceDiscovery.listLocalDevices()
    // Camera
    devices
        .filter { it.descriptor.type == Device.Descriptor.DeviceType.CAMERA }
        .maxByOrNull { it.descriptor.position == Device.Descriptor.Position.FRONT }
        ?.let { streams.add(ImageLocalStageStream(it)) }
    // Microphone
    devices
        .filter { it.descriptor.type == Device.Descriptor.DeviceType.MICROPHONE }
        .maxByOrNull { it.descriptor.isDefault }
        ?.let { streams.add(AudioLocalStageStream(it)) }

    stage?.refreshStrategy()

    // Update our local participant with these new streams
    participantAdapter.participantUpdated(null) {
        it.streams.clear()
        it.streams.addAll(streams)
    }
}
```

## Implementierung des Stage-SDK
<a name="getting-started-pub-sub-android-stage-sdk"></a>

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

### Stage.Strategy
<a name="getting-started-pub-sub-android-stage-sdk-strategy"></a>

Die `Stage.Strategy`-Implementierung ist einfach:

```
override fun stageStreamsToPublishForParticipant(
    stage: Stage,
    participantInfo: ParticipantInfo
): MutableList<LocalStageStream> {
    // Return the camera and microphone to be published.
    // This is only called if `shouldPublishFromParticipant` returns true.
    return streams
}

override fun shouldPublishFromParticipant(stage: Stage, participantInfo: ParticipantInfo): Boolean {
    return publishEnabled
}

override fun shouldSubscribeToParticipant(stage: Stage, participantInfo: ParticipantInfo): Stage.SubscribeType {
    // Subscribe to both audio and video for all publishing participants.
    return Stage.SubscribeType.AUDIO_VIDEO
}
```

Zusammenfassend lässt sich sagen, dass wir auf der Grundlage unseres internen Status `publishEnabled` veröffentlichen, und wenn wir veröffentlichen, veröffentlichen wir die zuvor gesammelten Streams. Schließlich abonnieren wir für dieses Beispiel immer andere Teilnehmer und erhalten sowohl ihr Audio als auch ihr Video.

### StageRenderer
<a name="getting-started-pub-sub-android-stage-sdk-renderer"></a>

Die `StageRenderer`-Implementierung ist ebenfalls ziemlich einfach, obwohl sie angesichts der Anzahl der Funktionen reichlich mehr Code enthält. Der allgemeine Ansatz in diesem Renderer besteht darin, unseren `ParticipantAdapter` zu aktualisieren, wenn das SDK uns über eine Änderung an einem Teilnehmer informiert. Es gibt bestimmte Szenarien, in denen wir mit lokalen Teilnehmern anders umgehen, weil wir beschlossen haben, sie selbst zu verwalten, sodass sie ihre Kameravorschau sehen können, bevor sie beitreten.

```
override fun onError(exception: BroadcastException) {
    Toast.makeText(getApplication(), "onError ${exception.localizedMessage}", Toast.LENGTH_LONG).show()
    Log.e("BasicRealTime", "onError $exception")
}

override fun onConnectionStateChanged(
    stage: Stage,
    connectionState: Stage.ConnectionState,
    exception: BroadcastException?
) {
    _connectionState.value = connectionState
}

override fun onParticipantJoined(stage: Stage, participantInfo: ParticipantInfo) {
    if (participantInfo.isLocal) {
        // If this is the local participant joining the stage, update the participant with a null ID because we
        // manually added that participant when setting up our preview
        participantAdapter.participantUpdated(null) {
            it.participantId = participantInfo.participantId
        }
    } else {
        // If they are not local, add them normally
        participantAdapter.participantJoined(
            StageParticipant(
                participantInfo.isLocal,
                participantInfo.participantId
            )
        )
    }
}

override fun onParticipantLeft(stage: Stage, participantInfo: ParticipantInfo) {
    if (participantInfo.isLocal) {
        // If this is the local participant leaving the stage, update the ID but keep it around because
        // we want to keep the camera preview active
        participantAdapter.participantUpdated(participantInfo.participantId) {
            it.participantId = null
        }
    } else {
        // If they are not local, have them leave normally
        participantAdapter.participantLeft(participantInfo.participantId)
    }
}

override fun onParticipantPublishStateChanged(
    stage: Stage,
    participantInfo: ParticipantInfo,
    publishState: Stage.PublishState
) {
    // Update the publishing state of this participant
    participantAdapter.participantUpdated(participantInfo.participantId) {
        it.publishState = publishState
    }
}

override fun onParticipantSubscribeStateChanged(
    stage: Stage,
    participantInfo: ParticipantInfo,
    subscribeState: Stage.SubscribeState
) {
    // Update the subscribe state of this participant
    participantAdapter.participantUpdated(participantInfo.participantId) {
        it.subscribeState = subscribeState
    }
}

override fun onStreamsAdded(stage: Stage, participantInfo: ParticipantInfo, streams: MutableList<StageStream>) {
    // We don't want to take any action for the local participant because we track those streams locally
    if (participantInfo.isLocal) {
        return
    }
    // For remote participants, add these new streams to that participant's streams array.
    participantAdapter.participantUpdated(participantInfo.participantId) {
        it.streams.addAll(streams)
    }
}

override fun onStreamsRemoved(stage: Stage, participantInfo: ParticipantInfo, streams: MutableList<StageStream>) {
    // We don't want to take any action for the local participant because we track those streams locally
    if (participantInfo.isLocal) {
        return
    }
    // For remote participants, remove these streams from that participant's streams array.
    participantAdapter.participantUpdated(participantInfo.participantId) {
        it.streams.removeAll(streams)
    }
}

override fun onStreamsMutedChanged(
    stage: Stage,
    participantInfo: ParticipantInfo,
    streams: MutableList<StageStream>
) {
    // We don't want to take any action for the local participant because we track those streams locally
    if (participantInfo.isLocal) {
        return
    }
    // For remote participants, notify the adapter that the participant has been updated. There is no need to modify
    // the `streams` property on the `StageParticipant` because it is the same `StageStream` instance. Just
    // query the `isMuted` property again.
    participantAdapter.participantUpdated(participantInfo.participantId) {}
}
```

## Implementierung eines benutzerdefinierten RecyclerView-LayoutManagers
<a name="getting-started-pub-sub-android-layout"></a>

Die Festlegung verschiedener Teilnehmerzahlen kann komplex sein. Sie möchten, dass sie den gesamten Frame der übergeordneten Ansicht einnehmen, aber Sie möchten nicht jede Teilnehmerkonfiguration unabhängig voneinander handhaben. Um dies zu vereinfachen, führen wir die Implementierung eines `RecyclerView.LayoutManager` durch.

Erstelle eine weitere neue Klasse `StageLayoutManager`, welche `GridLayoutManager` erweitern soll. In diesem Kurs wird das Layout für jeden Teilnehmer anhand der Anzahl der Teilnehmer in einem flussbasierten Zeilen-/Spaltenlayout berechnet. Jede Zeile hat dieselbe Höhe wie die anderen, aber Spalten können pro Zeile unterschiedlich breit sein. Sehen Sie den Code-Kommentar über der `layouts`-Variable für eine Beschreibung, wie dieses Verhalten angepasst werden kann.

```
package com.amazonaws.ivs.realtime.basicrealtime

import android.content.Context
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView

class StageLayoutManager(context: Context?) : GridLayoutManager(context, 6) {

    companion object {
        /**
         * This 2D array contains the description of how the grid of participants should be rendered
         * The index of the 1st dimension is the number of participants needed to active that configuration
         * Meaning if there is 1 participant, index 0 will be used. If there are 5 participants, index 4 will be used.
         *
         * The 2nd dimension is a description of the layout. The length of the array is the number of rows that
         * will exist, and then each number within that array is the number of columns in each row.
         *
         * See the code comments next to each index for concrete examples.
         *
         * This can be customized to fit any layout configuration needed.
         */
        val layouts: List<List<Int>> = listOf(
            // 1 participant
            listOf(1), // 1 row, full width
            // 2 participants
            listOf(1, 1), // 2 rows, all columns are full width
            // 3 participants
            listOf(1, 2), // 2 rows, first row's column is full width then 2nd row's columns are 1/2 width
            // 4 participants
            listOf(2, 2), // 2 rows, all columns are 1/2 width
            // 5 participants
            listOf(1, 2, 2), // 3 rows, first row's column is full width, 2nd and 3rd row's columns are 1/2 width
            // 6 participants
            listOf(2, 2, 2), // 3 rows, all column are 1/2 width
            // 7 participants
            listOf(2, 2, 3), // 3 rows, 1st and 2nd row's columns are 1/2 width, 3rd row's columns are 1/3rd width
            // 8 participants
            listOf(2, 3, 3),
            // 9 participants
            listOf(3, 3, 3),
            // 10 participants
            listOf(2, 3, 2, 3),
            // 11 participants
            listOf(2, 3, 3, 3),
            // 12 participants
            listOf(3, 3, 3, 3),
        )
    }

    init {
        spanSizeLookup = object : SpanSizeLookup() {
            override fun getSpanSize(position: Int): Int {
                if (itemCount <= 0) {
                    return 1
                }
                // Calculate the row we're in
                val config = layouts[itemCount - 1]
                var row = 0
                var curPosition = position
                while (curPosition - config[row] >= 0) {
                    curPosition -= config[row]
                    row++
                }
                // spanCount == max spans, config[row] = number of columns we want
                // So spanCount / config[row] would be something like 6 / 3 if we want 3 columns.
                // So this will take up 2 spans, with a max of 6 is 1/3rd of the view.
                return spanCount / config[row]
            }
        }
    }

    override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
        if (itemCount <= 0 || state?.isPreLayout == true) return

        val parentHeight = height
        val itemHeight = parentHeight / layouts[itemCount - 1].size // height divided by number of rows.

        // Set the height of each view based on how many rows exist for the current participant count.
        for (i in 0 until childCount) {
            val child = getChildAt(i) ?: continue
            val layoutParams = child.layoutParams as RecyclerView.LayoutParams
            if (layoutParams.height != itemHeight) {
                layoutParams.height = itemHeight
                child.layoutParams = layoutParams
            }
        }
        // After we set the height for all our views, call super.
        // This works because our RecyclerView can not scroll and all views are always visible with stable IDs.
        super.onLayoutChildren(recycler, state)
    }

    override fun canScrollVertically(): Boolean = false
    override fun canScrollHorizontally(): Boolean = false
}
```

Wieder zurück in `MainActivity.kt` müssen wir den Adapter und den Layoutmanager für unsere `RecyclerView` einrichten:

```
// In onCreate after setting recyclerView.
recyclerView.layoutManager = StageLayoutManager(this)
recyclerView.adapter = viewModel.participantAdapter
```

## UI-Aktionen verbinden
<a name="getting-started-pub-sub-android-actions"></a>

Wir sind nah dran; es gibt nur ein paar UI-Aktionen, die wir verbinden müssen.

Zuerst haben wir unsere `MainActivity`, die die `StateFlow`-Änderungen von`MainViewModel` beobachtet:

```
// At the end of your onCreate method
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.CREATED) {
        viewModel.connectionState.collect { state ->
            buttonJoin.setText(if (state == ConnectionState.DISCONNECTED) R.string.join else R.string.leave)
            textViewState.text = getString(R.string.state, state.name)
        }
    }
}
```

Als Nächstes fügen wir Listener zu unseren Schaltflächen „Beitreten“ und „Veröffentlichen“ hinzu:

```
buttonJoin.setOnClickListener {
    viewModel.joinStage(editTextToken.text.toString())
}
checkboxPublish.setOnCheckedChangeListener { _, isChecked ->
    viewModel.setPublishEnabled(isChecked)
}
```

Beide der oben genannten Anruffunktionen in unserer `MainViewModel`, die wir jetzt implementieren:

```
internal fun joinStage(token: String) {
    if (_connectionState.value != Stage.ConnectionState.DISCONNECTED) {
        // If we're already connected to a stage, leave it.
        stage?.leave()
    } else {
        if (token.isEmpty()) {
            Toast.makeText(getApplication(), "Empty Token", Toast.LENGTH_SHORT).show()
            return
        }
        try {
            // Destroy the old stage first before creating a new one.
            stage?.release()
            val stage = Stage(getApplication(), token, this)
            stage.addRenderer(this)
            stage.join()
            this.stage = stage
        } catch (e: BroadcastException) {
            Toast.makeText(getApplication(), "Failed to join stage ${e.localizedMessage}", Toast.LENGTH_LONG).show()
            e.printStackTrace()
        }
    }
}

internal fun setPublishEnabled(enabled: Boolean) {
    publishEnabled = enabled
}
```

## Rendern der Teilnehmer
<a name="getting-started-pub-sub-android-participants"></a>

Schließlich müssen wir die Daten, die wir vom SDK erhalten, auf das zuvor erstellte Teilnehmerelement übertragen. Wir haben die Logik von `RecyclerView` bereits fertig, also müssen wir nur noch die API von `bind` in `ParticipantItem` implementieren.

Wir beginnen mit dem Hinzufügen der leeren Funktion und gehen sie dann Schritt für Schritt durch:

```
fun bind(participant: StageParticipant) {

}
```

Zuerst kümmern wir uns um den Status „Einfach“, die Teilnehmer-ID, den Veröffentlichungsstatus und den Abonnementstatus. Für diese aktualisieren wir einfach unsere `TextViews` direkt:

```
val participantId = if (participant.isLocal) {
    "You (${participant.participantId ?: "Disconnected"})"
} else {
    participant.participantId
}
textViewParticipantId.text = participantId
textViewPublish.text = participant.publishState.name
textViewSubscribe.text = participant.subscribeState.name
```

Als Nächstes aktualisieren wir die stummgeschalteten Audio- und Videozustände. Um den Stummschaltzustand zu erhalten, müssen wir den `ImageDevice` und `AudioDevice` aus dem Streams-Array finden. Um die Leistung zu optimieren, erinnern wir uns an die zuletzt angehängten Geräte-IDs.

```
// This belongs outside the `bind` API.
private var imageDeviceUrn: String? = null
private var audioDeviceUrn: String? = null

// This belongs inside the `bind` API.
val newImageStream = participant
    .streams
    .firstOrNull { it.device is ImageDevice }
textViewVideoMuted.text = if (newImageStream != null) {
    if (newImageStream.muted) "Video muted" else "Video not muted"
} else {
    "No video stream"
}

val newAudioStream = participant
    .streams
    .firstOrNull { it.device is AudioDevice }
textViewAudioMuted.text = if (newAudioStream != null) {
    if (newAudioStream.muted) "Audio muted" else "Audio not muted"
} else {
    "No audio stream"
}
```

Abschließend wollen wir eine Vorschau für das `imageDevice` rendern:

```
if (newImageStream?.device?.descriptor?.urn != imageDeviceUrn) {
    // If the device has changed, remove all subviews from the preview container
    previewContainer.removeAllViews()
    (newImageStream?.device as? ImageDevice)?.let {
        val preview = it.getPreviewView(BroadcastConfiguration.AspectMode.FIT)
        previewContainer.addView(preview)
        preview.layoutParams = FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT
        )
    }
}
imageDeviceUrn = newImageStream?.device?.descriptor?.urn
```

Und wir zeigen Audiostatistiken von der `audioDevice`:

```
if (newAudioStream?.device?.descriptor?.urn != audioDeviceUrn) {
    (newAudioStream?.device as? AudioDevice)?.let {
        it.setStatsCallback { _, rms ->
            textViewAudioLevel.text = "Audio Level: ${rms.roundToInt()} dB"
        }
    }
}
audioDeviceUrn = newAudioStream?.device?.descriptor?.urn
```

# Mit dem IVS iOS Broadcast SDK veröffentlichen und abonnieren
<a name="getting-started-pub-sub-ios"></a>

Dieser Abschnitt führt Sie durch die Schritte zur Veröffentlichung und zum Abonnieren einer Stage mithilfe Ihrer iOS-App.

## Ansichten erstellen
<a name="getting-started-pub-sub-ios-views"></a>

Wir beginnen mit der automatisch erstellten `ViewController.swift`-Datei, um `AmazonIVSBroadcast` zu importieren und dann fügen wir etwas `@IBOutlets` hinzu zum Verlinken:

```
import AmazonIVSBroadcast

class ViewController: UIViewController {

    @IBOutlet private var textFieldToken: UITextField!
    @IBOutlet private var buttonJoin: UIButton!
    @IBOutlet private var labelState: UILabel!
    @IBOutlet private var switchPublish: UISwitch!
    @IBOutlet private var collectionViewParticipants: UICollectionView!
```

Jetzt erstellen wir diese Ansichten und verknüpfen sie in `Main.storyboard`. Hier ist die Ansichtsstruktur, die wir verwenden werden:

![\[Verwenden Sie Main.storyboard, um eine iOS-Ansicht zu erstellen.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_iOS_1.png)


Für die AutoLayout-Konfiguration müssen wir drei Ansichten anpassen. Die erste Ansicht ist **Sammlungsansicht der Teilnehmer** (ein `UICollectionView`). Binden Sie **Führend**, **Verfolgend**, und **Unterseite** zu **Sicherer Bereich**. Binden Sie auch **Oben** zu **Steuert Container**.

![\[Passen Sie die iOS-Sammlungsansicht der Teilnehmer an.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_iOS_2.png)


Die zweite Ansicht ist **Steuert Container**. Binden Sie **Führend**, **Verfolgend**, und **Unterseite** zu **Sicherer Bereich**:

![\[Passen Sie die iOS-Ansicht „Steuert Container“ an.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_iOS_3.png)


Die dritte und letzte Ansicht ist **Vertikale Stapelansicht**. Binden Sie **Oben**,**Führend**,**Verfolgend**, und **Unterseite** zu **Superansicht**. Stellen Sie für das Styling den Abstand auf 8 statt auf 0 ein.

![\[Passen Sie die vertikale Stack-Ansicht von iOS an.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_iOS_4.png)


**UIStack-Ansichten** kümmert sich um das Layout der verbleibenden Ansichten. Für alle drei **UIStack-Ansichten**, verwenden Sie **Füllen**, sowie **Ausrichtung** und **Verteilung**.

![\[Passen Sie die verbleibenden iOS-Ansichten mit UIStack-Ansichten an.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_iOS_5.png)


Lassen Sie uns abschließend diese Ansichten mit unserem `ViewController` verknüpfen. Kartieren Sie von oben die folgenden Ansichten:
+ **Textfeld-Verknüpfung** bindet an `textFieldToken`.
+ **Schaltfläche Beitreten** bindet an `buttonJoin`.
+ **Status beschriften** bindet an `labelState`.
+ **Veröffentlichen wechseln** bindet an `switchPublish`.
+ **Sammlungsansicht der Teilnehmer** bindet an `collectionViewParticipants`.

Nutzen Sie diese Zeit auch, um das `dataSource` des Elements **Sammlungsansicht der Teilnehmer** auf den Besitz von `ViewController` einzustellen:

![\[Legen Sie die dataSource der Anwendung „Sammlungsansicht der Teilnehmer“ für iOS fest.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_iOS_6.png)


Jetzt erstellen wir die `UICollectionViewCell`-Unterklasse, in welcher die Teilnehmer gerendert werden sollen. Erstellen Sie zunächst eine neue **Cocoa-Touch-Class**-Datei:

![\[Erstellen Sie eine UICollectionViewCell, um iOS-Teilnehmer in Echtzeit zu rendern.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_iOS_7.png)


Nennen Sie sie `ParticipantUICollectionViewCell` und machen Sie es zu einer Unterklasse von `UICollectionViewCell` in Swift. Wir beginnen erneut in der Swift-Datei und erstellen unsere `@IBOutlets` zum Verlinken:

```
import AmazonIVSBroadcast

class ParticipantCollectionViewCell: UICollectionViewCell {

    @IBOutlet private var viewPreviewContainer: UIView!
    @IBOutlet private var labelParticipantId: UILabel!
    @IBOutlet private var labelSubscribeState: UILabel!
    @IBOutlet private var labelPublishState: UILabel!
    @IBOutlet private var labelVideoMuted: UILabel!
    @IBOutlet private var labelAudioMuted: UILabel!
    @IBOutlet private var labelAudioVolume: UILabel!
```

Erstellen Sie in der zugehörigen XIB-Datei diese Ansichtshierarchie:

![\[Erstellen Sie die Hierarchie der iOS-Ansicht in der zugehörigen XIB-Datei.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_iOS_8.png)


Für AutoLayout ändern wir erneut drei Ansichten. Die erste Ansicht ist **Vorschaucontainer anzeigen**. Setzen Sie **Verfolgend**, **Führend**, **Oben** und **Unterseite** zu **Sammlungsansicht der Teilnehmer Zelle**.

![\[Passen Sie die Vorschau der Container-Ansicht in der iOS-Ansicht an.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_iOS_9.png)


Die zweite Ansicht ist **Ansicht**. Setzen Sie **Führend** und **Oben**  zu **Sammlungsansicht der Teilnehmer Zelle** und ändern Sie den Wert auf 4.

![\[Passen Sie die iOS-Ansicht an.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_iOS_10.png)


Die dritte Ansicht ist **Stapelansicht**. Setzen Sie **Verfolgend**, **Führend**, **Oben** und **Unten** auf **Superansicht** und ändern Sie den Wert auf 4.

![\[Passen Sie die iOS-Stack-Ansicht an.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_iOS_11.png)


## Berechtigungen und Idle Timer
<a name="getting-started-pub-sub-ios-perms"></a>

Zurück zu unserem `ViewController`. Wir werden den System-Leerlauf-Timer deaktivieren, um zu verhindern, dass das Gerät in den Ruhemodus wechselt, während unsere Anwendung verwendet wird:

```
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // Prevent the screen from turning off during a call.
    UIApplication.shared.isIdleTimerDisabled = true
}

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

Als Nächstes fordern wir Kamera- und Mikrofonberechtigungen vom System an:

```
private func checkPermissions() {
    checkOrGetPermission(for: .video) { [weak self] granted in
        guard granted else {
            print("Video permission denied")
            return
        }
        self?.checkOrGetPermission(for: .audio) { [weak self] granted in
            guard granted else {
                print("Audio permission denied")
                return
            }
            self?.setupLocalUser() // we will cover this later
        }
    }
}

private func checkOrGetPermission(for mediaType: AVMediaType, _ result: @escaping (Bool) -> Void) {
    func mainThreadResult(_ success: Bool) {
        DispatchQueue.main.async {
            result(success)
        }
    }
    switch AVCaptureDevice.authorizationStatus(for: mediaType) {
    case .authorized: mainThreadResult(true)
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: mediaType) { granted in
            mainThreadResult(granted)
        }
    case .denied, .restricted: mainThreadResult(false)
    @unknown default: mainThreadResult(false)
    }
}
```

## Anwendungsstatus
<a name="getting-started-pub-sub-ios-app-state"></a>

Wir müssen unsere `collectionViewParticipants` konfigurieren mit der Layout-Datei, die wir zuvor erstellt haben:

```
override func viewDidLoad() {
    super.viewDidLoad()
    // We render everything to exactly the frame, so don't allow scrolling.
    collectionViewParticipants.isScrollEnabled = false
    collectionViewParticipants.register(UINib(nibName: "ParticipantCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: "ParticipantCollectionViewCell")
}
```

Um jeden Teilnehmer zu repräsentieren, erstellen wir eine einfache Struktur namens `StageParticipant`. Diese kann enthalten sein in der `ViewController.swift`-Datei, oder es kann eine neue Datei erstellt werden.

```
import Foundation
import AmazonIVSBroadcast

struct StageParticipant {
    let isLocal: Bool
    var participantId: String?
    var publishState: IVSParticipantPublishState = .notPublished
    var subscribeState: IVSParticipantSubscribeState = .notSubscribed
    var streams: [IVSStageStream] = []

    init(isLocal: Bool, participantId: String?) {
        self.isLocal = isLocal
        self.participantId = participantId
    }
}
```

Um diese Teilnehmer zu verfolgen, verwahren wir eine Reihe von ihnen als Privateigentum in unserem `ViewController`:

```
private var participants = [StageParticipant]()
```

Diese Eigenschaft wird verwendet, um unsere `UICollectionViewDataSource` anzutreiben, die  früher  vom Storyboard aus verlinkt wurde:

```
extension ViewController: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return participants.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ParticipantCollectionViewCell", for: indexPath) as? ParticipantCollectionViewCell {
            cell.set(participant: participants[indexPath.row])
            return cell
        } else {
            fatalError("Couldn't load custom cell type 'ParticipantCollectionViewCell'")
        }
    }

}
```

Um Ihre eigene Vorschau zu sehen, bevor Sie eine Stage betreten, erstellen wir sofort einen lokalen Teilnehmer:

```
override func viewDidLoad() {
    /* existing UICollectionView code */
    participants.append(StageParticipant(isLocal: true, participantId: nil))
}
```

Dies führt dazu, dass sofort nach dem Ausführen der App eine Teilnehmerzelle gerendert wird, die den lokalen Teilnehmer darstellt.

Die Benutzer möchten sich selbst sehen können, bevor sie einer Stage beitreten. Deshalb implementieren wir als Nächstes die `setupLocalUser()`-Methode, die zuvor aus dem Code zur Bearbeitung von Berechtigungen aufgerufen wurde. Wir speichern die Kamera- und Mikrofonreferenz als `IVSLocalStageStream`-Objekte.

```
private var streams = [IVSLocalStageStream]()
private let deviceDiscovery = IVSDeviceDiscovery()

private func setupLocalUser() {
    // Gather our camera and microphone once permissions have been granted
    let devices = deviceDiscovery.listLocalDevices()
    streams.removeAll()
    if let camera = devices.compactMap({ $0 as? IVSCamera }).first {
        streams.append(IVSLocalStageStream(device: camera))
        // Use a front camera if available.
        if let frontSource = camera.listAvailableInputSources().first(where: { $0.position == .front }) {
            camera.setPreferredInputSource(frontSource)
        }
    }
    if let mic = devices.compactMap({ $0 as? IVSMicrophone }).first {
        streams.append(IVSLocalStageStream(device: mic))
    }
    participants[0].streams = streams
    participantsChanged(index: 0, changeType: .updated)
}
```

Hier haben wir die Kamera und das Mikrofon des Geräts über das SDK gefunden und sie in unserem lokalen `streams`-Objekt gespeichert, dann zum `streams`-Array des ersten Teilnehmers (des lokalen Teilnehmers, den wir zuvor erstellt haben) zu unserem `streams` zugewiesen. Schließlich rufen wir `participantsChanged` mit einem `index` von 0 und `changeType` von `updated` auf. Diese Funktion ist eine Hilfsfunktion für die Aktualisierung unserer `UICollectionView` mit hübschen Animationen. So sieht es aus:

```
private func participantsChanged(index: Int, changeType: ChangeType) {
    switch changeType {
    case .joined:
        collectionViewParticipants?.insertItems(at: [IndexPath(item: index, section: 0)])
    case .updated:
        // Instead of doing reloadItems, just grab the cell and update it ourselves. It saves a create/destroy of a cell
        // and more importantly fixes some UI flicker. We disable scrolling so the index path per cell
        // never changes.
        if let cell = collectionViewParticipants?.cellForItem(at: IndexPath(item: index, section: 0)) as? ParticipantCollectionViewCell {
            cell.set(participant: participants[index])
        }
    case .left:
        collectionViewParticipants?.deleteItems(at: [IndexPath(item: index, section: 0)])
    }
}
```

Machen Sie sich jetzt keine Sorgen über `cell.set`. Darauf kommen wir später zurück, aber dort werden wir den Inhalt der Zelle basierend auf dem Teilnehmer rendern.

Der `ChangeType` ist eine einfache Aufzählung:

```
enum ChangeType {
    case joined, updated, left
}
```

Schließlich möchten wir verfolgen, ob die Stage angeschlossen ist. Wir verwenden eine einfache `bool`, um das zu verfolgen, wodurch unsere Benutzeroberfläche automatisch aktualisiert wird, wenn sie selbst aktualisiert wird.

```
private var connectingOrConnected = false {
    didSet {
        buttonJoin.setTitle(connectingOrConnected ? "Leave" : "Join", for: .normal)
        buttonJoin.tintColor = connectingOrConnected ? .systemRed : .systemBlue
    }
}
```

## Implementierung des Stage-SDK
<a name="getting-started-pub-sub-ios-stage-sdk"></a>

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

### IVSStageStrategy
<a name="getting-started-pub-sub-ios-stage-sdk-strategy"></a>

Die `IVSStageStrategy`-Implementierung ist einfach:

```
extension ViewController: IVSStageStrategy {
    func stage(_ stage: IVSStage, streamsToPublishForParticipant participant: IVSParticipantInfo) -> [IVSLocalStageStream] {
        // Return the camera and microphone to be published.
        // This is only called if `shouldPublishParticipant` returns true.
        return streams
    }

    func stage(_ stage: IVSStage, shouldPublishParticipant participant: IVSParticipantInfo) -> Bool {
        // Our publish status is based directly on the UISwitch view
        return switchPublish.isOn
    }

    func stage(_ stage: IVSStage, shouldSubscribeToParticipant participant: IVSParticipantInfo) -> IVSStageSubscribeType {
        // Subscribe to both audio and video for all publishing participants.
        return .audioVideo
    }
}
```

Zusammenfassend lässt sich sagen, dass wir nur veröffentlichen, wenn die Option „Veröffentlichen“ aktiviert ist, und wenn wir veröffentlichen, veröffentlichen wir die Streams, die wir zuvor gesammelt haben. Schließlich abonnieren wir für dieses Beispiel immer andere Teilnehmer und erhalten sowohl ihr Audio als auch ihr Video.

### IVSStageRenderer
<a name="getting-started-pub-sub-ios-stage-sdk-renderer"></a>

Die `IVSStageRenderer`-Implementierung ist ebenfalls ziemlich einfach, obwohl sie angesichts der Anzahl der Funktionen reichlich mehr Code enthält. Der allgemeine Ansatz in diesem Renderer besteht darin, unser `participants`-Array zu aktualisieren, wenn das SDK uns über eine Änderung an einen Teilnehmer informiert. Es gibt bestimmte Szenarien, in denen wir mit lokalen Teilnehmern anders umgehen, weil wir beschlossen haben, sie selbst zu verwalten, sodass sie ihre Kameravorschau sehen können, bevor sie beitreten.

```
extension ViewController: IVSStageRenderer {

    func stage(_ stage: IVSStage, didChange connectionState: IVSStageConnectionState, withError error: Error?) {
        labelState.text = connectionState.text
        connectingOrConnected = connectionState != .disconnected
    }

    func stage(_ stage: IVSStage, participantDidJoin participant: IVSParticipantInfo) {
        if participant.isLocal {
            // If this is the local participant joining the Stage, update the first participant in our array because we
            // manually added that participant when setting up our preview
            participants[0].participantId = participant.participantId
            participantsChanged(index: 0, changeType: .updated)
        } else {
            // If they are not local, add them to the array as a newly joined participant.
            participants.append(StageParticipant(isLocal: false, participantId: participant.participantId))
            participantsChanged(index: (participants.count - 1), changeType: .joined)
        }
    }

    func stage(_ stage: IVSStage, participantDidLeave participant: IVSParticipantInfo) {
        if participant.isLocal {
            // If this is the local participant leaving the Stage, update the first participant in our array because
            // we want to keep the camera preview active
            participants[0].participantId = nil
            participantsChanged(index: 0, changeType: .updated)
        } else {
            // If they are not local, find their index and remove them from the array.
            if let index = participants.firstIndex(where: { $0.participantId == participant.participantId }) {
                participants.remove(at: index)
                participantsChanged(index: index, changeType: .left)
            }
        }
    }

    func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChange publishState: IVSParticipantPublishState) {
        // Update the publishing state of this participant
        mutatingParticipant(participant.participantId) { data in
            data.publishState = publishState
        }
    }

    func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChange subscribeState: IVSParticipantSubscribeState) {
        // Update the subscribe state of this participant
        mutatingParticipant(participant.participantId) { data in
            data.subscribeState = subscribeState
        }
    }

    func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChangeMutedStreams streams: [IVSStageStream]) {
        // We don't want to take any action for the local participant because we track those streams locally
        if participant.isLocal { return }
        // For remote participants, notify the UICollectionView that they have updated. There is no need to modify
        // the `streams` property on the `StageParticipant` because it is the same `IVSStageStream` instance. Just
        // query the `isMuted` property again.
        if let index = participants.firstIndex(where: { $0.participantId == participant.participantId }) {
            participantsChanged(index: index, changeType: .updated)
        }
    }

    func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didAdd streams: [IVSStageStream]) {
        // We don't want to take any action for the local participant because we track those streams locally
        if participant.isLocal { return }
        // For remote participants, add these new streams to that participant's streams array.
        mutatingParticipant(participant.participantId) { data in
            data.streams.append(contentsOf: streams)
        }
    }

    func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didRemove streams: [IVSStageStream]) {
        // We don't want to take any action for the local participant because we track those streams locally
        if participant.isLocal { return }
        // For remote participants, remove these streams from that participant's streams array.
        mutatingParticipant(participant.participantId) { data in
            let oldUrns = streams.map { $0.device.descriptor().urn }
            data.streams.removeAll(where: { stream in
                return oldUrns.contains(stream.device.descriptor().urn)
            })
        }
    }

    // A helper function to find a participant by its ID, mutate that participant, and then update the UICollectionView accordingly.
    private func mutatingParticipant(_ participantId: String?, modifier: (inout StageParticipant) -> Void) {
        guard let index = participants.firstIndex(where: { $0.participantId == participantId }) else {
            fatalError("Something is out of sync, investigate if this was a sample app or SDK issue.")
        }

        var participant = participants[index]
        modifier(&participant)
        participants[index] = participant
        participantsChanged(index: index, changeType: .updated)
    }
}
```

Dieser Code verwendet eine Erweiterung, um den Verbindungsstatus in menschenfreundlichen Text umzuwandeln:

```
extension IVSStageConnectionState {
    var text: String {
        switch self {
        case .disconnected: return "Disconnected"
        case .connecting: return "Connecting"
        case .connected: return "Connected"
        @unknown default: fatalError()
        }
    }
}
```

## Implementierung eines benutzerdefinierten UICollectionViewLayouts
<a name="getting-started-pub-sub-ios-layout"></a>

Die Festlegung verschiedener Teilnehmerzahlen kann komplex sein. Sie möchten, dass sie den gesamten Frame der übergeordneten Ansicht einnehmen, aber Sie möchten nicht jede Teilnehmerkonfiguration unabhängig voneinander handhaben. Um dies zu vereinfachen, führen wir die Implementierung eines `UICollectionViewLayout` durch.

Erstellen Sie eine weitere neue Datei, `ParticipantCollectionViewLayout.swift`, welche `UICollectionViewLayout` erweitern soll. Diese Klasse verwendet eine andere Klasse namens `StageLayoutCalculator`, was wir bald behandeln werden. Die Klasse erhält berechnete Rahmenwerte für jeden Teilnehmer und generiert dann die notwendigen `UICollectionViewLayoutAttributes`-Objekte.

```
import Foundation
import UIKit

/**
 Code modified from https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/layouts/customizing_collection_view_layouts?language=objc
 */
class ParticipantCollectionViewLayout: UICollectionViewLayout {

    private let layoutCalculator = StageLayoutCalculator()

    private var contentBounds = CGRect.zero
    private var cachedAttributes = [UICollectionViewLayoutAttributes]()

    override func prepare() {
        super.prepare()

        guard let collectionView = collectionView else { return }

        cachedAttributes.removeAll()
        contentBounds = CGRect(origin: .zero, size: collectionView.bounds.size)

        layoutCalculator.calculateFrames(participantCount: collectionView.numberOfItems(inSection: 0),
                                         width: collectionView.bounds.size.width,
                                         height: collectionView.bounds.size.height,
                                         padding: 4)
        .enumerated()
        .forEach { (index, frame) in
            let attributes = UICollectionViewLayoutAttributes(forCellWith: IndexPath(item: index, section: 0))
            attributes.frame = frame
            cachedAttributes.append(attributes)
            contentBounds = contentBounds.union(frame)
        }
    }

    override var collectionViewContentSize: CGSize {
        return contentBounds.size
    }

    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        guard let collectionView = collectionView else { return false }
        return !newBounds.size.equalTo(collectionView.bounds.size)
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return cachedAttributes[indexPath.item]
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var attributesArray = [UICollectionViewLayoutAttributes]()

        // Find any cell that sits within the query rect.
        guard let lastIndex = cachedAttributes.indices.last, let firstMatchIndex = binSearch(rect, start: 0, end: lastIndex) else {
            return attributesArray
        }

        // Starting from the match, loop up and down through the array until all the attributes
        // have been added within the query rect.
        for attributes in cachedAttributes[..<firstMatchIndex].reversed() {
            guard attributes.frame.maxY >= rect.minY else { break }
            attributesArray.append(attributes)
        }

        for attributes in cachedAttributes[firstMatchIndex...] {
            guard attributes.frame.minY <= rect.maxY else { break }
            attributesArray.append(attributes)
        }

        return attributesArray
    }

    // Perform a binary search on the cached attributes array.
    func binSearch(_ rect: CGRect, start: Int, end: Int) -> Int? {
        if end < start { return nil }

        let mid = (start + end) / 2
        let attr = cachedAttributes[mid]

        if attr.frame.intersects(rect) {
            return mid
        } else {
            if attr.frame.maxY < rect.minY {
                return binSearch(rect, start: (mid + 1), end: end)
            } else {
                return binSearch(rect, start: start, end: (mid - 1))
            }
        }
    }
}
```

Wichtiger ist die `StageLayoutCalculator.swift`-Klasse. Es ist so konzipiert, dass es die Rahmen für jeden Teilnehmer auf der Grundlage der Anzahl der Teilnehmer in einem fließenden Zeilen-/Spaltenlayout berechnet. Jede Zeile hat dieselbe Höhe wie die anderen, aber Spalten können pro Zeile unterschiedlich breit sein. Sehen Sie den Code-Kommentar über der `layouts`-Variable für eine Beschreibung, wie dieses Verhalten angepasst werden kann.

```
import Foundation
import UIKit

class StageLayoutCalculator {

    /// This 2D array contains the description of how the grid of participants should be rendered
    /// The index of the 1st dimension is the number of participants needed to active that configuration
    /// Meaning if there is 1 participant, index 0 will be used. If there are 5 participants, index 4 will be used.
    ///
    /// The 2nd dimension is a description of the layout. The length of the array is the number of rows that
    /// will exist, and then each number within that array is the number of columns in each row.
    ///
    /// See the code comments next to each index for concrete examples.
    ///
    /// This can be customized to fit any layout configuration needed.
    private let layouts: [[Int]] = [
        // 1 participant
        [ 1 ], // 1 row, full width
        // 2 participants
        [ 1, 1 ], // 2 rows, all columns are full width
        // 3 participants
        [ 1, 2 ], // 2 rows, first row's column is full width then 2nd row's columns are 1/2 width
        // 4 participants
        [ 2, 2 ], // 2 rows, all columns are 1/2 width
        // 5 participants
        [ 1, 2, 2 ], // 3 rows, first row's column is full width, 2nd and 3rd row's columns are 1/2 width
        // 6 participants
        [ 2, 2, 2 ], // 3 rows, all column are 1/2 width
        // 7 participants
        [ 2, 2, 3 ], // 3 rows, 1st and 2nd row's columns are 1/2 width, 3rd row's columns are 1/3rd width
        // 8 participants
        [ 2, 3, 3 ],
        // 9 participants
        [ 3, 3, 3 ],
        // 10 participants
        [ 2, 3, 2, 3 ],
        // 11 participants
        [ 2, 3, 3, 3 ],
        // 12 participants
        [ 3, 3, 3, 3 ],
    ]

    // Given a frame (this could be for a UICollectionView, or a Broadcast Mixer's canvas), calculate the frames for each
    // participant, with optional padding.
    func calculateFrames(participantCount: Int, width: CGFloat, height: CGFloat, padding: CGFloat) -> [CGRect] {
        if participantCount > layouts.count {
            fatalError("Only \(layouts.count) participants are supported at this time")
        }
        if participantCount == 0 {
            return []
        }
        var currentIndex = 0
        var lastFrame: CGRect = .zero

        // If the height is less than the width, the rows and columns will be flipped.
        // Meaning for 6 participants, there will be 2 rows of 3 columns each.
        let isVertical = height > width

        let halfPadding = padding / 2.0

        let layout = layouts[participantCount - 1] // 1 participant is in index 0, so `-1`.
        let rowHeight = (isVertical ? height : width) / CGFloat(layout.count)

        var frames = [CGRect]()
        for row in 0 ..< layout.count {
            // layout[row] is the number of columns in a layout
            let itemWidth = (isVertical ? width : height) / CGFloat(layout[row])
            let segmentFrame = CGRect(x: (isVertical ? 0 : lastFrame.maxX) + halfPadding,
                                      y: (isVertical ? lastFrame.maxY : 0) + halfPadding,
                                      width: (isVertical ? itemWidth : rowHeight) - padding,
                                      height: (isVertical ? rowHeight : itemWidth) - padding)

            for column in 0 ..< layout[row] {
                var frame = segmentFrame
                if isVertical {
                    frame.origin.x = (itemWidth * CGFloat(column)) + halfPadding
                } else {
                    frame.origin.y = (itemWidth * CGFloat(column)) + halfPadding
                }
                frames.append(frame)
                currentIndex += 1
            }

            lastFrame = segmentFrame
            lastFrame.origin.x += halfPadding
            lastFrame.origin.y += halfPadding
        }
        return frames
    }

}
```

Wieder in `Main.storyboard`, stellen Sie sicher, dass Sie die Layoutklasse für die `UICollectionView` festlegen zu der Klasse, die wir gerade erstellt haben:

![\[Xcode interface showing storyboard with UICollectionView and its layout settings.\]](http://docs.aws.amazon.com/de_de/ivs/latest/RealTimeUserGuide/images/Publish_iOS_12.png)


## UI-Aktionen verbinden
<a name="getting-started-pub-sub-ios-actions"></a>

Wir sind nah dran, es gibt ein paar `IBActions` die wir erstellen müssen.

Zuerst kümmern wir uns um die „Beitreten“-Schaltfläche. Sie reagiert unterschiedlich je nach Wert von `connectingOrConnected`. Wenn sie bereits angeschlossen ist, verlässt sie einfach die Stage. Wenn die Verbindung unterbrochen ist, liest sie den Text aus dem Token `UITextField`und schafft eine neue `IVSStage` mit diesem Text. Dann fügen wir unseren `ViewController` hinzu als `strategy`, `errorDelegate`, und Renderer für `IVSStage`, und schließlich treten wir asynchron der Stage bei.

```
@IBAction private func joinTapped(_ sender: UIButton) {
    if connectingOrConnected {
        // If we're already connected to a Stage, leave it.
        stage?.leave()
    } else {
        guard let token = textFieldToken.text else {
            print("No token")
            return
        }
        // Hide the keyboard after tapping Join
        textFieldToken.resignFirstResponder()
        do {
            // Destroy the old Stage first before creating a new one.
            self.stage = nil
            let stage = try IVSStage(token: token, strategy: self)
            stage.errorDelegate = self
            stage.addRenderer(self)
            try stage.join()
            self.stage = stage
        } catch {
            print("Failed to join stage - \(error)")
        }
    }
}
```

Die andere UI-Aktion, die wir anschließen müssen, ist der Publish-Switch:

```
@IBAction private func publishToggled(_ sender: UISwitch) {
    // Because the strategy returns the value of `switchPublish.isOn`, just call `refreshStrategy`.
    stage?.refreshStrategy()
}
```

## Rendern der Teilnehmer
<a name="getting-started-pub-sub-ios-participants"></a>

Schließlich müssen wir die Daten, die wir vom SDK erhalten, in die Teilnehmerzelle rendern, die wir zuvor erstellt haben. Wir haben die Logik von `UICollectionView` bereits fertig, also müssen wir nur noch die API von `set` in `ParticipantCollectionViewCell.swift` implementieren.

Wir beginnen mit dem Hinzufügen der `empty`-Funktion und gehen Sie sie dann Schritt für Schritt durch:

```
func set(participant: StageParticipant) {
   
}
```

Zuerst kümmern wir uns um den Status „Einfach“, die Teilnehmer-ID, den Veröffentlichungsstatus und den Abonnementstatus. Für diese aktualisieren wir einfach unsere `UILabels` direkt:

```
labelParticipantId.text = participant.isLocal ? "You (\(participant.participantId ?? "Disconnected"))" : participant.participantId
labelPublishState.text = participant.publishState.text
labelSubscribeState.text = participant.subscribeState.text
```

Die Texteigenschaften der Publish- und Subscribe-Enums stammen aus lokalen Erweiterungen:

```
extension IVSParticipantPublishState {
    var text: String {
        switch self {
        case .notPublished: return "Not Published"
        case .attemptingPublish: return "Attempting to Publish"
        case .published: return "Published"
        @unknown default: fatalError()
        }
    }
}

extension IVSParticipantSubscribeState {
    var text: String {
        switch self {
        case .notSubscribed: return "Not Subscribed"
        case .attemptingSubscribe: return "Attempting to Subscribe"
        case .subscribed: return "Subscribed"
        @unknown default: fatalError()
        }
    }
}
```

Als Nächstes aktualisieren wir die stummgeschalteten Audio- und Videozustände. Um den Stummschaltzustand zu erhalten, müssen wir den `IVSImageDevice` und `IVSAudioDevice` aus dem `streams`-Array finden. Um die Leistung zu optimieren, erinnern wir uns an die zuletzt angehängten Geräte-IDs.

```
// This belongs outside `set(participant:)`
private var registeredStreams: Set<IVSStageStream> = []
private var imageDevice: IVSImageDevice? {
    return registeredStreams.lazy.compactMap { $0.device as? IVSImageDevice }.first
}
private var audioDevice: IVSAudioDevice? {
    return registeredStreams.lazy.compactMap { $0.device as? IVSAudioDevice }.first
}

// This belongs inside `set(participant:)`
let existingAudioStream = registeredStreams.first { $0.device is IVSAudioDevice }
let existingImageStream = registeredStreams.first { $0.device is IVSImageDevice }

registeredStreams = Set(participant.streams)

let newAudioStream = participant.streams.first { $0.device is IVSAudioDevice }
let newImageStream = participant.streams.first { $0.device is IVSImageDevice }

// `isMuted != false` covers the stream not existing, as well as being muted.
labelVideoMuted.text = "Video Muted: \(newImageStream?.isMuted != false)"
labelAudioMuted.text = "Audio Muted: \(newAudioStream?.isMuted != false)"
```

Abschließend wollen wir eine Vorschau für das `imageDevice` rendern und  Audiostatistiken der `audioDevice` anzeigen:

```
if existingImageStream !== newImageStream {
    // The image stream has changed
    updatePreview() // We’ll cover this next
}

if existingAudioStream !== newAudioStream {
    (existingAudioStream?.device as? IVSAudioDevice)?.setStatsCallback(nil)
    audioDevice?.setStatsCallback( { [weak self] stats in
        self?.labelAudioVolume.text = String(format: "Audio Level: %.0f dB", stats.rms)
    })
    // When the audio stream changes, it will take some time to receive new stats. Reset the value temporarily.
    self.labelAudioVolume.text = "Audio Level: -100 dB"
}
```

Die letzte Funktion, die wir erstellen müssen, ist `updatePreview()`, was unserer Ansicht eine Vorschau des Teilnehmers hinzufügt:

```
private func updatePreview() {
    // Remove any old previews from the preview container
    viewPreviewContainer.subviews.forEach { $0.removeFromSuperview() }
    if let imageDevice = self.imageDevice {
        if let preview = try? imageDevice.previewView(with: .fit) {
            viewPreviewContainer.addSubviewMatchFrame(preview)
        }
    }
}
```

Das Obige verwendet eine Hilfsfunktion auf `UIView`, um das Einbetten von Unteransichten zu vereinfachen:

```
extension UIView {
    func addSubviewMatchFrame(_ view: UIView) {
        view.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(view)
        NSLayoutConstraint.activate([
            view.topAnchor.constraint(equalTo: self.topAnchor, constant: 0),
            view.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0),
            view.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0),
            view.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0),
        ])
    }
}
```