fix: code-review #2 — dedup MagicBytes, auth für check_corrupted, UI-Flag, merged player streams, jq-JSON
This commit is contained in:
@@ -27,6 +27,7 @@ class _CloudScreenState extends State<CloudScreen> {
|
||||
String _letzterSync = 'Nie';
|
||||
List<Map> _korrupteSongs = [];
|
||||
bool _ladtKorrupt = false;
|
||||
bool _hatGeprueft = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -94,12 +95,13 @@ class _CloudScreenState extends State<CloudScreen> {
|
||||
}
|
||||
|
||||
Future<void> _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<CloudScreen> {
|
||||
],
|
||||
),
|
||||
)),
|
||||
] else if (!_ladtKorrupt && _korrupteSongs.isEmpty)
|
||||
] else if (!_ladtKorrupt && _hatGeprueft && _korrupteSongs.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
|
||||
@@ -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<http.Response> _retryHttpPost(
|
||||
Uri url, {
|
||||
|
||||
@@ -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('.');
|
||||
|
||||
@@ -15,7 +15,6 @@ class PlayerService {
|
||||
final List<Song> _warteschlange = [];
|
||||
int _aktuellerIndex = -1;
|
||||
StreamSubscription<PlayerState>? _autoNextSub;
|
||||
StreamSubscription<PlayerState>? _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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user