DEV Community

loading...
Cover image for Floating Audio Player in Flutter

Floating Audio Player in Flutter

Paurakh Sharma Humagain
Full-StackOverflow developer. I love instructing computers to do stuffs.
・5 min read

Floating Audio Player in Flutter

Today, we are going to learn how to build a floating audio player in Flutter. This method is not limited to the audio player but can use used for the video player as well.

Floating Audio Player

First of all, let's create a new flutter project.

flutter create floating_audio_player
Enter fullscreen mode Exit fullscreen mode

Replace all the codes in main.dart with the following:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.teal,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Floating media player')),
      body: ListView.builder(itemBuilder: (context, index) {
        return Card(
          child: Container(
            height: 200,
            child: Center(
                child: Text(
              'Item $index',
              style: TextStyle(
                color: index.isEven ? Colors.white : Colors.green,
              ),
            )),
          ),
          color: index.isEven ? Colors.green : Colors.white70,
        );
      }),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

Here we are rendering an infinite list with cards inside for demonstration purposes only.

Infinite ListView

Now we are going to create a FAB (Floating Action Button) which can transform into a container. We are going to use AnimationSwitcher for a slick animation.

Update your main.dart and add floatingActionButton

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.teal,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  bool isPlayerOpened = false;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Floating media player')),
      body: ListView.builder(itemBuilder: (context, index) {
        return Card(
          child: Container(
            height: 200,
            child: Center(
                child: Text(
              'Item $index',
              style: TextStyle(
                color: index.isEven ? Colors.white : Colors.green,
              ),
            )),
          ),
          color: index.isEven ? Colors.green : Colors.white70,
        );
      }),
      floatingActionButton: floatingActionItem,
    );
  }

  get floatingActionItem {
    Widget floatingPlayer = GestureDetector(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          SizedBox(width: 35,),
          Container(
            height: 125,
            width: 325,
            color: Colors.teal,
          ),
        ],
      ),
      onTap: () {
        setState(() {
          isPlayerOpened = false;
        });
      },
    );

    Widget floatingActionButton = FloatingActionButton(
      onPressed: () {
        setState(() {
          isPlayerOpened = true;
        });
      },
      child: Icon(Icons.play_arrow_outlined),
    );

    return AnimatedSwitcher(
      reverseDuration: Duration(milliseconds: 0),
      duration: const Duration(milliseconds: 200),
      transitionBuilder: (Widget child, Animation<double> animation) {
        return ScaleTransition(child: child, scale: animation);
      },
      child: isPlayerOpened ? floatingPlayer : floatingActionButton,
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

A bool isPlayerOpened is used to show a play button or a container using an AnimatedSwitcher.

AnimatedSwitcher

Let's move the floating audio player code into FloatingAudioPlayer.dart, add the following code inside it to show a player UI.

// floating_audio_player.dart

import 'package:flutter/material.dart';

class FloatingAudioPlayer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        SizedBox(width: 35),
        Container(
            height: 75,
            width: 325,
            decoration: BoxDecoration(
              color: Colors.teal.withOpacity(0.8),
              borderRadius: BorderRadius.circular(10.0),
            ),
            child: Row(
              children: [
                pauseButton(),
                Expanded(child: progressSlider()),
              ],
            )),
      ],
    );
  }

  Widget pauseButton() {
    return IconButton(
      icon: Icon(Icons.pause_outlined),
      onPressed: pause,
    );
  }

  pause() {}

  Widget progressSlider() {
    const textStyle = TextStyle(color: Colors.white);
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 25.0),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text('00:00', style: textStyle),
              Text('10:10', style: textStyle),
            ],
          ),
        ),
        SizedBox(
          width: 300,
          height: 25,
          child: Slider(
            min: 0,
            max: 280,
            value: 150,
            activeColor: Colors.white,
            inactiveColor: Colors.grey,
            onChanged: (value) {},
          ),
        )
      ],
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Make sure you replace child inside GestureDetector() in main.dart with newly created FloatingAudioPlayer().

Finally, we are going to implement playing audio. For this I am using just_audio, you can use your favorite audio player.

Add just_audio: ^0.5.6 in your pubspec.yml's dependencies.

Note: Make sure you restart your app after installing dependency which uses platform channels.

  • For iOS: Make sure you add the following inside your Info.plist file. Even though we do not use the Microphone, iOS requires us to add this.
