File size: 4,483 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
// lib/widgets/video_player/playback_controller.dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:aitube2/config/config.dart';
import 'package:aitube2/services/clip_queue/video_clip.dart';

/// Manages video playback logic for the video player
class PlaybackController {
  /// The current video player controller
  VideoPlayerController? currentController;
  
  /// The next video player controller (preloaded)
  VideoPlayerController? nextController;
  
  /// The current clip being played
  VideoClip? currentClip;
  
  /// Whether the video is playing
  bool isPlaying = false;
  
  /// Whether the video is loading
  bool isLoading = false;
  
  /// Whether this is the initial load
  bool isInitialLoad = true;
  
  /// Current playback position
  Duration currentPlaybackPosition = Duration.zero;
  
  /// Whether initial playback has started
  bool startedInitialPlayback = false;
  
  /// Timer for checking if buffer is ready
  Timer? nextClipCheckTimer;
  
  /// Timer for playback duration
  Timer? playbackTimer;
  
  /// Timer for tracking position
  Timer? positionTrackingTimer;
  
  /// Whether the controller is disposed
  bool isDisposed = false;
  
  /// Callback for when video is completed
  Function()? onVideoCompleted;
  
  /// Callback for when the queue needs updating
  Function()? onQueueUpdate;
  
  /// Toggle playback between play and pause
  void togglePlayback() {
    if (isLoading) return;
    
    final controller = currentController;
    if (controller == null) return;

    isPlaying = !isPlaying;
    
    if (isPlaying) {
      // Restore previous position before playing
      controller.seekTo(currentPlaybackPosition);
      controller.play();
      startPlaybackTimer();
    } else {
      controller.pause();
      playbackTimer?.cancel();
      positionTrackingTimer?.cancel();
    }
  }
  
  /// Start the playback timer to manage clip duration
  void startPlaybackTimer() {
    playbackTimer?.cancel();
    nextClipCheckTimer?.cancel();

    playbackTimer = Timer(Configuration.instance.actualClipPlaybackDuration, () {
      if (isDisposed || !isPlaying) return;
      
      onVideoCompleted?.call();
    });

    startPositionTracking();
  }
  
  /// Start tracking the video position
  void startPositionTracking() {
    positionTrackingTimer?.cancel();
    positionTrackingTimer = Timer.periodic(const Duration(milliseconds: 50), (_) {
      if (isDisposed || !isPlaying) return;
      
      final controller = currentController;
      if (controller != null && controller.value.isInitialized) {
        currentPlaybackPosition = controller.value.position;
      }
    });
  }

  /// Start checking for the next clip
  void startNextClipCheck() {
    nextClipCheckTimer?.cancel();
    nextClipCheckTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
      if (isDisposed || !isPlaying) {
        timer.cancel();
        return;
      }

      onQueueUpdate?.call();
    });
  }
  
  /// Log the current playback status (debug only)
  void logPlaybackStatus() {
    if (kDebugMode) {
      final controller = currentController;
      if (controller != null && controller.value.isInitialized) {
        final position = controller.value.position;
        final duration = controller.value.duration;
        debugPrint('Playback status: ${position.inSeconds}s / ${duration.inSeconds}s'
            ' (${isPlaying ? "playing" : "paused"})');
        debugPrint('Current clip: ${currentClip?.seed}, Next controller ready: ${nextController != null}');
      }
    }
  }
  
  /// Initialize a controller for a video clip
  Future<VideoPlayerController> initializeController(String videoUrl) async {
    final controller = VideoPlayerController.networkUrl(
      Uri.parse(videoUrl),
    );
    
    await controller.initialize();
    
    // Configure the controller
    controller.setLooping(true);
    controller.setVolume(0.0);
    controller.setPlaybackSpeed(Configuration.instance.clipPlaybackSpeed);
    
    return controller;
  }
  
  /// Prepare the controller to be disposed
  Future<void> dispose() async {
    isDisposed = true;
    
    playbackTimer?.cancel();
    nextClipCheckTimer?.cancel();
    positionTrackingTimer?.cancel();
    
    await currentController?.dispose();
    await nextController?.dispose();
    
    currentController = null;
    nextController = null;
  }
}