Nowadays many apps have audio files inside to listen to, whether they are fitness, music or mental health apps. Let's see a basic approach to build an app with an audio player.
In this article we will focus on the audioplayers plugin but there are many other plugins to handle audios like audio_service.
So without further ado let's dive into coding! The first thing to do is to add the following packages to your pubspec.yaml
and then runflutter pub get.
dependencies:
audioplayers: ^0.19.0
http: ^0.13.3
path_provider: ^2.0.2
Notice that http and path_provider are required only if you need to download your audio files.
Platform Specific Setup
To allow HTTP connections to any site you will need few lines of code:
iOS
Edit the Info.plist file with:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Android
Edit AndroidManifest.xml file located in android/app/src/main/AndroidManifest.xml
<manifest ...>
<uses-permission android:name="android.permission.INTERNET" />
<application
...
android:usesCleartextTraffic="true"
...>
...
</application>
</manifest>
Creating the widget
Now, let's start to create our Widget that will handle the audio; First of all, let's import the package in our widget:
import 'package:audioplayers/audioplayers.dart';
then create a Stateful Widget that will accept a url as parameter and an isAsset
that will be true only if the audio to be played will be inside assets:
class AudioPlayerWidget extends StatefulWidget {
final String url;
final bool isAsset;
const AudioPlayerWidget({
Key? key,
required this.url,
this.isAsset = false,
}) : super(key: key);
@override
_AudioPlayerWidgetState createState() => _AudioPlayerWidgetState();
}
In our _AudioPlayerWidgetState
let's setup some properties:
- the AudioPlayer itself
- the AudioCache*, mandatory* if you need to reproduce an asset audio
- our PlayerState that will store the current state of the AudioPlayer
- a commodity computed variable
isPlaying
that we'll use to see wether the player is playing or not - another computed variable
_isLocal
that we'll use to see whether the url to play is local or not
late AudioPlayer _audioPlayer;
late AudioCache _audioCache;
PlayerState _playerState = PlayerState.STOPPED;
bool get _isPlaying => _playerState == PlayerState.PLAYING;
bool get _isLocal => !widget.url.contains('https');
Since we've declared the _audioPlayer
and _audioCache
as late variables we are going to init them inside the initState
method:
@override
void initState() {
_audioPlayer = AudioPlayer(mode: PlayerMode.MEDIA_PLAYER);
_audioCache = AudioCache(fixedPlayer: _audioPlayer);
AudioPlayer.logEnabled = true;
_audioPlayer.onPlayerError.listen((msg) {
print('audioPlayer error : $msg');
setState(() {
_playerState = PlayerState.STOPPED;
});
});
super.initState();
}
@override
void dispose() {
_audioPlayer.dispose();
super.dispose();
}
Again, this is a starter guide so I won't handle here duration, seek or background; just remember that if you are going to choose the PlayerMode.LOW_LATENCY
you won't be able to handle seek or duration updates since this mode is crafted for very short audio files.
The only stream that we're going to listen to will be onPlayerError
, so, if an error is thrown for any reason, we will stop immediately the audio.
Please note: always remember to invoke the dispose()
method to close all the streams!!
Play / Pause / Stop
Play/pause actions are managed by the _playPause()
method which checks the player's status and invokes an action accordingly. We won't show the Stop method for the sake of brevity but the logic is exactly the same as for the method below.
_playPause() async {
if (_playerState == PlayerState.PLAYING) {
final playerResult = await _audioPlayer.pause();
if (playerResult == 1) {
setState(() {
_playerState = PlayerState.PAUSED;
});
}
} else if (_playerState == PlayerState.PAUSED) {
final playerResult = await _audioPlayer.resume();
if (playerResult == 1) {
setState(() {
_playerState = PlayerState.PLAYING;
});
}
} else {
if (widget.isAsset) {
_audioPlayer = await _audioCache.play(widget.url);
setState(() {
_playerState = PlayerState.PLAYING;
});
} else {
final playerResult = await _audioPlayer.play(widget.url, isLocal: _isLocal);
if (playerResult == 1) {
setState(() {
_playerState = PlayerState.PLAYING;
});
}
}
}
}
Usage
The usage is pretty straightforward, just implement theAudioPlayerWidget
inside you widget specifying for:
Assets
- specify the filename + extension (e.g. my_audio.mp3) of the asset to reproduce
- set
isAsset
variable to true
Remote
Set the audio widget's url property with the url of the audio.
Local
Download the audio from the url, save it and set the file path as the audio widget's url property.
Bonus 🥳
You don't how how to fetch and save an audio from a provided url? don't worry we are here for this Just set _loadFilePath()
as the future of a FutureBuilder.
Future<String> _loadFilePath() async {
final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/the_name.mp3');
if (await _assetAlreadyPresent(file.path)) {
return file.path;
}
final bytes = await readBytes(Uri.parse(_remoteUrl));
await file.writeAsBytes(bytes);
return file.path;
}
Future<bool> _assetAlreadyPresent(String filePath) async {
final File file = File(filePath);
return file.exists();
}
Conclusions
As you may have noticed a simple implementation of an Audio Player is pretty straightforward and you won't have any problem to implement it in you app.
You can find the whole code inside this GitHub repo!
Where to go now?
You can dig deeper inside audioplayers plugin and do some experiments with seek and background handling or... just wait my next chapter of this Audio Player series see you soon!
Article written by the majestic Alessandro Viviani.
Top comments (0)