Skip to content

Commit

Permalink
No automatic track change on track complete fix (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed May 3, 2022
1 parent c0e2a21 commit 83a45dd
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 73 deletions.
7 changes: 6 additions & 1 deletion lib/components/Player/Player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -51,7 +54,9 @@ class Player extends HookConsumerWidget {
} else {
player.setAsset("assets/warmer.mp3");
}
return null;
return () {
playback.dispose();
};
}, []);

useEffect(() {
Expand Down
13 changes: 1 addition & 12 deletions lib/components/Player/PlayerControls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,14 @@ 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);

final onPrevious = usePreviousTrack(playback);

final _playOrPause = useTogglePlayPause(playback);

final duration = _duration.value ?? Duration.zero;
final duration = playback.duration ?? Duration.zero;

return Container(
constraints: const BoxConstraints(maxWidth: 600),
Expand Down
88 changes: 28 additions & 60 deletions lib/provider/Playback.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -82,58 +77,58 @@ class Playback extends ChangeNotifier {
CurrentPlaylist? currentPlaylist,
Track? currentTrack,
}) : _currentPlaylist = currentPlaylist,
_currentTrack = currentTrack {
_currentTrack = currentTrack;

void register() {
_playingStreamListener = player.playingStream.listen(
(playing) {
_isPlaying = playing;
notifyListeners();
},
);

_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());
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -211,7 +183,6 @@ class Playback extends ChangeNotifier {

@override
dispose() {
_processingStateStreamListener?.cancel();
_durationStreamListener?.cancel();
_playingStreamListener?.cancel();
_audioInterruptionEventListener?.cancel();
Expand All @@ -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
Expand Down Expand Up @@ -268,8 +238,6 @@ class Playback extends ChangeNotifier {
)
.then((value) async {
_currentTrack = track;
_duration = value;
_callAllDurationListeners(value);
notifyListeners();
});
}
Expand Down

0 comments on commit 83a45dd

Please sign in to comment.