b6b73fdd0c
Why the seek bar appeared frozen: it was reading PlaybackState.updatePosition, which audio_service only updates on event transitions (play/pause/buffer/seek). Between events it sits unchanged, so the bar only jumped at intervals. Expose just_audio's positionStream (~200ms cadence) from the audio handler, wrap as positionProvider, and read that in both the mini bar and the full player. Now the bar advances continuously while playing. While we're here: spread the full player's vertical layout per operator — gap to seek 20→32, seek-to-primary 8→24, primary-to- secondary 16→24, plus 24 below to match. The full player had visible slack above the controls; this redistributes it.
108 lines
3.6 KiB
Dart
108 lines
3.6 KiB
Dart
import 'package:audio_service/audio_service.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../auth/auth_provider.dart';
|
|
import '../cache/audio_cache_manager.dart';
|
|
import '../library/library_providers.dart' show dioProvider;
|
|
import '../models/track.dart';
|
|
import 'album_cover_cache.dart';
|
|
import 'audio_handler.dart';
|
|
|
|
final audioHandlerProvider = Provider<MinstrelAudioHandler>((ref) {
|
|
throw UnimplementedError('overridden in main()');
|
|
});
|
|
|
|
final albumCoverCacheProvider = Provider<AlbumCoverCache>((ref) {
|
|
return AlbumCoverCache(
|
|
dioFactory: () => ref.read(dioProvider.future),
|
|
);
|
|
});
|
|
|
|
final playbackStateProvider = StreamProvider<PlaybackState>(
|
|
(ref) => ref.watch(audioHandlerProvider).playbackState,
|
|
);
|
|
|
|
final mediaItemProvider = StreamProvider<MediaItem?>(
|
|
(ref) => ref.watch(audioHandlerProvider).mediaItem,
|
|
);
|
|
|
|
final queueProvider = StreamProvider<List<MediaItem>>(
|
|
(ref) => ref.watch(audioHandlerProvider).queue,
|
|
);
|
|
|
|
/// Player volume in [0.0, 1.0]. Mirrors just_audio's player.volume;
|
|
/// modify via PlayerActions.setVolume(). Surfaced separately from
|
|
/// PlaybackState because audio_service's PlaybackState doesn't carry
|
|
/// volume — it's a player-side concern.
|
|
final volumeProvider = StreamProvider<double>(
|
|
(ref) => ref.watch(audioHandlerProvider).volumeStream,
|
|
);
|
|
|
|
/// Live playback position. just_audio emits at ~200ms cadence so seek
|
|
/// bars driven by this provider scrub smoothly. Don't use
|
|
/// PlaybackState.updatePosition for that — it only changes on state
|
|
/// transitions (play/pause/buffer/seek) and the bar would appear
|
|
/// frozen between events.
|
|
final positionProvider = StreamProvider<Duration>(
|
|
(ref) => ref.watch(audioHandlerProvider).positionStream,
|
|
);
|
|
|
|
class PlayerActions {
|
|
PlayerActions(this._ref);
|
|
final Ref _ref;
|
|
|
|
Future<void> playTracks(List<TrackRef> tracks, {int initialIndex = 0}) async {
|
|
final url = await _ref.read(serverUrlProvider.future);
|
|
final token = await _ref.read(secureStorageProvider).read(key: 'session_token');
|
|
final cache = _ref.read(albumCoverCacheProvider);
|
|
final audioCache = _ref.read(audioCacheManagerProvider);
|
|
final h = _ref.read(audioHandlerProvider)
|
|
..configure(
|
|
baseUrl: url ?? '',
|
|
token: token,
|
|
coverCache: cache,
|
|
audioCacheManager: audioCache,
|
|
);
|
|
await h.setQueueFromTracks(tracks, initialIndex: initialIndex);
|
|
await h.play();
|
|
}
|
|
|
|
Future<void> playNext(TrackRef track) async {
|
|
final h = _ref.read(audioHandlerProvider);
|
|
await h.playNext(track);
|
|
}
|
|
|
|
Future<void> enqueue(TrackRef track) async {
|
|
final h = _ref.read(audioHandlerProvider);
|
|
await h.enqueue(track);
|
|
}
|
|
|
|
/// Toggles shuffle on/off. Pre-existing audio_service convention.
|
|
Future<void> toggleShuffle() async {
|
|
final h = _ref.read(audioHandlerProvider);
|
|
final state = await h.playbackState.first;
|
|
final next = state.shuffleMode == AudioServiceShuffleMode.none
|
|
? AudioServiceShuffleMode.all
|
|
: AudioServiceShuffleMode.none;
|
|
await h.setShuffleMode(next);
|
|
}
|
|
|
|
/// Cycles repeat mode: none → all → one → none.
|
|
Future<void> cycleRepeat() async {
|
|
final h = _ref.read(audioHandlerProvider);
|
|
final state = await h.playbackState.first;
|
|
final next = switch (state.repeatMode) {
|
|
AudioServiceRepeatMode.none => AudioServiceRepeatMode.all,
|
|
AudioServiceRepeatMode.all => AudioServiceRepeatMode.one,
|
|
_ => AudioServiceRepeatMode.none,
|
|
};
|
|
await h.setRepeatMode(next);
|
|
}
|
|
|
|
Future<void> setVolume(double v) async {
|
|
await _ref.read(audioHandlerProvider).setVolume(v);
|
|
}
|
|
}
|
|
|
|
final playerActionsProvider = Provider<PlayerActions>((ref) => PlayerActions(ref));
|