From f00fd445f5a7917ebb1894eb3a2a04a4dfb75e78 Mon Sep 17 00:00:00 2001 From: Dustin Date: Sat, 1 Aug 2026 16:48:45 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20code-review=20#2=20=E2=80=94=20dedup=20M?= =?UTF-8?q?agicBytes,=20auth=20f=C3=BCr=20check=5Fcorrupted,=20UI-Flag,=20?= =?UTF-8?q?merged=20player=20streams,=20jq-JSON?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/screens/cloud_screen.dart | 8 +++-- lib/services/download_service.dart | 46 ++--------------------------- lib/services/musik_scanner.dart | 34 ++------------------- lib/services/player_service.dart | 12 +------- lib/utils/audio_validator.dart | 47 ++++++++++++++++++++++++++++++ 5 files changed, 57 insertions(+), 90 deletions(-) create mode 100644 lib/utils/audio_validator.dart diff --git a/lib/screens/cloud_screen.dart b/lib/screens/cloud_screen.dart index 17bf480..2ca8acf 100644 --- a/lib/screens/cloud_screen.dart +++ b/lib/screens/cloud_screen.dart @@ -27,6 +27,7 @@ class _CloudScreenState extends State { String _letzterSync = 'Nie'; List _korrupteSongs = []; bool _ladtKorrupt = false; + bool _hatGeprueft = false; @override void initState() { @@ -94,12 +95,13 @@ class _CloudScreenState extends State { } Future _ladeKorrupteSongs() async { - setState(() => _ladtKorrupt = true); + setState(() { _ladtKorrupt = true; _hatGeprueft = false; }); try { final corrupted = await widget.cloud.getCorrupted(); - if (mounted) setState(() => _korrupteSongs = corrupted); + if (mounted) setState(() { _korrupteSongs = corrupted; _hatGeprueft = true; }); } catch (e) { MeloLogger().fehler('cloud_corrupted_laden', e); + if (mounted) setState(() => _hatGeprueft = true); } finally { if (mounted) setState(() => _ladtKorrupt = false); } @@ -598,7 +600,7 @@ class _CloudScreenState extends State { ], ), )), - ] else if (!_ladtKorrupt && _korrupteSongs.isEmpty) + ] else if (!_ladtKorrupt && _hatGeprueft && _korrupteSongs.isEmpty) const Padding( padding: EdgeInsets.only(top: 8), child: Text( diff --git a/lib/services/download_service.dart b/lib/services/download_service.dart index 52c7b6c..961fc68 100644 --- a/lib/services/download_service.dart +++ b/lib/services/download_service.dart @@ -6,6 +6,7 @@ import 'package:http/http.dart' as http; import 'package:path_provider/path_provider.dart'; import '../models/song.dart'; import '../database/db_helper.dart'; +import '../utils/audio_validator.dart'; import 'melo_logger.dart'; import '../config/app_config.dart'; @@ -268,7 +269,7 @@ class DownloadService extends ChangeNotifier { await file.writeAsBytes(mp3Antwort.bodyBytes); // ── Magic-Byte-Prüfung: Echte MP3-Datei? ── - istKorrupt = !_hatValideMagicBytes(file); + istKorrupt = !hatValideMagicBytes(file); if (istKorrupt) { debugPrint('⚠️ Korrupte Datei erkannt (Magic Bytes): $dateiPfad'); MeloLogger().fehler('magic_bytes_check', 'Ungültiger MP3-Header in $dateiPfad'); @@ -336,49 +337,6 @@ class DownloadService extends ChangeNotifier { } } - /// Prüft die Magic Bytes einer Audiodatei. - /// MP3: ID3-Tag (49 44 33) oder MPEG-Frame (FF FB, FF FA, FF F3, FF F2) - /// M4A/AAC: ftyp-Box (66 74 79 70) - /// FLAC: fLaC (66 4C 61 43) - /// WAV: RIFF (52 49 46 46) - /// OGG: OggS (4F 67 67 53) - static bool _hatValideMagicBytes(File file) { - try { - if (!file.existsSync()) return false; - final bytes = file.readAsBytesSync().take(16).toList(); - if (bytes.length < 4) return false; - - // ID3v2 Tag (MP3 mit Metadaten) — Bytes: 49 44 33 - if (bytes[0] == 0x49 && bytes[1] == 0x44 && bytes[2] == 0x33) return true; - - // MP3 ohne ID3: MPEG Audio Frame Sync (FF FB, FF FA, FF F3, FF F2) - if (bytes[0] == 0xFF && (bytes[1] & 0xFE) == 0xFA) return true; // MPEG v1 - if (bytes[0] == 0xFF && bytes[1] == 0xF3) return true; // MPEG v2 / v2.5 - if (bytes[0] == 0xFF && bytes[1] == 0xF2) return true; // MPEG v2 / v2.5 - - // M4A/AAC: ftyp-Box - if (bytes.length >= 8 && - bytes[4] == 0x66 && bytes[5] == 0x74 && - bytes[6] == 0x79 && bytes[7] == 0x70) return true; - - // FLAC: fLaC - if (bytes[0] == 0x66 && bytes[1] == 0x4C && - bytes[2] == 0x61 && bytes[3] == 0x43) return true; - - // WAV: RIFF - if (bytes[0] == 0x52 && bytes[1] == 0x49 && - bytes[2] == 0x46 && bytes[3] == 0x46) return true; - - // OGG: OggS - if (bytes[0] == 0x4F && bytes[1] == 0x67 && - bytes[2] == 0x67 && bytes[3] == 0x53) return true; - - return false; - } catch (_) { - return false; - } - } - /// HTTP POST mit Retry (exponentieller Backoff) Future _retryHttpPost( Uri url, { diff --git a/lib/services/musik_scanner.dart b/lib/services/musik_scanner.dart index 3bbfafc..fee0056 100644 --- a/lib/services/musik_scanner.dart +++ b/lib/services/musik_scanner.dart @@ -5,6 +5,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import '../models/song.dart'; import '../database/db_helper.dart'; +import '../utils/audio_validator.dart'; import 'id3_reader.dart'; import 'melo_logger.dart'; @@ -52,7 +53,7 @@ class MusikScanner { final tags = Id3Reader.lesen(pfad); // ── Magic-Byte-Prüfung ── - final istKorrupt = !_hatValideMagicBytes(pfad); + final istKorrupt = !hatValideMagicBytes(file); if (istKorrupt) { MeloLogger().fehler('magic_bytes_scan', 'Ungültiger Audio-Header: $pfad'); } @@ -192,37 +193,6 @@ class MusikScanner { } } - static bool _hatValideMagicBytes(String pfad) { - try { - final file = File(pfad); - if (!file.existsSync()) return false; - final bytes = file.readAsBytesSync().take(16).toList(); - if (bytes.length < 4) return false; - - // ID3v2 Tag (MP3) — 49 44 33 - if (bytes[0] == 0x49 && bytes[1] == 0x44 && bytes[2] == 0x33) return true; - // MPEG Audio Frame Sync - if (bytes[0] == 0xFF && (bytes[1] & 0xFE) == 0xFA) return true; - if (bytes[0] == 0xFF && (bytes[1] == 0xF3 || bytes[1] == 0xF2)) return true; - // M4A/AAC - if (bytes.length >= 8 && bytes[4] == 0x66 && bytes[5] == 0x74 && - bytes[6] == 0x79 && bytes[7] == 0x70) return true; - // FLAC - if (bytes[0] == 0x66 && bytes[1] == 0x4C && - bytes[2] == 0x61 && bytes[3] == 0x43) return true; - // WAV - if (bytes[0] == 0x52 && bytes[1] == 0x49 && - bytes[2] == 0x46 && bytes[3] == 0x46) { return true; } - // OGG - if (bytes[0] == 0x4F && bytes[1] == 0x67 && - bytes[2] == 0x67 && bytes[3] == 0x53) { return true; } - - return false; - } catch (_) { - return false; - } - } - String _dateiNameOhneEndung(String pfad) { final name = pfad.split('/').last; final dot = name.lastIndexOf('.'); diff --git a/lib/services/player_service.dart b/lib/services/player_service.dart index 6019dac..ad9cb6e 100644 --- a/lib/services/player_service.dart +++ b/lib/services/player_service.dart @@ -15,7 +15,6 @@ class PlayerService { final List _warteschlange = []; int _aktuellerIndex = -1; StreamSubscription? _autoNextSub; - StreamSubscription? _fehlerSub; AudioPlayer get _p { if (_player == null) { @@ -23,14 +22,7 @@ class PlayerService { _autoNextSub = _player!.playerStateStream.listen((state) { if (state.processingState == ProcessingState.completed) { naechstes(); - } - }); - // ── Playback-Fehler abfangen → Song als korrupt markieren ── - _fehlerSub = _player!.playerStateStream.listen((state) { - if (state.processingState == ProcessingState.completed) return; // bereits behandelt - // PlayerState hat kein explizites "failure"-Feld, aber wir prüfen ob - // die Quelle nicht geladen werden konnte (idle nach Fehlversuch) - if (state.processingState == ProcessingState.idle && + } else if (state.processingState == ProcessingState.idle && _aktuellerIndex >= 0 && _aktuellerIndex < _warteschlange.length) { final song = _warteschlange[_aktuellerIndex]; @@ -39,7 +31,6 @@ class PlayerService { _markiereAlsKorrupt(song); } } - }); } return _player!; } @@ -122,7 +113,6 @@ class PlayerService { void dispose() { _autoNextSub?.cancel(); - _fehlerSub?.cancel(); _player?.dispose(); _player = null; _songWechsel.close(); diff --git a/lib/utils/audio_validator.dart b/lib/utils/audio_validator.dart new file mode 100644 index 0000000..c16e5f4 --- /dev/null +++ b/lib/utils/audio_validator.dart @@ -0,0 +1,47 @@ +import 'dart:io'; + +/// Gemeinsame Utility: Validiert Magic Bytes von Audiodateien. +/// Prüft MP3 (ID3v2 + MPEG-Frames), M4A/AAC, FLAC, WAV, OGG. +/// +/// Prüft die Magic Bytes einer Audiodatei. +/// MP3: ID3-Tag (49 44 33) oder MPEG-Frame (FF FB, FF FA, FF F3, FF F2) +/// M4A/AAC: ftyp-Box (66 74 79 70) +/// FLAC: fLaC (66 4C 61 43) +/// WAV: RIFF (52 49 46 46) +/// OGG: OggS (4F 67 67 53) +bool hatValideMagicBytes(File file) { + try { + if (!file.existsSync()) return false; + final bytes = file.readAsBytesSync().take(16).toList(); + if (bytes.length < 4) return false; + + // ID3v2 Tag (MP3 mit Metadaten) — Bytes: 49 44 33 + if (bytes[0] == 0x49 && bytes[1] == 0x44 && bytes[2] == 0x33) return true; + + // MP3 ohne ID3: MPEG Audio Frame Sync (FF FB, FF FA, FF F3, FF F2) + if (bytes[0] == 0xFF && (bytes[1] & 0xFE) == 0xFA) return true; // MPEG v1 + if (bytes[0] == 0xFF && bytes[1] == 0xF3) return true; // MPEG v2 / v2.5 + if (bytes[0] == 0xFF && bytes[1] == 0xF2) return true; // MPEG v2 / v2.5 + + // M4A/AAC: ftyp-Box + if (bytes.length >= 8 && + bytes[4] == 0x66 && bytes[5] == 0x74 && + bytes[6] == 0x79 && bytes[7] == 0x70) return true; + + // FLAC: fLaC + if (bytes[0] == 0x66 && bytes[1] == 0x4C && + bytes[2] == 0x61 && bytes[3] == 0x43) { return true; } + + // WAV: RIFF + if (bytes[0] == 0x52 && bytes[1] == 0x49 && + bytes[2] == 0x46 && bytes[3] == 0x46) { return true; } + + // OGG: OggS + if (bytes[0] == 0x4F && bytes[1] == 0x67 && + bytes[2] == 0x67 && bytes[3] == 0x53) { return true; } + + return false; + } catch (_) { + return false; + } +}