streaming edits

This commit is contained in:
Forrest 2026-08-13 11:29:11 -04:00
parent 0a7af1d813
commit db33f0764b
3 changed files with 62 additions and 15 deletions

View file

@ -32,6 +32,7 @@ class PlaybackState {
this.queue = const [],
this.currentIndex,
this.playing = false,
this.buffering = false,
this.position = Duration.zero,
this.duration = Duration.zero,
this.shuffle = false,
@ -43,6 +44,13 @@ class PlaybackState {
final List<Song> queue;
final int? currentIndex;
final bool playing;
/// True while the player is loading/buffering a source (not yet `ready`). A
/// streamed source that is buffering legitimately reports position 0; this
/// lets the UI show a spinner instead of a frozen 0:00 bar. Transient — never
/// persisted.
final bool buffering;
final Duration position;
final Duration duration;
final bool shuffle;
@ -83,6 +91,7 @@ class PlaybackState {
List<Song>? queue,
int? currentIndex,
bool? playing,
bool? buffering,
Duration? position,
Duration? duration,
bool? shuffle,
@ -95,6 +104,7 @@ class PlaybackState {
queue: queue ?? this.queue,
currentIndex: currentIndex ?? this.currentIndex,
playing: playing ?? this.playing,
buffering: buffering ?? this.buffering,
position: position ?? this.position,
duration: duration ?? this.duration,
shuffle: shuffle ?? this.shuffle,
@ -215,7 +225,12 @@ class PlaybackController extends StateNotifier<PlaybackState>
_maybeSlideWindow();
});
player.playerStateStream.listen((s) {
state = state.copyWith(playing: s.playing);
final ps = s.processingState;
state = state.copyWith(
playing: s.playing,
buffering: ps == ProcessingState.loading ||
ps == ProcessingState.buffering,
);
});
player.positionStream.listen((p) {
state = state.copyWith(position: p);
@ -306,20 +321,32 @@ class PlaybackController extends StateNotifier<PlaybackState>
AudioSource _sourceFor(Song song) {
final uri = _streamUriFor(song)!;
if (!uri.isScheme('file')) _remoteSourceIds.add(song.id);
final isRemote = !uri.isScheme('file');
if (isRemote) _remoteSourceIds.add(song.id);
final art = _coverArtUriFor(song);
return AudioSource.uri(
uri,
tag: MediaItem(
id: '${song.id}#${_tagSeq++}',
title: song.title ?? 'Unknown',
album: song.album,
artist: song.artist,
duration:
song.duration != null ? Duration(seconds: song.duration!) : null,
artUri: art,
),
final tag = MediaItem(
id: '${song.id}#${_tagSeq++}',
title: song.title ?? 'Unknown',
album: song.album,
artist: song.artist,
duration:
song.duration != null ? Duration(seconds: song.duration!) : null,
artUri: art,
);
// Wrap remote streams in a caching source on mobile: it fetches bytes to an
// OS-evictable temp file, giving the native player a genuinely seekable
// source with a known length — robust against chunked transcodes that omit
// Content-Length and against brief network drops. Downloads (file://) are
// already seekable, and the media_kit desktop backend uses just_audio's
// localhost proxy path we don't rely on, so both stay on the plain source.
if (isRemote && (Platform.isAndroid || Platform.isIOS)) {
// LockCachingAudioSource is marked experimental in just_audio but is
// stable in practice; the streaming reliability it provides is the whole
// point of this path.
// ignore: experimental_member_use
return LockCachingAudioSource(uri, tag: tag);
}
return AudioSource.uri(uri, tag: tag);
}
/// Replace the queue with [songs] and start at [startIndex].