Ravi Jagani · Flutter articles

Streaming feed videos in a Flutter app with S3, HLS and DASH

· Ravi Jagani

How it works: From upload to playback, chunk by chunk
  1. Trainer’s phone: The Flutter app uploads the video
  2. AWS S3: Stores the original file
  3. Conversion: Splits the video into short chunks
  4. CloudFront: Caches the chunks close to viewers
  5. Fan’s phone: Plays the video chunk by chunk

On the UK fitness app I led, built in Flutter, the home screen is a feed of trainer videos. Video is the main content, so the way a video travels from a trainer’s phone to a fan’s screen shaped a lot of the app. This is how that pipeline works, and why it’s built that way.

The problem with one big video file

The simplest way to show a video is to upload an MP4 and play its URL. In a scrolling feed that breaks down quickly:

  • The phone downloads a large part of the file before playback feels smooth, yet people scroll past most videos within seconds.
  • One file means one quality. A fan on a weak mobile connection gets the same large file as someone on Wi-Fi.
  • If uploads pass through your backend first, every video costs your servers bandwidth twice: once coming in, once going out to storage.

The pipeline

1. The app uploads straight to S3

The app sends each video directly to an AWS S3 bucket instead of to our backend. The backend never carries the video itself, so several trainers uploading at once doesn’t slow down the API the rest of the app depends on.

A mobile app can’t hold permanent AWS keys, so it needs short-lived permission to write to the bucket. The usual ways to give it are a pre-signed upload URL from your backend, or temporary credentials. Either way, the permission covers one upload and expires soon after.

2. The video is converted once the upload finishes

When the upload completes, a conversion process starts. It turns the original file into two streaming formats:

  • HLS: an .m3u8 playlist, with the video split into short chunks.
  • MPEG-DASH: an .mpd manifest that describes chunks in the same way.

The playlist or manifest is a small text file listing where each chunk is. The player reads it and fetches the video piece by piece, instead of as one big download.

3. CloudFront delivers the chunks

The converted files are served through Amazon CloudFront, AWS’s content delivery network. Chunks are cached in locations close to viewers, so a popular trainer’s video comes from a nearby cache instead of from the bucket every time.

Why chunks help a mobile app

  • Playback starts as soon as the first chunk arrives, not after the whole file.
  • When someone scrolls away, the player stops asking for chunks, so no data is wasted on videos nobody watches.
  • Seeking jumps straight to the chunk that holds that moment.
  • HLS and DASH can both list several quality levels in one playlist. If you encode more than one, the player can switch quality as the connection changes.

Why produce both formats? Player support differs by platform. Apple’s AVPlayer on iOS plays HLS natively but not DASH, while ExoPlayer on Android plays both. Having both means each platform’s player gets a format it handles well.

Tuning the feed without an app release

The feed isn’t only trainer videos. Other posts and events are mixed in at calculated intervals. I tuned the algorithm that decides where they go, and its settings live in Firebase Remote Config, so the team can change the mix without shipping a new version of the app.

What I’d tell someone building this

  • Upload straight to storage. Keep large files away from your API servers.
  • Start the conversion automatically when the upload finishes, so nothing depends on the app staying open.
  • Put a CDN in front of the chunks from day one.
  • Treat the playlist URL as “the video”. The app should only ever need that one URL.

From the case study: Fitness social app: Live coaching for trainers and fans

More articles