No automatic track change on track complete fix (https://github.com/KRTirtho/spotube/issues/46)

This commit is contained in:
Kingkor Roy Tirtho 2022-05-03 14:20:10 +06:00
parent c0e2a21765
commit 83a45dd9b6
3 changed files with 35 additions and 73 deletions

View File

@ -36,6 +36,9 @@ class Player extends HookConsumerWidget {
useFuture(future, initialData: null);
useEffect(() {
// registering all the stream subscription listeners of player
playback.register();
/// warm up the audio player before playing actual audio
/// It's for resolving unresolved issue related to just_audio's
/// [disposeAllPlayers] method which is throwing
@ -51,7 +54,9 @@ class Player extends HookConsumerWidget {
} else {
player.setAsset("assets/warmer.mp3");
}
return null;
return () {
playback.dispose();
};
}, []);
useEffect(() {

View File

@ -22,17 +22,6 @@ class PlayerControls extends HookConsumerWidget {
final AudioPlayer player = playback.player;
final _shuffled = useState(false);
final _duration = useState<Duration?>(playback.duration);
useEffect(() {
listener(Duration? duration) {
_duration.value = duration;
}
playback.addDurationChangeListener(listener);
return () => playback.removeDurationChangeListener(listener);
}, []);
final onNext = useNextTrack(playback);
@ -40,7 +29,7 @@ class PlayerControls extends HookConsumerWidget {
final _playOrPause = useTogglePlayPause(playback);
final duration = _duration.value ?? Duration.zero;
final duration = playback.duration ?? Duration.zero;
return Container(
constraints: const BoxConstraints(maxWidth: 600),

View File

@ -57,21 +57,16 @@ class Playback extends ChangeNotifier {
// states
bool _isPlaying = false;
Duration? _duration;
// using custom listeners for duration as it changes super quickly
// which will cause re-renders in components that don't even need it
// thus only allowing to listen to change in duration through only
// a listener function
List<Function(Duration?)> _durationListeners = [];
Duration? duration;
// listeners
StreamSubscription<bool>? _playingStreamListener;
StreamSubscription<Duration?>? _durationStreamListener;
StreamSubscription<ProcessingState>? _processingStateStreamListener;
StreamSubscription<AudioInterruptionEvent>? _audioInterruptionEventListener;
StreamSubscription<Duration>? _positionStreamListener;
Duration _prevPosition = Duration.zero;
AudioPlayer player;
YoutubeExplode youtube;
AudioSession? _audioSession;
@ -82,7 +77,9 @@ class Playback extends ChangeNotifier {
CurrentPlaylist? currentPlaylist,
Track? currentTrack,
}) : _currentPlaylist = currentPlaylist,
_currentTrack = currentTrack {
_currentTrack = currentTrack;
void register() {
_playingStreamListener = player.playingStream.listen(
(playing) {
_isPlaying = playing;
@ -90,50 +87,48 @@ class Playback extends ChangeNotifier {
},
);
_durationStreamListener = player.durationStream.listen((duration) async {
if (duration != null) {
_durationStreamListener = player.durationStream.listen((event) async {
if (event != null) {
// Actually things doesn't work all the time as they were
// described. So instead of listening to a `_ready`
// stream, it has to listen to duration stream since duration
// is always added to the Stream sink after all icyMetadata has
// been loaded thus indicating buffering started
if (duration != Duration.zero && duration != _duration) {
if (event != Duration.zero && event != duration) {
// this line is for prev/next or already playing playlist
if (player.playing) await player.pause();
await player.play();
}
_duration = duration;
// for avoiding unnecessary re-renders in other components that
// doesn't need duration
_callAllDurationListeners(duration);
duration = event;
notifyListeners();
}
});
_processingStateStreamListener =
player.processingStateStream.listen((event) async {
_logger.v("[Processing State Change] $event");
try {
if (event != ProcessingState.completed) return;
_positionStreamListener =
player.createPositionStream().listen((position) async {
// detecting multiple same call
if (_prevPosition.inSeconds == position.inSeconds) return;
_prevPosition = position;
/// Because of ProcessingState.complete never gets set bug using a
/// custom solution to know when the audio stops playing
///
/// Details: https://github.com/KRTirtho/spotube/issues/46
if (duration != Duration.zero &&
duration?.isNegative == false &&
position.inSeconds == duration?.inSeconds) {
if (_currentTrack?.id != null) {
await player.pause();
movePlaylistPositionBy(1);
} else {
await audioSession?.setActive(false);
_isPlaying = false;
_duration = null;
_callAllDurationListeners(null);
duration = null;
notifyListeners();
}
} catch (e, stack) {
_logger.e("PrecessingStateStreamListener", e, stack);
}
});
_positionStreamListener = (player.positionStream.isBroadcast
? player.positionStream
: player.positionStream.asBroadcastStream())
.listen((position) async {});
AudioSession.instance.then((session) async {
_audioSession = session;
await session.configure(const AudioSessionConfiguration.music());
@ -148,28 +143,6 @@ class Playback extends ChangeNotifier {
bool get isPlaying => _isPlaying;
AudioSession? get audioSession => _audioSession;
/// this duration field is almost static & changes occasionally
///
/// If you want realtime duration with state-update/re-render
/// use custom state & the [addDurationChangeListener] function to do so
Duration? get duration => _duration;
_callAllDurationListeners(Duration? arg) {
for (var listener in _durationListeners) {
listener(arg);
}
}
void addDurationChangeListener(void Function(Duration? duration) listener) {
_durationListeners.add(listener);
}
void removeDurationChangeListener(
void Function(Duration? duration) listener) {
_durationListeners =
_durationListeners.where((p) => p != listener).toList();
}
set setCurrentTrack(Track track) {
_logger.v("[Setting Current Track] ${track.name} - ${track.id}");
_currentTrack = track;
@ -185,8 +158,7 @@ class Playback extends ChangeNotifier {
void reset() {
_logger.v("Playback Reset");
_isPlaying = false;
_duration = null;
_callAllDurationListeners(null);
duration = null;
_currentPlaylist = null;
_currentTrack = null;
_audioSession?.setActive(false);
@ -211,7 +183,6 @@ class Playback extends ChangeNotifier {
@override
dispose() {
_processingStateStreamListener?.cancel();
_durationStreamListener?.cancel();
_playingStreamListener?.cancel();
_audioInterruptionEventListener?.cancel();
@ -234,8 +205,7 @@ class Playback extends ChangeNotifier {
? _currentPlaylist!.tracks.elementAt(safeIndex)
: null;
if (track != null) {
_duration = null;
_callAllDurationListeners(null);
duration = null;
_currentTrack = track;
notifyListeners();
// starts to play the newly entered next/prev track
@ -268,8 +238,6 @@ class Playback extends ChangeNotifier {
)
.then((value) async {
_currentTrack = track;
_duration = value;
_callAllDurationListeners(value);
notifyListeners();
});
}