File size: 6,588 Bytes
2e813e6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// lib/widgets/video_player/nano_clip_manager.dart

import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:aitube2/models/video_result.dart';
import 'package:aitube2/services/clip_queue/video_clip.dart';
import 'package:aitube2/services/clip_queue/clip_states.dart';
import 'package:aitube2/services/websocket_api_service.dart';
import 'package:aitube2/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<void> 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<void>();
      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<String?> _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;
  }
}