<key>NSMicrophoneUsageDescription</key>
<string>... explain why you use (or don't use) the microphone ...</string>
Enter fullscreen mode Exit fullscreen mode

Update floating_audio_player.dart and all audio playing functionality.

// floating_audio_player.dart

import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';

class FloatingAudioPlayer extends StatefulWidget {
  final Function onPaused;
  FloatingAudioPlayer({@required this.onPaused});
  @override
  _FloatingAudioPlayerState createState() => _FloatingAudioPlayerState();
}

class _FloatingAudioPlayerState extends State<FloatingAudioPlayer> {
  Duration _duration;
  AudioPlayer _audioPlayer;
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        SizedBox(width: 35),
        Container(
          height: 75,
          width: 325,
          decoration: BoxDecoration(
            color: Colors.teal.withOpacity(0.8),
            borderRadius: BorderRadius.circular(10.0),
          ),
          child: StreamBuilder(
            stream: _audioPlayer.positionStream,
            builder: (context, snapshot) {
              if (snapshot.hasData && _duration != null) {
                if (_audioPlayer.processingState == ProcessingState.completed) {
                  _audioPlayer.stop();
                  Future.delayed(Duration(seconds: 0), widget.onPaused);
                }
                return Row(
                  children: [
                    if (_audioPlayer.playerState.playing)
                      pauseButton()
                    else
                      playButton(),
                    Expanded(child: progressSlider(snapshot.data)),
                  ],
                );
              }

              return Center(child: CircularProgressIndicator());
            },
          ),
        ),
      ],
    );
  }

  Widget playButton() {
    return IconButton(
      icon: Icon(Icons.play_arrow_outlined),
      onPressed: playAudio,
      iconSize: 34,
      color: Colors.white,
    );
  }

  Widget pauseButton() {
    return IconButton(
      icon: Icon(Icons.pause_outlined),
      onPressed: pause,
      iconSize: 34,
      color: Colors.white,
    );
  }

  pause() {
    _audioPlayer.pause();
    Future.delayed(Duration(milliseconds: 500), widget.onPaused);
  }

  Widget progressSlider(Duration position) {
    const textStyle = TextStyle(color: Colors.white);
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 25.0),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(position.toString().substring(2, 7), style: textStyle),
              Text(_duration.toString().substring(2, 7), style: textStyle),
            ],
          ),
        ),
        SizedBox(
          width: 300,
          height: 25,
          child: Slider(
            min: 0,
            max: _duration.inMilliseconds.toDouble(),
            value: position.inMilliseconds.toDouble(),
            activeColor: Colors.white,
            inactiveColor: Colors.grey,
            onChanged: (value) {
              _audioPlayer.seek(Duration(milliseconds: value.floor()));
            },
          ),
        )
      ],
    );
  }

  @override
  void initState() {
    super.initState();
    initAudio();
  }

  @override
  void dispose() {
    super.dispose();
    _audioPlayer?.dispose();
  }

  initAudio() async {
    _audioPlayer = AudioPlayer();
    final duration = await _audioPlayer.setUrl(
      'https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3',
    );

    setState(() {
      _duration = duration;
    });

    playAudio();
  }

  playAudio() {
    _audioPlayer.play();
  }
}
Enter fullscreen mode Exit fullscreen mode

Here onPaused is passed inside the FloatingAudioPlayer() widget so that when the user pauses the audio or the audio is played completely, the floating audio player is closed.

Likewise, we need to update our main.dart to implement onPaused

// main.dart

import 'package:flutter/material.dart';

import 'floating_audio_player.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.teal,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  bool isPlayerOpened = false;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Floating media player')),
      body: ListView.builder(itemBuilder: (context, index) {
        return Card(
          child: Container(
            height: 200,
            child: Center(
                child: Text(
              'Item $index',
              style: TextStyle(
                color: index.isEven ? Colors.white : Colors.green,
              ),
            )),
          ),
          color: index.isEven ? Colors.green : Colors.white70,
        );
      }),
      floatingActionButton: floatingActionItem,
    );
  }

  onPaused() {
    setState(() {
      isPlayerOpened = false;
    });
  }

  get floatingActionItem {
    Widget floatingPlayer = FloatingAudioPlayer(onPaused: onPaused);

    Widget floatingActionButton = FloatingActionButton(
      onPressed: () {
        setState(() {
          isPlayerOpened = true;
        });
      },
      child: Icon(Icons.play_arrow_outlined),
    );

    return AnimatedSwitcher(
      reverseDuration: Duration(milliseconds: 0),
      duration: const Duration(milliseconds: 200),
      transitionBuilder: (Widget child, Animation<double> animation) {
        return ScaleTransition(child: child, scale: animation);
      },
      child: isPlayerOpened ? floatingPlayer : floatingActionButton,
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Floating Audio Player

That's it 🎉🥳, we have created a floating audio player. As mentioned earlier this is not limited to the audio player but can be used for anything.

Now go and create awesome stuff with it.

You can find the source code here

If you have any suggestions or comments feel free to write down in the comment section, follow me on Twitter @PaurakhSharma for new contents or to suggest one ;)

Happy Coding!

Discussion (0)