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

How to Set Up a Music Streaming Service with FlutterFlow

Build a music streaming app using Firestore tracks and playlists collections. Browse music in horizontal genre carousels, play tracks via a just_audio Custom Widget with play/pause/seek controls, and persist playback across page navigation with a global mini-player implemented as a Stack at the app scaffold level. Users create and manage playlists by adding or removing trackIds. Background audio support uses the audio_service package for notification media controls.

What you'll learn

  • How to model tracks and playlists in Firestore for a music library
  • How to build a just_audio Custom Widget with play, pause, seek, and skip controls
  • How to persist audio playback across pages with a global mini-player using Stack
  • How to implement playlist management with add, remove, and reorder functionality
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read30-40 minFlutterFlow Pro+ (Custom Widgets required for audio player)March 2026RapidDev Engineering Team
TL;DR

Build a music streaming app using Firestore tracks and playlists collections. Browse music in horizontal genre carousels, play tracks via a just_audio Custom Widget with play/pause/seek controls, and persist playback across page navigation with a global mini-player implemented as a Stack at the app scaffold level. Users create and manage playlists by adding or removing trackIds. Background audio support uses the audio_service package for notification media controls.

Music player app with streaming, playlists, and persistent playback

This tutorial builds a full music streaming application: a Firestore-backed music library with tracks organized by genre, horizontal carousel browsing, a Custom Widget audio player using just_audio with full playback controls, a persistent mini-player bar that continues playing across page navigation, and user playlist management. This is a full STREAMING APP — for a simple single-track audio player widget, see the audio player tutorial in Cluster 1.

Prerequisites

  • A FlutterFlow project with Firebase/Firestore connected
  • Audio files hosted on Firebase Storage or external URLs (MP3/AAC)
  • FlutterFlow Pro plan for Custom Widgets
  • Basic understanding of App State and Custom Widgets in FlutterFlow

Step-by-step guide

1

Create the Firestore tracks and playlists collections

