Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Implement a Video Streaming Service in FlutterFlow

Build a video-on-demand streaming service in FlutterFlow by using a dedicated video host like Mux or Cloudflare Stream for adaptive HLS streams, storing content metadata in Firestore, displaying a GridView of thumbnails, and gating premium content behind subscription checks. Avoid hosting raw video files on Firebase Storage — it lacks adaptive bitrate streaming.

What you'll learn

  • How to choose and configure a video hosting provider (Mux, Cloudflare Stream) for HLS streaming
  • How to store and query a video content library in Firestore
  • How to display a thumbnail GridView and launch the video player
  • How to gate premium videos behind subscription status and track watch history
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read45-60 minFlutterFlow Pro+ (code export required for custom packages)March 2026RapidDev Engineering Team
TL;DR

Build a video-on-demand streaming service in FlutterFlow by using a dedicated video host like Mux or Cloudflare Stream for adaptive HLS streams, storing content metadata in Firestore, displaying a GridView of thumbnails, and gating premium content behind subscription checks. Avoid hosting raw video files on Firebase Storage — it lacks adaptive bitrate streaming.

Building a Scalable Video Streaming App in FlutterFlow

Video streaming is one of the most infrastructure-heavy features you can add to a mobile app. FlutterFlow's built-in video player works well for short clips served by direct URL, but a real streaming service needs adaptive bitrate (ABR) delivery — where the video quality automatically adjusts to the viewer's network speed. Services like Mux and Cloudflare Stream transcode your uploads into HLS format and serve them via CDN, handling all of this automatically. In this tutorial you will configure a Firestore videos collection as your content library, display it in a responsive GridView, launch the FlutterFlow VideoPlayer widget pointed at an HLS playlist URL, add a watchHistory subcollection for Continue Watching, and gate premium titles behind a user subscription field.

Prerequisites

  • FlutterFlow project connected to Firebase/Firestore
  • A Mux or Cloudflare Stream account (free tier is sufficient to start)
  • Basic familiarity with FlutterFlow's widget tree and Firestore queries
  • Firebase Authentication enabled in your project

Step-by-step guide

1

Create your Firestore videos collection

Open your Firebase console and create a top-level collection named videos. Each document should contain: title (String), description (String), thumbnailUrl (String — a regular image URL), hlsUrl (String — the .m3u8 playlist URL from Mux or Cloudflare Stream), isPremium (Boolean), durationSeconds (Integer), and createdAt (Timestamp). In FlutterFlow, go to Firestore tab → Add Collection → name it videos → add each field with the correct type. This schema gives you everything you need for the UI and access control without any extra Cloud Function calls.

Expected result: The videos collection appears in FlutterFlow's Firestore panel with all fields listed.

2

Build the video library GridView page

Create a new page named VideoLibrary. Add a GridView widget to the page body. In the GridView settings, set Cross Axis Count to 2 and Child Aspect Ratio to 0.7 to give each card portrait proportions. Inside the GridView cell, build a Column containing: an Image widget bound to thumbnailUrl (set fit to Cover, height 150), a Text widget bound to title, and a small lock Icon widget with visibility conditional on isPremium being true AND the current user's subscription field being false. Set the GridView's data source to a Firestore Query on the videos collection, ordered by createdAt descending, with a page size of 20 for pagination.

Expected result: A two-column grid of video thumbnails loads from Firestore, with a lock icon on premium items.

3

Configure the VideoPlayer page with HLS support

Create a page named VideoPlayer that accepts a videoDoc parameter (type: Firestore Document → videos). Add a FlutterFlow VideoPlayer widget at the top, set URL source to the hlsUrl field from the passed document, and enable Auto Play and Show Controls. Below the player, add a Text widget for title and description. Before launching the player, check access: in the GridView tap action, add a Conditional Action — if isPremium is true AND the current user's isPremium app state field is false, navigate to a Paywall page instead of VideoPlayer. Otherwise, navigate to VideoPlayer passing the document reference.

