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/audio_handler.dart
T
Dustin e46ffa76a3 fix: 6 Claude-Audit-Fixes (3 CRIT + 3 HIGH)
CRITICAL:
- Logout: LoginScreen statt MeloHome pushen (home_screen.dart)
- Path Traversal: sanitizeDateiname() zentral + sync-loop abgesichert
- SecureStorage: flutter_secure_storage für Token+Passwort (auth_service, navidrome)

HIGH:
- Seek: PlayerService.seek() reload-frei (audio_handler)
- Play/Pause: Zielzustand statt Toggle für System-Controls
- X-User: Header entfernt, Server verlässt sich auf JWT
2026-08-03 01:41:04 +02:00

92 lines
2.4 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.play, MediaControl.skipToPrevious, MediaControl.skipToNext],
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.play();
}
}
@override
Future<void> pause() async {
_playing = false;
await _player.pause();
}
@override
Future<void> stop() async {
_playing = false;
await _player.pause();
playbackState.add(playbackState.value.copyWith(
controls: [MediaControl.play],
playing: false,
processingState: AudioProcessingState.idle,
));
}
@override
Future<void> seek(Duration position) async {
await _player.seek(position);
}
@override
Future<void> skipToNext() => _player.naechstes();
@override
Future<void> skipToPrevious() => _player.vorheriges();
// Exposed for the ViewModel
PlayerService get player => _player;
}