Skip to main content
Technology & EngineeringVideo Services301 lines

Amazon IVS

"Amazon Interactive Video Service: low-latency live streaming, real-time stages, chat, stream recording, channel management, viewer analytics"

Quick Summary30 lines
Amazon Interactive Video Service (IVS) is a managed live streaming service that makes it easy to create low-latency interactive video experiences. There are two core products: IVS Low-Latency Streaming for standard live broadcasts with sub-5-second latency, and IVS Real-Time Streaming (Stages) for multi-host interactive sessions with sub-300ms latency. IVS also provides a built-in Chat service for viewer interaction. The architecture follows an AWS-native pattern: you create channels via the AWS SDK, stream to RTMPS ingest endpoints using OBS or programmatic sources, and viewers consume via the IVS Player SDK. Design around channels as the core resource, use stream keys for authentication, and rely on EventBridge for stream lifecycle events rather than polling.

## Key Points

- Use EventBridge rules to react to stream lifecycle events instead of polling GetStream.
- Store the channel ARN and playback URL in your database; reconstruct ingest details from the channel resource.
- Rotate stream keys after each broadcast session or if a key is compromised.
- Enable auto-recording to S3 for VOD replay and compliance archival.
- Use timed metadata to build interactive overlays (polls, product links) synchronized with the live stream.
- Set `authorized: true` on channels for private streams, then generate signed playback tokens server-side.
- Do not poll GetStream in a loop to detect when a channel goes live; use EventBridge events.
- Do not expose stream key values to the client; they grant broadcast access to your channel.
- Do not use BASIC channel type for production streams with more than a handful of concurrent viewers.
- Do not ignore the `STARVING` health status; it indicates the broadcaster's connection is unstable and viewers may experience buffering.
- Do not embed AWS credentials in client-side code; use Cognito or a backend token endpoint for chat tokens.
- Do not assume recordings are instantly available after a stream ends; wait for the `Recording End` event.

## Quick Example

```
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=us-east-1
```

```html
<script src="https://player.live-video.net/1.27.0/amazon-ivs-player.min.js"></script>
```
skilldb get video-services-skills/Amazon IVSFull skill: 301 lines
Paste into your CLAUDE.md or agent config

Amazon IVS

Core Philosophy

Amazon Interactive Video Service (IVS) is a managed live streaming service that makes it easy to create low-latency interactive video experiences. There are two core products: IVS Low-Latency Streaming for standard live broadcasts with sub-5-second latency, and IVS Real-Time Streaming (Stages) for multi-host interactive sessions with sub-300ms latency. IVS also provides a built-in Chat service for viewer interaction. The architecture follows an AWS-native pattern: you create channels via the AWS SDK, stream to RTMPS ingest endpoints using OBS or programmatic sources, and viewers consume via the IVS Player SDK. Design around channels as the core resource, use stream keys for authentication, and rely on EventBridge for stream lifecycle events rather than polling.

Setup

Install the AWS SDK v3 IVS clients:

import { IvsClient, CreateChannelCommand, GetStreamCommand } from "@aws-sdk/client-ivs";
import { IvschatClient, CreateRoomCommand } from "@aws-sdk/client-ivschat";

const ivsClient = new IvsClient({
  region: process.env.AWS_REGION ?? "us-east-1",
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
});

const chatClient = new IvschatClient({
  region: process.env.AWS_REGION ?? "us-east-1",
});

Environment variables required:

AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=us-east-1

Key Techniques

Creating a Channel

import {
  CreateChannelCommand,
  type CreateChannelCommandOutput,
} from "@aws-sdk/client-ivs";

async function createChannel(name: string, enableRecording = false) {
  const input: any = {
    name,
    latencyMode: "LOW", // LOW or NORMAL
    type: "STANDARD", // STANDARD or BASIC
    authorized: false, // set true for private streams
  };

  if (enableRecording) {
    input.recordingConfigurationArn = process.env.IVS_RECORDING_CONFIG_ARN;
  }

  const command = new CreateChannelCommand(input);
  const response: CreateChannelCommandOutput = await ivsClient.send(command);

  return {
    channelArn: response.channel?.arn,
    ingestEndpoint: response.channel?.ingestEndpoint,
    playbackUrl: response.channel?.playbackUrl,
    streamKeyValue: response.streamKey?.value, // keep secret, used by broadcaster
  };
}

Managing Stream Keys

import {
  CreateStreamKeyCommand,
  ListStreamKeysCommand,
  DeleteStreamKeyCommand,
} from "@aws-sdk/client-ivs";

async function rotateStreamKey(channelArn: string) {
  // List existing keys
  const listCmd = new ListStreamKeysCommand({ channelArn });
  const existing = await ivsClient.send(listCmd);

  // Delete old keys
  for (const key of existing.streamKeys ?? []) {
    if (key.arn) {
      await ivsClient.send(new DeleteStreamKeyCommand({ arn: key.arn }));
    }
  }

  // Create new key
  const createCmd = new CreateStreamKeyCommand({ channelArn });
  const result = await ivsClient.send(createCmd);

  return result.streamKey?.value;
}

Checking Stream Status

import { GetStreamCommand } from "@aws-sdk/client-ivs";

interface StreamStatus {
  isLive: boolean;
  viewerCount?: number;
  health?: string;
  startTime?: Date;
}

