Why Cloudflare R2 is different from S3
R2 is Cloudflare's object storage. The critical difference from AWS S3 or Google Cloud Storage: zero egress fees. You pay for storage ($0.015/GB/month) and Class B operations (reads), but not for bandwidth out. For a video file that gets served thousands of times per month, this is the line item that makes everything else irrelevant.
R2 integrates natively with Cloudflare Workers. You can serve R2 content directly from a Worker at Cloudflare's edge — geographically close to the viewer, sub-50ms to first byte on most requests, with no origin server roundtrip. For HLS, where the player makes dozens of small segment requests in sequence, edge delivery is the difference between smooth playback and buffering.
HLS: how it works
HLS (HTTP Live Streaming) splits the video into small segments — typically 2–6 seconds each — and generates a manifest file (.m3u8) listing the segments in order. The video player fetches the manifest, then requests segments sequentially. When it finishes one, it requests the next.
An HLS package can include multiple quality levels: 360p, 720p, 1080p. The master manifest lists all quality levels with their bandwidth requirements. The player starts at a low quality, measures download speed, and switches up or down automatically based on available bandwidth.
The practical result: a viewer on a slow mobile connection gets 360p that loads fast; a viewer on fast broadband gets 1080p. Same video, no buffering, no configuration required.
Encoding and packaging
The source video needs to be encoded into multiple quality levels and packaged as HLS segments. FFmpeg handles both in one pass:
# Encode source to three quality levels + package as HLS
ffmpeg -i source.mp4 \
-filter_complex "[0:v]split=3[v1][v2][v3]" \
-map "[v1]" -map 0:a -vf scale=1280:720 -b:v 2800k -hls_time 4 \
-hls_playlist_type vod -hls_segment_filename "720p/seg%03d.ts" 720p/index.m3u8 \
-map "[v2]" -map 0:a -vf scale=854:480 -b:v 1400k -hls_time 4 \
-hls_playlist_type vod -hls_segment_filename "480p/seg%03d.ts" 480p/index.m3u8 \
-map "[v3]" -map 0:a -vf scale=640:360 -b:v 700k -hls_time 4 \
-hls_playlist_type vod -hls_segment_filename "360p/seg%03d.ts" 360p/index.m3u8
# Write the master manifest manually (or script it):
cat > master.m3u8 << EOF
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=854x480
480p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=700000,RESOLUTION=640x360
360p/index.m3u8
EOFThe R2 bucket structure
Upload the encoded files to R2 following a predictable path structure:
videos/
hero/
master.m3u8
720p/
index.m3u8
seg000.ts
seg001.ts
...
480p/
index.m3u8
seg000.ts
...
360p/
...The Cloudflare Worker
A Worker sits in front of the R2 bucket and handles two things: serving the files with the correct content types, and adding CORS headers so the HLS player (hls.js on the frontend) can fetch cross-origin.
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const key = url.pathname.slice(1); // strip leading /
const object = await env.VIDEOS_BUCKET.get(key);
if (!object) return new Response('Not found', { status: 404 });
const isManifest = key.endsWith('.m3u8');
const contentType = isManifest
? 'application/vnd.apple.mpegurl'
: 'video/mp2t';
return new Response(object.body, {
headers: {
'Content-Type': contentType,
'Access-Control-Allow-Origin': '*',
'Cache-Control': isManifest
? 'public, max-age=60' // manifests: short cache
: 'public, max-age=31536000', // segments: immutable
},
});
},
};Actual cost comparison
For Tepe Watersports at current traffic (several thousand unique visitors per month, hero video playing on the homepage):
Previous setup: dedicated video hosting platform, $40–80/month depending on bandwidth.
R2 setup: storage for ~2GB of encoded video files: $0.03/month. R2 read operations at current request volume: ~$0.50/month. Worker requests: within the free tier (100k requests/day free).
Total: under a dollar a month for the same result — adaptive bitrate, global edge delivery, fast loads on mobile.
The dedicated platform only makes sense at the scale where its additional features (auto-transcoding pipeline, DRM, detailed analytics, signed URLs) justify the price. For a hero video on a marketing site, you're paying for infrastructure you don't need.
The migration from hosted video to R2 + HLS took one weekend. The monthly cost is now negligible. The performance is better because Cloudflare's edge is closer to your visitors than any origin server.