bluefireteam / audioplayers

A Flutter package to play multiple audio files simultaneously (Android/iOS/web/Linux/Windows/macOS)
https://pub.dartlang.org/packages/audioplayers
MIT License
1.97k stars 838 forks source link

AudioPlayer seek issue #1597

Closed krishnaprasadgandrath-nooor closed 10 months ago

krishnaprasadgandrath-nooor commented 1 year ago

Checklist

Current bug behaviour

Im using audioplayer to play audio files in flutterweb. whenever im trying to create a new player and play from specific duration it starts from 0 instead of provided duration, though i'm passing duration to it

Below is the code : _audioPlayer.play(UrlSource(url), position: position);

Expected behaviour

I want to play the audio from specification duration of the audio clip

Steps to reproduce

  1. Create a service which holds the main audio player and create multiple widgets which consume this service to avoid concurrent audio players.
  2. Play any audio with specific duration just after player is initialized

Code sample

Code sample audio_player_test_screen.dart ```dart import 'dart:async'; import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; import 'audio_player_service.dart'; void main(List args) { runApp(const MaterialApp( home: AudioPlayerTestScreen(), )); } class AudioPlayerTestScreen extends StatefulWidget { const AudioPlayerTestScreen({super.key}); @override State createState() => _AudioPlayerTestScreenState(); } class _AudioPlayerTestScreenState extends State { final String audio1 = 'https://cdn.pixabay.com/download/audio/2023/03/25/audio_cce6ddcdc9.mp3?filename=duckface-143956.mp3'; late final String audio2 = audio1; // 'https://download.samplelib.com/mp3/sample-3s.mp3'; @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ /* const DecoratedBox( decoration: BoxDecoration( image: DecorationImage( image: NetworkImage( 'https://media1.giphy.com/media/duzpaTbCUy9Vu/giphy.webp?cid=ecf05e47pjgtad7sxhz4w26pkb31102aw2eujk00uto78678&ep=v1_gifs_search&rid=giphy.webp&ct=g'))), child: SizedBox.square(dimension: 300.0), ), */ AudioTile(audioPlayerService: liquidAudioPlayer, audioSrc: audio1), AudioTile(audioPlayerService: liquidAudioPlayer, audioSrc: audio2), ], ), ), ); } } class AudioTile extends StatefulWidget { final String audioSrc; final LAudioPlayerService audioPlayerService; const AudioTile({super.key, required this.audioSrc, required this.audioPlayerService}); @override State createState() => _AudioTileState(); } class _AudioTileState extends State { Duration position = Duration.zero; Duration duration = Duration.zero; PlayerState state = PlayerState.stopped; late StreamSubscription posSub, stateSub, duratSub; final String thisPlayerId = const Uuid().v1(); @override void initState() { super.initState(); initSubscriptions(); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: DecoratedBox( decoration: BoxDecoration( color: Colors.white, boxShadow: [BoxShadow(blurRadius: 10.0, spreadRadius: 13.0, color: Colors.black.withAlpha(100))]), child: SizedBox.square( dimension: 300.0, child: Row( children: [ IconButton( onPressed: state == PlayerState.playing ? _pause : _play, icon: Icon(state == PlayerState.playing ? Icons.pause : Icons.play_arrow)), Expanded( child: Slider( value: position != Duration.zero && duration != Duration.zero ? position.inSeconds / duration.inSeconds : 0, onChanged: _seek, )), SizedBox( width: 50, child: FittedBox( child: Text("${position.inSeconds}/${duration.inSeconds}"), ), ) ], ), ), ), ); } void _play() { widget.audioPlayerService.playAudio(playerId: thisPlayerId, url: widget.audioSrc, position: position); } void _pause() { widget.audioPlayerService.pauseAudio(playerId: thisPlayerId); } void _seek(double value) { final position = Duration(seconds: (value * duration.inSeconds).toInt()); } void initSubscriptions() { widget.audioPlayerService.addListener(() { if (widget.audioPlayerService.activePlayerId != thisPlayerId && state == PlayerState.playing) { state = PlayerState.paused; setState(() {}); } }); posSub = widget.audioPlayerService.positionStream.listen((event) { if (widget.audioPlayerService.activePlayerId != thisPlayerId) return; position = event; setState(() {}); }); duratSub = widget.audioPlayerService.durationStream.listen((event) { if (widget.audioPlayerService.activePlayerId != thisPlayerId) return; duration = event; setState(() {}); }); stateSub = widget.audioPlayerService.playerStateStream.listen((event) { if (widget.audioPlayerService.activePlayerId != thisPlayerId) return; state = event; setState(() {}); }); } } ``` audio_player_service.dart ```dart import 'dart:async'; import 'dart:developer'; import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/foundation.dart'; const String kUninitializedPlayer = 'uninitializedPlayer'; abstract class BaseAudioPlayerService { ///Fields String get activePlayerId; AudioPlayer get audioPlayer; Stream get positionStream; Stream get durationStream; Stream get playerStateStream; ///Methods Future _updatePlayer({required String playerId}); Future playAudio({required String playerId, required String url, Duration? position}); Future pauseAudio({required String playerId}); Future stopAudio({required String playerId}); void setVolume({required String playerId, required double value}); void seekTo({required String playerId, required Duration position}); } class LAudioPlayerService extends ChangeNotifier implements BaseAudioPlayerService { ///Fields String _currentPlayerId = ''; AudioPlayer _audioPlayer = AudioPlayer(playerId: kUninitializedPlayer); final StreamController _durationStreamController = StreamController.broadcast(); final StreamController _positionStreamController = StreamController.broadcast(); final StreamController _playerStateStreamController = StreamController.broadcast(); StreamSubscription? _durationSub, _positionSub, _playerStateSub; /* final Stream _durationStream = const Stream.empty(); final Stream _positionStream = const Stream.empty(); final Stream _playerStatestream = const Stream.empty(); */ ///Getters @override String get activePlayerId => _currentPlayerId; @override AudioPlayer get audioPlayer => _audioPlayer; @override Stream get durationStream => _durationStreamController.stream; @override Stream get positionStream => _positionStreamController.stream; @override Stream get playerStateStream => _playerStateStreamController.stream; @override Future playAudio({required String playerId, required String url, Duration? position}) async { if (_currentPlayerId == playerId) { _audioPlayer.play(UrlSource(url), position: position); notifyListeners(); } else { await pauseAudio(playerId: _currentPlayerId); await _updatePlayer(playerId: playerId); _audioPlayer.play(UrlSource(url), position: position); position != null ? _audioPlayer.seek(position) : null; /// _durationSub?.resume(); _positionSub?.resume(); _playerStateSub?.resume(); notifyListeners(); } } @override Future pauseAudio({required String playerId}) async { log("In Pause Audio"); if (_currentPlayerId == playerId) { log("Paused Audio"); await _audioPlayer.pause(); notifyListeners(); return true; } else { return false; } } @override Future stopAudio({required String playerId}) async { if (_currentPlayerId == playerId) { await _audioPlayer.stop(); notifyListeners(); return true; } else { return false; } } @override Future _updatePlayer({required String playerId}) async { log("In Update Player"); if (_audioPlayer.playerId == playerId) return _audioPlayer; if (_audioPlayer.state == PlayerState.playing) { await pauseAudio(playerId: _currentPlayerId); } _disposeOldPlayer(); _initializeNewPlayer(playerId); /// // notifyListeners(); return _audioPlayer; } @override void setVolume({required String playerId, required double value}) { if (_currentPlayerId != playerId) return; _audioPlayer.setVolume(value); } @override void seekTo({required String playerId, required Duration position}) { if (_currentPlayerId != playerId) return; _audioPlayer.seek(position); } void _disposeOldPlayer() { _durationSub?.cancel(); _positionSub?.cancel(); _playerStateSub?.cancel(); _audioPlayer.pause(); // _audioPlayer.dispose(); } void _initializeNewPlayer(String playerId) { _audioPlayer = AudioPlayer(playerId: playerId); _currentPlayerId = _audioPlayer.playerId; log("Service ctive player changed to $_currentPlayerId"); /* _durationStreamController.add(Duration.zero); _positionStreamController.add(Duration.zero); _playerStateStreamController.add(PlayerState.stopped); */ _durationSub = _audioPlayer.onDurationChanged.listen((duration) { _durationStreamController.add(duration); }) ..pause(); _positionSub = _audioPlayer.onPositionChanged.listen((pos) { _positionStreamController.add(pos); }) ..pause(); _playerStateSub = _audioPlayer.onPlayerStateChanged.listen((state) { _playerStateStreamController.add(state); }) ..pause(); } void resetPlayer({required String playerId}) { _disposeOldPlayer(); _updatePlayer(playerId: ''); } } final LAudioPlayerService liquidAudioPlayer = LAudioPlayerService(); ```

