aitube2 / lib /widgets /video_player /nano_clip_manager.dart
jbilcke-hf's picture
jbilcke-hf HF Staff
fixing small bugs here and there
2e813e6
// 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;
}
}