0

I am using this package: https://pub.dev/packages/audioplayers

I have 2 problems, first of all the way the code is, when I press play the music starts, but when I press stop it does nothing, it is as if I were emitting a state that does not hit the same audioPlayer. Second, I don't know how I could change the PlayerState, the player state in the pad comes out as null, and I think that I am emitting these states wrong, could someone please show me in a practical way which is the correct way so I learn it? since I am relatively new to flutter_bloc.

Bloc

import 'dart:async';

import 'package:audioplayers/audioplayers.dart';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

part 'audioplayersbloc_event.dart';
part 'audioplayersbloc_state.dart';

class AudioPlayersBloc
    extends Bloc<AudioPlayersBlocEvent, AudioPlayersBlocState> {
  AudioPlayersBloc() : super(AudioPlayersBlocState());

  String URL =
      '******'; // i put "***" because private url but its a mp3 file

  @override
  Stream<AudioPlayersBlocState> mapEventToState(
    AudioPlayersBlocEvent event,
  ) async* {
    if (event is OnPlayPlayerRemote) {
    PlayerState plyState;
  audioPlayersBloc.state.audioPlayer.onPlayerStateChanged.listen((event) 
  {
     plyState = event;
  });
     yield this.state.copyWith(audioPlayer: await this.playMusic(), 
     playerState: plyState);

    } else if (event is OnStopPlayer) {
      await this.stopMusic();
      yield this.state.copyWith(audioPlayer: await this.stopMusic());
    }
  }

  playMusic() async {

    await this.state.audioPlayer.play(URL);
  }

  pauseMusic() async {

    await this.state.audioPlayer.pause();

  }

  stopMusic() async {

    await this.state.audioPlayer.stop();
    
  }
}

State

part of 'audioplayersbloc_bloc.dart';

class AudioPlayersBlocState {
  final AudioPlayer audioPlayer = AudioPlayer();
  final PlayerState playerState = PlayerState.PAUSED;
  final AudioCache audioCache = AudioCache();

  AudioPlayersBlocState(
      {AudioPlayer? audioPlayer,
      PlayerState? playerState,
      AudioCache? audioCache});

  AudioPlayersBlocState copyWith(
          {AudioPlayer? audioPlayer,
          PlayerState? playerState,
          AudioCache? audioCache}) =>
      AudioPlayersBlocState(
          audioPlayer: audioPlayer ?? this.audioPlayer,
          playerState: playerState ?? this.playerState,
          audioCache: audioCache ?? this.audioCache);
}

Event

part of 'audioplayersbloc_bloc.dart';

@immutable
abstract class AudioPlayersBlocEvent {}

class OnPlayPlayerRemote extends AudioPlayersBlocEvent {}


class OnStopPlayer extends AudioPlayersBlocEvent {}

The button where i play the music in the home page, is only a button, it is a simple player

IconButton(
                  icon: Icon(Icons.play_arrow),

                  onPressed: () {
                    context.read<AudioPlayersBloc>().add(OnPlayPlayerRemote());
                  },
                ),
                IconButton(
                  icon: Icon(Icons.stop),

                  onPressed: () {
                    context.read<AudioPlayersBloc>().add(OnStopPlayer());
                  },
                ),
Dubbain
  • 73
  • 1
  • 8

1 Answers1

0

First approach that should be fixed is, saving AudioPlayer, AudioCache and PlayerState. As a best practice, business logic related stuff should not be carried in a state object. In that case, you move that stuff from bloc and state to your screen (widget).

Note: Answer is not covering all scenarios

Move

final AudioPlayer audioPlayer = AudioPlayer();
final PlayerState playerState = PlayerState.PAUSED;
final AudioCache audioCache = AudioCache();

to HomePage.

Steps:

  • We will add a BlocConsumer to start/stop player.
  • yield proper states to start/stop
  • edit events

You can review BlocConsumer usage from here

Create an enum to carry states:

enum PlayerState {playing, stopped}

We will carry these states in our bloc state like:

class AudioPlayersBlocState extends Equatable {
  PlayerState playerState;

  AudioPlayersBlocState({this.playerState});

  AudioPlayersBlocState copyWith(
          {PlayerState playerState}) =>
      AudioPlayersBlocState(playerState: playerState ?? this.playerState);
     
      @override
      List<Object> get props => [playerState];
}

Now, we are carrying player state on our state, and by using Equatable our bloc can now knows when to update state and will make it perfomant since new incoming states won't update view if nothing has changed.

And we can update events as:

@immutable
abstract class AudioPlayersBlocEvent {}

class PlayerStateChanged extends AudioPlayersBlocEvent {
    PlayerState playerState;

    PlayerStateChanged({this.playerState});
}

Then we will yield with new state structure:

@override
  Stream<AudioPlayersBlocState> mapEventToState(
    AudioPlayersBlocEvent event,
  ) async* {
    if (event is PlayerStateChanged) {
        if (event.playerState == PlayerState.playing) {
            yield this.state.copyWith(playerState: PlayerState.playing);
        } else if (event.playerState == PlayerState.stopped) {
            yield this.state.copyWith(playerState: PlayerState.stopped);
        }
    }
  }

Also we can add functions adding events:

  playMusic() {
    add(PlayerStateChanged(playerState: PlayerState.playing));
  }

  stopMusic() {
    add(PlayerStateChanged(playerState: PlayerState.stopped));
  }

Then we should update your button like below:

onPressed: () {
                    context.read<AudioPlayersBloc>().playMusic();
                  },

Then we will reflect to these changes in view. (We are holding AudioPlayer in screen, we could make it in the bloc but for sake of keeping simple, did it that way)

  BlocConsumer< AudioPlayersBloc, AudioPlayersBlocState >(
      listener: (context, state) {
    if (state.playerState == PlayerState.playing) {
        audioPlayer.play();
    } else if (state.playerState == PlayerState.stopped) {
        audioPlayer.stop();
    }
  },
  builder: (context, state) {
    // return a widget here based on Blocs's state
  }
)