import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; import 'package:path_provider/path_provider.dart'; import '../models/song.dart'; import '../database/db_helper.dart'; class DownloadService { static final DownloadService _instanz = DownloadService._(); factory DownloadService() => _instanz; DownloadService._(); final YoutubeExplode _yt = YoutubeExplode(); final DbHelper _db = DbHelper(); bool _ladt = false; double _fortschritt = 0; String? _aktuellerTitel; String? _fehler; bool get ladt => _ladt; double get fortschritt => _fortschritt; String? get aktuellerTitel => _aktuellerTitel; String? get fehler => _fehler; void _resetStatus() { _ladt = true; _fortschritt = 0; _aktuellerTitel = null; _fehler = null; } Future downloadVonUrl(String url) async { _resetStatus(); try { // URL-Validierung if (!url.contains('youtube.com') && !url.contains('youtu.be')) { _fehler = 'Keine gültige YouTube-URL'; _ladt = false; return null; } String titel = ''; String kuenstler = ''; int dauer = 0; // Video-Info mit Timeout _aktuellerTitel = 'YouTube wird kontaktiert...'; try { final video = await _yt.videos.get(url).timeout(const Duration(seconds: 15)); titel = video.title; kuenstler = video.author; dauer = video.duration?.inSeconds ?? 0; _aktuellerTitel = 'Video gefunden: $titel'; } catch (e) { _fehler = 'Timeout/Fehler bei Video-Info: ${e.toString()}'; debugPrint('Video-Info Fehler (vollständig): $e'); _ladt = false; return null; } // Manifest abrufen _aktuellerTitel = 'Audio-Stream wird ermittelt... (2/4)'; AudioStreamInfo? audio; try { final manifest = await _yt.videos.streamsClient .getManifest(url).timeout(const Duration(seconds: 15)); final streams = manifest.audioOnly.toList(); if (streams.isEmpty) { _fehler = 'Kein Audio-Stream verfügbar'; _ladt = false; return null; } audio = streams.reduce((a, b) => a.bitrate.bitsPerSecond > b.bitrate.bitsPerSecond ? a : b); } catch (e) { _fehler = 'Stream-Fehler: ${e.toString()}'; debugPrint('Stream Manifest Fehler (vollständig): $e'); _ladt = false; return null; } // Zielpfad _aktuellerTitel = 'Speicher wird vorbereitet... (3/4)'; final dir = await getApplicationDocumentsDirectory(); final musikDir = Directory('${dir.path}/music'); if (!await musikDir.exists()) await musikDir.create(recursive: true); final safeName = titel.replaceAll(RegExp(r'[^\w\s-]'), '').trim(); final dateiName = '${safeName.isEmpty ? "song" : safeName}.mp4'; final dateiPfad = '${musikDir.path}/$dateiName'; // Download _aktuellerTitel = 'Lade herunter... (4/4)'; try { final fileStream = _yt.videos.streamsClient.get(audio); final file = File(dateiPfad); final sink = file.openWrite(); int downloaded = 0; final total = audio.size.totalBytes; await for (final chunk in fileStream) { sink.add(chunk); downloaded += chunk.length; _fortschritt = downloaded / total; } await sink.flush(); await sink.close(); } catch (e) { _fehler = 'Download-Fehler: ${e.toString()}'; debugPrint('Download Stream Fehler (vollständig): $e'); _ladt = false; return null; } // Song speichern final song = Song( titel: titel, kuenstler: kuenstler, album: 'YouTube', dauerSekunden: dauer, dateiPfad: dateiPfad, groesseBytes: await File(dateiPfad).length(), istHeruntergeladen: true, downloadQuelle: 'youtube', ); await _db.songEinfuegen(song); _ladt = false; _aktuellerTitel = '✅ ${song.titel} heruntergeladen'; return song; } catch (e) { _fehler = 'Fehler: ${e.toString()}'; debugPrint('Download allgemeiner Fehler (vollständig): $e'); _ladt = false; return null; } } void dispose() { _yt.close(); } }