jbilcke-hf HF Staff commited on
Commit
3cc7c13
·
1 Parent(s): fa88ecf

paving the way for orientation control

Browse files
api_core.py CHANGED
@@ -677,6 +677,21 @@ Your caption:"""
677
  num_inference_steps = self.get_config_value(user_role, 'num_inference_steps', options)
678
  frame_rate = self.get_config_value(user_role, 'clip_framerate', options)
679
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
680
  # Log the user role and config values being used
681
  logger.info(f"Using config values: width={width}, height={height}, num_frames={num_frames}, steps={num_inference_steps}, fps={frame_rate} | role: {user_role}")
682
 
@@ -725,7 +740,7 @@ Your caption:"""
725
  "double_num_frames": False, # <- False for real-time generation
726
  "fps": frame_rate,
727
  "super_resolution": False, # <- False for real-time generation
728
- "grain_amount": 0, # No film grain
729
  }
730
  }
731
 
@@ -772,7 +787,7 @@ Your caption:"""
772
  raise Exception(f"Video generation failed: HTTP {response.status} - {error_text}")
773
 
774
  result = await response.json()
775
- logger.info(f"[{request_id}] Successfully parsed JSON response")
776
 
777
  if "error" in result:
778
  error_msg = result['error']
@@ -793,7 +808,7 @@ Your caption:"""
793
 
794
  # Get data size
795
  data_size = len(video_data_uri)
796
- logger.info(f"[{request_id}] Received video data: {data_size} chars")
797
 
798
  # Reset error count on successful call
799
  endpoint.error_count = 0
 
677
  num_inference_steps = self.get_config_value(user_role, 'num_inference_steps', options)
678
  frame_rate = self.get_config_value(user_role, 'clip_framerate', options)
679
 
680
+ # Get orientation from options
681
+ orientation = options.get('orientation', 'LANDSCAPE')
682
+
683
+ # Adjust width and height based on orientation if needed
684
+ if orientation == 'PORTRAIT' and width > height:
685
+ # Swap width and height for portrait orientation
686
+ width, height = height, width
687
+ logger.info(f"Orientation: {orientation}, swapped dimensions to width={width}, height={height}")
688
+ elif orientation == 'LANDSCAPE' and height > width:
689
+ # Swap height and width for landscape orientation
690
+ height, width = width, height
691
+ logger.info(f"Orientation: {orientation}, swapped dimensions to width={width}, height={height}")
692
+ else:
693
+ logger.info(f"Orientation: {orientation}, using original dimensions width={width}, height={height}")
694
+
695
  # Log the user role and config values being used
696
  logger.info(f"Using config values: width={width}, height={height}, num_frames={num_frames}, steps={num_inference_steps}, fps={frame_rate} | role: {user_role}")
697
 
 
740
  "double_num_frames": False, # <- False for real-time generation
741
  "fps": frame_rate,
742
  "super_resolution": False, # <- False for real-time generation
743
+ "grain_amount": 0, # No film grain (on low-res, low-quality generation the effects aren't worth it + it adds weight to the MP4 payload)
744
  }
745
  }
746
 
 
787
  raise Exception(f"Video generation failed: HTTP {response.status} - {error_text}")
788
 
789
  result = await response.json()
790
+ #logger.info(f"[{request_id}] Successfully parsed JSON response")
791
 
792
  if "error" in result:
793
  error_msg = result['error']
 
808
 
809
  # Get data size
810
  data_size = len(video_data_uri)
811
+ #logger.info(f"[{request_id}] Received video data: {data_size} chars")
812
 
813
  # Reset error count on successful call
814
  endpoint.error_count = 0
