StubMusicPlaybackService is a base for test doubles with no production caller, so `check-unused-code lib` flagged it and the analysis job failed. Moves it to test/test_helpers/, where shared fakes belong.
150 lines
5.2 KiB
Dart
150 lines
5.2 KiB
Dart
import 'package:flutter/foundation.dart';
|
||
|
||
import '../../media/lyrics.dart';
|
||
import '../../media/media_item.dart';
|
||
|
||
/// Repeat behavior of the music queue.
|
||
enum MusicRepeatMode { off, all, one }
|
||
|
||
/// Coarse playback state of the music session.
|
||
enum MusicPlaybackStatus { idle, loading, playing, paused, error }
|
||
|
||
/// What kind of container playback was started from. The player keeps
|
||
/// artist/playlist/mix provenance stable, while album and ad-hoc queues use
|
||
/// the active track's album for the "Playing from …" line.
|
||
enum MusicPlayContextKind { album, artist, playlist, mix, tracks }
|
||
|
||
/// Provenance of the current queue (album/artist/playlist/instant mix).
|
||
class MusicPlayContext {
|
||
/// Backend id of the source container, when it has one (instant mixes
|
||
/// don't).
|
||
final String? id;
|
||
|
||
/// Display title of the session source. Used directly for stable
|
||
/// artist/playlist/mix provenance labels.
|
||
final String title;
|
||
|
||
final MusicPlayContextKind kind;
|
||
|
||
const MusicPlayContext({this.id, required this.title, required this.kind});
|
||
}
|
||
|
||
/// Backend-neutral music playback session: owns the audio `Player`, the
|
||
/// queue (shuffle/repeat), OS media-session feed, and progress reporting.
|
||
///
|
||
/// UI consumes this via `context.watch<MusicPlaybackService>()` — it is
|
||
/// registered per profile session (see `profile_session_screen.dart`) so a
|
||
/// profile switch tears the session down. [notifyListeners] fires only on
|
||
/// discrete changes (track, status, queue shape, modes) — progress bars
|
||
/// subscribe to [positionStream] instead.
|
||
abstract class MusicPlaybackService extends ChangeNotifier {
|
||
MediaItem? get currentTrack;
|
||
MusicPlaybackStatus get status;
|
||
bool get isPlaying => status == MusicPlaybackStatus.playing;
|
||
|
||
Duration? get duration;
|
||
Duration get position;
|
||
Stream<Duration> get positionStream;
|
||
|
||
/// Full queue in playback order (shuffle already applied).
|
||
List<MediaItem> get queue;
|
||
|
||
/// Index of [currentTrack] within [queue]; -1 when idle.
|
||
int get currentIndex;
|
||
|
||
MusicPlayContext? get playContext;
|
||
bool get shuffled;
|
||
MusicRepeatMode get repeatMode;
|
||
|
||
/// Playback failures the UI should surface (snackbar); the service already
|
||
/// handles recovery (skip / stop) itself.
|
||
Stream<Object> get errors;
|
||
|
||
/// Claims the latest user intent to replace playback after asynchronous
|
||
/// queue construction. Callers must check [isPlayIntentCurrent] before
|
||
/// committing fetched tracks.
|
||
int beginPlayIntent();
|
||
|
||
/// Whether [intent] is still the latest playback-replacement request.
|
||
bool isPlayIntentCurrent(int intent);
|
||
|
||
/// Changes whenever a queue session starts or stops. Asynchronous enqueue
|
||
/// actions use this to avoid appending fetched tracks to a newer session.
|
||
int get queueSessionRevision;
|
||
|
||
/// Start a new queue from [tracks], optionally at [startTrack] (defaults
|
||
/// to the first track). [shuffle] shuffles with the start track anchored
|
||
/// first.
|
||
Future<void> playFromList({
|
||
required List<MediaItem> tracks,
|
||
MediaItem? startTrack,
|
||
required MusicPlayContext playContext,
|
||
bool shuffle = false,
|
||
});
|
||
|
||
/// Fetch an instant mix seeded from [seed] and play it.
|
||
Future<void> playInstantMix(MediaItem seed);
|
||
|
||
Future<void> play();
|
||
Future<void> pause();
|
||
Future<void> togglePlayPause();
|
||
|
||
/// Advance to the next queue entry (respecting repeat mode).
|
||
Future<void> next();
|
||
|
||
/// Restart the current track when more than a few seconds in, otherwise
|
||
/// step to the previous queue entry.
|
||
Future<void> previous();
|
||
|
||
Future<void> seek(Duration position);
|
||
|
||
/// Music playback volume, 0–100. Preview updates are exposed separately so
|
||
/// a slider does not notify every service consumer on each drag event.
|
||
double get volume;
|
||
ValueListenable<double> get volumeListenable;
|
||
Future<void> setVolume(double volume, {bool persist = true});
|
||
|
||
void setRepeatMode(MusicRepeatMode mode);
|
||
void toggleShuffle();
|
||
|
||
/// Jump playback to queue index [index].
|
||
Future<void> jumpTo(int index);
|
||
|
||
void removeAt(int index);
|
||
void reorder(int from, int to);
|
||
|
||
/// Insert after the current track.
|
||
void addNext(List<MediaItem> tracks);
|
||
void addToEnd(List<MediaItem> tracks);
|
||
|
||
/// Drop everything after the current track.
|
||
void clearUpcoming();
|
||
|
||
/// Stop playback and clear the session (mini-player disappears).
|
||
Future<void> stop();
|
||
|
||
/// Whether a sleep timer (timed or end-of-track) is armed.
|
||
bool get sleepTimerActive;
|
||
|
||
/// When the timed sleep timer fires; null in end-of-track mode or when
|
||
/// inactive.
|
||
DateTime? get sleepTimerEndsAt;
|
||
|
||
/// The duration the timed sleep timer was armed with (for marking the
|
||
/// chosen preset); null in end-of-track mode or when inactive.
|
||
Duration? get sleepTimerDuration;
|
||
|
||
/// Whether the sleep timer pauses at the end of the current track instead
|
||
/// of after a fixed duration.
|
||
bool get sleepTimerEndOfTrack;
|
||
|
||
/// Arm the sleep timer: a fixed [duration], or [endOfTrack] to pause when
|
||
/// the current track finishes. Pass `null` with `endOfTrack: false` to
|
||
/// cancel. Fires as a pause (session stays); cancelled by [stop].
|
||
void setSleepTimer(Duration? duration, {bool endOfTrack = false});
|
||
|
||
/// Lyrics for [track] (defaults to the current track's backend). Delegates
|
||
/// to `MediaServerClient.fetchLyrics`; null = none available.
|
||
Future<Lyrics?> fetchLyrics(MediaItem track);
|
||
}
|