import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart' as path; import 'package:video_thumbnail/video_thumbnail.dart' as video_thumb; import '../../core/constants/env_config.dart'; /// Résultat d'un upload de média. class MediaUploadResult { const MediaUploadResult({ required this.url, required this.thumbnailUrl, required this.type, this.duration, }); final String url; final String? thumbnailUrl; final String type; // 'image' ou 'video' final Duration? duration; } /// Service d'upload de médias vers le backend. /// /// Gère l'upload d'images et de vidéos avec compression et génération de thumbnails. class MediaUploadService { MediaUploadService(this._client); final http.Client _client; /// URL de base pour l'upload (à configurer selon votre backend) static const String _uploadEndpoint = '${EnvConfig.apiBaseUrl}/media/upload'; /// Upload un seul média (image ou vidéo). Future uploadMedia(XFile file) async { try { if (EnvConfig.enableDetailedLogs) { debugPrint('[MediaUploadService] Upload de: ${file.path}'); } final fileExtension = path.extension(file.path).toLowerCase(); final isVideo = _isVideoFile(fileExtension); // Créer la requête multipart final request = http.MultipartRequest('POST', Uri.parse(_uploadEndpoint)); // Ajouter le fichier final fileBytes = await file.readAsBytes(); final multipartFile = http.MultipartFile.fromBytes( 'file', fileBytes, filename: path.basename(file.path), ); request.files.add(multipartFile); // Ajouter le type request.fields['type'] = isVideo ? 'video' : 'image'; // Envoyer la requête final streamedResponse = await request.send(); final response = await http.Response.fromStream(streamedResponse); if (response.statusCode == 200 || response.statusCode == 201) { // Parser la réponse JSON du backend final responseData = json.decode(response.body) as Map; // Format attendu du backend: // { // "url": "https://...", // "thumbnailUrl": "https://...", (optionnel) // "type": "image" ou "video", // "duration": 60 (en secondes, optionnel) // } final url = responseData['url'] as String? ?? 'https://example.com/media/${path.basename(file.path)}'; final thumbnailUrl = responseData['thumbnailUrl'] as String?; final typeFromBackend = responseData['type'] as String?; final durationSeconds = responseData['duration'] as int?; if (EnvConfig.enableDetailedLogs) { debugPrint('[MediaUploadService] Upload réussi: $url'); } return MediaUploadResult( url: url, thumbnailUrl: thumbnailUrl, type: typeFromBackend ?? (isVideo ? 'video' : 'image'), duration: durationSeconds != null ? Duration(seconds: durationSeconds) : null, ); } else { throw Exception( 'Échec de l\'upload: ${response.statusCode} - ${response.body}', ); } } catch (e) { debugPrint('[MediaUploadService] Erreur: $e'); rethrow; } } /// Upload plusieurs médias en parallèle. Future> uploadMultipleMedias( List files, { void Function(int uploaded, int total)? onProgress, }) async { final results = []; int uploaded = 0; for (final file in files) { try { final result = await uploadMedia(file); results.add(result); uploaded++; if (onProgress != null) { onProgress(uploaded, files.length); } } catch (e) { debugPrint('[MediaUploadService] Échec upload ${file.path}: $e'); // On continue avec les autres fichiers } } return results; } /// Vérifie si le fichier est une vidéo. bool _isVideoFile(String extension) { const videoExtensions = ['.mp4', '.mov', '.avi', '.mkv', '.m4v']; return videoExtensions.contains(extension); } /// Supprime un média du serveur. Future deleteMedia(String mediaUrl) async { try { if (EnvConfig.enableDetailedLogs) { debugPrint('[MediaUploadService] Suppression de: $mediaUrl'); } // Extraire l'ID ou le nom du fichier de l'URL final uri = Uri.parse(mediaUrl); final fileName = uri.pathSegments.last; // Appel API pour supprimer le média final deleteUrl = '${EnvConfig.apiBaseUrl}/media/$fileName'; final response = await _client.delete( Uri.parse(deleteUrl), headers: {'Content-Type': 'application/json'}, ); if (response.statusCode == 200 || response.statusCode == 204) { if (EnvConfig.enableDetailedLogs) { debugPrint('[MediaUploadService] Média supprimé: $mediaUrl'); } } else { throw Exception( 'Échec de la suppression: ${response.statusCode} - ${response.body}', ); } } catch (e) { debugPrint('[MediaUploadService] Erreur suppression: $e'); rethrow; } } /// Génère un thumbnail pour une vidéo. Future generateVideoThumbnail(String videoPath) async { try { if (EnvConfig.enableDetailedLogs) { debugPrint('[MediaUploadService] Génération thumbnail pour: $videoPath'); } // Générer le thumbnail à partir de la vidéo final thumbnailPath = await video_thumb.VideoThumbnail.thumbnailFile( video: videoPath, thumbnailPath: (await Directory.systemTemp.createTemp()).path, imageFormat: video_thumb.ImageFormat.JPEG, maxWidth: 640, quality: 75, ); if (thumbnailPath != null && EnvConfig.enableDetailedLogs) { debugPrint('[MediaUploadService] Thumbnail généré: $thumbnailPath'); } return thumbnailPath; } catch (e) { debugPrint('[MediaUploadService] Erreur génération thumbnail: $e'); return null; } } }