// lib/widgets/video_player/nano_clip_manager.dart import 'dart:async'; import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:tikslop/models/video_result.dart'; import 'package:tikslop/services/clip_queue/video_clip.dart'; import 'package:tikslop/services/clip_queue/clip_states.dart'; import 'package:tikslop/services/websocket_api_service.dart'; import 'package:tikslop/utils/seed.dart'; import 'package:uuid/uuid.dart'; /// Manages a single video clip generation for thumbnail playback class NanoClipManager { /// The video result for which the clip is being generated final VideoResult video; /// WebSocket service for API communication final WebSocketApiService _websocketService; /// Callback for when the clip is updated final void Function()? onClipUpdated; /// The generated video clip VideoClip? _videoClip; /// Whether the manager is disposed bool _isDisposed = false; /// Status text to show during generation String _statusText = 'Initializing...'; /// Get the current video clip VideoClip? get videoClip => _videoClip; /// Get the current status text String get statusText => _statusText; /// Constructor NanoClipManager({ required this.video, WebSocketApiService? websocketService, this.onClipUpdated, }) : _websocketService = websocketService ?? WebSocketApiService(); /// Initialize and generate a single clip Future initialize({ int? overrideSeed, Duration timeout = const Duration(seconds: 10), }) async { if (_isDisposed) return; try { // Use either provided seed, video's seed, or generate a new one final seed = overrideSeed ?? (video.useFixedSeed && video.seed > 0 ? video.seed : generateSeed()); // Create a video clip _videoClip = VideoClip( prompt: "${video.title}\n${video.description}", seed: seed, ); _updateStatus('Connecting...'); // Set up WebSocket API service if needed if (_websocketService.status != ConnectionStatus.connected) { _updateStatus('Connecting to server...'); await _websocketService.initialize(); if (_isDisposed) return; if (_websocketService.status != ConnectionStatus.connected) { _updateStatus('Connection failed'); _videoClip!.state = ClipState.failedToGenerate; return; } } _updateStatus('Requesting thumbnail...'); // Set up timeout final completer = Completer(); Timer? timeoutTimer; timeoutTimer = Timer(timeout, () { if (!completer.isCompleted) { _updateStatus('Generation timed out'); completer.complete(); } }); // Request the thumbnail generation try { // Create request for thumbnail generation final requestId = const Uuid().v4(); // Mark as generating _videoClip!.state = ClipState.generationInProgress; // Initiate a request to generate a thumbnail // Using available methods in WebSocketApiService _generateThumbnail(seed, requestId).then((thumbnailData) { if (_isDisposed) return; if (thumbnailData != null && thumbnailData.isNotEmpty) { // Successful generation _videoClip!.base64Data = thumbnailData; _videoClip!.state = ClipState.generatedAndReadyToPlay; _updateStatus('Ready'); } else { // Generation failed _videoClip!.state = ClipState.failedToGenerate; _updateStatus('Failed to generate'); } completer.complete(); }).catchError((error) { debugPrint('Error generating thumbnail: $error'); _videoClip!.state = ClipState.failedToGenerate; _updateStatus('Error: $error'); completer.complete(); }); // Wait for completion or timeout await completer.future; timeoutTimer.cancel(); } catch (e) { // Handle any errors debugPrint('Error in thumbnail generation: $e'); _videoClip!.state = ClipState.failedToGenerate; _updateStatus('Error generating'); timeoutTimer.cancel(); } } catch (e) { debugPrint('Error initializing nano clip: $e'); _updateStatus('Error initializing'); } } /// Generate a thumbnail using the WebSocketApiService Future _generateThumbnail(int seed, String requestId) async { if (_isDisposed) return null; // Show progress updates _simulateProgress(); // If we're in debug mode and on web, we might need to mock the response if (kDebugMode && !_websocketService.isConnected) { await Future.delayed(const Duration(seconds: 3)); return 'data:video/mp4;base64,AAAA'; // Mock base64 data } try { // Create a request to generate the thumbnail // We'll actually implement this using the VideoResult object since that's // what the API expects final result = await _websocketService.generateVideo( video, width: 512, // Small size for thumbnail height: 288, // 16:9 aspect ratio seed: seed, // Use our specific seed ); return result; } catch (e) { debugPrint('Error generating thumbnail through API: $e'); if (kDebugMode) { // In debug mode, return mock data so development can continue return 'data:video/mp4;base64,AAAA'; } return null; } } /// Simulate generation progress during development or when server is slow void _simulateProgress() { if (_isDisposed) return; const progressSteps = [ {'delay': Duration(milliseconds: 500), 'progress': 20}, {'delay': Duration(seconds: 1), 'progress': 40}, {'delay': Duration(seconds: 2), 'progress': 60}, {'delay': Duration(seconds: 3), 'progress': 80} ]; // Show progress updates for (final step in progressSteps) { Future.delayed(step['delay'] as Duration, () { if (_isDisposed) return; _updateStatus('Generating (${step['progress']}%)'); }); } } /// Update the status text and notify listeners void _updateStatus(String status) { if (_isDisposed) return; _statusText = status; onClipUpdated?.call(); } /// Dispose resources void dispose() { _isDisposed = true; } }