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();
  }
}