♻️ Improve progress display

This commit is contained in:
LittleSheep 2024-08-29 00:41:40 +08:00
parent 2134500089
commit 249c8fbf80
4 changed files with 66 additions and 88 deletions

View File

@ -11,6 +11,10 @@ import 'package:spotify/spotify.dart' hide Playlist;
import 'package:rhythm_box/services/audio_player/audio_player.dart'; import 'package:rhythm_box/services/audio_player/audio_player.dart';
class AudioPlayerProvider extends GetxController { class AudioPlayerProvider extends GetxController {
Rx<Duration> durationTotal = Rx(Duration.zero);
Rx<Duration> durationCurrent = Rx(Duration.zero);
Rx<Duration> durationBuffered = Rx(Duration.zero);
RxBool isPlaying = false.obs; RxBool isPlaying = false.obs;
Rx<AudioPlayerState> state = Rx(AudioPlayerState( Rx<AudioPlayerState> state = Rx(AudioPlayerState(
@ -54,6 +58,11 @@ class AudioPlayerProvider extends GetxController {
state.value = state.value.copyWith(playlist: playlist); state.value = state.value.copyWith(playlist: playlist);
await _updatePlaylist(playlist); await _updatePlaylist(playlist);
}), }),
audioPlayer.durationStream.listen((value) => durationTotal.value = value),
audioPlayer.positionStream
.listen((value) => durationCurrent.value = value),
audioPlayer.bufferedPositionStream
.listen((value) => durationBuffered.value = value),
]; ];
_readSavedState(); _readSavedState();

View File

@ -39,13 +39,6 @@ class _PlayerScreenState extends State<PlayerScreen> {
bool get _isFetchingActiveTrack => _query.isQueryingTrackInfo.value; bool get _isFetchingActiveTrack => _query.isQueryingTrackInfo.value;
PlaylistMode get _loopMode => _playback.state.value.loopMode; PlaylistMode get _loopMode => _playback.state.value.loopMode;
double _bufferProgress = 0;
Duration _durationCurrent = Duration.zero;
Duration _durationTotal = Duration.zero;
List<StreamSubscription>? _subscriptions;
Future<void> _togglePlayState() async { Future<void> _togglePlayState() async {
if (!audioPlayer.isPlaying) { if (!audioPlayer.isPlaying) {
await audioPlayer.resume(); await audioPlayer.resume();
@ -57,32 +50,6 @@ class _PlayerScreenState extends State<PlayerScreen> {
double? _draggingValue; double? _draggingValue;
@override
void initState() {
super.initState();
_durationCurrent = audioPlayer.position;
_durationTotal = audioPlayer.duration;
_bufferProgress = audioPlayer.bufferedPosition.inMilliseconds.toDouble();
_subscriptions = [
audioPlayer.durationStream
.listen((dur) => setState(() => _durationTotal = dur)),
audioPlayer.positionStream
.listen((dur) => setState(() => _durationCurrent = dur)),
audioPlayer.bufferedPositionStream.listen((dur) =>
setState(() => _bufferProgress = dur.inMilliseconds.toDouble())),
];
}
@override
void dispose() {
if (_subscriptions != null) {
for (final subscription in _subscriptions!) {
subscription.cancel();
}
}
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
@ -134,50 +101,58 @@ class _PlayerScreenState extends State<PlayerScreen> {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
const Gap(24), const Gap(24),
Column( Obx(
children: [ () => Column(
SliderTheme( children: [
data: SliderThemeData( SliderTheme(
trackHeight: 2, data: SliderThemeData(
trackShape: _PlayerProgressTrackShape(), trackHeight: 2,
thumbShape: const RoundSliderThumbShape( trackShape: _PlayerProgressTrackShape(),
enabledThumbRadius: 8, thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 8,
),
overlayShape: SliderComponentShape.noOverlay,
),
child: Slider(
secondaryTrackValue: _playback
.durationBuffered.value.inMilliseconds
.abs()
.toDouble(),
value: _draggingValue?.abs() ??
_playback.durationCurrent.value.inMilliseconds
.toDouble()
.abs(),
min: 0,
max: max(
_playback.durationCurrent.value.inMilliseconds.abs(),
_playback.durationTotal.value.inMilliseconds.abs(),
).toDouble(),
onChanged: (value) {
setState(() => _draggingValue = value);
},
onChangeEnd: (value) {
audioPlayer
.seek(Duration(milliseconds: value.toInt()));
},
), ),
overlayShape: SliderComponentShape.noOverlay,
), ),
child: Slider( Row(
secondaryTrackValue: _bufferProgress.abs(), mainAxisAlignment: MainAxisAlignment.spaceBetween,
value: _draggingValue?.abs() ?? children: [
_durationCurrent.inMilliseconds.toDouble().abs(), Text(
min: 0, _playback.durationCurrent.value
max: max( .toHumanReadableString(),
_durationTotal.inMilliseconds.abs(), style: GoogleFonts.robotoMono(fontSize: 12),
_durationTotal.inMilliseconds.abs(), ),
).toDouble(), Text(
onChanged: (value) { _playback.durationTotal.value.toHumanReadableString(),
setState(() => _draggingValue = value); style: GoogleFonts.robotoMono(fontSize: 12),
}, ),
onChangeEnd: (value) { ],
print('Seek to $value ms'); ).paddingSymmetric(horizontal: 8, vertical: 4),
audioPlayer.seek(Duration(milliseconds: value.toInt())); ],
}, ).paddingSymmetric(horizontal: 24),
), ),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_durationCurrent.toHumanReadableString(),
style: GoogleFonts.robotoMono(fontSize: 12),
),
Text(
_durationTotal.toHumanReadableString(),
style: GoogleFonts.robotoMono(fontSize: 12),
),
],
).paddingSymmetric(horizontal: 8, vertical: 4),
],
).paddingSymmetric(horizontal: 24),
const Gap(24), const Gap(24),
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,

View File

@ -107,7 +107,7 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
) )
: Padding( : Padding(
padding: idx == _lyric!.lyrics.length - 1 padding: idx == _lyric!.lyrics.length - 1
? const EdgeInsets.all(8.0).copyWith(bottom: 100) ? const EdgeInsets.all(8.0).copyWith(bottom: 80)
: const EdgeInsets.all(8.0), : const EdgeInsets.all(8.0),
child: AnimatedDefaultTextStyle( child: AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 250), duration: const Duration(milliseconds: 250),
@ -141,8 +141,9 @@ class _SyncedLyricsState extends State<SyncedLyrics> {
), ),
).animate(target: isActive ? 1 : 0).scale( ).animate(target: isActive ? 1 : 0).scale(
duration: 300.ms, duration: 300.ms,
begin: const Offset(0.9, 0.9), begin: const Offset(1, 1),
end: const Offset(1.3, 1.3), end: const Offset(1.25, 1.25),
curve: Curves.easeInOut,
); );
}).paddingSymmetric(horizontal: 12), }).paddingSymmetric(horizontal: 12),
), ),

