Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AudioPlayer seek issue #1597

Closed
2 tasks done
krishnaprasadgandrath-nooor opened this issue Aug 9, 2023 · 6 comments · Fixed by #1695
Closed
2 tasks done

AudioPlayer seek issue #1597

krishnaprasadgandrath-nooor opened this issue Aug 9, 2023 · 6 comments · Fixed by #1695
Assignees
Labels

Comments

@krishnaprasadgandrath-nooor
Copy link

krishnaprasadgandrath-nooor commented Aug 9, 2023

Checklist

  • I read the troubleshooting guide before raising this issue
  • I made sure that the issue I am raising doesn't already exist

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

 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<String> args) {
  runApp(const MaterialApp(
    home: AudioPlayerTestScreen(),
  ));
}

class AudioPlayerTestScreen extends StatefulWidget {
  const AudioPlayerTestScreen({super.key});

  @override
  State<AudioPlayerTestScreen> createState() => _AudioPlayerTestScreenState();
}

class _AudioPlayerTestScreenState extends State<AudioPlayerTestScreen> {
  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<AudioTile> createState() => _AudioTileState();
}

class _AudioTileState extends State<AudioTile> {
  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

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<Duration> get positionStream;
  Stream<Duration> get durationStream;
  Stream<PlayerState> get playerStateStream;

  ///Methods
  Future<AudioPlayer> _updatePlayer({required String playerId});
  Future<void> playAudio({required String playerId, required String url, Duration? position});
  Future<bool> pauseAudio({required String playerId});
  Future<bool> 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<Duration> _durationStreamController = StreamController<Duration>.broadcast();
  final StreamController<Duration> _positionStreamController = StreamController<Duration>.broadcast();
  final StreamController<PlayerState> _playerStateStreamController = StreamController<PlayerState>.broadcast();

  StreamSubscription? _durationSub, _positionSub, _playerStateSub;
  /* final Stream<Duration> _durationStream = const Stream<Duration>.empty();
  final Stream<Duration> _positionStream = const Stream<Duration>.empty();
  final Stream<PlayerState> _playerStatestream = const Stream<PlayerState>.empty(); */

  ///Getters
  @override
  String get activePlayerId => _currentPlayerId;
  @override
  AudioPlayer get audioPlayer => _audioPlayer;

  @override
  Stream<Duration> get durationStream => _durationStreamController.stream;

  @override
  Stream<Duration> get positionStream => _positionStreamController.stream;

  @override
  Stream<PlayerState> get playerStateStream => _playerStateStreamController.stream;

  @override
  Future<void> 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<bool> 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<bool> stopAudio({required String playerId}) async {
    if (_currentPlayerId == playerId) {
      await _audioPlayer.stop();
      notifyListeners();
      return true;
    } else {
      return false;
    }
  }

  @override
  Future<AudioPlayer> _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

@Gustl22 Gustl22 added the platform-web Affects the web platform label Sep 17, 2023
@Dhanesh-Sawant
Copy link
Contributor

should I work on this bug??

@Gustl22
Copy link
Collaborator

Gustl22 commented Oct 5, 2023

@Dhanesh-Sawant that would be awesome :D

@Dhanesh-Sawant
Copy link
Contributor

@Gustl22 Please assign it to me..

@Dhanesh-Sawant
Copy link
Contributor

@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
Copy link
Contributor

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

Dhanesh-Sawant added a commit to Dhanesh-Sawant/audioplayers that referenced this issue Oct 28, 2023
@Dhanesh-Sawant
Copy link
Contributor

@Gustl22 please see to it..

Gustl22 pushed a commit that referenced this issue Nov 7, 2023
# Description

This PR is fixing the bug in which in web while initialising the play
with duration specified, there was no seek happening, which means that
it starts from the beginning itself. setting the source before calling
seek in play() function, instead of setting it afterwards solves the
issue.

Closes #1597
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants