CRIT: - Tag-Leiste: Toggle-Kopf 'Tags & Filter' eingebaut (war unerreichbar) + TagStats - Profil: Cloud-Count vor Dialog aufloesen (kein 'Instance of Future' mehr) HIGH: - CloudEinstellungen: toten Sync-Timer entfernt (echter Timer im ViewModel) - StatistikCard + RecentWidget jetzt eingebaut (gesamtMB/gesamtMin endlich genutzt) - CloudService als Singleton (konsistenter Token-Zustand in allen Services) MED/LOW: - Playlist-Erkennung nur noch via list= Parameter - Player: fehlende Quelle wird geloggt statt still - song_tile: null-ID-Guard vor Tag-Dialog - Scanner-Log mit Exception-Objekt - FavoritenService: anzahlFavoriten() fuer StatistikCard
92 lines
2.5 KiB
Dart
92 lines
2.5 KiB
Dart
import 'dart:async';
|
|
import 'package:audio_service/audio_service.dart';
|
|
import 'package:just_audio/just_audio.dart';
|
|
import '../models/song.dart';
|
|
import 'player_service.dart';
|
|
|
|
/// Hintergrund-Audio-Handler für Sperrbildschirm & Benachrichtigung
|
|
class MeloAudioHandler extends BaseAudioHandler {
|
|
final PlayerService _player = PlayerService();
|
|
|
|
MeloAudioHandler() {
|
|
// Zustand vom Player an audio_service weiterleiten
|
|
_player.stateStream.listen(_updateState);
|
|
_player.positionStream.listen((pos) {
|
|
if (_playing) {
|
|
playbackState.add(playbackState.value.copyWith(
|
|
updatePosition: pos,
|
|
));
|
|
}
|
|
});
|
|
_player.onSongWechsel.listen((song) {
|
|
if (song != null) {
|
|
mediaItem.add(_toMediaItem(song));
|
|
}
|
|
});
|
|
}
|
|
|
|
bool _playing = false;
|
|
|
|
void _updateState(PlayerState state) {
|
|
_playing = state.playing;
|
|
playbackState.add(PlaybackState(
|
|
controls: _playing
|
|
? [MediaControl.pause, MediaControl.skipToPrevious, MediaControl.skipToNext, MediaControl.stop]
|
|
: [MediaControl.play, MediaControl.skipToPrevious, MediaControl.skipToNext, MediaControl.stop],
|
|
systemActions: const {MediaAction.seek},
|
|
androidCompactActionIndices: const [0, 1, 2],
|
|
processingState: _playing ? AudioProcessingState.ready : AudioProcessingState.idle,
|
|
playing: _playing,
|
|
speed: 1.0,
|
|
));
|
|
}
|
|
|
|
MediaItem _toMediaItem(Song song) => MediaItem(
|
|
id: song.id?.toString() ?? '0',
|
|
album: song.album.isEmpty ? 'Melo' : song.album,
|
|
title: song.titel,
|
|
artist: song.kuenstler,
|
|
duration: Duration(seconds: song.dauerSekunden),
|
|
artUri: song.coverPfad != null ? Uri.file(song.coverPfad!) : null,
|
|
);
|
|
|
|
@override
|
|
Future<void> play() async {
|
|
if (_player.aktuellerSong != null) {
|
|
_playing = true;
|
|
await _player.playPause();
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> pause() async {
|
|
_playing = false;
|
|
await _player.playPause();
|
|
}
|
|
|
|
@override
|
|
Future<void> stop() async {
|
|
_playing = false;
|
|
await _player.playPause();
|
|
playbackState.add(playbackState.value.copyWith(
|
|
controls: [MediaControl.play],
|
|
playing: false,
|
|
processingState: AudioProcessingState.idle,
|
|
));
|
|
}
|
|
|
|
@override
|
|
Future<void> seek(Duration position) async {
|
|
_player.spiele(_player.aktuellerSong!, position: position.inSeconds);
|
|
}
|
|
|
|
@override
|
|
Future<void> skipToNext() => _player.naechstes();
|
|
|
|
@override
|
|
Future<void> skipToPrevious() => _player.vorheriges();
|
|
|
|
// Exposed for the ViewModel
|
|
PlayerService get player => _player;
|
|
}
|