This repository has been archived on 2026-08-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
melo-app/lib/services/player_service.dart
T
Hermes (Server) 8b6a04ead8 Fix: Review-Findings korrigiert (Tag-Leiste, Profil, Singleton u.a.)
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
2026-07-31 15:42:54 +02:00

108 lines
3.2 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:just_audio/just_audio.dart';
import '../models/song.dart';
import 'melo_logger.dart';
class PlayerService {
static final PlayerService _instanz = PlayerService._();
factory PlayerService() => _instanz;
PlayerService._();
AudioPlayer? _player;
final List<Song> _warteschlange = [];
int _aktuellerIndex = -1;
StreamSubscription<PlayerState>? _autoNextSub;
AudioPlayer get _p {
if (_player == null) {
_player = AudioPlayer();
_autoNextSub = _player!.playerStateStream.listen((state) {
if (state.processingState == ProcessingState.completed) {
naechstes();
}
});
}
return _player!;
}
Song? get aktuellerSong => _aktuellerIndex >= 0 && _aktuellerIndex < _warteschlange.length
? _warteschlange[_aktuellerIndex] : null;
bool get spieltAb => _player?.playing ?? false;
Duration get position => _player?.position ?? Duration.zero;
Duration get dauer => _player?.duration ?? Duration.zero;
Stream<Duration> get positionStream => _p.positionStream;
Stream<PlayerState> get stateStream => _p.playerStateStream;
final StreamController<Song?> _songWechsel = StreamController.broadcast();
Stream<Song?> get onSongWechsel => _songWechsel.stream;
Future<void> spiele(Song song, {int position = 0}) async {
_aktuellerIndex = _warteschlange.indexWhere((s) => s.id == song.id);
if (_aktuellerIndex < 0) {
_warteschlange.add(song);
_aktuellerIndex = _warteschlange.length - 1;
}
try {
if (song.streamUrl != null && song.streamUrl!.isNotEmpty) {
// Streaming vom Server
await _p.setUrl(song.streamUrl!);
} else if (song.dateiPfad.isNotEmpty && await File(song.dateiPfad).exists()) {
// Lokale Datei
await _p.setFilePath(song.dateiPfad);
} else {
MeloLogger().fehler('player_keine_quelle', 'Keine gültige Quelle für: ${song.titel}');
return;
}
if (position > 0) await _p.seek(Duration(seconds: position));
await _p.play();
_songWechsel.add(song);
} catch (e) {
debugPrint('Fehler beim Abspielen: $e');
}
}
Future<void> playPause() async {
if (_player == null) return;
if (_p.playing) {
await _p.pause();
} else if (aktuellerSong != null) {
await _p.play();
}
}
Future<void> vorheriges() async {
if (_player == null || aktuellerSong == null) return;
final pos = _p.position;
if (pos.inSeconds > 5) {
await _p.seek(Duration.zero);
} else {
final neuerIndex = _aktuellerIndex - 1;
if (neuerIndex >= 0) {
await spiele(_warteschlange[neuerIndex]);
}
}
}
Future<void> naechstes() async {
final neuerIndex = _aktuellerIndex + 1;
if (neuerIndex < _warteschlange.length) {
await spiele(_warteschlange[neuerIndex]);
}
}
void setWarteschlange(List<Song> songs, {int startIndex = 0}) {
_warteschlange.clear();
_warteschlange.addAll(songs);
_aktuellerIndex = startIndex;
}
void dispose() {
_autoNextSub?.cancel();
_player?.dispose();
_player = null;
_songWechsel.close();
}
}