Expected result: Tapping a free video opens the player and begins streaming. Tapping a premium video while unsubscribed redirects to the paywall.

4

Track watch history with a watchHistory subcollection

To implement Continue Watching, create a watchHistory subcollection under each user document in Firestore (path: users/{userId}/watchHistory). Each document uses the videoId as its document ID and stores: videoId (String), title (String), thumbnailUrl (String), progressSeconds (Integer), and watchedAt (Timestamp). In the VideoPlayer page, add an On Page Dispose action that calls a Custom Action to write or update the watchHistory document with the current player position. On the VideoLibrary page, add a horizontal ListView at the top querying the current user's watchHistory ordered by watchedAt descending, limited to 10 items.

update_watch_history.dart
1// Custom Action: updateWatchHistory
2// Parameters: videoId (String), progressSeconds (int)
3import 'package:cloud_firestore/cloud_firestore.dart';
4import 'package:firebase_auth/firebase_auth.dart';
5
6Future updateWatchHistory(String videoId, int progressSeconds) async {
7 final uid = FirebaseAuth.instance.currentUser?.uid;
8 if (uid == null) return;
9 await FirebaseFirestore.instance
10 .collection('users')
11 .doc(uid)
12 .collection('watchHistory')
13 .doc(videoId)
14 .set({
15 'videoId': videoId,
16 'progressSeconds': progressSeconds,
17 'watchedAt': FieldValue.serverTimestamp(),
18 }, SetOptions(merge: true));
19}

Expected result: After watching any video, it appears in the Continue Watching row at the top of the library page.

5

Add premium gating and subscription status

In Firestore, add an isPremium Boolean field to your users collection documents (default false). When a user subscribes via Stripe or your payment provider, a Cloud Function updates this field to true. In FlutterFlow, create an App State variable isPremiumUser (Boolean, persisted). On app launch in your AuthState widget, query the current user document and set the isPremiumUser app state variable from the Firestore isPremium field. Update all premium content checks throughout the app to reference this app state variable. This avoids re-querying Firestore on every navigation and keeps the UI snappy.

Expected result: Free users see the paywall when tapping premium content. Subscribed users play all videos without interruption.

Complete working example

update_watch_history.dart
1// Custom Action: updateWatchHistory
2// Add to FlutterFlow via Custom Actions panel
3// Parameters:
4// videoId: String
5// progressSeconds: int
6// videoTitle: String
7// thumbnailUrl: String
8
9import 'package:cloud_firestore/cloud_firestore.dart';
10import 'package:firebase_auth/firebase_auth.dart';
11
12Future<void> updateWatchHistory(
13 String videoId,
14 int progressSeconds,
15 String videoTitle,
16 String thumbnailUrl,
17) async {
18 final user = FirebaseAuth.instance.currentUser;
19 if (user == null) return;
20
21 final historyRef = FirebaseFirestore.instance
22 .collection('users')
23 .doc(user.uid)
24 .collection('watchHistory')
25 .doc(videoId);
26
27 await historyRef.set(
28 {
29 'videoId': videoId,
30 'title': videoTitle,
31 'thumbnailUrl': thumbnailUrl,
32 'progressSeconds': progressSeconds,
33 'watchedAt': FieldValue.serverTimestamp(),
34 },
35 SetOptions(merge: true),
36 );
37}
38
39// Companion function: getWatchProgress
40// Returns saved progress in seconds (0 if never watched)
41Future<int> getWatchProgress(String videoId) async {
42 final user = FirebaseAuth.instance.currentUser;
43 if (user == null) return 0;
44
45 final doc = await FirebaseFirestore.instance
46 .collection('users')
47 .doc(user.uid)
48 .collection('watchHistory')
49 .doc(videoId)
50 .get();
51
52 if (!doc.exists) return 0;
53 return (doc.data()?['progressSeconds'] as int?) ?? 0;
54}

Common mistakes when implementing a Video Streaming Service in FlutterFlow

Why it's a problem: Hosting all videos on Firebase Storage with direct download URLs