View File

@ -44,9 +44,6 @@ class _BottomPlayerState extends State<BottomPlayer>
bool get _isPlaying => _playback.isPlaying.value; bool get _isPlaying => _playback.isPlaying.value;
bool get _isFetchingActiveTrack => _query.isQueryingTrackInfo.value; bool get _isFetchingActiveTrack => _query.isQueryingTrackInfo.value;
Duration _durationCurrent = Duration.zero;
Duration _durationTotal = Duration.zero;
List<StreamSubscription>? _subscriptions; List<StreamSubscription>? _subscriptions;
Future<void> _togglePlayState() async { Future<void> _togglePlayState() async {
@ -63,10 +60,6 @@ class _BottomPlayerState extends State<BottomPlayer>
void initState() { void initState() {
super.initState(); super.initState();
_subscriptions = [ _subscriptions = [
audioPlayer.durationStream
.listen((dur) => setState(() => _durationTotal = dur)),
audioPlayer.positionStream
.listen((dur) => setState(() => _durationCurrent = dur)),
_playback.state.listen((state) { _playback.state.listen((state) {
if (state.playlist.medias.isNotEmpty && !_isLifted) { if (state.playlist.medias.isNotEmpty && !_isLifted) {
_animationController.animateTo(1); _animationController.animateTo(1);
@ -109,12 +102,12 @@ class _BottomPlayerState extends State<BottomPlayer>
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
child: Column( child: Column(
children: [ children: [
if (_durationCurrent != Duration.zero) if (_playback.durationCurrent.value != Duration.zero)
TweenAnimationBuilder<double>( TweenAnimationBuilder<double>(
tween: Tween( tween: Tween(
begin: 0, begin: 0,
end: _durationCurrent.inMilliseconds / end: _playback.durationCurrent.value.inMilliseconds /
max(_durationTotal.inMilliseconds, 1), max(_playback.durationTotal.value.inMilliseconds, 1),
), ),
duration: const Duration(milliseconds: 1000), duration: const Duration(milliseconds: 1000),
builder: (context, value, _) => LinearProgressIndicator( builder: (context, value, _) => LinearProgressIndicator(