flutterdartflutter-audioplayers

Flutter Bloc with audioplayers


Almost a day of figuring way how to do it and I need your help.

I've created an app that would have a state with an instance of audioplayers and using flutter_bloc.

Problems:

  1. The state player plays but the Widget MusicPlayer doesn't rebuild the widget with the BlocBuilder
  2. I'm trying also to get the currentPosition and duration of the music I'm playing and displaying it using LinearPercentIndicator from linear_percent_indicator package but can't seem to find a solution because rebuild doesn't work.
  3. What am I missing?

Here's what I have so far:

Bloc

class AudioPlayerBloc extends Bloc<AudioPlayerEvents, MusicPlayerState> {
  @override
  MusicPlayerState get initialState => MusicPlayerState(
        player: AudioPlayer(),
        episode: Episode(),
      );

  @override
  Stream<MusicPlayerState> mapEventToState(AudioPlayerEvents event) async* {
    if (event is InitializePlayer) {
      this.currentState.episode = event.episode;
      this.dispatch(PlayPlayer());
      yield this.currentState;
    }

    if (event is PlayPlayer) {
      this.play(this.currentState.episode.source);
    }

    if (event is PlayRemote) {
      this.currentState.player.stop();

      this.currentState.player.play(event.remoteURL);
      yield this.currentState;
    }

    if (event is ShowPlayer) {
      yield this.currentState;
    }

    if (event is HidePlayer) {
      yield this.currentState;
    }
  }

  void play(String remoteURL) {
    this.dispatch(PlayRemote(remoteURL));
  }

  void stop() async {
    await this.currentState.player.stop();
  }

  void pause() async{
    await this.currentState.player.pause();
  }

  void resume(){
    this.currentState.player.resume();
  }

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

Event

abstract class AudioPlayerEvents {}

class InitializePlayer extends AudioPlayerEvents {
  Episode episode;

  InitializePlayer(this.episode);
}

class PlayRemote extends AudioPlayerEvents {
  final String remoteURL;

  PlayRemote(this.remoteURL);
}

class PlayPlayer extends AudioPlayerEvents {}

class ShowPlayer extends AudioPlayerEvents {}

class HidePlayer extends AudioPlayerEvents {}

State

import 'package:audioplayers/audioplayers.dart';

class MusicPlayerState {
  AudioPlayer player;
  Episode episode; // My Custom Class

  MusicPlayerState({
    this.player,
    this.episode,
  });
}

main.dart

@override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<AudioPlayerBloc>(
          builder: (_) => AudioPlayerBloc(),
        )
      ],
      child: MaterialApp(
        navigatorObservers: [],
        initialRoute: HomeScreen.id,
        routes: {
          HomeScreen.id: (context) => HomeScreen(app: widget.app),
        },
      ),
    );
  }
}

MusicPlayer.dart <-- my Player Widget

class MusicPlayer extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    final AudioPlayerBloc audioPlayerBloc =
        BlocProvider.of<AudioPlayerBloc>(context);

    return BlocBuilder<AudioPlayerBloc, MusicPlayerState>(
      bloc: audioPlayerBloc,
      builder: (context, state) {
        return Container(
          height: 200.0,
          color: Colors.cyan[200],
          child: Padding(
            padding: const EdgeInsets.only(top: 20.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                Column(
                  children: <Widget>[
                    Text("${state.episode.name}"),
                    Row(
                      children: <Widget>[
                        Expanded(
                          flex: 1,
                          child: FutureBuilder<int>(
                            future: audioPlayerBloc.currentState.player.getCurrentPosition(),
                              builder: (context, AsyncSnapshot<int> snapshot) {
                                double seconds = snapshot.data / 1000;
                                if (snapshot.hasData) {
                                  return Text("${printDuration(Duration(seconds: seconds.toInt()), abbreviated: true)}");
                                } else {
                                  return Text('Calculating..');
                                }
                              },
                            ),
                        ),
                        Expanded(
                          flex: 3,
                          child: LinearPercentIndicator(
                            lineHeight: 7.0,
//                            percent: this.currentPosition / this.fileDuration,
                            percent: 0.3,
                            backgroundColor: Colors.grey,
                            progressColor: Colors.blue,
                          ),
                        ),
                        Expanded(
                          flex: 1,
                          child: FutureBuilder<int>(
                            future: audioPlayerBloc.currentState.player.getDuration(),
                            builder: (context, AsyncSnapshot<int> snapshot) {
                              double seconds = snapshot.data / 1000;
                              if (snapshot.hasData) {
                                return Text("${printDuration(Duration(seconds: seconds.toInt()), abbreviated: true)}");
                              } else {
                                return Text('Calculating..');
                              }
                            },
                          ),
                        ),
                      ],
                    ),
                    Text(state.player.state.toString()),
                    FlatButton(
                      onPressed: () {
                        print('close here');
                        Navigator.of(context).pop();
                      },
                      child: Icon(
                        Icons.close,
                        color: Colors.black.withOpacity(0.5),
                      ),
                    ),
                    Row(
                      children: <Widget>[
                        FlatButton(
                          onPressed: () {
                            audioPlayerBloc.pause();
                          },
                          child: Text('Pause Player'),
                        ),
                        FlatButton(
                          onPressed: () {
                            audioPlayerBloc.resume();
                          },
                          child: Text('Resume Player'),
                        ),
                        FlatButton(
                          onPressed: () {
                            audioPlayerBloc.stop();
                          },
                          child: Text('Stop Player'),
                        ),
                      ],
                    )
                  ],
                )
              ],
            ),
          ),
        );
      },
    );
  }
}

