This document is an online companion to the AWS Cloud Cards deck for Amazon Interactive Video Service (Amazon IVS).
- 👋 Welcome To Cloud Cards!
- 🤘 IVS Rocks!
- ⭐️ Live Streaming Made Easy
- 💪 Save It For Later (Low-Latency)
- 💪 Change The Channel
- 💪 Members Only
- 💪 Flexible Ingest
- 💪 Equal Access
- 💪 What Time Is It?
- 💪 No Pirates!
- 💪 No Interruptions
- ⭐️ Welcome To The Stage!
- 💪 Extended Reach (Part 1)
- 💪 Extended Reach (Part 2)
- 💪 Save It For Later (Real-Time)
- 💪 WHIP It Up!
- 💪 Perfect Timing
- 💪 Alternative Ingest
- 💪 Real Time, Real Quality
- ⭐️ Hey Chat!
- 💪 Watch What You Say!
- 💪 Remember What They Said
- ☁️ Amazon CloudWatch
- ☁️ Amazon EventBridge
- ☁️ AWS CloudTrail
- ☁️ Amazon S3
- ☁️ AWS AppSync
- 🧑🏽💻 Watch Anywhere
- 🧑🏽💻 Streaming On The Go
- 💡 Let Them Cook
- 💡 Place Your Bets
- 💡 Learning Time
- 💡 Can You Hear Me?
- 💡 Shop Until You Drop
- 💡 Social Streaming
Code Samples
$ aws ivs create-channel --name low-latency-demo
import { IvsClient, CreateChannelCommand }
from '@aws-sdk/client-ivs';
const client = new IvsClient();
const createChannelInput = {
'name': 'low-latency-demo',
'type': 'STANDARD',
}
const command = new CreateChannelCommand(createChannelInput);
const response = await client.send(command);
# create S3 bucket
$ aws s3 mb s3://ivs-demo-recording-bucket
# create recording config
$ aws ivs create-recording-configuration \
--name demo-config \
--destination-configuration '{
"s3": {
"bucketName": "ivs-demo-recording-bucket"
}
}' \
--thumbnail-configuration '{
"recordingMode": "INTERVAL",
"targetIntervalSeconds": 30,
"storage": ["LATEST"],
}'
#create channel, associate new recording config
$ aws ivs create-channel \
--name recorded-channel-demo \
--recording-configuration-arn [RECORDING_CONFIG_ARN]
$ aws ivs create-channel \
--name low-latency-demo \
--type STANDARD
$ openssl ecparam -name secp384r1 -genkey -noout -out priv.pem
$ openssl ec -in priv.pem -pubout -out public.pem
$ aws ivs import-playback-key-pair \
--public-key-material "`cat public.pem`"
$ aws ivs create-channel --authorized
$ ffmpeg -re -stream_loop -1 \
-i $VIDEO_FILEPATH -r 30 \
-c:v libx264 -pix_fmt yuv420p \
-profile:v main -preset veryfast \
-x264opts "nal-hrd=cbr:no-scenecut" \
-minrate 3000 -maxrate 3000 -g 60 \
-c:a aac -b:a 160k -ac 2 -ar 44100 \
-f flv rtmps://$INGEST_ENDPOINT:443/app/$STREAM_KEY
$ export URI="srt://$INGEST_ENDPOINT:9000"
$ URI="$URI?streamid=$STREAM_KEY"
$ URI="$URI&passphrase=$PASSPHRASE"
$ ffmpeg -re -i $VIDEO_FILEPATH -c copy -f mpegts $URI
$ git clone https://github.com/aws-samples/\
amazon-ivs-webgpu-captions-demo.git
# initialize the infrastructure
$ npm run deploy:init
# deploy the backend stack
$ npm run deploy:backend
# run the client app
$ npm ci
$ npm run dev
# deploy the client app (optional)
$ npm run deploy:website
$ aws ivs put-metadata \
--channel-arn "[CHANNEL_ARN]" \
--metadata "test metadata"
const videoEl = document.getElementById('video-player');
const streamUrl = '[PLAYBACK_URL]';
const ivsPlayer = IVSPlayer.create();
ivsPlayer.attachHTMLVideoElement(videoEl);
ivsPlayer.load(streamUrl);
ivsPlayer.play();
const evt = IVSPlayer.PlayerEventType.TEXT_METADATA_CUE;
ivsPlayer.addEventListener(evt, (metadata) => {
console.log(metadata);
})
$ aws ivs create-playback-restriction-policy \
--name demo-policy \
--enable-strict-origin-enforcement \
--allowed-countries "US", "JP" \
--allowed-origins "https://example.com"
# add policy to channel
$ aws ivs update-channel \
--arn [CHANNEL_ARN] \
--playback-restriction-policy-arn [POLICY_ARN]
$ aws ivs-realtime create-stage --name real-time-demo
$ aws ivs-realtime create-stage --name real-time-demo \
--participant-token-configurations '[{"userId": "1"}]'
import { IvsRealTimeClient, CreateStageCommand }
from "@aws-sdk-client-ivs-realtime";
const client = new IvsRealTimeClient();
const input = {
name: 'real-time-demo',
participantTokenConfigurations: [
{
userId: "1",
}
]
};
const command = new CreateStageCommand(input);
const response = await client.send(command);
$ aws ivs-realtime create-encoder-configuration \
--name demo-encoder-configuration \
--video "bitrate=25000000,height=720,width=1280,framerate=30"
$ aws ivs-realtime start-composition \
--stage-arn [REAL_TIME_STAGE_ARN] \
--destination '[
{
"channel": {
"channelArn": "[LOW_LATENCY_CHANNEL_ARN]",
"encoderConfigurationArn": "[ENCODER_CONFIG_ARN]"
}
}
]'
$ aws ivs-realtime create-storage-configuration \
--name demo-storage-config \
--s3 "bucket=demo-recording-bucket"
$ aws ivs-realtime create-encoder-configuration \
--name demo-encoder-configuration \
--video "bitrate=6000000,height=1080,width=1920,framerate=60"
$ aws ivs-realtime start-composition \
--stage-arn "[STAGE_ARN]" \
--destination '[{
"s3": {
"encoderConfigurationArn": ["[ENCODER_CONFIG_ARN]"],
"storageConfigurationArn": "[STORAGE_CONFIG_ARN]"
}
}]
'
const config = {
inBandMessaging: { enabled: true }
};
const stream = new LocalStageStream(videoTrack, config);
const payload = new TextEncoder().encode('test').buffer;
stream.insertSeiMessage(payload);
const strategy = {
subscribeConfiguration: (participant) => {
return { inBandMessaging: { enabled: true } };
}
// ... other strategy functions
}
stage.on(StageEvents.STAGE_STREAM_SEI_MESSAGE_RECEIVED,
(participant, seiMessage) => {
console.log(seiMessage.payload, seiMessage.uuid);
});
{"timestamp":"...","event":"card_dealt","seat":2,"card":"7H","face_up":true}
{"timestamp":"...","event":"card_dealt","seat":4,"card":"6S","face_up":true}
{"timestamp":"...","event":"card_dealt","card":"3S","face_up":true}
{"timestamp":"...","event":"card_dealt","seat":2,"card":"5H","face_up":true}
{"timestamp":"...","event":"card_dealt","seat":4,"card":"10H","face_up":true}
{"timestamp":"...","event":"card_dealt","card":"4S","face_up":false}
{"timestamp":"...","event":"prompt_player","seat":4}
{"timestamp":"...","event":"player_action","seat":4,"action":"hit"}
{"timestamp":"...","event":"card_dealt","seat":4,"card":"4S","face_up":false}
{"timestamp":"...","event":"prompt_player","seat":4}
{"timestamp":"...","event":"player_action","seat":4,"action":"stand"}
{"timestamp":"...","event":"prompt_player","seat":2}
{"timestamp":"...","event":"player_action","seat":2,"action":"hit"}
{"timestamp":"...","event":"card_dealt","seat":2,"card":"JS","face_up":false}
{"timestamp":"...","event":"prompt_player","seat":2}
{"timestamp":"...","event":"player_action","seat":2,"action":"stay"}
{"timestamp":"...","event":"dealer_reveal","card":"4S"}
$ aws ivs-realtime create-ingest-configuration \
--name demo-ingest-config \
--stage-arn "[STAGE_ARN]" \
--ingest-protocol RTMPS
let cameraStream = new LocalStageStream(
cameraDevice,
{
simulcast: { enabled: true }
}
);
const initialLayerPreference: InitialLayerPreference.LOWEST_QUALITY;
const strategy = {
subscribeConfiguration: (participant) => {
return {
simulcast: {
initialLayerPreference
}
}
},
preferredLayerForStream: (participant, stream) => {
return stream.getLowestQualityLayer();
}
// ... other strategy functions
}
$ aws ivschat create-room --name chat
$ aws ivschat create-chat-token \
--room-identifier "[CHAT_ARN]" \
--user-id "1" \
--capabilities "SEND_MESSAGE"
const token = "[CHAT_TOKEN]";
const endpoint = "[CHAT_ENDPOINT]";
const connection = new WebSocket(endpoint, token);
const payload = {
"Action": "SEND_MESSAGE",
"Content": "text message",
}
connection.send(JSON.stringify(payload));
connection.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log(message);
}
{
"Content": "string",
"MessageId": "string",
"RoomArn": "string",
"Attributes": {"string": "string"},
"Sender": {
"Attributes": { "string": "string" },
"UserId": "string",
"Ip": "string"
}
}
{
"Content": "string",
"ReviewResult": "string",
"Attributes": {"string": "string"},
}
"Attributes": { "Reason": "denied for moderation" }
$ aws ivschat create-room \
--name demo-chat \
--message-review-handler '
{
"fallbackResult": "DENY",
"uri": "[LAMBDA_ARN]",
}
'
$ aws logs create-log-group --log-group-name demo-chat-cw-logs
$ aws ivschat create-logging-configuration \
--name demo-chat-log \
--destination-configuration '
{
"cloudWatchLogs": {
"logGroupName" : "demo-chat-cw-logs"
}
}
'
$ aws ivschat create-room \
--name demo-chat \
--logging-configuration-identifiers "[LOGGING_CONFIG_ARN]"
$ aws s3 mb s3://demo-chat-s3-logs
$ aws ivschat create-logging-configuration \
--name demo-chat-log \
--destination-configuration '
{
"s3": {
"bucketName" : "demo-chat-s3-logs"
}
}
'
# create firehose delivery stream first...
$ aws ivschat create-logging-configuration \
--name demo-chat-log \
--destination-configuration '
{
"firehose": {
"deliveryStreamName" : "demo-chat-firehose-stream"
}
}
'
export const handler = async (event) => {
console.log(`
Received: '${event["detail-type"]}'
named '${event.detail.event_name}'
at ${event.time}
on channel ${event.detail.channel_name}
with stream id ${event.detail.stream_id}.
`);
}
# create rule
$ aws events put-rule \
--name demo-ivs-event \
--event-pattern "{\"source\": [\"aws.ivs\"]}" \
--state ENABLED
# add permission to invoke lambda
$ aws lambda add-permission \
--function-name ivs-demo-function \
--statement-id EventBridgeInvoke \
--action lambda:InvokeFunction \
--principal events.amazonaws.com \
--source-arn [RULE_ARN]
# add lambda function as target for rule
$ aws events put-targets --rule demo-ivs-rule \
--targets '{"Id": "1", "Arn": "[LAMBDA_ARN]"}'
$ aws appsync create-api \
--name demo-event-api \
--event-config '
{
"authProviders": [{"authType": "API_KEY"}],
"connectionAuthModes": [{"authType": "API_KEY"}],
"defaultPublishAuthModes": [{"authType": "API_KEY"}],
"defaultSubscribeAuthModes": [{"authType": "API_KEY"}]
}'
$ aws appsync create-api-key --api-id [API_ID]
$ aws appsync create-channel-namespace \
--name default-api-ns \
--api-id [API_ID]
const videoEl = document.getElementById('video-player');
const streamUrl = '[PLAYBACK_URL]';
const ivsPlayer = IVSPlayer.create();
ivsPlayer.attachHTMLVideoElement(videoEl);
ivsPlayer.load(streamUrl);
ivsPlayer.play();
<canvas id="preview"></canvas>
<script type="module">
const streamKey = '[STREAM_KEY]';
const ingestEndpoint = '[INGEST_ENDPOINT]';
const streamConfig = IVSBroadcastClient.STANDARD_LANDSCAPE;
const config = { streamConfig, ingestEndpoint };
const client = IVSBroadcastClient.create(config);
await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
client.attachPreview(document.getElementById('preview'));
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(d => d.kind == 'videoinput');
const audioDevices = devices.filter(d => d.kind == 'audioinput');
const cameraStream =
await navigator.mediaDevices.getUserMedia({
video: {
deviceId: videoDevices[0].deviceId,
aspectRatio: 16 / 9,
},
});
const microphoneStream =
await navigator.mediaDevices.getUserMedia({
audio: {
deviceId: audioDevices[0].deviceId
},
});
client.addVideoInputDevice(cameraStream, 'camera1', { index: 0 });
client.addAudioInputDevice(microphoneStream, 'mic1');
client.startBroadcast(streamKey)
</script>
$ git clone \
https://github.com/aws-samples/amazon-ivs-real-time-audio-rooms-web-demo.git
$ cd amazon-ivs-real-time-audio-rooms-web-demo
$ npm run deploy:init
$ npm run deploy:backend:dev # for development
$ npm run deploy:backend:prod # for production
$ npm run deploy:website:dev # for development
$ npm run deploy:website:prod # for production
$ npm run dev
# check out the project
$ git clone \
https://github.com/aws-samples/\
amazon-ivs-real-time-basic-web-demo.git
$ cd amazon-ivs-real-time-basic-web-demo
# install required packages
$ npm ci
# run the deploy
# when the deployment successfully completes
# copy the url provided in the output
# you may need the url when running client app
$ npm run deploy
# retrieve the CloudFormation stack outputs
$ aws cloudformation describe-stacks \
--stack-name AmazonIVSRtWebDemoStack \
--query 'Stacks[].Outputs'
# cleanup (delete all resources associated
# with this demo including DynamoDB table)
$ npm run destroy