build/web/flutter_bootstrap.js CHANGED
@@ -39,6 +39,6 @@ _flutter.buildConfig = {"engineRevision":"382be0028d370607f76215a9be322e5514b263
39
 
40
  _flutter.loader.load({
41
  serviceWorkerSettings: {
42
- serviceWorkerVersion: "658126929"
43
  }
44
  });
 
39
 
40
  _flutter.loader.load({
41
  serviceWorkerSettings: {
42
+ serviceWorkerVersion: "683227308"
43
  }
44
  });
build/web/flutter_service_worker.js CHANGED
@@ -3,11 +3,11 @@ const MANIFEST = 'flutter-app-manifest';
3
  const TEMP = 'flutter-temp-cache';
4
  const CACHE_NAME = 'flutter-app-cache';
5
 
6
- const RESOURCES = {"flutter_bootstrap.js": "5b9f56e43ffaacf52a435800b8ff1adf",
7
  "version.json": "b5eaae4fc120710a3c35125322173615",
8
  "index.html": "cd2094e3989e3eb0424e47d7d188c298",
9
  "/": "cd2094e3989e3eb0424e47d7d188c298",
10
- "main.dart.js": "4e26b37ccf9ca902db38fe5292263609",
11
  "flutter.js": "83d881c1dbb6d6bcd6b42e274605b69c",
12
  "aitube.svg": "26140ba0d153b213b122bc6ebcc17f6c",
13
  "favicon.png": "c8a183c516004e648a7bac7497c89b97",
 
3
  const TEMP = 'flutter-temp-cache';
4
  const CACHE_NAME = 'flutter-app-cache';
5
 
6
+ const RESOURCES = {"flutter_bootstrap.js": "05bd8824501c20bb663a0f9ab56f5c49",
7
  "version.json": "b5eaae4fc120710a3c35125322173615",
8
  "index.html": "cd2094e3989e3eb0424e47d7d188c298",
9
  "/": "cd2094e3989e3eb0424e47d7d188c298",
10
+ "main.dart.js": "62f7660e444a6aa729a12c4639b95395",
11
  "flutter.js": "83d881c1dbb6d6bcd6b42e274605b69c",
12
  "aitube.svg": "26140ba0d153b213b122bc6ebcc17f6c",
13
  "favicon.png": "c8a183c516004e648a7bac7497c89b97",
build/web/index.html CHANGED
@@ -156,7 +156,7 @@
156
  </script>
157
 
158
  <!-- Add version parameter for cache busting -->
159
- <script src="flutter_bootstrap.js?v=1746632747" async></script>
160
 
161
  <!-- Add cache busting script -->
162
  <script>
 
156
  </script>
157
 
158
  <!-- Add version parameter for cache busting -->
159
+ <script src="flutter_bootstrap.js?v=1746635426" async></script>
160
 
161
  <!-- Add cache busting script -->
162
  <script>
build/web/main.dart.js CHANGED
The diff for this file is too large to render. See raw diff
 
lib/models/video_orientation.dart CHANGED
@@ -3,10 +3,10 @@
3
  /// Enum representing the orientation of a video clip.
4
  enum VideoOrientation {
5
  /// Landscape orientation (horizontal, typically 16:9)
6
- landscape,
7
 
8
  /// Portrait orientation (vertical, typically 9:16)
9
- portrait
10
  }
11
 
12
  /// Extension methods for VideoOrientation enum
@@ -14,24 +14,34 @@ extension VideoOrientationExtension on VideoOrientation {
14
  /// Get the string representation of the orientation
15
  String get name {
16
  switch (this) {
17
- case VideoOrientation.landscape:
18
- return 'landscape';
19
- case VideoOrientation.portrait:
20
- return 'portrait';
21
  }
22
  }
23
 
 
 
 
 
 
24
  /// Get the orientation from a string
25
  static VideoOrientation fromString(String? str) {
26
- if (str?.toLowerCase() == 'portrait') {
27
- return VideoOrientation.portrait;
28
  }
29
- return VideoOrientation.landscape; // Default to landscape
30
  }
31
 
32
  /// Whether this orientation is portrait
33
- bool get isPortrait => this == VideoOrientation.portrait;
34
 
35
  /// Whether this orientation is landscape
36
- bool get isLandscape => this == VideoOrientation.landscape;
 
 
 
 
 
37
  }
 
3
  /// Enum representing the orientation of a video clip.
4
  enum VideoOrientation {
5
  /// Landscape orientation (horizontal, typically 16:9)
6
+ LANDSCAPE,
7
 
8
  /// Portrait orientation (vertical, typically 9:16)
9
+ PORTRAIT
10
  }
11
 
12
  /// Extension methods for VideoOrientation enum
 
14
  /// Get the string representation of the orientation
15
  String get name {
16
  switch (this) {
17
+ case VideoOrientation.LANDSCAPE:
18
+ return 'LANDSCAPE';
19
+ case VideoOrientation.PORTRAIT:
20
+ return 'PORTRAIT';
21
  }
22
  }
23
 
24
+ /// Value for API communication
25
+ String get value {
26
+ return name;
27
+ }
28
+
29
  /// Get the orientation from a string
30
  static VideoOrientation fromString(String? str) {
31
+ if (str?.toUpperCase() == 'PORTRAIT') {
32
+ return VideoOrientation.PORTRAIT;
33
  }
34
+ return VideoOrientation.LANDSCAPE; // Default to landscape
35
  }
36
 
37
  /// Whether this orientation is portrait
38
+ bool get isPortrait => this == VideoOrientation.PORTRAIT;
39
 
40
  /// Whether this orientation is landscape
41
+ bool get isLandscape => this == VideoOrientation.LANDSCAPE;
42
+ }
43
+
44
+ /// Helper function to determine orientation from width and height
45
+ VideoOrientation getOrientationFromDimensions(int width, int height) {
46
+ return width >= height ? VideoOrientation.LANDSCAPE : VideoOrientation.PORTRAIT;
47
  }
lib/services/clip_queue/clip_generation_handler.dart CHANGED
@@ -99,10 +99,11 @@ class ClipGenerationHandler {
99
  return;
100
  }
101
 
102
- // Generate new video with timeout
103
  String videoData = await _websocketService.generateVideo(
104
  video,
105
  seed: clip.seed,
 
106
  ).timeout(ClipQueueConstants.generationTimeout);
107
 
108
  if (!_isDisposed) {
 
99
  return;
100
  }
101
 
102
+ // Generate new video with timeout, passing the orientation
103
  String videoData = await _websocketService.generateVideo(
104
  video,
105
  seed: clip.seed,
106
+ orientation: clip.orientation,
107
  ).timeout(ClipQueueConstants.generationTimeout);
108
 
109
  if (!_isDisposed) {
lib/services/clip_queue/clip_queue_manager.dart CHANGED
@@ -5,6 +5,7 @@ import 'package:aitube2/config/config.dart';
5
  import 'package:flutter/foundation.dart';
6
  import 'package:collection/collection.dart';
7
  import '../../models/video_result.dart';
 
8
  import '../websocket_api_service.dart';
9
  import '../../utils/seed.dart';
10
  import 'clip_states.dart';
@@ -87,8 +88,14 @@ class ClipQueueManager {
87
  /// Unmodifiable view of the clip history
88
  List<VideoClip> get clipHistory => List.unmodifiable(_clipHistory);
89
 
 
 
 
 
 
 
90
  /// Initialize the clip queue
91
- Future<void> initialize() async {
92
  if (_isDisposed) return;
93
 
94
  _logger.logStateChange(
@@ -100,6 +107,12 @@ class ClipQueueManager {
100
  );
101
  _clipBuffer.clear();
102
 
 
 
 
 
 
 
103
  try {
104
  final bufferSize = Configuration.instance.renderQueueBufferSize;
105
  while (_clipBuffer.length < bufferSize) {
@@ -108,9 +121,10 @@ class ClipQueueManager {
108
  final newClip = VideoClip(
109
  prompt: "${video.title}\n${video.description}",
110
  seed: video.useFixedSeed && video.seed > 0 ? video.seed : generateSeed(),
 
111
  );
112
  _clipBuffer.add(newClip);
113
- ClipQueueConstants.logEvent('Added initial clip ${newClip.seed} to buffer');
114
  }
115
 
116
  if (_isDisposed) return;
@@ -182,9 +196,10 @@ class ClipQueueManager {
182
  final newClip = VideoClip(
183
  prompt: "${video.title}\n${video.description}",
184
  seed: video.useFixedSeed && video.seed > 0 ? video.seed : generateSeed(),
 
185
  );
186
  _clipBuffer.add(newClip);
187
- ClipQueueConstants.logEvent('Added new clip ${newClip.seed} to maintain buffer size');
188
  }
189
 
190
  // Process played clips first
@@ -301,13 +316,14 @@ class ClipQueueManager {
301
  _clipBuffer.remove(clip);
302
  _clipHistory.add(clip);
303
 
304
- // Add a new pending clip
305
  final newClip = VideoClip(
306
  prompt: "${video.title}\n${video.description}",
307
  seed: video.useFixedSeed && video.seed > 0 ? video.seed : generateSeed(),
 
308
  );
309
  _clipBuffer.add(newClip);
310
- ClipQueueConstants.logEvent('Replaced played clip ${clip.seed} with new clip ${newClip.seed}');
311
  }
312
 
313
  // Immediately trigger buffer fill to start generating new clips
@@ -367,6 +383,32 @@ class ClipQueueManager {
367
  ClipQueueConstants.logEvent('Manual buffer fill requested');
368
  _fillBuffer();
369
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
 
371
  /// Print the current state of the queue
372
  void printQueueState() {
 
5
  import 'package:flutter/foundation.dart';
6
  import 'package:collection/collection.dart';
7
  import '../../models/video_result.dart';
8
+ import '../../models/video_orientation.dart';
9
  import '../websocket_api_service.dart';
10
  import '../../utils/seed.dart';
11
  import 'clip_states.dart';
 
88
  /// Unmodifiable view of the clip history
89
  List<VideoClip> get clipHistory => List.unmodifiable(_clipHistory);
90
 
91
+ /// Current orientation of clips being generated
92
+ VideoOrientation _currentOrientation = VideoOrientation.LANDSCAPE;
93
+
94
+ /// Get the current orientation
95
+ VideoOrientation get currentOrientation => _currentOrientation;
96
+
97
  /// Initialize the clip queue
98
+ Future<void> initialize({VideoOrientation? orientation}) async {
99
  if (_isDisposed) return;
100
 
101
  _logger.logStateChange(
 
107
  );
108
  _clipBuffer.clear();
109
 
110
+ // Set initial orientation
111
+ _currentOrientation = orientation ?? getOrientationFromDimensions(
112
+ Configuration.instance.originalClipWidth,
113
+ Configuration.instance.originalClipHeight
114
+ );
115
+
116
  try {
117
  final bufferSize = Configuration.instance.renderQueueBufferSize;
118
  while (_clipBuffer.length < bufferSize) {
 
121
  final newClip = VideoClip(
122
  prompt: "${video.title}\n${video.description}",
123
  seed: video.useFixedSeed && video.seed > 0 ? video.seed : generateSeed(),
124
+ orientation: _currentOrientation,
125
  );
126
  _clipBuffer.add(newClip);
127
+ ClipQueueConstants.logEvent('Added initial clip ${newClip.seed} to buffer with orientation: ${_currentOrientation.name}');
128
  }
129
 
130
  if (_isDisposed) return;
 
196
  final newClip = VideoClip(
197
  prompt: "${video.title}\n${video.description}",
198
  seed: video.useFixedSeed && video.seed > 0 ? video.seed : generateSeed(),
199
+ orientation: _currentOrientation,
200
  );
201
  _clipBuffer.add(newClip);
202
+ ClipQueueConstants.logEvent('Added new clip ${newClip.seed} with orientation ${_currentOrientation.name} to maintain buffer size');
203
  }
204
 
205
  // Process played clips first
 
316
  _clipBuffer.remove(clip);
317
  _clipHistory.add(clip);
318
 
319
+ // Add a new pending clip with current orientation
320
  final newClip = VideoClip(
321
  prompt: "${video.title}\n${video.description}",
322
  seed: video.useFixedSeed && video.seed > 0 ? video.seed : generateSeed(),
323
+ orientation: _currentOrientation,
324
  );
325
  _clipBuffer.add(newClip);
326
+ ClipQueueConstants.logEvent('Replaced played clip ${clip.seed} with new clip ${newClip.seed} using orientation ${_currentOrientation.name}');
327
  }
328
 
329
  // Immediately trigger buffer fill to start generating new clips
 
383
  ClipQueueConstants.logEvent('Manual buffer fill requested');
384
  _fillBuffer();
385
  }
386
+
387
+ /// Handle orientation change
388
+ Future<void> updateOrientation(VideoOrientation newOrientation) async {
389
+ if (_currentOrientation == newOrientation) {
390
+ ClipQueueConstants.logEvent('Orientation unchanged: ${newOrientation.name}');
391
+ return;
392
+ }
393
+
394
+ ClipQueueConstants.logEvent('Orientation changed from ${_currentOrientation.name} to ${newOrientation.name}');
395
+ _currentOrientation = newOrientation;
396
+
397
+ // Cancel any active generations
398
+ for (var clipSeed in _activeGenerations.toList()) {
399
+ _activeGenerations.remove(clipSeed);
400
+ }
401
+
402
+ // Clear buffer and history
403
+ _clipBuffer.clear();
404
+ _clipHistory.clear();
405
+
406
+ // Re-initialize the queue with the new orientation
407
+ await initialize(orientation: newOrientation);
408
+
409
+ // Notify listeners
410
+ onQueueUpdated?.call();
411
+ }
412
 
413
  /// Print the current state of the queue
414
  void printQueueState() {
lib/services/clip_queue/video_clip.dart CHANGED
@@ -2,7 +2,9 @@
2
 
3
  import 'dart:async';
4
  import 'package:uuid/uuid.dart';
 
5
  import 'clip_states.dart';
 
6
 
7
  /// Represents a video clip in the queue
8
  class VideoClip {
@@ -18,6 +20,9 @@ class VideoClip {
18
  /// Current state of the clip
19
  ClipState state;
20
 
 
 
 
21
  /// Base64 encoded video data
22
  String? base64Data;
23
 
@@ -47,6 +52,7 @@ class VideoClip {
47
  String? id,
48
  required this.prompt,
49
  required this.seed,
 
50
  this.state = ClipState.generationPending,
51
  this.base64Data,
52
  }): id = id ?? const Uuid().v4();
 
2
 
3
  import 'dart:async';
4
  import 'package:uuid/uuid.dart';
5
+ import 'package:flutter/foundation.dart';
6
  import 'clip_states.dart';
7
+ import '../../models/video_orientation.dart';
8
 
9
  /// Represents a video clip in the queue
10
  class VideoClip {
 
20
  /// Current state of the clip
21
  ClipState state;
22
 
23
+ /// Device orientation for the clip
24
+ final VideoOrientation orientation;
25
+
26
  /// Base64 encoded video data
27
  String? base64Data;
28
 
 
52
  String? id,
53
  required this.prompt,
54
  required this.seed,
55
+ this.orientation = VideoOrientation.LANDSCAPE,
56
  this.state = ClipState.generationPending,
57
  this.base64Data,
58
  }): id = id ?? const Uuid().v4();
lib/services/websocket_api_service.dart CHANGED
@@ -15,6 +15,7 @@ import 'package:web_socket_channel/web_socket_channel.dart';
15
  import 'package:http/http.dart' as http;
16
  import '../models/search_state.dart';
17
  import '../models/video_result.dart';
 
18
 
19
  class WebSocketRequest {
20
  final String requestId;
@@ -1136,6 +1137,7 @@ class WebSocketApiService {
1136
  int width = 512,
1137
  int seed = 0,
1138
  Duration timeout = const Duration(seconds: 12), // we keep things super tight, as normally a video only takes 2~3s to generate
 
1139
  }) async {
1140
  final settings = SettingsService();
1141
 
@@ -1155,6 +1157,7 @@ class WebSocketApiService {
1155
  'height': Configuration.instance.originalClipHeight,
1156
  'width': Configuration.instance.originalClipWidth,
1157
  'num_frames': Configuration.instance.originalClipNumberOfFrames,
 
1158
  'seed': seed,
1159
  },
1160
  },
 
15
  import 'package:http/http.dart' as http;
16
  import '../models/search_state.dart';
17
  import '../models/video_result.dart';
18
+ import '../models/video_orientation.dart';
19
 
20
  class WebSocketRequest {
21
  final String requestId;
 
1137
  int width = 512,
1138
  int seed = 0,
1139
  Duration timeout = const Duration(seconds: 12), // we keep things super tight, as normally a video only takes 2~3s to generate
1140
+ VideoOrientation orientation = VideoOrientation.LANDSCAPE,
1141
  }) async {
1142
  final settings = SettingsService();
1143
 
 
1157
  'height': Configuration.instance.originalClipHeight,
1158
  'width': Configuration.instance.originalClipWidth,
1159
  'num_frames': Configuration.instance.originalClipNumberOfFrames,
1160
+ 'orientation': orientation.value, // Add orientation parameter
1161
  'seed': seed,
1162
  },
1163
  },
lib/widgets/video_player/buffer_manager.dart CHANGED
@@ -7,6 +7,7 @@ import 'package:aitube2/services/clip_queue/video_clip.dart';
7
  import 'package:aitube2/services/clip_queue/clip_queue_manager.dart';
8
  import 'package:video_player/video_player.dart';
9
  import 'package:aitube2/models/video_result.dart';
 
10
 
11
  /// Manages buffering and clip preloading for video player
12
  class BufferManager {
@@ -25,10 +26,16 @@ class BufferManager {
25
  /// Timer for debug printing
26
  Timer? debugTimer;
27
 
 
 
 
 
 
 
28
  /// Constructor
29
  BufferManager({
30
- required VideoResult video,
31
- required Function() onQueueUpdated,
32
  ClipQueueManager? existingQueueManager,
33
  }) : queueManager = existingQueueManager ?? ClipQueueManager(
34
  video: video,
@@ -149,6 +156,20 @@ class BufferManager {
149
  return null;
150
  }
151
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  /// Dispose resources
153
  void dispose() {
154
  isDisposed = true;
 
7
  import 'package:aitube2/services/clip_queue/clip_queue_manager.dart';
8
  import 'package:video_player/video_player.dart';
9
  import 'package:aitube2/models/video_result.dart';
10
+ import 'package:aitube2/models/video_orientation.dart';
11
 
12
  /// Manages buffering and clip preloading for video player
13
  class BufferManager {
 
26
  /// Timer for debug printing
27
  Timer? debugTimer;
28
 
29
+ /// The video result
30
+ final VideoResult video;
31
+
32
+ /// Callback when queue is updated
33
+ final Function() onQueueUpdated;
34
+
35
  /// Constructor
36
  BufferManager({
37
+ required this.video,
38
+ required this.onQueueUpdated,
39
  ClipQueueManager? existingQueueManager,
40
  }) : queueManager = existingQueueManager ?? ClipQueueManager(
41
  video: video,
 
156
  return null;
157
  }
158
 
159
+ /// Update the orientation when device rotates
160
+ Future<void> updateOrientation(VideoOrientation newOrientation) async {
161
+ if (isDisposed) return;
162
+ if (queueManager.currentOrientation == newOrientation) return;
163
+
164
+ debugPrint('Updating video orientation to ${newOrientation.name}');
165
+
166
+ // Start loading progress again as we'll be regenerating clips
167
+ startLoadingProgress();
168
+
169
+ // Update the orientation in the queue manager
170
+ await queueManager.updateOrientation(newOrientation);
171
+ }
172
+
173
  /// Dispose resources
174
  void dispose() {
175
  isDisposed = true;
lib/widgets/video_player/video_player_widget.dart CHANGED
@@ -1,11 +1,15 @@
1
  // lib/widgets/video_player/video_player_widget.dart
2
 
3
  import 'dart:async';
 
 
4
  import 'package:aitube2/config/config.dart';
5
  import 'package:flutter/material.dart';
6
  import 'package:flutter/foundation.dart' show kDebugMode, kIsWeb;
 
7
  import 'package:video_player/video_player.dart';
8
  import 'package:aitube2/models/video_result.dart';
 
9
  import 'package:aitube2/theme/colors.dart';
10
 
11
  // Import components
@@ -16,6 +20,13 @@ import 'ui_components.dart' as ui;
16
  // Conditionally import dart:html for web platform
17
  import '../web_utils.dart' if (dart.library.html) 'dart:html' as html;
18
 
 
 
 
 
 
 
 
19
  /// A widget that plays video clips with buffering and automatic playback
20
  class VideoPlayerWidget extends StatefulWidget {
21
  /// The video to play
@@ -59,6 +70,9 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget> with WidgetsBindi
59
 
60
  /// Whether playback was happening before going to background
61
  bool _wasPlayingBeforeBackground = false;
 
 
 
62
 
63
  @override
64
  void initState() {
@@ -120,6 +134,12 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget> with WidgetsBindi
120
  Future<void> _initializePlayer() async {
121
  if (_isDisposed) return;
122
 
 
 
 
 
 
 
123
  _playbackController = PlaybackController();
124
  _playbackController.isLoading = true;
125
  _playbackController.isInitialLoad = true;
@@ -138,7 +158,7 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget> with WidgetsBindi
138
  },
139
  );
140
 
141
- // Initialize buffer manager
142
  await _bufferManager.initialize();
143
  }
144
 
@@ -354,6 +374,46 @@ class _VideoPlayerWidgetState extends State<VideoPlayerWidget> with WidgetsBindi
354
  Widget build(BuildContext context) {
355
  return LayoutBuilder(
356
  builder: (context, constraints) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  final controller = _playbackController.currentController;
358
  final aspectRatio = controller?.value.aspectRatio ?? 16/9;
359
  final playerHeight = constraints.maxWidth / aspectRatio;
 
1
  // lib/widgets/video_player/video_player_widget.dart
2
 
3
  import 'dart:async';
4
+ import 'dart:math' as math;
5
+ import 'dart:io' show Platform;
6
  import 'package:aitube2/config/config.dart';
7
  import 'package:flutter/material.dart';
8
  import 'package:flutter/foundation.dart' show kDebugMode, kIsWeb;
9
+ import 'package:flutter/foundation.dart';
10
  import 'package:video_player/video_player.dart';
11
  import 'package:aitube2/models/video_result.dart';
12
+ import 'package:aitube2/models/video_orientation.dart';
13
  import 'package:aitube2/theme/colors.dart';
14
 
15
  // Import components
 
20
  // Conditionally import dart:html for web platform
21
  import '../web_utils.dart' if (dart.library.html) 'dart:html' as html;
22
 
23
+ // Extension to check if target platform is mobile
24
+ extension TargetPlatformExtension on TargetPlatform {
25
+ bool get isMobile =>
26
+ this == TargetPlatform.iOS ||
27
+ this == TargetPlatform.android;
28
+ }
29
+
30
  /// A widget that plays video clips with buffering and automatic playback
31
  class VideoPlayerWidget extends StatefulWidget {
32
  /// The video to play
 
70
 
71
  /// Whether playback was happening before going to background
72
  bool _wasPlayingBeforeBackground = false;
73
+
74
+ /// Current orientation
75
+ VideoOrientation _currentOrientation = VideoOrientation.LANDSCAPE;
76
 
77
  @override
78
  void initState() {
 
134
  Future<void> _initializePlayer() async {
135
  if (_isDisposed) return;
136
 
137
+ // Get initial orientation
138
+ final mediaQuery = MediaQuery.of(context);
139
+ _currentOrientation = mediaQuery.orientation == Orientation.landscape
140
+ ? VideoOrientation.LANDSCAPE
141
+ : VideoOrientation.PORTRAIT;
142
+
143
  _playbackController = PlaybackController();
144
  _playbackController.isLoading = true;
145
  _playbackController.isInitialLoad = true;
 
158
  },
159
  );
160
 
161
+ // Initialize buffer manager with current orientation
162
  await _bufferManager.initialize();
163
  }
164
 
 
374
  Widget build(BuildContext context) {
375
  return LayoutBuilder(
376
  builder: (context, constraints) {
377
+ // Determine orientation based on form factor rather than device orientation
378
+ // This ensures proper behavior on desktop platforms
379
+ VideoOrientation newOrientation;
380
+
381
+ if (kIsWeb || !defaultTargetPlatform.isMobile) {
382
+ // For web and desktop platforms, use form factor (width vs height) to determine orientation
383
+ newOrientation = constraints.maxWidth > constraints.maxHeight
384
+ ? VideoOrientation.LANDSCAPE
385
+ : VideoOrientation.PORTRAIT;
386
+
387
+ // Add a small buffer so we don't change orientation too frequently on borderline cases
388
+ if (newOrientation == VideoOrientation.LANDSCAPE &&
389
+ constraints.maxWidth / constraints.maxHeight < 1.05) {
390
+ newOrientation = _currentOrientation;
391
+ } else if (newOrientation == VideoOrientation.PORTRAIT &&
392
+ constraints.maxHeight / constraints.maxWidth < 1.05) {
393
+ newOrientation = _currentOrientation;
394
+ }
395
+ } else {
396
+ // For mobile platforms, use the device orientation
397
+ final orientation = MediaQuery.of(context).orientation;
398
+ newOrientation = orientation == Orientation.landscape
399
+ ? VideoOrientation.LANDSCAPE
400
+ : VideoOrientation.PORTRAIT;
401
+ }
402
+
403
+ // Check if orientation changed
404
+ if (newOrientation != _currentOrientation) {
405
+ debugPrint('Orientation changed to ${newOrientation.name} (form factor: ${constraints.maxWidth}x${constraints.maxHeight})');
406
+ _currentOrientation = newOrientation;
407
+
408
+ // Update buffer manager orientation (without awaiting to avoid blocking UI)
409
+ Future.microtask(() async {
410
+ if (!_isDisposed && mounted) {
411
+ await _bufferManager.updateOrientation(newOrientation);
412
+ }
413
+ });
414
+ }
415
+
416
+ // Video player layout
417
  final controller = _playbackController.currentController;
418
  final aspectRatio = controller?.value.aspectRatio ?? 16/9;
419
  final playerHeight = constraints.maxWidth / aspectRatio;