Getting to know Flutter: Simple audioplayer

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 run flutter 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_LATENCYyou 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 the AudioPlayerWidget 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.

Add a Comment

Your email address will not be published. Required fields are marked *