

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

# SDK Siaran IVS \$1 Streaming Waktu Nyata
<a name="broadcast"></a>

Amazon Interactive Video Services (IVS) Real-Time Streaming broadcast SDK adalah untuk pengembang yang sedang membangun aplikasi dengan Amazon IVS. SDK ini dirancang untuk memanfaatkan arsitektur Amazon IVS dan akan melihat peningkatan berkelanjutan dan fitur baru, bersama Amazon IVS. Sebagai SDK siaran asli, SDK ini dirancang untuk meminimalkan dampak kinerja pada aplikasi Anda dan pada perangkat yang digunakan pengguna untuk mengakses aplikasi Anda.

Perhatikan bahwa SDK siaran digunakan untuk mengirim dan menerima video; yaitu, Anda menggunakan SDK yang sama untuk host dan pemirsa. Tidak diperlukan SDK pemain terpisah.

Aplikasi Anda dapat memanfaatkan fitur utama SDK siaran Amazon IVS:
+ **Streaming berkualitas tinggi** - SDK siaran mendukung streaming berkualitas tinggi. Rekam video dari kamera Anda dan encode hingga 720p.
+ **Penyesuaian Bitrate Otomatis** — Pengguna ponsel cerdas bersifat mobile, sehingga kondisi jaringan mereka dapat berubah sepanjang siaran. SDK siaran Amazon IVS secara otomatis menyesuaikan bitrate video untuk mengakomodasi perubahan kondisi jaringan.
+ **Dukungan Potret dan Lansekap** — Tidak peduli bagaimana pengguna Anda memegang perangkat mereka, gambar akan muncul di sisi kanan atas dan diskalakan dengan benar. SDK siaran mendukung ukuran kanvas potret dan lanskap. Ini secara otomatis mengelola rasio aspek ketika pengguna memutar perangkat mereka dari orientasi yang dikonfigurasi.
+ **Streaming Aman** — Siaran pengguna Anda dienkripsi menggunakan TLS, sehingga mereka dapat menjaga aliran mereka tetap aman.
+ **Perangkat Audio Eksternal** - SDK siaran Amazon IVS mendukung jack audio, USB, dan mikrofon eksternal Bluetooth SCO.

## Persyaratan Platform
<a name="broadcast-platform-requirements"></a>

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


| Platform | Versi yang Didukung | 
| --- | --- | 
| Android |  9.0\$1 -- perhatikan pelanggan dapat membangun dengan versi 6.0\$1 tetapi tidak akan dapat menggunakan fungsionalitas streaming real-time.  | 
| iOS |  14\$1  | 

IVS mendukung minimal 4 versi iOS utama dan 6 versi Android utama. Dukungan versi kami saat ini dapat melampaui batas minimum ini. Pelanggan akan diberi tahu melalui catatan rilis SDK setidaknya 3 bulan sebelum versi utama tidak lagi didukung.

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


| Peramban | Platform yang Didukung | Versi yang Didukung | 
| --- | --- | --- | 
| Chrome | Windows, macOS | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 
| Firefox | Windows, macOS | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 
| Edge | Windows 8.1\$1 | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) Tidak termasuk Edge Legacy | 
| Safari | macOS | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 

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


| Peramban | Platform yang Didukung | Versi yang Didukung | 
| --- | --- | --- | 
| Chrome | iOS, Android | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 
| Firefox | Android | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 
| Safari | iOS | Dua versi utama (versi saat ini dan versi terbaru sebelumnya) | 