async function getStreamStatus(channelArn: string): Promise<StreamStatus> {
  try {
    const command = new GetStreamCommand({ channelArn });
    const response = await ivsClient.send(command);

    return {
      isLive: response.stream?.state === "LIVE",
      viewerCount: response.stream?.viewerCount,
      health: response.stream?.health, // HEALTHY, STARVING, UNKNOWN
      startTime: response.stream?.startTime,
    };
  } catch (err: any) {
    if (err.name === "ChannelNotBroadcasting") {
      return { isLive: false };
    }
    throw err;
  }
}

IVS Player Integration (Web)

<script src="https://player.live-video.net/1.27.0/amazon-ivs-player.min.js"></script>
function createIvsPlayer(playbackUrl: string, videoElement: HTMLVideoElement) {
  if (!IVSPlayer.isPlayerSupported) {
    console.error("IVS Player not supported in this browser");
    return null;
  }

  const player = IVSPlayer.create();
  player.attachHTMLVideoElement(videoElement);

  player.addEventListener(IVSPlayer.PlayerState.PLAYING, () => {
    console.log("Stream is playing");
  });

  player.addEventListener(IVSPlayer.PlayerState.ENDED, () => {
    console.log("Stream ended");
  });

  player.addEventListener(IVSPlayer.PlayerEventType.ERROR, (err: any) => {
    console.error("Player error:", err);
  });

  // Access timed metadata sent by the broadcaster
  player.addEventListener(IVSPlayer.PlayerEventType.TEXT_METADATA_CUE, (cue: any) => {
    const metadata = JSON.parse(cue.text);
    handleTimedMetadata(metadata);
  });

  player.load(playbackUrl);
  player.play();

  return player;
}

IVS Chat Integration

import {
  CreateRoomCommand,
  CreateChatTokenCommand,
} from "@aws-sdk/client-ivschat";

async function createChatRoom(name: string) {
  const command = new CreateRoomCommand({
    name,
    maximumMessageLength: 500,
    maximumMessageRatePerSecond: 5,
  });
  const response = await chatClient.send(command);
  return response.arn;
}

// Generate a chat token for a specific user (call from your auth'd API)
async function createChatToken(roomArn: string, userId: string, displayName: string) {
  const command = new CreateChatTokenCommand({
    roomIdentifier: roomArn,
    userId,
    attributes: { displayName },
    capabilities: ["SEND_MESSAGE"], // or ["SEND_MESSAGE", "DELETE_MESSAGE", "DISCONNECT_USER"]
    sessionDurationInMinutes: 180,
  });
  const response = await chatClient.send(command);

  return {
    token: response.token,
    sessionExpirationTime: response.sessionExpirationTime,
    tokenExpirationTime: response.tokenExpirationTime,
  };
}

EventBridge Stream Events

// Lambda handler for IVS events via EventBridge
export async function handler(event: {
  source: string;
  "detail-type": string;
  detail: {
    channel_name: string;
    stream_id: string;
    event_name: string;
  };
}) {
  if (event.source !== "aws.ivs") return;

  const { channel_name, stream_id, event_name } = event.detail;

  switch (event_name) {
    case "Stream Start":
      await db.channel.update({
        where: { name: channel_name },
        data: { isLive: true, currentStreamId: stream_id },
      });
      await notifyFollowers(channel_name, "is now live!");
      break;

    case "Stream End":
      await db.channel.update({
        where: { name: channel_name },
        data: { isLive: false, currentStreamId: null },
      });
      break;

    case "Recording Start":
      console.log(`Recording started for ${stream_id}`);
      break;

    case "Recording End":
      // Recording is available in your S3 bucket
      await db.recording.create({
        data: { streamId: stream_id, channelName: channel_name, status: "available" },
      });
      break;
  }
}

Timed Metadata (Interactive Features)

import { PutMetadataCommand } from "@aws-sdk/client-ivs";

// Send timed metadata to all viewers (polls, quizzes, product links)
async function sendTimedMetadata(channelArn: string, payload: Record<string, unknown>) {
  const command = new PutMetadataCommand({
    channelArn,
    metadata: JSON.stringify(payload),
  });
  await ivsClient.send(command);
}

// Example: trigger a poll overlay for all viewers
await sendTimedMetadata(channelArn, {
  type: "poll",
  question: "Which feature should we build next?",
  options: ["Dark mode", "Mobile app", "API v2"],
  duration: 30,
});

Best Practices

  • Use EventBridge rules to react to stream lifecycle events instead of polling GetStream.
  • Store the channel ARN and playback URL in your database; reconstruct ingest details from the channel resource.
  • Rotate stream keys after each broadcast session or if a key is compromised.
  • Enable auto-recording to S3 for VOD replay and compliance archival.
  • Use timed metadata to build interactive overlays (polls, product links) synchronized with the live stream.
  • Set authorized: true on channels for private streams, then generate signed playback tokens server-side.

Anti-Patterns

  • Do not poll GetStream in a loop to detect when a channel goes live; use EventBridge events.
  • Do not expose stream key values to the client; they grant broadcast access to your channel.
  • Do not use BASIC channel type for production streams with more than a handful of concurrent viewers.
  • Do not ignore the STARVING health status; it indicates the broadcaster's connection is unstable and viewers may experience buffering.
  • Do not embed AWS credentials in client-side code; use Cognito or a backend token endpoint for chat tokens.
  • Do not assume recordings are instantly available after a stream ends; wait for the Recording End event.

Install this skill directly: skilldb add video-services-skills

Get CLI access →