Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
File size: 5,388 Bytes
2e813e6 3cc7c13 2e813e6 3cc7c13 2e813e6 3cc7c13 2e813e6 3cc7c13 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 |
// lib/widgets/video_player/buffer_manager.dart
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:aitube2/config/config.dart';
import 'package:aitube2/services/clip_queue/video_clip.dart';
import 'package:aitube2/services/clip_queue/clip_queue_manager.dart';
import 'package:video_player/video_player.dart';
import 'package:aitube2/models/video_result.dart';
import 'package:aitube2/models/video_orientation.dart';
/// Manages buffering and clip preloading for video player
class BufferManager {
/// The queue manager for clips
final ClipQueueManager queueManager;
/// Whether the manager is disposed
bool isDisposed = false;
/// Loading progress (0.0 to 1.0)
double loadingProgress = 0.0;
/// Timer for showing loading progress
Timer? progressTimer;
/// Timer for debug printing
Timer? debugTimer;
/// The video result
final VideoResult video;
/// Callback when queue is updated
final Function() onQueueUpdated;
/// Constructor
BufferManager({
required this.video,
required this.onQueueUpdated,
ClipQueueManager? existingQueueManager,
}) : queueManager = existingQueueManager ?? ClipQueueManager(
video: video,
onQueueUpdated: onQueueUpdated,
);
/// Initialize the buffer with clips
Future<void> initialize() async {
if (isDisposed) return;
// Start loading progress animation
startLoadingProgress();
// Initialize queue manager but don't await it
await queueManager.initialize();
}
/// Start loading progress animation
void startLoadingProgress() {
progressTimer?.cancel();
loadingProgress = 0.0;
const totalDuration = Duration(seconds: 12);
const updateInterval = Duration(milliseconds: 50);
final steps = totalDuration.inMilliseconds / updateInterval.inMilliseconds;
final increment = 1.0 / steps;
progressTimer = Timer.periodic(updateInterval, (timer) {
if (isDisposed) {
timer.cancel();
return;
}
loadingProgress += increment;
if (loadingProgress >= 1.0) {
progressTimer?.cancel();
}
});
}
/// Start debug printing (for development)
void startDebugPrinting() {
debugTimer = Timer.periodic(const Duration(seconds: 5), (_) {
if (!isDisposed) {
queueManager.printQueueState();
}
});
}
/// Check if buffer is ready to start playback
bool isBufferReadyToStartPlayback() {
final readyClips = queueManager.clipBuffer.where((c) => c.isReady).length;
final totalClips = queueManager.clipBuffer.length;
final bufferPercentage = (readyClips / totalClips * 100);
return bufferPercentage >= Configuration.instance.minimumBufferPercentToStartPlayback;
}
/// Ensure the buffer remains full
void ensureBufferFull() {
if (isDisposed) return;
// Add additional safety check to avoid errors if the widget has been disposed
// but this method is still being called by an ongoing operation
try {
queueManager.fillBuffer();
} catch (e) {
debugPrint('Error filling buffer: $e');
}
}
/// Preload the next clip to ensure smooth playback
Future<VideoPlayerController?> preloadNextClip() async {
if (isDisposed) return null;
VideoPlayerController? nextController;
try {
// Always try to preload the next ready clip
final nextReadyClip = queueManager.nextReadyClip;
if (nextReadyClip?.base64Data != null &&
nextReadyClip != queueManager.currentClip &&
!nextReadyClip!.isPlaying) {
nextController = VideoPlayerController.networkUrl(
Uri.parse(nextReadyClip.base64Data!),
);
await nextController.initialize();
if (isDisposed) {
nextController.dispose();
return null;
}
// we always keep things looping. We never want any video to stop.
nextController.setLooping(true);
nextController.setVolume(0.0);
nextController.setPlaybackSpeed(Configuration.instance.clipPlaybackSpeed);
// Always ensure we're generating new clips after preloading
// This is wrapped in a try-catch within ensureBufferFull now
ensureBufferFull();
return nextController;
}
} catch (e) {
// Make sure we dispose any created controller if there was an error
nextController?.dispose();
debugPrint('Error preloading next clip: $e');
}
// Always ensure we're generating new clips after preloading
// This is wrapped in a try-catch within ensureBufferFull now
if (!isDisposed) {
ensureBufferFull();
}
return null;
}
/// Update the orientation when device rotates
Future<void> updateOrientation(VideoOrientation newOrientation) async {
if (isDisposed) return;
if (queueManager.currentOrientation == newOrientation) return;
debugPrint('Updating video orientation to ${newOrientation.name}');
// Start loading progress again as we'll be regenerating clips
startLoadingProgress();
// Update the orientation in the queue manager
await queueManager.updateOrientation(newOrientation);
}
/// Dispose resources
void dispose() {
isDisposed = true;
progressTimer?.cancel();
debugTimer?.cancel();
}
} |