AWS MediaConvert for Video Streaming Using Go
AWS MediaConvert is a managed service that converts uploaded video files into streaming-ready formats like HLS and DASH. In this guide, we’ll build a simple HLS transcoding pipeline using Golang, suitable for VOD (Video on Demand).
Prerequisites
Before starting, make sure you have:
An AWS account
An S3 bucket for input videos
An S3 bucket for output streams
An IAM role with MediaConvert permissions
Go 1.20+
AWS SDK for Go v2
Step 1: Create an IAM Role for MediaConvert
MediaConvert requires a service role to access S3.
Attach this policy:
AmazonS3FullAccess(or scoped access)AmazonAPIGatewayInvokeFullAccess
The role ARN will look like:
arn:aws:iam::123456789012:role/MediaConvert_Default_Role
You’ll need this later.
Step 2: Install AWS SDK for Go v2
go get github.com/aws/aws-sdk-go-v2
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/mediaconvert
Step 3: Discover MediaConvert Endpoint
MediaConvert uses account-specific endpoints, not a global one.
cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithRegion("us-east-1"),
)
if err != nil {
log.Fatal(err)
}
mc := mediaconvert.NewFromConfig(cfg)
endpoints, err := mc.DescribeEndpoints(context.TODO(), &mediaconvert.DescribeEndpointsInput{})
if err != nil {
log.Fatal(err)
}
endpoint := *endpoints.Endpoints[0].Url
You must use this endpoint to create jobs.
Step 4: Create a MediaConvert Client with Custom Endpoint
client := mediaconvert.New(mediaconvert.Options{
Region: "us-east-1",
EndpointResolver: mediaconvert.EndpointResolverFromURL(endpoint),
Credentials: cfg.Credentials,
})
This ensures requests go to your account’s MediaConvert service.
Step 5: Define HLS Output Settings
This configuration creates:
HLS playlist
Segmented
.tsfilesAdaptive bitrate support
hlsGroup := mediaconvert.OutputGroup{
Name: aws.String("HLS Group"),
OutputGroupSettings: &mediaconvert.OutputGroupSettings{
Type: mediaconvert.OutputGroupTypeHlsGroupSettings,
HlsGroupSettings: &mediaconvert.HlsGroupSettings{
Destination: aws.String("s3://my-output-bucket/hls/"),
SegmentLength: aws.Int32(6),
MinSegmentLength: aws.Int32(2),
},
},
Outputs: []mediaconvert.Output{
{
VideoDescription: &mediaconvert.VideoDescription{
CodecSettings: &mediaconvert.VideoCodecSettings{
Codec: mediaconvert.VideoCodecH264,
H264Settings: &mediaconvert.H264Settings{
Bitrate: aws.Int32(3000000),
RateControlMode: mediaconvert.H264RateControlModeCbr,
GopSize: aws.Float64(2),
},
},
Width: aws.Int32(1280),
Height: aws.Int32(720),
},
ContainerSettings: &mediaconvert.ContainerSettings{
Container: mediaconvert.ContainerTypeM3u8,
},
},
},
}
Step 6: Create the MediaConvert Job
jobInput := mediaconvert.CreateJobInput{
Role: aws.String("arn:aws:iam::123456789012:role/MediaConvert_Default_Role"),
Settings: &mediaconvert.JobSettings{
Inputs: []mediaconvert.Input{
{
FileInput: aws.String("s3://my-input-bucket/video.mp4"),
AudioSelectors: map[string]mediaconvert.AudioSelector{
"Audio Selector 1": {
DefaultSelection: mediaconvert.AudioDefaultSelectionDefault,
},
},
},
},
OutputGroups: []mediaconvert.OutputGroup{hlsGroup},
},
}
job, err := client.CreateJob(context.TODO(), &jobInput)
if err != nil {
log.Fatal(err)
}
fmt.Println("Job ID:", *job.Job.Id)
This starts the transcoding process asynchronously.
Step 7: Output Structure in S3
After completion, your output bucket will contain:
hls/
├── master.m3u8
├── index_720p.m3u8
├── segment000.ts
├── segment001.ts
You can now stream using:
<video controls src="https://cdn.example.com/hls/master.m3u8"></video>
Step 8: Serve Through a CDN (Recommended)
For production, always serve HLS via a CDN:
AWS CloudFront
Cloudflare
Fastly
This improves startup time and reduces buffering.
Step 9: Integrating CloudFront for Video Streaming
Serving HLS content directly from S3 works for testing, but it does not scale well. For production, you should always place CloudFront in front of your MediaConvert output. CloudFront caches video segments close to users, reduces latency, and handles high traffic efficiently.
CloudFront Architecture for Streaming
The final architecture looks like this:
User
↓
CloudFront CDN
↓
S3 (HLS Output from MediaConvert)
MediaConvert writes HLS segments to S3 → CloudFront pulls and caches them → Users stream from the nearest edge location.
Step 9.1: Create a CloudFront Distribution
When creating the distribution:
Origin: Your MediaConvert output S3 bucket
Origin Access: Use Origin Access Control (OAC) or OAI
Viewer Protocol Policy: Redirect HTTP to HTTPS
Allowed Methods: GET, HEAD
Compress Objects Automatically: Yes
Cache Policy (Important)
Use or create a policy with:
Minimum TTL: 0
Default TTL: 86400 (1 day)
Maximum TTL: 31536000 (1 year)
Query strings: None
Headers: None
HLS files don’t need cookies or headers.
Step 9.2: Secure the S3 Bucket
Your S3 bucket must not be public.
Attach a bucket policy that allows access only from CloudFront:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "cloudfront.amazonaws.com" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::my-output-bucket/*", "Condition": { "StringEquals": { "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EDFDVBD632BHDS5" } } } ] }
This prevents users from bypassing the CDN.
Step 9.3: Update MediaConvert Output Destination
Change your HLS destination to match your CloudFront path structure:
Destination: aws.String("s3://my-output-bucket/streams/video-123/")
Your playback URL will become:
https://d123abcd.cloudfront.net/streams/video-123/master.m3u8
Step 9.4: Play the Stream Using CloudFront
HTML5 (Safari)
<video controls autoplay>
<source src="https://d123abcd.cloudfront.net/streams/video-123/master.m3u8" type="application/x-mpegURL">
</video>
Cross-Browser (hls.js)
<video id="video" controls></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>const video = document.getElementById('video')
const src = 'https://d123abcd.cloudfront.net/streams/video-123/master.m3u8' if (Hls.isSupported()) {
const hls = new Hls()
hls.loadSource(src)
hls.attachMedia(video)
} else {
video.src = src
}
</script>
Step 9.5: Enable Byte-Range Requests (Critical)
HLS requires byte-range support.
In CloudFront:
Enable Range Requests
Do not block
Rangeheaders
CloudFront supports this by default, but custom cache policies can break it.
Step 9.6: Cache Invalidation Strategy
HLS playlists (.m3u8) change more frequently than .ts segments.
Best practice:
Cache
.tssegments for long durationsKeep
.m3u8TTL low (0–60 seconds)
You can control this using:
Separate cache behaviors
File extension–based rules
Step 9.7: Optional – Signed URLs for Secure Streaming
If your content is private, use CloudFront signed URLs.
This allows:
Time-limited access
IP restrictions
User-specific streams
CloudFront signs URLs, not S3.
(Implementation usually happens outside MediaConvert and fits well with Go backend services.)
Final Production Stack
A clean, scalable streaming stack looks like this:
Upload: Presigned S3 URLs
Processing: AWS MediaConvert
Storage: S3 (private)
Delivery: CloudFront CDN
Playback: HLS + Video.js / hls.js