How to avoid: Upload videos to Mux or Cloudflare Stream. Both transcode to HLS automatically and serve via CDN. Store the resulting .m3u8 playlist URL in Firestore and point the VideoPlayer widget at that URL.

Why it's a problem: Checking subscription status with a Firestore read on every video tap

How to avoid: Load the user's isPremium field once on app start, store it in a persisted App State variable, and reference that variable for all gating checks. Refresh it on app foreground events.

Why it's a problem: Not setting Firestore security rules on the videos collection

How to avoid: Set Firestore rules to require authentication for reads. For premium content, require request.auth != null AND get the user document to verify isPremium is true before returning the hlsUrl field.

Best practices

  • Use Mux or Cloudflare Stream for all video hosting — never Firebase Storage for streaming content
  • Store only metadata (title, thumbnailUrl, hlsUrl) in Firestore, not the video files themselves
  • Preload thumbnail images with a placeholder color or shimmer animation while the GridView loads
  • Limit Continue Watching queries to the 10 most recent items — large watchHistory collections slow down the page
  • Re-check subscription status on app foreground, not just on launch, to catch plan changes mid-session
  • Use document IDs as watchHistory keys (videoId as ID) to prevent duplicate history entries automatically
  • Add Firestore security rules that protect premium hlsUrl values from unauthenticated reads

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I am building a video-on-demand app in FlutterFlow with Firebase. My videos are hosted on Mux and I have HLS playlist URLs stored in Firestore. How do I structure Firestore security rules so that only authenticated users with isPremium: true can read the hlsUrl field of documents where isPremium is true?

FlutterFlow Prompt

Create a FlutterFlow Custom Action called updateWatchHistory that takes videoId (String), progressSeconds (Integer), videoTitle (String), and thumbnailUrl (String) as parameters. It should write to Firestore path users/{currentUserId}/watchHistory/{videoId} using set with merge: true, storing all four parameters plus a serverTimestamp watchedAt field.

Frequently asked questions

Can I use YouTube embeds instead of Mux for video streaming?

Yes, FlutterFlow's WebView widget can embed a YouTube player using the youtube_player_flutter package via Custom Widget. However, YouTube embeds show YouTube branding, related videos, and ads, which looks unprofessional for a paid streaming service. Mux or Cloudflare Stream give you a clean, branded experience.

How much does Mux cost compared to Firebase Storage for video?

Mux charges around $0.015 per minute of video stored and $0.0026 per minute delivered. Cloudflare Stream is $5 per 1,000 minutes stored and $1 per 1,000 minutes delivered. Firebase Storage charges $0.026 per GB, but you also pay egress fees and get no transcoding or CDN. For anything beyond a few test videos, dedicated video hosting is cheaper and dramatically better quality.

Does FlutterFlow's built-in VideoPlayer widget support HLS streams?

Yes. The FlutterFlow VideoPlayer widget is built on the video_player package which supports HLS .m3u8 URLs natively on both iOS and Android. Simply paste your HLS playlist URL into the URL field and it will stream adaptively.

How do I show a loading spinner while a video buffers?

Wrap your VideoPlayer widget in a Stack and place a CircularProgressIndicator centered on top. Use the VideoPlayer's isBuffering state (available via a Custom Widget or the video_player controller) to toggle the spinner's visibility. In FlutterFlow without custom code, you can show a placeholder image in the same Stack position that hides once the player initialises.

What is the maximum video file size I can upload to Mux?

Mux accepts uploads up to 10GB per file via their direct upload API. For larger files, use Mux's chunked upload option. Once uploaded, Mux transcodes the video into multiple HLS renditions regardless of the original file size or format.

How do I handle offline viewing for premium subscribers?

Offline video download requires the flutter_downloader package and significant custom Dart code, which is beyond FlutterFlow's no-code capabilities. You would need to export your project to Flutter code and implement the download and local playback logic manually. This is an Advanced feature and requires the FlutterFlow Pro plan for code export.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.