developer1996
developer1996

Reputation: 894

just_audio - Audio player after completer, start from the beginning

I created player audio and I used the just_audio plugin, I have a problem when the audio player completes and does not return to the beginning. I tried to use player.playerStateStream.listen() but when I click on the play icon it does not play the audio and the start duration does not change to 0, but when I manually move the slider it works, how can this problem be solved?

enter image description here

Edit:

What am I need?

I need after audio complete the slider back to the beginning and reset duration just like the first time I played the audio and pause the audio with onPressed play audio.

Code:

String currentTime = "", endTime = "";
double minDuration = 0, maxDuration = 0, currentDuration = 0;

void setAudio(String url) async {
    setState(() => isLoadAudio = true);
    await player.setUrl(url);
    currentDuration = minDuration;
    maxDuration = player.duration!.inMilliseconds.toDouble();
    setState(() {
      currentTime = getDuration(currentDuration);
      endTime = getDuration(maxDuration);
    });
    isPlaying = false;
    changeStatusPlaying();
    player.positionStream.listen((duration) {
      currentDuration = duration.inMilliseconds.toDouble();
      setState(() => currentTime = getDuration(currentDuration));
    });
    setState(() => isLoadAudio = false);
    player.playerStateStream.listen((state) {
      if (state.processingState == ProcessingState.completed) {
        setState(() {
          currentDuration = minDuration;
          if (isRepeating == true) {
            isPlaying = true;
          } else {
            isPlaying = false;
            isRepeating = false;
          }
        });
      }
    });
  }
void changeStatusPlaying() {
    setState(() => isPlaying = !isPlaying);
    isPlaying ? player.play() : player.pause();
    currentDuration == maxDuration ? isPlaying : !isPlaying;
  }

String getDuration(double value) {
    Duration duration = Duration(milliseconds: value.round());
    return [duration.inHours, duration.inMinutes, duration.inSeconds]
        .map((e) => e.remainder(60).toString().padLeft(2, "0"))
        .join(":");
  }

Slider(
   value: currentDuration,
   min: minDuration,
   max: maxDuration,
   onChanged: (v) {
     currentDuration = v;
     playAudio(currentDuration);
   },
),

Upvotes: 4

Views: 7578

Answers (4)

nickshoe
nickshoe

Reputation: 103

For those running into the "Cannot fire new event. Controller is already firing an event" exception, when asking the player to pause in the player completion event handler: I've tried to use a dear old (dirty) trick from JS programming and it seems to be working.

Let the audio player state machine end the completed event notification process before asking the player to change its state (with .pause(), .seek(), ...). This can be accomplished by deferring the calls to the "next event loop iteration", by using a Future (a Promise in JS world).

_audioPlayer.playbackEventStream.listen((event) async {
  if (event.processingState == just_audio.ProcessingState.completed) {
    if (_audioPlayer.playing) {
      /// Deferring "pause" and "seek" calls to the next tick is needed because of the "sync: true" in just_audio code:
      /// ```
      /// final _playbackEventSubject = BehaviorSubject<PlaybackEvent>(sync: true);
      /// ```
      Future.delayed(Duration.zero, () async {
        await _audioPlayer.pause();
        await _audioPlayer.seek(Duration.zero);
      });
    }
  }
});

Upvotes: 0

Dayveed Daniel
Dayveed Daniel

Reputation: 49

I solved this by pausing the audio once the processingState is completed, then player.seek(Duration.zero);

StreamBuilder<PlayerState>(
      stream: player.playerStateStream,
      builder: (context, snapshot) {
         if (snapshot.data?.processingState ==
             ProcessingState.completed) {
             player.pause();
              player.seek(Duration.zero);
            }

             return Icon(
                snapshot.data?.playing == true
                    ? Icons.pause
                    : Icons.play_arrow,
        );
   }),

Upvotes: 1

jartos
jartos

Reputation: 51

Try this:
        
bool _isPlaying = false;
bool _isCompleted = false;
Duration? _duration = Duration.zero;
Duration? _position = Duration.zero; 
                
After player has completed, move position to Duration(seconds: 0)   
                        
            Init state  
                            
                            @override
                              void initState() {
                                _initLoadFile();
                                // Listen player states
                                _audioPlayer.playerStateStream.listen((event) async {
                                  setState(() {
                                    _isPlaying = event.playing;
                                  });
                                  if (event.processingState == ProcessingState.completed) {
                                    setState(() {
                                      _isPlaying = false;
                                      _position = const Duration(seconds: 0);
                                      _isCompleted = true;
                                    });
                                  }
                                });
                                // Listen audio duration
                                _audioPlayer.durationStream.listen((newDuration) {
                                  setState(() {
                                    _duration = newDuration;
                                  });
                                });
                                // Listen audio position
                                _audioPlayer.positionStream.listen((newPosition) {
                                  setState(() {
                                    _position = newPosition;
                                  });
                                });
                                super.initState();
                              }
                            
                            
                    In widget
                            
                    ElevatedButton(
                                  onPressed: () async {
                                    if (!_isPlaying) {
                                      setState(() {
                                        _isPlaying = true;
                                        _isCompleted = false;
                                      });
                                      await _audioPlayer.play();
                                      if (_isCompleted) {
                                        await _audioPlayer.seek(const Duration(seconds: 0));
                                        await _audioPlayer.pause();
                                      }
                                    }
                                    if (_isPlaying) {
                                      setState(() {
                                        _isPlaying = false;
                                      });
                                      await _audioPlayer.pause();
                                    }
                                  },
                                  child: Icon(
                                    _isPlaying ? Icons.pause : Icons.play_arrow,
                                  ),
                                ),

Upvotes: 4

WSBT
WSBT

Reputation: 36323

Set the audio player to loop:

player.setLoopMode(LoopMode.one);

Full example (put your audio file at /assets/audio):

class MySimplePlayer extends StatefulWidget {
  final String name, asset;

  const MySimplePlayer(this.name, String filename)
      : this.asset = "assets/audio/$filename";

  @override
  _MySimplePlayerState createState() => _MySimplePlayerState();
}

class _MySimplePlayerState extends State<MySimplePlayer> {
  late AudioPlayer player;
  bool _ready = false;

  @override
  void initState() {
    super.initState();
    player = AudioPlayer();
    player.setLoopMode(LoopMode.one);    
    player.setAsset(widget.asset).then((_) {
      if (mounted) setState(() => _ready = true);
    });
  }

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

  @override
  Widget build(BuildContext context) {
    if (!_ready) return const SizedBox();

    return Card(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          const SizedBox(width: 16),
          Expanded(child: Text(widget.name)),
          if (!player.playing)
            IconButton(
              icon: Icon(Icons.play_arrow),
              onPressed: () async {
                player.play();
                setState(() {});
              },
            ),
          if (player.playing)
            IconButton(
              icon: Icon(Icons.pause),
              onPressed: () async {
                await player.pause();
                setState(() {});
              },
            ),
          IconButton(
            icon: Icon(Icons.stop),
            onPressed: () async {
              await player.stop();
              setState(() {});
            },
          ),
          const SizedBox(width: 16),
        ],
      ),
    );
  }
}

Upvotes: 0

Related Questions