Create a tracks collection with fields: title (String), artist (String), albumArt (String — image URL), audioUrl (String — MP3/AAC URL in Firebase Storage or external CDN), duration (Integer — seconds), genre (String — Pop, Rock, Electronic, Jazz, Hip-Hop), and album (String). Create a playlists collection: userId (String), name (String), description (String), trackIds (List of Strings — references to track document IDs), coverArt (String — first track's albumArt or custom image), and createdAt (Timestamp). Upload 8-10 test audio files to Firebase Storage and create corresponding track documents with real audio URLs. Set Firestore rules: tracks are public read; playlists are read/write only by owner.

Expected result: Firestore has tracks with real audio URLs and playlists collection ready for user-created playlists.

2

Build the browse page with horizontal genre carousels

Create a BrowsePage with a Column inside a SingleChildScrollView. For each genre, add a section: a Row with a Text heading (genre name in headlineSmall bold) and a 'See All' TextButton. Below it, place a horizontal ListView (scrollDirection: horizontal, height: 180) bound to a Backend Query on tracks where genre == 'Pop' (repeat for each genre). Each item is a Container (width: 140) with a Column: ClipRRect Image (albumArt, square, borderRadius: 8), Text (title, maxLines: 1, overflow: ellipsis), and Text (artist, caption, secondary color). On Tap, set App State currentTrack to this track's data and start playback (trigger the global player). Alternatively, navigate to a NowPlayingPage for full-screen controls.

Expected result: A browse page shows horizontal carousels per genre. Tapping a track starts playback immediately.

3

Create the just_audio Custom Widget for playback controls

Create a Custom Widget named AudioPlayerWidget that imports the just_audio package. The widget accepts parameters: audioUrl (String), title (String), artist (String), albumArt (String), and Action Parameter callbacks onNext and onPrevious. Inside the widget, create an AudioPlayer instance. On init, call player.setUrl(audioUrl). Build the UI: a large Image for album art, Text for title and artist, a Slider bound to player.positionStream (current position / duration), a Row with IconButton skip_previous (calls onPrevious), IconButton play_arrow/pause (toggles player.play/pause based on player.playerState), and IconButton skip_next (calls onNext). Display current time and remaining time as Text widgets formatted from position and duration streams.

audio_player_widget.dart
1// Custom Widget: AudioPlayerWidget (core playback logic)
2import 'package:just_audio/just_audio.dart';
3
4final _player = AudioPlayer();
5
6@override
7void initState() {
8 super.initState();
9 _player.setUrl(widget.audioUrl);
10}
11
12// Play/Pause toggle
13StreamBuilder<PlayerState>(
14 stream: _player.playerStateStream,
15 builder: (context, snapshot) {
16 final playing = snapshot.data?.playing ?? false;
17 return IconButton(
18 iconSize: 64,
19 icon: Icon(playing ? Icons.pause_circle_filled : Icons.play_circle_filled),
20 onPressed: () => playing ? _player.pause() : _player.play(),
21 );
22 },
23),
24// Seek slider
25StreamBuilder<Duration>(
26 stream: _player.positionStream,
27 builder: (context, snapshot) {
28 final position = snapshot.data ?? Duration.zero;
29 final duration = _player.duration ?? Duration.zero;
30 return Slider(
31 value: position.inMilliseconds.toDouble(),
32 max: duration.inMilliseconds.toDouble(),
33 onChanged: (v) => _player.seek(Duration(milliseconds: v.toInt())),
34 );
35 },
36)

Expected result: A full audio player widget with play/pause, seek slider, skip controls, and album art display.

4

Implement the persistent mini-player with global App State

The mini-player must persist across page navigation. In App State, create variables: currentTrackTitle (String), currentTrackArtist (String), currentTrackAudioUrl (String), currentTrackAlbumArt (String), and isPlaying (Boolean). In your app's main scaffold (the root page or NavBar page), wrap the body in a Stack. The bottom layer is the normal page content (via NavBar or page router). On top, add a Positioned widget pinned to the bottom (above the NavBar if present) containing a MiniPlayer Component: a Container (height: 64, with shadow) showing a Row of Image (albumArt, 48x48), Column (title + artist, truncated), and play/pause IconButton. The MiniPlayer reads from App State and controls playback via a global AudioPlayer singleton managed in a Custom Action. Tapping the mini-player navigates to NowPlayingPage.

Expected result: A mini-player bar appears at the bottom of all pages when a track is playing, and playback continues seamlessly during navigation.

5

Build playlist management with create, add, and remove

Create a PlaylistsPage with a ListView of the user's playlists (query: playlists where userId == currentUser.uid). Each item shows coverArt, name, track count (trackIds.length), and a play button. Add a FAB to create a new playlist: BottomSheet with TextField for name → Create Document in playlists with empty trackIds array. On any track in the browse page or now playing screen, add a long-press action that shows a BottomSheet listing the user's playlists with an 'Add to Playlist' button for each. The action updates the playlist document by appending the track ID to trackIds using FieldValue.arrayUnion. Viewing a playlist: navigate to PlaylistDetailPage showing all tracks (query: tracks whereIn trackIds) in a ListView. Remove track: swipe-to-dismiss or long-press → FieldValue.arrayRemove.

Expected result: Users can create playlists, add tracks from anywhere in the app, view playlist contents, and remove tracks.

Complete working example

Music Streaming App Architecture
1Firestore Data Model:
2 tracks/{trackId}
3 title: String ("Midnight Dreams")
4 artist: String ("Aurora Band")
5 albumArt: String (image URL)
6 audioUrl: String (Firebase Storage MP3 URL)
7 duration: Integer (243 seconds)
8 genre: String ("Electronic")
9 album: String ("Nightscapes")
10 playlists/{playlistId}
11 userId: String
12 name: String ("My Favorites")
13 description: String
14 trackIds: List<String> (["track1", "track2", ...])
15 coverArt: String
16 createdAt: Timestamp
17
18App State (global playback):
19 currentTrackTitle: String
20 currentTrackArtist: String
21 currentTrackAudioUrl: String
22 currentTrackAlbumArt: String
23 isPlaying: Boolean
24
25App Scaffold (root level):
26 Stack
27 NavBar/PageRouter (normal app pages)
28 Positioned (bottom: navBarHeight)
29 MiniPlayer Component [Cond. Vis: audioUrl isNotEmpty]
30 Container (h: 64, shadow)
31 Row
32 Image (albumArt, 48x48)
33 Column: Text(title) + Text(artist)
34 IconButton (play/pause)
35
36BrowsePage:
37 SingleChildScrollView Column
38 Genre Section: "Pop"
39 Row: Text("Pop") + TextButton("See All")
40 ListView.horizontal (tracks where genre=="Pop")
41 Container(w: 140): albumArt + title + artist
42 Genre Section: "Rock"
43 Genre Section: "Electronic"
44 ... more genres
45
46NowPlayingPage:
47 Image (albumArt, large)
48 Text (title, headlineMedium)
49 Text (artist, bodyLarge)
50 Slider (seek: position / duration)
51 Row: Text(elapsed) + Spacer + Text(remaining)
52 Row: skip_previous + play/pause + skip_next

Common mistakes

Why it's a problem: Creating a new AudioPlayer instance per page — audio restarts on navigation

How to avoid: Lift the AudioPlayer to a global singleton managed via a Custom Action service class or static field. Store the current playback state in App State. All pages and the mini-player reference the same singleton instance.

Why it's a problem: Loading all tracks from all genres in a single query on the browse page

How to avoid: Query each genre carousel separately with a limit (e.g., 10 tracks per genre). Only load more when the user taps 'See All' for that genre. This keeps initial load fast and reads minimal.

Why it's a problem: Storing full track objects in the playlist instead of just track IDs

How to avoid: Store only trackIds (strings) in the playlist. When displaying the playlist, query tracks using whereIn on the trackIds array (in batches of 10 due to Firestore's whereIn limit).

Best practices

  • Use a global AudioPlayer singleton so playback persists across page navigation
  • Implement a mini-player as a Positioned widget in a Stack at the app scaffold level
  • Query genre carousels separately with limits to keep browse page loading fast
  • Store only track IDs in playlists — resolve full track data on display
  • Compress audio to 128kbps for mobile streaming to reduce bandwidth and loading time
  • Use StreamBuilders to reactively update the play/pause button and seek slider from the audio player
  • Add audio_service package for background playback with notification media controls on Android/iOS

Still stuck?

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

ChatGPT Prompt

Design a Firestore data model for a music streaming app with tracks (title, artist, albumArt, audioUrl, genre) and user playlists (trackIds array). Write a Dart class that wraps just_audio AudioPlayer as a global singleton with methods for play, pause, seek, skip next, and skip previous from a playlist queue.

FlutterFlow Prompt

Create a music browse page with horizontal scrollable carousels for each genre (Pop, Rock, Electronic, Jazz). Each carousel shows track album art, title, and artist. Add a mini-player bar at the bottom that shows the currently playing track with play/pause controls.

Frequently asked questions

How do I keep music playing when the user switches pages?

Use a global AudioPlayer singleton stored in a static field or App State, not a page-level instance. Place a mini-player Component at the app scaffold level in a Stack above the page content. The singleton continues playback regardless of which page is displayed.

How do I implement background audio with notification controls?

Add the audio_service package to your project. Wrap your AudioPlayer in an AudioHandler that implements play, pause, skip, and seek. This enables the lock screen / notification bar media controls on both Android and iOS so users can control playback without opening the app.

Can I stream audio directly from Firebase Storage?

Yes. Upload MP3/AAC files to Firebase Storage and use the download URL as the audioUrl in your tracks collection. just_audio can stream directly from HTTPS URLs. Set Storage rules to allow public read for audio files or use signed URLs for premium content.

How do I implement a play queue with shuffle and repeat?

Store the current playlist's trackIds in App State as a queue. Maintain a currentIndex. For shuffle, create a shuffled copy of the queue and play from that. For repeat, when the current track ends and currentIndex equals the last index, reset to 0. Use just_audio's ConcatenatingAudioSource for built-in queue management.

What audio formats does just_audio support?

just_audio supports MP3, AAC, OGG Vorbis, OPUS, FLAC, and WAV. For mobile streaming, MP3 at 128-256kbps provides the best balance of quality and file size. FLAC is lossless but significantly larger — use it only for premium quality tiers.

Can RapidDev help build a production music streaming service?

Yes. A production music app needs DRM protection, adaptive bitrate streaming, offline caching, music licensing compliance, recommendation algorithms, and CDN distribution. RapidDev can architect the full platform beyond what the visual builder handles.

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.