Suragch Flutter/Dart developer and technical writer.

Best Flutter music streaming options

12 min read 3457

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!

Contents

Audio streaming app considerations

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:

  • Playlists: How will you organize songs, by genre artist, or album? Can a user create their own custom playlists?
  • Shuffling: Will you allow a user to reorder a playlist randomly?
  • Looping: Can a user repeat a song or album?
  • Downloads: In addition to streaming, are users able to download a song for offline playback?
  • Playback state: How do you want to show that a song is loading, with a circular progress indicator? Do you want to show buffering progress as well?

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.

Background audio and system notifications

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.

Permissions

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.

Handling headphones

You’ll need to decide if your app should keep playing or pause when a user unplugs their headphones.

Ducking

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.

Hiding the plugin from the app (optional)

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 up
  • load: Loads a single song to stream from a URL
  • play: Plays the song from the beginning or continues playback if the song was previously paused
  • pause: Pauses the song
  • seek: Changes the playback position; you’ll call this if the user slides to a new position on the audio progress bar
  • position stream: Reports the current playback position so that you can animate the marker on your progress bar
  • totalDuration 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 state
  • dispose: Closes the audio player and releases its resources

This 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.

Progress Bar

You can also achieve something similar using a Slider widget.


More great articles from LogRocket:


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.

Choosing an audio player plugin

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

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:

  • For Android, edit your Android Manifest and request permissions for INTERNET, WAKE_LOCK, and FOREGROUND_SERVICE; you’ll also need to add service and receiver elements
  • For iOS, add audio 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 audio
  • There are additional changes you’ll need to make if your audio file URLs are HTTP rather than HTTPS, since Android and iOS don’t enable HTTP sources by default

Here 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.

AudioPlayers

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:

  • Create a custom AudioHandler class that wraps your AudioPlayers player
  • For every method you implement in your audio handler, you’ll need to forward the Audio Service method to the audio player and then tell the system what you’re doing so that it can update the lock screen and notification UI
  • Initialize the audio handler in the main method before runApp
  • The Android and iOS configuration setup is similar to the setup described in the previous Just Audio section; for additional information, see the Audio Service documentation

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.

Assets Audio Player

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

Flutter music streaming packages comparison

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
  • Solid support on all major platforms (Android, iOS, web, macOS, Windows, Linux)
  • Solid support for playing local assets and files as well as streaming from URLs
  • Feature rich with a well-developed system for handling playlists
  • Has good separation of concerns, with audio playback handled by Just Audio and system notifications handled by Audio Service; there’s also a relatively easy-to-use Just Audio Background plugin, which combines the two
  • Developer actively maintains the project and responds quickly to well-formed GitHub issues
  • Documentation is fairly extensive (full disclosure: I’ve written some of the community-based tutorials)
  • Supports caching so you don’t need to keep streaming the same song
  • Flutter Favorite
  • Steep learning curve if you need to set up Audio Service yourself
  • Just Audio Background is still in beta and does not have a lot of documentation
AudioPlayers
  • Solid support on all major platforms (Android, iOS, web, macOS, Windows, Linux)
  • Solid support for playing local assets and files as well as streaming from URLs
  • Long development history and still going strong; this package’s adoption as the audio player for the Flame game engine is a vote of confidence for its long-term stability
  • Offers low-latency mode for playing short audio clips, which is useful in games
  • Strong community support
  • No inbuilt support for playlists, which means no shuffling and looping; users must write this logic themselves
  • No inbuilt support for background audio and system notifications; users must implement this with the relatively difficult-to-use Audio Service plugin
  • Documentation is somewhat lacking on more advanced topics
Assets Audio Player
  • Supports Android, iOS, web, and macOS
  • Solid support for playing local assets and files as well as streaming from URLs
  • Offers many inbuilt features
  • Supports playlists with shuffling and looping
  • Offers inbuilt background audio and system notifications
  • Relatively easy to use
  • The name is a little misleading; the plugin isn’t limited to assets
  • No support currently for Windows and Linux
  • Missing some general tidiness (e.g., the API docs are out of date in one case, the publisher is not yet verified on Pub, there are GitHub issues with no response, the example project is somewhat disorganized, commented-out code has not been removed)

Conclusion

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!

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Suragch Flutter/Dart developer and technical writer.

Leave a Reply