#10FlutterChallenges - Part 3: Audio + LifeCycle service 🎵

Real scenarios & real problems. Learn how to make Audio and LifeCycle services to play music in your app!

#10FlutterChallenges - Part 3: Audio + LifeCycle service 🎵
Photo by Tudor Baciu / Unsplash

Welcome to the part 3 of the Flutter Challenges blog series. 🎉

Part 3 👋:

Today we are going to implement easy audio service to play local media asset.

Issue #1 – Audio service

This time, we will use an external library to handle play local audio assets. I'm going to use the latest version of the audioplayers. It's very easy to use and suit well for this easy example.

So first add the package to your pubspec.yml (in my case the current version is ^ 0.20.1):

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  audioplayers: ^0.20.1

Also, remember that if you want to use local resources, add the path in the assets key as follows:

flutter:
  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  assets:
    - assets/fonts/
    - assets/music/

With that, we are ready to go.

Below, I have prepared a ready-made audio_service.dart file using the audioplayers library:

abstract class AudioService {
  static AudioCache audioCache = AudioCache();
  static AudioPlayer audioPlayer = AudioPlayer(
    playerId: 'my_unique_playerId'
  );

  static void play(String filename) async {
    audioPlayer = await audioCache.play(filename);
  }

  static void stop() async => await audioPlayer.stop();
}

Let's go over what's going on here:

  • At the very beginning, we create a class for the service
  • We need to initialize two statics variables—one is AudioCache class responsible for playing from local resources—the other is an instance of the AudioPlayer class
  • In the AudioPlayer class as an argument, we provide a unique playerId in case there is more than one player instance
  • Then you can see the play method which is used to start playing the local audio file. We pass the path to the file as an argument.
  • The second method—⁣stop is simple to stop playing currently music

Issue #2 – Lifecycle service

Mainly our AudioService should work, but the problem with mobile applications is that they can change state, e.g., to run in the background. In this case, if we do not properly handle the music being played, it will continue to work even if our application is not currently in use.

To remedy this, we will create a LifeCycleService that will handle every possible state of our application.

And this is what LifeCycleService looks like:

import 'package:flutter/material.dart';
import 'package:samples/audio_service/audio_service.dart';

class LifeCycleService extends StatefulWidget {
  final Widget child;

  const LifeCycleService({Key? key, required this.child}) : super(key: key);

  @override
  _LifeCycleServicerState createState() => _LifeCycleServiceState();
}

class _LifeCycleServiceState extends State<LifeCycleService>
    with WidgetsBindingObserver {
  @override
  void initState() {
    WidgetsBinding.instance!.addObserver(this);
    super.initState();
  }

  @override
  void dispose() {
    WidgetsBinding.instance!.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        AudioService.play('music/track_01.mp3');
        break;
      case AppLifecycleState.inactive:
      case AppLifecycleState.paused:
      case AppLifecycleState.detached:
        AudioService.stop();
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: widget.child,
    );
  }
}

The magic happens in the didChangeAppLifecycleState method, which takes the application state as an argument. For them, they can be: resumed, inactive, paused and detached.

Specifically, for the purposes of this post, I have included an AudioService in the didChangeAppLifecycleState method to stop playing music in case the app switches to background and resumes when resuming.

The last thing to do is wrap the particular class with this service:

 @override
  Widget build(BuildContext context) {
    return const LifeCycleService(
      child: MaterialApp(

Summary:

And that's it 😎 Today you learned how to use the audioplayers package, how to create a LifeCycleService to handle different states of the application, and connect it with AudioService to handle playing music when the application state changes.

You can always check out the full source code below:

This is the source code of the Flutter Challenges series post: https://fun4code.com/10flutterchallanges-part3/
This is the source code of the Flutter Challenges series post: https://fun4code.com/10flutterchallanges-part3/ - audio_service.dart

What’s next?

And that's all for this challenge ✌

In the next chapter, we'll go through the nightmare of forms and try to add a custom validation and mask to the form input. So stay tuned for this series!


Thanks for reading ♥️♥️

…and stay tuned! 👊

Also check out the rest of the Flutter Challenges:

Part 1 — #10FlutterChallenges - Part 1: progress bar with gradient
Part 2 — #10FlutterChallenges - Part 2: switch button
Part 3 — #10FlutterChallenges - Part 3: Audio + LifeCycle service