HomeScreen.dart <-- my first screen

@override
  Widget build(BuildContext context) {

    final AudioPlayerBloc audioPlayerBloc = BlocProvider.of<AudioPlayerBloc>(context);

    return MultiBlocProvider(
      providers: [
        BlocProvider<AudioPlayerBloc>(
          builder: (_) => AudioPlayerBloc(),
        )
      ],
      child: Scaffold(
          appBar: AppBar(
            title: Text('Global Audio Player'),
          ),
          body: Container(
            child: BlocBuilder<AudioPlayerBloc, MusicPlayerState>(
              builder: (context, state) {
                return Column(
                  children: <Widget>[
                    Flexible(
                        child: getListView(context)
                    ),
                    displayPlayer(state.player), // Here I'm trying to display the player when the AudioPlayerState is PLAYING
                  ],
                );
              },
            ),
          ),
      ),
    );

  }

Global function

 Widget displayPlayer(AudioPlayer player){
    return MusicPlayer();
    if(player.state == AudioPlayerState.PLAYING) {
      return MusicPlayer();
    }
    return Container();
  }

EpisodesScreen.dart <-- ListView of the Episodes

class _EpisodesScreenState extends State<EpisodesScreen> {
  @override
  void initState() {
    super.initState();

    print(widget.series);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text(widget.series.name)),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            Expanded(
              flex: 0,
              child: Image.network(widget.series.image.original),
            ),
            Expanded(
              child: getListView(context),
            ),
            Expanded(
              child: BlocBuilder<AudioPlayerBloc, MusicPlayerState>(
                builder: (context, state) {
                  return displayPlayer(state.player);
                },
              ),
            )
          ],
        ));
  }


  Widget getListView(BuildContext context) {
    List<Episode> episodesList = widget.series.episodes;

    final AudioPlayerBloc audioPlayerBloc =
        BlocProvider.of<AudioPlayerBloc>(context);

    var listView = ListView.separated(
      itemCount: episodesList.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(episodesList[index].name),
          trailing: BlocBuilder<AudioPlayerBloc, MusicPlayerState>(
            builder: (context, state) {
              return FlatButton(
                onPressed: () {
                  audioPlayerBloc.dispatch(InitializePlayer(episodesList[index]));
                },
                child: Icon(
                  Icons.play_arrow,
                  color: Colors.black87,
                ),
              );
            },
          ),
        );
      },
      separatorBuilder: (BuildContext context, int index) {
        return Divider(
          height: 1.0,
          color: Colors.black12,
        );
      },
    );

    return listView;
  }
}

Solution

  • I missed the yield * part of it and it is now working.

    Stream<Duration> currentPosition() async* {
      yield* this.currentState.audioPlayer.onAudioPositionChanged;
    }
    
    Stream<Duration> fileDuration() async* {
      yield* this.currentState.audioPlayer.onDurationChanged;
    }