# HLS streaming with Ittybit and CloudFront

Generate adaptive HLS streams with Ittybit and deliver them globally via 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](/guides/process-files-from-s3) 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.

<CodeGroup labels={["TypeScript", "curl"]}>
```typescript
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"

````

```bash
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"
````

</CodeGroup>

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.

```bash
# 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:

```json
{
  "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:

```json
{
  "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.

```bash
# 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:

```json
{
  "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:

```bash
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.

```html
<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](/guides/create-hls-streams) -- adaptive bitrate streaming basics
- [Process files from S3](/guides/process-files-from-s3) -- setting up S3 connections
- [Write output to S3](/guides/write-output-to-s3) -- delivering processed files to your bucket
- [AWS event-driven media processing](/guides/aws-event-driven-media-processing) -- trigger tasks automatically on S3 upload