#### Keterbatasan yang Sudah Diketahui
<a name="browser-mobile-limitations"></a>
+ Di semua browser web seluler, kami merekomendasikan publishing/subscribing dengan tidak lebih dari tiga penerbit simultan, karena kendala kinerja yang menyebabkan artefak video dan layar hitam. Jika Anda memerlukan lebih banyak penerbit, konfigurasikan publikasi dan [berlangganan khusus audio](web-publish-subscribe.md#web-publish-subscribe-concepts-strategy-updates).
+ Kami tidak menyarankan untuk menyusun panggung dan menyiarkannya ke saluran di Android Mobile Web, karena pertimbangan kinerja dan potensi crash. Jika fungsionalitas siaran diperlukan, integrasikan [SDK siaran Android streaming real-time IVS](broadcast-android.md).

## Tampilan Web
<a name="broadcast-webviews"></a>

SDK siaran Web tidak menyediakan dukungan untuk tampilan web atau lingkungan seperti web (TVs, konsol, dll). [Untuk implementasi seluler, lihat Panduan SDK Siaran Streaming Waktu Nyata untuk [Android](broadcast-android.md) dan iOS.](broadcast-ios.md)

## Akses Perangkat yang Diperlukan
<a name="broadcast-device-access"></a>

SDK siaran memerlukan akses ke kamera dan mikrofon perangkat, baik yang terpasang di perangkat maupun yang terhubung melalui Bluetooth, USB, atau jack audio.

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

SDK siaran terus ditingkatkan. Lihat [Catatan Rilis Amazon IVS](release-notes.md) untuk versi yang tersedia dan masalah yang diperbaiki. Jika perlu, sebelum menghubungi dukungan, perbarui versi SDK siaran Anda dan lihat apakah itu menyelesaikan masalah Anda.

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

Siaran Amazon IVS SDKs menggunakan versi [semantik](https://semver.org/).

Untuk diskusi ini, misalkan:
+ Rilis terbaru adalah 4.1.3.
+ Rilis terbaru dari versi utama sebelumnya adalah 3.2.4.
+ Rilis terbaru versi 1.x adalah 1.5.6.

Fitur baru yang kompatibel dengan versi sebelumnya ditambahkan sebagai rilis minor dari versi terbaru. Dalam hal ini, rangkaian fitur baru berikutnya akan ditambahkan sebagai versi 4.2.0.

Perbaikan bug minor yang kompatibel dengan versi sebelumnya ditambahkan sebagai rilis patch dari versi terbaru. Di sini, set perbaikan bug minor berikutnya akan ditambahkan sebagai versi 4.1.4.

Perbaikan bug besar yang kompatibel dengan versi sebelumnya ditangani secara berbeda; perbaikan ini ditambahkan ke beberapa versi:
+ Rilis patch versi terbaru. Di sini, ini adalah versi 4.1.4.
+ Rilis patch dari versi kecil sebelumnya. Di sini, ini adalah versi 3.2.5.
+ Rilis patch dari rilis versi 1.x terbaru. Di sini, ini adalah versi 1.5.7.

Perbaikan bug besar ditentukan oleh tim produk Amazon IVS. Contoh umumnya adalah pembaruan keamanan yang penting dan perbaikan pilihan lainnya yang diperlukan pelanggan.

**Catatan:** Dalam contoh di atas, versi yang dirilis meningkat tanpa melewatkan angka apa pun (misalnya, dari 4.1.3 ke 4.1.4). Pada kenyataannya, satu atau beberapa nomor patch mungkin tetap bersifat internal dan tidak dirilis, sehingga versi yang dirilis dapat meningkat dari 4.1.3 menjadi, katakanlah, 4.1.6.

# SDK Siaran IVS: Panduan Web \$1 Streaming Waktu Nyata
<a name="broadcast-web"></a>

IVS real-time streaming Web broadcast SDK memberi pengembang alat untuk membangun pengalaman interaktif dan real-time di web. SDK ini untuk pengembang yang sedang membangun aplikasi web dengan Amazon IVS.

Web broadcast SDK memungkinkan peserta untuk mengirim dan menerima video. SDK mendukung operasi berikut:
+ Bergabunglah dengan panggung
+ Publikasikan media ke peserta lain di panggung
+ Berlangganan media dari peserta lain di panggung
+ Kelola dan pantau video dan audio yang dipublikasikan ke panggung
+ Dapatkan statistik WebRTC untuk setiap koneksi rekan
+ Semua operasi dari SDK siaran Web streaming latensi rendah IVS

**Versi terbaru dari Web broadcast SDK:** [1.34.0 (Catatan Rilis)](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#apr09-26-broadcast-web-rt) 

**Dokumentasi referensi:** [Untuk informasi tentang metode terpenting yang tersedia di Amazon IVS Web Broadcast SDK, lihat https://aws.github. io/amazon-ivs-web-broadcast/docs/sdk-referensi](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference). Pastikan versi SDK terbaru dipilih.

**Contoh kode**: Sampel di bawah ini adalah tempat yang baik untuk memulai dengan cepat dengan SDK:
+ [Pemutaran Sederhana](https://codepen.io/amazon-ivs/pen/RNwVBRK)
+ [Penerbitan dan Berlangganan Sederhana](https://codepen.io/amazon-ivs/pen/ZEqgrpo)
+ [Demo Kolaborasi Real-Time React Komprehensif](https://github.com/aws-samples/amazon-ivs-real-time-collaboration-web-demo/tree/main)

**Persyaratan platform**: Lihat [Amazon IVS Broadcast SDK](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/broadcast.html) untuk daftar platform yang didukung

**Catatan:** Menerbitkan dari browser nyaman bagi pengguna akhir karena tidak memerlukan menginstal perangkat lunak tambahan. Namun, penerbitan berbasis browser tunduk pada kendala dan variabilitas lingkungan browser. Jika Anda perlu memprioritaskan stabilitas (misalnya, untuk streaming acara), kami umumnya merekomendasikan penerbitan dari sumber non-browser (misalnya, OBS Studio atau encoder khusus lainnya), yang sering memiliki akses langsung ke sumber daya sistem dan menghindari keterbatasan browser. Untuk informasi lebih lanjut tentang opsi penerbitan non-browser, lihat dokumentasi [Stream Ingest](rt-stream-ingest.md).

# Memulai dengan SDK Siaran Web IVS \$1 Streaming Waktu Nyata
<a name="broadcast-web-getting-started"></a>

Dokumen ini membawa Anda melalui langkah-langkah yang terlibat dalam memulai dengan SDK siaran Web streaming real-time IVS.

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

Blok bangunan untuk waktu nyata terletak di namespace yang berbeda dari modul penyiaran root.

### Menggunakan Tag Skrip
<a name="broadcast-web-getting-started-imports-script"></a>

SDK siaran Web didistribusikan sebagai JavaScript perpustakaan dan dapat diambil di [https://web-broadcast.live-video.net/1.34.0/ amazon-ivs-web-broadcast](https://web-broadcast.live-video.net/1.34.0/amazon-ivs-web-broadcast.js) .js.

Kelas dan enum yang didefinisikan dalam contoh di bawah ini dapat ditemukan pada objek `IVSBroadcastClient` global:

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

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

Untuk menginstal `npm` paket: 

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

Kelas, enum, dan tipe juga dapat diimpor dari modul paket:

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

### Dukungan Rendering Sisi Server
<a name="broadcast-web-getting-started-imports-server-side-rendering"></a>

Pustaka Tahapan SDK Siaran Web tidak dapat dimuat dalam konteks sisi server, karena mereferensikan primitif browser yang diperlukan untuk fungsi pustaka saat dimuat. Untuk mengatasinya, muat pustaka secara dinamis, seperti yang ditunjukkan dalam [Demo Siaran Web menggunakan Next dan React](https://github.com/aws-samples/amazon-ivs-broadcast-web-demo/blob/main/hooks/useBroadcastSDK.js#L26-L31).

## Permintaan Izin
<a name="broadcast-web-request-permissions"></a>

Aplikasi Anda harus meminta izin untuk mengakses kamera dan mikrofon pengguna, dan harus disajikan menggunakan HTTPS. (Ini tidak khusus untuk Amazon IVS; diperlukan untuk situs web apa pun yang membutuhkan akses ke kamera dan mikrofon.)

Berikut adalah contoh fungsi yang menunjukkan bagaimana Anda dapat meminta dan menangkap izin untuk perangkat audio dan video:

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

[Untuk informasi tambahan, lihat [API Izin](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API) danMediaDevices. getUserMedia()](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia).

## Daftar Perangkat yang Tersedia
<a name="broadcast-web-request-list-devices"></a>

Untuk melihat perangkat apa yang tersedia untuk ditangkap, kueri metode [MediaDevices.enumerateDevices](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices) () browser:

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

## Mengambil MediaStream dari Perangkat
<a name="broadcast-web-retrieve-mediastream"></a>

Setelah memperoleh daftar perangkat yang tersedia, Anda dapat mengambil aliran dari sejumlah perangkat. Misalnya, Anda dapat menggunakan `getUserMedia()` metode ini untuk mengambil aliran dari kamera.

Jika Anda ingin menentukan perangkat mana yang akan menangkap aliran, Anda dapat secara eksplisit menyetel `video` bagian `audio` atau batasan media. `deviceId` Sebagai alternatif, Anda dapat menghilangkan `deviceId` dan meminta pengguna memilih perangkat mereka dari prompt browser.

Anda juga dapat menentukan resolusi kamera yang ideal menggunakan `width` dan `height` kendala. [(Baca lebih lanjut tentang kendala ini di sini.)](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#properties_of_video_tracks) SDK secara otomatis menerapkan batasan lebar dan tinggi yang sesuai dengan resolusi siaran maksimum Anda; namun, sebaiknya Anda juga menerapkannya sendiri untuk memastikan bahwa rasio aspek sumber tidak berubah setelah Anda menambahkan sumber ke SDK.

Untuk streaming real-time, pastikan media dibatasi hingga resolusi 720p. Secara khusus, nilai Anda `getUserMedia` dan `getDisplayMedia` batasan untuk lebar dan tinggi tidak boleh melebihi 921600 (1280\$1 720) saat dikalikan bersama. 

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

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

# Menerbitkan & Berlangganan dengan SDK Siaran Web IVS \$1 Streaming Waktu Nyata
<a name="web-publish-subscribe"></a>

Dokumen ini membawa Anda melalui langkah-langkah yang terlibat dalam penerbitan dan berlangganan ke panggung menggunakan SDK siaran Web streaming real-time IVS.

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

Tiga konsep inti mendasari fungsionalitas waktu nyata: [panggung](#web-publish-subscribe-concepts-stage), [strategi](#web-publish-subscribe-concepts-strategy), dan [acara](#web-publish-subscribe-concepts-events). Tujuan desain adalah meminimalkan jumlah logika sisi klien yang diperlukan untuk membangun produk yang berfungsi.

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

`Stage`Kelas adalah titik utama interaksi antara aplikasi host dan SDK. Ini mewakili panggung itu sendiri dan digunakan untuk bergabung dan meninggalkan panggung. Membuat dan menggabungkan tahap membutuhkan string token yang valid dan belum kedaluwarsa dari bidang kontrol (direpresentasikan sebagai`token`). Bergabung dan meninggalkan panggung itu sederhana:

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

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

stage.leave();
```

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

`StageStrategy`Antarmuka menyediakan cara bagi aplikasi host untuk mengkomunikasikan status tahap yang diinginkan ke SDK. Tiga fungsi perlu diimplementasikan:`shouldSubscribeToParticipant`,`shouldPublishParticipant`, dan`stageStreamsToPublish`. Semua dibahas di bawah ini.

Untuk menggunakan strategi yang ditentukan, berikan ke `Stage` konstruktor. Berikut ini adalah contoh lengkap aplikasi yang menggunakan strategi untuk mempublikasikan webcam peserta ke panggung dan berlangganan semua peserta. Setiap tujuan fungsi strategi yang diperlukan dijelaskan secara rinci di bagian selanjutnya.

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

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

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

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

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

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

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


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

#### Berlangganan Peserta
<a name="web-publish-subscribe-concepts-strategy-participants"></a>

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

Ketika peserta jarak jauh bergabung dengan panggung, SDK menanyakan aplikasi host tentang status langganan yang diinginkan untuk peserta tersebut. Pilihannya adalah`NONE`,`AUDIO_ONLY`, dan`AUDIO_VIDEO`. Saat mengembalikan nilai untuk fungsi ini, aplikasi host tidak perlu khawatir tentang status publikasi, status langganan saat ini, atau status koneksi tahap. Jika `AUDIO_VIDEO` dikembalikan, SDK menunggu hingga peserta jarak jauh mempublikasikan sebelum berlangganan, dan memperbarui aplikasi host dengan memancarkan peristiwa selama proses berlangsung.

Berikut adalah contoh implementasi:

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

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

Ini adalah implementasi lengkap dari fungsi ini untuk aplikasi host yang selalu ingin semua peserta untuk melihat satu sama lain; misalnya, aplikasi obrolan video.

Implementasi yang lebih maju juga dimungkinkan. Misalnya, asumsikan aplikasi menyediakan `role` atribut saat membuat token dengan CreateParticipantToken. Aplikasi dapat menggunakan `attributes` properti `StageParticipantInfo` untuk berlangganan peserta secara selektif berdasarkan atribut yang disediakan server:

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

Ini dapat digunakan untuk membuat panggung di mana moderator dapat memantau semua tamu tanpa terlihat atau didengar sendiri. Aplikasi host dapat menggunakan logika bisnis tambahan untuk membiarkan moderator melihat satu sama lain tetapi tetap tidak terlihat oleh tamu.

#### Konfigurasi untuk Berlangganan Peserta
<a name="web-publish-subscribe-concepts-strategy-participants-config"></a>

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

Jika peserta jarak jauh sedang berlangganan (lihat [Berlangganan Peserta](#web-publish-subscribe-concepts-strategy-participants)), SDK akan menanyakan aplikasi host tentang konfigurasi langganan khusus untuk peserta tersebut. Konfigurasi ini bersifat opsional dan memungkinkan aplikasi host untuk mengontrol aspek-aspek tertentu dari perilaku pelanggan. Untuk informasi tentang apa yang dapat dikonfigurasi, lihat [SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration)di dokumentasi referensi SDK.

Berikut adalah contoh implementasi:

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

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

Implementasi ini memperbarui penundaan minimum jitter-buffer untuk semua peserta berlangganan ke preset. `MEDIUM`

Seperti halnya`shouldSubscribeToParticipant`, implementasi yang lebih maju dimungkinkan. Yang diberikan `ParticipantInfo` dapat digunakan untuk memperbarui konfigurasi berlangganan secara selektif untuk peserta tertentu.

Sebaiknya gunakan perilaku default. Tentukan konfigurasi khusus hanya jika ada perilaku tertentu yang ingin Anda ubah.

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

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

Setelah terhubung ke panggung, SDK menanyakan aplikasi host untuk melihat apakah peserta tertentu harus mempublikasikannya. Ini hanya dipanggil pada peserta lokal yang memiliki izin untuk mempublikasikan berdasarkan token yang disediakan.

Berikut adalah contoh implementasi:

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

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

Ini untuk aplikasi obrolan video standar di mana pengguna selalu ingin mempublikasikan. Mereka dapat membisukan dan menonaktifkan audio dan video mereka, untuk langsung disembunyikan atau seen/heard. (They also can use publish/unpublish, but that is much slower. Mute/unmute lebih disukai untuk kasus penggunaan di mana mengubah visibilitas sering diinginkan.)

#### Memilih Streaming untuk Publikasikan
<a name="web-publish-subscribe-concepts-strategy-streams"></a>

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

Saat menerbitkan, ini digunakan untuk menentukan aliran audio dan video apa yang harus dipublikasikan. Ini dibahas secara lebih rinci nanti di [Publish a Media Stream](#web-publish-subscribe-publish-stream).

#### Memperbarui Strategi
<a name="web-publish-subscribe-concepts-strategy-updates"></a>

Strategi ini dimaksudkan untuk menjadi dinamis: nilai yang dikembalikan dari salah satu fungsi di atas dapat diubah kapan saja. Misalnya, jika aplikasi host tidak ingin mempublikasikan sampai pengguna akhir mengetuk tombol, Anda dapat mengembalikan variabel dari `shouldPublishParticipant` (sesuatu seperti`hasUserTappedPublishButton`). Ketika variabel itu berubah berdasarkan interaksi oleh pengguna akhir, panggil `stage.refreshStrategy()` untuk memberi sinyal ke SDK bahwa ia harus menanyakan strategi untuk nilai terbaru, hanya menerapkan hal-hal yang telah berubah. Jika SDK mengamati bahwa `shouldPublishParticipant` nilainya telah berubah, SDK memulai proses publikasi. Jika kueri SDK dan semua fungsi mengembalikan nilai yang sama seperti sebelumnya, `refreshStrategy` panggilan tidak mengubah tahapan.

Jika nilai pengembalian `shouldSubscribeToParticipant` perubahan dari `AUDIO_VIDEO` ke`AUDIO_ONLY`, aliran video akan dihapus untuk semua peserta dengan nilai yang dikembalikan diubah, jika aliran video sudah ada sebelumnya.

Umumnya, tahap menggunakan strategi untuk menerapkan perbedaan antara strategi sebelumnya dan saat ini secara efisien, tanpa aplikasi host perlu khawatir tentang semua keadaan yang diperlukan untuk mengelolanya dengan benar. Karena itu, anggap menelepon `stage.refreshStrategy()` sebagai operasi yang murah, karena tidak melakukan apa-apa kecuali strateginya berubah.

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

`Stage`Instance adalah emitor peristiwa. Menggunakan`stage.on()`, keadaan panggung dikomunikasikan ke aplikasi host. Pembaruan untuk UI aplikasi host biasanya dapat didukung sepenuhnya oleh peristiwa. Peristiwa-peristiwa tersebut adalah sebagai berikut:

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

Untuk sebagian besar acara ini, yang sesuai `ParticipantInfo` disediakan.

Tidak diharapkan bahwa informasi yang diberikan oleh peristiwa berdampak pada nilai pengembalian strategi. Misalnya, nilai pengembalian `shouldSubscribeToParticipant` tidak diharapkan berubah ketika `STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED` dipanggil. Jika aplikasi host ingin berlangganan ke peserta tertentu, itu harus mengembalikan jenis langganan yang diinginkan terlepas dari status publikasi peserta tersebut. SDK bertanggung jawab untuk memastikan bahwa keadaan strategi yang diinginkan ditindaklanjuti pada waktu yang tepat berdasarkan keadaan tahap.

## Publikasikan Aliran Media
<a name="web-publish-subscribe-publish-stream"></a>

Perangkat lokal seperti mikrofon dan kamera diambil menggunakan langkah yang sama seperti yang diuraikan di atas dalam [Ambil MediaStream ](broadcast-web-getting-started.md#broadcast-web-retrieve-mediastream) dari Perangkat. Dalam contoh yang kita gunakan `MediaStream` untuk membuat daftar `LocalStageStream` objek yang digunakan untuk penerbitan oleh SDK:

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

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

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

## Publikasikan Screenshare
<a name="web-publish-subscribe-publish-screenshare"></a>

Aplikasi sering perlu mempublikasikan screenshare selain kamera web pengguna. Menerbitkan screenshare mengharuskan pembuatan token tambahan untuk panggung, khususnya untuk menerbitkan media screenshare. Gunakan `getDisplayMedia` dan batasi resolusi hingga maksimum 720p. Setelah itu, langkah-langkahnya mirip dengan menerbitkan kamera ke panggung.

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

## Tampilkan dan Hapus Peserta
<a name="web-publish-subscribe-participants"></a>

Setelah berlangganan selesai, Anda menerima berbagai `StageStream` objek melalui `STAGE_PARTICIPANT_STREAMS_ADDED` acara. Acara ini juga memberi Anda info peserta untuk membantu saat menampilkan aliran media:

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

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

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

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

Ketika peserta berhenti menerbitkan atau berhenti berlangganan dari aliran, `STAGE_PARTICIPANT_STREAMS_REMOVED` fungsi dipanggil dengan aliran yang telah dihapus. Aplikasi host harus menggunakan ini sebagai sinyal untuk menghapus aliran video peserta dari DOM.

`STAGE_PARTICIPANT_STREAMS_REMOVED`dipanggil untuk semua skenario di mana aliran mungkin dihapus, termasuk:
+ Peserta jarak jauh berhenti menerbitkan.
+ Perangkat lokal berhenti berlangganan atau mengubah langganan dari ke`AUDIO_VIDEO`. `AUDIO_ONLY`
+ Peserta jarak jauh meninggalkan panggung.
+ Peserta lokal meninggalkan panggung.

Karena `STAGE_PARTICIPANT_STREAMS_REMOVED` dipanggil untuk semua skenario, tidak diperlukan logika bisnis khusus untuk menghapus peserta dari UI selama operasi cuti jarak jauh atau lokal.

## Bisukan dan Bunyikan Streaming Media
<a name="web-publish-subscribe-mute-streams"></a>

`LocalStageStream`objek memiliki `setMuted` fungsi yang mengontrol apakah aliran diredam. Fungsi ini dapat dipanggil pada aliran sebelum atau sesudah dikembalikan dari fungsi `stageStreamsToPublish` strategi.

**Penting**: Jika instance `LocalStageStream` objek baru dikembalikan `stageStreamsToPublish` setelah panggilan ke`refreshStrategy`, status bisu objek aliran baru diterapkan ke panggung. Hati-hati saat membuat `LocalStageStream` instance baru untuk memastikan status bisu yang diharapkan dipertahankan.

## Pantau Status Bisu Media Peserta Jarak Jauh
<a name="web-publish-subscribe-mute-state"></a>

Saat peserta mengubah status bisu video atau audio mereka, `STAGE_STREAM_MUTE_CHANGED` acara dipicu dengan daftar aliran yang telah berubah. Gunakan `isMuted` properti `StageStream` untuk memperbarui UI Anda sesuai dengan itu:

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

Selain itu, Anda dapat melihat [StageParticipantInfo](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference#stageparticipantinfo)informasi negara tentang apakah audio atau video diredam:

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

## Dapatkan Statistik WebRTC
<a name="web-publish-subscribe-webrtc-stats"></a>

`requestQualityStats()`Metode ini menyediakan akses ke statistik WebRTC terperinci untuk aliran lokal dan jarak jauh. Ini tersedia pada keduanya LocalStageStream dan RemoteStageStream objek. Ini mengembalikan metrik kualitas yang komprehensif termasuk kualitas jaringan, statistik paket, informasi bitrate, dan metrik terkait bingkai.

Ini adalah metode asinkron yang dengannya Anda dapat mengambil statistik baik melalui await atau dengan merantai janji. Ini kembali `undefined` ketika statistik tidak tersedia; misalnya, aliran tidak aktif atau statistik internal tidak tersedia. Jika statistik tersedia, dan tergantung pada aliran (jarak jauh atau lokal, video atau audio), metode mengembalikan [LocalVideoStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/LocalVideoStats), [LocalAudioStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/LocalAudioStats), [RemoteVideoStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/RemoteVideoStats), atau [RemoteAudioStats](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/RemoteAudioStats)objek.

Perhatikan bahwa untuk streaming video dengan simulcast, array berisi beberapa objek stat (satu per lapisan).

**Praktik Terbaik**
+ Frekuensi polling - Panggilan `requestQualityStats()` pada interval yang wajar (1-5 detik) untuk menghindari dampak kinerja
+ Penanganan kesalahan - Selalu periksa apakah nilai yang dikembalikan `undefined` sebelum diproses
+ Manajemen memori — Hapus intervals/timeouts saat aliran tidak lagi diperlukan
+ Kualitas jaringan — Gunakan `networkQuality` untuk umpan balik pengguna mengenai kemungkinan degradasi yang disebabkan oleh jaringan. Lihat perinciannya di [NetworkQuality](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/enumerations/NetworkQuality).

**Contoh Penggunaan**

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

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

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

## Mengoptimalkan Media
<a name="web-publish-subscribe-optimizing-media"></a>

Disarankan untuk membatasi `getUserMedia` dan `getDisplayMedia` memanggil ke batasan berikut untuk kinerja terbaik:

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

Anda selanjutnya dapat membatasi media melalui opsi tambahan yang diteruskan ke `LocalStageStream` konstruktor:

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

Dalam kode di atas:
+ `minBitrate`menetapkan bitrate minimum yang diharapkan digunakan browser. Namun, aliran video dengan kompleksitas rendah dapat mendorong encoder lebih rendah dari bitrate ini.
+ `maxBitrate`menetapkan bitrate maksimum yang diharapkan tidak melebihi browser untuk aliran ini.
+ `maxFramerate`menetapkan frame rate maksimum yang diharapkan tidak melebihi browser untuk aliran ini.
+ `simulcast`Opsi ini hanya dapat digunakan di browser berbasis Chromium. Ini memungkinkan pengiriman tiga lapisan rendisi aliran.
  + Hal ini memungkinkan server untuk memilih rendisi mana yang akan dikirim ke peserta lain, berdasarkan keterbatasan jaringan mereka.
  + Ketika `simulcast` ditentukan bersama dengan `maxBitrate` and/or `maxFramerate` nilai, diharapkan bahwa lapisan rendisi tertinggi akan dikonfigurasi dengan nilai-nilai ini dalam pikiran, asalkan `maxBitrate` tidak berada di bawah `maxBitrate` nilai default lapisan tertinggi kedua SDK internal yaitu 900 kbps.
  + Jika `maxBitrate` ditentukan sebagai terlalu rendah dibandingkan dengan nilai default lapisan tertinggi kedua, `simulcast` akan dinonaktifkan.
  + `simulcast`tidak dapat dinyalakan dan dimatikan tanpa menerbitkan ulang media melalui kombinasi `shouldPublishParticipant` pengembalian`false`, panggilan, pengembalian, `true` dan panggilan `refreshStrategy` `shouldPublishParticipant` lagi. `refreshStrategy`

## Dapatkan Atribut Peserta
<a name="web-publish-subscribe-participant-attributes"></a>

Jika Anda menentukan atribut dalam permintaan `CreateParticipantToken` operasi, Anda dapat melihat atribut di `StageParticipantInfo` properti:

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

## Informasi Peningkatan Tambahan (SEI)
<a name="web-publish-subscribe-sei-attributes"></a>

Unit Supplemental Enhancement Information (SEI) NAL digunakan untuk menyimpan metadata yang selaras dengan bingkai di samping video. Ini dapat digunakan saat menerbitkan dan berlangganan aliran video H.264. Payload SEI tidak dijamin sampai ke pelanggan, terutama dalam kondisi jaringan yang buruk. Karena muatan SEI menyimpan data secara langsung dalam struktur bingkai H.264, kemampuan ini tidak dapat dimanfaatkan untuk aliran audio saja.

### Memasukkan Muatan SEI
<a name="sei-attributes-inserting-sei-payloads"></a>

Klien penerbitan dapat memasukkan muatan SEI ke aliran panggung yang sedang dipublikasikan dengan mengonfigurasi video mereka LocalStageStream untuk mengaktifkan `inBandMessaging` dan kemudian menjalankan metode. `insertSeiMessage` Perhatikan bahwa mengaktifkan `inBandMessaging` meningkatkan penggunaan memori SDK.

Muatan harus dari [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)jenisnya. Ukuran muatan harus lebih besar dari 0KB dan kurang dari 1KB. Jumlah pesan SEI yang dimasukkan per detik tidak boleh melebihi 10KB per detik.

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

#### Mengulangi Muatan SEI
<a name="sei-attributes-repeating-sei-payloads"></a>

Opsional menyediakan `repeatCount` untuk mengulangi penyisipan muatan SEI untuk N frame berikutnya yang dikirim. Ini dapat membantu untuk mengurangi kerugian bawaan yang mungkin terjadi karena protokol transportasi UDP yang mendasari yang digunakan untuk mengirim video. Perhatikan nilai ini harus antara 0 dan 30. Klien yang menerima harus memiliki logika untuk menghapus duplikat pesan.

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

### Membaca Muatan SEI
<a name="sei-attributes-reading-sei-payloads"></a>

Klien berlangganan dapat membaca muatan SEI dari penerbit yang menerbitkan video H.264 jika ada dengan mengonfigurasi pelanggan `SubscribeConfiguration` untuk mengaktifkan `inBandMessaging` dan mendengarkan acara, seperti yang ditunjukkan pada `StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED` contoh berikut:

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

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

## Pengkodean Berlapis dengan Simulcast
<a name="web-publish-subscribe-layered-encoding-simulcast"></a>

Layered encoding dengan simulcast adalah fitur streaming real-time IVS yang memungkinkan penerbit mengirim beberapa lapisan video berkualitas berbeda, dan pelanggan untuk mengubah lapisan tersebut secara dinamis atau manual. Fitur ini dijelaskan lebih lanjut dalam dokumen [Pengoptimalan Streaming](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/real-time-streaming-optimization.html).

### Mengkonfigurasi Layered Encoding (Publisher)
<a name="web-layered-encoding-simulcast-configure-publisher"></a>

Sebagai penerbit, untuk mengaktifkan pengkodean berlapis dengan simulcast, tambahkan konfigurasi berikut ke instantiasi saat Anda: `LocalStageStream`

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

*Bergantung pada resolusi input perangkat kamera Anda, sejumlah lapisan akan dikodekan dan dikirim seperti yang didefinisikan di bagian [Default Layers, Quality, dan Framerates](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) dari Pengoptimalan Streaming.*

Selain itu, Anda dapat secara opsional mengonfigurasi lapisan individual dari dalam konfigurasi simulcast:

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

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

Sebagai alternatif, Anda dapat membuat konfigurasi lapisan kustom Anda sendiri hingga tiga lapisan. Jika Anda memberikan array kosong atau tidak ada nilai, default yang dijelaskan di atas digunakan. Lapisan dijelaskan dengan properti yang diperlukan berikut:
+ `height: number;`
+ `width: number;`
+ `maxBitrateKbps: number;`
+ `maxFramerate: number;`

Mulai dari preset, Anda dapat mengganti properti individual atau membuat konfigurasi yang sama sekali baru:

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

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

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

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

Untuk nilai maksimum, batas, dan kesalahan yang dapat dipicu saat mengonfigurasi lapisan individual, lihat dokumentasi referensi SDK.

### Mengkonfigurasi Layered Encoding (Subscriber)
<a name="web-layered-encoding-simulcast-configure-subscriber"></a>

Sebagai pelanggan, tidak ada yang diperlukan untuk mengaktifkan pengkodean berlapis. Jika penerbit mengirim lapisan simulcast, maka secara default server secara dinamis beradaptasi di antara lapisan untuk memilih kualitas optimal berdasarkan perangkat pelanggan dan kondisi jaringan.

Atau, untuk memilih lapisan eksplisit yang dikirimkan penerbit, ada beberapa opsi, yang dijelaskan di bawah ini.

### Opsi 1: Preferensi Kualitas Lapisan Awal
<a name="web-layered-encoding-simulcast-layer-quality-preference"></a>

Dengan menggunakan `subscribeConfiguration` strategi, dimungkinkan untuk memilih lapisan awal apa yang ingin Anda terima sebagai pelanggan:

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

Secara default, pelanggan selalu dikirim lapisan kualitas terendah terlebih dahulu; ini perlahan naik ke lapisan kualitas tertinggi. Ini mengoptimalkan konsumsi bandwidth pengguna akhir dan memberikan waktu terbaik untuk video, mengurangi pembekuan video awal bagi pengguna di jaringan yang lebih lemah.

Opsi ini tersedia untuk`InitialLayerPreference`:
+ `LOWEST_QUALITY`— Server memberikan lapisan video dengan kualitas terendah terlebih dahulu. Ini mengoptimalkan konsumsi bandwidth, serta waktu ke media. Kualitas didefinisikan sebagai kombinasi ukuran, bitrate, dan framerate video. Misalnya, video 720p memiliki kualitas lebih rendah dari video 1080p.
+ `HIGHEST_QUALITY`— Server memberikan lapisan video kualitas tertinggi terlebih dahulu. Ini mengoptimalkan kualitas tetapi dapat meningkatkan waktu ke media. Kualitas didefinisikan sebagai kombinasi ukuran, bitrate, dan framerate video. Misalnya, video 1080p memiliki kualitas lebih tinggi dari video 720p.

**Catatan:** Agar preferensi lapisan awal (`initialLayerPreference`panggilan) diterapkan, berlangganan ulang diperlukan karena pembaruan ini tidak berlaku untuk langganan aktif.



### Opsi 2: Lapisan Pilihan untuk Stream
<a name="web-layered-encoding-simulcast-preferred-layer"></a>

Setelah streaming dimulai, Anda dapat menggunakan metode `preferredLayerForStream ` strategi. Metode strategi ini mengekspos peserta dan informasi aliran.

Metode strategi dapat dikembalikan dengan yang berikut:
+ Objek layer secara langsung, berdasarkan apa yang `RemoteStageStream.getLayers` kembali 
+ String label objek lapisan, berdasarkan `StageStreamLayer.label`
+ Tidak terdefinisi atau null, yang menunjukkan bahwa tidak ada lapisan yang harus dipilih, dan adaptasi dinamis lebih disukai

Misalnya, strategi berikut akan selalu membuat pengguna memilih lapisan video dengan kualitas terendah yang tersedia:

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

Untuk mengatur ulang pemilihan lapisan dan kembali ke adaptasi dinamis, kembalikan null atau undefined dalam strategi. Dalam contoh ini `appState` adalah variabel dummy yang mewakili status aplikasi yang mungkin.

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

### Opsi 3: Pembantu RemoteStageStream Lapisan
<a name="web-layered-encoding-simulcast-remotestagestream-helpers"></a>

`RemoteStageStream`memiliki beberapa pembantu yang dapat digunakan untuk membuat keputusan tentang pemilihan lapisan dan menampilkan pilihan yang sesuai untuk pengguna akhir:
+ **Layer Events** — Di samping itu`StageEvents`, `RemoteStageStream` objek itu sendiri memiliki peristiwa yang mengkomunikasikan perubahan adaptasi layer dan simulcast:
  + `stream.on(RemoteStageStreamEvents.ADAPTION_CHANGED, (isAdapting) => {})`
  + `stream.on(RemoteStageStreamEvents.LAYERS_CHANGED, (layers) => {})`
  + `stream.on(RemoteStageStreamEvents.LAYER_SELECTED, (layer, reason) => {})`
+ **Metode Layer** — `RemoteStageStream` memiliki beberapa metode pembantu yang dapat digunakan untuk mendapatkan informasi tentang aliran dan lapisan yang disajikan. Metode ini tersedia di aliran jarak jauh yang disediakan dalam `preferredLayerForStream ` strategi, serta aliran jarak jauh yang diekspos melalui`StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED`.
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`

Untuk detailnya, lihat `RemoteStageStream` kelas dalam [dokumentasi referensi SDK](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference). Untuk `LAYER_SELECTED` alasannya, jika `UNAVAILABLE` dikembalikan, ini menunjukkan bahwa lapisan yang diminta tidak dapat dipilih. Pilihan upaya terbaik dilakukan sebagai gantinya, yang biasanya merupakan lapisan kualitas yang lebih rendah untuk menjaga stabilitas aliran.

## Menangani Masalah Jaringan
<a name="web-publish-subscribe-network-issues"></a>

Ketika koneksi jaringan perangkat lokal terputus, SDK secara internal mencoba menyambung kembali tanpa tindakan pengguna apa pun. Dalam beberapa kasus, SDK tidak berhasil dan tindakan pengguna diperlukan.

Secara umum keadaan panggung dapat ditangani melalui acara: `STAGE_CONNECTION_STATE_CHANGED`

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

Secara umum, Anda dapat mengabaikan status kesalahan yang ditemui setelah berhasil bergabung dengan tahap, karena SDK akan mencoba memulihkan secara internal. Jika SDK melaporkan `ERRORED` status dan tahapan tetap dalam `CONNECTING` status untuk jangka waktu yang lama (misalnya, 30 detik atau lebih), Anda mungkin terputus dari jaringan.

## Siarkan Panggung ke Saluran IVS
<a name="web-publish-subscribe-broadcast-stage"></a>

Untuk menyiarkan panggung, buat `IVSBroadcastClient` sesi terpisah dan kemudian ikuti instruksi biasa untuk penyiaran dengan SDK, yang dijelaskan di atas. Daftar `StageStream` ekspos via `STAGE_PARTICIPANT_STREAMS_ADDED` dapat digunakan untuk mengambil aliran media peserta yang dapat diterapkan pada komposisi aliran siaran, sebagai berikut:

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

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

Secara opsional, Anda dapat menggabungkan panggung dan menyiarkannya ke saluran latensi rendah IVS, untuk menjangkau audiens yang lebih besar. Lihat [Mengaktifkan Beberapa Host di Amazon IVS Stream di Panduan Pengguna Streaming](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/multiple-hosts.html) Latensi Rendah IVS.

# Masalah & Solusi yang Diketahui di SDK Siaran Web IVS \$1 Streaming Waktu Nyata
<a name="broadcast-web-known-issues"></a>

Dokumen ini mencantumkan masalah yang diketahui yang mungkin Anda temui saat menggunakan SDK siaran Web streaming real-time Amazon IVS dan menyarankan solusi potensial.
+ Saat menutup tab browser atau keluar dari browser tanpa menelepon`stage.leave()`, pengguna masih dapat muncul dalam sesi dengan bingkai beku atau layar hitam hingga 10 detik.

  **Solusi**: Tidak ada.
+ Sesi Safari sebentar-sebentar muncul dengan layar hitam untuk pengguna yang bergabung setelah sesi dimulai.

  **Solusi:** Segarkan browser dan sambungkan kembali sesi.
+ Safari tidak pulih dengan anggun dari switching jaringan.

  **Solusi:** Segarkan browser dan sambungkan kembali sesi.
+ Konsol pengembang mengulangi `Error: UnintentionalError at StageSocket.onClose` kesalahan.

  **Solusi:** Hanya satu tahap yang dapat dibuat per token peserta. Kesalahan ini terjadi ketika lebih dari satu `Stage` instance dibuat dengan token peserta yang sama, terlepas dari apakah instance tersebut ada di satu perangkat atau beberapa perangkat.
+ Anda mungkin mengalami kesulitan mempertahankan `StageParticipantPublishState.PUBLISHED` status dan mungkin menerima `StageParticipantPublishState.ATTEMPTING_PUBLISH` status berulang saat mendengarkan `StageEvents.STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED` acara tersebut.

  **Solusi:** Batasi resolusi video ke 720p saat memanggil atau. `getUserMedia` `getDisplayMedia` Secara khusus, nilai Anda `getUserMedia` dan `getDisplayMedia` batasan untuk lebar dan tinggi tidak boleh melebihi 921600 (1280\$1 720) saat dikalikan bersama.
+ Ketika `stage.leave()` dipanggil atau peserta jarak jauh pergi, kesalahan 404 DELETE muncul di konsol debug browser.

  **Solusi**: Tidak ada. Ini adalah kesalahan yang tidak berbahaya.

## Keterbatasan Safari
<a name="broadcast-web-safari-limitations"></a>
+ Menolak prompt izin memerlukan pengaturan ulang izin di pengaturan situs web Safari di tingkat OS.
+ Safari tidak secara native mendeteksi semua perangkat seefektif Firefox atau Chrome. Misalnya, OBS Virtual Camera tidak terdeteksi.

## Keterbatasan Firefox
<a name="broadcast-web-firefox-limitations"></a>
+ Izin sistem harus diaktifkan agar Firefox dapat berbagi layar. Setelah mengaktifkannya, pengguna harus me-restart Firefox agar berfungsi dengan benar; jika tidak, jika izin dianggap diblokir, browser akan memberikan [NotFoundError](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#exceptions)pengecualian.
+ `getCapabilities`Metodenya hilang. Ini berarti pengguna tidak bisa mendapatkan resolusi atau rasio aspek trek media. Lihat utas [bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1179084) ini.
+ Beberapa `AudioContext` properti hilang; misalnya, latensi dan jumlah saluran. Ini bisa menimbulkan masalah bagi pengguna tingkat lanjut yang ingin memanipulasi trek audio.
+ Umpan kamera dari `getUserMedia` dibatasi hingga rasio aspek 4:3 di macOS. Lihat utas [bugzilla 1 dan utas](https://bugzilla.mozilla.org/show_bug.cgi?id=1193640) [bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1306034) 2.
+ Pengambilan audio tidak didukung dengan`getDisplayMedia`. Lihat utas [bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1541425) ini.
+ Framerate dalam tangkapan layar kurang optimal (sekitar 15fps?). Lihat utas [bugzilla](https://bugzilla.mozilla.org/show_bug.cgi?id=1703522) ini.

## Batasan Web Seluler
<a name="broadcast-web-mobile-web-limitations"></a>
+ [getDisplayMedia](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#browser_compatibility)berbagi layar tidak didukung di perangkat seluler.

  **Solusi**: Tidak ada.
+ Peserta membutuhkan waktu 15-30 detik untuk pergi saat menutup browser tanpa menelepon`leave()`.

  **Solusi**: Tambahkan UI yang mendorong pengguna untuk memutuskan sambungan dengan benar.
+ Aplikasi latar belakang menyebabkan penerbitan video berhenti.

  **Solusi**: Menampilkan papan tulis UI saat penerbit dijeda.
+ Video framerate turun selama kurang lebih 5 detik setelah mematikan kamera di perangkat Android.

  **Solusi**: Tidak ada.
+ Umpan video diregangkan pada rotasi untuk iOS 16.0.

  **Solusi**: Tampilkan UI yang menguraikan masalah OS yang diketahui ini.
+ Mengalihkan perangkat input audio secara otomatis mengalihkan perangkat output audio.

  **Solusi**: Tidak ada.
+ Latar belakang browser menyebabkan aliran penerbitan menjadi hitam dan hanya menghasilkan audio.

  **Solusi**: Tidak ada. Ini untuk alasan keamanan.

# Penanganan Kesalahan di SDK Siaran Web IVS \$1 Streaming Waktu Nyata
<a name="broadcast-web-error-handling"></a>

Bagian ini adalah ikhtisar kondisi kesalahan, bagaimana SDK siaran Web melaporkannya ke aplikasi, dan apa yang harus dilakukan aplikasi ketika kesalahan tersebut ditemui. Kesalahan dilaporkan oleh SDK kepada pendengar acara: `StageEvents.ERROR`

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

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

A StageError dilaporkan ketika SDK mengalami masalah yang tidak dapat dipulihkan dan umumnya memerlukan koneksi ulang and/or jaringan intervensi aplikasi untuk pulih.

Setiap dilaporkan `StageError` memiliki kode (atau`StageErrorCode`), pesan (string), dan kategori (`StageErrorCategory`). Masing-masing terkait dengan kategori operasi yang mendasarinya.

Kategori operasi kesalahan ditentukan berdasarkan apakah itu terkait dengan koneksi ke tahap (`JOIN_ERROR`), mengirim media ke tahap (`PUBLISH_ERROR`), atau menerima aliran media yang masuk dari tahap (`SUBSCRIBE_ERROR`).

Properti kode `StageError` melaporkan masalah spesifik:


| Nama | Kode | Tindakan yang Direkomendasikan | 
| --- | --- | --- | 
| TOKEN\$1MALFORMED | 1 | Buat token yang valid dan coba lagi membuat instance stage. | 
| TOKEN\$1KEDALUWARSA | 2 | Buat token yang belum kedaluwarsa dan coba lagi membuat instance panggung. | 
| BATAS WAKTU | 3 | Waktu operasi habis. Jika tahap ada dan token valid, kegagalan ini kemungkinan merupakan masalah jaringan. Dalam hal ini, tunggu konektivitas perangkat pulih. | 
| FAILED | 4 | Kondisi fatal ditemui ketika mencoba operasi. Periksa detail kesalahan. Jika tahap ada dan token valid, kegagalan ini kemungkinan merupakan masalah jaringan. Dalam hal ini, tunggu konektivitas perangkat pulih. Untuk sebagian besar kegagalan yang terkait dengan stabilitas jaringan, SDK akan mencoba lagi secara internal untuk jangka waktu hingga 30 detik sebelum memancarkan kesalahan GAGAL.  | 
| MEMBATALKAN | 5 | Periksa kode aplikasi dan pastikan tidak ada pemanggilan berulang `join``refreshStrategy`, atau `replaceStrategy` pemanggilan, yang dapat menyebabkan operasi berulang dimulai dan dibatalkan sebelum selesai. | 
| STAGE\$1AT\$1CAPACITY | 6 | Kesalahan ini menunjukkan bahwa panggung atau akun Anda dalam kapasitas. Jika tahap telah mencapai batas pesertanya, coba operasi lagi ketika tahap tidak lagi dalam kapasitas, dengan menyegarkan strategi. [Jika akun Anda telah mencapai langganan bersamaan atau kuota penayang bersamaan, kurangi penggunaan atau minta peningkatan kuota melalui konsol AWS Service Quotas.](https://console.aws.amazon.com/servicequotas/)  | 
| CODEC\$1MISMATCH | 7 | Codec tidak didukung oleh panggung. Periksa browser dan platform untuk dukungan codec. Untuk streaming real-time IVS, browser harus mendukung codec H.264 untuk video dan codec Opus untuk audio. | 
| TOKEN\$1NOT\$1ALLOWED | 8 | Token tidak memiliki izin untuk operasi. Buat ulang token dengan izin yang benar dan coba lagi. | 
| STAGE\$1DELETED | 9 | Tidak ada; mencoba bergabung dengan tahap yang dihapus memicu kesalahan ini. | 
| PARTISIPANT\$1TERPUTUS | 10 | Tidak ada; mencoba bergabung dengan token peserta yang terputus memicu kesalahan ini. | 

### StageError Contoh Penanganan
<a name="web-error-handling-stage-errors-example"></a>

Gunakan StageError kode untuk menentukan apakah kesalahan disebabkan oleh token yang kedaluwarsa:

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

### Kesalahan Jaringan saat Sudah Bergabung
<a name="web-error-handling-stage-errors-network"></a>

Jika koneksi jaringan perangkat mati, SDK mungkin kehilangan koneksinya ke server panggung. Anda mungkin melihat kesalahan di konsol karena SDK tidak dapat lagi menjangkau layanan backend. POSTs ke https://broadcast.stats.live-video.net akan gagal.

Jika Anda menerbitkan and/or langganan, Anda akan melihat kesalahan di konsol terkait dengan upaya untuk menerbitkan/berlangganan.

Secara internal SDK akan mencoba terhubung kembali dengan strategi backoff eksponensial.

**Tindakan**: Tunggu konektivitas perangkat pulih.

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

Kami menyarankan Anda menggunakan status ini untuk pencatatan aplikasi dan untuk menampilkan pesan kepada pengguna yang memberi tahu mereka tentang masalah konektivitas ke panggung untuk peserta tertentu.

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

SDK melaporkan `ERRORED` saat publikasi gagal.

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

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

SDK melaporkan `ERRORED` saat berlangganan gagal. Ini dapat terjadi karena kondisi jaringan atau jika suatu tahap berada pada kapasitas untuk pelanggan.

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

# SDK Siaran IVS: Panduan Android \$1 Streaming Waktu Nyata
<a name="broadcast-android"></a>

IVS real-time streaming Android broadcast SDK memungkinkan peserta untuk mengirim dan menerima video di Android.

Paket `com.amazonaws.ivs.broadcast` mengimplementasikan antarmuka yang dijelaskan dalam dokumen ini. SDK mendukung operasi berikut:
+ Bergabunglah dengan panggung 
+ Publikasikan media ke peserta lain di panggung
+ Berlangganan media dari peserta lain di panggung
+ Kelola dan pantau video dan audio yang dipublikasikan ke panggung
+ Dapatkan statistik WebRTC untuk setiap koneksi rekan
+ Semua operasi dari SDK siaran Android streaming latensi rendah IVS

**Versi terbaru dari SDK siaran Android:** [1.41.0 (Catatan Rilis)](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#apr09-26-broadcast-mobile-rt) 

**Dokumentasi referensi:** [Untuk informasi tentang metode terpenting yang tersedia di SDK siaran Android Amazon IVS, lihat dokumentasi referensi di https://aws.github. io/amazon-ivs-broadcast-docs/1.41.0/android](https://aws.github.io/amazon-ivs-broadcast-docs/1.41.0/android/)/.

**Contoh kode:** [Lihat repositori contoh Android di GitHub: https://github.com/aws-samples/ amazon-ivs-real-time -. streaming-android-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-android-samples)

**Persyaratan platform:** Android 9.0\$1

# Memulai dengan SDK Siaran Android IVS \$1 Streaming Waktu Nyata
<a name="broadcast-android-getting-started"></a>

Dokumen ini membawa Anda melalui langkah-langkah yang terlibat dalam memulai dengan SDK siaran Android streaming real-time IVS.

## Instal Perpustakaan
<a name="broadcast-android-install"></a>

Ada beberapa cara untuk menambahkan library siaran Android Amazon IVS ke lingkungan pengembangan Android Anda: gunakan Gradle secara langsung, gunakan katalog versi Gradle, atau instal SDK secara manual.

**Gunakan Gradle secara langsung**: Tambahkan pustaka ke `build.gradle` file modul Anda, seperti yang ditunjukkan di sini (untuk versi terbaru SDK siaran IVS):

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

**Gunakan katalog versi Gradle**: Pertama sertakan ini dalam file modul Anda: `build.gradle`

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

Kemudian sertakan yang berikut ini dalam `libs.version.toml` file (untuk versi terbaru SDK siaran IVS):

```
[versions]
ivs="1.41.0"

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

**Instal SDK secara manual**: Unduh versi terbaru dari lokasi ini:

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

Pastikan untuk mengunduh `aar` dengan `-stages` menambahkan.

**Juga izinkan kontrol SDK atas speakerphone**: Terlepas dari metode penginstalan yang Anda pilih, tambahkan juga izin berikut ke manifes Anda, untuk memungkinkan SDK mengaktifkan dan menonaktifkan speakerphone:

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

## Menggunakan SDK dengan Simbol Debug
<a name="broadcast-android-using-debug-symbols-rt"></a>

Kami juga menerbitkan versi SDK siaran Android yang menyertakan simbol debug. Anda dapat menggunakan versi ini untuk meningkatkan kualitas laporan debug (jejak tumpukan) di Firebase Crashlytics, jika Anda mengalami crash di SDK broadcast IVS; yaitu,. `libbroadcastcore.so` Saat Anda melaporkan crash ini ke tim IVS SDK, jejak tumpukan berkualitas lebih tinggi memudahkan untuk memperbaiki masalah.

Untuk menggunakan versi SDK ini, letakkan yang berikut ini di file build Gradle Anda:

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

Gunakan baris di atas alih-alih ini:

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

### Mengunggah Simbol ke Firebase Crashlytics
<a name="android-debug-symbols-rt-firebase-crashlytics"></a>

Pastikan file build Gradle Anda disiapkan untuk Firebase Crashlytics. Ikuti instruksi Google di sini:

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

Pastikan untuk memasukkan `com.google.firebase:firebase-crashlytics-ndk` sebagai ketergantungan.

Saat membuat aplikasi Anda untuk rilis, plugin Firebase Crashlytics akan mengunggah simbol secara otomatis. Untuk mengunggah simbol secara manual, jalankan salah satu dari berikut ini:

```
gradle uploadCrashlyticsSymbolFileRelease
```

```
./gradlew uploadCrashlyticsSymbolFileRelease
```

(Tidak ada salahnya jika simbol diunggah dua kali, baik secara otomatis maupun manual.)

### Mencegah Rilis Anda .apk dari Menjadi Lebih Besar
<a name="android-debug-symbols-rt-sizing-apk"></a>

Sebelum mengemas `.apk` file rilis, Plugin Android Gradle secara otomatis mencoba menghapus informasi debug dari pustaka bersama (termasuk library SDK siaran IVS). `libbroadcastcore.so` Namun, terkadang hal ini tidak terjadi. Akibatnya, `.apk` file Anda bisa menjadi lebih besar dan Anda bisa mendapatkan pesan peringatan dari Plugin Android Gradle bahwa file tersebut tidak dapat menghapus simbol debug dan mengemas `.so` file apa adanya. Jika ini terjadi, lakukan hal berikut:
+ Instal Android NDK. Versi terbaru apa pun akan berfungsi.
+ Tambahkan `ndkVersion <your_installed_ndk_version_number>` ke `build.gradle` file aplikasi Anda. Lakukan ini bahkan jika aplikasi Anda sendiri tidak mengandung kode asli.

Untuk informasi selengkapnya, lihat [laporan masalah](https://issuetracker.google.com/issues/353554169) ini.

## Permintaan Izin
<a name="broadcast-android-permissions"></a>

Aplikasi Anda harus meminta izin untuk mengakses kamera dan mikrofon pengguna. (Ini tidak spesifik untuk Amazon IVS; diperlukan untuk aplikasi apa pun yang membutuhkan akses ke kamera dan mikrofon.)

Di sini, kami memeriksa apakah pengguna telah memberikan izin dan, jika tidak, memintanya:

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

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

Di sini, kami mendapatkan respons pengguna:

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

# Menerbitkan & Berlangganan dengan SDK Siaran Android IVS \$1 Streaming Waktu Nyata
<a name="android-publish-subscribe"></a>

Dokumen ini membawa Anda melalui langkah-langkah yang terlibat dalam penerbitan dan berlangganan ke panggung menggunakan SDK siaran Android streaming real-time IVS.

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

Tiga konsep inti mendasari fungsionalitas real-time: [panggung](#android-publish-subscribe-concepts-stage), [strategi](#android-publish-subscribe-concepts-strategy), dan [penyaji](#android-publish-subscribe-concepts-renderer). Tujuan desain adalah meminimalkan jumlah logika sisi klien yang diperlukan untuk membangun produk yang berfungsi.

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

`Stage`Kelas adalah titik utama interaksi antara aplikasi host dan SDK. Ini mewakili panggung itu sendiri dan digunakan untuk bergabung dan meninggalkan panggung. Membuat dan menggabungkan tahap membutuhkan string token yang valid dan belum kedaluwarsa dari bidang kontrol (direpresentasikan sebagai`token`). Bergabung dan meninggalkan panggung itu sederhana. 

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

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

stage.leave();
```

`Stage`Kelas juga di mana `StageRenderer` dapat dilampirkan:

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

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

`Stage.Strategy`Antarmuka menyediakan cara bagi aplikasi host untuk mengkomunikasikan status tahap yang diinginkan ke SDK. Tiga fungsi perlu diimplementasikan:`shouldSubscribeToParticipant`,`shouldPublishFromParticipant`, dan`stageStreamsToPublishForParticipant`. Semua dibahas di bawah ini.

#### Berlangganan Peserta
<a name="android-publish-subscribe-concepts-strategy-participants"></a>

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

Ketika peserta jarak jauh bergabung dengan panggung, SDK menanyakan aplikasi host tentang status langganan yang diinginkan untuk peserta tersebut. Pilihannya adalah`NONE`,`AUDIO_ONLY`, dan`AUDIO_VIDEO`. Saat mengembalikan nilai untuk fungsi ini, aplikasi host tidak perlu khawatir tentang status publikasi, status langganan saat ini, atau status koneksi tahap. Jika `AUDIO_VIDEO` dikembalikan, SDK menunggu hingga peserta jarak jauh memublikasikan sebelum berlangganan, dan memperbarui aplikasi host melalui perender selama proses berlangsung.

Berikut adalah contoh implementasi:

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

Ini adalah implementasi lengkap dari fungsi ini untuk aplikasi host yang selalu ingin semua peserta untuk melihat satu sama lain; misalnya, aplikasi obrolan video.

Implementasi yang lebih maju juga dimungkinkan. Gunakan `userInfo` properti `ParticipantInfo` untuk berlangganan peserta secara selektif berdasarkan atribut yang disediakan server:

```
@Override
Stage.SubscribeType shouldSubscribeToParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	switch(participantInfo.userInfo.get(“role”)) {
		case “moderator”:
			return Stage.SubscribeType.NONE;
		case “guest”:
			return Stage.SubscribeType.AUDIO_VIDEO;
		default:
			return Stage.SubscribeType.NONE;
	}
}
```

Ini dapat digunakan untuk membuat panggung di mana moderator dapat memantau semua tamu tanpa terlihat atau didengar sendiri. Aplikasi host dapat menggunakan logika bisnis tambahan untuk membiarkan moderat melihat satu sama lain tetapi tetap tidak terlihat oleh tamu.

#### Konfigurasi untuk Berlangganan Peserta
<a name="android-publish-subscribe-concepts-strategy-participants-config"></a>

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

Jika peserta jarak jauh sedang berlangganan (lihat [Berlangganan Peserta](#android-publish-subscribe-concepts-strategy-participants)), SDK akan menanyakan aplikasi host tentang konfigurasi langganan khusus untuk peserta tersebut. Konfigurasi ini bersifat opsional dan memungkinkan aplikasi host untuk mengontrol aspek-aspek tertentu dari perilaku pelanggan. Untuk informasi tentang apa yang dapat dikonfigurasi, lihat [SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration)di dokumentasi referensi SDK.

Berikut adalah contoh implementasi:

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

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

    return config;
}
```

Implementasi ini memperbarui penundaan minimum jitter-buffer untuk semua peserta berlangganan ke preset. `MEDIUM`

Seperti halnya`shouldSubscribeToParticipant`, implementasi yang lebih maju dimungkinkan. Yang diberikan `ParticipantInfo` dapat digunakan untuk memperbarui konfigurasi berlangganan secara selektif untuk peserta tertentu.

Sebaiknya gunakan perilaku default. Tentukan konfigurasi khusus hanya jika ada perilaku tertentu yang ingin Anda ubah.

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

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

Setelah terhubung ke panggung, SDK menanyakan aplikasi host untuk melihat apakah peserta tertentu harus mempublikasikannya. Ini hanya dipanggil pada peserta lokal yang memiliki izin untuk mempublikasikan berdasarkan token yang disediakan.

Berikut adalah contoh implementasi:

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

Ini untuk aplikasi obrolan video standar di mana pengguna selalu ingin mempublikasikan. Mereka dapat membisukan dan menonaktifkan audio dan video mereka, untuk langsung disembunyikan atau seen/heard. (They also can use publish/unpublish, but that is much slower. Mute/unmute lebih disukai untuk kasus penggunaan di mana mengubah visibilitas sering diinginkan.)

#### Memilih Streaming untuk Publikasikan
<a name="android-publish-subscribe-concepts-strategy-streams"></a>

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

Saat menerbitkan, ini digunakan untuk menentukan aliran audio dan video apa yang harus dipublikasikan. Ini dibahas secara lebih rinci nanti di [Publish a Media Stream](#android-publish-subscribe-publish-stream).

#### Memperbarui Strategi
<a name="android-publish-subscribe-concepts-strategy-updates"></a>

Strategi ini dimaksudkan untuk menjadi dinamis: nilai yang dikembalikan dari salah satu fungsi di atas dapat diubah kapan saja. Misalnya, jika aplikasi host tidak ingin mempublikasikan sampai pengguna akhir mengetuk tombol, Anda dapat mengembalikan variabel dari `shouldPublishFromParticipant` (sesuatu seperti`hasUserTappedPublishButton`). Ketika variabel itu berubah berdasarkan interaksi oleh pengguna akhir, panggil `stage.refreshStrategy()` untuk memberi sinyal ke SDK bahwa ia harus menanyakan strategi untuk nilai terbaru, hanya menerapkan hal-hal yang telah berubah. Jika SDK mengamati bahwa `shouldPublishFromParticipant` nilainya telah berubah, SDK akan memulai proses publikasi. Jika kueri SDK dan semua fungsi mengembalikan nilai yang sama seperti sebelumnya, `refreshStrategy` panggilan tidak akan melakukan modifikasi apa pun pada tahapan.

Jika nilai pengembalian `shouldSubscribeToParticipant` perubahan dari `AUDIO_VIDEO` ke`AUDIO_ONLY`, aliran video akan dihapus untuk semua peserta dengan nilai yang dikembalikan diubah, jika aliran video ada sebelumnya.

Umumnya, tahap menggunakan strategi untuk menerapkan perbedaan antara strategi sebelumnya dan saat ini secara efisien, tanpa aplikasi host perlu khawatir tentang semua keadaan yang diperlukan untuk mengelolanya dengan benar. Karena itu, anggap menelepon `stage.refreshStrategy()` sebagai operasi yang murah, karena tidak melakukan apa-apa kecuali strateginya berubah.

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

`StageRenderer`Antarmuka mengkomunikasikan keadaan panggung ke aplikasi host. Pembaruan pada UI aplikasi host biasanya dapat didukung sepenuhnya oleh peristiwa yang disediakan oleh perender. Penyaji menyediakan fungsi-fungsi berikut:

```
void onParticipantJoined(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);

void onParticipantLeft(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo);

void onParticipantPublishStateChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull Stage.PublishState publishState);

void onParticipantSubscribeStateChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull Stage.SubscribeState subscribeState);

void onStreamsAdded(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams);

void onStreamsRemoved(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams);

void onStreamsMutedChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull List<StageStream> streams);

void onError(@NonNull BroadcastException exception);

void onConnectionStateChanged(@NonNull Stage stage, @NonNull Stage.ConnectionState state, @Nullable BroadcastException exception);
                
void onStreamAdaptionChanged(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo, @NonNull RemoteStageStream stream, boolean adaption);

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

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

Untuk sebagian besar metode ini, yang sesuai `Stage` dan `ParticipantInfo` disediakan.

Tidak diharapkan bahwa informasi yang diberikan oleh penyaji berdampak pada nilai pengembalian strategi. Misalnya, nilai pengembalian `shouldSubscribeToParticipant` tidak diharapkan berubah ketika `onParticipantPublishStateChanged` dipanggil. Jika aplikasi host ingin berlangganan ke peserta tertentu, itu harus mengembalikan jenis langganan yang diinginkan terlepas dari status publikasi peserta tersebut. SDK bertanggung jawab untuk memastikan bahwa keadaan strategi yang diinginkan ditindaklanjuti pada waktu yang tepat berdasarkan keadaan tahap.

`StageRenderer`Dapat dilampirkan ke kelas panggung:

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

Perhatikan bahwa hanya peserta penerbitan yang dipicu`onParticipantJoined`, dan setiap kali peserta berhenti menerbitkan atau meninggalkan sesi panggung, `onParticipantLeft` dipicu.

## Publikasikan Aliran Media
<a name="android-publish-subscribe-publish-stream"></a>

Perangkat lokal seperti mikrofon dan kamera internal ditemukan melalui`DeviceDiscovery`. Berikut adalah contoh memilih kamera yang menghadap ke depan dan mikrofon default, lalu mengembalikannya seperti `LocalStageStreams` yang akan dipublikasikan oleh SDK:

```
DeviceDiscovery deviceDiscovery = new DeviceDiscovery(context);

List<Device> devices = deviceDiscovery.listLocalDevices();
List<LocalStageStream> publishStreams = new ArrayList<LocalStageStream>();

Device frontCamera = null;
Device microphone = null;

// Create streams using the front camera, first microphone
for (Device device : devices) {
	Device.Descriptor descriptor = device.getDescriptor();
	if (!frontCamera && descriptor.type == Device.Descriptor.DeviceType.Camera && descriptor.position = Device.Descriptor.Position.FRONT) {
		front Camera = device;
	}
	if (!microphone && descriptor.type == Device.Descriptor.DeviceType.Microphone) {
		microphone = device;
	}
}

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera);
AudioLocalStageStream microphoneStream = new AudioLocalStageStream(microphoneDevice);

publishStreams.add(cameraStream);
publishStreams.add(microphoneStream);

// Provide the streams in Stage.Strategy
@Override
@NonNull List<LocalStageStream> stageStreamsToPublishForParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	return publishStreams;
}
```

## Tampilkan dan Hapus Peserta
<a name="android-publish-subscribe-participants"></a>

Setelah berlangganan selesai, Anda akan menerima array `StageStream` objek melalui fungsi renderer. `onStreamsAdded` Anda dapat mengambil pratinjau dari: `ImageStageStream`

```
ImagePreviewView preview = ((ImageStageStream)stream).getPreview();

// Add the view to your view hierarchy
LinearLayout previewHolder = findViewById(R.id.previewHolder);
preview.setLayoutParams(new LinearLayout.LayoutParams(
		LinearLayout.LayoutParams.MATCH_PARENT,
		LinearLayout.LayoutParams.MATCH_PARENT));
previewHolder.addView(preview);
```

Anda dapat mengambil statistik tingkat audio dari: `AudioStageStream`

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

Ketika peserta berhenti menerbitkan atau berhenti berlangganan, `onStreamsRemoved` fungsi dipanggil dengan aliran yang telah dihapus. Aplikasi host harus menggunakan ini sebagai sinyal untuk menghapus aliran video peserta dari hierarki tampilan.

`onStreamsRemoved`dipanggil untuk semua skenario di mana aliran mungkin dihapus, termasuk: 
+ Peserta jarak jauh berhenti menerbitkan.
+ Perangkat lokal berhenti berlangganan atau mengubah langganan dari ke`AUDIO_VIDEO`. `AUDIO_ONLY`
+ Peserta jarak jauh meninggalkan panggung.
+ Peserta lokal meninggalkan panggung.

Karena `onStreamsRemoved` dipanggil untuk semua skenario, tidak diperlukan logika bisnis khusus untuk menghapus peserta dari UI selama operasi cuti jarak jauh atau lokal.

## Bisukan dan Bunyikan Streaming Media
<a name="android-publish-subscribe-mute-streams"></a>

`LocalStageStream`objek memiliki `setMuted` fungsi yang mengontrol apakah aliran diredam. Fungsi ini dapat dipanggil pada aliran sebelum atau sesudah dikembalikan dari fungsi `streamsToPublishForParticipant` strategi.

**Penting**: Jika instance `LocalStageStream` objek baru dikembalikan `streamsToPublishForParticipant` setelah panggilan ke`refreshStrategy`, status bisu objek aliran baru diterapkan ke panggung. Hati-hati saat membuat `LocalStageStream` instance baru untuk memastikan status bisu yang diharapkan dipertahankan.

## Pantau Status Bisu Media Peserta Jarak Jauh
<a name="android-publish-subscribe-mute-state"></a>

Saat peserta mengubah status bisu aliran video atau audio mereka, `onStreamMutedChanged` fungsi penyaji dipanggil dengan daftar aliran yang telah berubah. Gunakan `getMuted` metode ini `StageStream` untuk memperbarui UI Anda sesuai dengan itu. 

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

## Dapatkan Statistik WebRTC
<a name="android-publish-subscribe-webrtc-stats"></a>

Untuk mendapatkan statistik WebRTC terbaru untuk aliran penerbitan atau aliran berlangganan, gunakan terus. `requestRTCStats` `StageStream` Ketika koleksi selesai, Anda akan menerima statistik melalui `StageStream.Listener` yang dapat diatur`StageStream`.

```
stream.requestRTCStats();

@Override
void onRTCStats(Map<String, Map<String, String>> statsMap) {
	for (Map.Entry<String, Map<String, string>> stat : statsMap.entrySet()) {
		for(Map.Entry<String, String> member : stat.getValue().entrySet()) {
			Log.i(TAG, stat.getKey() + “ has member “ + member.getKey() + “ with value “ + member.getValue());
		}
	}
}
```

## Dapatkan Atribut Peserta
<a name="android-publish-subscribe-participant-attributes"></a>

Jika Anda menentukan atribut dalam permintaan `CreateParticipantToken` operasi, Anda dapat melihat atribut di `ParticipantInfo` properti:

```
@Override
void onParticipantJoined(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	for (Map.Entry<String, String> entry : participantInfo.userInfo.entrySet()) {
		Log.i(TAG, “attribute: “ + entry.getKey() + “ = “ + entry.getValue());
	}
}
```

## Sematkan Pesan
<a name="android-publish-subscribe-embed-messages"></a>

`embedMessage`Metode ini ImageDevice memungkinkan Anda untuk memasukkan muatan metadata langsung ke bingkai video selama penerbitan. Ini memungkinkan pesan yang disinkronkan bingkai untuk aplikasi waktu nyata. Penyematan pesan hanya tersedia saat menggunakan SDK untuk penerbitan real-time (bukan penerbitan latensi rendah).

Pesan yang disematkan tidak dijamin sampai ke pelanggan karena mereka disematkan langsung dalam bingkai video dan ditransmisikan melalui UDP, yang tidak menjamin pengiriman paket. Kehilangan paket selama transmisi dapat mengakibatkan pesan hilang, terutama dalam kondisi jaringan yang buruk. Untuk mengurangi ini, `embedMessage` metode ini menyertakan `repeatCount` parameter yang menduplikasi pesan di beberapa frame berturut-turut, meningkatkan keandalan pengiriman. Kemampuan ini hanya tersedia untuk streaming video.

### Menggunakan EmbedMessage
<a name="android-embed-messages-using-embedmessage"></a>

Klien penerbitan dapat menyematkan muatan pesan ke dalam aliran video mereka menggunakan `embedMessage` metode aktif. ImageDevice Ukuran muatan harus lebih besar dari 0KB dan kurang dari 1KB. Jumlah pesan tertanam yang dimasukkan per detik tidak boleh melebihi 10KB per detik. 

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

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

### Mengulangi Muatan Pesan
<a name="android-embed-messages-repeat-payloads"></a>

Gunakan `repeatCount` untuk menduplikasi pesan di beberapa frame untuk meningkatkan keandalan. Nilai ini harus antara 0 dan 30. Klien penerima harus memiliki logika untuk menghapus duplikat pesan.

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

### Membaca Pesan Tertanam
<a name="android-embed-messages-read-messages"></a>

Lihat “Dapatkan Informasi Peningkatan Tambahan (SEI)” di bawah ini untuk mengetahui cara membaca pesan yang disematkan dari aliran masuk.

## Dapatkan Informasi Peningkatan Tambahan (SEI)
<a name="android-publish-subscribe-sei-attributes"></a>

Unit Supplemental Enhancement Information (SEI) NAL digunakan untuk menyimpan metadata yang selaras dengan bingkai di samping video. Klien berlangganan dapat membaca muatan SEI dari penerbit yang menerbitkan video H.264 dengan memeriksa `embeddedMessages` properti pada objek yang keluar dari penerbit. `ImageDeviceFrame` `ImageDevice` Untuk melakukan ini, dapatkan publisher`ImageDevice`, lalu amati setiap frame melalui callback yang disediakan`setOnFrameCallback`, seperti yang ditunjukkan pada contoh berikut:

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

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

## Lanjutkan Sesi di Latar Belakang
<a name="android-publish-subscribe-background-session"></a>

Saat aplikasi memasuki latar belakang, Anda mungkin ingin berhenti menerbitkan atau berlangganan hanya audio peserta jarak jauh lainnya. Untuk mencapai hal ini, perbarui `Strategy` implementasi Anda untuk menghentikan penerbitan, dan berlangganan `AUDIO_ONLY` (atau`NONE`, jika ada).

```
// Local variables before going into the background
boolean shouldPublish = true;
Stage.SubscribeType subscribeType = Stage.SubscribeType.AUDIO_VIDEO;

// Stage.Strategy implementation
@Override
boolean shouldPublishFromParticipant(@NonNull Stage stage, @NonNull ParticipantInfo participantInfo) {
	return shouldPublish;
}

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

// In our Activity, modify desired publish/subscribe when we go to background, then call refreshStrategy to update the stage
@Override
void onStop() {
	super.onStop();
	shouldPublish = false;
	subscribeTpye = Stage.SubscribeType.AUDIO_ONLY;
	stage.refreshStrategy();
}
```

## Pengkodean Berlapis dengan Simulcast
<a name="android-publish-subscribe-layered-encoding-simulcast"></a>

Layered encoding dengan simulcast adalah fitur streaming real-time IVS yang memungkinkan penerbit mengirim beberapa lapisan video berkualitas berbeda, dan pelanggan untuk mengonfigurasi lapisan tersebut secara dinamis atau manual. Fitur ini dijelaskan lebih lanjut dalam dokumen [Pengoptimalan Streaming](real-time-streaming-optimization.md).

### Mengkonfigurasi Layered Encoding (Publisher)
<a name="android-layered-encoding-simulcast-configure-publisher"></a>

Sebagai penerbit, untuk mengaktifkan pengkodean berlapis dengan simulcast, tambahkan konfigurasi berikut ke instantiasi saat Anda: `LocalStageStream`

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

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

*Bergantung pada resolusi yang Anda tetapkan pada konfigurasi video, sejumlah lapisan akan dikodekan dan dikirim seperti yang didefinisikan di bagian [Default Layers, Quality, dan Framerates](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) dari Pengoptimalan Streaming.*

Selain itu, Anda dapat secara opsional mengonfigurasi lapisan individual dari dalam konfigurasi simulcast: 

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

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

config.simulcast.setLayers(simulcastLayers);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

Sebagai alternatif, Anda dapat membuat konfigurasi lapisan kustom Anda sendiri hingga tiga lapisan. Jika Anda memberikan array kosong atau tidak ada nilai, default yang dijelaskan di atas digunakan. Lapisan dijelaskan dengan setter properti wajib berikut:
+ `setSize: Vec2;`
+ `setMaxBitrate: integer;`
+ `setMinBitrate: integer;`
+ `setTargetFramerate: integer;`

Mulai dari preset, Anda dapat mengganti properti individual atau membuat konfigurasi yang sama sekali baru:

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

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

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

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

config.simulcast.setLayers(simulcastLayers);

ImageLocalStageStream cameraStream = new ImageLocalStageStream(frontCamera, config);

// Other Stage implementation code
```

Untuk nilai maksimum, batas, dan kesalahan yang dapat dipicu saat mengonfigurasi lapisan individual, lihat dokumentasi referensi SDK.

### Mengkonfigurasi Layered Encoding (Subscriber)
<a name="android-layered-encoding-simulcast-configure-subscriber"></a>

Sebagai pelanggan, tidak ada yang diperlukan untuk mengaktifkan pengkodean berlapis. Jika penerbit mengirim lapisan simulcast, maka secara default server secara dinamis beradaptasi di antara lapisan untuk memilih kualitas optimal berdasarkan perangkat pelanggan dan kondisi jaringan.

Atau, untuk memilih lapisan eksplisit yang dikirimkan penerbit, ada beberapa opsi, yang dijelaskan di bawah ini.

### Opsi 1: Preferensi Kualitas Lapisan Awal
<a name="android-layered-encoding-simulcast-layer-quality-preference"></a>

Dengan menggunakan `subscribeConfigurationForParticipant` strategi, dimungkinkan untuk memilih lapisan awal apa yang ingin Anda terima sebagai pelanggan:

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

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

    return config;
}
```

Secara default, pelanggan selalu dikirim lapisan kualitas terendah terlebih dahulu; ini perlahan naik ke lapisan kualitas tertinggi. Ini mengoptimalkan konsumsi bandwidth pengguna akhir dan memberikan waktu terbaik untuk video, mengurangi pembekuan video awal bagi pengguna di jaringan yang lebih lemah.

Opsi ini tersedia untuk`InitialLayerPreference`:
+ `LOWEST_QUALITY`— Server memberikan lapisan video dengan kualitas terendah terlebih dahulu. Ini mengoptimalkan konsumsi bandwidth, serta waktu ke media. Kualitas didefinisikan sebagai kombinasi ukuran, bitrate, dan framerate video. Misalnya, video 720p memiliki kualitas lebih rendah dari video 1080p.
+ `HIGHEST_QUALITY`— Server memberikan lapisan video kualitas tertinggi terlebih dahulu. Ini mengoptimalkan kualitas tetapi dapat meningkatkan waktu ke media. Kualitas didefinisikan sebagai kombinasi ukuran, bitrate, dan framerate video. Misalnya, video 1080p berkualitas lebih tinggi dari video 720p.

**Catatan:** Agar preferensi lapisan awal (`setInitialLayerPreference`panggilan) diterapkan, berlangganan ulang diperlukan karena pembaruan ini tidak berlaku untuk langganan aktif.

### Opsi 2: Lapisan Pilihan untuk Stream
<a name="android-layered-encoding-simulcast-preferred-layer"></a>

Metode `preferredLayerForStream` strategi memungkinkan Anda memilih lapisan setelah aliran dimulai. Metode strategi ini menerima peserta dan informasi aliran, sehingga Anda dapat memilih lapisan participant-by-participant berdasarkan. SDK memanggil metode ini sebagai respons terhadap peristiwa tertentu, seperti ketika lapisan aliran berubah, status peserta berubah, atau aplikasi host menyegarkan strategi.

Metode strategi mengembalikan `RemoteStageStream.Layer` objek, yang dapat menjadi salah satu dari berikut:
+ Objek layer, seperti yang dikembalikan oleh`RemoteStageStream.getLayers`.
+ null, yang menunjukkan bahwa tidak ada lapisan yang harus dipilih dan adaptasi dinamis lebih disukai.

Misalnya, strategi berikut akan selalu membuat pengguna memilih lapisan video dengan kualitas terendah yang tersedia:

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

Untuk mengatur ulang pemilihan lapisan dan kembali ke adaptasi dinamis, kembalikan null atau undefined dalam strategi. Dalam contoh ini, `appState` adalah variabel placeholder yang mewakili status aplikasi host.

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

### Opsi 3: Pembantu RemoteStageStream Lapisan
<a name="android-layered-encoding-simulcast-remotestagestream-helpers"></a>

`RemoteStageStream`memiliki beberapa pembantu yang dapat digunakan untuk membuat keputusan tentang pemilihan lapisan dan menampilkan pilihan yang sesuai untuk pengguna akhir:
+ **Layer Events** — Di samping itu`StageRenderer`, `RemoteStageStream.Listener` memiliki peristiwa yang mengkomunikasikan perubahan adaptasi layer dan simulcast:
  + `void onAdaptionChanged(boolean adaption)`
  + `void onLayersChanged(@NonNull List<Layer> layers)`
  + `void onLayerSelected(@Nullable Layer layer, @NonNull LayerSelectedReason reason)`
+ **Metode Layer** — `RemoteStageStream` memiliki beberapa metode pembantu yang dapat digunakan untuk mendapatkan informasi tentang aliran dan lapisan yang disajikan. Metode ini tersedia di aliran jarak jauh yang disediakan dalam `preferredLayerForStream` strategi, serta aliran jarak jauh yang diekspos melalui`StageRenderer.onStreamsAdded`.
  + `stream.getLayers`
  + `stream.getSelectedLayer`
  + `stream.getLowestQualityLayer`
  + `stream.getHighestQualityLayer`
  + `stream.getLayersWithConstraints`

Untuk detailnya, lihat `RemoteStageStream` kelas dalam [dokumentasi referensi SDK](https://aws.github.io/amazon-ivs-broadcast-docs/latest/android/). Untuk `LayerSelected` alasannya, jika `UNAVAILABLE` dikembalikan, ini menunjukkan bahwa lapisan yang diminta tidak dapat dipilih. Pilihan upaya terbaik dilakukan sebagai gantinya, yang biasanya merupakan lapisan kualitas yang lebih rendah untuk menjaga stabilitas aliran.

## Batasan Konfigurasi Video
<a name="android-publish-subscribe-video-limits"></a>

SDK tidak mendukung pemaksaan mode potret atau mode lanskap menggunakan`StageVideoConfiguration.setSize(BroadcastConfiguration.Vec2 size)`. Dalam orientasi potret, dimensi yang lebih kecil digunakan sebagai lebar; dalam orientasi lanskap, tinggi. Ini berarti bahwa dua panggilan berikut `setSize` memiliki efek yang sama pada konfigurasi video:

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

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

## Menangani Masalah Jaringan
<a name="android-publish-subscribe-network-issues"></a>

Ketika koneksi jaringan perangkat lokal terputus, SDK secara internal mencoba menyambung kembali tanpa tindakan pengguna apa pun. Dalam beberapa kasus, SDK tidak berhasil dan tindakan pengguna diperlukan. Ada dua kesalahan utama yang terkait dengan kehilangan koneksi jaringan:
+ Kode kesalahan 1400, pesan: "PeerConnection hilang karena kesalahan jaringan yang tidak diketahui”
+ Kode kesalahan 1300, pesan: “Coba lagi upaya habis”

Jika kesalahan pertama diterima tetapi yang kedua tidak, SDK masih terhubung ke panggung dan akan mencoba membangun kembali koneksinya secara otomatis. Sebagai pengaman, Anda dapat memanggil `refreshStrategy` tanpa perubahan apa pun pada nilai pengembalian metode strategi, untuk memicu upaya penyambungan kembali manual.

Jika kesalahan kedua diterima, upaya penyambungan kembali SDK telah gagal dan perangkat lokal tidak lagi terhubung ke panggung. Dalam hal ini, cobalah untuk bergabung kembali dengan panggung dengan menelepon `join` setelah koneksi jaringan Anda dibangun kembali.

Secara umum, menemukan kesalahan setelah bergabung dengan tahap berhasil menunjukkan bahwa SDK tidak berhasil membangun kembali koneksi. Buat `Stage` objek baru dan cobalah untuk bergabung ketika kondisi jaringan membaik.

## Menggunakan Mikrofon Bluetooth
<a name="android-publish-subscribe-bluetooth-microphones"></a>

Untuk mempublikasikan menggunakan perangkat mikrofon Bluetooth, Anda harus memulai koneksi Bluetooth SCO:

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

# Masalah & Solusi yang Diketahui di SDK Siaran Android IVS \$1 Streaming Waktu Nyata
<a name="broadcast-android-known-issues"></a>

Dokumen ini mencantumkan masalah yang diketahui yang mungkin Anda temui saat menggunakan SDK siaran Android streaming real-time Amazon IVS dan menyarankan solusi potensial.
+ Saat perangkat Android tertidur dan bangun, pratinjau mungkin dalam keadaan beku.

  **Solusi:** Buat dan gunakan yang baru. `Stage`
+ Ketika peserta bergabung dengan token yang digunakan oleh peserta lain, koneksi pertama terputus tanpa kesalahan tertentu.

  **Solusi**: Tidak ada. 
+ Ada masalah langka di mana penerbit menerbitkan tetapi status publikasi yang diterima pelanggan adalah`inactive`.

  **Solusi:** Coba pergi dan kemudian bergabung dengan sesi. Jika masalah tetap ada, buat token baru untuk penerbit.
+ Masalah distorsi audio yang jarang terjadi dapat terjadi sebentar-sebentar selama sesi panggung, biasanya pada panggilan dengan durasi yang lebih lama.

  **Solusi:** Peserta dengan audio yang terdistorsi dapat meninggalkan dan bergabung kembali dengan sesi, atau membatalkan publikasi dan menerbitkan ulang audio mereka untuk memperbaiki masalah.
+ Mikrofon eksternal tidak didukung saat menerbitkan ke panggung.

  **Solusi:** Jangan gunakan mikrofon eksternal yang terhubung melalui USB untuk menerbitkan ke panggung.
+ Penerbitan ke panggung dengan berbagi layar menggunakan `createSystemCaptureSources` tidak didukung.

  **Solusi:** Kelola pengambilan sistem secara manual, menggunakan sumber input gambar khusus dan sumber input audio khusus.
+ Ketika `ImagePreviewView` dihapus dari induk (misalnya, `removeView()` dipanggil di induk), segera `ImagePreviewView` dilepaskan. `ImagePreviewView`Tidak menampilkan bingkai apa pun saat ditambahkan ke tampilan induk lain.

  **Solusi:** Minta pratinjau lain menggunakan. `getPreview`
+ Saat bergabung dengan panggung dengan Samsung Galaxy S22/\$1 dengan Android 12, Anda mungkin mengalami kesalahan 1401 dan perangkat lokal gagal bergabung dengan panggung atau bergabung tetapi tidak memiliki audio.

  **Solusi: Tingkatkan** ke Android 13.
+ Saat bergabung dengan panggung dengan Nokia X20 di Android 13, kamera mungkin gagal dibuka dan pengecualian dilemparkan.

  **Solusi**: Tidak ada.
+ Perangkat dengan chipset MediaTek Helio mungkin tidak merender video peserta jarak jauh dengan benar.

  **Solusi**: Tidak ada.
+ Pada beberapa perangkat, OS perangkat dapat memilih mikrofon yang berbeda dari yang dipilih melalui SDK. Ini karena Amazon IVS Broadcast SDK tidak dapat mengontrol bagaimana rute `VOICE_COMMUNICATION` audio didefinisikan, karena bervariasi sesuai dengan produsen perangkat yang berbeda.

  **Solusi**: Tidak ada.
+ Beberapa encoder video Android tidak dapat dikonfigurasi dengan ukuran video kurang dari 176x176. Mengkonfigurasi ukuran yang lebih kecil menyebabkan kesalahan dan mencegah streaming.

  **Solusi:** Jangan mengonfigurasi ukuran video menjadi kurang dari 176x176.

# Penanganan Kesalahan di SDK Siaran Android IVS \$1 Streaming Waktu Nyata
<a name="broadcast-android-error-handling"></a>

Bagian ini adalah ikhtisar kondisi kesalahan, bagaimana SDK siaran Android streaming real-time IVS melaporkannya ke aplikasi, dan apa yang harus dilakukan aplikasi ketika kesalahan tersebut ditemui.

## Kesalahan Fatal vs Non-Fatal
<a name="broadcast-android-fatal-vs-nonfatal-errors"></a>

Objek kesalahan memiliki bidang boolean “fatal” dari. `BroadcastException`

Secara umum, kesalahan fatal terkait dengan koneksi ke server Tahapan (baik koneksi tidak dapat dibuat atau hilang dan tidak dapat dipulihkan). Aplikasi harus membuat ulang panggung dan bergabung kembali, mungkin dengan token baru atau ketika konektivitas perangkat pulih.

Kesalahan non-fatal umumnya terkait dengan publish/subscribe status dan ditangani oleh SDK, yang mencoba ulang operasi. publish/subscribe 

Anda dapat memeriksa properti ini:

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

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

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

Ini terjadi ketika token panggung salah bentuk.

SDK melempar pengecualian Java dari panggilan ke`stage.join`, dengan kode kesalahan = 1000 dan fatal = true.

**Tindakan**: Buat token yang valid dan coba lagi bergabung.

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

Ini terjadi ketika token panggung kedaluwarsa.

SDK melempar pengecualian Java dari panggilan ke`stage.join`, dengan kode kesalahan = 1001 dan fatal = true.

**Tindakan**: Buat token baru dan coba lagi bergabung.

### Token Tidak Valid atau Dicabut
<a name="broadcast-android-stage-join-errors-invalid-token"></a>

Ini terjadi ketika token panggung tidak cacat tetapi ditolak oleh server Stages. Ini dilaporkan secara asinkron melalui perender tahap yang disediakan aplikasi.

SDK memanggil `onConnectionStateChanged` dengan pengecualian, dengan kode kesalahan = 1026 dan fatal = true.

**Tindakan**: Buat token yang valid dan coba lagi bergabung.

### Kesalahan Jaringan untuk Gabung Awal
<a name="broadcast-android-stage-join-errors-network-initial-join"></a>

Ini terjadi ketika SDK tidak dapat menghubungi server Stages untuk membuat koneksi. Ini dilaporkan secara asinkron melalui perender tahap yang disediakan aplikasi.

SDK memanggil `onConnectionStateChanged` dengan pengecualian, dengan kode kesalahan = 1300 dan fatal = true.

**Tindakan**: Tunggu konektivitas perangkat pulih dan coba lagi bergabung.

### Kesalahan Jaringan saat Sudah Bergabung
<a name="broadcast-android-stage-join-errors-network-already-joined"></a>

Jika koneksi jaringan perangkat mati, SDK mungkin kehilangan koneksinya ke server Stage. Ini dilaporkan secara asinkron melalui perender tahap yang disediakan aplikasi.

SDK memanggil `onConnectionStateChanged` dengan pengecualian, dengan kode kesalahan = 1300 dan fatal = true.

**Tindakan**: Tunggu konektivitas perangkat pulih dan coba lagi bergabung.

## Kesalahan Publikasi/Berlangganan
<a name="broadcast-android-publish-subscribe-errors"></a>

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

Ada beberapa kesalahan:
+ MultihostSessionOfferCreationFailPublish (1020)
+ MultihostSessionOfferCreationFailSubscribe (1021)
+ MultihostSessionNoIceCandidates (1022)
+ MultihostSessionStageAtCapacity (1024)
+ SignallingSessionCannotRead (1201)
+ SignallingSessionCannotSend (1202)
+ SignallingSessionBadResponse (1203)

Ini dilaporkan secara asinkron melalui perender tahap yang disediakan aplikasi.

SDK mencoba ulang operasi untuk beberapa kali. Selama percobaan ulang, publish/subscribe negara adalah`ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE`. Jika upaya coba lagi berhasil, status berubah menjadi`PUBLISHED`/`SUBSCRIBED`.

Panggilan SDK `onError` dengan kode kesalahan yang relevan dan fatal = false.

**Tindakan**: Tidak diperlukan tindakan, karena SDK mencoba ulang secara otomatis. Secara opsional, aplikasi dapat menyegarkan strategi untuk memaksa lebih banyak percobaan ulang.

### Sudah Didirikan, Lalu Gagal
<a name="broadcast-android-publish-subscribe-errors-established"></a>

Publikasi atau berlangganan dapat gagal setelah dibuat, kemungkinan besar karena kesalahan jaringan. Kode kesalahan untuk “koneksi rekan hilang karena kesalahan jaringan” adalah 1400.

Ini dilaporkan secara asinkron melalui perender tahap yang disediakan aplikasi.

SDK mencoba ulang operasi. publish/subscribe Selama percobaan ulang, publish/subscribe negara adalah`ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE`. Jika upaya coba lagi berhasil, status berubah menjadi`PUBLISHED`/`SUBSCRIBED`.

SDK memanggil `onError` dengan kode kesalahan = 1400 dan fatal = false.

**Tindakan**: Tidak diperlukan tindakan, karena SDK mencoba ulang secara otomatis. Secara opsional, aplikasi dapat menyegarkan strategi untuk memaksa lebih banyak percobaan ulang. Jika terjadi kehilangan konektivitas total, kemungkinan koneksi ke Stages juga akan gagal.

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

IVS real-time streaming iOS broadcast SDK memungkinkan peserta untuk mengirim dan menerima video di iOS.

`AmazonIVSBroadcast`Modul mengimplementasikan antarmuka yang dijelaskan dalam dokumen ini. Operasi berikut didukung:
+ Bergabunglah dengan panggung 
+ Publikasikan media ke peserta lain di panggung
+ Berlangganan media dari peserta lain di panggung
+ Kelola dan pantau video dan audio yang dipublikasikan ke panggung
+ Dapatkan statistik WebRTC untuk setiap koneksi rekan
+ Semua operasi dari SDK siaran iOS streaming latensi rendah IVS

**Versi terbaru dari SDK siaran iOS:** [1.41.0 (Catatan Rilis)](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/release-notes.html#apr09-26-broadcast-mobile-rt) 

**Dokumentasi referensi:** [Untuk informasi tentang metode terpenting yang tersedia di SDK siaran Amazon IVS iOS, lihat dokumentasi referensi di https://aws.github. io/amazon-ivs-broadcast-docs/1.41.0/ios](https://aws.github.io/amazon-ivs-broadcast-docs/1.41.0/ios/)/.

**Contoh kode:** [Lihat contoh repositori iOS di GitHub: https://github.com/aws-samples/ amazon-ivs-real-time -. streaming-ios-samples](https://github.com/aws-samples/amazon-ivs-real-time-streaming-ios-samples)

**Persyaratan platform:** iOS 14\$1

# Memulai dengan SDK Siaran iOS IVS \$1 Streaming Waktu Nyata
<a name="broadcast-ios-getting-started"></a>

Dokumen ini membawa Anda melalui langkah-langkah yang terlibat dalam memulai dengan SDK siaran iOS streaming real-time IVS.

## Instal Perpustakaan
<a name="broadcast-ios-install"></a>

Kami menyarankan Anda mengintegrasikan SDK siaran melalui Swift Package Manager. (Atau, Anda dapat menambahkan kerangka kerja secara manual ke proyek Anda.)

### Direkomendasikan: Integrasikan Broadcast SDK (Swift Package Manager)
<a name="broadcast-ios-install-swift"></a>

1. Unduh file Package.swift dari [https://broadcast.live-video.net/1.41.0/Package.swift](https://broadcast.live-video.net/1.41.0/Package.swift).

1. Dalam proyek Anda, buat direktori baru bernama Amazon IVSBroadcast dan tambahkan ke kontrol versi.

1. Tempatkan file Package.swift yang diunduh di direktori baru.

1. **Di Xcode, buka **File > Add Package Dependencies dan pilih Add** Local...**

1. Arahkan ke dan pilih IVSBroadcast direktori Amazon yang Anda buat, lalu pilih **Add Package**.

1. Saat diminta untuk **Memilih Produk Paket untuk Amazon IVSBroadcast**, pilih **IVSBroadcastTahapan Amazon** sebagai **Produk Package** Anda dengan menetapkan target aplikasi Anda di bagian **Tambahkan ke Target**.

1. Pilih **Add Package**.

**Penting**: SDK siaran streaming real-time IVS mencakup semua fitur SDK siaran streaming latensi rendah IVS. Tidak mungkin untuk mengintegrasikan keduanya SDKs dalam proyek yang sama.

### Pendekatan Alternatif: Instal Kerangka Secara Manual
<a name="broadcast-ios-install-manual"></a>

1. Unduh versi terbaru dari [ https://broadcast.live-video.net/1.41.0/AmazonIVSBroadcast-Stages.xcframework.zip](https://broadcast.live-video.net/1.41.0/AmazonIVSBroadcast-Stages.xcframework.zip).

1. Ekstrak konten arsip. `AmazonIVSBroadcast.xcframework` berisi SDK untuk perangkat dan simulator.

1. Sematkan `AmazonIVSBroadcast.xcframework` dengan menyeretnya ke bagian **Frameworks, Libraries, dan Embedded Content** pada tab **General** untuk target aplikasi Anda.  
![\[Bagian Kerangka Kerja, Pustaka, dan Konten Tersemat pada tab Umum untuk target aplikasi Anda.\]](http://docs.aws.amazon.com/id_id/ivs/latest/RealTimeUserGuide/images/iOS_Broadcast_SDK_Guide_xcframework.png)

## Permintaan Izin
<a name="broadcast-ios-permissions"></a>

Aplikasi Anda harus meminta izin untuk mengakses kamera dan mikrofon pengguna. (Ini tidak khusus untuk Amazon IVS; diperlukan untuk aplikasi apa pun yang membutuhkan akses ke kamera dan mikrofon.)

Di sini, kami memeriksa apakah pengguna telah memberikan izin dan, jika tidak, kami memintanya:

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

Anda perlu melakukan ini untuk keduanya `.video` dan jenis `.audio` media, jika Anda ingin akses ke kamera dan mikrofon, masing-masing.

Anda juga perlu menambahkan entri untuk `NSCameraUsageDescription` dan `NSMicrophoneUsageDescription` ke Anda`Info.plist`. Jika tidak, aplikasi Anda akan macet saat mencoba meminta izin.

## Nonaktifkan Aplikasi Idle Timer
<a name="broadcast-ios-disable-idle-timer"></a>

Ini adalah langkah opsional, tetapi direkomendasikan. Ini mencegah perangkat Anda tertidur saat menggunakan SDK siaran, yang akan mengganggu siaran.

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

# Menerbitkan & Berlangganan dengan SDK Siaran iOS IVS \$1 Streaming Waktu Nyata
<a name="ios-publish-subscribe"></a>

Dokumen ini membawa Anda melalui langkah-langkah yang terlibat dalam penerbitan dan berlangganan ke panggung menggunakan SDK siaran iOS streaming real-time IVS.

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

Tiga konsep inti mendasari fungsionalitas real-time: [panggung](#ios-publish-subscribe-concepts-stage), [strategi](#ios-publish-subscribe-concepts-strategy), dan [penyaji](#ios-publish-subscribe-concepts-renderer). Tujuan desain adalah meminimalkan jumlah logika sisi klien yang diperlukan untuk membangun produk yang berfungsi.

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

`IVSStage`Kelas adalah titik utama interaksi antara aplikasi host dan SDK. Kelas mewakili panggung itu sendiri dan digunakan untuk bergabung dan meninggalkan panggung. Membuat atau bergabung dengan tahap memerlukan string token yang valid dan belum kedaluwarsa dari bidang kontrol (direpresentasikan sebagai`token`). Bergabung dan meninggalkan panggung itu sederhana.

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

try stage.join()

stage.leave()
```

`IVSStage`Kelas juga adalah tempat `IVSStageRenderer` dan `IVSErrorDelegate` dapat dilampirkan:

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

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

`IVSStageStrategy`Protokol menyediakan cara bagi aplikasi host untuk mengkomunikasikan keadaan tahap yang diinginkan ke SDK. Tiga fungsi perlu diimplementasikan:`shouldSubscribeToParticipant`,`shouldPublishParticipant`, dan`streamsToPublishForParticipant`. Semua dibahas di bawah ini.

#### Berlangganan Peserta
<a name="ios-publish-subscribe-concepts-strategy-participants"></a>

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

Ketika peserta jarak jauh bergabung dengan tahap, SDK menanyakan aplikasi host tentang status langganan yang diinginkan untuk peserta tersebut. Pilihannya adalah`.none`,`.audioOnly`, dan`.audioVideo`. Saat mengembalikan nilai untuk fungsi ini, aplikasi host tidak perlu khawatir tentang status publikasi, status langganan saat ini, atau status koneksi tahap. Jika `.audioVideo` dikembalikan, SDK menunggu hingga peserta jarak jauh memublikasikan sebelum berlangganan, dan memperbarui aplikasi host melalui perender selama proses berlangsung.

Berikut adalah contoh implementasi:

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

Ini adalah implementasi lengkap dari fungsi ini untuk aplikasi host yang selalu ingin semua peserta saling bertemu; misalnya, aplikasi obrolan video.

Implementasi yang lebih maju juga dimungkinkan. Gunakan `attributes` properti `IVSParticipantInfo` untuk berlangganan peserta secara selektif berdasarkan atribut yang disediakan server:

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

Ini dapat digunakan untuk membuat panggung di mana moderator dapat memantau semua tamu tanpa terlihat atau didengar sendiri. Aplikasi host dapat menggunakan logika bisnis tambahan untuk membiarkan moderator melihat satu sama lain tetapi tetap tidak terlihat oleh tamu.

#### Konfigurasi untuk Berlangganan Peserta
<a name="ios-publish-subscribe-concepts-strategy-participants-config"></a>

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

Jika peserta jarak jauh sedang berlangganan (lihat [Berlangganan Peserta](#ios-publish-subscribe-concepts-strategy-participants)), SDK akan menanyakan aplikasi host tentang konfigurasi langganan khusus untuk peserta tersebut. Konfigurasi ini bersifat opsional dan memungkinkan aplikasi host untuk mengontrol aspek-aspek tertentu dari perilaku pelanggan. Untuk informasi tentang apa yang dapat dikonfigurasi, lihat [SubscribeConfiguration](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/interfaces/SubscribeConfiguration)di dokumentasi referensi SDK.

Berikut adalah contoh implementasi:

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

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

    return config
}
```

Implementasi ini memperbarui penundaan minimum jitter-buffer untuk semua peserta berlangganan ke preset. `MEDIUM`

Seperti halnya`shouldSubscribeToParticipant`, implementasi yang lebih maju dimungkinkan. Yang diberikan `ParticipantInfo` dapat digunakan untuk memperbarui konfigurasi berlangganan secara selektif untuk peserta tertentu.

Sebaiknya gunakan perilaku default. Tentukan konfigurasi khusus hanya jika ada perilaku tertentu yang ingin Anda ubah.

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

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

Setelah terhubung ke panggung, SDK menanyakan aplikasi host untuk melihat apakah peserta tertentu harus mempublikasikannya. Ini hanya dipanggil pada peserta lokal yang memiliki izin untuk mempublikasikan berdasarkan token yang disediakan.

Berikut adalah contoh implementasi:

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

Ini untuk aplikasi obrolan video standar di mana pengguna selalu ingin mempublikasikan. Mereka dapat membisukan dan menonaktifkan audio dan video mereka, untuk langsung disembunyikan atau seen/heard. (They also can use publish/unpublish, but that is much slower. Mute/unmute lebih disukai untuk kasus penggunaan di mana mengubah visibilitas sering diinginkan.)

#### Memilih Streaming untuk Publikasikan
<a name="ios-publish-subscribe-concepts-strategy-streams"></a>

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

Saat menerbitkan, ini digunakan untuk menentukan aliran audio dan video apa yang harus dipublikasikan. Ini dibahas secara lebih rinci nanti di [Publish a Media Stream](#ios-publish-subscribe-publish-stream).

#### Memperbarui Strategi
<a name="ios-publish-subscribe-concepts-strategy-updates"></a>

Strategi ini dimaksudkan untuk menjadi dinamis: nilai yang dikembalikan dari salah satu fungsi di atas dapat diubah kapan saja. Misalnya, jika aplikasi host tidak ingin mempublikasikan sampai pengguna akhir mengetuk tombol, Anda dapat mengembalikan variabel dari `shouldPublishParticipant` (sesuatu seperti`hasUserTappedPublishButton`). Ketika variabel itu berubah berdasarkan interaksi oleh pengguna akhir, panggil `stage.refreshStrategy()` untuk memberi sinyal ke SDK bahwa ia harus menanyakan strategi untuk nilai terbaru, hanya menerapkan hal-hal yang telah berubah. Jika SDK mengamati bahwa `shouldPublishParticipant` nilainya telah berubah, SDK akan memulai proses publikasi. Jika kueri SDK dan semua fungsi mengembalikan nilai yang sama seperti sebelumnya, `refreshStrategy` panggilan tidak akan membuat modifikasi apa pun pada tahapan.

Jika nilai pengembalian `shouldSubscribeToParticipant` perubahan dari `.audioVideo` ke`.audioOnly`, aliran video akan dihapus untuk semua peserta dengan nilai yang dikembalikan diubah, jika aliran video ada sebelumnya.

Umumnya, tahap menggunakan strategi untuk menerapkan perbedaan antara strategi sebelumnya dan saat ini secara efisien, tanpa aplikasi host perlu khawatir tentang semua keadaan yang diperlukan untuk mengelolanya dengan benar. Karena itu, anggap menelepon `stage.refreshStrategy()` sebagai operasi yang murah, karena tidak melakukan apa-apa kecuali strateginya berubah.

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

`IVSStageRenderer`Protokol mengkomunikasikan keadaan panggung ke aplikasi host. Pembaruan pada UI aplikasi host biasanya dapat didukung sepenuhnya oleh peristiwa yang disediakan oleh perender. Penyaji menyediakan fungsi-fungsi berikut:

```
func stage(_ stage: IVSStage, participantDidJoin participant: IVSParticipantInfo)

func stage(_ stage: IVSStage, participantDidLeave participant: IVSParticipantInfo)

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChange publishState: IVSParticipantPublishState)

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChange subscribeState: IVSParticipantSubscribeState)

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didAdd streams: [IVSStageStream])

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didRemove streams: [IVSStageStream])

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didChangeMutedStreams streams: [IVSStageStream])

func stage(_ stage: IVSStage, didChange connectionState: IVSStageConnectionState, withError error: Error?)

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, stream: IVSRemoteStageStream, didChangeStreamAdaption adaption: Bool)

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, stream: IVSRemoteStageStream, didChange layers: [IVSRemoteStageStreamLayer])

func stage(_ stage: IVSStage, participant: IVSParticipantInfo, stream: IVSRemoteStageStream, didSelect layer: IVSRemoteStageStreamLayer?, reason: IVSRemoteStageStream.LayerSelectedReason)
```

Tidak diharapkan bahwa informasi yang diberikan oleh penyaji berdampak pada nilai pengembalian strategi. Misalnya, nilai pengembalian `shouldSubscribeToParticipant` tidak diharapkan berubah ketika `participant:didChangePublishState` dipanggil. Jika aplikasi host ingin berlangganan ke peserta tertentu, itu harus mengembalikan jenis langganan yang diinginkan terlepas dari status publikasi peserta tersebut. SDK bertanggung jawab untuk memastikan bahwa keadaan strategi yang diinginkan ditindaklanjuti pada waktu yang tepat berdasarkan keadaan tahap.

Perhatikan bahwa hanya peserta penerbitan yang dipicu`participantDidJoin`, dan setiap kali peserta berhenti menerbitkan atau meninggalkan sesi panggung, `participantDidLeave` dipicu.

## Publikasikan Aliran Media
<a name="ios-publish-subscribe-publish-stream"></a>

Perangkat lokal seperti mikrofon dan kamera internal ditemukan melalui`IVSDeviceDiscovery`. Berikut adalah contoh memilih kamera yang menghadap ke depan dan mikrofon default, lalu mengembalikannya `IVSLocalStageStreams` agar dipublikasikan oleh SDK:

```
let devices = IVSDeviceDiscovery().listLocalDevices()

// Find the camera virtual device, choose the front source, and create a stream
let camera = devices.compactMap({ $0 as? IVSCamera }).first!
let frontSource = camera.listAvailableInputSources().first(where: { $0.position == .front })!
camera.setPreferredInputSource(frontSource)
let cameraStream = IVSLocalStageStream(device: camera)

// Find the microphone virtual device and create a stream
let microphone = devices.compactMap({ $0 as? IVSMicrophone }).first!
let microphoneStream = IVSLocalStageStream(device: microphone)

// Configure the audio manager to use the videoChat preset, which is optimized for bi-directional communication, including echo cancellation.
IVSStageAudioManager.sharedInstance().setPreset(.videoChat)

// This is a function on IVSStageStrategy
func stage(_ stage: IVSStage, streamsToPublishForParticipant participant: IVSParticipantInfo) -> [IVSLocalStageStream] {
    return [cameraStream, microphoneStream]
}
```

## Tampilkan dan Hapus Peserta
<a name="ios-publish-subscribe-participants"></a>

Setelah berlangganan selesai, Anda akan menerima array `IVSStageStream` objek melalui fungsi renderer. `didAddStreams` Untuk melihat pratinjau atau menerima statistik tingkat audio tentang peserta ini, Anda dapat mengakses `IVSDevice` objek yang mendasarinya dari aliran:

```
if let imageDevice = stream.device as? IVSImageDevice {
    let preview = imageDevice.previewView()
    /* attach this UIView subclass to your view */
} else if let audioDevice = stream.device as? IVSAudioDevice {
    audioDevice.setStatsCallback( { stats in
        /* process stats.peak and stats.rms */
    })
}
```

Ketika peserta berhenti menerbitkan atau berhenti berlangganan, `didRemoveStreams` fungsi dipanggil dengan aliran yang telah dihapus. Aplikasi host harus menggunakan ini sebagai sinyal untuk menghapus aliran video peserta dari hierarki tampilan.

`didRemoveStreams`dipanggil untuk semua skenario di mana aliran mungkin dihapus, termasuk:
+ Peserta jarak jauh berhenti menerbitkan.
+ Perangkat lokal berhenti berlangganan atau mengubah langganan dari ke`.audioVideo`. `.audioOnly`
+ Peserta jarak jauh meninggalkan panggung.
+ Peserta lokal meninggalkan panggung.

Karena `didRemoveStreams` dipanggil untuk semua skenario, tidak diperlukan logika bisnis khusus untuk menghapus peserta dari UI selama operasi cuti jarak jauh atau lokal.

## Bisukan dan Bunyikan Streaming Media
<a name="ios-publish-subscribe-mute-streams"></a>

`IVSLocalStageStream`objek memiliki `setMuted` fungsi yang mengontrol apakah aliran diredam. Fungsi ini dapat dipanggil pada aliran sebelum atau sesudah dikembalikan dari fungsi `streamsToPublishForParticipant` strategi.

**Penting**: Jika instance `IVSLocalStageStream` objek baru dikembalikan `streamsToPublishForParticipant` setelah panggilan ke`refreshStrategy`, status bisu objek aliran baru diterapkan ke panggung. Hati-hati saat membuat `IVSLocalStageStream` instance baru untuk memastikan status bisu yang diharapkan dipertahankan.

## Pantau Status Bisu Media Peserta Jarak Jauh
<a name="ios-publish-subscribe-mute-state"></a>

Saat peserta mengubah status bisu aliran video atau audionya, `didChangeMutedStreams` fungsi penyaji dipanggil dengan larik aliran yang telah berubah. Gunakan `isMuted` properti `IVSStageStream` untuk memperbarui UI Anda sesuai:

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

## Buat Konfigurasi Panggung
<a name="ios-publish-subscribe-stage-config"></a>

Untuk menyesuaikan nilai konfigurasi video panggung, gunakan`IVSLocalStageStreamVideoConfiguration`:

```
let config = IVSLocalStageStreamVideoConfiguration()
try config.setMaxBitrate(900_000)
try config.setMinBitrate(100_000)
try config.setTargetFramerate(30)
try config.setSize(CGSize(width: 360, height: 640))
config.degradationPreference = .balanced
```

## Dapatkan Statistik WebRTC
<a name="ios-publish-subscribe-webrtc-stats"></a>

Untuk mendapatkan statistik WebRTC terbaru untuk aliran penerbitan atau aliran berlangganan, gunakan terus. `requestRTCStats` `IVSStageStream` Ketika koleksi selesai, Anda akan menerima statistik melalui `IVSStageStreamDelegate` yang dapat diatur`IVSStageStream`. Untuk terus mengumpulkan statistik WebRTC, panggil fungsi ini di file. `Timer`

```
func stream(_ stream: IVSStageStream, didGenerateRTCStats stats: [String : [String : String]]) {
    for stat in stats {
      for member in stat.value {
         print("stat \(stat.key) has member \(member.key) with value \(member.value)")
      }
   }
}
```

## Dapatkan Atribut Peserta
<a name="ios-publish-subscribe-participant-attributes"></a>

Jika Anda menentukan atribut dalam permintaan `CreateParticipantToken` operasi, Anda dapat melihat atribut di `IVSParticipantInfo` properti:

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

## Sematkan Pesan
<a name="ios-publish-subscribe-embed-messages"></a>

`embedMessage`Metode pada IVSImage Perangkat memungkinkan Anda memasukkan muatan metadata langsung ke bingkai video selama penerbitan. Ini memungkinkan pesan yang disinkronkan bingkai untuk aplikasi waktu nyata. Penyematan pesan hanya tersedia saat menggunakan SDK untuk penerbitan real-time (bukan penerbitan latensi rendah).

Pesan yang disematkan tidak dijamin sampai ke pelanggan karena mereka disematkan langsung dalam bingkai video dan ditransmisikan melalui UDP, yang tidak menjamin pengiriman paket. Kehilangan paket selama transmisi dapat mengakibatkan pesan hilang, terutama dalam kondisi jaringan yang buruk. Untuk mengurangi ini, `embedMessage` metode ini menyertakan `repeatCount` parameter yang menduplikasi pesan di beberapa frame berturut-turut, meningkatkan keandalan pengiriman. Kemampuan ini hanya tersedia untuk streaming video.

### Menggunakan EmbedMessage
<a name="ios-embed-messages-using-embedmessage"></a>

Klien penerbitan dapat menyematkan muatan pesan ke aliran video mereka menggunakan `embedMessage` metode di IVSImage Perangkat. Ukuran muatan harus lebih besar dari 0KB dan kurang dari 1KB. Jumlah pesan tertanam yang dimasukkan per detik tidak boleh melebihi 10KB per detik.

```
let imageDevice: IVSImageDevice = imageStream.device as! IVSImageDevice
let messageData = Data("hello world".utf8)

do {
    try imageDevice.embedMessage(messageData, withRepeatCount: 0)
} catch {
    print("Failed to embed message: \(error)")
}
```

### Mengulangi Muatan Pesan
<a name="ios-embed-messages-repeat-payloads"></a>

Gunakan `repeatCount` untuk menduplikasi pesan di beberapa frame untuk meningkatkan keandalan. Nilai ini harus antara 0 dan 30. Klien yang menerima harus memiliki logika untuk menghapus duplikat pesan.

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

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

### Membaca Pesan Tertanam
<a name="ios-embed-messages-read-messages"></a>

Lihat “Dapatkan Informasi Peningkatan Tambahan (SEI)” di bawah ini untuk cara membaca pesan yang disematkan dari aliran masuk. 

## Dapatkan Informasi Peningkatan Tambahan (SEI)
<a name="ios-publish-subscribe-sei-attributes"></a>

Unit Supplemental Enhancement Information (SEI) NAL digunakan untuk menyimpan metadata yang selaras dengan bingkai di samping video. Klien berlangganan dapat membaca muatan SEI dari penerbit yang menerbitkan video H.264 dengan memeriksa `embeddedMessages` properti pada objek yang keluar dari penerbit. `IVSImageDeviceFrame` `IVSImageDevice` Untuk melakukan ini, dapatkan publisher`IVSImageDevice`, lalu amati setiap frame melalui callback yang disediakan`setOnFrameCallback`, seperti yang ditunjukkan pada contoh berikut:

```
// in an IVSStageRenderer’s stage:participant:didAddStreams: function, after acquiring the new IVSImageStream

let imageDevice: IVSImageDevice? = imageStream.device as? IVSImageDevice
imageDevice?.setOnFrameCallback { frame in
	for message in frame.embeddedMessages {
    		if let seiMessage = message as? IVSUserDataUnregisteredSEIMessage {
        		let seiMessageData = seiMessage.data
        		let seiMessageUUID = seiMessage.UUID

        		// interpret the message's data based on the UUID
    		}
	}
}
```

## Lanjutkan Sesi di Latar Belakang
<a name="ios-publish-subscribe-background-session"></a>

Saat aplikasi memasuki latar belakang, Anda dapat terus berada di panggung sambil mendengar audio jarak jauh, meskipun tidak mungkin untuk terus mengirim gambar dan audio Anda sendiri. Anda perlu memperbarui `IVSStrategy` implementasi Anda untuk berhenti menerbitkan dan berlangganan `.audioOnly` (atau`.none`, jika ada):

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

Kemudian buat panggilan ke`stage.refreshStrategy()`.

## Pengkodean Berlapis dengan Simulcast
<a name="ios-publish-subscribe-layered-encoding-simulcast"></a>

Layered encoding dengan simulcast adalah fitur streaming real-time IVS yang memungkinkan penerbit mengirim beberapa lapisan video berkualitas berbeda, dan pelanggan untuk mengonfigurasi lapisan tersebut secara dinamis atau manual. Fitur ini dijelaskan lebih lanjut dalam dokumen [Pengoptimalan Streaming](real-time-streaming-optimization.md).

### Mengkonfigurasi Layered Encoding (Publisher)
<a name="ios-layered-encoding-simulcast-configure-publisher"></a>

Sebagai penerbit, untuk mengaktifkan pengkodean berlapis dengan simulcast, tambahkan konfigurasi berikut ke instantiasi saat Anda: `IVSLocalStageStream`

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

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

// Other Stage implementation code
```

*Bergantung pada resolusi yang Anda tetapkan pada konfigurasi video, sejumlah lapisan akan dikodekan dan dikirim seperti yang didefinisikan di bagian [Lapisan Default, Kualitas, dan Framerates](real-time-streaming-optimization.md#real-time-streaming-optimization-default-layers) dari Pengoptimalan Streaming.*

Selain itu, Anda dapat secara opsional mengonfigurasi lapisan individual dari dalam konfigurasi simulcast:

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

let layers = [
    IVSStagePresets.simulcastLocalLayer().default720(),
    IVSStagePresets.simulcastLocalLayer().default180()
]

try config.simulcast.setLayers(layers)

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

// Other Stage implementation code
```

Sebagai alternatif, Anda dapat membuat konfigurasi lapisan kustom Anda sendiri hingga tiga lapisan. Jika Anda memberikan array kosong atau tidak ada nilai, default yang dijelaskan di atas digunakan. Lapisan dijelaskan dengan setter properti wajib berikut:
+ `setSize: CGSize;`
+ `setMaxBitrate: integer;`
+ `setMinBitrate: integer;`
+ `setTargetFramerate: float;`

Mulai dari preset, Anda dapat mengganti properti individual atau membuat konfigurasi yang sama sekali baru:

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

let customHiLayer = IVSStagePresets.simulcastLocalLayer().default720()
try customHiLayer.setTargetFramerate(15)

let layers = [
    customHiLayer,
    IVSStagePresets.simulcastLocalLayer().default180()
]

try config.simulcast.setLayers(layers)

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

// Other Stage implementation code
```

Untuk nilai maksimum, batas, dan kesalahan yang dapat dipicu saat mengonfigurasi lapisan individual, lihat dokumentasi referensi SDK.

### Mengkonfigurasi Layered Encoding (Subscriber)
<a name="ios-layered-encoding-simulcast-configure-subscriber"></a>

Sebagai pelanggan, tidak ada yang diperlukan untuk mengaktifkan pengkodean berlapis. Jika penerbit mengirim lapisan simulcast, maka secara default server secara dinamis beradaptasi antara lapisan untuk memilih kualitas optimal berdasarkan perangkat pelanggan dan kondisi jaringan.

Atau, untuk memilih lapisan eksplisit yang dikirimkan penerbit, ada beberapa opsi, yang dijelaskan di bawah ini.

### Opsi 1: Preferensi Kualitas Lapisan Awal
<a name="ios-layered-encoding-simulcast-layer-quality-preference"></a>

Dengan menggunakan `subscribeConfigurationForParticipant` strategi, dimungkinkan untuk memilih lapisan awal apa yang ingin Anda terima sebagai pelanggan:

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

    config.simulcast.initialLayerPreference = .lowestQuality

    return config
}
```

Secara default, pelanggan selalu dikirim lapisan kualitas terendah terlebih dahulu; ini perlahan naik ke lapisan kualitas tertinggi. Ini mengoptimalkan konsumsi bandwidth pengguna akhir dan memberikan waktu terbaik untuk video, mengurangi pembekuan video awal bagi pengguna di jaringan yang lebih lemah.

Opsi ini tersedia untuk`InitialLayerPreference`:
+ `lowestQuality`— Server memberikan lapisan video dengan kualitas terendah terlebih dahulu. Ini mengoptimalkan konsumsi bandwidth, serta waktu ke media. Kualitas didefinisikan sebagai kombinasi ukuran, bitrate, dan framerate video. Misalnya, video 720p memiliki kualitas lebih rendah dari video 1080p.
+ `highestQuality`— Server memberikan lapisan video kualitas tertinggi terlebih dahulu. Ini mengoptimalkan kualitas tetapi dapat meningkatkan waktu ke media. Kualitas didefinisikan sebagai kombinasi ukuran, bitrate, dan framerate video. Misalnya, video 1080p berkualitas lebih tinggi dari video 720p.

**Catatan:** Agar preferensi lapisan awal (`initialLayerPreference`panggilan) diterapkan, berlangganan ulang diperlukan karena pembaruan ini tidak berlaku untuk langganan aktif.

### Opsi 2: Lapisan Pilihan untuk Stream
<a name="ios-layered-encoding-simulcast-preferred-layer"></a>

Metode `preferredLayerForStream` strategi memungkinkan Anda memilih lapisan setelah aliran dimulai. Metode strategi ini menerima peserta dan informasi aliran, sehingga Anda dapat memilih lapisan participant-by-participant berdasarkan. SDK memanggil metode ini sebagai respons terhadap peristiwa tertentu, seperti ketika lapisan aliran berubah, status peserta berubah, atau aplikasi host menyegarkan strategi.

Metode strategi mengembalikan `IVSRemoteStageStreamLayer` objek, yang dapat menjadi salah satu dari berikut:
+ Objek layer, seperti yang dikembalikan oleh`IVSRemoteStageStream.layers`.
+ null, yang menunjukkan bahwa tidak ada lapisan yang harus dipilih dan adaptasi dinamis lebih disukai.

Misalnya, strategi berikut akan selalu membuat pengguna memilih lapisan video berkualitas terendah yang tersedia:

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

Untuk mengatur ulang pemilihan lapisan dan kembali ke adaptasi dinamis, kembalikan null atau undefined dalam strategi. Dalam contoh ini, `appState` adalah variabel placeholder yang mewakili status aplikasi host.

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

### Opsi 3: Pembantu RemoteStageStream Lapisan
<a name="ios-layered-encoding-simulcast-remotestagestream-helpers"></a>

`IVSRemoteStageStream`memiliki beberapa pembantu yang dapat digunakan untuk membuat keputusan tentang pemilihan lapisan dan menampilkan pilihan yang sesuai untuk pengguna akhir:
+ **Layer Events** — Di samping itu`IVSStageRenderer`, `IVSRemoteStageStreamDelegate` memiliki peristiwa yang mengkomunikasikan perubahan adaptasi layer dan simulcast:
  + `func stream(_ stream: IVSRemoteStageStream, didChangeAdaption adaption: Bool)`
  + `func stream(_ stream: IVSRemoteStageStream, didChange layers: [IVSRemoteStageStreamLayer])`
  + `func stream(_ stream: IVSRemoteStageStream, didSelect layer: IVSRemoteStageStreamLayer?, reason: IVSRemoteStageStream.LayerSelectedReason)`
+ **Metode Layer** — `IVSRemoteStageStream` memiliki beberapa metode pembantu yang dapat digunakan untuk mendapatkan informasi tentang aliran dan lapisan yang disajikan. Metode ini tersedia di aliran jarak jauh yang disediakan dalam `preferredLayerForStream` strategi, serta aliran jarak jauh yang diekspos melalui`func stage(_ stage: IVSStage, participant: IVSParticipantInfo, didAdd streams: [IVSStageStream])`.
  + `stream.layers`
  + `stream.selectedLayer`
  + `stream.lowestQualityLayer`
  + `stream.highestQualityLayer`
  + `stream.layers(with: IVSRemoteStageStreamLayerConstraints)`

Untuk detailnya, lihat `IVSRemoteStageStream` kelas dalam [dokumentasi referensi SDK](https://aws.github.io/amazon-ivs-broadcast-docs/latest/ios/). Untuk `LayerSelected` alasannya, jika `UNAVAILABLE` dikembalikan, ini menunjukkan bahwa lapisan yang diminta tidak dapat dipilih. Pilihan upaya terbaik dilakukan sebagai gantinya, yang biasanya merupakan lapisan kualitas yang lebih rendah untuk menjaga stabilitas aliran.

## Siarkan Panggung ke Saluran IVS
<a name="ios-publish-subscribe-broadcast-stage"></a>

Untuk menyiarkan panggung, buat yang terpisah `IVSBroadcastSession` dan kemudian ikuti instruksi biasa untuk penyiaran dengan SDK, dijelaskan di atas. `device`Properti pada `IVSStageStream` akan berupa `IVSImageDevice` atau `IVSAudioDevice` seperti yang ditunjukkan pada cuplikan di atas; ini dapat dihubungkan ke `IVSBroadcastSession.mixer` untuk menyiarkan seluruh tahap dalam tata letak yang dapat disesuaikan.

Secara opsional, Anda dapat menggabungkan panggung dan menyiarkannya ke saluran latensi rendah IVS, untuk menjangkau audiens yang lebih besar. Lihat [Mengaktifkan Beberapa Host di Amazon IVS Stream di Panduan Pengguna Streaming](https://docs.aws.amazon.com//ivs/latest/LowLatencyUserGuide/multiple-hosts.html) Latensi Rendah IVS.

# Bagaimana iOS Memilih Resolusi Kamera dan Frame Rate
<a name="ios-publish-subscribe-resolution-framerate"></a>

Kamera yang dikelola oleh SDK siaran mengoptimalkan resolusi dan frame rate (frames-per-second, atau FPS) untuk meminimalkan produksi panas dan konsumsi energi. Bagian ini menjelaskan bagaimana resolusi dan frame rate dipilih untuk membantu aplikasi host mengoptimalkan kasus penggunaannya.

Saat membuat `IVSLocalStageStream` dengan`IVSCamera`, kamera dioptimalkan untuk frame rate `IVSLocalStageStreamVideoConfiguration.targetFramerate` dan resolusi`IVSLocalStageStreamVideoConfiguration.size`. Panggilan `IVSLocalStageStream.setConfiguration` memperbarui kamera dengan nilai yang lebih baru. 

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

Jika Anda membuat pratinjau `IVSCamera` tanpa melampirkannya ke `IVSBroadcastSession` atau`IVSStage`, defaultnya ke resolusi 1080p dan frame rate 60 fps.

## Menyiarkan Panggung
<a name="resolution-framerate-broadcast-stage"></a>

Saat menggunakan `IVSBroadcastSession` untuk menyiarkan`IVSStage`, SDK mencoba mengoptimalkan kamera dengan resolusi dan kecepatan bingkai yang memenuhi kriteria kedua sesi.

Misalnya, jika konfigurasi siaran diatur untuk memiliki frame rate 15 FPS dan resolusi 1080p, sedangkan Stage memiliki frame rate 30 FPS dan resolusi 720p, SDK akan memilih konfigurasi kamera dengan frame rate 30 FPS dan resolusi 1080p. Ini `IVSBroadcastSession` akan menjatuhkan setiap bingkai lain dari kamera, dan akan `IVSStage` menskalakan gambar 1080p ke 720p.

Jika aplikasi host berencana menggunakan keduanya `IVSBroadcastSession` dan `IVSStage` bersama-sama, dengan kamera, kami menyarankan agar `targetFramerate` dan `size` properti dari konfigurasi masing-masing cocok. Ketidakcocokan dapat menyebabkan kamera mengkonfigurasi ulang dirinya sendiri saat merekam video, yang akan menyebabkan penundaan singkat dalam pengiriman sampel video.

Jika memiliki nilai yang identik tidak memenuhi kasus penggunaan aplikasi host, membuat kamera berkualitas lebih tinggi terlebih dahulu akan mencegah kamera mengkonfigurasi ulang dirinya sendiri ketika sesi kualitas yang lebih rendah ditambahkan. Misalnya, jika Anda menyiarkan pada 1080p dan 30 FPS dan kemudian bergabung dengan Stage yang disetel ke 720p dan 30 FPS, kamera tidak akan mengkonfigurasi ulang dirinya sendiri dan video akan terus tanpa gangguan. Ini karena 720p kurang dari atau sama dengan 1080p dan 30 FPS kurang dari atau sama dengan 30 FPS.

## Frame Rate, Resolusi, dan Rasio Aspek Sewenang-wenang
<a name="resolution-framerate-arbitrary"></a>

Sebagian besar perangkat keras kamera dapat sama persis dengan format umum, seperti 720p pada 30 FPS atau 1080p pada 60 FPS. Namun, tidak mungkin untuk benar-benar mencocokkan semua format. SDK siaran memilih konfigurasi kamera berdasarkan aturan berikut (dalam urutan prioritas):

1. Lebar dan tinggi resolusi lebih besar dari atau sama dengan resolusi yang diinginkan, tetapi dalam batasan ini, lebar dan tinggi sekecil mungkin.

1. Frame rate lebih besar dari atau sama dengan frame rate yang diinginkan, tetapi dalam batasan ini, frame rate serendah mungkin.

1. Rasio aspek cocok dengan rasio aspek yang diinginkan.

1. Jika ada beberapa format yang cocok, format dengan bidang pandang terbesar digunakan.

Berikut adalah dua contoh:
+ Aplikasi host mencoba menyiarkan dalam 4k pada 120 FPS. Kamera yang dipilih hanya mendukung 4k pada 60 FPS atau 1080p pada 120 FPS. Format yang dipilih akan menjadi 4k pada 60 FPS, karena aturan resolusi lebih tinggi prioritas daripada aturan frame-rate.
+ Resolusi tidak teratur diminta, 1910x1070. Kamera akan menggunakan 1920x1080. *Hati-hati: memilih resolusi seperti 1921x1080 akan menyebabkan kamera meningkatkan ke resolusi berikutnya yang tersedia (seperti 2592x1944),* yang menimbulkan penalti CPU dan memory-bandwidth.

## Bagaimana dengan Android?
<a name="resolution-framerate-android"></a>

Android tidak menyesuaikan resolusi atau frame rate dengan cepat seperti iOS, jadi ini tidak memengaruhi SDK siaran Android.

# Masalah & Solusi yang Diketahui di SDK Siaran iOS IVS \$1 Streaming Waktu Nyata
<a name="broadcast-ios-known-issues"></a>

Dokumen ini mencantumkan masalah yang diketahui yang mungkin Anda temui saat menggunakan SDK siaran iOS streaming real-time Amazon IVS dan menyarankan solusi potensial.
+ Mengubah rute audio Bluetooth tidak dapat diprediksi. Jika Anda menghubungkan perangkat baru di tengah sesi, iOS mungkin atau mungkin tidak secara otomatis mengubah rute input. Selain itu, tidak mungkin memilih di antara beberapa headset Bluetooth yang terhubung secara bersamaan. Ini terjadi di sesi siaran reguler dan panggung.

  **Solusi:** Jika Anda berencana untuk menggunakan headset Bluetooth, sambungkan sebelum memulai siaran atau panggung dan biarkan terhubung sepanjang sesi.
+ Peserta yang menggunakan iPhone 14, iPhone 14 Plus, iPhone 14 Pro, atau iPhone 14 Pro Max dapat menyebabkan masalah gema audio bagi peserta lain.

  **Solusi:** Peserta yang menggunakan perangkat yang terpengaruh dapat menggunakan headphone untuk mencegah masalah gema bagi peserta lain.
+ Ketika peserta bergabung dengan token yang digunakan oleh peserta lain, koneksi pertama terputus tanpa kesalahan tertentu.

  **Solusi**: Tidak ada.
+ Ada masalah langka di mana penerbit menerbitkan tetapi status publikasi yang diterima pelanggan adalah`inactive`.

  **Solusi:** Coba pergi dan kemudian bergabung dengan sesi. Jika masalah tetap ada, buat token baru untuk penerbit.
+ Ketika seorang peserta menerbitkan atau berlangganan, dimungkinkan untuk menerima kesalahan dengan kode 1400 yang menunjukkan pemutusan karena masalah jaringan, bahkan ketika jaringan stabil.

  **Solusi: Coba terbitkan ulang/berlangganan** ulang.
+ Masalah distorsi audio yang jarang terjadi dapat terjadi sebentar-sebentar selama sesi panggung, biasanya pada panggilan dengan durasi yang lebih lama.

  **Solusi:** Peserta dengan audio yang terdistorsi dapat meninggalkan dan bergabung kembali dengan sesi, atau membatalkan publikasi dan menerbitkan ulang audio mereka untuk memperbaiki masalah.

# Penanganan Kesalahan di SDK Siaran iOS IVS \$1 Streaming Waktu Nyata
<a name="broadcast-ios-error-handling"></a>

Bagian ini adalah ikhtisar kondisi kesalahan, bagaimana IVS real-time streaming iOS broadcast SDK melaporkannya ke aplikasi, dan apa yang harus dilakukan aplikasi ketika kesalahan tersebut ditemui.

## Kesalahan Fatal vs Non-Fatal
<a name="broadcast-ios-fatal-vs-nonfatal-errors"></a>

Objek kesalahan memiliki boolean “fatal”. Ini adalah entri kamus di bawahnya `IVSBroadcastErrorIsFatalKey` yang berisi boolean.

Secara umum, kesalahan fatal terkait dengan koneksi ke server Tahapan (baik koneksi tidak dapat dibuat atau hilang dan tidak dapat dipulihkan). Aplikasi harus membuat ulang panggung dan bergabung kembali, mungkin dengan token baru atau ketika konektivitas perangkat pulih.

Kesalahan non-fatal umumnya terkait dengan publish/subscribe status dan ditangani oleh SDK, yang mencoba ulang operasi. publish/subscribe 

Anda dapat memeriksa properti ini:

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

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

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

Ini terjadi ketika token panggung salah bentuk.

SDK melempar pengecualian Swift dengan kode kesalahan = 1000 dan IVSBroadcast ErrorIsFatalKey = YA.

**Tindakan**: Buat token yang valid dan coba lagi bergabung.

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

Ini terjadi ketika token panggung kedaluwarsa.

SDK melempar pengecualian Swift dengan kode kesalahan = 1001 dan = YA. IVSBroadcast ErrorIsFatalKey 

**Tindakan**: Buat token baru dan coba lagi bergabung.

### Token Tidak Valid atau Dicabut
<a name="broadcast-ios-stage-join-errors-invalid-token"></a>

Ini terjadi ketika token panggung tidak cacat tetapi ditolak oleh server Stages. Ini dilaporkan secara asinkron melalui perender tahap yang disediakan aplikasi.

SDK memanggil `stage(didChange connectionState, withError error)` dengan kode kesalahan = 1026 dan IVSBroadcast ErrorIsFatalKey = YA.

**Tindakan**: Buat token yang valid dan coba lagi bergabung.

### Kesalahan Jaringan untuk Gabung Awal
<a name="broadcast-ios-stage-join-errors-network-initial-join"></a>

Ini terjadi ketika SDK tidak dapat menghubungi server Stages untuk membuat koneksi. Ini dilaporkan secara asinkron melalui perender tahap yang disediakan aplikasi.

SDK memanggil `stage(didChange connectionState, withError error)` dengan kode kesalahan = 1300 dan IVSBroadcast ErrorIsFatalKey = YA.

**Tindakan**: Tunggu konektivitas perangkat pulih dan coba lagi bergabung.

### Kesalahan Jaringan saat Sudah Bergabung
<a name="broadcast-ios-stage-join-errors-network-already-joined"></a>

Jika koneksi jaringan perangkat mati, SDK mungkin kehilangan koneksinya ke server Stage. Ini dilaporkan secara asinkron melalui perender tahap yang disediakan aplikasi.

SDK memanggil `stage(didChange connectionState, withError error)` dengan kode kesalahan = 1300 dan IVSBroadcast ErrorIsFatalKey nilai = YA.

**Tindakan**: Tunggu konektivitas perangkat pulih dan coba lagi bergabung.

## Kesalahan Publikasi/Berlangganan
<a name="broadcast-ios-publish-subscribe-errors"></a>

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

Ada beberapa kesalahan:
+ MultihostSessionOfferCreationFailPublish (1020)
+ MultihostSessionOfferCreationFailSubscribe (1021)
+ MultihostSessionNoIceCandidates (1022)
+ MultihostSessionStageAtCapacity (1024)
+ SignallingSessionCannotRead (1201)
+ SignallingSessionCannotSend (1202)
+ SignallingSessionBadResponse (1203)

Ini dilaporkan secara asinkron melalui perender tahap yang disediakan aplikasi.

SDK mencoba ulang operasi untuk beberapa kali. Selama percobaan ulang, publish/subscribe negara adalah`ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE`. Jika upaya coba lagi berhasil, status berubah menjadi`PUBLISHED`/`SUBSCRIBED`.

Panggilan SDK `IVSErrorDelegate:didEmitError` dengan kode kesalahan yang relevan dan`IVSBroadcastErrorIsFatalKey == NO`.

**Tindakan**: Tidak diperlukan tindakan, karena SDK mencoba ulang secara otomatis. Secara opsional, aplikasi dapat menyegarkan strategi untuk memaksa lebih banyak percobaan ulang.

### Sudah Didirikan, Lalu Gagal
<a name="broadcast-ios-publish-subscribe-errors-established"></a>

Publikasi atau berlangganan dapat gagal setelah dibuat, kemungkinan besar karena kesalahan jaringan. Kode kesalahan untuk “koneksi rekan hilang karena kesalahan jaringan” adalah 1400.

Ini dilaporkan secara asinkron melalui perender tahap yang disediakan aplikasi.

SDK mencoba ulang operasi. publish/subscribe Selama percobaan ulang, publish/subscribe negara adalah`ATTEMPTING_PUBLISH`/`ATTEMPTING_SUBSCRIBE`. Jika upaya coba lagi berhasil, status berubah menjadi`PUBLISHED`/`SUBSCRIBED`.

SDK memanggil `didEmitError` dengan kode kesalahan = 1400 dan IVSBroadcast ErrorIsFatalKey = NO.

**Tindakan**: Tidak diperlukan tindakan, karena SDK mencoba ulang secara otomatis. Secara opsional, aplikasi dapat menyegarkan strategi untuk memaksa lebih banyak percobaan ulang. Jika terjadi kehilangan konektivitas total, kemungkinan koneksi ke Stages juga akan gagal.

# SDK Siaran IVS: Perangkat Campuran
<a name="broadcast-mixed-devices"></a>

Perangkat campuran adalah perangkat audio dan video yang mengambil beberapa sumber input dan menghasilkan satu output. Perangkat pencampuran adalah fitur canggih yang memungkinkan Anda menentukan dan mengelola beberapa elemen (video) dan trek audio di layar. Anda dapat menggabungkan video dan audio dari berbagai sumber seperti kamera, mikrofon, tangkapan layar, serta audio dan video yang dihasilkan oleh aplikasi Anda. Anda dapat menggunakan transisi untuk memindahkan sumber-sumber ini di sekitar video yang Anda streaming ke IVS, dan menambah dan menghapus sumber di tengah aliran.

Perangkat campuran datang dalam rasa gambar dan audio. Untuk membuat perangkat gambar campuran, hubungi:

`DeviceDiscovery.createMixedImageDevice()`di Android

`IVSDeviceDiscovery.createMixedImageDevice()`di iOS

Perangkat yang dikembalikan dapat dilampirkan ke `BroadcastSession` (streaming latensi rendah) atau `Stage` (streaming waktu nyata), seperti perangkat lainnya.

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

![\[IVS menyiarkan terminologi perangkat campuran.\]](http://docs.aws.amazon.com/id_id/ivs/latest/RealTimeUserGuide/images/Broadcast_SDK_Mixer_Glossary.png)



| Istilah | Deskripsi | 
| --- | --- | 
| Perangkat | Komponen perangkat keras atau perangkat lunak yang menghasilkan input audio atau gambar. Contoh perangkat adalah mikrofon, kamera, headset Bluetooth, dan perangkat virtual seperti tangkapan layar atau input gambar khusus. | 
| Perangkat Campuran | A `Device` yang dapat dilampirkan `BroadcastSession` seperti yang lain`Device`, tetapi dengan tambahan APIs yang memungkinkan `Source` objek ditambahkan. Perangkat campuran memiliki mixer internal yang menggabungkan audio atau gambar, menghasilkan audio output tunggal dan aliran gambar. Perangkat campuran datang dalam ragam audio atau gambar.  | 
| Konfigurasi perangkat campuran | Objek konfigurasi untuk perangkat campuran. Untuk perangkat gambar campuran, ini mengonfigurasi properti seperti dimensi dan framerate. Untuk perangkat audio campuran, ini mengonfigurasi jumlah saluran. | 
|  Sumber | Wadah yang mendefinisikan posisi elemen visual di layar dan properti trek audio dalam campuran audio. Perangkat campuran dapat dikonfigurasi dengan nol atau lebih sumber. Sumber diberikan konfigurasi yang mempengaruhi bagaimana media sumber digunakan. Gambar di atas menunjukkan empat sumber gambar: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/id_id/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html)  | 
| Konfigurasi Sumber |  Objek konfigurasi untuk sumber yang masuk ke perangkat campuran. Objek konfigurasi lengkap dijelaskan di bawah ini..   | 
| Transisi | Untuk memindahkan slot ke posisi baru atau mengubah beberapa propertinya, gunakan`MixedDevice.transitionToConfiguration()`. Metode ini membutuhkan: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/id_id/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html) | 

## Perangkat Audio Campuran
<a name="broadcast-mixed-audio-device"></a>

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

`MixedAudioDeviceConfiguration`di Android

`IVSMixedAudioDeviceConfiguration`di iOS


| Nama | Tipe | Deskripsi | 
| --- | --- | --- | 
| `channels` | Bilangan Bulat | Jumlah saluran output dari mixer audio. Nilai yang valid: 1, 2. 1 adalah audio mono; 2, audio stereo. Default: 2. | 

### Konfigurasi Sumber
<a name="broadcast-mixed-audio-device-source-configuration"></a>

`MixedAudioDeviceSourceConfiguration`di Android

`IVSMixedAudioDeviceSourceConfiguration`di iOS


| Nama | Tipe | Deskripsi | 
| --- | --- | --- | 
| `gain` | Desimal | Keuntungan audio. Ini adalah pengganda, jadi nilai apa pun di atas 1 meningkatkan keuntungan; nilai apa pun di bawah 1, menurunkannya. Nilai yang valid: 0-2. Default: 1.  | 

## Perangkat Gambar Campuran
<a name="broadcast-mixed-image-device"></a>

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

`MixedImageDeviceConfiguration`di Android

`IVSMixedImageDeviceConfiguration`di iOS


| Nama | Tipe | Deskripsi | 
| --- | --- | --- | 
| `size` | Vec2 | Ukuran kanvas video. | 
| `targetFramerate` | Bilangan Bulat | Jumlah frame target per detik untuk perangkat campuran. Rata-rata, nilai ini harus dipenuhi, tetapi sistem dapat menjatuhkan frame dalam keadaan tertentu (misalnya, beban CPU atau GPU yang tinggi). | 
| `transparencyEnabled` | Boolean | Ini memungkinkan pencampuran menggunakan `alpha` properti pada konfigurasi sumber gambar. Mengatur ini untuk `true` meningkatkan konsumsi memori dan CPU. Default: `false`. | 

### Konfigurasi Sumber
<a name="broadcast-mixed-image-device-source-configuration"></a>

`MixedImageDeviceSourceConfiguration`di Android

`IVSMixedImageDeviceSourceConfiguration`di iOS


| Nama | Tipe | Deskripsi | 
| --- | --- | --- | 
| `alpha` | Desimal | Alpha dari slot. Ini multiplikatif dengan nilai alfa apa pun pada gambar. Nilai yang valid: 0-1. 0 sepenuhnya transparan dan 1 sepenuhnya buram. Default: 1. | 
| `aspect` | AspectMode | Mode rasio aspek untuk gambar apa pun yang dirender di slot. Nilai valid: [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/id_id/ivs/latest/RealTimeUserGuide/broadcast-mixed-devices.html) Default: `Fit`  | 
| `fillColor` | Vec4 | Isi warna yang akan digunakan `aspect Fit` ketika rasio aspek slot dan gambar tidak cocok. Formatnya adalah (merah, hijau, biru, alfa). Nilai valid (untuk setiap saluran): 0-1. Default: (0, 0, 0, 0). | 
| `position` | Vec2 | Posisi slot (dalam piksel), relatif terhadap sudut kiri atas kanvas. Asal slot juga kiri atas. | 
| `size` | Vec2 | Ukuran slot, dalam piksel. Menyetel nilai ini juga disetel `matchCanvasSize` ke`false`. Default: (0, 0); Namun, karena `matchCanvasSize` defaultnya`true`, ukuran slot yang dirender adalah ukuran kanvas, bukan (0, 0). | 
| `zIndex` | Desimal | Pemesanan slot relatif. Slot dengan `zIndex` nilai yang lebih tinggi digambar di atas slot dengan `zIndex` nilai yang lebih rendah. | 

## Membuat dan Mengkonfigurasi Perangkat Gambar Campuran
<a name="broadcast-mixed-image-device-creating-configuring"></a>

![\[Mengkonfigurasi sesi siaran untuk pencampuran.\]](http://docs.aws.amazon.com/id_id/ivs/latest/RealTimeUserGuide/images/Broadcast_SDK_Mixer_Configuring.png)


Di sini, kami membuat adegan yang mirip dengan yang ada di awal panduan ini, dengan tiga elemen di layar:
+ Slot kiri bawah untuk kamera.
+ Slot kanan bawah untuk hamparan logo.
+ Slot kanan atas untuk film.

Perhatikan bahwa asal kanvas adalah sudut kiri atas dan ini sama untuk slot. Oleh karena itu, memposisikan slot di (0, 0) menempatkannya di sudut kiri atas dengan seluruh slot terlihat.

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

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

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

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

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

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

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

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

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

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

## Menghapus Sumber
<a name="broadcast-mixed-devices-removing-sources"></a>

Untuk menghapus sumber, panggil `MixedDevice.remove` dengan `Source` objek yang ingin Anda hapus.

## Animasi dengan Transisi
<a name="broadcast-mixed-devices-animations-transitions"></a>

Metode transisi menggantikan konfigurasi sumber dengan konfigurasi baru. Penggantian ini dapat dianimasikan dari waktu ke waktu dengan menetapkan durasi lebih tinggi dari 0, dalam hitungan detik. 

### Properti apa yang bisa dianimasikan?
<a name="broadcast-mixed-devices-animations-properties"></a>

Tidak semua properti dalam struktur slot dapat dianimasikan. Properti apa pun berdasarkan tipe Float dapat dianimasikan; properti lain berlaku baik di awal atau akhir animasi.


| Nama | Bisakah itu dianimasikan? | Titik Dampak | 
| --- | --- | --- | 
| `Audio.gain` | Ya | Diinterpolasi | 
| `Image.alpha` | Ya | Diinterpolasi | 
| `Image.aspect` | Tidak | Akhiri | 
| `Image.fillColor` | Ya | Diinterpolasi | 
| `Image.position` | Ya | Diinterpolasi | 
| `Image.size` | Ya | Diinterpolasi | 
| `Image.zIndex` Catatan: Pesawat `zIndex` bergerak 2D melalui ruang 3D, sehingga transisi terjadi ketika dua bidang menyeberang di beberapa titik di tengah animasi. Ini bisa dihitung, tetapi itu tergantung pada `zIndex` nilai awal dan akhir. Untuk transisi yang lebih lancar, gabungkan ini dengan`alpha`.  | Ya | Tidak Diketahui | 

### Contoh Sederhana
<a name="broadcast-mixed-devices-animations-examples"></a>

Di bawah ini adalah contoh pengambilalihan kamera layar penuh menggunakan konfigurasi yang ditentukan di atas dalam [Membuat dan Mengkonfigurasi Perangkat Gambar Campuran](#broadcast-mixed-image-device-creating-configuring). Ini dianimasikan lebih dari 0,5 detik.

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

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

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

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

## Mencerminkan Siaran
<a name="broadcast-mixed-devices-mirroring"></a>


| Untuk mencerminkan perangkat gambar terlampir dalam siaran ke arah ini... | Gunakan nilai negatif untuk... | 
| --- | --- | 
| Horizontal | Lebar slot | 
| Vertikal | Ketinggian slot | 
| Baik horisontal maupun vertikal | Baik lebar dan tinggi slot | 

Posisi perlu disesuaikan dengan nilai yang sama, untuk menempatkan slot pada posisi yang benar saat dicerminkan.

Di bawah ini adalah contoh untuk mencerminkan siaran secara horizontal dan vertikal.

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

Pencerminan horisontal:

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

Pencerminan vertikal:

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

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

Pencerminan horisontal:

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

Pencerminan vertikal:

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

Catatan: Pencerminan ini berbeda dengan `setMirrored` metode di `ImagePreviewView` (Android) dan `IVSImagePreviewView` (iOS). Metode itu hanya memengaruhi tampilan pratinjau lokal di perangkat dan tidak memengaruhi siaran.

# SDK Siaran IVS: Pertukaran Token \$1 Streaming Waktu Nyata
<a name="broadcast-mobile-token-exchange"></a>

Pertukaran token memungkinkan Anda untuk meningkatkan atau menurunkan kemampuan token peserta dan memperbarui atribut token dalam SDK siaran, tanpa mengharuskan peserta untuk terhubung kembali. Ini berguna untuk skenario seperti co-hosting, di mana peserta dapat memulai dengan kemampuan berlangganan saja dan kemudian membutuhkan kemampuan publikasi.

Pertukaran token didukung di siaran seluler dan web SDKs. Saat peserta bertukar token, komposisi sisi server mendeteksi atribut yang diperbarui secara real time dan secara otomatis menyesuaikan tata letak — misalnya, menetapkan kembali slot unggulan, menyusun ulang peserta, atau memindahkan peserta ke overlay — tanpa memerlukan sambungan ulang. picture-in-picture 

Batasan: Pertukaran token hanya berfungsi dengan token yang dibuat di server Anda menggunakan [key pair](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-distribute-tokens.html#getting-started-distribute-tokens-self-signed). Ini tidak berfungsi dengan token yang dibuat melalui [CreateParticipantToken API](https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/API_CreateParticipantToken.html).

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

Pertukaran token sangat mudah: panggil `exchangeToken` API pada `IVSStage` objek`Stage`/dan berikan token baru. Jika token baru berbeda dari token sebelumnya, kemampuan token baru segera dievaluasi. `capabilities` Misalnya, jika token sebelumnya tidak memiliki `publish` kemampuan dan token baru melakukannya, fungsi strategi panggung untuk penerbitan dipanggil, memungkinkan aplikasi host untuk memutuskan apakah mereka ingin segera mempublikasikan dengan kemampuan baru, atau menunggu. Hal yang sama berlaku untuk kemampuan yang dihapus: jika token sebelumnya memiliki `publish` kemampuan dan token baru tidak, peserta segera membatalkan publikasi tanpa menggunakan fungsi strategi panggung untuk penerbitan.

Saat bertukar token, token sebelumnya dan baru harus memiliki nilai yang sama untuk bidang payload berikut: 
+ `topic`
+ `resource`
+ `jti`
+ `whip_url`
+ `events_url`

Bidang ini tidak dapat diubah. Bertukar token yang memodifikasi bidang yang tidak dapat diubah menghasilkan SDK segera menolak pertukaran.

Bidang yang tersisa dapat diubah, termasuk:
+ `attributes`
+ `capabilities`
+ `user`
+ `_id`
+ `iat`
+ `exp`

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



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

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



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

### Web
<a name="broadcast-web-token-exchange-exchanging-tokens"></a>



```
const stage = new Stage(originalToken, strategy);
await stage.join();
await stage.exchangeToken(newToken);
```

## Menerima Pembaruan
<a name="broadcast-mobile-token-exchange-receiving-updates"></a>

Fungsi di`StageRenderer`/`IVSStageRenderer`menerima pembaruan tentang peserta jarak jauh yang sudah dipublikasikan yang bertukar token mereka untuk memperbarui atau. `userId` `attributes` Peserta jarak jauh yang belum menerbitkan akan diperbarui `userId` dan `attributes` diekspos melalui fungsi`onParticipantJoined`/`participantDidJoin`renderer yang ada jika mereka akhirnya mempublikasikannya.

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



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

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



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

### Web
<a name="broadcast-web-token-exchange-receiving-updates"></a>



```
stage.on(StageEvents.STAGE_PARTICIPANT_METADATA_CHANGED, (participantInfo: StageParticipantInfo) => { 
    // participantInfo properties will be updated with the changed properties 
    }
);
```

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

Saat peserta menukar token untuk memperbarui `userId` atau`attributes`, visibilitas perubahan ini bergantung pada status penerbitan mereka saat ini: 
+ **Jika peserta *tidak* menerbitkan:** Pembaruan diproses secara diam-diam. Jika mereka akhirnya mempublikasikan, semua SDKs akan menerima yang diperbarui `userId` dan `attributes` sebagai bagian dari acara publikasi awal.
+ **Jika peserta *sudah* menerbitkan:** Pembaruan disiarkan segera untuk peserta yang menggunakan seluler SDKs v1.37.0\$1, SDK web, dan komposisi sisi server. Peserta yang menggunakan ponsel lama SDKs tidak melihat perubahan sampai peserta tidak menerbitkan dan menerbitkan kembali.

Tabel ini menjelaskan matriks dukungan:


| Negara Peserta | Pengamat: SDK Seluler 1.37.0\$1, SDK Web, Komposisi Sisi Server  | Pengamat: Ponsel Lama SDKs | 
| --- | --- | --- | 
| Tidak menerbitkan (lalu dimulai) | ✅ Terlihat (saat dipublikasikan melalui acara yang bergabung dengan peserta) | ✅ Terlihat (saat dipublikasikan melalui acara yang bergabung dengan peserta) | 
| Sudah menerbitkan (tidak pernah menerbitkan kembali) | ✅ Terlihat (segera melalui acara yang diperbarui metadata peserta) | ❌ Tidak Terlihat | 
| Sudah menerbitkan (tidak menerbitkan dan menerbitkan kembali) | ✅ Terlihat (segera melalui acara yang diperbarui metadata peserta) | ⚠️ Akhirnya Terlihat (saat dipublikasikan ulang melalui acara yang bergabung dengan peserta) | 

# SDK Siaran IVS: Sumber Gambar Kustom \$1 Streaming Waktu Nyata
<a name="broadcast-custom-image-sources"></a>

Sumber input gambar khusus memungkinkan aplikasi untuk menyediakan input gambarnya sendiri ke SDK siaran, alih-alih terbatas pada kamera preset. Sumber gambar khusus dapat sesederhana tanda air semi-transparan atau adegan “segera kembali” statis, atau memungkinkan aplikasi untuk melakukan pemrosesan khusus tambahan seperti menambahkan filter kecantikan ke kamera.

Saat Anda menggunakan sumber input gambar khusus untuk kontrol kustom kamera (seperti menggunakan pustaka filter kecantikan yang memerlukan akses kamera), SDK siaran tidak lagi bertanggung jawab untuk mengelola kamera. Sebagai gantinya, aplikasi bertanggung jawab untuk menangani siklus hidup kamera dengan benar. Lihat dokumentasi platform resmi tentang bagaimana aplikasi Anda harus mengelola kamera.

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

Setelah Anda membuat `DeviceDiscovery` sesi, buat sumber input gambar:

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

Metode ini mengembalikan`CustomImageSource`, yang merupakan sumber gambar yang didukung oleh Android [Surface](https://developer.android.com/reference/android/view/Surface) standar. Sublcass `SurfaceSource` dapat diubah ukurannya dan diputar. Anda juga dapat membuat `ImagePreviewView` untuk menampilkan pratinjau isinya.

Untuk mengambil yang mendasarinya`Surface`:

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

Ini `Surface` dapat digunakan sebagai buffer output untuk produsen gambar seperti Camera2, OpenGL ES, dan perpustakaan lainnya. Kasus penggunaan paling sederhana adalah langsung menggambar bitmap statis atau warna ke dalam kanvas Surface. Namun, banyak pustaka (seperti pustaka filter kecantikan) menyediakan metode yang memungkinkan aplikasi menentukan eksternal untuk rendering. `Surface` Anda dapat menggunakan metode seperti itu untuk meneruskan ini `Surface` ke pustaka filter, yang memungkinkan pustaka mengeluarkan bingkai yang diproses agar sesi siaran dapat dialirkan.

Ini `CustomImageSource` dapat dibungkus dalam a `LocalStageStream` dan dikembalikan oleh `StageStrategy` untuk mempublikasikan ke a`Stage`.

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

Setelah Anda membuat `DeviceDiscovery` sesi, buat sumber input gambar:

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

Metode ini mengembalikan`IVSCustomImageSource`, yang merupakan sumber gambar yang memungkinkan aplikasi untuk mengirimkan `CMSampleBuffers` secara manual. Untuk format piksel yang didukung, lihat Referensi SDK Siaran iOS; tautan ke versi terbaru ada di [Catatan Rilis Amazon IVS untuk rilis](release-notes.md) SDK siaran terbaru.

Sampel yang dikirimkan ke sumber kustom akan dialirkan ke Panggung:

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

Untuk streaming video, gunakan metode ini dalam panggilan balik. Misalnya, jika Anda menggunakan kamera, maka setiap kali buffer sampel baru diterima dari sebuah`AVCaptureSession`, aplikasi dapat meneruskan buffer sampel ke sumber gambar khusus. Jika diinginkan, aplikasi dapat menerapkan pemrosesan lebih lanjut (seperti filter kecantikan) sebelum mengirimkan sampel ke sumber gambar khusus.

`IVSCustomImageSource`Dapat dibungkus dalam `IVSLocalStageStream` dan dikembalikan oleh `IVSStageStrategy` untuk mempublikasikan ke a`Stage`.

# SDK Siaran IVS: Sumber Audio Kustom \$1 Streaming Waktu Nyata
<a name="broadcast-custom-audio-sources"></a>

**Catatan:** Panduan ini hanya berlaku untuk SDK siaran Android streaming real-time IVS. Informasi untuk iOS dan web SDKs akan dipublikasikan di masa depan.

Sumber input audio khusus memungkinkan aplikasi untuk menyediakan input audio sendiri ke SDK siaran, alih-alih terbatas pada mikrofon bawaan perangkat. Sumber audio khusus memungkinkan aplikasi untuk mengalirkan audio yang diproses dengan efek, mencampur beberapa aliran audio, atau mengintegrasikan dengan pustaka pemrosesan audio pihak ketiga.

Saat Anda menggunakan sumber input audio khusus, SDK siaran tidak lagi bertanggung jawab untuk mengelola mikrofon secara langsung. Sebagai gantinya, aplikasi Anda bertanggung jawab untuk menangkap, memproses, dan mengirimkan data audio ke sumber khusus.

 custom-audio-sourceAlur kerja mengikuti langkah-langkah ini:

1. Input audio - Buat sumber audio khusus dengan format audio tertentu (laju sampel, saluran, format). 

1. Pemrosesan Anda — Tangkap atau hasilkan data audio dari pipeline pemrosesan audio Anda.

1. Sumber audio khusus - Kirim buffer audio ke sumber khusus menggunakan`appendBuffer()`.

1. Panggung — Bungkus `LocalStageStream` dan publikasikan ke panggung melalui Anda`StageStrategy`. 

1. Peserta — Peserta panggung menerima audio yang diproses secara real time.

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

### Membuat Sumber Audio Kustom
<a name="custom-audio-sources-android-creating-a-custom-audio-source"></a>

Setelah Anda membuat `DeviceDiscovery` sesi, buat sumber input audio kustom:

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

Metode ini mengembalikan`CustomAudioSource`, yang menerima data audio PCM mentah. Sumber audio kustom harus dikonfigurasi dengan format audio yang sama dengan yang dihasilkan pipeline pemrosesan audio Anda.

#### Format Audio yang Didukung
<a name="custom-audio-sources-android-submitting-audio-data-supportedi-audio-formats"></a>


| Parameter | Opsi | Deskripsi | 
| --- | --- | --- | 
| Saluran | 1 (mono), 2 (stereo) | Jumlah saluran audio. | 
| Tingkat sampel | RATE\$116000, RATE\$144100, RATE\$148000 | Kecepatan sampel audio dalam Hz. 48kHz direkomendasikan untuk kualitas tinggi. | 
| Format | INT16, FLOAT32 | Format sampel audio. INT16 adalah PCM titik tetap 16-bit, adalah PCM floating-point FLOAT32 32-bit. Format interleaved dan planar tersedia. | 

### Mengirimkan Data Audio
<a name="custom-audio-sources-android-submitting-audio-data"></a>

Untuk mengirimkan data audio ke sumber kustom, gunakan `appendBuffer()` metode ini:

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

**Pertimbangan penting:**
+ Data audio harus dalam format yang ditentukan saat membuat sumber khusus.
+ Stempel waktu harus ditingkatkan secara monoton dan disediakan oleh sumber audio Anda untuk pemutaran audio yang lancar.
+ Kirim audio secara teratur untuk menghindari celah dalam aliran.
+ Metode mengembalikan jumlah sampel yang diproses (0 menunjukkan kegagalan). 

### Penerbitan ke Panggung
<a name="custom-audio-sources-android-publishing-to-a-stage"></a>

Bungkus `CustomAudioSource` dalam `AudioLocalStageStream` dan kembalikan dari`StageStrategy`:

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

### Contoh Lengkap: Integrasi Pemrosesan Audio
<a name="custom-audio-sources-android-complete-example"></a>

Berikut adalah contoh lengkap yang menunjukkan integrasi dengan SDK pemrosesan audio:

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

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

### Praktik Terbaik
<a name="custom-audio-sources-android-best-practices"></a>

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

Pastikan format audio yang Anda kirimkan cocok dengan format yang ditentukan saat membuat sumber kustom:

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

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

Gunakan langsung `ByteBuffers` dan gunakan kembali untuk meminimalkan pengumpulan sampah: 

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

#### Pengaturan Waktu dan Sinkronisasi
<a name="custom-audio-sources-android-best-practices-timing-and-synchronization"></a>

Anda harus menggunakan stempel waktu yang disediakan oleh sumber audio Anda untuk pemutaran audio yang lancar. Jika sumber audio Anda tidak menyediakan stempel waktu sendiri, buat stempel waktu epoch Anda sendiri dan hitung durasi antara setiap sampel secara manual menggunakan jumlah bingkai dan ukuran bingkai. 

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

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

Selalu periksa nilai pengembalian`appendBuffer()`: 

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

# SDK Siaran IVS: Filter Kamera Pihak Ketiga \$1 Streaming Waktu Nyata
<a name="broadcast-3p-camera-filters"></a>

Panduan ini mengasumsikan Anda sudah terbiasa dengan sumber [gambar khusus](broadcast-custom-image-sources.md) serta mengintegrasikan [SDK siaran streaming real-time IVS](broadcast.md) ke dalam aplikasi Anda.

Filter kamera memungkinkan pembuat live-stream untuk menambah atau mengubah tampilan wajah atau latar belakang mereka. Ini berpotensi meningkatkan keterlibatan pemirsa, menarik pemirsa, dan meningkatkan pengalaman streaming langsung.

# Mengintegrasikan Filter Kamera Pihak Ketiga
<a name="broadcast-3p-camera-filters-integrating"></a>

Anda dapat mengintegrasikan filter kamera pihak ketiga SDKs dengan SDK siaran IVS dengan memasukkan output SDK filter ke sumber input [gambar khusus](broadcast-custom-image-sources.md). Sumber input gambar khusus memungkinkan aplikasi memberikan input gambarnya sendiri ke Broadcast SDK. SDK penyedia filter pihak ketiga dapat mengelola siklus hidup kamera untuk memproses gambar dari kamera, menerapkan efek filter, dan mengeluarkannya dalam format yang dapat diteruskan ke sumber gambar khusus.

![\[Mengintegrasikan filter kamera pihak ketiga SDKs dengan SDK siaran IVS dengan memasukkan output SDK filter ke sumber input gambar khusus.\]](http://docs.aws.amazon.com/id_id/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Integrating.png)


Konsultasikan dokumentasi penyedia filter pihak ketiga Anda untuk mengetahui metode bawaan untuk mengonversi bingkai kamera, dengan efek filter, diterapkan ke format yang dapat diteruskan ke sumber [input gambar khusus](broadcast-custom-image-sources.md). Prosesnya bervariasi, tergantung pada versi SDK siaran IVS mana yang digunakan:
+ **Web** — Penyedia filter harus dapat merender outputnya ke elemen kanvas. Metode [captureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) kemudian dapat digunakan untuk mengembalikan MediaStream konten kanvas. Kemudian MediaStream dapat dikonversi ke instance a [LocalStageStream](https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-reference/classes/LocalStageStream)dan dipublikasikan ke Stage.
+ **Android** — SDK penyedia filter dapat merender bingkai ke Android yang `Surface` disediakan oleh SDK siaran IVS atau mengonversi bingkai menjadi bitmap. Jika menggunakan bitmap, itu kemudian dapat dirender ke dasar yang `Surface` disediakan oleh sumber gambar kustom, dengan membuka kunci dan menulis ke kanvas.
+ **iOS** — SDK penyedia filter pihak ketiga harus menyediakan bingkai kamera dengan efek filter yang diterapkan sebagai `CMSampleBuffer` file. Lihat dokumentasi SDK vendor filter pihak ketiga Anda untuk informasi tentang cara mendapatkan `CMSampleBuffer` hasil akhir setelah gambar kamera diproses.

# Menggunakan BytePlus dengan IVS Broadcast SDK
<a name="broadcast-3p-camera-filters-integrating-byteplus"></a>

Dokumen ini menjelaskan cara menggunakan BytePlus Effects SDK dengan IVS broadcast SDK.

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

### Instal dan Atur BytePlus Efek SDK
<a name="integrating-byteplus-android-install-effects-sdk"></a>

Lihat [Panduan Akses BytePlus Android](https://docs.byteplus.com/en/effects/docs/android-v4101-access-guide) untuk detail tentang cara menginstal, menginisialisasi, dan menyiapkan BytePlus Effects SDK.

### Mengatur Sumber Gambar Kustom
<a name="integrating-byteplus-android-setup-image-source"></a>

Setelah menginisialisasi SDK, berikan bingkai kamera yang diproses dengan efek filter yang diterapkan ke sumber input gambar khusus. Untuk melakukan itu, buat instance `DeviceDiscovery` objek dan buat sumber gambar khusus. Perhatikan bahwa saat Anda menggunakan sumber input gambar khusus untuk kontrol kustom kamera, SDK siaran tidak lagi bertanggung jawab untuk mengelola kamera. Sebagai gantinya, aplikasi bertanggung jawab untuk menangani siklus hidup kamera dengan benar.

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

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

### Konversi Output ke Bitmap dan Umpan ke Sumber Input Gambar Kustom
<a name="integrating-byteplus-android-convert-to-bitmap"></a>

Untuk mengaktifkan bingkai kamera dengan efek filter yang diterapkan dari BytePlus Effect SDK untuk diteruskan langsung ke SDK siaran IVS, ubah output tekstur BytePlus Effects SDK menjadi bitmap. Saat gambar diproses, `onDrawFrame()` metode ini dipanggil oleh SDK. `onDrawFrame()`Metode ini adalah metode publik antarmuka [GLSurfaceView.Renderer](https://developer.android.com/reference/android/opengl/GLSurfaceView.Renderer) Android. Di aplikasi sampel Android yang disediakan oleh BytePlus, metode ini dipanggil pada setiap bingkai kamera; ini menghasilkan tekstur. Secara bersamaan, Anda dapat melengkapi `onDrawFrame()` metode dengan logika untuk mengonversi tekstur ini menjadi bitmap dan memasukkannya ke sumber input gambar khusus. Seperti yang ditunjukkan pada contoh kode berikut, gunakan `transferTextureToBitmap` metode yang disediakan oleh BytePlus SDK untuk melakukan konversi ini. Metode ini disediakan oleh [com.bytedance.labcv.core.util. ImageUtil](https://docs.byteplus.com/en/effects/docs/android-v4101-access-guide#Appendix:%20convert%20input%20texture%20to%202D%20texture%20with%20upright%20face)library dari BytePlus Effects SDK, seperti yang ditunjukkan pada contoh kode berikut.Anda kemudian dapat merender ke Android `Surface` dasar a `CustomImageSource` dengan menulis bitmap yang dihasilkan ke kanvas Surface. Banyak pemanggilan berturut-turut `onDrawFrame()` menghasilkan urutan bitmap, dan ketika digabungkan, menciptakan aliran video.

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

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


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

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

# Menggunakan DeepAR dengan IVS Broadcast SDK
<a name="broadcast-3p-camera-filters-integrating-deepar"></a>

Dokumen ini menjelaskan cara menggunakan DeepAR SDK dengan IVS broadcast SDK.

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

Lihat [Panduan Integrasi Android dari DeepAR](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/android/) untuk detail tentang cara mengintegrasikan DeepAR SDK dengan SDK siaran Android IVS.

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

Lihat [Panduan Integrasi iOS dari DeepAR](https://docs.deepar.ai/deepar-sdk/integrations/video-calling/amazon-ivs/ios/) untuk detail tentang cara mengintegrasikan DeepAR SDK dengan SDK siaran iOS IVS.

# Menggunakan Snap dengan IVS Broadcast SDK
<a name="broadcast-3p-camera-filters-integrating-snap"></a>

Dokumen ini menjelaskan cara menggunakan SDK Kit Kamera Snap dengan SDK siaran IVS.

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

Bagian ini mengasumsikan Anda sudah terbiasa dengan [penerbitan dan berlangganan video menggunakan Web Broadcast SDK](getting-started-pub-sub-web.md).

Untuk mengintegrasikan SDK Kit Kamera Snap dengan SDK siaran Web streaming real-time IVS, Anda perlu:

1. Instal Camera Kit SDK dan Webpack. (Contoh kami menggunakan Webpack sebagai bundler, tetapi Anda dapat menggunakan bundler pilihan Anda.)

1. Buat`index.html`.

1. Tambahkan elemen pengaturan.

1. Buat`index.css`.

1. Tampilkan dan atur peserta.

1. Tampilkan kamera dan mikrofon yang terhubung.

1. Buat sesi Kit Kamera.

1. Ambil lensa dan isi pemilih lensa.

1. Render output dari sesi Kit Kamera ke kanvas.

1. Buat fungsi untuk mengisi dropdown Lens.

1. Menyediakan Kit Kamera dengan sumber media untuk rendering dan mempublikasikan file. `LocalStageStream`

1. Buat`package.json`.

1. Buat file konfigurasi Webpack.

1. Siapkan server HTTPS dan uji.

Masing-masing langkah ini dijelaskan di bawah ini.

### Instal SDK Kit Kamera dan Webpack
<a name="integrating-snap-web-install-camera-kit"></a>

Dalam contoh ini kami menggunakan Webpack sebagai bundler kami; Namun, Anda dapat menggunakan bundler apa pun.

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

### Buat index.html
<a name="integrating-snap-web-create-index"></a>

Selanjutnya, buat boilerplate HTML dan impor SDK siaran Web sebagai tag skrip. Dalam kode berikut, pastikan untuk mengganti `<SDK version>` dengan versi SDK siaran yang Anda gunakan.

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

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

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

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

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

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

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

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

  <!-- Display Remote Participants -->

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

### Tambahkan Elemen Pengaturan
<a name="integrating-snap-web-add-setup-elements"></a>

Buat HTML untuk memilih kamera, mikrofon, dan lensa dan tentukan token peserta:

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

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

Tambahkan HTML tambahan di bawahnya untuk menampilkan umpan kamera dari peserta lokal dan jarak jauh:

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

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

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

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

Muat logika tambahan, termasuk metode pembantu untuk mengatur kamera dan file yang dibundel JavaScript . (Nanti di bagian ini, Anda akan membuat JavaScript file-file ini dan menggabungkannya menjadi satu file, sehingga Anda dapat mengimpor Kit Kamera sebagai modul. JavaScript File yang dibundel akan berisi logika untuk menyiapkan Kit Kamera, menerapkan Lensa, dan menerbitkan umpan kamera dengan Lensa yang diterapkan ke panggung.) Tambahkan tag penutup untuk `html` elemen `body` dan untuk menyelesaikan pembuatan`index.html`.

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

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

### Buat index.css
<a name="integrating-snap-web-create-index-css"></a>

Buat file sumber CSS untuk menata halaman. Kami tidak akan membahas kode ini untuk fokus pada logika untuk mengelola Stage dan mengintegrasikan dengan Snap Camera Kit SDK.

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

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

html,
body {
  margin: 2rem;
  box-sizing: border-box;
  height: 100vh;
  max-height: 100vh;
  display: flex;
  flex-direction: column;
}

hr {
  margin: 1rem 0;
}

table {
  display: table;
}

canvas {
  margin-bottom: 1rem;
  background: green;
}

video {
  margin-bottom: 1rem;
  background: black;
  max-width: 100%;
  max-height: 150px;
}

.log {
  flex: none;
  height: 300px;
}

.content {
  flex: 1 0 auto;
}

.button {
  display: block;
  margin: 0 auto;
}

.local-container {
  position: relative;
}

.static-controls {
  position: absolute;
  margin-left: auto;
  margin-right: auto;
  left: 0;
  right: 0;
  bottom: -4rem;
  text-align: center;
}

.static-controls button {
  display: inline-block;
}

.hidden {
  display: none;
}

.participant-container {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  margin: 1rem;
}

video {
  border: 0.5rem solid #555;
  border-radius: 0.5rem;
}
.placeholder {
  background-color: #333333;
  display: flex;
  text-align: center;
  margin-bottom: 1rem;
}
.placeholder span {
  margin: auto;
  color: white;
}
#local-media {
  display: inline-block;
  width: 100vw;
}

#local-media video {
  max-height: 300px;
}

#remote-media {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: row;
  width: 100%;
}

#lens-selector {
  width: 100%;
  margin-bottom: 1rem;
}
```

### Menampilkan dan Mengatur Peserta
<a name="integrating-snap-web-setup-participants"></a>

Selanjutnya, buat`helpers.js`, yang berisi metode pembantu yang akan Anda gunakan untuk menampilkan dan mengatur peserta:

#### JavaScript
<a name="integrating-snap-web-setup-participants-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

function setupParticipant({ isLocal, id }) {
  const groupId = isLocal ? 'local-media' : 'remote-media';
  const groupContainer = document.getElementById(groupId);

  const participantContainerId = isLocal ? 'local' : id;
  const participantContainer = createContainer(participantContainerId);
  const videoEl = createVideoEl(participantContainerId);

  participantContainer.appendChild(videoEl);
  groupContainer.appendChild(participantContainer);

  return videoEl;
}

function teardownParticipant({ isLocal, id }) {
  const groupId = isLocal ? 'local-media' : 'remote-media';
  const groupContainer = document.getElementById(groupId);
  const participantContainerId = isLocal ? 'local' : id;

  const participantDiv = document.getElementById(
    participantContainerId + '-container'
  );
  if (!participantDiv) {
    return;
  }
  groupContainer.removeChild(participantDiv);
}

function createVideoEl(id) {
  const videoEl = document.createElement('video');
  videoEl.id = id;
  videoEl.autoplay = true;
  videoEl.playsInline = true;
  videoEl.srcObject = new MediaStream();
  return videoEl;
}

function createContainer(id) {
  const participantContainer = document.createElement('div');
  participantContainer.classList = 'participant-container';
  participantContainer.id = id + '-container';

  return participantContainer;
}
```

### Tampilkan Kamera dan Mikrofon yang Terhubung
<a name="integrating-snap-web-display-cameras-microphones"></a>

Selanjutnya, buat`media-devices.js`, yang berisi metode pembantu untuk menampilkan kamera dan mikrofon yang terhubung ke perangkat Anda:

#### JavaScript
<a name="integrating-snap-web-display-cameras-microphones-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

/**
 * Returns an initial list of devices populated on the page selects
 */
async function initializeDeviceSelect() {
  const videoSelectEl = document.getElementById('video-devices');
  videoSelectEl.disabled = false;

  const { videoDevices, audioDevices } = await getDevices();
  videoDevices.forEach((device, index) => {
    videoSelectEl.options[index] = new Option(device.label, device.deviceId);
  });

  const audioSelectEl = document.getElementById('audio-devices');

  audioSelectEl.disabled = false;
  audioDevices.forEach((device, index) => {
    audioSelectEl.options[index] = new Option(device.label, device.deviceId);
  });
}

/**
 * Returns all devices available on the current device
 */
async function getDevices() {
  // Prevents issues on Safari/FF so devices are not blank
  await navigator.mediaDevices.getUserMedia({ video: true, audio: true });

  const devices = await navigator.mediaDevices.enumerateDevices();
  // Get all video devices
  const videoDevices = devices.filter((d) => d.kind === 'videoinput');
  if (!videoDevices.length) {
    console.error('No video devices found.');
  }

  // Get all audio devices
  const audioDevices = devices.filter((d) => d.kind === 'audioinput');
  if (!audioDevices.length) {
    console.error('No audio devices found.');
  }

  return { videoDevices, audioDevices };
}

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

async function getMic(deviceId) {
  return navigator.mediaDevices.getUserMedia({
    video: false,
    audio: {
      deviceId: deviceId ? { exact: deviceId } : null,
    },
  });
}
```

### Buat Sesi Kit Kamera
<a name="integrating-snap-web-camera-kit-session"></a>

Buat`stages.js`, yang berisi logika untuk menerapkan Lens ke umpan kamera dan mempublikasikan umpan ke panggung. Kami merekomendasikan untuk menyalin dan menempelkan blok kode berikut ke dalam. `stages.js` Anda kemudian dapat meninjau kode sepotong demi sepotong untuk memahami apa yang terjadi di bagian berikut.

#### JavaScript
<a name="integrating-snap-web-camera-kit-session-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

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

import {
  bootstrapCameraKit,
  createMediaStreamSource,
  Transform2D,
} from '@snap/camera-kit';

let cameraButton = document.getElementById('camera-control');
let micButton = document.getElementById('mic-control');
let joinButton = document.getElementById('join-button');
let leaveButton = document.getElementById('leave-button');

let controls = document.getElementById('local-controls');
let videoDevicesList = document.getElementById('video-devices');
let audioDevicesList = document.getElementById('audio-devices');

let lensSelector = document.getElementById('lens-selector');
let session;
let availableLenses = [];

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

const liveRenderTarget = document.getElementById('canvas');

const init = async () => {
  await initializeDeviceSelect();

  const cameraKit = await bootstrapCameraKit({
    apiToken: 'INSERT_YOUR_API_TOKEN_HERE',
  });

  session = await cameraKit.createSession({ liveRenderTarget });
  const { lenses } = await cameraKit.lensRepository.loadLensGroups([
    'INSERT_YOUR_LENS_GROUP_ID_HERE',
  ]);

  availableLenses = lenses;
  populateLensSelector(lenses);

  const snapStream = liveRenderTarget.captureStream();

  lensSelector.addEventListener('change', handleLensChange);
  lensSelector.disabled = true;
  cameraButton.addEventListener('click', () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? 'Show Camera' : 'Hide Camera';
  });

  micButton.addEventListener('click', () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? 'Unmute Mic' : 'Mute Mic';
  });

  joinButton.addEventListener('click', () => {
    joinStage(session, snapStream);
  });

  leaveButton.addEventListener('click', () => {
    leaveStage();
  });
};

async function setCameraKitSource(session, mediaStream) {
  const source = createMediaStreamSource(mediaStream);
  await session.setSource(source);
  source.setTransform(Transform2D.MirrorX);
  session.play();
}

const populateLensSelector = (lenses) => {
  lensSelector.innerHTML = '<option selected disabled>Choose Lens</option>';

  lenses.forEach((lens, index) => {
    const option = document.createElement('option');
    option.value = index;
    option.text = lens.name || `Lens ${index + 1}`;
    lensSelector.appendChild(option);
  });
};

const handleLensChange = (event) => {
  const selectedIndex = parseInt(event.target.value);
  if (session && availableLenses[selectedIndex]) {
    session.applyLens(availableLenses[selectedIndex]);
  }
};

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

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

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

  // Retrieve the User Media currently set on the page
  localCamera = await getCamera(videoDevicesList.value);
  localMic = await getMic(audioDevicesList.value);
  await setCameraKitSource(session, localCamera);

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

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

  stage = new Stage(token, strategy);

  // Other available events:
  // https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events
  stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
    connected = state === ConnectionState.CONNECTED;

    if (connected) {
      joining = false;
      controls.classList.remove('hidden');
      lensSelector.disabled = false;
    } else {
      controls.classList.add('hidden');
      lensSelector.disabled = true;
    }
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
    console.log('Participant Joined:', participant);
  });

  stage.on(
    StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED,
    (participant, streams) => {
      console.log('Participant Media Added: ', participant, streams);

      let streamsToDisplay = streams;

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

      const videoEl = setupParticipant(participant);
      streamsToDisplay.forEach((stream) =>
        videoEl.srcObject.addTrack(stream.mediaStreamTrack)
      );
    }
  );

  stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
    console.log('Participant Left: ', participant);
    teardownParticipant(participant);
  });

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

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

  joining = false;
  connected = false;

  cameraButton.innerText = 'Hide Camera';
  micButton.innerText = 'Mute Mic';
  controls.classList.add('hidden');
};

init();
```

Di bagian pertama file ini, kami mengimpor SDK siaran dan SDK Web Kit Kamera dan menginisialisasi variabel yang akan kami gunakan dengan setiap SDK. Kami membuat sesi Kit Kamera dengan menelepon `createSession` setelah [bootstrap SDK Web Kit Kamera](https://kit.snapchat.com/reference/CameraKit/web/0.7.0/index.html#bootstrapping-the-sdk). Perhatikan bahwa objek elemen kanvas diteruskan ke sesi; ini memberitahu Kit Kamera untuk merender ke kanvas itu.

#### JavaScript
<a name="integrating-snap-web-camera-kit-session-code-2"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

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

import {
  bootstrapCameraKit,
  createMediaStreamSource,
  Transform2D,
} from '@snap/camera-kit';

let cameraButton = document.getElementById('camera-control');
let micButton = document.getElementById('mic-control');
let joinButton = document.getElementById('join-button');
let leaveButton = document.getElementById('leave-button');

let controls = document.getElementById('local-controls');
let videoDevicesList = document.getElementById('video-devices');
let audioDevicesList = document.getElementById('audio-devices');

let lensSelector = document.getElementById('lens-selector');
let session;
let availableLenses = [];

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

const liveRenderTarget = document.getElementById('canvas');

const init = async () => {
  await initializeDeviceSelect();

  const cameraKit = await bootstrapCameraKit({
    apiToken: 'INSERT_YOUR_API_TOKEN_HERE',
  });

  session = await cameraKit.createSession({ liveRenderTarget });
```

### Ambil Lensa dan Isi Pemilih Lensa
<a name="integrating-snap-web-fetch-apply-lens"></a>

Untuk mengambil Lensa Anda, ganti placeholder untuk ID Grup Lensa dengan milik Anda sendiri, yang dapat ditemukan di Portal Pengembang [Kit Kamera](https://camera-kit.snapchat.com/). Isi dropdown pemilihan Lensa menggunakan `populateLensSelector()` fungsi yang akan kita buat nanti.

#### JavaScript
<a name="integrating-snap-web-fetch-apply-lens-code"></a>

```
session = await cameraKit.createSession({ liveRenderTarget });
  const { lenses } = await cameraKit.lensRepository.loadLensGroups([
    'INSERT_YOUR_LENS_GROUP_ID_HERE',
  ]);

  availableLenses = lenses;
  populateLensSelector(lenses);
```

### Render Output dari Sesi Kit Kamera ke Kanvas
<a name="integrating-snap-web-render-output-to-canvas"></a>

Gunakan metode [captureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) untuk mengembalikan `MediaStream` konten kanvas. Kanvas akan berisi aliran video dari umpan kamera dengan Lensa diterapkan. Selain itu, tambahkan pendengar acara untuk tombol untuk membisukan kamera dan mikrofon serta pendengar acara untuk bergabung dan meninggalkan panggung. Dalam acara pendengar untuk bergabung dengan panggung, kami meneruskan sesi Kit Kamera dan `MediaStream` dari kanvas sehingga dapat dipublikasikan ke panggung.

#### JavaScript
<a name="integrating-snap-web-render-output-to-canvas-code"></a>

```
const snapStream = liveRenderTarget.captureStream();

  lensSelector.addEventListener('change', handleLensChange);
  lensSelector.disabled = true;
  cameraButton.addEventListener('click', () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? 'Show Camera' : 'Hide Camera';
  });

  micButton.addEventListener('click', () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? 'Unmute Mic' : 'Mute Mic';
  });

  joinButton.addEventListener('click', () => {
    joinStage(session, snapStream);
  });

  leaveButton.addEventListener('click', () => {
    leaveStage();
  });
};
```

### Buat Fungsi untuk Mengisi Dropdown Lensa
<a name="integrating-snap-web-populate-lens-dropdown"></a>

Buat fungsi berikut untuk mengisi pemilih **Lensa dengan lensa** yang diambil sebelumnya. Pemilih **Lensa** adalah elemen UI pada halaman yang memungkinkan Anda memilih dari daftar lensa untuk diterapkan ke umpan kamera. Juga, buat fungsi `handleLensChange` callback untuk menerapkan lensa yang ditentukan saat dipilih dari dropdown **Lens**.

#### JavaScript
<a name="integrating-snap-web-populate-lens-dropdown-code"></a>

```
const populateLensSelector = (lenses) => {
  lensSelector.innerHTML = '<option selected disabled>Choose Lens</option>';

  lenses.forEach((lens, index) => {
    const option = document.createElement('option');
    option.value = index;
    option.text = lens.name || `Lens ${index + 1}`;
    lensSelector.appendChild(option);
  });
};

const handleLensChange = (event) => {
  const selectedIndex = parseInt(event.target.value);
  if (session && availableLenses[selectedIndex]) {
    session.applyLens(availableLenses[selectedIndex]);
  }
};
```

### Menyediakan Kit Kamera dengan Sumber Media untuk Rendering dan Publikasikan LocalStageStream
<a name="integrating-snap-web-publish-localstagestream"></a>

Untuk mempublikasikan aliran video dengan Lens diterapkan, buat fungsi yang dipanggil `setCameraKitSource` untuk meneruskan yang `MediaStream` diambil dari kanvas sebelumnya. `MediaStream`Dari kanvas tidak melakukan apa-apa saat ini karena kami belum memasukkan umpan kamera lokal kami. Kami dapat menggabungkan umpan kamera lokal kami dengan memanggil metode `getCamera` pembantu dan menetapkannya. `localCamera` Kami kemudian dapat meneruskan umpan kamera lokal kami (via`localCamera`) dan objek sesi ke`setCameraKitSource`. `setCameraKitSource`Fungsi ini mengubah umpan kamera lokal kami ke [sumber media CameraKit](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#creating-a-camerakitsource) dengan menelepon`createMediaStreamSource`. Sumber media untuk kemudian `CameraKit` [diubah](https://docs.snap.com/camera-kit/integrate-sdk/web/web-configuration#2d-transforms) untuk mencerminkan kamera yang menghadap ke depan. Efek Lens kemudian diterapkan ke sumber media dan dirender ke kanvas keluaran dengan memanggil`session.play()`.

Dengan Lens sekarang diterapkan pada yang `MediaStream` ditangkap dari kanvas, kita kemudian dapat melanjutkan untuk menerbitkannya ke panggung. Kami melakukannya dengan membuat `LocalStageStream` dengan trek video dari`MediaStream`. Sebuah contoh kemudian `LocalStageStream` dapat diteruskan ke a `StageStrategy` untuk dipublikasikan.

#### JavaScript
<a name="integrating-snap-web-publish-localstagestream-code"></a>

```
async function setCameraKitSource(session, mediaStream) {
  const source = createMediaStreamSource(mediaStream);
  await session.setSource(source);
  source.setTransform(Transform2D.MirrorX);
  session.play();
}

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

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

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

  // Retrieve the User Media currently set on the page
  localCamera = await getCamera(videoDevicesList.value);
  localMic = await getMic(audioDevicesList.value);
  await setCameraKitSource(session, localCamera);
  // Create StageStreams for Audio and Video
  // cameraStageStream = new LocalStageStream(localCamera.getVideoTracks()[0]);
  cameraStageStream = new LocalStageStream(snapStream.getVideoTracks()[0]);
  micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

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

Kode yang tersisa di bawah ini adalah untuk membuat dan mengelola tahap kami:

#### JavaScript
<a name="integrating-snap-web-create-manage-stage-code"></a>

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

  // Other available events:
  // https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events

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

    if (connected) {
      joining = false;
      controls.classList.remove('hidden');
    } else {
      controls.classList.add('hidden');
    }
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
    console.log('Participant Joined:', participant);
  });

  stage.on(
    StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED,
    (participant, streams) => {
      console.log('Participant Media Added: ', participant, streams);

      let streamsToDisplay = streams;

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

      const videoEl = setupParticipant(participant);
      streamsToDisplay.forEach((stream) =>
        videoEl.srcObject.addTrack(stream.mediaStreamTrack)
      );
    }
  );

  stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
    console.log('Participant Left: ', participant);
    teardownParticipant(participant);
  });

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

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

  joining = false;
  connected = false;

  cameraButton.innerText = 'Hide Camera';
  micButton.innerText = 'Mute Mic';
  controls.classList.add('hidden');
};

init();
```

### Buat package.json
<a name="integrating-snap-web-package-json"></a>

Buat `package.json` dan tambahkan konfigurasi JSON berikut. File ini mendefinisikan dependensi kita dan menyertakan perintah script untuk bundling kode kita.

#### Konfigurasi JSON
<a name="integrating-snap-web-package-json-code"></a>

```
{
  "dependencies": {
    "@snap/camera-kit": "^0.10.0"
  },
  "name": "ivs-stages-with-snap-camerakit",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "webpack": "^5.95.0",
    "webpack-cli": "^5.1.4"
  }
}
```

### Membuat File Konfigurasi Webpack
<a name="integrating-snap-web-webpack-config"></a>

Buat `webpack.config.js` dan tambahkan kode berikut. Ini menggabungkan kode yang kami buat sejauh ini sehingga kami dapat menggunakan pernyataan impor untuk menggunakan Kit Kamera.

#### JavaScript
<a name="integrating-snap-web-webpack-config-code"></a>

```
const path = require('path');
module.exports = {
  entry: ['./stage.js'],
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};
```

Terakhir, jalankan `npm run build` untuk menggabungkan Anda JavaScript seperti yang didefinisikan dalam file konfigurasi Webpack. Untuk tujuan pengujian, Anda kemudian dapat melayani HTML dan JavaScript dari komputer lokal Anda. Dalam contoh ini, kita menggunakan `http.server` modul Python. 

### Siapkan Server HTTPS dan Uji
<a name="integrating-snap-web-https-server-test"></a>

Untuk menguji kode kita, kita perlu menyiapkan server HTTPS. Menggunakan server HTTPS untuk pengembangan lokal dan pengujian integrasi aplikasi web Anda dengan Snap Camera Kit SDK akan membantu menghindari masalah CORS (Cross-Origin Resource Sharing).

Buka terminal dan arahkan ke direktori tempat Anda membuat semua kode hingga saat ini. Jalankan perintah berikut untuk menghasilkan sertifikat SSL/TLS yang ditandatangani sendiri dan kunci pribadi:

```
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
```

Ini menciptakan dua file: `key.pem` (kunci pribadi) dan `cert.pem` (sertifikat yang ditandatangani sendiri). Buat file Python baru bernama `https_server.py` dan tambahkan kode berikut:

#### Python
<a name="integrating-snap-web-https-server-test-code"></a>

```
import http.server
import ssl

# Set the directory to serve files from
DIRECTORY = '.'

# Create the HTTPS server
server_address = ('', 4443)
httpd = http.server.HTTPServer(
    server_address, http.server.SimpleHTTPRequestHandler)

# Wrap the socket with SSL/TLS
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain('cert.pem', 'key.pem')
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)

print(f'Starting HTTPS server on https://localhost:4443, serving {DIRECTORY}')
httpd.serve_forever()
```

Buka terminal, arahkan ke direktori tempat Anda membuat `https_server.py` file, dan jalankan perintah berikut:

```
python3 https_server.py
```

Ini memulai server HTTPS di https://localhost:4443, menyajikan file dari direktori saat ini. Pastikan `key.pem` file `cert.pem` dan file berada di direktori yang sama dengan `https_server.py` file.

Buka browser Anda dan arahkan ke https://localhost:4443. Karena ini adalah SSL/TLS sertifikat yang ditandatangani sendiri, itu tidak akan dipercaya oleh browser web Anda, jadi Anda akan mendapatkan peringatan. Karena ini hanya untuk tujuan pengujian, Anda dapat melewati peringatan. Anda kemudian akan melihat efek AR untuk Lensa Snap yang Anda tentukan sebelumnya diterapkan ke umpan kamera Anda di layar.

Perhatikan bahwa pengaturan ini menggunakan built-in `http.server` dan `ssl` modul Python cocok untuk tujuan pengembangan dan pengujian lokal, tetapi tidak disarankan untuk lingkungan produksi. SSL/TLS Sertifikat yang ditandatangani sendiri yang digunakan dalam pengaturan ini tidak dipercaya oleh browser web dan klien lain, yang berarti pengguna akan menghadapi peringatan keamanan saat mengakses server. Selain itu, meskipun kami menggunakan modul http.server dan ssl bawaan Python dalam contoh ini, Anda dapat memilih untuk menggunakan solusi server HTTPS lain.

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

Untuk mengintegrasikan SDK Kit Kamera Snap dengan SDK siaran Android IVS, Anda harus menginstal SDK Kit Kamera, menginisialisasi sesi Kit Kamera, menerapkan Lensa, dan memasukkan output sesi Kit Kamera ke sumber input gambar khusus.

Untuk menginstal SDK Kit Kamera, tambahkan yang berikut ini ke `build.gradle` file modul Anda. Ganti `$cameraKitVersion` dengan [versi SDK Kit Kamera terbaru](https://docs.snap.com/camera-kit/integrate-sdk/mobile/changelog-mobile).

### Java
<a name="integrating-snap-android-install-camerakit-sdk-code"></a>

```
implementation "com.snap.camerakit:camerakit:$cameraKitVersion"
```

Inisialisasi dan dapatkan a. `cameraKitSession` Camera Kit juga menyediakan pembungkus yang nyaman untuk [ APIsCameraX](https://developer.android.com/media/camera/camerax) Android, jadi Anda tidak perlu menulis logika rumit untuk menggunakan CameraX dengan Camera Kit. Anda dapat menggunakan `CameraXImageProcessorSource` objek sebagai [Sumber](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-source/index.html) untuk [ImageProcessor](https://snapchat.github.io/camera-kit-reference/api/android/latest/-camera-kit/com.snap.camerakit/-image-processor/index.html), yang memungkinkan Anda untuk memulai frame streaming pratinjau kamera.

### Java
<a name="integrating-snap-android-initialize-camerakitsession-code"></a>

```
 protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        // Camera Kit support implementation of ImageProcessor that is backed by CameraX library:
        // https://developer.android.com/training/camerax
        CameraXImageProcessorSource imageProcessorSource = new CameraXImageProcessorSource( 
            this /*context*/, this /*lifecycleOwner*/
        );
        imageProcessorSource.startPreview(true /*cameraFacingFront*/);

        cameraKitSession = Sessions.newBuilder(this)
                .imageProcessorSource(imageProcessorSource)
                .attachTo(findViewById(R.id.camerakit_stub))
                .build();
    }
```

### Ambil dan Terapkan Lensa
<a name="integrating-snap-android-fetch-apply-lenses"></a>

Anda dapat mengonfigurasi Lensa dan urutannya di korsel di Portal [Pengembang Kit Kamera](https://camera-kit.snapchat.com/):

#### Java
<a name="integrating-snap-android-configure-lenses-code"></a>

```
// Fetch lenses from repository and apply them
 // Replace LENS_GROUP_ID with Lens Group ID from https://camera-kit.snapchat.com
cameraKitSession.getLenses().getRepository().get(new Available(LENS_GROUP_ID), available -> {
     Log.d(TAG, "Available lenses: " + available);
     Lenses.whenHasFirst(available, lens -> cameraKitSession.getLenses().getProcessor().apply(lens, result -> {
          Log.d(TAG,  "Apply lens [" + lens + "] success: " + result);
      }));
});
```

Untuk menyiarkan, kirim bingkai yang diproses ke `Surface` dasar sumber gambar kustom. Gunakan `DeviceDiscovery` objek dan buat `CustomImageSource` untuk mengembalikan a`SurfaceSource`. Anda kemudian dapat merender output dari `CameraKit` sesi ke dasar yang `Surface` disediakan oleh`SurfaceSource`.

#### Java
<a name="integrating-snap-android-broadcast-code"></a>

```
val publishStreams = ArrayList<LocalStageStream>()

val deviceDiscovery = DeviceDiscovery(applicationContext)
val customSource = deviceDiscovery.createImageInputSource(BroadcastConfiguration.Vec2(720f, 1280f))

cameraKitSession.processor.connectOutput(outputFrom(customSource.inputSurface))
val customStream = ImageLocalStageStream(customSource)

// After rendering the output from a Camera Kit session to the Surface, you can 
// then return it as a LocalStageStream to be published by the Broadcast SDK
val customStream: ImageLocalStageStream = ImageLocalStageStream(surfaceSource)
publishStreams.add(customStream)

@Override
fun stageStreamsToPublishForParticipant(stage: Stage, participantInfo: ParticipantInfo): List<LocalStageStream> = publishStreams
```

# Menggunakan Penggantian Latar Belakang dengan SDK Siaran IVS
<a name="broadcast-3p-camera-filters-background-replacement"></a>

Penggantian latar belakang adalah jenis filter kamera yang memungkinkan pembuat live-stream mengubah latar belakang mereka. Seperti yang ditunjukkan pada diagram berikut, mengganti latar belakang Anda melibatkan:

1. Mendapatkan gambar kamera dari umpan kamera langsung.

1. Mensegmentasinya menjadi komponen latar depan dan latar belakang menggunakan Google ML Kit.

1. Menggabungkan topeng segmentasi yang dihasilkan dengan gambar latar belakang khusus.

1. Meneruskannya ke Sumber Gambar Kustom untuk disiarkan.

![\[Alur kerja untuk menerapkan penggantian latar belakang.\]](http://docs.aws.amazon.com/id_id/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Background_Replacement.png)


## Web
<a name="background-replacement-web"></a>

Bagian ini mengasumsikan Anda sudah terbiasa dengan [penerbitan dan berlangganan video menggunakan Web Broadcast SDK](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-pub-sub-web.html).

Untuk mengganti latar belakang streaming langsung dengan gambar khusus, gunakan [model segmentasi selfie](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model) dengan [MediaPipe Image Segmenter](https://developers.google.com/mediapipe/solutions/vision/image_segmenter). Ini adalah model pembelajaran mesin yang mengidentifikasi piksel mana dalam bingkai video yang berada di latar depan atau latar belakang. Anda kemudian dapat menggunakan hasil dari model untuk mengganti latar belakang streaming langsung, dengan menyalin piksel latar depan dari umpan video ke gambar khusus yang mewakili latar belakang baru.

Untuk mengintegrasikan penggantian latar belakang dengan SDK siaran Web streaming real-time IVS, Anda perlu:

1. Instal MediaPipe dan Webpack. (Contoh kami menggunakan Webpack sebagai bundler, tetapi Anda dapat menggunakan bundler pilihan Anda.)

1. Buat`index.html`.

1. Tambahkan elemen media.

1. Tambahkan tag skrip.

1. Buat`app.js`.

1. Muat gambar latar belakang kustom.

1. Buat instans `ImageSegmenter`.

1. Render umpan video ke kanvas.

1. Buat logika penggantian latar belakang.

1. Buat File konfigurasi Webpack.

1. Bundel JavaScript file Anda.

### Instal MediaPipe dan Webpack
<a name="background-replacement-web-install-mediapipe-webpack"></a>

Untuk memulai, instal paket `@mediapipe/tasks-vision` dan `webpack` npm. Contoh di bawah ini menggunakan Webpack sebagai JavaScript bundler; Anda dapat menggunakan bundler yang berbeda jika diinginkan.

#### JavaScript
<a name="background-replacement-web-install-mediapipe-webpack-code"></a>

```
npm i @mediapipe/tasks-vision webpack webpack-cli
```

Pastikan juga memperbarui `package.json` untuk menentukan `webpack` skrip build Anda:

#### JavaScript
<a name="background-replacement-web-update-package-json-code"></a>

```
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
```

### Buat index.html
<a name="background-replacement-web-create-index"></a>

Selanjutnya, buat boilerplate HTML dan impor SDK siaran Web sebagai tag skrip. Dalam kode berikut, pastikan untuk mengganti `<SDK version>` dengan versi SDK siaran yang Anda gunakan.

#### JavaScript
<a name="background-replacement-web-create-index-code"></a>

```
<!DOCTYPE html>
<html lang="en">

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

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

<body>

</body>
</html>
```

### Tambahkan Elemen Media
<a name="background-replacement-web-add-media-elements"></a>

Selanjutnya, tambahkan elemen video dan dua elemen kanvas dalam tag tubuh. Elemen video akan berisi umpan kamera langsung Anda dan akan digunakan sebagai input ke MediaPipe Segmenter Gambar. Elemen kanvas pertama akan digunakan untuk membuat pratinjau umpan yang akan disiarkan. Elemen kanvas kedua akan digunakan untuk membuat gambar kustom yang akan digunakan sebagai latar belakang. Karena kanvas kedua dengan gambar khusus hanya digunakan sebagai sumber untuk menyalin piksel secara terprogram dari itu ke kanvas akhir, itu disembunyikan dari pandangan.

#### JavaScript
<a name="background-replacement-web-add-media-elements-code"></a>

```
<div class="row local-container">
      <video id="webcam" autoplay style="display: none"></video>
    </div>
    <div class="row local-container">
      <canvas id="canvas" width="640px" height="480px"></canvas>

      <div class="column" id="local-media"></div>
      <div class="static-controls hidden" id="local-controls">
        <button class="button" id="mic-control">Mute Mic</button>
        <button class="button" id="camera-control">Mute Camera</button>
      </div>
    </div>
    <div class="row local-container">
      <canvas id="background" width="640px" height="480px" style="display: none"></canvas>
    </div>
```

### Tambahkan Tag Skrip
<a name="background-replacement-web-add-script-tag"></a>

Tambahkan tag skrip untuk memuat JavaScript file yang dibundel yang akan berisi kode untuk melakukan penggantian latar belakang dan mempublikasikannya ke tahap:

```
<script src="./dist/bundle.js"></script>
```

### Buat app.js
<a name="background-replacement-web-create-appjs"></a>

Selanjutnya, buat JavaScript file untuk mendapatkan objek elemen untuk kanvas dan elemen video yang dibuat di halaman HTML. Impor `FilesetResolver` modul `ImageSegmenter` dan. `ImageSegmenter`Modul ini akan digunakan untuk melakukan tugas segmentasi.

#### JavaScript
<a name="create-appjs-import-imagesegmenter-fileresolver-code"></a>

```
const canvasElement = document.getElementById("canvas");
const background = document.getElementById("background");
const canvasCtx = canvasElement.getContext("2d");
const backgroundCtx = background.getContext("2d");
const video = document.getElementById("webcam");

import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision";
```

Selanjutnya, buat fungsi yang dipanggil `init()` untuk mengambil MediaStream dari kamera pengguna dan memanggil fungsi callback setiap kali bingkai kamera selesai dimuat. Tambahkan pendengar acara untuk tombol untuk bergabung dan meninggalkan panggung.

Perhatikan bahwa ketika bergabung dengan tahap, kita meneruskan variabel bernama`segmentationStream`. Ini adalah aliran video yang diambil dari elemen kanvas, berisi gambar latar depan yang dilapisi pada gambar khusus yang mewakili latar belakang. Nantinya, aliran kustom ini akan digunakan untuk membuat instance dari a`LocalStageStream`, yang dapat dipublikasikan ke panggung.

#### JavaScript
<a name="create-appjs-create-init-code"></a>

```
const init = async () => {
  await initializeDeviceSelect();

  cameraButton.addEventListener("click", () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? "Show Camera" : "Hide Camera";
  });

  micButton.addEventListener("click", () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? "Unmute Mic" : "Mute Mic";
  });

  localCamera = await getCamera(videoDevicesList.value);
  const segmentationStream = canvasElement.captureStream();

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

  leaveButton.addEventListener("click", () => {
    leaveStage();
  });
};
```

### Muat Gambar Latar Belakang Kustom
<a name="background-replacement-web-background-image"></a>

Di bagian bawah `init` fungsi, tambahkan kode untuk memanggil fungsi bernama`initBackgroundCanvas`, yang memuat gambar kustom dari file lokal dan merendernya ke kanvas. Kami akan mendefinisikan fungsi ini pada langkah berikutnya. Tetapkan `MediaStream` diambil dari kamera pengguna ke objek video. Nantinya, objek video ini akan diteruskan ke Image Segmenter. Juga, atur fungsi bernama `renderVideoToCanvas` sebagai fungsi panggilan balik untuk dipanggil setiap kali bingkai video selesai dimuat. Kami akan mendefinisikan fungsi ini di langkah selanjutnya.

#### JavaScript
<a name="background-replacement-web-load-background-image-code"></a>

```
initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
```

Mari kita implementasikan `initBackgroundCanvas` fungsi, yang memuat gambar dari file lokal. Dalam contoh ini, kami menggunakan gambar pantai sebagai latar belakang khusus. Kanvas yang berisi gambar khusus akan disembunyikan dari tampilan, karena Anda akan menggabungkannya dengan piksel latar depan dari elemen kanvas yang berisi umpan kamera.

#### JavaScript
<a name="background-replacement-web-implement-initBackgroundCanvas-code"></a>

```
const initBackgroundCanvas = () => {
  let img = new Image();
  img.src = "beach.jpg";

  img.onload = () => {
    backgroundCtx.clearRect(0, 0, canvas.width, canvas.height);
    backgroundCtx.drawImage(img, 0, 0);
  };
};
```

### Buat sebuah Instance dari ImageSegmenter
<a name="background-replacement-web-imagesegmenter"></a>

Selanjutnya, buat instance`ImageSegmenter`, yang akan mengelompokkan gambar dan mengembalikan hasilnya sebagai topeng. Saat membuat instance`ImageSegmenter`, Anda akan menggunakan [model segmentasi selfie](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model).

#### JavaScript
<a name="background-replacement-web-imagesegmenter-code"></a>

```
const createImageSegmenter = async () => {
  const audio = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm");

  imageSegmenter = await ImageSegmenter.createFromOptions(audio, {
    baseOptions: {
      modelAssetPath: "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
      delegate: "GPU",
    },
    runningMode: "VIDEO",
    outputCategoryMask: true,
  });
};
```

### Render Umpan Video ke Kanvas
<a name="background-replacement-web-render-video-to-canvas"></a>

Selanjutnya, buat fungsi yang membuat umpan video ke elemen kanvas lainnya. Kita perlu merender umpan video ke kanvas sehingga kita dapat mengekstrak piksel latar depan darinya menggunakan Canvas 2D API. Saat melakukan ini, kami juga akan meneruskan bingkai video ke instance kami`ImageSegmenter`, menggunakan metode [SegmentForVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) untuk mengelompokkan latar depan dari latar belakang dalam bingkai video. Ketika metode [SegmentForVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) kembali, ia memanggil fungsi callback kustom kami,`replaceBackground`, untuk melakukan penggantian latar belakang.

#### JavaScript
<a name="background-replacement-web-render-video-to-canvas-code"></a>

```
const renderVideoToCanvas = async () => {
  if (video.currentTime === lastWebcamTime) {
    window.requestAnimationFrame(renderVideoToCanvas);
    return;
  }
  lastWebcamTime = video.currentTime;
  canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);

  if (imageSegmenter === undefined) {
    return;
  }

  let startTimeMs = performance.now();

  imageSegmenter.segmentForVideo(video, startTimeMs, replaceBackground);
};
```

### Buat Logika Penggantian Latar Belakang
<a name="background-replacement-web-logic"></a>

Buat `replaceBackground` fungsi, yang menggabungkan gambar latar belakang kustom dengan latar depan dari umpan kamera untuk menggantikan latar belakang. Fungsi pertama mengambil data piksel yang mendasari gambar latar belakang kustom dan umpan video dari dua elemen kanvas yang dibuat sebelumnya. Kemudian iterasi melalui topeng yang disediakan oleh`ImageSegmenter`, yang menunjukkan piksel mana yang ada di latar depan. Saat beralih melalui topeng, ia secara selektif menyalin piksel yang berisi umpan kamera pengguna ke data piksel latar belakang yang sesuai. Setelah selesai, itu mengubah data piksel akhir dengan latar depan disalin ke latar belakang dan menariknya ke Canvas.

#### JavaScript
<a name="background-replacement-web-logic-create-replacebackground-code"></a>

```
function replaceBackground(result) {
  let imageData = canvasCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  let backgroundData = backgroundCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  const mask = result.categoryMask.getAsFloat32Array();
  let j = 0;

  for (let i = 0; i < mask.length; ++i) {
    const maskVal = Math.round(mask[i] * 255.0);

    j += 4;
  // Only copy pixels on to the background image if the mask indicates they are in the foreground
    if (maskVal < 255) {
      backgroundData[j] = imageData[j];
      backgroundData[j + 1] = imageData[j + 1];
      backgroundData[j + 2] = imageData[j + 2];
      backgroundData[j + 3] = imageData[j + 3];
    }
  }

 // Convert the pixel data to a format suitable to be drawn to a canvas
  const uint8Array = new Uint8ClampedArray(backgroundData.buffer);
  const dataNew = new ImageData(uint8Array, video.videoWidth, video.videoHeight);
  canvasCtx.putImageData(dataNew, 0, 0);
  window.requestAnimationFrame(renderVideoToCanvas);
}
```

Untuk referensi, berikut adalah `app.js` file lengkap yang berisi semua logika di atas:

#### JavaScript
<a name="background-replacement-web-logic-app-js-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

// All helpers are expose on 'media-devices.js' and 'dom.js'
const { setupParticipant } = window;

const { Stage, LocalStageStream, SubscribeType, StageEvents, ConnectionState, StreamType } = IVSBroadcastClient;
const canvasElement = document.getElementById("canvas");
const background = document.getElementById("background");
const canvasCtx = canvasElement.getContext("2d");
const backgroundCtx = background.getContext("2d");
const video = document.getElementById("webcam");

import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision";

let cameraButton = document.getElementById("camera-control");
let micButton = document.getElementById("mic-control");
let joinButton = document.getElementById("join-button");
let leaveButton = document.getElementById("leave-button");

let controls = document.getElementById("local-controls");
let audioDevicesList = document.getElementById("audio-devices");
let videoDevicesList = document.getElementById("video-devices");

// Stage management
let stage;
let joining = false;
let connected = false;
let localCamera;
let localMic;
let cameraStageStream;
let micStageStream;
let imageSegmenter;
let lastWebcamTime = -1;

const init = async () => {
  await initializeDeviceSelect();

  cameraButton.addEventListener("click", () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? "Show Camera" : "Hide Camera";
  });

  micButton.addEventListener("click", () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? "Unmute Mic" : "Mute Mic";
  });

  localCamera = await getCamera(videoDevicesList.value);
  const segmentationStream = canvasElement.captureStream();

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

  leaveButton.addEventListener("click", () => {
    leaveStage();
  });

  initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
};

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

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

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

  // Retrieve the User Media currently set on the page
  localMic = await getMic(audioDevicesList.value);

  cameraStageStream = new LocalStageStream(segmentationStream.getVideoTracks()[0]);
  micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

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

  stage = new Stage(token, strategy);

  // Other available events:
  // https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events
  stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
    connected = state === ConnectionState.CONNECTED;

    if (connected) {
      joining = false;
      controls.classList.remove("hidden");
    } else {
      controls.classList.add("hidden");
    }
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
    console.log("Participant Joined:", participant);
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => {
    console.log("Participant Media Added: ", participant, streams);

    let streamsToDisplay = streams;

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

    const videoEl = setupParticipant(participant);
    streamsToDisplay.forEach((stream) => videoEl.srcObject.addTrack(stream.mediaStreamTrack));
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
    console.log("Participant Left: ", participant);
    teardownParticipant(participant);
  });

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

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

  joining = false;
  connected = false;

  cameraButton.innerText = "Hide Camera";
  micButton.innerText = "Mute Mic";
  controls.classList.add("hidden");
};

function replaceBackground(result) {
  let imageData = canvasCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  let backgroundData = backgroundCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  const mask = result.categoryMask.getAsFloat32Array();
  let j = 0;

  for (let i = 0; i < mask.length; ++i) {
    const maskVal = Math.round(mask[i] * 255.0);

    j += 4;
    if (maskVal < 255) {
      backgroundData[j] = imageData[j];
      backgroundData[j + 1] = imageData[j + 1];
      backgroundData[j + 2] = imageData[j + 2];
      backgroundData[j + 3] = imageData[j + 3];
    }
  }
  const uint8Array = new Uint8ClampedArray(backgroundData.buffer);
  const dataNew = new ImageData(uint8Array, video.videoWidth, video.videoHeight);
  canvasCtx.putImageData(dataNew, 0, 0);
  window.requestAnimationFrame(renderVideoToCanvas);
}

const createImageSegmenter = async () => {
  const audio = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm");

  imageSegmenter = await ImageSegmenter.createFromOptions(audio, {
    baseOptions: {
      modelAssetPath: "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
      delegate: "GPU",
    },
    runningMode: "VIDEO",
    outputCategoryMask: true,
  });
};

const renderVideoToCanvas = async () => {
  if (video.currentTime === lastWebcamTime) {
    window.requestAnimationFrame(renderVideoToCanvas);
    return;
  }
  lastWebcamTime = video.currentTime;
  canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);

  if (imageSegmenter === undefined) {
    return;
  }

  let startTimeMs = performance.now();

  imageSegmenter.segmentForVideo(video, startTimeMs, replaceBackground);
};

const initBackgroundCanvas = () => {
  let img = new Image();
  img.src = "beach.jpg";

  img.onload = () => {
    backgroundCtx.clearRect(0, 0, canvas.width, canvas.height);
    backgroundCtx.drawImage(img, 0, 0);
  };
};

createImageSegmenter();
init();
```

### Membuat File Konfigurasi Webpack
<a name="background-replacement-web-webpack-config"></a>

Tambahkan konfigurasi ini ke file konfigurasi Webpack Anda ke bundel`app.js`, sehingga panggilan impor akan berfungsi:

#### JavaScript
<a name="background-replacement-web-webpack-config-code"></a>

```
const path = require("path");
module.exports = {
  entry: ["./app.js"],
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
};
```

### Bundel JavaScript file Anda
<a name="background-replacement-web-bundle-javascript"></a>

```
npm run build
```

Mulai server HTTP sederhana dari direktori yang berisi `index.html` dan buka `localhost:8000` untuk melihat hasilnya:

```
python3 -m http.server -d ./
```

## Android
<a name="background-replacement-android"></a>

Untuk mengganti latar belakang di live stream Anda, Anda dapat menggunakan API segmentasi selfie dari [Google ML Kit](https://developers.google.com/ml-kit/vision/selfie-segmentation). API segmentasi selfie menerima gambar kamera sebagai input dan mengembalikan topeng yang memberikan skor kepercayaan untuk setiap piksel gambar, yang menunjukkan apakah itu di latar depan atau latar belakang. Berdasarkan skor kepercayaan, Anda kemudian dapat mengambil warna piksel yang sesuai dari gambar latar belakang atau gambar latar depan. Proses ini berlanjut sampai semua skor kepercayaan pada topeng telah diperiksa. Hasilnya adalah array baru warna piksel yang berisi piksel latar depan dikombinasikan dengan piksel dari gambar latar belakang.

Untuk mengintegrasikan penggantian latar belakang dengan SDK siaran Android streaming real-time IVS, Anda perlu:

1. Instal pustaka CameraX dan kit Google ML.

1. Inisialisasi variabel boilerplate.

1. Buat sumber gambar khusus.

1. Kelola bingkai kamera.

1. Lewatkan bingkai kamera ke Google ML Kit.

1. Hamparkan latar depan bingkai kamera ke latar belakang kustom Anda.

1. Umpan gambar baru ke sumber gambar khusus.

### Instal CameraX Libraries dan Google ML Kit
<a name="background-replacement-android-install-camerax-googleml"></a>

Untuk mengekstrak gambar dari umpan kamera langsung, gunakan pustaka CameraX Android. Untuk menginstal library CameraX dan Google ML Kit, tambahkan yang berikut ini ke file modul Anda. `build.gradle` Ganti `${camerax_version}` dan `${google_ml_kit_version}` dengan versi terbaru dari pustaka [CameraX](https://developer.android.com/jetpack/androidx/releases/camera) dan [Google ML Kit](https://developers.google.com/ml-kit/vision/selfie-segmentation/android), masing-masing. 

#### Java
<a name="background-replacement-android-install-camerax-googleml-code"></a>

```
implementation "com.google.mlkit:segmentation-selfie:${google_ml_kit_version}"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
```

Impor pustaka berikut:

#### Java
<a name="background-replacement-android-import-libraries-code"></a>

```
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.lifecycle.ProcessCameraProvider
import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions
```

### Inisialisasi Variabel Boilerplate
<a name="background-replacement-android-initialize-variables"></a>

Menginisialisasi instance dari `ImageAnalysis` dan instance `ExecutorService` dari:

#### Java
<a name="background-replacement-android-initialize-imageanalysis-executorservice-code"></a>

```
private lateinit var binding: ActivityMainBinding
private lateinit var cameraExecutor: ExecutorService
private var analysisUseCase: ImageAnalysis? = null
```

[Inisialisasi instance Segmenter di STREAM\$1MODE:](https://developers.google.com/ml-kit/vision/selfie-segmentation/android#detector_mode)

#### Java
<a name="background-replacement-android-initialize-segmenter-code"></a>

```
private val options =
        SelfieSegmenterOptions.Builder()
            .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE)
            .build()

private val segmenter = Segmentation.getClient(options)
```

### Buat Sumber Gambar Kustom
<a name="background-replacement-android-create-image-source"></a>

Dalam `onCreate` metode aktivitas Anda, buat instance `DeviceDiscovery` objek dan buat sumber gambar kustom. Yang `Surface` disediakan oleh Custom Image Source akan menerima gambar akhir, dengan latar depan dilapisi pada gambar latar belakang kustom. Anda kemudian akan membuat instance dari `ImageLocalStageStream` menggunakan Custom Image Source. Instance dari `ImageLocalStageStream` (dinamai `filterStream` dalam contoh ini) kemudian dapat dipublikasikan ke sebuah panggung. Lihat Panduan [SDK Broadcast Android IVS untuk petunjuk](broadcast-android.md) tentang menyiapkan panggung. Terakhir, buat juga utas yang akan digunakan untuk mengelola kamera.

#### Java
<a name="background-replacement-android-create-image-source-code"></a>

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

cameraExecutor = Executors.newSingleThreadExecutor()
```

### Kelola Bingkai Kamera
<a name="background-replacement-android-camera-frames"></a>

Selanjutnya, buat fungsi untuk menginisialisasi kamera. Fungsi ini menggunakan perpustakaan CameraX untuk mengekstrak gambar dari umpan kamera langsung. Pertama, Anda membuat instance dari yang `ProcessCameraProvider` dipanggil`cameraProviderFuture`. Objek ini merupakan hasil future dari mendapatkan penyedia kamera. Kemudian Anda memuat gambar dari proyek Anda sebagai bitmap. Contoh ini menggunakan gambar pantai sebagai latar belakang, tetapi bisa berupa gambar apa pun yang Anda inginkan.

Anda kemudian menambahkan pendengar ke`cameraProviderFuture`. Pendengar ini diberitahu ketika kamera tersedia atau jika terjadi kesalahan selama proses mendapatkan penyedia kamera.

#### Java
<a name="background-replacement-android-initialize-camera-code"></a>

```
private fun startCamera(surface: Surface) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        val imageResource = R.drawable.beach
        val bgBitmap: Bitmap = BitmapFactory.decodeResource(resources, imageResource)
        var resultBitmap: Bitmap;


        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            
                if (mediaImage != null) {
                    val inputImage =
                        InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                            resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
                            canvas = surface.lockCanvas(null);
                            canvas.drawBitmap(resultBitmap, 0f, 0f, null)

                            surface.unlockCanvasAndPost(canvas);

                        }
                        .addOnFailureListener { exception ->
                            Log.d("App", exception.message!!)
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }

                }
            };

            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }
```

Di dalam pendengar, buat `ImageAnalysis.Builder` untuk mengakses setiap bingkai individu dari umpan kamera langsung. Atur strategi tekanan balik ke. `STRATEGY_KEEP_ONLY_LATEST` Ini menjamin bahwa hanya satu bingkai kamera pada satu waktu yang dikirimkan untuk diproses. Konversikan setiap bingkai kamera individual ke bitmap, sehingga Anda dapat mengekstrak pikselnya untuk kemudian menggabungkannya dengan gambar latar belakang khusus.

#### Java
<a name="background-replacement-android-create-imageanalysisbuilder-code"></a>

```
val imageAnalyzer = ImageAnalysis.Builder()
analysisUseCase = imageAnalyzer
    .setTargetResolution(Size(360, 640))
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()

analysisUseCase?.setAnalyzer(cameraExecutor) { imageProxy: ImageProxy ->
    val mediaImage = imageProxy.image
    val tempBitmap = imageProxy.toBitmap();
    val inputBitmap = tempBitmap.rotate(imageProxy.imageInfo.rotationDegrees.toFloat())
```

### Lewati Bingkai Kamera ke Google ML Kit
<a name="background-replacement-android-frames-to-mlkit"></a>

Selanjutnya, buat `InputImage` dan teruskan ke instance Segmenter untuk diproses. An `InputImage` dapat dibuat dari yang `ImageProxy` disediakan oleh instance`ImageAnalysis`. Setelah `InputImage` diberikan ke Segmenter, ia mengembalikan topeng dengan skor kepercayaan yang menunjukkan kemungkinan piksel berada di latar depan atau latar belakang. Topeng ini juga menyediakan properti lebar dan tinggi, yang akan Anda gunakan untuk membuat array baru yang berisi piksel latar belakang dari gambar latar belakang khusus yang dimuat sebelumnya.

#### Java
<a name="background-replacement-android-frames-to-mlkit-code"></a>

```
if (mediaImage != null) {
        val inputImage =
            InputImage.fromMediaImag


segmenter.process(inputImage)
    .addOnSuccessListener { segmentationMask ->
        val mask = segmentationMask.buffer
        val maskWidth = segmentationMask.width
        val maskHeight = segmentationMask.height
        val backgroundPixels = IntArray(maskWidth * maskHeight)
        bgBitmap.getPixels(backgroundPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)
```

### Hamparkan Latar Depan Bingkai Kamera ke Latar Belakang Kustom Anda
<a name="background-replacement-android-overlay-frame-foreground"></a>

Dengan topeng yang berisi skor kepercayaan diri, bingkai kamera sebagai bitmap, dan piksel warna dari gambar latar belakang kustom, Anda memiliki semua yang Anda butuhkan untuk melapisi latar depan ke latar belakang kustom Anda. `overlayForeground`Fungsi ini kemudian dipanggil dengan parameter berikut:

#### Java
<a name="background-replacement-android-call-overlayforeground-code"></a>

```
resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
```

Fungsi ini berulang melalui topeng dan memeriksa nilai kepercayaan untuk menentukan apakah akan mendapatkan warna piksel yang sesuai dari gambar latar belakang atau bingkai kamera. Jika nilai kepercayaan menunjukkan bahwa piksel di topeng kemungkinan besar ada di latar belakang, itu akan mendapatkan warna piksel yang sesuai dari gambar latar belakang; jika tidak, itu akan mendapatkan warna piksel yang sesuai dari bingkai kamera untuk membangun latar depan. Setelah fungsi selesai iterasi melalui mask, bitmap baru dibuat menggunakan array piksel warna baru dan dikembalikan. Bitmap baru ini berisi latar depan yang dilapisi pada latar belakang kustom.

#### Java
<a name="background-replacement-android-run-overlayforeground-code"></a>

```
private fun overlayForeground(
        byteBuffer: ByteBuffer,
        maskWidth: Int,
        maskHeight: Int,
        cameraBitmap: Bitmap,
        backgroundPixels: IntArray
    ): Bitmap {
        @ColorInt val colors = IntArray(maskWidth * maskHeight)
        val cameraPixels = IntArray(maskWidth * maskHeight)

        cameraBitmap.getPixels(cameraPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)

        for (i in 0 until maskWidth * maskHeight) {
            val backgroundLikelihood: Float = 1 - byteBuffer.getFloat()

            // Apply the virtual background to the color if it's not part of the foreground
            if (backgroundLikelihood > 0.9) {
                // Get the corresponding pixel color from the background image
                // Set the color in the mask based on the background image pixel color
                colors[i] = backgroundPixels.get(i)
            } else {
                // Get the corresponding pixel color from the camera frame
                // Set the color in the mask based on the camera image pixel color
                colors[i] = cameraPixels.get(i)
            }
        }

        return Bitmap.createBitmap(
            colors, maskWidth, maskHeight, Bitmap.Config.ARGB_8888
        )
    }
```

### Umpan Gambar Baru ke Sumber Gambar Kustom
<a name="background-replacement-android-custom-image-source"></a>

Anda kemudian dapat menulis bitmap baru ke yang `Surface` disediakan oleh sumber gambar khusus. Ini akan menyiarkannya ke panggung Anda.

#### Java
<a name="background-replacement-android-custom-image-source-code"></a>

```
resultBitmap = overlayForeground(mask, inputBitmap, mutableBitmap, bgBitmap)
canvas = surface.lockCanvas(null);
canvas.drawBitmap(resultBitmap, 0f, 0f, null)
```

Berikut adalah fungsi lengkap untuk mendapatkan bingkai kamera, meneruskannya ke Segmenter, dan melapisinya di latar belakang:

#### Java
<a name="background-replacement-android-custom-image-source-startcamera-code"></a>

```
@androidx.annotation.OptIn(androidx.camera.core.ExperimentalGetImage::class)
    private fun startCamera(surface: Surface) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        val imageResource = R.drawable.clouds
        val bgBitmap: Bitmap = BitmapFactory.decodeResource(resources, imageResource)
        var resultBitmap: Bitmap;

        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            val imageAnalyzer = ImageAnalysis.Builder()
            analysisUseCase = imageAnalyzer
                .setTargetResolution(Size(720, 1280))
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()

            analysisUseCase!!.setAnalyzer(cameraExecutor) { imageProxy: ImageProxy ->
                val mediaImage = imageProxy.image
                val tempBitmap = imageProxy.toBitmap();
                val inputBitmap = tempBitmap.rotate(imageProxy.imageInfo.rotationDegrees.toFloat())

                if (mediaImage != null) {
                    val inputImage =
                        InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                    segmenter.process(inputImage)
                        .addOnSuccessListener { segmentationMask ->
                            val mask = segmentationMask.buffer
                            val maskWidth = segmentationMask.width
                            val maskHeight = segmentationMask.height
                            val backgroundPixels = IntArray(maskWidth * maskHeight)
                            bgBitmap.getPixels(backgroundPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)

                            resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
                            canvas = surface.lockCanvas(null);
                            canvas.drawBitmap(resultBitmap, 0f, 0f, null)

                            surface.unlockCanvasAndPost(canvas);

                        }
                        .addOnFailureListener { exception ->
                            Log.d("App", exception.message!!)
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }

                }
            };

            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }
```

# SDK Siaran IVS: Mode Audio Seluler \$1 Streaming Waktu Nyata
<a name="broadcast-mobile-audio-modes"></a>

Kualitas audio adalah bagian penting dari pengalaman media tim nyata apa pun, dan tidak ada konfigurasi one-size-fits-all audio yang paling sesuai untuk setiap kasus penggunaan. Untuk memastikan bahwa pengguna Anda memiliki pengalaman terbaik saat mendengarkan streaming real-time IVS, ponsel kami SDKs menyediakan beberapa konfigurasi audio preset, serta penyesuaian yang lebih kuat sesuai kebutuhan.

## Pengantar
<a name="broadcast-mobile-audio-modes-introduction"></a>

Siaran seluler IVS SDKs menyediakan `StageAudioManager` kelas. Kelas ini dirancang untuk menjadi titik kontak tunggal untuk mengontrol mode audio yang mendasarinya pada kedua platform. Di Android, ini mengontrol [AudioManager](https://developer.android.com/reference/android/media/AudioManager), termasuk mode audio, sumber audio, jenis konten, penggunaan, dan perangkat komunikasi. Di iOS, ia mengontrol [AVAudioSesi](https://developer.apple.com/documentation/avfaudio/avaudiosession) aplikasi, serta apakah [VoiceProcessing diaktifkan](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc).

**Penting**: Jangan berinteraksi dengan `AVAudioSession` atau `AudioManager` langsung saat SDK siaran real-time IVS aktif. Melakukan hal itu dapat mengakibatkan hilangnya audio, atau audio yang direkam dari atau diputar kembali pada perangkat yang salah.

Sebelum Anda membuat `Stage` objek `DeviceDiscovery` atau pertama Anda, `StageAudioManager` kelas harus dikonfigurasi.

------
#### [ Android (Kotlin) ]

```
StageAudioManager.getInstance(context).setPreset(StageAudioManager.UseCasePreset.VIDEO_CHAT) // The default value

val deviceDiscovery = DeviceDiscovery(context)
val stage = Stage(context, token, this)

// Other Stage implementation code
```

------
#### [ iOS (Swift) ]

```
IVSStageAudioManager.sharedInstance().setPreset(.videoChat) // The default value

let deviceDiscovery = IVSDeviceDiscovery()
let stage = try? IVSStage(token: token, strategy: self)

// Other Stage implementation code
```

------

Jika tidak ada yang diatur pada inisialisasi `StageAudioManager` sebelum `DeviceDiscovery` atau `Stage` instance, `VideoChat` preset diterapkan secara otomatis.

## Preset Kasus Penggunaan Audio
<a name="broadcast-mobile-audio-modes-presets"></a>

SDK siaran real-time menyediakan tiga preset, masing-masing disesuaikan dengan kasus penggunaan umum, seperti yang dijelaskan di bawah ini. Untuk setiap preset, kami mencakup lima kategori utama yang membedakan preset satu sama lain.

Kategori **Volume Rocker** mengacu pada jenis volume (volume media atau volume panggilan) yang digunakan atau diubah melalui rocker volume fisik pada perangkat. Perhatikan bahwa ini memengaruhi volume saat beralih mode audio. Misalnya, volume perangkat diatur ke nilai maksimum saat menggunakan preset Obrolan Video. Beralih ke preset Subscribe Only menyebabkan tingkat volume yang berbeda dari sistem operasi, yang dapat menyebabkan perubahan volume yang signifikan pada perangkat.

### Obrolan Video
<a name="audio-modes-presets-video-chat"></a>

Ini adalah preset default, yang dirancang untuk saat perangkat lokal akan melakukan percakapan real-time dengan peserta lain.

**Masalah yang diketahui di iOS**: Menggunakan preset ini dan tidak memasang mikrofon menyebabkan audio diputar melalui lubang suara alih-alih speaker perangkat. Gunakan preset ini hanya dalam kombinasi dengan mikrofon.


| Kategori | Android | iOS | 
| --- | --- | --- | 
| Pembatalan Echo | Diaktifkan | Diaktifkan | 
| Penindasan Kebisingan | Diaktifkan | Diaktifkan | 
| Volume Rocker | Volume Panggilan | Volume Panggilan | 
| Pemilihan Mikrofon | Terbatas berdasarkan OS. Mikrofon USB mungkin tidak tersedia. | Terbatas berdasarkan OS. Mikrofon USB dan Bluetooth mungkin tidak tersedia. Headset Bluetooth yang menangani input dan output bersama-sama harus berfungsi; misalnya, AirPods. | 
| Keluaran Audio | Perangkat output apa pun harus berfungsi. | Terbatas berdasarkan OS. Headset kabel mungkin tidak tersedia. | 
| Kualitas Audio | Sedang/Rendah. Ini akan terdengar seperti panggilan telepon, tidak seperti pemutaran media. | Sedang/Rendah. Ini akan terdengar seperti panggilan telepon, tidak seperti pemutaran media. | 

### Hanya Berlangganan
<a name="audio-modes-presets-subscribe-only"></a>

Preset ini dirancang ketika Anda berencana untuk berlangganan peserta penerbitan lain tetapi tidak mempublikasikan sendiri. Ini berfokus pada kualitas audio dan mendukung semua perangkat output yang tersedia.


| Kategori | Android | iOS | 
| --- | --- | --- | 
| Pembatalan Echo | Nonaktif | Nonaktif | 
| Penindasan Kebisingan | Nonaktif | Nonaktif | 
| Volume Rocker | Volume Media | Volume Media | 
| Pemilihan Mikrofon | N/A, preset ini tidak dirancang untuk penerbitan. | N/A, preset ini tidak dirancang untuk penerbitan. | 
| Keluaran Audio | Perangkat output apa pun harus berfungsi. | Perangkat output apa pun harus berfungsi. | 
| Kualitas Audio | Tinggi. Setiap jenis media harus datang dengan jelas, termasuk musik. | Tinggi. Setiap jenis media harus datang dengan jelas, termasuk musik. | 

### Studio
<a name="audio-modes-presets-studio"></a>

Preset ini dirancang untuk berlangganan berkualitas tinggi sambil mempertahankan kemampuan untuk mempublikasikan. Ini membutuhkan perangkat keras perekaman dan pemutaran untuk memberikan pembatalan gema. Kasus penggunaan di sini adalah menggunakan mikrofon USB dan headset kabel. SDK akan mempertahankan audio berkualitas tinggi sambil mengandalkan pemisahan fisik perangkat tersebut agar tidak menyebabkan gema.


| Kategori | Android | iOS | 
| --- | --- | --- | 
| Pembatalan Echo | Nonaktif | Nonaktif | 
| Penindasan Kebisingan | Nonaktif | Nonaktif | 
| Volume Rocker | Volume Media dalam banyak kasus. Volume Panggilan saat mikrofon Bluetooth terhubung.  | Volume Media | 
| Pemilihan Mikrofon | Mikrofon apa pun harus berfungsi. | Mikrofon apa pun harus berfungsi. | 
| Keluaran Audio | Perangkat output apa pun harus berfungsi. | Perangkat output apa pun harus berfungsi. | 
| Kualitas Audio | Tinggi. Kedua belah pihak harus dapat mengirim musik dan mendengarnya dengan jelas di sisi lain. Saat headset Bluetooth terhubung, kualitas audio akan turun karena mode Bluetooth SCO diaktifkan. | Tinggi. Kedua belah pihak harus dapat mengirim musik dan mendengarnya dengan jelas di sisi lain. Saat headset Bluetooth terhubung, kualitas audio mungkin turun karena mode Bluetooth SCO diaktifkan, tergantung pada headset.  | 

## Kasus Penggunaan Tingkat Lanjut
<a name="broadcast-mobile-audio-modes-advanced-use-cases"></a>

Di luar preset, baik siaran streaming real-time iOS dan Android SDKs memungkinkan konfigurasi mode audio platform yang mendasarinya:
+ Di Android, atur [AudioSource](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource), [Penggunaan](https://developer.android.com/reference/android/media/AudioAttributes#USAGE_ALARM), dan [ContentType](https://developer.android.com/reference/android/media/AudioAttributes#CONTENT_TYPE_MOVIE).
+ [Di iOS, gunakan [AVAudioSession.Category, Session.](https://developer.apple.com/documentation/avfaudio/avaudiosession/category) AVAudio CategoryOptions](https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions), [AVAudioSession.Mode](https://developer.apple.com/documentation/avfaudio/avaudiosession/mode), dan kemampuan untuk beralih jika [pemrosesan suara](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) diaktifkan atau tidak saat penerbitan.

Catatan: Saat menggunakan metode SDK audio ini, dimungkinkan untuk salah mengonfigurasi sesi audio yang mendasarinya. Misalnya, menggunakan `.allowBluetooth` opsi di iOS dalam kombinasi dengan `.playback` kategori membuat konfigurasi audio yang tidak valid dan SDK tidak dapat merekam atau memutar audio. Metode ini dirancang untuk digunakan hanya ketika aplikasi memiliki persyaratan sesi audio tertentu yang telah divalidasi.

------
#### [ Android (Kotlin) ]

```
// This would act similar to the Subscribe Only preset, but it uses a different ContentType.
StageAudioManager.getInstance(context)
    .setConfiguration(StageAudioManager.Source.GENERIC,
                      StageAudioManager.ContentType.MOVIE,
                      StageAudioManager.Usage.MEDIA);

val stage = Stage(context, token, this)

// Other Stage implementation code
```

------
#### [ iOS (Swift) ]

```
// This would act similar to the Subscribe Only preset, but it uses a different mode and options.
IVSStageAudioManager.sharedInstance()
    .setCategory(.playback,
                 options: [.duckOthers, .mixWithOthers],
                 mode: .default)

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

// Other Stage implementation code
```

------

### Pembatalan Echo iOS
<a name="advanced-use-cases-ios_echo_cancellation"></a>

Pembatalan gema di iOS dapat dikontrol secara independen melalui `IVSStageAudioManager` juga menggunakan metodenya`echoCancellationEnabled`. Metode ini mengontrol apakah [pemrosesan suara](https://developer.apple.com/documentation/avfaudio/avaudioionode/3152101-voiceprocessingenabled?language=objc) diaktifkan pada node input dan output yang mendasari yang `AVAudioEngine` digunakan oleh SDK. Penting untuk memahami efek mengubah properti ini secara manual:
+ `AVAudioEngine`Properti hanya dihormati jika mikrofon SDK aktif; ini diperlukan karena persyaratan iOS bahwa pemrosesan suara diaktifkan pada node input dan output secara bersamaan. Biasanya ini dilakukan dengan menggunakan mikrofon yang dikembalikan `IVSDeviceDiscovery` untuk membuat `IVSLocalStageStream` untuk menerbitkan. Sebagai alternatif, mikrofon dapat diaktifkan, tanpa digunakan untuk mempublikasikan, dengan melampirkan `IVSAudioDeviceStatsCallback` ke mikrofon itu sendiri. Pendekatan alternatif ini berguna jika pembatalan gema diperlukan saat menggunakan mikrofon khusus alih-alih audio-source-based mikrofon IVS SDK.
+ Mengaktifkan `AVAudioEngine` properti membutuhkan mode `.videoChat` atau`.voiceChat`. Meminta mode yang berbeda menyebabkan kerangka audio dasar iOS melawan SDK, menyebabkan kehilangan audio.
+ Mengaktifkan `AVAudioEngine` secara otomatis mengaktifkan `.allowBluetooth ` opsi.

Perilaku dapat berbeda tergantung pada perangkat dan versi iOS.

### Sumber Audio Kustom iOS
<a name="advanced-use-cases-ios_custom_audio_sources"></a>

Sumber audio khusus dapat digunakan dengan SDK dengan menggunakan`IVSDeviceDiscovery.createAudioSource`. Saat menghubungkan ke Stage, SDK siaran streaming real-time IVS masih mengelola `AVAudioEngine` instans internal untuk pemutaran audio, bahkan jika mikrofon SDK tidak digunakan. Akibatnya, nilai yang diberikan `IVSStageAudioManager` harus kompatibel dengan audio yang disediakan oleh sumber audio khusus.

Jika sumber audio kustom yang digunakan untuk mempublikasikan merekam dari mikrofon tetapi dikelola oleh aplikasi host, SDK pembatalan gema di atas tidak akan berfungsi kecuali mikrofon yang dikelola SDK diaktifkan. Untuk mengatasi persyaratan tersebut, lihat [Pembatalan Gema iOS](#advanced-use-cases-ios_echo_cancellation).

### Menerbitkan dengan Bluetooth di Android
<a name="advanced-use-cases-bluetooth-android"></a>

SDK secara otomatis kembali ke `VIDEO_CHAT` preset di Android jika kondisi berikut terpenuhi:
+ Konfigurasi yang ditetapkan tidak menggunakan nilai `VOICE_COMMUNICATION` penggunaan.
+ Mikrofon Bluetooth terhubung ke perangkat.
+ Peserta lokal menerbitkan ke Panggung.

Ini adalah batasan sistem operasi Android dalam hal bagaimana headset Bluetooth digunakan untuk merekam audio.

## Integrasi dengan Lainnya SDKs
<a name="broadcast-mobile-audio-modes-integrating-other-sdks"></a>

Karena iOS dan Android hanya mendukung satu mode audio aktif per aplikasi, biasanya terjadi konflik jika aplikasi Anda menggunakan beberapa SDKs yang memerlukan kontrol mode audio. Ketika Anda mengalami konflik ini, ada beberapa strategi resolusi umum untuk dicoba, dijelaskan di bawah ini.

### Cocokkan Nilai Mode Audio
<a name="integrating-other-sdks-match-values"></a>

Dengan menggunakan opsi konfigurasi audio lanjutan IVS SDK atau fungsionalitas SDK lainnya, keduanya SDKs sejajar dengan nilai yang mendasarinya.

### Agora
<a name="integrating-other-sdks-agora"></a>

#### iOS
<a name="integrating-other-sdks-agora-ios"></a>

Di iOS, memberi tahu Agora SDK untuk tetap `AVAudioSession` aktif akan mencegahnya menonaktifkan saat SDK siaran streaming real-time IVS menggunakannya.

```
myRtcEngine.SetParameters("{\"che.audio.keep.audiosession\":true}");
```

#### Android
<a name="integrating-other-sdks-agora-android"></a>

Hindari `setEnableSpeakerphone` menelepon`RtcEngine`, dan menelepon `enableLocalAudio(false)` saat menerbitkan dengan SDK siaran streaming real-time IVS. Anda dapat menelepon `enableLocalAudio(true)` lagi saat IVS SDK tidak dipublikasikan.