Amazon IVS
"Amazon Interactive Video Service: low-latency live streaming, real-time stages, chat, stream recording, channel management, viewer analytics"
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 linesAmazon 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: trueon 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
STARVINGhealth 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 Endevent.
Install this skill directly: skilldb add video-services-skills
Related Skills
Api.video
"api.video: video hosting API, upload, live streaming, player customization, analytics, webhooks"
Bunny Stream
"Bunny.net Stream: video hosting, HLS delivery, direct uploads, video collections, thumbnail generation, token authentication, webhook events"
Cloudflare Stream
"Cloudflare Stream: video hosting, adaptive streaming, direct upload, watermarks, signed URLs, Workers integration"
LiveKit
"LiveKit: real-time video/audio, WebRTC, rooms, tracks, screen sharing, recording, React components"
Mux Video
"Mux: video hosting, HLS streaming, upload API, playback URLs, thumbnails, analytics, Mux Player, Next.js integration"
Vimeo OTT
"Vimeo OTT API: video-on-demand platform, subscription management, customer auth, content libraries, embed players, analytics, webhook events"