147 lines
4.0 KiB
Dart
147 lines
4.0 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:just_audio/just_audio.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
import '../models/song.dart';
|
|
import '../database/db_helper.dart';
|
|
|
|
class MusikScanner {
|
|
static final MusikScanner _instanz = MusikScanner._();
|
|
factory MusikScanner() => _instanz;
|
|
MusikScanner._();
|
|
|
|
final DbHelper _db = DbHelper();
|
|
int _neueSongs = 0;
|
|
|
|
int get anzahlNeueSongs => _neueSongs;
|
|
|
|
Future<bool> frageSpeicherZugriff() async {
|
|
final status = await Permission.audio.request();
|
|
return status.isGranted;
|
|
}
|
|
|
|
Future<List<Song>> scanneMusikOrdner() async {
|
|
_neueSongs = 0;
|
|
final gefunden = <Song>[];
|
|
final pfade = await _sammleMusikPfade();
|
|
|
|
for (final pfad in pfade) {
|
|
try {
|
|
final file = File(pfad);
|
|
if (!await file.exists()) continue;
|
|
final stat = await file.stat();
|
|
gefunden.add(Song(
|
|
titel: _dateiNameOhneEndung(pfad),
|
|
kuenstler: 'Unbekannt',
|
|
album: '',
|
|
dauerSekunden: await _ermittleDauer(pfad),
|
|
dateiPfad: pfad,
|
|
coverPfad: null,
|
|
groesseBytes: stat.size,
|
|
istHeruntergeladen: true,
|
|
downloadQuelle: 'local',
|
|
));
|
|
} catch (_) {
|
|
// Datei nicht lesbar → überspringen
|
|
}
|
|
}
|
|
|
|
// In DB speichern
|
|
final vorhandene = await _db.alleSongs();
|
|
final vorhandenePfade = vorhandene.map((s) => s.dateiPfad).toSet();
|
|
|
|
final neue = gefunden.where((s) => !vorhandenePfade.contains(s.dateiPfad)).toList();
|
|
if (neue.isNotEmpty) {
|
|
await _db.songsEinfuegen(neue);
|
|
_neueSongs = neue.length;
|
|
}
|
|
|
|
return gefunden;
|
|
}
|
|
|
|
Future<List<String>> _sammleMusikPfade() async {
|
|
final pfade = <String>{};
|
|
|
|
// Typische Musik-Ordner auf Android
|
|
final ordner = [
|
|
'/storage/emulated/0/Music',
|
|
'/storage/emulated/0/Download',
|
|
'/storage/emulated/0/Musik',
|
|
'/storage/emulated/0/Downloads',
|
|
'/sdcard/Music',
|
|
'/sdcard/Download',
|
|
'/sdcard/Musik',
|
|
];
|
|
|
|
// Externe SD-Karte (falls vorhanden)
|
|
try {
|
|
final extern = await getExternalStorageDirectory();
|
|
if (extern != null) {
|
|
ordner.add(extern.path);
|
|
}
|
|
} catch (_) {}
|
|
|
|
// Android Media Store (bessere Methode)
|
|
try {
|
|
final pfadeVonMediaStore = await _scanneViaMediaStore();
|
|
pfade.addAll(pfadeVonMediaStore);
|
|
} catch (_) {}
|
|
|
|
// Fallback: Dateisystem durchsuchen
|
|
for (final ord in ordner) {
|
|
try {
|
|
final dir = Directory(ord);
|
|
if (await dir.exists()) {
|
|
await _durchsucheOrdner(dir, pfade);
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
return pfade.toList();
|
|
}
|
|
|
|
Future<List<String>> _scanneViaMediaStore() async {
|
|
// Nutzt Android's MediaStore Query
|
|
// Wird über Method Channel in native Android implementiert
|
|
// Für v1: Fallback auf Dateisystem-Suche
|
|
return [];
|
|
}
|
|
|
|
Future<void> _durchsucheOrdner(Directory dir, Set<String> pfade, {int tiefe = 0}) async {
|
|
if (tiefe > 4) return;
|
|
try {
|
|
await for (final entity in dir.list(followLinks: false)) {
|
|
if (entity is File) {
|
|
final ext = entity.path.toLowerCase();
|
|
if (ext.endsWith('.mp3') || ext.endsWith('.m4a') ||
|
|
ext.endsWith('.flac') || ext.endsWith('.wav') ||
|
|
ext.endsWith('.aac') || ext.endsWith('.ogg')) {
|
|
pfade.add(entity.path);
|
|
}
|
|
} else if (entity is Directory) {
|
|
await _durchsucheOrdner(entity, pfade, tiefe: tiefe + 1);
|
|
}
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<int> _ermittleDauer(String pfad) async {
|
|
try {
|
|
final player = AudioPlayer();
|
|
await player.setFilePath(pfad);
|
|
final dauer = player.duration;
|
|
await player.dispose();
|
|
return dauer?.inSeconds ?? 0;
|
|
} catch (_) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
String _dateiNameOhneEndung(String pfad) {
|
|
final name = pfad.split('/').last;
|
|
final dot = name.lastIndexOf('.');
|
|
return dot > 0 ? name.substring(0, dot) : name;
|
|
}
|
|
}
|