mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
No automatic track change on track complete fix (https://github.com/KRTirtho/spotube/issues/46)
This commit is contained in:
parent
c0e2a21765
commit
83a45dd9b6
@ -36,6 +36,9 @@ class Player extends HookConsumerWidget {
|
|||||||
useFuture(future, initialData: null);
|
useFuture(future, initialData: null);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
|
// registering all the stream subscription listeners of player
|
||||||
|
playback.register();
|
||||||
|
|
||||||
/// warm up the audio player before playing actual audio
|
/// warm up the audio player before playing actual audio
|
||||||
/// It's for resolving unresolved issue related to just_audio's
|
/// It's for resolving unresolved issue related to just_audio's
|
||||||
/// [disposeAllPlayers] method which is throwing
|
/// [disposeAllPlayers] method which is throwing
|
||||||
@ -51,7 +54,9 @@ class Player extends HookConsumerWidget {
|
|||||||
} else {
|
} else {
|
||||||
player.setAsset("assets/warmer.mp3");
|
player.setAsset("assets/warmer.mp3");
|
||||||
}
|
}
|
||||||
return null;
|
return () {
|
||||||
|
playback.dispose();
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
|
@ -22,17 +22,6 @@ class PlayerControls extends HookConsumerWidget {
|
|||||||
final AudioPlayer player = playback.player;
|
final AudioPlayer player = playback.player;
|
||||||
|
|
||||||
final _shuffled = useState(false);
|
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 onNext = useNextTrack(playback);
|
||||||
|
|
||||||
@ -40,7 +29,7 @@ class PlayerControls extends HookConsumerWidget {
|
|||||||
|
|
||||||
final _playOrPause = useTogglePlayPause(playback);
|
final _playOrPause = useTogglePlayPause(playback);
|
||||||
|
|
||||||
final duration = _duration.value ?? Duration.zero;
|
final duration = playback.duration ?? Duration.zero;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 600),
|
constraints: const BoxConstraints(maxWidth: 600),
|
||||||
|
@ -57,21 +57,16 @@ class Playback extends ChangeNotifier {
|
|||||||
|
|
||||||
// states
|
// states
|
||||||
bool _isPlaying = false;
|
bool _isPlaying = false;
|
||||||
Duration? _duration;
|
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 = [];
|
|
||||||
|
|
||||||
// listeners
|
// listeners
|
||||||
StreamSubscription<bool>? _playingStreamListener;
|
StreamSubscription<bool>? _playingStreamListener;
|
||||||
StreamSubscription<Duration?>? _durationStreamListener;
|
StreamSubscription<Duration?>? _durationStreamListener;
|
||||||
StreamSubscription<ProcessingState>? _processingStateStreamListener;
|
|
||||||
StreamSubscription<AudioInterruptionEvent>? _audioInterruptionEventListener;
|
StreamSubscription<AudioInterruptionEvent>? _audioInterruptionEventListener;
|
||||||
StreamSubscription<Duration>? _positionStreamListener;
|
StreamSubscription<Duration>? _positionStreamListener;
|
||||||
|
|
||||||
|
Duration _prevPosition = Duration.zero;
|
||||||
|
|
||||||
AudioPlayer player;
|
AudioPlayer player;
|
||||||
YoutubeExplode youtube;
|
YoutubeExplode youtube;
|
||||||
AudioSession? _audioSession;
|
AudioSession? _audioSession;
|
||||||
@ -82,7 +77,9 @@ class Playback extends ChangeNotifier {
|
|||||||
CurrentPlaylist? currentPlaylist,
|
CurrentPlaylist? currentPlaylist,
|
||||||
Track? currentTrack,
|
Track? currentTrack,
|
||||||
}) : _currentPlaylist = currentPlaylist,
|
}) : _currentPlaylist = currentPlaylist,
|
||||||
_currentTrack = currentTrack {
|
_currentTrack = currentTrack;
|
||||||
|
|
||||||
|
void register() {
|
||||||
_playingStreamListener = player.playingStream.listen(
|
_playingStreamListener = player.playingStream.listen(
|
||||||
(playing) {
|
(playing) {
|
||||||
_isPlaying = playing;
|
_isPlaying = playing;
|
||||||
@ -90,50 +87,48 @@ class Playback extends ChangeNotifier {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
_durationStreamListener = player.durationStream.listen((duration) async {
|
_durationStreamListener = player.durationStream.listen((event) async {
|
||||||
if (duration != null) {
|
if (event != null) {
|
||||||
// Actually things doesn't work all the time as they were
|
// Actually things doesn't work all the time as they were
|
||||||
// described. So instead of listening to a `_ready`
|
// described. So instead of listening to a `_ready`
|
||||||
// stream, it has to listen to duration stream since duration
|
// stream, it has to listen to duration stream since duration
|
||||||
// is always added to the Stream sink after all icyMetadata has
|
// is always added to the Stream sink after all icyMetadata has
|
||||||
// been loaded thus indicating buffering started
|
// 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
|
// this line is for prev/next or already playing playlist
|
||||||
if (player.playing) await player.pause();
|
if (player.playing) await player.pause();
|
||||||
await player.play();
|
await player.play();
|
||||||
}
|
}
|
||||||
|
duration = event;
|
||||||
_duration = duration;
|
notifyListeners();
|
||||||
// for avoiding unnecessary re-renders in other components that
|
|
||||||
// doesn't need duration
|
|
||||||
_callAllDurationListeners(duration);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_processingStateStreamListener =
|
_positionStreamListener =
|
||||||
player.processingStateStream.listen((event) async {
|
player.createPositionStream().listen((position) async {
|
||||||
_logger.v("[Processing State Change] $event");
|
// detecting multiple same call
|
||||||
try {
|
if (_prevPosition.inSeconds == position.inSeconds) return;
|
||||||
if (event != ProcessingState.completed) 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) {
|
if (_currentTrack?.id != null) {
|
||||||
|
await player.pause();
|
||||||
movePlaylistPositionBy(1);
|
movePlaylistPositionBy(1);
|
||||||
} else {
|
} else {
|
||||||
await audioSession?.setActive(false);
|
await audioSession?.setActive(false);
|
||||||
_isPlaying = false;
|
_isPlaying = false;
|
||||||
_duration = null;
|
duration = null;
|
||||||
_callAllDurationListeners(null);
|
|
||||||
notifyListeners();
|
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.instance.then((session) async {
|
||||||
_audioSession = session;
|
_audioSession = session;
|
||||||
await session.configure(const AudioSessionConfiguration.music());
|
await session.configure(const AudioSessionConfiguration.music());
|
||||||
@ -148,28 +143,6 @@ class Playback extends ChangeNotifier {
|
|||||||
bool get isPlaying => _isPlaying;
|
bool get isPlaying => _isPlaying;
|
||||||
AudioSession? get audioSession => _audioSession;
|
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) {
|
set setCurrentTrack(Track track) {
|
||||||
_logger.v("[Setting Current Track] ${track.name} - ${track.id}");
|
_logger.v("[Setting Current Track] ${track.name} - ${track.id}");
|
||||||
_currentTrack = track;
|
_currentTrack = track;
|
||||||
@ -185,8 +158,7 @@ class Playback extends ChangeNotifier {
|
|||||||
void reset() {
|
void reset() {
|
||||||
_logger.v("Playback Reset");
|
_logger.v("Playback Reset");
|
||||||
_isPlaying = false;
|
_isPlaying = false;
|
||||||
_duration = null;
|
duration = null;
|
||||||
_callAllDurationListeners(null);
|
|
||||||
_currentPlaylist = null;
|
_currentPlaylist = null;
|
||||||
_currentTrack = null;
|
_currentTrack = null;
|
||||||
_audioSession?.setActive(false);
|
_audioSession?.setActive(false);
|
||||||
@ -211,7 +183,6 @@ class Playback extends ChangeNotifier {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
dispose() {
|
dispose() {
|
||||||
_processingStateStreamListener?.cancel();
|
|
||||||
_durationStreamListener?.cancel();
|
_durationStreamListener?.cancel();
|
||||||
_playingStreamListener?.cancel();
|
_playingStreamListener?.cancel();
|
||||||
_audioInterruptionEventListener?.cancel();
|
_audioInterruptionEventListener?.cancel();
|
||||||
@ -234,8 +205,7 @@ class Playback extends ChangeNotifier {
|
|||||||
? _currentPlaylist!.tracks.elementAt(safeIndex)
|
? _currentPlaylist!.tracks.elementAt(safeIndex)
|
||||||
: null;
|
: null;
|
||||||
if (track != null) {
|
if (track != null) {
|
||||||
_duration = null;
|
duration = null;
|
||||||
_callAllDurationListeners(null);
|
|
||||||
_currentTrack = track;
|
_currentTrack = track;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
// starts to play the newly entered next/prev track
|
// starts to play the newly entered next/prev track
|
||||||
@ -268,8 +238,6 @@ class Playback extends ChangeNotifier {
|
|||||||
)
|
)
|
||||||
.then((value) async {
|
.then((value) async {
|
||||||
_currentTrack = track;
|
_currentTrack = track;
|
||||||
_duration = value;
|
|
||||||
_callAllDurationListeners(value);
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user