HLS streaming with Ittybit and CloudFront
AWS MediaPackage is heavy machinery for what most teams actually need: take a video, produce an adaptive bitrate HLS stream, serve it fast. Ittybit’s adaptive_video task generates the .m3u8 manifest and .ts segments, writes them straight to your S3 bucket, and CloudFront handles the rest.
Prerequisites
- An S3 bucket for your HLS output (e.g.
my-stream-bucket) - An Ittybit connection configured for that bucket
- An Ittybit API key
Create the adaptive HLS task
Point Ittybit at your source video, set the output to an s3:// path in your bucket, and let the adaptive_video kind handle the multi-bitrate packaging.
const task = {
input: "s3://my-media-bucket/uploads/lecture.mov",
kind: "adaptive_video",
connection_id: "conn_abc123",
output: "s3://my-stream-bucket/streams/lecture/",
options: {
format: "hls",
quality: "high",
},
};
const res = await fetch("https://api.ittybit.com/jobs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(task),
});
const data = await res.json();
// data.id -> "task_xyz789"
TASK='{
"input": "s3://my-media-bucket/uploads/lecture.mov",
"kind": "adaptive_video",
"connection_id": "conn_abc123",
"output": "s3://my-stream-bucket/streams/lecture/",
"options": {
"format": "hls",
"quality": "high"
}
}'
curl -X POST https://api.ittybit.com/jobs \
-H "Authorization: Bearer $ITTYBIT_API_KEY" \
-H "Content-Type: application/json" \
-d "$TASK" When the task completes, your bucket contains the full HLS package:
s3://my-stream-bucket/streams/lecture/
master.m3u8 # top-level manifest
720p/stream.m3u8 # variant playlist
720p/segment-0.ts
720p/segment-1.ts
1080p/stream.m3u8
1080p/segment-0.ts
1080p/segment-1.ts
...
Create a CloudFront distribution
Point CloudFront at your S3 bucket. Use Origin Access Control (OAC) so the bucket stays private.
# Create an OAC
aws cloudfront create-origin-access-control \
--origin-access-control-config '{
"Name": "hls-oac",
"OriginAccessControlOriginType": "s3",
"SigningBehavior": "always",
"SigningProtocol": "sigv4"
}'
Then create the distribution:
{
"Origins": {
"Items": [
{
"Id": "hls-s3",
"DomainName": "my-stream-bucket.s3.us-east-1.amazonaws.com",
"S3OriginConfig": {
"OriginAccessIdentity": ""
},
"OriginAccessControlId": "<oac-id>"
}
],
"Quantity": 1
},
"DefaultCacheBehavior": {
"TargetOriginId": "hls-s3",
"ViewerProtocolPolicy": "redirect-to-https",
"CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
"AllowedMethods": {
"Quantity": 2,
"Items": ["GET", "HEAD"]
}
},
"Enabled": true
}
Update your bucket policy to allow CloudFront access:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-stream-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::<account-id>:distribution/<distribution-id>"
}
}
}
]
}
Cache behaviors for HLS
HLS manifests and segments have different caching needs. Manifests are small and may update if you reprocess content. Segments are immutable once written.
| Pattern | TTL | Reason |
|---|---|---|
*.m3u8 | 10 seconds | Manifests should refresh quickly |
*.ts | 1 year | Segments never change |
Create a cache policy for manifests with a short TTL and assign it to a behavior matching *.m3u8. Leave the default behavior (which catches .ts files) with a long TTL.
# Create a cache policy for manifests
aws cloudfront create-cache-policy \
--cache-policy-config '{
"Name": "hls-manifests",
"DefaultTTL": 10,
"MaxTTL": 30,
"MinTTL": 0,
"ParametersInCacheKeyAndForwardedToOrigin": {
"EnableAcceptEncodingGzip": true,
"HeadersConfig": { "HeaderBehavior": "none" },
"CookiesConfig": { "CookieBehavior": "none" },
"QueryStringsConfig": { "QueryStringBehavior": "none" }
}
}'
Add the behavior to your distribution config:
{
"CacheBehaviors": {
"Items": [
{
"PathPattern": "*.m3u8",
"TargetOriginId": "hls-s3",
"ViewerProtocolPolicy": "redirect-to-https",
"CachePolicyId": "<hls-manifests-policy-id>",
"AllowedMethods": {
"Quantity": 2,
"Items": ["GET", "HEAD"]
}
}
],
"Quantity": 1
}
}
Set CORS headers
Players loading .m3u8 and .ts files cross-origin need CORS. Add a response headers policy to your CloudFront behaviors:
aws cloudfront create-response-headers-policy \
--response-headers-policy-config '{
"Name": "hls-cors",
"CorsConfig": {
"AccessControlAllowOrigins": { "Items": ["*"], "Quantity": 1 },
"AccessControlAllowMethods": { "Items": ["GET", "HEAD"], "Quantity": 2 },
"AccessControlAllowHeaders": { "Items": ["*"], "Quantity": 1 },
"AccessControlMaxAgeSec": 86400,
"OriginOverride": true
}
}'
Play the stream
Once CloudFront is deployed, your master manifest is available at:
https://<distribution-id>.cloudfront.net/streams/lecture/master.m3u8
Point any HLS-compatible player at that URL — hls.js, Video.js, Safari native, or a mobile SDK.
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<video id="player" controls></video>
<script>
const video = document.getElementById('player');
const src = 'https://d1234abcd.cloudfront.net/streams/lecture/master.m3u8';
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(src);
hls.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = src;
}
</script>
See also
- Create HLS streams — adaptive bitrate streaming basics
- Process files from S3 — setting up S3 connections
- Write output to S3 — delivering processed files to your bucket
- AWS event-driven media processing — trigger tasks automatically on S3 upload