Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
// lib/widgets/video_player/ui_components.dart | |
import 'package:flutter/material.dart'; | |
import 'package:aitube2/theme/colors.dart'; | |
import 'package:aitube2/widgets/ai_content_disclaimer.dart'; | |
import 'package:aitube2/config/config.dart'; | |
/// Builds a placeholder widget for when video is not loaded | |
Widget buildPlaceholder(String? initialThumbnailUrl) { | |
// Use our new AI Content Disclaimer widget as the placeholder | |
if (initialThumbnailUrl?.isEmpty ?? true) { | |
// Set isInteractive to true as we generate content on-the-fly | |
return const AiContentDisclaimer(isInteractive: true); | |
} | |
try { | |
if (initialThumbnailUrl!.startsWith('data:image')) { | |
final uri = Uri.parse(initialThumbnailUrl); | |
final base64Data = uri.data?.contentAsBytes(); | |
if (base64Data == null) { | |
throw Exception('Invalid image data'); | |
} | |
return Image.memory( | |
base64Data, | |
fit: BoxFit.cover, | |
errorBuilder: (_, __, ___) => buildErrorPlaceholder(), | |
); | |
} | |
return Image.network( | |
initialThumbnailUrl, | |
fit: BoxFit.cover, | |
errorBuilder: (_, __, ___) => buildErrorPlaceholder(), | |
); | |
} catch (e) { | |
return buildErrorPlaceholder(); | |
} | |
} | |
/// Builds an error placeholder for when image loading fails | |
Widget buildErrorPlaceholder() { | |
return const Center( | |
child: Icon( | |
Icons.broken_image, | |
size: 64, | |
color: AiTubeColors.onSurfaceVariant, | |
), | |
); | |
} | |
/// Builds a buffer status indicator widget | |
Widget buildBufferStatus({ | |
required bool showDuringLoading, | |
required bool isLoading, | |
required List<dynamic> clipBuffer, | |
}) { | |
final readyOrPlayingClips = clipBuffer.where((c) => c.isReady || c.isPlaying).length; | |
final totalClips = clipBuffer.length; | |
final bufferPercentage = (readyOrPlayingClips / totalClips * 100).round(); | |
// since we are playing clips at a reduced speed, they each last "longer" | |
// eg a video playing back at 0.5 speed will see its duration multiplied by 2 | |
final actualDurationPerClip = Configuration.instance.actualClipPlaybackDuration; | |
final remainingSeconds = readyOrPlayingClips * actualDurationPerClip.inSeconds; | |
// Don't show 0% during initial loading | |
if (!showDuringLoading && bufferPercentage == 0) { | |
return const SizedBox.shrink(); | |
} | |
return Positioned( | |
right: 16, | |
top: 16, | |
child: Container( | |
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), | |
decoration: BoxDecoration( | |
color: Colors.black.withOpacity(0.6), | |
borderRadius: BorderRadius.circular(16), | |
), | |
child: Row( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
Icon( | |
_getBufferIcon(bufferPercentage), | |
color: _getBufferStatusColor(bufferPercentage), | |
size: 16, | |
), | |
const SizedBox(width: 4), | |
Text( | |
isLoading | |
? 'Buffering $bufferPercentage%' | |
: '$bufferPercentage% (${remainingSeconds}s)', | |
style: const TextStyle( | |
color: Colors.white, | |
fontSize: 12, | |
fontWeight: FontWeight.bold, | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
/// Get icon for buffer status based on percentage | |
IconData _getBufferIcon(int percentage) { | |
if (percentage >= 40) return Icons.network_wifi; | |
if (percentage >= 30) return Icons.network_wifi_3_bar; | |
if (percentage >= 20) return Icons.network_wifi_2_bar; | |
return Icons.network_wifi_1_bar; | |
} | |
/// Get color for buffer status based on percentage | |
Color _getBufferStatusColor(int percentage) { | |
if (percentage >= 30) return Colors.green; | |
if (percentage >= 20) return Colors.orange; | |
return Colors.red; | |
} | |
/// Formats queue statistics for display | |
String formatQueueStats(dynamic queueManager) { | |
final stats = queueManager.getBufferStats(); | |
final currentClipInfo = queueManager.currentClip != null | |
? '\nPlaying: ${queueManager.currentClip!.seed}' | |
: ''; | |
final nextClipInfo = stats['nextControllerReady'] == true | |
? '\nNext clip preloaded' | |
: ''; | |
return 'Queue: ${stats['readyClips']}/${stats['bufferSize']} ready\n' | |
'Gen: ${stats['activeGenerations']} active' | |
'$currentClipInfo$nextClipInfo'; | |
} | |
/// Builds a play/pause button overlay | |
Widget buildPlayPauseButton({ | |
required bool isPlaying, | |
required VoidCallback onTap, | |
}) { | |
return Positioned( | |
left: 16, | |
bottom: 16, | |
child: GestureDetector( | |
onTap: onTap, | |
child: Container( | |
padding: const EdgeInsets.all(8), | |
decoration: BoxDecoration( | |
color: Colors.black.withOpacity(0.6), | |
borderRadius: BorderRadius.circular(24), | |
), | |
child: Icon( | |
isPlaying ? Icons.pause : Icons.play_arrow, | |
color: Colors.white, | |
size: 24, | |
), | |
), | |
), | |
); | |
} |