So you want to make a music streaming app, but you don’t know where to start? Well, you’ve come to the right place. This guide will provide an overview of important factors to consider when creating the app, and will compare the features and benefits of three open source audio player plugins: Just Audio, AudioPlayers, and Assets Audio Player. As a bonus, this article will also provide information on how to future-proof your app for a potential scenario in which a third-party package stops working.
Let’s get started!
You may want to jump right into selecting an audio player plugin, but it’s best to postpone implementation details like that for as long as possible. First, you’ll need to carefully think through what you’d like your app to do and how its user interface will look.
To plan your app, you can use tools like Sketch, Adobe XD, or Figma. Or, you can opt for good ol’ notebook paper. Draw out every screen and decide what user interactions are possible. Going through this process will force you to consider scenarios you hadn’t thought about earlier.
Here are a few topics and questions that you may find helpful to consider as you plan your app’s UI/UX:
Unless you have a live stream, such as radio, you’ll also need to consider where the app will get songs. A music streaming app needs to stream songs from somewhere.
Also, where will you host the audio files? Will you put them in cloud storage like AWS, use a service like SoundCloud, or host the songs on your own server? If you decide to host on your own server, do you have the bandwidth to handle all of the streaming? If not, you may consider wrapping your server with a content delivery network.
Another item related to hosting is copyright issues. You either need to own the rights to the music or have permission to stream the songs to your users. Neither Google Play nor Apple’s App Store will allow apps that provide pirated content.
Beyond UI/UX and hosting-related issues, it’s also important to consider certain technical factors related to making a music streaming app such as notifications, permissions, handling headphones, and ducking.
For your music streaming app, you’ll almost certainly want to support background audio playback. This allows the music to keep playing even when users switch to a different app or put their phone in their pocket.
Connecting your audio player with the system gives users a UI control on the notification bar and lock screen so that they can pause the audio or skip to the next song in the playlist.
A music streaming app streams from the internet, so you’ll obviously need to have permission to access the internet. If the song URLs use HTTP, rather than HTTPS, you’ll need a special setup for that too.
Depending on your app, you may also need permissions for using Bluetooth, keeping the screen on, and allowing a foreground service. You’ll need to request these permissions in your app’s configuration file.
You’ll need to decide if your app should keep playing or pause when a user unplugs their headphones.
When users stream music from your app in the car while simultaneously using Google Maps for navigation, they will probably want the music volume to lower when the navigation assistant speaks. This is called ducking. Podcast players, on the other hand, generally pause playback.
This step isn’t required, but you never know when a third-party package is going to stop working. Most open source packages on Pub are made by good-hearted developers who volunteer their time to help the community. It’s a lot of work.
It’s always possible that a package maintainer may become too tired, too ill, or too busy to maintain the package. It’s also possible that your app may need a feature that the plugin doesn’t support.
One way to future-proof your app for this type of scenario is to hide the plugin behind an interface. This keeps the plugin code confined to a single location rather than scattered throughout your app. Only the app knows about the interface, so switching plugins is relatively painless. You can try one plugin out, and if you don’t like it, then just swap it out with a different plugin.
Creating an interface certainly isn’t a requirement, and it does add an extra layer of complexity to your app. However, thinking architecturally now can save you a lot of headaches in the future.
To make an interface, create an abstract class with an API similar to the following:
abstract class MusicPlayer { Future<void> init(); Future<void> load(String url); void play(); void pause(); void seek(Duration position); Stream<Duration> get position; Stream<Duration> get totalDuration; Future<void> dispose(); }
Here’s an explanation of each method:
init
: Some Flutter plugins need to initialize themselves, so you’ll call this method when the app starts upload
: Loads a single song to stream from a URLplay
: Plays the song from the beginning or continues playback if the song was previously pausedpause
: Pauses the songseek
: Changes the playback position; you’ll call this if the user slides to a new position on the audio progress barposition
stream: Reports the current playback position so that you can animate the marker on your progress bartotalDuration
stream: Reports the length of the current song; this duration may not be available when you first load the song, so reporting it as a stream allows your UI to react to the current playback statedispose
: Closes the audio player and releases its resourcesThis API is minimalistic. In a full app, you will probably want to load a whole playlist and enable other features like skipping to the next song, looping, and shuffling. In addition, you will likely want to listen to more streams, such as a playback state stream (initial, playing, paused, loading) and a buffered position stream.
You will make a concrete implementation of this API for whichever plugin you choose, but your Flutter widgets or state management code will only know about the interface. They don’t care what plugin you’re using.
For simplicity, the below examples have the MusicPlayer
interface code mixed directly in among the Flutter widgets. In a more complex app, you’d probably want to move the MusicPlayer
calls to your state management layer, whether that be with Bloc, Provider, RiverPod, or something else.
The plugin initialization needs to occur before the app runs. Add it to your main
function, like so:
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await player.init(); runApp(const MyApp()); }
player
is an instance of a concrete MusicPlayer
implementation. You could use GetIt
or some form of dependency injection to instantiate it.
Handle loading and disposing in a stateful widget’s lifecycle methods, like so:
@override void initState() { super.initState(); _loadSong(); } Future<void> _loadSong() async { const url = 'https://www.example.com/song.mp3'; await player.load(url); } @override void dispose() { player.dispose(); super.dispose(); }
You can update your progress bar by listening to the total duration and position streams:
StreamBuilder<Duration>( stream: player.totalDuration, builder: (context, totalDurationSnapshot) { return StreamBuilder<Duration>( stream: player.position, builder: (context, positionSnapshot) { return ProgressBar( progress: positionSnapshot.data ?? Duration.zero, total: totalDurationSnapshot.data ?? Duration.zero, onSeek: player.seek, ); }, ); }, ),
Rather than nesting two stream builders, you’d probably want to combine the streams with a package like rxdart
or redesign your MusicPlayer
API to provide both position and total duration in a single stream.
The ProgressBar
widget in the code above comes from the audio_video_progress_bar package I made for use in my own audio apps.
You can also achieve something similar using a Slider
widget.
Now that you have the user interface set up, you’re free to implement it with any audio plugin you like. Let’s take a closer look at three popular audio streaming packages.
There are a number of audio player packages that support streaming music in a Flutter app. This article will review the features and benefits of three popular packages: Just Audio, AudioPlayers, and Assets Audio Player.
Before you choose a package, though, consider future-proofing your app as discussed previously in this article.
Just Audio is an audio plugin that enables streaming music on all of the major platforms that Flutter supports. It offers playlists, shuffling, looping, and many more inbuilt features. Of the three audio packages reviewed in this article, Just Audio has the most likes on Pub (over 2,200 at the time of writing) and is the only one designated as a Flutter Favorite.
Just Audio does not support background audio and system notifications itself. However, the Just Audio’s author also created another package called Audio Service. This package handles playing music in the background and communicating with the system to display UI controls on the lock screen and notification area. You’ll still use Just Audio, but you’ll wrap it with Audio Service. I describe how to do this in this in-depth tutorial.
Audio Service isn’t the easiest plugin to use, since it requires managing both the audio player and the system notifications. It’s not impossible, though, and with a bit of study and experimentation, you can get it all working.
The same package author also created another plugin called Just Audio Background, which combines both Just Audio and Audio Service. For standard use cases, this might be your best choice. If you need something with more complex functionality, such as the ability to play two audio sources at the same time, you’ll have to use Just Audio and Audio Service separately.
Here’s how you might implement your MusicPlayer
API using Just Audio Background:
class JustAudioPlayer implements MusicPlayer { final AudioPlayer _player = AudioPlayer(); @override Future<void> init() async { await JustAudioBackground.init( androidNotificationChannelId: 'com.logrocket.demo.channel.audio', androidNotificationChannelName: 'Audio playback', androidNotificationOngoing: true, ); } @override Future<Duration> load(String url) async { final source = AudioSource.uri( Uri.parse(url), tag: const MediaItem( id: '1', title: "My song", ), ); return await _player.setAudioSource(source) ?? Duration.zero; } @override void play() => _player.play(); @override void pause() => _player.pause(); @override void seek(Duration position) => _player.seek(position); @override Stream<Duration> get position => _player.positionStream; @override Stream<Duration> get totalDuration => _player.durationStream.map( (duration) => duration ?? Duration.zero, ); @override Future<void> dispose() async => await _player.dispose(); }
Most of these methods just forward your MusicPlayer
API on to the plugin’s audio player. The init
method does some registration work for the background audio. AudioSource
is what Just Audio uses to set the current song or playlist. MediaItem
is how Audio Service keeps track of the current song to display in the system notification area.
In addition to coding work, you’ll also have to do a bit of setup in the Android and iOS configuration files. This is described in the documentation, but here’s a summary:
INTERNET
, WAKE_LOCK
, and FOREGROUND_SERVICE
; you’ll also need to add service and receiver elementsaudio
as an item in UIBackgroundModes
in your Info.plist
file; you should also edit your Podfile
to strip out some audio recording APIs if your app doesn’t need to record audioHere are some open source repositories that use Just Audio, Audio Service, or Just Audio Background::
You may also wish to consult the official example apps for Just Audio, Just Audio Background, and Audio Service.
The AudioPlayers plugin was originally a fork of another plugin named AudioPlayer. However, AudioPlayers has since advanced well beyond its predecessor. Of the three plugins being reviewed in this article, AudioPlayers is the oldest, started over two and a half years before the first stable version of Flutter. Although lagging slightly behind Just Audio for upvotes on Pub (over 1,720 as of this writing), it comes in at 100% for Pub’s mysteriously calculated popularity metric, bypassing Just Audio’s 99% popularity.
The author of AudioPlayers is also involved in Flame game engine development. This probably explains why AudioPlayers is embedded in the Flame Audio plugin that can be used for playing sounds in Flutter games.
Like Just Audio, AudioPlayers itself doesn’t support background audio. So, if you want users to be able to control your songs from the lock screen, you’ll need to wrap AudioPlayers with the Audio Service plugin. There’s no convenience package like Just Audio Background for AudioPlayers.
Here are the basic steps you’ll follow to set up Audio Service for AudioPlayers:
AudioHandler
class that wraps your AudioPlayers playermain
method before runApp
The methods of the AudioPlayers player itself are similar to the Just Audio player. Here’s a brief comparison:
_player.setAudioSource(AudioSource.uri(...)); // Just Audio _player.setSourceUrl(url); // AudioPlayers _player.play(); // Just Audio _player.resume(); // AudioPlayers _player.pause(); // Just Audio _player.pause(); // AudioPlayers _player.seek(duration); // Just Audio _player.seek(duration); // AudioPlayers _player.dispose(); // Just Audio _player.release(); // AudioPlayers
One of the biggest differences between Just Audio and AudioPlayers is that Just Audio supports playlists out of the box. With AudioPlayers, you’ll have to implement that functionality yourself.
Here are some open source repositories that use AudioPlayers:
Also check out the official AudioPlayer example app.
Contrary to what its name implies, the Assets Audio Player plugin can do more than just play audio from the assets folder. Among its many features, it can stream audio from a URL, which means you can also use this plugin in a music streaming app.
Assets Audio Player offers inbuilt support for playlists, shuffling, looping, system notifications, Bluetooth, pausing for a phone call, audio ducking, and much more. The developer has obviously put in a lot of time into this plugin. For ease of use, it’s probably the best of the three plugins reviewed in this article.
The Assets Audio Player plugin has fewer likes on Pub (nearly 800 as of this writing) compared to Just Audio and AudioPlayers, but it trails closely behind for its popularity score. And, like the other two plugins reviewed in this article, Assets Audio Player is actively maintained.
Here’s an Assets Audio Player setup wrapped by your MusicPlayer
interface:
class AssetsAudioPlayerPlayer implements MusicPlayer { final _player = AssetsAudioPlayer(); @override Future<void> init() async { AssetsAudioPlayer.setupNotificationsOpenAction((notification) { return true; }); } @override Future<void> load(String url) async { await _player.open( Audio.network( url, metas: Metas(title: "My song"), ), showNotification: true, ); } @override void pause() => _player.pause(); @override void play() => _player.play(); @override Stream<Duration> get position => _player.currentPosition; @override Stream<Duration> get totalDuration => _player.current.map( (playing) => playing?.audio.duration ?? Duration.zero, ); @override void seek(Duration position) => _player.seek(position); @override Future<void> dispose() => _player.dispose(); }
This setup is quite similar to that of Just Audio Background. However, there’s less to do on the Android and iOS sides. Android only needs to request INTERNET
permission. As with the other plugins, Android and iOS need to notify the system if it’s using HTTP, rather than HTTPS, for the URLs.
Here are some open source repositories that use Assets Audio Player:
Also, check out the official Assets Audio Player example app
To assist with your final decision, here’s a summary of the pros and cons for the Just Audio, AudioPlayers, and Assets Audio Player Flutter music streaming packages.
Flutter music streaming package | Pros | Cons |
Just Audio |
|
|
AudioPlayers |
|
|
Assets Audio Player |
|
|
If you want to build a Flutter app that will stream music, any of the three audio player plugins reviewed here will get the job done. For an easy start, consider Assets Audio Player or the Just Audio Background version of Just Audio. If you want more control of exactly what’s happening, try wrapping Just Audio or AudioPlayers with Audio Service. However, if you compare Just Audio and AudioPlayers, Just Audio is better suited for a music streaming app since it offers inbuilt support for playlists.
While writing this article, I convinced myself of the benefits of wrapping the audio plugin with an interface. During testing, I was able to swap between plugin implementations by changing just one single line of code.
Making a music streaming app can be challenging. If you get stuck, study how the example app in the plugin repo works. With a little perseverance, you’ll get it.
Now, get out there and make some music!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.