I recently needed to create a reusable Audio Player for a Flutter App I was working on. After a bit of research I decided to use the Just Audio plugin. As is often the case I found that the documentation was useful for standalone implementations, but not entirely helpful for reusability.
The first step is to include Just Audio in the project as follows.
flutter pub add just_audio
Once this was done I created a simple object to play an audio file from a URL as follows
import 'dart:ui'; // We'll need this later
import 'package:just_audio/just_audio.dart';
class Player {
final player = AudioPlayer();
Duration? _audioLength;
bool _isLoaded = false;
Future<bool> load (String url) async {
_isLoaded = false;
_audioLength = await player.setUrl(url);
if (_audioLength!.inSeconds > 0) {
_isLoaded = true;
}
return _isLoaded;
}
void playPause() {
if (_isLoaded) {
if (player.playing) {
player.pause();
}
else {
player.play();
}
}
}
bool isPlaying() {
if (_isLoaded) {
return player.playing;
}
return false;
}
}
DartThe audio length variable is a useful test to ensure the audio has been loaded. It’s declared as a class member here, because I intend to use it later.
Next I created a simple UI widget to control the player
import 'package:flutter/material.dart';
import 'myaudioplayer.dart';
void main() {
runApp(AudioApp());
}
class AudioApp extends StatefulWidget {
const AudioApp({super.key});
@override
AudioAppState createState() => AudioAppState();
}
class AudioAppState extends State<AudioApp> {
Player p = Player();
@override
void initState() {
super.initState();
// Replace the URL with your own audio URL!
p.load("https://github.com/rafaelreis-hotmart/Audio-Sample-files/raw/master/sample.mp3");
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
children: [
Expanded(
child: Center (child: Text("App Body here")),
),
Container(
color: Colors.teal[900],
height: 60,
child: Row(
children: [
IconButton(
onPressed: () {
setState(() {
p.playPause();
});
},
icon: Icon(
p.isPlaying() ? Icons.pause : Icons.play_arrow,
color: Colors.white,
)
)
]
)
)
]
)
)
);
}
}
DartThe initState function loads the AudioPlayer with the URL of an audio file. The IconButton in the widget calls playPause to start and stop the audio. It is enclosed within a call to setState so that every time it is pressed the widget calls isPlaying to test whether the audio is playing and changes the icon face to play or pause accordingly.
The next step is to add a seek bar. Before we add the control to the UI we need to implement a couple of callback methods for the Player object. The first of these is to track the progress of the playback, and the second is to notify us when the audio completes. I’ve used a typedef to declare onPositionChanged as I am passing two parameters back. I could also have used VoidCallback and let the callback access the variables from the player, but that would require a couple of getter functions, so this way seemed neater.
typedef void onPositionChanged(Duration position, Duration length);
class Player {
final player = AudioPlayer();
Duration? _audioLength;
bool _isLoaded = false;
onPositionChanged positionListener;
VoidCallback completionListener;
Player({required this.positionListener, required this.completionListener});
Future<bool> load (String url) async {
DartIn order to pass the progress and completion data back to the UI we also need to call these methods at the appropriate time. We’ve altered the constructor to pass the callback references in. Next we need to call the callbacks with the position data and the completions status. To do this we need to add two listeners to the audio player after the URL is loaded. Note that when calling the completion listener we also pause the player (which will ensure that the play button face is shown) and set the seek position to zero ready to play again if needed.
_audioLength = await player.setUrl(url);
if (_audioLength!.inSeconds > 0) {
_isLoaded = true;
player.positionStream.listen((position) {
positionListener(position, _audioLength!);
});
player.playerStateStream.listen((state) {
if (state.processingState == ProcessingState.completed) {
player.pause();
player.seek(Duration.zero);
completionListener();
}
});
}
return _isLoaded;
DartNow we need to implement the callbacks in the UI object.
To control the seek bar I have declared two new private variables, one to hold the current audio position, the other to hold the length. I have also moved the declaration of the player object into the initState function to send it references to the listener functions. Again note that the body of these functions are enclosed in setState to ensure that the Slider position and play/pause button face are updated.
class AudioAppState extends State<AudioApp> {
double _pos = 0;
double _max = 0;
late Player p;
@override
void initState() {
super.initState();
p = Player(positionListener: audioPositionChanged, completionListener: audioFinished, );
p.load("https://github.com/rafaelreis-hotmart/Audio-Sample-files/raw/master/sample.mp3");
}
void audioPositionChanged(Duration position, Duration length) {
setState(() {
_pos = position.inMilliseconds.toDouble();
_max = length.inMilliseconds.toDouble();
});
}
void audioFinished() {
setState(() {
_pos = 0;
});
}
DartNext we can add the seek bar to the UI using a Slider control. The value of the Slider is the current position, and the max is set to the length of the audio clip.
child: Row(
children: [
IconButton(
onPressed: () {
setState(() {
p.playPause();
});
},
icon: Icon(
p.isPlaying() ? Icons.pause : Icons.play_arrow,
color: Colors.white,
),
),
Expanded(
child: Slider(
inactiveColor: Colors.teal[800],
activeColor: Colors.white,
value: _pos,
min: 0,
max: _max,
onChanged: (v) { /* To do */ }
)
)
]
)
DartThe final piece of the puzzle is to enable the drag handle of the progress bar to seek to the right place in the audio. We can do this by adding a seek method to the Player object.
void seek(double v) {
if (isLoaded()) {
player.seek(Duration(milliseconds: v.toInt()));
}
}
DartThis can be called from onChanged in the slider object we’ve just declared in the UI Object
onChanged: (v) { p.seek(v); }
DartThere is one outstanding issue that needs to be fixed. Currently the seek bar max is only set by the progress listener, which means that we can’t use the seek bar until we’ve hit the play button. We can fix this by moving the load command into an async function and getting the total length from the player object.
@override
void initState() {
super.initState();
p = Player(positionListener: audioPositionChanged, completionListener: audioFinished);
load();
}
void load() async {
if (await p.load("https://github.com/rafaelreis-hotmart/Audio-Sample-files/raw/master/sample.mp3")) {
_max = p.player.duration!.inMilliseconds.toDouble();
}
}
DartAnd there it is, a simple reusable audio player object and associated UI.

Leave a Reply