# Amazon Interactive Video Service (IVS) - Developer Context

> **Last updated:** 2026-02-16
> **Purpose:** Self-contained plaintext reference for developers building with Amazon IVS. Covers all three product lines, SDK usage across platforms, streaming setup, and troubleshooting.

---

## Table of Contents

1. [Amazon IVS Overview](#1-amazon-ivs-overview)
2. [Player SDK (Low-Latency)](#2-player-sdk-low-latency)
3. [Broadcast SDK (Low-Latency)](#3-broadcast-sdk-low-latency)
4. [Real-Time Streaming SDK](#4-real-time-streaming-sdk)
5. [Chat SDK](#5-chat-sdk)
6. [Platform Features & Capabilities](#6-platform-features--capabilities)
7. [Streaming with OBS](#7-streaming-with-obs)
8. [Streaming with FFmpeg](#8-streaming-with-ffmpeg)
9. [Common Issues and Resolutions](#9-common-issues-and-resolutions)
10. [Error Code Reference](#10-error-code-reference)
11. [Additional Resources](#11-additional-resources)

---

## 1. Amazon IVS Overview

Amazon Interactive Video Service (IVS) is a managed live streaming service built on the same technology that powers Twitch. It lets you create live, interactive video experiences without needing to manage infrastructure.

### Three Product Lines

| Feature | Low-Latency Streaming | Real-Time Streaming | Chat |
|---|---|---|---|
| **What it does** | Channel-based live streaming | WebRTC-based multi-participant stages | WebSocket-based messaging rooms |
| **Latency** | 3-5 seconds | < 300 milliseconds | Real-time delivery |
| **Max viewers** | Millions | Thousands (adjustable via quotas) | Thousands of concurrent connections |
| **Max resolution** | 1080p (Full HD) | 720p (HD) | N/A |
| **Ingest protocols** | RTMPS, SRT, WebRTC | WebRTC (Broadcast SDK, WHIP) | WebSocket |
| **Playback protocol** | HLS | WebRTC | WebSocket |
| **Pricing model** | Video input + output per hour | Participant-hours | Messages sent + delivered |
| **Key resource** | Channel | Stage | Room |

### Key Quotas and Limits

Service quotas vary by account and region. Many limits are adjustable via AWS Support. Check the AWS Service Quotas console for your account's current limits.

**Low-Latency Streaming:**

| Resource | Notes |
|---|---|
| Channels per account | Default quota applies; adjustable |
| Concurrent streams | Default quota applies; adjustable |
| Concurrent viewers | Default quota applies; adjustable |
| Ingest bitrate | Varies by channel type (Basic vs Standard/Advanced) |
| Ingest resolution | 1080p max |
| Stream session data retention | Retention period applies |

**Real-Time Streaming:**

| Resource | Notes |
|---|---|
| Stages per region | Default quota applies; adjustable |
| Publishers per stage | Fixed limit applies |
| Subscribers per stage | Default quota applies; adjustable to higher values |
| Participant resolution | 720p max |
| Participant download bitrate | Maximum bitrate enforced |
| Participant duration | Maximum session duration applies |
| Compositions (server-side) | Default quota applies; adjustable |

**Chat:**

| Resource | Notes |
|---|---|
| Rooms | Default quota applies; adjustable |
| Concurrent connections | Default quota applies; adjustable |
| Messages per connection | Rate limit enforced |
| SendMessage rate (all rooms) | Default rate limit; adjustable |
| Max message length | Character limit enforced |
| Message review handler timeout | Fixed timeout applies |
| Chat token rate | Rate limit enforced |

### Low-Latency Channel Types

| Channel Type | Max Ingest Bitrate | Transcoding | Renditions | Pricing |
|---|---|---|---|---|
| Basic | Lower bitrate | Transmux only | Source only | Lowest cost tier |
| Standard | Higher bitrate | Full transcode | Multiple (Auto) | Standard pricing |
| Advanced SD | Higher bitrate | Full transcode | SD-optimized | SD pricing tier |
| Advanced HD | Higher bitrate | Full transcode | HD-optimized | HD pricing tier |

Pricing varies by channel type, input/output hours, and region. Contact AWS or consult the IVS pricing page for current rates.

### SDKs by Product

| SDK | Real-Time | Low-Latency | Chat |
|---|---|---|---|
| Player SDK (Web, iOS, Android) | - | Yes | - |
| Broadcast SDK (Web, iOS, Android) | Yes | Yes | - |
| Chat Client SDK (Web, iOS, Android) | - | - | Yes |

---

## 2. Player SDK (Low-Latency)

The IVS Player SDK plays HLS streams from IVS low-latency channels. It is optimized for low latency with adaptive bitrate (ABR) streaming, timed metadata support, and built-in reconnection handling.

### Latest Versions

| Platform | Version | Release Date |
|---|---|---|
| Web | 1.48.0 | 2025-12-18 |
| iOS | 1.48.0 | 2025-12-18 |
| Android | 1.48.0 | 2025-12-18 |

### Web - Starter Code

**Script tag (CDN):**
```html
<script src="https://player.live-video.net/1.48.0/amazon-ivs-player.min.js"></script>
```

**Basic integration:**
```html
<script src="https://player.live-video.net/1.48.0/amazon-ivs-player.min.js"></script>
<video id="video-player" controls playsinline></video>

<script>
  const player = IVSPlayer.create();
  player.attachHTMLVideoElement(document.getElementById('video-player'));
  player.load("PLAYBACK_URL");
  player.play();
</script>
```

**Full example with event listeners:**
```js
(function (IVSPlayerPackage) {
    if (!IVSPlayerPackage.isPlayerSupported) {
        console.warn("The current browser does not support the IVS player.");
        return;
    }

    const PlayerState = IVSPlayerPackage.PlayerState;
    const PlayerEventType = IVSPlayerPackage.PlayerEventType;

    // Initialize player
    const player = IVSPlayerPackage.create();
    console.log("IVS Player version:", player.getVersion());
    player.attachHTMLVideoElement(document.getElementById("video-player"));

    // Attach event listeners
    player.addEventListener(PlayerState.PLAYING, function () {
        console.log("Player State - PLAYING");
        console.log("Latency: " + player.getLiveLatency());
    });
    player.addEventListener(PlayerState.ENDED, function () {
        console.log("Player State - ENDED");
    });
    player.addEventListener(PlayerState.READY, function () {
        console.log("Player State - READY");
    });
    player.addEventListener(PlayerEventType.ERROR, function (err) {
        console.warn("Player Event - ERROR:", err);
    });
    player.addEventListener(PlayerEventType.TEXT_METADATA_CUE, (cue) => {
        const metadataText = cue.text;
        const position = player.getPosition().toFixed(2);
        console.log(
            `PlayerEvent - TEXT_METADATA_CUE: "${metadataText}". ` +
            `Observed ${position}s after playback started.`
        );
    });
    player.addEventListener(PlayerState.BUFFERING, function () {
        console.log("Player State - BUFFERING");
    });
    player.addEventListener(PlayerEventType.REBUFFERING, function () {
        console.log("Player State - REBUFFERING");
    });

    // Setup stream and play
    player.setAutoplay(true);
    player.load("PLAYBACK_URL");
    player.setVolume(0.5);
})(window.IVSPlayer);
```

### iOS - Starter Code

**CocoaPods:**
```ruby
pod 'AmazonIVSPlayer', '~> 1.48.0'
```

**Swift Package Manager:** Add `https://github.com/aws/amazon-ivs-player-ios.git`

**Basic usage (Swift):**
```swift
import AmazonIVSPlayer

class PlayerViewController: UIViewController {
    var player: IVSPlayer!
    @IBOutlet weak var playerView: IVSPlayerView!

    override func viewDidLoad() {
        super.viewDidLoad()
        player = IVSPlayer()
        player.delegate = self
        playerView.player = player

        // Reduce latency on rebuffer
        player.setRebufferToLive(true)

        let url = URL(string: "PLAYBACK_URL")!
        player.load(url)
    }
}

extension PlayerViewController: IVSPlayer.Delegate {
    func player(_ player: IVSPlayer, didChangeState state: IVSPlayer.State) {
        switch state {
        case .playing:
            print("Player is playing. Latency: \(player.liveLatency)")
        case .buffering:
            print("Player is buffering")
        case .ended:
            print("Stream ended")
        default:
            break
        }
    }

    func player(_ player: IVSPlayer, didFailWithError error: Error) {
        print("Player error: \(error.localizedDescription)")
    }

    func player(_ player: IVSPlayer, didOutputCue cue: IVSCue) {
        if let textCue = cue as? IVSTextMetadataCue {
            print("Timed metadata: \(textCue.text)")
        }
    }
}
```

### Android - Starter Code

**Gradle:**
```groovy
implementation 'com.amazonaws:ivs-player:1.48.0'
```

**Basic usage (Kotlin):**
```kotlin
import com.amazonaws.ivs.player.Player
import com.amazonaws.ivs.player.PlayerException
import com.amazonaws.ivs.player.SurfacePlayerView

class PlayerActivity : AppCompatActivity() {
    private lateinit var player: Player
    private lateinit var playerView: SurfacePlayerView

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

        playerView = findViewById(R.id.player_view)
        player = Player.Factory.create(this)
        playerView.player = player

        player.setRebufferToLive(true)

        player.addListener(object : Player.Listener() {
            override fun onStateChanged(state: Player.State) {
                when (state) {
                    Player.State.PLAYING -> {
                        Log.d("IVS", "Playing. Latency: ${player.liveLatency}")
                    }
                    Player.State.BUFFERING -> Log.d("IVS", "Buffering")
                    Player.State.ENDED -> Log.d("IVS", "Stream ended")
                    else -> {}
                }
            }

            override fun onError(exception: PlayerException) {
                Log.e("IVS", "Error: ${exception.message}")
            }

            override fun onCue(cue: Cue) {
                if (cue is TextMetadataCue) {
                    Log.d("IVS", "Timed metadata: ${cue.text}")
                }
            }
        })

        player.load(Uri.parse("PLAYBACK_URL"))
        player.play()
    }

    override fun onDestroy() {
        super.onDestroy()
        player.release()
    }
}
```

### Key Features

- **Adaptive Bitrate (ABR):** Automatically adjusts quality based on viewer network. Use `setAutoMaxQuality()` to cap quality, or `setQuality()` to force a specific rendition.
- **setRebufferToLive:** When `true`, player jumps to live edge after rebuffer events. Reduces latency but may cause more rebuffering on poor connections. Call before `load()`.
  - Web: `player.setRebufferToLive(true)`
  - Android: `((MediaPlayer)player).setRebufferToLive(true)`
  - iOS: `player?.perform(Selector(("setRebufferToLive:")), with: true)`
- **Timed Metadata:** Receive in-band metadata via `TEXT_METADATA_CUE` events (web) or `didOutputCue` delegate (iOS) / `onCue` listener (Android).
- **Debug Logging:** Enable with `player.setLogLevel("DEBUG")` (web), `.logLevel = IVSLogLevelDebug` (iOS), or `.setLogLevel(Player.LogLevel.DEBUG)` (Android).
- **Session ID:** Call `player.getSessionId()` during playback to get a session ID for AWS Support troubleshooting.
- **Service Worker (iOS Safari):** Default latency on iOS Safari is 15-20s. Use IVS Player SDK with a service worker to reduce to 6-8s. See: https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/web-getting-started.html#web-service-worker
- **Audio-Only Playback:** The Player SDK supports audio-only streams. For Advanced SD/HD channels, an audio-only rendition is automatically generated as part of ABR transcoding. Use `setQuality()` to select the audio-only rendition, or let ABR handle it automatically on constrained networks.

### Framework Integrations (Web)

The IVS Web Player SDK can be used standalone or integrated with third-party player frameworks:

| Player Type | Description | Custom UI | Plugins |
|---|---|---|---|
| IVS Player SDK (standalone) | Lightweight, low-level SDK for developers who want full control | No (BYO) | No |
| Amazon IVS Player Tech for Video.js | Turnkey integration for Video.js users | Yes (Video.js Skins) | Yes (Video.js Plugins) |
| Amazon IVS Provider for JW Player | Turnkey integration for JW Player users | Yes | N/A |

**Video.js Integration:**

Amazon IVS support for Video.js is implemented as a Video.js "tech". Supports Video.js 7.6.6+ and 8.x.

```html
<!-- Script tag setup -->
<script src="https://player.live-video.net/1.48.0/amazon-ivs-videojs-tech.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/video.js/7.14.3/video-js.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/video.js/7.14.3/video.min.js"></script>

<div id="video-player"></div>
<script>
  // Register the IVS tech with Video.js
  registerIVSTech(videojs);

  const player = videojs('video-player', {
    techOrder: ['AmazonIVS'],
    autoplay: true,
  });

  player.src(PLAYBACK_URL);
</script>
```

Also available via npm: `npm install amazon-ivs-player` (includes the Video.js tech module). The Quality Plugin (`registerIVSQualityPlugin`) adds ABR quality selection UI to the Video.js control bar.

**JW Player Integration:**

Amazon IVS support for JW Player is implemented as a Provider. Supports JW Player 8.18.4+.

```html
<script src="https://player.live-video.net/1.48.0/amazon-ivs-jw-provider.min.js"></script>
<div id="player"></div>
<script>
  jwplayer(JW_PLAYER_DIV).setup({
    playlist: [{
      file: IVS_STREAM,
      type: 'ivs',
    }],
    cast: null, // Chromecast not supported
  });
</script>
```

### Content Security Policy (CSP)

When using the IVS Player SDK on pages with Content Security Policy headers, the following minimum directives are required:

```
worker-src blob:;
media-src blob:;
connect-src *.live-video.net;
script-src 'wasm-unsafe-eval';
```

Older browsers may not recognize `wasm-unsafe-eval`. As a workaround, host the IVS library assets on the same origin as your page rather than using `unsafe-eval`.

### Reducing Latency in Third-Party Players

If using a non-IVS HLS player (e.g., native Safari HLS, hls.js), latency may be significantly higher (15-20s on iOS Safari). To minimize latency:
- Use the IVS Player SDK instead - it includes proprietary low-latency HLS optimizations
- Set keyframe interval to 2 seconds in the encoder
- On iOS Safari without the IVS SDK, use a service worker to reduce latency from ~20s to ~6-8s

---

## 3. Broadcast SDK (Low-Latency)

The IVS Broadcast SDK sends video to IVS low-latency channels via RTMPS or WebRTC ingest. It handles camera/microphone capture, encoding, and publishing.

### Latest Versions

| Platform | Version | Release Date |
|---|---|---|
| Web | 1.31.0 | 2025-12-05 |
| iOS | 1.38.0 | 2026-01-13 |
| Android | 1.38.0 | 2026-01-13 |

### Web - Starter Code

**Install:**
```bash
npm install amazon-ivs-web-broadcast
```

Or via CDN:
```html
<script src="https://web-broadcast.live-video.net/1.31.0/amazon-ivs-web-broadcast.js"></script>
```

**Basic broadcast:**
```js
import IVSBroadcastClient, {
  BASIC_LANDSCAPE
} from 'amazon-ivs-web-broadcast';

// 1. Create broadcast client
const client = IVSBroadcastClient.create({
  streamConfig: IVSBroadcastClient.BASIC_LANDSCAPE,
  ingestEndpoint: 'UNIQUE_ID.global-contribute.live-video.net',
});

// 2. Request permissions and get devices
const stream = await navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true,
});
for (const track of stream.getTracks()) track.stop();

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

// 3. Get media streams
const cameraStream = await navigator.mediaDevices.getUserMedia({
  video: { deviceId: videoDevices[0].deviceId },
});
const micStream = await navigator.mediaDevices.getUserMedia({
  audio: { deviceId: audioDevices[0].deviceId },
});

// 4. Attach preview (optional)
const previewCanvas = document.getElementById('preview');
client.attachPreview(previewCanvas);

// 5. Add devices to broadcast
client.addVideoInputDevice(cameraStream, 'camera1', { index: 0 });
client.addAudioInputDevice(micStream, 'mic1');

// 6. Start broadcast
client
  .startBroadcast('YOUR_STREAM_KEY')
  .then(() => console.log('Broadcasting!'))
  .catch((err) => console.error('Broadcast failed:', err));

// 7. Stop broadcast
// client.stopBroadcast();
```

**Mute/unmute audio:**
```js
let audioStream = client.getAudioInputDevice('mic1');
audioStream.getAudioTracks()[0].enabled = false; // mute
audioStream.getAudioTracks()[0].enabled = true;  // unmute
```

**Hide/show video:**
```js
let videoStream = client.getVideoInputDevice('camera1').source;
videoStream.getVideoTracks()[0].enabled = false; // hide
videoStream.getVideoTracks()[0].enabled = true;  // show
```

### iOS - Starter Code

**CocoaPods:**
```ruby
pod 'AmazonIVSBroadcast', '~> 1.38.0'
```

**Basic broadcast (Swift):**
```swift
import AmazonIVSBroadcast

class BroadcastViewController: UIViewController {
    var broadcastSession: IVSBroadcastSession?
    @IBOutlet weak var previewView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Create broadcast session with BASIC preset
        let config = IVSPresets.configurations().basicLandscape()
        broadcastSession = try? IVSBroadcastSession(
            configuration: config,
            descriptors: IVSPresets.devices().frontCamera(),
            delegate: self
        )

        // Attach preview
        if let preview = try? broadcastSession?.previewView(with: .fit) {
            previewView.addSubview(preview)
            preview.frame = previewView.bounds
        }
    }

    func startBroadcast() {
        let url = URL(string: "rtmps://INGEST_ENDPOINT:443/app/")!
        broadcastSession?.start(with: url, streamKey: "STREAM_KEY")
    }

    func stopBroadcast() {
        broadcastSession?.stop()
    }
}

extension BroadcastViewController: IVSBroadcastSession.Delegate {
    func broadcastSession(_ session: IVSBroadcastSession,
                          didChange state: IVSBroadcastSession.State) {
        switch state {
        case .connected:
            print("Broadcast connected")
        case .disconnected:
            print("Broadcast disconnected")
        case .error:
            print("Broadcast error")
        default:
            break
        }
    }

    func broadcastSession(_ session: IVSBroadcastSession,
                          didEmitError error: Error) {
        print("Broadcast error: \(error.localizedDescription)")
    }
}
```

### Android - Starter Code

**Gradle:**
```groovy
implementation 'com.amazonaws:ivs-broadcast:1.38.0'
```

**Basic broadcast (Kotlin):**
```kotlin
import com.amazonaws.ivs.broadcast.BroadcastSession
import com.amazonaws.ivs.broadcast.BroadcastConfiguration
import com.amazonaws.ivs.broadcast.Presets

class BroadcastActivity : AppCompatActivity() {
    private var broadcastSession: BroadcastSession? = null

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

        val config = BroadcastConfiguration().apply {
            video.setSize(720, 1280)
            video.targetFramerate = 30
            video.setMaxBitrate(2500000)
        }

        val previewView = findViewById<ImageView>(R.id.preview)

        broadcastSession = BroadcastSession(
            this, broadcastListener, config,
            Presets.Devices.FRONT_CAMERA(this)
        )

        broadcastSession?.previewView?.let { preview ->
            previewView.addView(preview)
        }
    }

    fun startBroadcast() {
        broadcastSession?.start(
            "rtmps://INGEST_ENDPOINT:443/app/",
            "STREAM_KEY"
        )
    }

    fun stopBroadcast() {
        broadcastSession?.stop()
    }

    private val broadcastListener = object : BroadcastSession.Listener() {
        override fun onStateChanged(state: BroadcastSession.State) {
            when (state) {
                BroadcastSession.State.CONNECTED ->
                    Log.d("IVS", "Broadcast connected")
                BroadcastSession.State.DISCONNECTED ->
                    Log.d("IVS", "Broadcast disconnected")
                BroadcastSession.State.ERROR ->
                    Log.e("IVS", "Broadcast error")
                else -> {}
            }
        }

        override fun onError(exception: BroadcastException) {
            Log.e("IVS", "Error: ${exception.message}, code: ${exception.code}")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        broadcastSession?.release()
    }
}
```

### Stream Config Presets (Web)

| Preset | Resolution | Max Bitrate | Use With Channel Type |
|---|---|---|---|
| `BASIC_LANDSCAPE` | 480p (852x480) | 1.5 Mbps | Basic |
| `BASIC_PORTRAIT` | 480p (480x852) | 1.5 Mbps | Basic |
| `BASIC_FULL_HD_LANDSCAPE` | 1080p (1920x1080) | 3.5 Mbps | Standard / Advanced HD |
| `BASIC_FULL_HD_PORTRAIT` | 1080p (1080x1920) | 3.5 Mbps | Standard / Advanced HD |
| `STANDARD_LANDSCAPE` | 1080p (1920x1080) | 8.5 Mbps | Standard / Advanced HD |
| `STANDARD_PORTRAIT` | 1080p (1080x1920) | 8.5 Mbps | Standard / Advanced HD |

### Custom Image Sources

Custom image-input sources allow an application to provide its own image input to the Broadcast SDK, instead of being limited to preset cameras or screen share. Use cases include watermark overlays, "be right back" scenes, beauty filters, and custom camera processing.

**iOS:**
```swift
let customImageSource = broadcastSession.createImageSource(withName: "video")
try broadcastSession.attach(customImageSource, toSlotWithName: "custom")
// Feed CMSampleBuffer frames to customImageSource
```

**Android:** Use `createImageInputSource()` on the `BroadcastSession` to get an `ImageDevice.SurfaceSource`. Draw frames to the returned `Surface`.

**Web:** Use a `<canvas>` element as the video source. Capture its stream with `canvas.captureStream()` and pass it to `client.addVideoInputDevice()`.

**Note:** On iOS, you cannot use `AVCaptureSession` with camera input to feed a custom image source while also using a camera device from the SDK. For multiple cameras, use `AVCaptureMultiCamSession` and provide two custom image sources.

### Mixed Audio/Video Devices

The Broadcast SDK supports managing multiple audio and video input devices simultaneously. Key capabilities:

- **Adding/removing sources:** Dynamically add or remove camera, microphone, screen share, and custom sources during a broadcast
- **Animations with transitions:** Animate slot positions, sizes, and transparency when switching between sources or layouts
- **Mirroring:** Mirror the broadcast output (e.g., for front-facing camera preview) using slot mirroring configuration

---

## 4. Real-Time Streaming SDK

The IVS Real-Time Streaming SDK uses WebRTC to enable multi-participant interactive video with sub-300ms latency. Participants connect to **stages** where they can publish and subscribe to each other's streams.

### Key Concepts

- **Stage:** A virtual space where participants exchange real-time audio/video
- **Participant Token:** JWT that authenticates a user when joining a stage (generated server-side)
- **Strategy:** Interface defining what streams to publish, which participants to subscribe to
- **LocalStageStream:** Wraps local media tracks (camera, mic, screen share)
- **RemoteStageStream:** Represents a remote participant's media
- **Simulcast:** Sends multiple quality layers; subscribers pick the best one for their bandwidth

### Web - Starter Code

**Install:**
```bash
npm install amazon-ivs-web-broadcast
```

**Import:**
```js
import {
  Stage,
  LocalStageStream,
  SubscribeType,
  StageEvents
} from 'amazon-ivs-web-broadcast';
```

**Full example - join a stage, publish, and subscribe:**
```js
// 1. Request permissions and configure media
async function configureMedia() {
  await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
  const devices = await navigator.mediaDevices.enumerateDevices();

  const camera = devices.find((d) => d.kind === 'videoinput');
  const mic = devices.find((d) => d.kind === 'audioinput');

  const cameraStream = await navigator.mediaDevices.getUserMedia({
    video: {
      deviceId: camera.deviceId,
      width: { ideal: 1280 },
      height: { ideal: 720 },
    },
  });
  const micStream = await navigator.mediaDevices.getUserMedia({
    audio: { deviceId: mic.deviceId },
  });

  const videoStream = new LocalStageStream(cameraStream.getVideoTracks()[0]);
  const audioStream = new LocalStageStream(micStream.getAudioTracks()[0]);
  return { videoStream, audioStream };
}

// 2. Define a strategy
const { videoStream, audioStream } = await configureMedia();

const strategy = {
  stageStreamsToPublish: () => [videoStream, audioStream],
  shouldPublishParticipant: () => true,
  shouldSubscribeToParticipant: () => SubscribeType.AUDIO_VIDEO,
};

// 3. Create stage and join
const stage = new Stage(PARTICIPANT_TOKEN, strategy);

stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => {
  // Render remote participant streams
  streams.forEach((stream) => {
    if (stream.streamType === 'video') {
      const videoEl = document.createElement('video');
      videoEl.srcObject = new MediaStream([stream.mediaStreamTrack]);
      videoEl.autoplay = true;
      document.getElementById('remote-media').appendChild(videoEl);
    }
    if (stream.streamType === 'audio') {
      const audioEl = document.createElement('audio');
      audioEl.srcObject = new MediaStream([stream.mediaStreamTrack]);
      audioEl.autoplay = true;
      document.getElementById('remote-media').appendChild(audioEl);
    }
  });
});

stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
  console.log('Participant left:', participant.id);
});

stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
  console.log('Connection state:', state);
});

try {
  await stage.join();
  console.log('Joined stage successfully');
} catch (err) {
  console.error('Failed to join stage:', err);
}

// 4. Mute/unmute
videoStream.setMuted(true);  // mute video
audioStream.setMuted(true);  // mute audio
videoStream.setMuted(false); // unmute video
audioStream.setMuted(false); // unmute audio

// 5. Leave stage
// stage.leave();
```

**Screen sharing:**
```js
async function startScreenShare() {
  const screenStream = await navigator.mediaDevices.getDisplayMedia({
    video: true,
    audio: true,
  });
  const screenVideo = new LocalStageStream(screenStream.getVideoTracks()[0]);
  // Add to your published streams array and call stage.refreshStrategy()
}
```

### React Component Example

```jsx
import { useEffect, useRef, useState } from 'react';
import {
  Stage, LocalStageStream, SubscribeType, StageEvents
} from 'amazon-ivs-web-broadcast';

function StageComponent({ participantToken }) {
  const videoRef = useRef();
  const stageRef = useRef();
  const [participants, setParticipants] = useState([]);

  useEffect(() => {
    const init = async () => {
      // Get local media
      const cameraStream = await navigator.mediaDevices.getUserMedia({
        video: { width: { ideal: 1280 }, height: { ideal: 720 } },
        audio: true,
      });
      const videoStream = new LocalStageStream(
        cameraStream.getVideoTracks()[0]
      );
      const audioStream = new LocalStageStream(
        cameraStream.getAudioTracks()[0]
      );

      const strategy = {
        stageStreamsToPublish: () => [videoStream, audioStream],
        shouldPublishParticipant: () => true,
        shouldSubscribeToParticipant: () => SubscribeType.AUDIO_VIDEO,
      };

      const stage = new Stage(participantToken, strategy);
      stageRef.current = stage;

      stage.on(
        StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED,
        (participant, streams) => {
          setParticipants((prev) => [
            ...prev,
            { ...participant, streams },
          ]);
        }
      );

      stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
        setParticipants((prev) =>
          prev.filter((p) => p.id !== participant.id)
        );
      });

      await stage.join();

      // Show local preview
      videoRef.current.srcObject = new MediaStream([
        videoStream.mediaStreamTrack,
      ]);
    };

    init();
    return () => stageRef.current?.leave();
  }, [participantToken]);

  return (
    <div>
      <video ref={videoRef} autoPlay playsInline muted />
      {participants.map((p) => (
        <video
          key={p.id}
          autoPlay
          playsInline
          ref={(el) => {
            if (el) {
              const tracks = p.streams.map((s) => s.mediaStreamTrack);
              el.srcObject = new MediaStream(tracks);
            }
          }}
        />
      ))}
    </div>
  );
}
```

### iOS - Starter Code (Swift)

```swift
import AmazonIVSBroadcast

class StageViewController: UIViewController {
    var stage: IVSStage?
    var localStreams: [IVSLocalStageStream] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        // Set up camera and microphone
        let camera = IVSDevice.listLocalDevices()
            .compactMap { $0 as? IVSCamera }
            .first { $0.position == .front }
        let mic = IVSDevice.listLocalDevices()
            .compactMap { $0 as? IVSMicrophone }
            .first

        if let camera = camera {
            localStreams.append(IVSLocalStageStream(device: camera))
        }
        if let mic = mic {
            localStreams.append(IVSLocalStageStream(device: mic))
        }

        // Create stage and join
        stage = try? IVSStage(token: PARTICIPANT_TOKEN, strategy: self)
        stage?.addRenderer(self)
        try? stage?.join()
    }
}

extension StageViewController: IVSStageStrategy {
    func stage(_ stage: IVSStage,
               streamsToPublishForParticipant participant: IVSParticipantInfo)
    -> [IVSLocalStageStream] {
        return localStreams
    }

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

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

extension StageViewController: IVSStageRenderer {
    func stage(_ stage: IVSStage,
               participantDidJoin participant: IVSParticipantInfo) {
        print("Participant joined: \(participant.participantId)")
    }

    func stage(_ stage: IVSStage,
               participant: IVSParticipantInfo,
               didAdd streams: [IVSStageStream]) {
        // Render remote participant video
        for stream in streams {
            if let imageDevice = stream.device as? IVSImageDevice,
               let preview = try? imageDevice.previewView(with: .fit) {
                view.addSubview(preview)
            }
        }
    }

    func stage(_ stage: IVSStage,
               participantDidLeave participant: IVSParticipantInfo) {
        print("Participant left: \(participant.participantId)")
    }
}
```

### Android - Starter Code (Kotlin)

```kotlin
import com.amazonaws.ivs.broadcast.Stage
import com.amazonaws.ivs.broadcast.StageStrategy
import com.amazonaws.ivs.broadcast.StageRenderer
import com.amazonaws.ivs.broadcast.ParticipantInfo
import com.amazonaws.ivs.broadcast.LocalStageStream
import com.amazonaws.ivs.broadcast.SubscribeType

class StageActivity : AppCompatActivity(), StageStrategy, StageRenderer {
    private var stage: Stage? = null
    private var localStreams = mutableListOf<LocalStageStream>()

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

        // Set up local camera/mic streams
        val devices = DeviceDiscovery(this).listLocalDevices()
        devices.forEach { device ->
            localStreams.add(LocalStageStream(device))
        }

        // Create and join stage
        stage = Stage(this, PARTICIPANT_TOKEN, this)
        stage?.addRenderer(this)
        stage?.join()
    }

    // StageStrategy
    override fun stageStreamsToPublishForParticipant(
        stage: Stage, participantInfo: ParticipantInfo
    ): List<LocalStageStream> = localStreams

    override fun shouldPublishFromParticipant(
        stage: Stage, participantInfo: ParticipantInfo
    ): Boolean = true

    override fun shouldSubscribeToParticipant(
        stage: Stage, participantInfo: ParticipantInfo
    ): SubscribeType = SubscribeType.AUDIO_VIDEO

    // StageRenderer
    override fun onParticipantJoined(
        stage: Stage, participantInfo: ParticipantInfo
    ) {
        Log.d("IVS", "Participant joined: ${participantInfo.participantId}")
    }

    override fun onParticipantPublished(
        stage: Stage, participantInfo: ParticipantInfo, streams: List<StageStream>
    ) {
        // Render remote streams
        streams.forEach { stream ->
            if (stream.device is ImageDevice) {
                val preview = (stream.device as ImageDevice).getPreviewView()
                runOnUiThread { remoteContainer.addView(preview) }
            }
        }
    }

    override fun onParticipantLeft(
        stage: Stage, participantInfo: ParticipantInfo
    ) {
        Log.d("IVS", "Participant left: ${participantInfo.participantId}")
    }

    override fun onDestroy() {
        super.onDestroy()
        stage?.leave()
    }
}
```

### Stage Events (Web)

| Event | Description |
|---|---|
| `STAGE_CONNECTION_STATE_CHANGED` | Stage connection state changed (CONNECTING, CONNECTED, DISCONNECTED, ERRORED) |
| `STAGE_PARTICIPANT_JOINED` | A participant joined the stage |
| `STAGE_PARTICIPANT_LEFT` | A participant left the stage |
| `STAGE_PARTICIPANT_PUBLISH_STATE_CHANGED` | Participant's publish state changed |
| `STAGE_PARTICIPANT_SUBSCRIBE_STATE_CHANGED` | Participant's subscribe state changed |
| `STAGE_PARTICIPANT_STREAMS_ADDED` | Participant published new streams |
| `STAGE_PARTICIPANT_STREAMS_REMOVED` | Participant's streams removed |
| `STAGE_STREAM_MUTE_CHANGED` | A stream's mute state changed |
| `ERROR` | An error occurred |

### Stage Error Codes (Web)

| Error | Description |
|---|---|
| `TOKEN_MALFORMED` | Participant token is invalid or malformed |
| `TOKEN_EXPIRED` | Participant token has expired |
| `TIMEOUT` | Connection timed out |
| `FAILED` | Generic connection failure |
| `CANCELED` | Operation was canceled |
| `STAGE_AT_CAPACITY` | Stage has reached maximum participants |
| `CODEC_MISMATCH` | Codec negotiation failed |
| `TOKEN_NOT_ALLOWED` | Token does not have required capabilities |

### Token Exchange

Token exchange enables upgrading or downgrading participant-token capabilities and updating token attributes mid-session, without requiring participants to reconnect. Useful for co-hosting scenarios where a participant starts as subscribe-only and later gains publish capabilities.

**Usage:**
```swift
// iOS
try stage.exchangeToken(newToken)
```
```kotlin
// Android
stage.exchangeToken(newToken)
```

**Limitations:**
- Token exchange only works with tokens created using a key pair (not tokens from the `CreateParticipantToken` API)
- Attribute changes that drive server-side composition layouts (e.g., `featuredParticipantAttribute`) do not update the layout until the participant reconnects

### Third-Party Camera Filters

The IVS Real-Time Broadcast SDK supports integrating third-party AR/beauty filter SDKs on iOS and Android by feeding the filter SDK's processed output to a custom image source.

**Supported integrations:**
- **BytePlus Effects** - AR effects, beauty filters, face reshaping, stickers
- **DeepAR** - AR effects, face filters, background segmentation
- **Snap Camera Kit** - Snapchat lenses and AR effects
- **Background Replacement** - Virtual backgrounds using built-in or third-party segmentation

**Integration pattern:**
1. Initialize the third-party filter SDK with camera access
2. Create a custom image source via the IVS Broadcast SDK
3. In the filter SDK's output callback, feed processed frames to the custom image source
4. Attach the custom image source to the stage for publishing

The filter SDK manages the camera lifecycle and image processing. The IVS SDK receives already-processed frames through the custom image source, keeping the two SDKs decoupled.

### Mobile Audio Modes

The IVS mobile Broadcast SDKs provide a `StageAudioManager` class for controlling audio behavior on iOS and Android. On Android, it manages `AudioManager` (audio mode, source, content type, communication devices). On iOS, it manages the `AVAudioSession` and voice processing settings.

**Audio mode presets:**

| Preset | Use Case | Voice Processing | Stereo Output |
|---|---|---|---|
| `videoChat` | Video calls, co-hosting (default) | Enabled | No |
| `music` | Live music, DJ streaming, karaoke | Disabled | Yes (if supported) |
| `gaming` | Game streaming with voice chat | Enabled | No |

Set the audio mode before initializing `DeviceDiscovery` or `Stage`:
```swift
// iOS
StageAudioManager.shared().setPreset(.music)
```
```kotlin
// Android
StageAudioManager.getInstance(context).setPreset(StageAudioManager.Preset.MUSIC)
```

If no preset is set, `videoChat` is applied automatically. Voice processing includes echo cancellation and noise suppression, which should be disabled for music streaming.

### Custom Audio Sources

Custom audio-input sources allow an application to provide its own audio input to the Broadcast SDK, instead of being limited to the device microphone. Use cases include streaming processed audio with effects, mixing multiple audio streams, or integrating with third-party audio processing libraries.

When using a custom audio source, the SDK is no longer responsible for managing the microphone - the application handles capturing, processing, and delivering audio data to the SDK.

---

## 5. Chat SDK

IVS Chat provides WebSocket-based real-time messaging rooms. It works standalone or alongside either streaming product. Moderation is handled via AWS Lambda message review handlers.

### Web - Starter Code

**Install:**
```bash
npm install amazon-ivs-chat-messaging
```

**Basic usage:**
```js
import { ChatRoom } from 'amazon-ivs-chat-messaging';

// Token provider function (calls your backend)
async function tokenProvider() {
  const response = await fetch('/api/chat-token', {
    method: 'POST',
    body: JSON.stringify({ roomId: 'YOUR_ROOM_ID', userId: 'user123' }),
  });
  const data = await response.json();
  return {
    token: data.token,
    sessionExpirationTime: new Date(data.sessionExpirationTime),
    tokenExpirationTime: new Date(data.tokenExpirationTime),
  };
}

// Create and connect
const room = new ChatRoom({
  regionOrUrl: 'us-west-2',
  tokenProvider,
});

room.addListener('connect', () => {
  console.log('Connected to chat room');
});

room.addListener('message', (message) => {
  console.log(`${message.sender.userId}: ${message.content}`);
});

room.addListener('event', (event) => {
  console.log('Chat event:', event.eventName);
});

room.addListener('disconnect', (reason) => {
  console.log('Disconnected:', reason);
});

room.connect();

// Send a message
const request = new SendMessageRequest('Hello, world!');
room.sendMessage(request);

// Disconnect
// room.disconnect();
```

### iOS - Starter Code (Swift)

```swift
import AmazonIVSChatMessaging

class ChatViewController: UIViewController {
    var room: ChatRoom?

    override func viewDidLoad() {
        super.viewDidLoad()

        room = ChatRoom(
            regionOrUrl: "us-west-2",
            tokenProvider: { completion in
                // Call your backend to get a chat token
                self.fetchChatToken { token, error in
                    if let token = token {
                        completion(token, nil)
                    } else {
                        completion(nil, error)
                    }
                }
            }
        )

        room?.delegate = self
        room?.connect()
    }
}

extension ChatViewController: ChatRoomDelegate {
    func room(_ room: ChatRoom, didReceive message: ChatMessage) {
        print("\(message.sender?.userId ?? "unknown"): \(message.content)")
    }

    func roomDidConnect(_ room: ChatRoom) {
        print("Connected to chat")
    }

    func room(_ room: ChatRoom, didDisconnectWith reason: DisconnectReason) {
        print("Disconnected: \(reason)")
    }
}
```

### Android - Starter Code (Kotlin)

```kotlin
import com.amazonaws.ivs.chat.messaging.ChatRoom
import com.amazonaws.ivs.chat.messaging.ChatRoomListener
import com.amazonaws.ivs.chat.messaging.ChatMessage

class ChatActivity : AppCompatActivity() {
    private var chatRoom: ChatRoom? = null

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

        chatRoom = ChatRoom(
            region = "us-west-2",
            tokenProvider = { callback ->
                // Call your backend to get a chat token
                fetchChatToken { token, error ->
                    callback(token, error)
                }
            }
        )

        chatRoom?.listener = object : ChatRoomListener {
            override fun onConnected(room: ChatRoom) {
                Log.d("Chat", "Connected")
            }

            override fun onMessageReceived(room: ChatRoom, message: ChatMessage) {
                Log.d("Chat", "${message.sender?.userId}: ${message.content}")
            }

            override fun onDisconnected(room: ChatRoom, reason: DisconnectReason) {
                Log.d("Chat", "Disconnected: $reason")
            }
        }

        chatRoom?.connect()
    }

    fun sendMessage(text: String) {
        chatRoom?.sendMessage(text)
    }
}
```

### Token Generation (Backend - Node.js)

Chat tokens must be generated server-side using the AWS SDK:

```js
import {
  IvschatClient,
  CreateChatTokenCommand,
} from '@aws-sdk/client-ivschat';

const client = new IvschatClient({ region: 'us-west-2' });

async function createChatToken(roomId, userId, capabilities) {
  const command = new CreateChatTokenCommand({
    roomIdentifier: roomId,
    userId: userId,
    capabilities: capabilities || ['SEND_MESSAGE'], // or ['SEND_MESSAGE', 'DISCONNECT_USER', 'DELETE_MESSAGE']
    sessionDurationInMinutes: 120,
  });

  const response = await client.send(command);
  return {
    token: response.token,
    sessionExpirationTime: response.sessionExpirationTime,
    tokenExpirationTime: response.tokenExpirationTime,
  };
}
```

### Moderation with Lambda

Attach a Lambda function as a message review handler to a chat room. The Lambda receives each message before delivery and can:
- **Allow:** Return `{ "ReviewResult": "ALLOW" }`
- **Deny:** Return `{ "ReviewResult": "DENY" }`

**Important:** The Lambda must respond within 200ms. Use provisioned concurrency to avoid cold start delays. Configure `FallbackResult` to `ALLOW` or `DENY` to handle timeout scenarios.

### React & React Native Best Practices

**React Native support:** The IVS Chat JavaScript SDK works in React Native with a polyfill for `crypto.getRandomValues`:
```js
// Import at the top of index.js, before other imports
import 'react-native-get-random-values';
```

**ChatRoom Initializer Hook:** Create a custom hook that manages ChatRoom lifecycle (connection, disconnection, cleanup):
```js
function useChatRoom({ regionOrUrl, tokenProvider }) {
  const roomRef = useRef(null);

  useEffect(() => {
    const room = new ChatRoom({ regionOrUrl, tokenProvider });
    roomRef.current = room;
    room.connect();
    return () => room.disconnect();
  }, [regionOrUrl, tokenProvider]);

  return roomRef;
}
```

**ChatRoom Instance Provider:** Wrap your app in a context provider to share a single ChatRoom instance across components:
```jsx
const ChatRoomContext = createContext(null);

function ChatRoomProvider({ children, regionOrUrl, tokenProvider }) {
  const room = useChatRoom({ regionOrUrl, tokenProvider });
  return (
    <ChatRoomContext.Provider value={room.current}>
      {children}
    </ChatRoomContext.Provider>
  );
}
```

**Message Listener pattern:** Use `useEffect` to subscribe to `message` events and update state:
```js
useEffect(() => {
  const handler = (message) => {
    setMessages((prev) => [...prev, message]);
  };
  room.addListener('message', handler);
  return () => room.removeListener('message', handler);
}, [room]);
```

**Multiple Chat Rooms:** For apps requiring multiple concurrent chat rooms (e.g., separate rooms per stream), create separate `ChatRoom` instances with independent token providers and manage them via a map or array in state.

---

## 6. Platform Features & Capabilities

### 6.1 Record-to-S3 (Auto-Recording)

**Product:** Low-Latency Streaming

Automatically records live streams from Low-Latency channels to Amazon S3 buckets for later playback as video-on-demand (VOD). Real-Time streams cannot be recorded directly - use stage-to-channel forwarding (see 6.8) to record Real-Time content via a Low-Latency channel.

**Use cases:** VOD replay, highlights, content archiving, compliance recording, post-production workflows.

**Setup:**
1. Create a recording configuration specifying an S3 bucket destination
2. Associate the recording configuration with a channel
3. Streams are automatically recorded as HLS manifest (.m3u8) and segment (.ts) files

**CLI example:**
```bash
aws ivs create-recording-configuration \
    --name "my-recording-config" \
    --recording-reconnect-window-seconds 60 \
    --destination-configuration s3={bucketName=MY_BUCKET} \
    --thumbnail-configuration recordingMode="INTERVAL",targetIntervalSeconds=30
```

**Recording output structure in S3:**
```
/ivs/v1/{account_id}/{channel_id}/{year}/{month}/{day}/{hours}/{minutes}/{recording_id}/
  media/
    hls/
      master.m3u8              # Master manifest
      480p30/output.m3u8       # Rendition manifests
      720p30/output.m3u8
      480p30/byte-range-variant.m3u8   # Byte-range manifest (1s precision)
    thumbnails/
      thumb0.jpg, thumb1.jpg, ...
```

**Key details:**
- Recordings include Program-Date-Time (PDT) tags for precise time-synchronized access to segments
- Byte-range manifests enable up to 1-second precision clipping (depends on keyframe interval)
- Thumbnail generation at configurable intervals via `targetIntervalSeconds`
- S3 objects are private by default - use CloudFront to serve recorded VODs (do NOT use CloudFront for live playback URLs)
- Use CBR encoding with <10% bitrate variation for proper recording segment stitching
- Recording reconnect window allows resuming a recording if the stream briefly disconnects

**Merge Fragmented Streams:** When a stream briefly disconnects and reconnects within the `recordingReconnectWindowSeconds` window (configurable, default 0), IVS merges the fragments into a single recording prefix. EventBridge emits a `Recording End` event with type `recording-ended-with-merging` when this occurs. Rapid reconnects (within ~10 seconds of the previous broadcast finishing S3 writes) may not be merged.

**Playback of Recorded Content from Private Buckets:** Objects recorded to S3 are private by default. To enable playback, set up an Amazon CloudFront distribution with an origin access control (OAC) pointing to the private S3 bucket. CloudFront serves the HLS content with proper CORS headers. Do NOT use direct S3 URLs for playback - they will return permission errors.

**EventBridge events:** `Recording Start`, `Recording End`, `Recording Start Failure` - use these to trigger post-processing workflows (e.g., MediaConvert transcoding, notifications).

### 6.2 VOD Clip Generation

**Product:** Low-Latency Streaming

Create clips from recorded streams using Program-Date-Time (PDT) timestamps and byte-range manifests. This enables extracting highlights or best moments from archived live streams.

**Use cases:** Highlight reels, social media clips, content repurposing, removing dead air from beginning/end of streams.

**How it works:**
1. Enable Record-to-S3 with byte-range manifests on the channel
2. After the stream ends, use the PDT timestamps in the manifest to identify clip boundaries
3. Generate a new .m3u8 manifest containing only the desired segments
4. Serve the clipped manifest via CloudFront for playback

**Open-source clip API** (AWS SAM-based): https://github.com/aws-samples/amazon-ivs-clip-manifest

```bash
curl -X POST https://API_ENDPOINT/clipmanifest/ \
  -H "Content-Type: application/json" \
  -d '{"start_time": 20, "end_time": 70, "master_url": "https://CLOUDFRONT_URL/.../master.m3u8", "byte_range": true}'
```

Clip precision depends on the keyframe interval - use 1-2s keyframe intervals for finer granularity.

### 6.3 Converting Recordings to MP4

**Product:** Low-Latency Streaming

HLS recordings in S3 can be converted to MP4 using AWS Elemental MediaConvert for broader playback compatibility.

**Use cases:** Downloading recorded streams, uploading to social platforms, offline viewing, post-production editing.

**Workflow:**
1. EventBridge `Recording End` event triggers a Lambda function
2. Lambda creates a MediaConvert job using the HLS master manifest as input
3. MediaConvert outputs MP4 (or DASH, or other formats) to S3
4. Optionally stitch pre/post bumpers and image overlays during conversion

### 6.4 Playback Authorization (Private Channels)

**Product:** Low-Latency Streaming only (not available for Real-Time)

Restrict access to live streams by requiring valid authorization tokens for HLS playlist requests. When enabled, only viewers with valid tokens can watch the stream.

**Use cases:** Pay-per-view events, subscriber-only content, geo-restricted streams, age-gated content.

**How it works:**
1. Generate a playback key pair (public/private RSA keys)
2. Import the public key to IVS via the `ImportPlaybackKeyPair` API
3. Enable `authorized` on the channel (set `authorized=true` when creating or updating)
4. Server-side, sign JWT tokens with the private key for authorized viewers
5. Viewers pass the token when requesting the playback URL

**Key details:**
- Tokens are JWTs signed with the private RSA key
- Tokens can be scoped per-channel and include expiration times
- IVS does NOT support DRM (Digital Rights Management) or stream content encryption
- If the control plane is down, existing viewers continue playback but new viewers may fail authorization

### 6.5 Playback Restriction Policies

**Product:** Low-Latency Streaming

Playback restriction policies provide geographic and origin-based access control for live streams. These are distinct from playback authorization (which uses playback key pairs and JWT tokens) - restriction policies enforce rules based on where the viewer is located or which domain they are watching from, without requiring per-viewer tokens.

**Use cases:** Geo-restricted broadcasts (e.g., sports rights limited to specific countries), domain-locked embeds (only allow playback on your website), regulatory compliance.

**How it works:**
1. Create a playback restriction policy specifying allowed countries and/or allowed origins
2. Attach the policy to a channel
3. IVS enforces the restrictions at the playback level - viewers outside allowed countries or origins are denied

**Key details:**
- `allowedCountries`: List of ISO 3166-1 alpha-2 country codes where playback is permitted (max 200 per policy)
- `allowedOrigins`: List of allowed referrer domains (max 5 per policy, max 256 characters each)
- Maximum 3 playback restriction policies per AWS Region (not adjustable)
- Can be combined with playback authorization (key pairs) for layered security
- API operations: `CreatePlaybackRestrictionPolicy`, `GetPlaybackRestrictionPolicy`, `UpdatePlaybackRestrictionPolicy`, `DeletePlaybackRestrictionPolicy`, `ListPlaybackRestrictionPolicies`

### 6.6 Timed Metadata API (PutMetadata)

**Product:** Low-Latency Streaming only (NOT supported in Real-Time)

Inject time-synchronized metadata into live streams via a REST API. The metadata is delivered to viewers in sync with the video frame it was attached to.

**Use cases:** Live polls, trivia questions, leaderboard updates, product links, synchronized overlays, ad markers.

**Server-side (inject metadata):**
```js
import { IvsClient, PutMetadataCommand } from '@aws-sdk/client-ivs';

const client = new IvsClient({ region: 'us-west-2' });
await client.send(new PutMetadataCommand({
  channelArn: 'arn:aws:ivs:us-west-2:123456789:channel/abcdef',
  metadata: JSON.stringify({ type: 'poll', question: 'Favorite color?', options: ['Red', 'Blue'] }),
}));
```

**Client-side (receive metadata):**
- Web: Listen for `PlayerEventType.TEXT_METADATA_CUE` - `cue.text` contains the metadata string
- iOS: `IVSPlayer.Delegate.player(_:didOutputCue:)` with `IVSTextMetadataCue`
- Android: `Player.Listener.onCue(TextMetadataCue)` - `cue.text` contains the string

**Note:** For Real-Time Streaming, use participant `attributes` (set during token creation) instead of timed metadata.

### 6.7 Server-Side Composition

**Product:** Real-Time Streaming

Compose multiple Real-Time stage participants into a single video output on the server side using the `StartComposition` API. Outputs can be recorded to S3 or forwarded to other destinations (e.g., a Low-Latency channel for HLS broadcast).

**Use cases:** Recording multi-participant stages, broadcasting a stage to a large Low-Latency audience, creating production-quality layouts without client-side rendering.

**Layouts:**

**Grid Layout** - Arranges participants in an automatic grid:
```json
{
  "layout": {
    "grid": {
      "featuredParticipantAttribute": "role",
      "videoAspectRatio": "VIDEO",
      "videoFillMode": "COVER",
      "omitStoppedVideo": true,
      "gridGap": 2
    }
  }
}
```

| Property | Options | Default | Description |
|---|---|---|---|
| `videoAspectRatio` | AUTO, VIDEO (16:9), SQUARE (1:1), PORTRAIT (3:4) | VIDEO | Aspect ratio of video tiles |
| `videoFillMode` | FILL, COVER, CONTAIN | COVER (non-featured), CONTAIN (featured) | How video fits in tile |
| `omitStoppedVideo` | true/false | false | Hide participants with camera off |
| `gridGap` | pixels | 2 | Spacing between tiles |
| `featuredParticipantAttribute` | string | "" | Attribute key to identify the featured participant |

**PIP (Picture-in-Picture) Layout** - Featured participant with overlay:
```json
{
  "layout": {
    "pip": {
      "featuredParticipantAttribute": "role",
      "pipParticipantAttribute": "pip_user",
      "pipPosition": "BOTTOM_RIGHT",
      "pipBehavior": "DYNAMIC",
      "videoFillMode": "COVER",
      "omitStoppedVideo": true
    }
  }
}
```

| Property | Options | Default | Description |
|---|---|---|---|
| `pipPosition` | TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT | BOTTOM_RIGHT | PIP window corner |
| `pipBehavior` | STATIC, DYNAMIC | STATIC | DYNAMIC = PIP goes fullscreen when alone |
| `pipWidth` / `pipHeight` | pixels | auto | PIP window dimensions |
| `pipOffset` | pixels | 0 | Distance from edges |

**Custom Participant Ordering:** Control the order of participants in grid and PiP layouts using the `participantOrderAttribute` property. When specified, participants are sorted by the value of the matching attribute on their token. This allows deterministic ordering (e.g., host always in first position).

**Screen Share in Compositions:** To include a screen-sharing participant in a server-side composition, the screen share stream must come from a separate participant (with its own token) that is marked with the `featuredParticipantAttribute`. The screen share participant appears as the featured tile in grid layouts or the main view in PiP layouts.

**Quotas:** Max 5 concurrent compositions per account (adjustable), max 24-hour duration, max 2 destinations per composition.

**Related resources:**

- **Encoder Configurations:** Define the output encoding parameters for compositions (resolution, bitrate, framerate). Created via `CreateEncoderConfiguration` API. Max 20 per account (adjustable). Reusable across multiple compositions.
- **Storage Configurations:** Define the S3 bucket destination for composition recordings. Created via `CreateStorageConfiguration` API. Max 5 per account (adjustable). Must be created before starting a composition that records to S3.

**Recording with Server-Side Composition:** Composition outputs can be recorded to:
1. A storage configuration (S3 bucket) as a destination for the composition
2. An IVS Low-Latency channel with record-to-s3 enabled as a destination for the composition

For recording individual participants without composition, see section 6.8 below.

### 6.8 Real-Time Individual Participant Recording

**Product:** Real-Time Streaming

Record individual stage participants to S3 without server-side composition. Each participant's audio and video streams are recorded separately, enabling post-production editing, highlight generation, and independent participant archives.

**Use cases:** Podcast production (separate tracks per speaker), multi-camera recording for post-production, backup recordings of individual speakers, compliance recording of specific participants, content archival with maximum flexibility.

**Key features:**
- Records each participant's streams independently to S3
- Outputs include separate audio and video files per participant
- No composition overhead - raw participant streams are captured
- Can be combined with server-side composition (record both individual participants and the composed output)
- Storage configuration defines the S3 bucket destination
- API operations: `StartParticipantRecording`, `StopParticipantRecording`, `GetParticipantRecording`, `ListParticipantRecordings`

**How it works:**
1. Create a storage configuration via `CreateStorageConfiguration` API specifying an S3 bucket
2. Start recording for a specific participant using `StartParticipantRecording` with the participant's stage ARN and participant ID
3. Participant streams are recorded to the configured S3 bucket
4. Stop recording with `StopParticipantRecording` or let it end automatically when the participant leaves
5. Retrieve recording details with `GetParticipantRecording` for post-processing workflows

**Recording output structure in S3:**
Individual participant recordings are stored with metadata indicating participant ID, timestamps, and stream information. Use EventBridge events for `Participant Recording Start` and `Participant Recording End` to trigger automated workflows.

**Recording modes:** The `mediaTypes` parameter controls what is captured:
- `AUDIO_VIDEO` - Records both audio and video (default)
- `AUDIO_ONLY` - Records audio tracks only (smaller file size, useful for podcast-style recordings)

**Thumbnail recording:** Configure `thumbnailConfiguration` with `targetIntervalSeconds` and `storage` (SEQUENTIAL for numbered thumbnails, LATEST for a single overwritten thumbnail) when setting up auto-participant recording.

**Synchronize Multiple Participant Recordings:** Individual recordings include JSON metadata with timestamps that can be used to align recordings in post-production. Use the `recording_started_at` and per-segment timestamps in the metadata files to synchronize tracks across participants.

#### Composite Recording

Server-side composition recordings capture the composed output (all participants arranged in a layout) as a single recording to S3. This is distinct from individual participant recording.

**How it works:**
1. Create a storage configuration pointing to an S3 bucket
2. Start a composition with `StartComposition`, specifying the storage configuration as a destination
3. The composed video (grid or PiP layout with all participants) is recorded as HLS to S3
4. Composition recordings support the same thumbnail configuration options as individual recordings

**Use cases:** Recording a complete multi-participant show as a single video file, archiving composed output for VOD playback, compliance recording of the full stage.

### 6.9 Stage-to-Channel Forwarding

**Product:** Real-Time -> Low-Latency

Forward a Real-Time stage to a Low-Latency channel, enabling stage participants to be broadcast as HLS to a large audience. This bridges the two product lines.

**Use cases:** Co-host show broadcast to thousands, multi-participant events, combining real-time interaction with mass-scale delivery.

**How it works:**
1. Create a Real-Time stage and a Low-Latency channel
2. Use server-side composition (`StartComposition`) with the channel as a destination
3. The composed output is sent to the channel's ingest endpoint
4. Viewers watch via the channel's playback URL using the Player SDK

### 6.10 Stream Health & Monitoring

**Product:** Low-Latency Streaming (primary), Real-Time Streaming (WebRTC stats)

#### EventBridge Integration

IVS publishes events to Amazon EventBridge for stream lifecycle and health monitoring:

**Low-Latency events:**
- `IVS Stream State Change` - `Session Created`, `Session Ended`, `Stream Start`, `Stream End`, `Stream Failure`, `Stream Takeover`, `Stream Takeover Failure`
- `IVS Stream Health Change` - `Starvation Start`, `Starvation End`
- `IVS Limit Breach` - `Ingest Bitrate`, `Ingest Resolution`, `Concurrent Broadcasts`, `Concurrent Viewers`
- `IVS Recording State Change` - `Recording Start`, `Recording End`, `Recording Start Failure`, `Recording End Failure`

**Real-Time events:**
- `IVS Composition State Change` - Composition started, stopped, or failed
- `IVS Stage Update` - Stage configuration changed
- `IVS Participant Recording State Change` - Individual participant recording started or ended

**Use cases:** Trigger notifications, update UI when stream goes live, start/stop recording workflows, alert on stream health issues.

**EventBridge rule example:**
```json
{
  "source": ["aws.ivs"],
  "detail-type": ["IVS Stream State Change"],
  "detail": {
    "event_name": ["Stream Start", "Stream End"]
  }
}
```

#### Stream Health Dashboard

Access stream session data via the AWS Console or API:
- `ListStreams` API includes a `health` field (e.g., `"STARVING"`) for live streams
- `GetStreamSession` provides detailed session metrics (bitrate, frame rate, resolution over time)
- Stream session data is retained for 60 days, even after channel deletion

#### CloudWatch Metrics

IVS publishes CloudWatch metrics for monitoring:
- `ConcurrentStreams` - number of active streams (filterable by Health dimension)
- `ConcurrentViews` - number of concurrent viewers
- Use CloudWatch alarms to trigger actions on threshold breaches

#### Real-Time WebRTC Stats

For Real-Time stages, call `getStats()` on any `RemoteStageStream` to get WebRTC quality metrics (bitrate, packet loss, jitter, round-trip time). Use this for in-app quality indicators.

### 6.11 Closed Captions

**Product:** Low-Latency Streaming

IVS supports CEA-708/EIA-608 closed captions embedded in the RTMP ingest stream. The IVS Player SDK delivers caption data via `TextCue` events.

**Use cases:** Accessibility, compliance requirements, noisy environments, multilingual audiences.

**How to add captions:**
1. Install an OBS caption plugin (e.g., `OBS-captions-plugin` by ratwithacompiler) that performs speech-to-text and embeds captions in CEA-708/EIA-608 format
2. Configure the plugin with a caption source (microphone) and timeout settings
3. Start streaming - captions are embedded automatically

**Receiving captions (Web):**
```js
player.addEventListener(IVSPlayer.PlayerEventType.TEXT_CUE, (cue) => {
  // cue.text contains the caption text
  document.getElementById('captions').textContent = cue.text;
});
```

**Note:** Captions are available only in Low-Latency streaming via the `TEXT_CUE` event (distinct from `TEXT_METADATA_CUE` used for timed metadata).

### 6.12 Adaptive Bitrate (ABR) Transcoding

**Product:** Low-Latency Streaming

IVS automatically transcodes streams into multiple renditions based on channel type. Viewers automatically receive the best quality for their network via ABR.

| Channel Type | Renditions |
|---|---|
| **Basic** | Source only (transmux, no transcode) |
| **Standard** | 1080p (source), 720p, 480p, 360p, 160p |
| **Advanced HD** | 720p, 480p, 360p, 160p, audio-only (two bandwidth presets: higher and constrained) |
| **Advanced SD** | 480p, 360p, 160p, audio-only (two bandwidth presets: higher and constrained) |

- Renditions are capped at input quality (no upscaling)
- Advanced channels also support Multi-Track E-RTMP for client-side ABR (send multiple renditions from the encoder)
- The IVS proprietary Low-Latency HLS (LHLS) system uses segment prefetching, chunked transfer, and specialized hardware transcoding (300-500ms overhead) to achieve 2-3s median latency

**Broadcast Performance Metrics (BPM):** For multitrack video (E-RTMP) channels, broadcast software must embed BPM data as SEI (H.264/HEVC) messages before every IDR on every video track. BPM captures:
- **Timestamps:** NTP-synchronized UTC timestamps for measuring end-to-end latency between broadcaster and viewer
- **Frame counters:** Per-rendition metrics for monitoring encoder performance (frames encoded, dropped, etc.)

BPM enables the IVS automatic stream configuration system to optimize stream settings and provides broadcasters with a performance dashboard. All three BPM messages (BPM TS, BPM SM, BPM ERM) must be sent together as separate NAL units, just prior to each IDR. Third-party broadcast software implementing IVS multitrack video support must implement BPM as a required feature.

### 6.13 Screen Sharing

**Product:** Real-Time Streaming

Stage participants can share their screen alongside camera/microphone streams.

**Web:**
```js
const screenStream = await navigator.mediaDevices.getDisplayMedia({
  video: true,
  audio: true, // optional system audio
});
const screenVideo = new LocalStageStream(screenStream.getVideoTracks()[0]);
// Add to published streams and call stage.refreshStrategy()
```

The `displaySurface` option in `getDisplayMedia` can be used to default to entire screen, window, or tab - but browser UI always lets users override the selection.

**iOS:** Use a Broadcast Upload Extension for full-screen sharing, or `RPScreenRecorder` for app-only sharing.

**Android:** Partial screen sharing (app-only) requires Android 15+. Full screen sharing via system media projection.

### 6.14 Real-Time Public Keys

**Product:** Real-Time Streaming

Public keys enable server-side participant token verification. Instead of relying solely on the IVS-managed token signing, you can import your own public key and sign participant tokens with the corresponding private key. IVS uses the public key to verify that tokens were generated by your application.

**Use cases:** Custom token signing workflows, bring-your-own-key security models, token verification without calling IVS APIs for each token.

**Key details:**
- Import public keys via `ImportPublicKey` API
- Maximum 3 public keys per AWS Region (not adjustable)
- API operations: `ImportPublicKey`, `GetPublicKey`, `ListPublicKeys`, `DeletePublicKey` (all 3 TPS)

### 6.15 Real-Time Ingest & Transcode Configurations

**Product:** Real-Time Streaming

**Ingest Configurations** define how external sources (RTMP, SRT) are ingested into a Real-Time stage. This allows traditional broadcast equipment and encoders to publish to stages without using the WebRTC-based Broadcast SDK or WHIP protocol.

**Use cases:** Professional broadcast equipment integration, existing RTMP/SRT encoder workflows feeding into Real-Time stages, hybrid production setups.

**Supported ingest protocols for stages:**
- **RTMP single-track video:** Standard RTMP/RTMPS ingest to a stage. Creates a single-quality stream.
- **E-RTMP multitrack video:** Enhanced RTMP with multiple video tracks for adaptive bitrate. Each track is a separate rendition (e.g., 720p, 360p, 160p) encoded by the broadcaster.
- **WHIP:** WebRTC HTTP Ingestion Protocol for low-latency ingest from OBS 30+ or other WHIP-compatible encoders.

**Private Ingest to Stages:** You can publish RTMP(S) and E-RTMP(S) streams to a stage from resources inside your Amazon VPC or from Direct Connect by using an interface VPC endpoint. This enables a private connection between your VPC and IVS, keeping ingest traffic within the AWS network. Setup follows the same VPC endpoint configuration as Low-Latency private ingest (see Section 6.20).

**Transcode Configurations** define transcoding parameters for Real-Time content, controlling how stage participant streams are processed for different output needs.

### 6.16 Chat Logging Configurations

**Product:** Chat

Logging configurations enable automatic logging of chat messages to external AWS services for archival, analytics, and compliance purposes.

**Use cases:** Message archival for compliance, chat analytics pipelines, content moderation audit trails, historical message retrieval.

**Supported destinations:**
- **Amazon S3:** Batch message logs stored as objects
- **Amazon CloudWatch Logs:** Real-time message logging with search and filter
- **Amazon Kinesis Data Firehose:** Streaming delivery to data lakes, analytics services, or custom destinations

**Key details:**
- Create a logging configuration specifying the destination, then attach it to a chat room
- Maximum 10 logging configurations per account (adjustable)
- API operations: `CreateLoggingConfiguration`, `GetLoggingConfiguration`, `UpdateLoggingConfiguration`, `DeleteLoggingConfiguration`, `ListLoggingConfigurations` (all 3 TPS)
- **Monitoring delivery errors:** Use CloudWatch metrics to monitor log delivery failures. If the destination (S3, CloudWatch Logs, or Firehose) is misconfigured or inaccessible, IVS emits error metrics. Check IAM permissions on the logging destination and verify the destination resource exists.

### 6.17 Resiliency & Architecture

**Product:** All

IVS uses a **regional control plane** (US East, US West, Tokyo, Seoul, Mumbai, Ireland, Frankfurt) for resource management and a **global data plane** for streaming/delivery. They are independent:

- **Control plane down:** Errors creating/managing resources. Ongoing streams continue. Recording to S3 may be interrupted. Playback authorization for new viewers may fail.
- **Data plane:** Video travels over a dedicated, congestion-free network with direct peering - separate from the public internet. Streams are ingested at the nearest IVS edge location regardless of control plane region.

**SLA:** 99.9% uptime commitment.

**Best practices:**
- IVS does not support automatic regional failover - implement your own if needed
- Encoder reconnect: most encoders (OBS) auto-reconnect on brief disconnects; IVS waits ~30s before ending a session
- Use EventBridge events to detect stream health issues and trigger failover logic
- For critical broadcasts, consider redundant encoder setups (separate channels in different regions)

### 6.18 Cost Management

**Product:** All

- **Cost allocation tags:** Tag IVS resources (channels, stages, rooms) with custom keys for cost tracking across departments or projects. Activate tags in the Billing console; costs appear in Cost Explorer grouped by tag.
- **Unused stages:** No charge for stages with no connected participants. Billing is based on participant-hours only.
- **Low-Latency pricing:** Based on video input hours + video output hours. Varies by channel type.
- **Real-Time pricing:** Based on participant-hours (publishers + subscribers).
- **Chat pricing:** Based on messages sent + messages delivered.

### 6.19 Control-Plane APIs

**Product:** All

The IVS control-plane APIs are REST APIs (HTTP/JSON) used to create, configure, and manage IVS resources server-side. Each product line has its own API endpoint. All requests must be signed with AWS Signature Version 4. Use the AWS SDK or CLI to interact with these APIs.

**API Endpoints:**
- **Low-Latency Streaming:** `https://ivs.{region}.amazonaws.com` - [API Reference](https://docs.aws.amazon.com/ivs/latest/APIReference/Welcome.html)
- **Real-Time Streaming:** `https://ivsrealtime.{region}.amazonaws.com` - [API Reference](https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/Welcome.html)
- **Chat:** `https://ivschat.{region}.amazonaws.com` - [API Reference](https://docs.aws.amazon.com/ivs/latest/ChatAPIReference/Welcome.html)

#### Low-Latency Streaming API

| Resource | Operations |
|---|---|
| **Channel** | `CreateChannel`, `GetChannel`, `UpdateChannel`, `DeleteChannel`, `BatchGetChannel`, `ListChannels` |
| **Stream Key** | `CreateStreamKey`, `GetStreamKey`, `DeleteStreamKey`, `BatchGetStreamKey`, `ListStreamKeys` |
| **Stream** | `GetStream`, `GetStreamSession`, `ListStreams`, `ListStreamSessions`, `StopStream`, `PutMetadata` |
| **Playback Key Pair** | `ImportPlaybackKeyPair`, `GetPlaybackKeyPair`, `DeletePlaybackKeyPair`, `ListPlaybackKeyPairs` |
| **Playback Restriction Policy** | `CreatePlaybackRestrictionPolicy`, `GetPlaybackRestrictionPolicy`, `UpdatePlaybackRestrictionPolicy`, `DeletePlaybackRestrictionPolicy`, `ListPlaybackRestrictionPolicies` |
| **Recording Configuration** | `CreateRecordingConfiguration`, `GetRecordingConfiguration`, `DeleteRecordingConfiguration`, `ListRecordingConfigurations` |
| **Viewer Session** | `StartViewerSessionRevocation`, `BatchStartViewerSessionRevocation` |
| **Tags** | `TagResource`, `UntagResource`, `ListTagsForResource` |

**Key operations:**

- `CreateChannel` - Creates a new channel. Parameters include `name`, `type` (BASIC, STANDARD, ADVANCED_SD, ADVANCED_HD), `authorized` (enable playback authorization), `latencyMode` (LOW, NORMAL), `recordingConfigurationArn`, `playbackRestrictionPolicyArn`, `preset` (optional, for constrained/higher bandwidth), and `tags`.
- `UpdateChannel` - Updates channel configuration. Accepts same parameters as create (except stream key generation). Changes take effect on next stream.
- `CreateStreamKey` - Generates a stream key for a channel. A channel can have multiple stream keys. Treat stream keys as secrets.
- `StopStream` - Forcefully stops a live stream on a channel. Use when a broadcaster is unresponsive or for moderation.
- `PutMetadata` - Injects timed metadata into a live stream (max 1KB payload). Delivered to viewers in sync with video.
- `ImportPlaybackKeyPair` - Imports an RSA public key for playback authorization token verification. The corresponding private key is used server-side to sign viewer JWTs.
- `CreateRecordingConfiguration` - Creates an auto-recording configuration targeting an S3 bucket with optional thumbnail and rendition settings.
- `StartViewerSessionRevocation` / `BatchStartViewerSessionRevocation` - Revokes viewer sessions for a specific channel. Revoked sessions are disconnected and cannot reconnect. Used for banning viewers.

#### Real-Time Streaming API

| Resource | Operations |
|---|---|
| **Stage** | `CreateStage`, `GetStage`, `UpdateStage`, `DeleteStage`, `ListStages` |
| **Stage Session** | `GetStageSession`, `ListStageSessions` |
| **Participant** | `CreateParticipantToken`, `GetParticipant`, `ListParticipants`, `ListParticipantEvents`, `DisconnectParticipant` |
| **Composition** | `StartComposition`, `StopComposition`, `GetComposition`, `ListCompositions` |
| **Encoder Configuration** | `CreateEncoderConfiguration`, `GetEncoderConfiguration`, `DeleteEncoderConfiguration`, `ListEncoderConfigurations` |
| **Storage Configuration** | `CreateStorageConfiguration`, `GetStorageConfiguration`, `DeleteStorageConfiguration`, `ListStorageConfigurations` |
| **Public Key** | `ImportPublicKey`, `GetPublicKey`, `DeletePublicKey`, `ListPublicKeys` |
| **Tags** | `TagResource`, `UntagResource`, `ListTagsForResource` |

**Key operations:**

- `CreateStage` - Creates a new stage. Optionally accepts `participantTokenConfigurations` to generate tokens at creation time (each with `userId`, `attributes`, `capabilities` [PUBLISH, SUBSCRIBE], and `duration`). Also accepts `autoParticipantRecordingConfiguration` to enable automatic participant recording.
- `CreateParticipantToken` - Generates a participant token for joining a stage. Parameters: `stageArn`, `userId`, `attributes` (key-value metadata attached to the participant), `capabilities` (PUBLISH and/or SUBSCRIBE), `duration` (1-720 minutes, default 720). Tokens are single-use.
- `DisconnectParticipant` - Forcefully disconnects a participant from a stage. Used for moderation. Parameters: `stageArn`, `participantId`, `reason` (optional).
- `StartComposition` - Starts server-side composition of a stage. Parameters: `stageArn`, `layout` (grid or pip configuration), `destinations` (channel and/or S3), `idempotencyToken`. See Section 6.7 for layout options.
- `StopComposition` - Stops a running composition.
- `CreateEncoderConfiguration` - Defines output encoding (resolution, bitrate, framerate). Default: 1280x720, 2500kbps, 30fps.
- `CreateStorageConfiguration` - Defines an S3 bucket destination for composition recordings or participant recordings.
- `ImportPublicKey` - Imports an RSA public key for custom participant token signing.

#### Chat API (Control Plane)

| Resource | Operations |
|---|---|
| **Room** | `CreateRoom`, `GetRoom`, `UpdateRoom`, `DeleteRoom`, `ListRooms` |
| **Chat Token** | `CreateChatToken` |
| **Moderation** | `DeleteMessage`, `DisconnectUser`, `SendEvent` |
| **Logging Configuration** | `CreateLoggingConfiguration`, `GetLoggingConfiguration`, `UpdateLoggingConfiguration`, `DeleteLoggingConfiguration`, `ListLoggingConfigurations` |
| **Tags** | `TagResource`, `UntagResource`, `ListTagsForResource` |

**Key operations:**

- `CreateRoom` - Creates a chat room. Parameters: `name`, `maximumMessageLength` (1-500 characters, default 500), `maximumMessageRatePerSecond` (1-100, default 10), `messageReviewHandler` (Lambda ARN + `fallbackResult`), `loggingConfigurationIdentifiers`, and `tags`.
- `CreateChatToken` - Generates a token for a user to connect to a room via WebSocket. Parameters: `roomIdentifier` (room ARN), `userId`, `capabilities` (SEND_MESSAGE, DISCONNECT_USER, DELETE_MESSAGE), `attributes` (user metadata like display name, avatar), `sessionDurationInMinutes` (1-180, default 60). Tokens are single-use.
- `DeleteMessage` - Deletes a message from a room (server-side). Parameters: `roomIdentifier`, `id` (message ID), `reason` (optional).
- `DisconnectUser` - Disconnects a user from a room (server-side). Parameters: `roomIdentifier`, `userId`, `reason` (optional). Use with token creation flow to implement bans.
- `SendEvent` - Sends an application-defined event to a room. Parameters: `roomIdentifier`, `eventName`, `attributes` (custom data). Used for custom notifications like polls, alerts, or system messages.
- `UpdateRoom` - Updates room settings including message length limits, rate limits, moderation handler, and logging configuration.

#### Chat Messaging API (Data Plane - WebSocket)

Clients connect to chat rooms via WebSocket using tokens from `CreateChatToken`. The WebSocket endpoint is region-specific: `wss://edge.ivschat.{region}.amazonaws.com`. Token is passed via the `sec-websocket-protocol` header during the WebSocket handshake.

**Publish operations (client -> server):**
- `SEND_MESSAGE` - Sends a message (requires `SEND_MESSAGE` capability). Fields: `Content` (message text), `Attributes` (optional metadata), `RequestId` (optional tracking ID).
- `DELETE_MESSAGE` - Instructs clients to delete a message (requires `DELETE_MESSAGE` capability). Fields: `Id` (message ID), `Reason` (optional).
- `DISCONNECT_USER` - Disconnects another user (requires `DISCONNECT_USER` capability). Fields: `UserId`, `Reason` (optional).

**Subscribe operations (server -> client):**
- `MESSAGE` - Received when another user sends a message. Includes `Content`, `Sender` (with `UserId` and `Attributes`), `SendTime`, `Id`.
- `EVENT` - Received for system or application events (e.g., `aws:DELETE_MESSAGE`, custom events from `SendEvent`). Includes `EventName`, `Attributes`, `SendTime`.

**WebSocket error codes:** 400 (bad request), 401 (token expired), 403 (insufficient capabilities), 404 (room not found), 406 (rejected by review handler), 413 (payload too large), 429 (rate limited), 500 (server error).

### 6.20 Private Ingest (VPC Endpoints)

**Product:** Low-Latency Streaming, Real-Time Streaming

Private ingest allows publishing streams to IVS from within an Amazon VPC or from Direct Connect without traversing the public internet. This uses AWS PrivateLink interface VPC endpoints.

**Use cases:** Enterprise environments requiring all traffic to remain on the AWS network, EC2-based encoding workflows, regulated industries with strict network egress policies.

**Setup:**
1. Create an interface VPC endpoint for the IVS ingest service in your VPC
2. Launch an EC2 instance (or use existing resources) in the same VPC
3. Configure FFmpeg or other encoder software on the EC2 instance to stream to the VPC endpoint's DNS name
4. IVS receives the stream through the private endpoint - no public internet traversal

The VPC endpoint supports both RTMPS and SRT ingest for Low-Latency channels. For Real-Time stages, RTMP(S) and E-RTMP(S) are supported via the same VPC endpoint mechanism.

### 6.21 Stream Takeover

**Product:** Low-Latency Streaming

Stream takeover allows a new ingest connection to take over an active stream on a channel by appending a priority number to the stream key. Higher priority values take precedence over lower ones.

**How it works:**
- Append `?priority=N` (integer, 1-2,147,483,647) to the stream key when connecting
- If the new connection's priority is higher than the current stream's priority, the new stream takes over
- The previous stream is disconnected with a `Stream Takeover` EventBridge event

**Use cases:** Redundant encoder failover, switching between broadcast sources without stream interruption, mobile-to-desktop handoff.

**Note:** The IVS mobile Broadcast SDK's auto-reconnect feature uses stream takeover internally - it appends an incrementing priority number on each reconnect attempt. Do not use manual stream takeover from another device while auto-reconnect is enabled on the SDK, as priority conflicts may occur.

**EventBridge events:** `Stream Takeover` (successful takeover) and `Stream Takeover Failure` (rejected - possible reasons: `StreamTakeoverMediaMismatch`, `StreamTakeoverInvalidPriority`, `StreamTakeoverLimitBreached`).

### 6.22 Undesired Content and Viewers

**Product:** Low-Latency Streaming

Tools for detecting and preventing re-streaming of undesired content (e.g., pirated sports) and unauthorized embedding.

**Detection:**
- **Anomaly detection:** Monitor EventBridge `Concurrent Viewers` limit breach events and CloudWatch `ConcurrentViews` metrics for unusual spikes that may indicate unauthorized re-streaming
- **Custom content moderation:** Use Record-to-S3 thumbnail generation combined with Amazon Rekognition (or similar ML services) to automatically analyze stream content. Trigger a Lambda function on thumbnail upload to S3, run content analysis, and call `StopStream` if violations are detected

**Prevention:**
- `StopStream` API - Forcefully stop a live stream and optionally reset the stream key
- **Private channels** - Require playback authorization tokens (see Section 6.4)
- **Playback restriction policies** - Restrict by country or origin domain (see Section 6.5)

### 6.23 AWS CloudTrail Integration

**Product:** All

IVS integrates with AWS CloudTrail to provide audit logging of all IVS API calls. Every control-plane API call (CreateChannel, CreateStage, CreateRoom, etc.) is recorded as a CloudTrail event with details including the caller identity, source IP, timestamp, request parameters, and response.

**Use cases:** Security auditing, compliance, tracking resource changes, troubleshooting API permission issues.

CloudTrail logging is automatic - no additional IVS configuration is required. Events appear in the CloudTrail console and can be delivered to S3, CloudWatch Logs, or EventBridge for analysis.

### 6.24 Participant Replication

**Product:** Real-Time Streaming

Participant replication allows a single participant's streams to be replicated across multiple stages simultaneously, without requiring the participant to join each stage individually.

**Use cases:** Broadcasting a speaker to multiple breakout rooms, scaling a host's presence across parallel sessions, cross-stage content sharing.

**How it works:**
1. The participant joins and publishes to a source stage
2. Call the `StartParticipantReplication` API specifying the source stage, participant, and destination stage(s)
3. The participant's streams appear in the destination stages as remote participants
4. Call `StopParticipantReplication` to end replication

**Key details:**
- Replication is managed server-side - the participant's client does not need to be aware of replication
- Replicated participants appear as regular remote participants to subscribers in the destination stage
- Subject to concurrent participant replication quotas

### 6.25 Streaming Optimizations (Real-Time)

**Product:** Real-Time Streaming

**Simulcast / Adaptive Streaming:** The IVS Real-Time SDK supports simulcast, sending multiple quality layers simultaneously. Subscribers automatically receive the best quality layer for their bandwidth. Simulcast is enabled by default on the Web SDK and can be configured on mobile SDKs.

**Jitter Buffer MinDelay:** Configure the jitter buffer minimum delay for subscribers to balance latency versus smoothness. Lower values reduce latency but may increase visual artifacts on lossy networks; higher values smooth out jitter at the cost of added delay.

Available presets: `LOW`, `MEDIUM` (default), `LONG`

**Web:**
```js
const strategy = {
  subscribeConfiguration: (participant) => ({
    jitterBuffer: { minDelay: JitterBufferMinDelay.MEDIUM }
  }),
  // ...other strategy methods
};
```

**iOS:**
```swift
try! config.jitterBuffer.setMinDelay(.medium())
```

**Android:**
```kotlin
config.jitterBuffer.setMinDelay(JitterBufferConfiguration.JitterBufferDelay.MEDIUM())
```

**Other optimizations:**
- **Video bitrate:** Adjust per-participant video bitrate via `maxBitrate` in the streaming configuration
- **Framerate:** Configure target framerate (15-60 fps) based on use case (lower for screen share, higher for action)
- **Audio bitrate and stereo:** Configure audio bitrate and enable stereo output for music streaming use cases (see Mobile Audio Modes in Section 4)

---

## 7. Streaming with OBS

### Low-Latency Streaming (RTMP)

Works with any OBS version.

1. Open OBS -> **Settings** -> **Stream**
2. Set **Service** to `Amazon IVS` (or `Custom...`). If you select `Amazon IVS`, OBS auto-populates the server URL - just paste your stream key.
3. If using `Custom...`, set **Server** to your channel's ingest endpoint:
   ```
   rtmps://INGEST_ENDPOINT:443/app/
   ```
4. Set **Stream Key** to your channel's stream key
5. Go to **Output** -> **Output Mode: Advanced** -> **Streaming** tab:
   - **Encoder:** x264
   - **Rate Control:** CBR
   - **Bitrate:** Match your channel type (1500 for Basic, up to 8500 for Standard/Advanced)
   - **Keyframe Interval:** 2 seconds (recommended for lowest latency)
   - **CPU Usage Preset:** veryfast or faster
   - **Profile:** main or high
   - **Tune:** zerolatency
   - **B-frames:** 0 (set to 0 to avoid ingest issues)
6. Click **Start Streaming**

### Low-Latency Streaming (SRT)

Works with OBS and any SRT-compatible encoder.

1. Open OBS -> **Settings** -> **Stream**
2. Set **Service** to `Custom...`
3. Set **Server** to:
   ```
   srt://ENDPOINT.srt.live-video.net:9000?streamid=STREAM_KEY&passphrase=PASSPHRASE
   ```
4. Leave **Stream Key** empty
5. Configure output settings same as RTMP above
6. Click **Start Streaming**

### Real-Time Streaming (WHIP)

Requires OBS version 30 or later.

1. Open OBS -> **Settings** -> **Stream**
2. Set **Service** to `WHIP`
3. Set **Server** to:
   ```
   https://global.whip.live-video.net
   ```
4. Set **Bearer Token** to a valid stage participant token
5. OBS will prompt about Opus audio encoding - select **Yes**
6. Go to **Output** -> **Output Mode: Advanced** -> **Streaming** tab:
   - **Bitrate:** <= 2500 Kbps
   - **Keyframe Interval:** 1s or 2s
   - **CPU Usage Preset:** ultrafast
   - **Tune:** zerolatency
7. Click **Start Streaming**

**Note:** Stage participant tokens have a default expiration period (configurable up to a maximum duration).

---

## 8. Streaming with FFmpeg

### Low-Latency (RTMPS)

```bash
ffmpeg -re -i input.mp4 \
  -c:v libx264 -preset veryfast -tune zerolatency \
  -profile:v main -b:v 3500k -maxrate 3500k -bufsize 7000k \
  -g 60 -keyint_min 60 \
  -c:a aac -b:a 128k -ar 44100 -ac 2 \
  -f flv "rtmps://INGEST_ENDPOINT:443/app/STREAM_KEY"
```

Key flags:
- `-g 60`: Keyframe interval (2 seconds at 30fps)
- `-preset veryfast`: Balanced speed/quality
- `-tune zerolatency`: Minimize encoding latency
- `-b:v 3500k`: Constant bitrate matching channel type

### Real-Time (WHIP) - FFmpeg 8.0+

**Build FFmpeg with WHIP support:**
```bash
cd ~/git
git clone -b master https://github.com/ossrs/ffmpeg-webrtc.git
cd ffmpeg-webrtc
./configure --enable-muxer=whip --enable-openssl --enable-version3 \
    --enable-libx264 --enable-gpl --enable-libopus
make -j
```

**Prerequisites:** nasm, libx264, libopus, OpenSSL 3+

**Publish to a stage:**
```bash
ffmpeg -re -f lavfi -i testsrc=size=1280x720 -f lavfi -i sine=frequency=440 \
  -pix_fmt yuv420p \
  -vcodec libx264 -profile:v baseline -r 25 -g 50 \
  -acodec libopus -ar 48000 -ac 2 \
  -f whip -authorization "PUBLISHER_PARTICIPANT_TOKEN" \
  "https://global.whip.live-video.net"
```

**From a file:**
```bash
ffmpeg -re -i input.mp4 \
  -vcodec libx264 -profile:v baseline -r 25 -g 50 -b:v 2500k \
  -acodec libopus -ar 48000 -ac 2 -b:a 128k \
  -f whip -authorization "PUBLISHER_PARTICIPANT_TOKEN" \
  "https://global.whip.live-video.net"
```

### Multi-Track E-RTMP (ABR - Advanced Channels)

For advanced channel types, send multiple renditions:

```bash
ffmpeg -y -re -i input.mp4 \
  -filter_complex \
    "[0:v]scale=1280:720:force_original_aspect_ratio=decrease:force_divisible_by=2[v0]; \
     [0:v]scale=640:360:force_original_aspect_ratio=decrease:force_divisible_by=2[v1]; \
     [0:v]scale=286:160:force_original_aspect_ratio=decrease:force_divisible_by=2[v2]" \
  -map "[v0]" -map "[v1]" -map "[v2]" -map 0:a \
  -c:v libx264 -bf 0 -x264opts no-scenecut=1 -g 60 -profile:v baseline \
  -b:v:0 3500k -maxrate:v:0 3500k -bufsize:v:0 3500k \
  -b:v:1 1200k -maxrate:v:1 1200k -bufsize:v:1 1200k \
  -b:v:2 300k  -maxrate:v:2 300k  -bufsize:v:2 300k \
  -c:a aac -b:a 128k \
  -f flv "rtmps://INGEST_ENDPOINT:443/app/STREAM_KEY"
```

### GStreamer WHIP Pipeline (Real-Time)

```bash
gst-launch-1.0 \
  avfvideosrc device-index=0 \
  ! videoconvert \
  ! x264enc tune=zerolatency bitrate=2500 speed-preset=ultrafast \
  ! rtph264pay \
  ! 'application/x-rtp,media=video,encoding-name=H264,payload=97,clock-rate=90000' \
  ! whip.sink_0 autoaudiosrc wave=4 \
  ! audioconvert \
  ! opusenc \
  ! rtpopuspay \
  ! 'application/x-rtp,media=audio,encoding-name=OPUS,payload=96,clock-rate=48000' \
  ! whip.sink_1 \
  whipsink \
  name=whip \
  auth-token=$IVS_STAGE_TOKEN \
  whip-endpoint=https://global.whip.live-video.net/
```

---

## 9. Common Issues and Resolutions

### Playback Issues

| Problem | Cause | Resolution |
|---|---|---|
| **15-20s latency on iOS Safari** | No service worker configured | Use IVS Player SDK with service worker. Reduces latency to 6-8s. See [Web Guide](https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/web-getting-started.html#web-service-worker). Set keyframe interval to 2s. |
| **Buffering / rebuffering** | Viewer's poor network or high bitrate | Enable `setRebufferToLive(true)`. Use a 2s keyframe interval. Verify viewer bandwidth supports stream bitrate. ABR will auto-adjust quality. |
| **"No playable format found" error** | Browser doesn't support HLS | Use the IVS Player SDK which handles HLS natively. Check `IVSPlayer.isPlayerSupported` before creating player. |
| **Player doesn't restart when stream goes live** | IVS Player has no auto-play-on-live feature | Use EventBridge `Stream Start` events to trigger player.load() + play() in your application. |
| **404 on playback URL** | Channel is not currently live | Playback URL only works while channel is streaming. Use the `GetStream` API or EventBridge to check stream status. |
| **Audio muted on autoplay (Chrome)** | Chrome autoplay policy | Chrome blocks autoplay with sound. Call `player.setMuted(true)` initially, then unmute on user interaction. Twitch is whitelisted by Chrome, but IVS is not. |
| **Stream startup takes 8-10s** | Normal IVS pipeline processing | Expected: IVS probes 2 IDR frames (min 4s) + transcoding + distribution. Use 1-2s keyframe interval. Consider adding a countdown overlay. |

### Ingest / Broadcast Issues

| Problem | Cause | Resolution |
|---|---|---|
| **Bitrate fluctuates wildly** | VBR encoding | Switch to CBR. In OBS: Settings -> Output -> Rate Control -> CBR. |
| **Stream starvation events** | Insufficient upload bandwidth or poor network | Monitor via EventBridge starvation events. Use CBR encoding. Enable OBS "Dynamically change bitrate to manage congestion" (Settings -> Advanced -> Network). |
| **B-frame related issues** | B-frames can cause ingest problems | Set B-frames to 0 in encoder settings. In x264: `-bf 0`. |
| **Stream disconnects on WiFi->LTE** | IP address changes break RTMPS connection | IVS waits ~30s for reconnection. When network switches, the old connection dies. Call `StopStream` API when detecting network switch, then reconnect. |
| **Only one BroadcastSession** | SDK limitation | Only one BroadcastSession instance is supported at a time. Multiple instances cause TCP connection issues. |
| **Recording stitching fails** | Bitrate fluctuates >10% | Use CBR encoding with stable bitrate to enable proper Record-to-S3 stitching. |
| **Web Broadcast SDK RAM usage** | Non-BASIC presets on Windows Chrome | Use BASIC_LANDSCAPE/BASIC_PORTRAIT presets, or disable hardware acceleration in Chrome. |

### Real-Time Issues

| Problem | Cause | Resolution |
|---|---|---|
| **Stage at capacity** | Publisher limit per stage reached | Architect your application to stay within service limits. Consider using server-side composition to forward to additional stages. |
| **Timed metadata not supported** | Real-Time does not support Timed Metadata | Timed Metadata is only available in Low-Latency streaming. For Real-Time, use participant `attributes` or data channels instead. |
| **Black screen in Safari** | Known WebRTC issue | See [known issues](https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/broadcast-web-known-issues.html). Try toggling video track or refreshing. |
| **Token expired** | Participant tokens have expiration | Tokens have a configurable expiration period. Generate fresh tokens server-side and have clients rejoin. Handle `TOKEN_EXPIRED` error. |

### Chat Issues

| Problem | Cause | Resolution |
|---|---|---|
| **fetchTokenError** | Token expired or connection lost mid-session | Generate tokens server-side with appropriate duration. Implement token refresh logic in your `tokenProvider`. |
| **Messages lost / silent denials** | Lambda cold start exceeds timeout limit | Use provisioned concurrency for the review handler Lambda. Set `FallbackResult` to `ALLOW` to prevent message loss during cold starts. |
| **WebSocket connection refused** | Firewall blocking or incorrect endpoint | Verify firewall allows WebSocket connections to `*.live-video.net` on TCP 443. |
| **Message length limit exceeded** | Character limit enforced | Use the `Attributes` field (JSON) to pass structured data alongside or instead of message content. |

### Network Issues

| Problem | Cause | Resolution |
|---|---|---|
| **Real-Time media fails** | UDP blocked | Real-Time requires UDP port 3478 to IVS_REALTIME IP ranges. If UDP is completely blocked, media will fail. TCP 443 is also used. |
| **Firewall configuration** | Dynamic IPs | Whitelist `*.live-video.net` for TCP 443. For Real-Time media, download [ip-ranges.json](https://ip-ranges.amazonaws.com/ip-ranges.json) and allow IVS_REALTIME service IPs on UDP 3478 and TCP 443. |
| **DNS resolution failures** | DNS misconfigured | Ensure DNS can resolve `*.live-video.net` domains. Use the diagnostics tool at https://diagnostics.ivs.rocks/ to verify network. |

### Mobile SDK Issues

| Problem | Cause | Resolution |
|---|---|---|
| **iOS audio device errors** | Multiple external audio devices connected | SDK intentionally limits to avoid iOS crash. Provide users option to select audio device. Use `detachDevice()` when muting to clear audio indicator. |
| **Android Camera2 crashes** | `getPreviewView()` called when stream not LIVE | Guard calls with state checks. Ensure stream is in LIVE state before accessing preview. |
| **iOS background crash** | Broadcast SDK not designed for background | iOS terminates background video processing. Use a Broadcast Upload Extension for background streaming. |
| **VTCompressionSession error** | Hardware H264 encoder exhaustion | Update to latest SDK version. Reboot device to release hardware encoder resources. |
| **Battery drain** | High resolution + bitrate | Lower resolution to 360p, reduce bitrate to 750-800 kbps, set 30fps, disable B-frames, use 1s keyframe interval. |

---

## 10. Error Code Reference

Error codes from the IVS mobile Broadcast SDK (iOS and Android). These appear in the `code` field of broadcast errors.

### Net Errors (100-999)

| Code Range | Category | Description |
|---|---|---|
| 201-208 | Socket errors | Socket creation, connection, send/receive failures. Check network connectivity, firewall rules, file descriptor limits. |
| 212-214 | Socket config | RTT measurement not supported on platform (212), send low water mark (213), DNS resolution failure (214). |
| 300-301 | Socket tracking | Rate/congestion measurement failures (programming errors - zero duration). |
| 400-413 | TLS errors | TLS handshake, send, receive failures. Check SSL/TLS configuration, certificate validation, protocol version. Common causes: broken pipe, connection reset, cert validation failure. |
| 500 | Event loop | Timer creation failed - system resource exhaustion. |
| 600-627 | WebSocket handshake | Invalid upgrade response (600), invalid accept key (601), protocol negotiation failed (602), handshake timeout (603), invalid status code (604), invalid headers (605). |
| 620-627 | WebSocket frame | Invalid opcode (620), invalid length (621), invalid mask (622), control frame too large (623), invalid UTF-8 (624), reserved bits set (625), incomplete data (626), fragmented control frame (627). |
| 641-642 | WebSocket protocol | Message too large (641), connection not established (642). |
| 660-662 | WebSocket connection | Connection timeout (660), connection lost (661), close timeout (662). |

### RTMP Errors (3000-3999)

| Code Range | Category | Description |
|---|---|---|
| 3000-3007 | NetConnection | Unexpected command (3000), state errors (3001-3006), server error response (3007). Check RTMP handshake sequence and stream key validity. |
| 3100-3110 | NetStream | State errors for publish/unpublish/write operations (must be in correct state). Metadata too large - 10KB limit (3110). |
| 3200-3215 | RTMP Protocol | State machine errors (3200-3201), unsupported version (3202), data doesn't look like RTMP (3203), chunk/control message format errors (3204-3215). |
| 3300 | AMF | AMF decode limit exceeded. |

### Broadcast Errors (20000-49999)

| Code Range | Category | Description |
|---|---|---|
| 20000-20004 | Audio | Not implemented (20000), audio mixer format/rate/channel mismatches (20001-20003), missing audio data (20004). |
| 20100-20105 | Session state | Not ready (20100), not streaming (20101), already streaming (20102), metadata too large (20103), endpoint too long (20104), invalid scheme (20105). |
| 20200-20220 | Pipeline | Sample filter rejected (20200), bus/component receiver gone (20210, 20220). |

### Most Actionable Errors

| Code | Name | What to Do |
|---|---|---|
| 214 | CouldNotResolveAddress | Check DNS configuration and network connectivity. Common: hostname not found, temporary DNS failure. |
| 410 | TLSHandshakeFailedOpenSSLError | Check network allows TLS connections. Common: cert validation failure, protocol mismatch, firewall interference. |
| 603 | WebSocketHandshakeTimeout | Check network latency, server responsiveness, and firewall rules. |
| 661 | WebSocketConnectionLost | Socket closed unexpectedly. Check network stability, server logs. |
| 3007 | NetConnectionErrorReceivedFromServer | Server rejected connection. Verify stream key, ingest endpoint, and channel is not already streaming. |
| 3203 | DataDoesNotLookLikeRTMP | Wrong protocol/port. Ensure URL uses `rtmp://` or `rtmps://` scheme with correct port. |
| 20100 | BroadcastSessionNotReady | Call `setup()` and wait for Ready state before starting broadcast. |
| 20102 | BroadcastSessionAlreadyStreaming | Stop current broadcast before starting a new one. |

---

## 11. Additional Resources

### Documentation

- **Low-Latency User Guide:** https://docs.aws.amazon.com/ivs/latest/LowLatencyUserGuide/what-is.html
- **Real-Time User Guide:** https://docs.aws.amazon.com/ivs/latest/RealTimeUserGuide/what-is.html
- **Chat User Guide:** https://docs.aws.amazon.com/ivs/latest/ChatUserGuide/what-is.html
- **Low-Latency API Reference:** https://docs.aws.amazon.com/ivs/latest/APIReference/Welcome.html
- **Real-Time API Reference:** https://docs.aws.amazon.com/ivs/latest/RealTimeAPIReference/Welcome.html
- **Chat API Reference:** https://docs.aws.amazon.com/ivs/latest/ChatAPIReference/Welcome.html

### SDK CDN URLs & Packages

| SDK | CDN / Package |
|---|---|
| Player SDK (Web) | `https://player.live-video.net/1.48.0/amazon-ivs-player.min.js` |
| Broadcast SDK (Web) | `https://web-broadcast.live-video.net/1.31.0/amazon-ivs-web-broadcast.js` |
| Broadcast SDK (npm) | `npm install amazon-ivs-web-broadcast` |
| Chat SDK (npm) | `npm install amazon-ivs-chat-messaging` |
| Player SDK (iOS - CocoaPods) | `pod 'AmazonIVSPlayer'` |
| Player SDK (Android - Gradle) | `com.amazonaws:ivs-player:1.48.0` |
| Broadcast SDK (iOS - CocoaPods) | `pod 'AmazonIVSBroadcast'` |
| Broadcast SDK (Android - Gradle) | `com.amazonaws:ivs-broadcast:1.38.0` |

### GitHub Sample Repos

- **Web Broadcast Demo:** https://github.com/aws-samples/amazon-ivs-broadcast-web-demo
- **Real-Time Web Demo (CodePen Publisher):** https://codepen.io/amazon-ivs/pen/ZEqgrpo
- **Real-Time Web Demo (CodePen Subscriber):** https://codepen.io/amazon-ivs/pen/RNwVBRK
- **Low-Latency Web Broadcast (CodePen):** https://codepen.io/amazon-ivs/pen/poLRoPp
- **Stream Demo:** https://stream.ivs.rocks/

### Tools

- **Network Diagnostics Tool:** https://diagnostics.ivs.rocks/ - Tests network for Real-Time streaming compatibility
- **Player Debug Tool:** https://debug.ivsdemos.com - Debug IVS player issues
- **Live Test Page:** https://ivs.rocks/live - Test live playback

### Streaming Endpoints

| Purpose | Endpoint Pattern |
|---|---|
| Low-Latency RTMPS Ingest | `rtmps://INGEST_ENDPOINT:443/app/` |
| Low-Latency SRT Ingest | `srt://ENDPOINT.srt.live-video.net:9000` |
| Real-Time WHIP Ingest | `https://global.whip.live-video.net` |
| Playback (HLS) | `https://PLAYBACK_URL` (from channel creation) |
| Chat WebSocket | Handled by Chat SDK (connects to `*.live-video.net:443`) |

### Recommended Encoder Settings

| Setting | Low-Latency | Real-Time (WHIP) |
|---|---|---|
| Video Codec | H.264 (x264) | H.264 (x264) |
| Audio Codec | AAC | Opus |
| Keyframe Interval | 2 seconds | 1-2 seconds |
| Rate Control | CBR | CBR |
| CPU Preset | veryfast | ultrafast |
| Tune | zerolatency | zerolatency |
| B-frames | 0 | 0 |
| Profile | main / high | baseline |
| Max Resolution | 1080p | 720p |
| Max Bitrate | Varies by channel type | Service limit applies |

### AWS Free Tier

AWS offers a free tier for new customers. Free tier allowances and duration vary by product line. See the AWS Free Tier documentation for current offerings and eligibility requirements.