Affected platforms

web

Platform details

No response

AudioPlayers Version

5.0.0

Build mode

debug

Audio Files/URLs/Sources

No response

Screenshots

No response

Logs

my relevant logs
Full Logs ``` my full logs or a link to a gist ``` Flutter doctor: ``` Output of: flutter doctor -v ```

Related issues / more information

No response

Working on PR

no way

Dhanesh-Sawant commented 11 months ago

should I work on this bug??

Gustl22 commented 11 months ago

@Dhanesh-Sawant that would be awesome :D

Dhanesh-Sawant commented 11 months ago

@Gustl22 Please assign it to me..

Dhanesh-Sawant commented 10 months ago

@Gustl22 setting the source before calling seek in play() function, instead of setting it afterwards solves the issue, bec the AudioElement instance _player in wrappedPlayer class should be initailised before seeking.

suggested code change:-

Future play( Source source, { double? volume, double? balance, AudioContext? ctx, Duration? position, PlayerMode? mode, }) async { await setSource(source);

if (mode != null) {
  await setPlayerMode(mode);
}
if (volume != null) {
  await setVolume(volume);
}
if (balance != null) {
  await setBalance(balance);
}
if (ctx != null) {
  await setAudioContext(ctx);
}
if (position != null) {
  await seek(position);
}
return resume();

}

Dhanesh-Sawant commented 10 months ago

@Gustl22 is the code change above correct, or is there any issue??

Dhanesh-Sawant commented 10 months ago

@Gustl22 please see to it..