zhabby_shorts 0.0.1 copy "zhabby_shorts: ^0.0.1" to clipboard
zhabby_shorts: ^0.0.1 copied to clipboard

Shorts for Flutter.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:zhabby_shorts/zhabby_shorts.dart';
import 'dart:async';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Zhabby Shorts Demo',
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: Colors.black,
        appBarTheme: const AppBarTheme(
          backgroundColor: Colors.black,
          foregroundColor: Colors.white,
        ),
      ),
      home: const VideoFeedScreen(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class VideoFeedScreen extends StatefulWidget {
  const VideoFeedScreen({super.key});

  @override
  State<VideoFeedScreen> createState() => _VideoFeedScreenState();
}

class _VideoFeedScreenState extends State<VideoFeedScreen>
    with TickerProviderStateMixin {
  late final ShortsPlayerController _player;
  late PageController _pageController;

  // Test video URLs from Cloudflare Stream
  final List<String> _baseVideoUrls = [
    'https://p2.proxy.zhabby.com/d26e36b13e2ab198ca02e7ab60f61257/manifest/video.m3u8',

    'https://p2.proxy.zhabby.com/6c625457da995cf6c21b2e6fabe13d46/manifest/video.m3u8',

    'https://p2.proxy.zhabby.com/762062a33279396c6e6b70a0ff58e210/manifest/video.m3u8',

    'https://p2.proxy.zhabby.com/d26e36b13e2ab198ca02e7ab60f61257/manifest/video.m3u8',
    'https://p2.proxy.zhabby.com/762062a33279396c6e6b70a0ff58e210/manifest/video.m3u8',
    'https://p2.proxy.zhabby.com/2c261e5a18cd5d6688f1843a04c388aa/manifest/video.m3u8',
    'https://p2.proxy.zhabby.com/6c625457da995cf6c21b2e6fabe13d46/manifest/video.m3u8',
    'https://p2.proxy.zhabby.com/f26c4926b597081bd1da62d6fcb233fe/manifest/video.m3u8',
    'https://p2.proxy.zhabby.com/44b149efa348797aba46d1b50f5a3a11/manifest/video.m3u8',
    'https://p2.proxy.zhabby.com/e2ae47624cd692b85b65493ecd4b82b2/manifest/video.m3u8',
  ];

  // Dynamic video list that grows with pagination
  List<String> _videoUrls = [];

  int _currentIndex = 0;
  bool _isInitialized = false;
  final Map<int, int> _textureIds = {};

  late AnimationController _fadeController;
  late Animation<double> _fadeAnimation;

  bool _isLoadingMoreVideos = false;

  // Video fitting mode for demonstration
  VideoFit _currentVideoFit = VideoFit.cover;

  // Debug information storage
  final List<String> _debugMessages = [];
  final List<String> _errorMessages = [];
  String? _lastDebugMessage;
  String? _lastErrorMessage;
  DateTime? _lastDebugTime;
  DateTime? _lastErrorTime;

  @override
  void initState() {
    super.initState();
    _player = ShortsPlayerController();
    _pageController = PageController();
    _fadeController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
    _fadeAnimation = Tween<double>(begin: 0, end: 1).animate(_fadeController);

    // Initialize with the base video URLs
    _videoUrls = List.from(_baseVideoUrls);

    _initializePlayer();
  }

  Future<void> _initializePlayer() async {
    try {
      // Initialize the player
      await _player.init();

      // Set up debug callbacks to capture all debug information
      _setupDebugCallbacks();

      // Set up all video URLs
      await _player.appendUrls(0, _videoUrls);

      // Configure player settings
      await _player.setLooping(true);
      await _player.setProgressTracking(enabled: true, intervalMs: 500);

      // Prime the first few videos
      for (int i = 0; i < 3 && i < _videoUrls.length; i++) {
        _textureIds[i] = await _player.attach(i);
        await _player.prime(i);
      }

      // Start playback immediately - let the native side handle loading states
      if (_videoUrls.isNotEmpty) {
        _addDebugMessage('Starting first video playback');

        await _player.switchTo(0);
        _fadeController.forward();
      }

      setState(() {
        _isInitialized = true;
      });
    } catch (e) {
      debugPrint('Error initializing player: $e');
      _addErrorMessage('Initialization Error: $e');
    }
  }

  void _setupDebugCallbacks() {
    // Access the method channel directly to set up additional debug callbacks
    final methodChannel = _player.methodChannel;

    // Store original callbacks and wrap them with debug logging
    final originalOnVideoLoaded = methodChannel.onVideoLoaded;
    final originalOnBufferingUpdate = methodChannel.onBufferingUpdate;
    final originalOnPlaybackStateChanged = methodChannel.onPlaybackStateChanged;
    final originalOnVideoDimensionsChanged =
        methodChannel.onVideoDimensionsChanged;
    final originalOnProgress = methodChannel.onProgress;
    final originalOnWatched = methodChannel.onWatched;
    final originalOnError = methodChannel.onError;
    final originalOnDebugInfo = methodChannel.onDebugInfo;

    methodChannel.onVideoLoaded = (int index, String url) {
      originalOnVideoLoaded?.call(index, url);
      _addDebugMessage('Video Loaded: Index $index, URL: ${_truncateUrl(url)}');
    };

    methodChannel.onBufferingUpdate = (int index, String url, int percent) {
      originalOnBufferingUpdate?.call(index, url, percent);
      _addDebugMessage('Buffering: Index $index, ${percent}%');
    };

    methodChannel.onPlaybackStateChanged =
        (int index, String url, String state) {
          originalOnPlaybackStateChanged?.call(index, url, state);
          _addDebugMessage('Playback State: Index $index, State: $state');
        };

    methodChannel.onVideoDimensionsChanged =
        (int index, String url, double width, double height) {
          originalOnVideoDimensionsChanged?.call(index, url, width, height);
          _addDebugMessage(
            'Dimensions: Index $index, ${width.toInt()}x${height.toInt()}',
          );
        };

    methodChannel.onProgress =
        (
          int index,
          String url,
          int positionMs,
          int durationMs,
          int bufferedMs,
        ) {
          originalOnProgress?.call(
            index,
            url,
            positionMs,
            durationMs,
            bufferedMs,
          );
          // Don't log progress too frequently to avoid spam
          if (positionMs % 5000 < 500) {
            // Log every ~5 seconds
            _addDebugMessage(
              'Progress: Index $index, ${(positionMs / 1000).toStringAsFixed(1)}s/${(durationMs / 1000).toStringAsFixed(1)}s',
            );
          }
        };

    methodChannel.onWatched = (int index, String url) {
      originalOnWatched?.call(index, url);
      _addDebugMessage('Video Watched: Index $index');
    };

    methodChannel.onError =
        (int index, String url, String errorMessage, String errorCode) {
          originalOnError?.call(index, url, errorMessage, errorCode);
          _addErrorMessage(
            'Error: Index $index, Code: $errorCode, Message: $errorMessage',
          );
        };

    methodChannel.onDebugInfo = (int index, String message) {
      originalOnDebugInfo?.call(index, message);
      _addDebugMessage('Native Debug: Index $index, $message');
    };
  }

  void _addDebugMessage(String message) {
    if (mounted) {
      setState(() {
        _debugMessages.insert(0, message);
        if (_debugMessages.length > 50) {
          _debugMessages.removeLast();
        }
        _lastDebugMessage = message;
        _lastDebugTime = DateTime.now();
      });
    }
  }

  void _addErrorMessage(String message) {
    if (mounted) {
      setState(() {
        _errorMessages.insert(0, message);
        if (_errorMessages.length > 20) {
          _errorMessages.removeLast();
        }
        _lastErrorMessage = message;
        _lastErrorTime = DateTime.now();
      });
    }
  }

  String _truncateUrl(String url) {
    if (url.length <= 50) return url;
    return '${url.substring(0, 25)}...${url.substring(url.length - 20)}';
  }

  Future<void> _loadMoreVideos() async {
    if (_isLoadingMoreVideos) return;

    _isLoadingMoreVideos = true;

    try {
      final currentCount = _videoUrls.length;
      final newUrls = List<String>.from(_baseVideoUrls);

      // Add the same videos again for pagination demo
      _videoUrls.addAll(newUrls);

      // Append the new URLs to the player starting from the current count
      await _player.appendUrls(currentCount, newUrls);

      setState(() {}); // Trigger rebuild to update itemCount
    } catch (e) {
      debugPrint('Error loading more videos: $e');
    } finally {
      _isLoadingMoreVideos = false;
    }
  }

  Future<void> _onPageChanged(int index) async {
    if (!_isInitialized) return;

    _currentIndex = index;

    // Check if we need to load more videos (when reaching second-to-last page)
    if (index >= _videoUrls.length - 2 && !_isLoadingMoreVideos) {
      _loadMoreVideos();
    }

    try {
      // Ensure texture is attached for current video
      if (!_textureIds.containsKey(index)) {
        _textureIds[index] = await _player.attach(index);
        await _player.prime(index);
      }

      // Switch to the new video
      await _player.switchTo(index);

      // Prewarm adjacent videos
      int? nextIndex = index + 1 < _videoUrls.length ? index + 1 : null;
      int? prevIndex = index - 1 >= 0 ? index - 1 : null;

      // Attach textures for adjacent videos if needed
      if (nextIndex != null && !_textureIds.containsKey(nextIndex)) {
        _textureIds[nextIndex] = await _player.attach(nextIndex);
      }
      if (prevIndex != null && !_textureIds.containsKey(prevIndex)) {
        _textureIds[prevIndex] = await _player.attach(prevIndex);
      }

      await _player.prewarm(next: nextIndex, prev: prevIndex);
    } catch (e) {
      debugPrint('Error changing page: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: AnimatedBuilder(
        animation: _player,
        builder: (context, child) {
          return Stack(
            children: [
              // Video feed
              PageView.builder(
                controller: _pageController,
                scrollDirection: Axis.vertical,
                onPageChanged: _onPageChanged,
                itemCount: _videoUrls.length,
                itemBuilder: (context, index) {
                  return AdaptiveVideoFeedPlayer(
                    key: ValueKey('video_${index}_${_currentVideoFit.name}'),
                    index: index,
                    controller: _player,
                    isActive: _currentIndex == index,
                    fit: _currentVideoFit, // Use dynamic fit mode
                    showDebugInfo:
                        true, // Show debug info to demonstrate aspect ratio adaptation
                    onTap: () {
                      // Optional: Add custom tap handling here
                    },
                  );
                },
              ),

              // Top overlay with app info and fit mode controls
              Positioned(
                top: MediaQuery.of(context).padding.top + 10,
                left: 20,
                right: 20,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    // App title and video counter
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        const Text(
                          'Zhabby Shorts',
                          style: TextStyle(
                            color: Colors.white,
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        Row(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Text(
                              '${_currentIndex + 1} / ${_videoUrls.length}',
                              style: const TextStyle(
                                color: Colors.white70,
                                fontSize: 14,
                              ),
                            ),
                            if (_isLoadingMoreVideos) ...[
                              const SizedBox(width: 8),
                              const SizedBox(
                                width: 12,
                                height: 12,
                                child: CircularProgressIndicator(
                                  strokeWidth: 2,
                                  color: Colors.white70,
                                ),
                              ),
                            ],
                          ],
                        ),
                      ],
                    ),
                    const SizedBox(height: 10),
                    // Video fit mode controls
                    Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 8,
                        vertical: 4,
                      ),
                      decoration: BoxDecoration(
                        color: Colors.black.withOpacity(0.5),
                        borderRadius: BorderRadius.circular(20),
                      ),
                      child: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Text(
                            'Fit: ',
                            style: TextStyle(
                              color: Colors.white70,
                              fontSize: 12,
                            ),
                          ),
                          ...VideoFit.values.map((fit) {
                            final isSelected = _currentVideoFit == fit;
                            return GestureDetector(
                              onTap: () {
                                setState(() {
                                  _currentVideoFit = fit;
                                });
                              },
                              child: Container(
                                margin: const EdgeInsets.symmetric(
                                  horizontal: 2,
                                ),
                                padding: const EdgeInsets.symmetric(
                                  horizontal: 8,
                                  vertical: 2,
                                ),
                                decoration: BoxDecoration(
                                  color: isSelected
                                      ? Colors.blue
                                      : Colors.transparent,
                                  borderRadius: BorderRadius.circular(12),
                                ),
                                child: Text(
                                  fit.name.toUpperCase(),
                                  style: TextStyle(
                                    color: isSelected
                                        ? Colors.white
                                        : Colors.white70,
                                    fontSize: 10,
                                    fontWeight: isSelected
                                        ? FontWeight.bold
                                        : FontWeight.normal,
                                  ),
                                ),
                              ),
                            );
                          }).toList(),
                        ],
                      ),
                    ),
                  ],
                ),
              ),

              // Debug overlay with video status
              Positioned(
                bottom: MediaQuery.of(context).padding.bottom + 10,
                left: 10,
                right: 10,
                child: Container(
                  padding: const EdgeInsets.all(12),
                  decoration: BoxDecoration(
                    color: Colors.black.withOpacity(0.8),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: _buildDebugInfo(),
                ),
              ),
            ],
          );
        },
      ),
    );
  }

  Widget _buildDebugInfo() {
    final currentStatus = _player.getVideoStatus(_currentIndex);
    final activeIndex = _player.activeIndex;
    final playbackInfo = _player.currentPlaybackInfo;
    final currentTextureId = _textureIds[_currentIndex];

    return DefaultTabController(
      length: 4,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          // Header with tabs
          Container(
            height: 30,
            child: TabBar(
              isScrollable: true,
              labelColor: Colors.white,
              unselectedLabelColor: Colors.white54,
              indicatorColor: Colors.blue,
              labelStyle: const TextStyle(
                fontSize: 10,
                fontWeight: FontWeight.bold,
              ),
              tabs: [
                Tab(text: 'PLAYER'),
                Tab(text: 'DEBUG (${_debugMessages.length})'),
                Tab(text: 'ERRORS (${_errorMessages.length})'),
                Tab(text: 'TECHNICAL'),
              ],
            ),
          ),

          // Tab content
          Container(
            height: 200,
            child: TabBarView(
              children: [
                // Player Status Tab
                _buildPlayerStatusTab(
                  currentStatus,
                  activeIndex,
                  playbackInfo,
                  currentTextureId,
                ),

                // Debug Messages Tab
                _buildDebugMessagesTab(),

                // Error Messages Tab
                _buildErrorMessagesTab(),

                // Technical Info Tab
                _buildTechnicalInfoTab(playbackInfo),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildPlayerStatusTab(
    VideoStatus? currentStatus,
    int? activeIndex,
    Map<String, dynamic>? playbackInfo,
    int? textureId,
  ) {
    final playerState = _player.getPlayerState(_currentIndex);

    return SingleChildScrollView(
      padding: const EdgeInsets.all(8),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _buildInfoRow('Active Index', '$activeIndex'),
          _buildInfoRow('Current Index', '$_currentIndex'),
          _buildInfoRow('Texture ID', '$textureId'),
          _buildInfoRow('Initialized', '$_isInitialized'),
          _buildInfoRow('Loading More', '$_isLoadingMoreVideos'),
          const SizedBox(height: 8),

          if (playerState != null) ...[
            const Text(
              'DETAILED PLAYER STATE',
              style: TextStyle(
                color: Colors.cyan,
                fontSize: 10,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 4),
            _buildInfoRow('Player Status', playerState.playerStatus),
            _buildInfoRow('Time Control', playerState.timeControlStatus),
            _buildInfoRow('Item Status', playerState.itemStatus),
            _buildInfoRow(
              'Player Rate',
              playerState.playerRate.toStringAsFixed(2),
            ),
            _buildInfoRow(
              'Current Time',
              '${playerState.currentTimeSeconds.toStringAsFixed(2)}s',
            ),
            _buildInfoRow(
              'Duration',
              '${playerState.itemDurationSeconds.toStringAsFixed(2)}s',
            ),
            _buildInfoRow('Video Tracks', '${playerState.videoTracksCount}'),
            _buildInfoRow('Has New Frame', '${playerState.hasNewFrame}'),
            _buildInfoRow('Is Playing', '${playerState.isPlaying}'),
            _buildInfoRow('Is Ready', '${playerState.isReady}'),
            if (playerState.hasErrors)
              _buildInfoRow('Errors', playerState.errorMessage ?? 'Unknown'),
            _buildInfoRow(
              'Updated',
              '${playerState.timestamp.hour.toString().padLeft(2, '0')}:${playerState.timestamp.minute.toString().padLeft(2, '0')}:${playerState.timestamp.second.toString().padLeft(2, '0')}',
            ),
            const SizedBox(height: 8),
          ],

          if (currentStatus != null) ...[
            const Text(
              'BASIC VIDEO STATUS',
              style: TextStyle(
                color: Colors.yellow,
                fontSize: 10,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 4),
            _buildInfoRow('URL', _truncateUrl(currentStatus.url)),
            _buildInfoRow('State', currentStatus.playbackState),
            _buildInfoRow('Loaded', '${currentStatus.isLoaded}'),
            _buildInfoRow('Buffer %', '${currentStatus.bufferingPercent}%'),
            _buildInfoRow(
              'Position',
              '${(currentStatus.positionMs / 1000).toStringAsFixed(1)}s',
            ),
            _buildInfoRow(
              'Duration',
              '${(currentStatus.durationMs / 1000).toStringAsFixed(1)}s',
            ),
            _buildInfoRow(
              'Buffered',
              '${(currentStatus.bufferedMs / 1000).toStringAsFixed(1)}s',
            ),
            if (currentStatus.videoWidth != null &&
                currentStatus.videoHeight != null) ...[
              _buildInfoRow(
                'Dimensions',
                '${currentStatus.videoWidth!.toInt()}x${currentStatus.videoHeight!.toInt()}',
              ),
              _buildInfoRow(
                'Aspect Ratio',
                currentStatus.aspectRatio?.toStringAsFixed(2) ?? 'N/A',
              ),
            ],
          ] else ...[
            const Text(
              'No basic video status available',
              style: TextStyle(color: Colors.orange, fontSize: 10),
            ),
          ],
        ],
      ),
    );
  }

  Widget _buildDebugMessagesTab() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        if (_lastDebugMessage != null)
          Container(
            padding: const EdgeInsets.all(6),
            margin: const EdgeInsets.all(4),
            decoration: BoxDecoration(
              color: Colors.blue.withOpacity(0.2),
              borderRadius: BorderRadius.circular(4),
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'LATEST:',
                  style: TextStyle(
                    color: Colors.blue,
                    fontSize: 9,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                Text(
                  _lastDebugMessage!,
                  style: const TextStyle(color: Colors.white, fontSize: 9),
                ),
                if (_lastDebugTime != null)
                  Text(
                    '${_lastDebugTime!.hour.toString().padLeft(2, '0')}:${_lastDebugTime!.minute.toString().padLeft(2, '0')}:${_lastDebugTime!.second.toString().padLeft(2, '0')}',
                    style: const TextStyle(color: Colors.white54, fontSize: 8),
                  ),
              ],
            ),
          ),

        Expanded(
          child: _debugMessages.isEmpty
              ? const Center(
                  child: Text(
                    'No debug messages yet',
                    style: TextStyle(color: Colors.white54, fontSize: 10),
                  ),
                )
              : ListView.builder(
                  padding: const EdgeInsets.symmetric(horizontal: 4),
                  itemCount: _debugMessages.length,
                  itemBuilder: (context, index) {
                    final message = _debugMessages[index];
                    return Container(
                      padding: const EdgeInsets.symmetric(
                        vertical: 2,
                        horizontal: 4,
                      ),
                      child: Text(
                        '• $message',
                        style: const TextStyle(
                          color: Colors.white70,
                          fontSize: 9,
                        ),
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                      ),
                    );
                  },
                ),
        ),

        // Add button to trigger diagnose
        Container(
          padding: const EdgeInsets.all(4),
          child: ElevatedButton(
            onPressed: () => _player.diagnoseVideoState(_currentIndex),
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.blue,
              minimumSize: const Size(double.infinity, 30),
            ),
            child: const Text(
              'Diagnose Current Video',
              style: TextStyle(fontSize: 10),
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildErrorMessagesTab() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        if (_lastErrorMessage != null)
          Container(
            padding: const EdgeInsets.all(6),
            margin: const EdgeInsets.all(4),
            decoration: BoxDecoration(
              color: Colors.red.withOpacity(0.2),
              borderRadius: BorderRadius.circular(4),
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'LATEST ERROR:',
                  style: TextStyle(
                    color: Colors.red,
                    fontSize: 9,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                Text(
                  _lastErrorMessage!,
                  style: const TextStyle(color: Colors.white, fontSize: 9),
                ),
                if (_lastErrorTime != null)
                  Text(
                    '${_lastErrorTime!.hour.toString().padLeft(2, '0')}:${_lastErrorTime!.minute.toString().padLeft(2, '0')}:${_lastErrorTime!.second.toString().padLeft(2, '0')}',
                    style: const TextStyle(color: Colors.white54, fontSize: 8),
                  ),
              ],
            ),
          ),

        Expanded(
          child: _errorMessages.isEmpty
              ? const Center(
                  child: Text(
                    'No errors (Good!)',
                    style: TextStyle(color: Colors.green, fontSize: 10),
                  ),
                )
              : ListView.builder(
                  padding: const EdgeInsets.symmetric(horizontal: 4),
                  itemCount: _errorMessages.length,
                  itemBuilder: (context, index) {
                    final message = _errorMessages[index];
                    return Container(
                      padding: const EdgeInsets.symmetric(
                        vertical: 2,
                        horizontal: 4,
                      ),
                      child: Text(
                        '• $message',
                        style: const TextStyle(
                          color: Colors.redAccent,
                          fontSize: 9,
                        ),
                        maxLines: 3,
                        overflow: TextOverflow.ellipsis,
                      ),
                    );
                  },
                ),
        ),
      ],
    );
  }

  Widget _buildTechnicalInfoTab(Map<String, dynamic>? playbackInfo) {
    final currentStatus = _player.getVideoStatus(_currentIndex);

    return SingleChildScrollView(
      padding: const EdgeInsets.all(8),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'ADAPTIVE VIDEO INFO',
            style: TextStyle(
              color: Colors.cyan,
              fontSize: 10,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 4),
          _buildInfoRow(
            'Current Fit Mode',
            _currentVideoFit.name.toUpperCase(),
          ),
          if (currentStatus != null) ...[
            if (currentStatus.videoWidth != null &&
                currentStatus.videoHeight != null) ...[
              _buildInfoRow(
                'Video Dimensions',
                '${currentStatus.videoWidth!.toInt()}x${currentStatus.videoHeight!.toInt()}',
              ),
              _buildInfoRow(
                'Aspect Ratio',
                currentStatus.aspectRatio?.toStringAsFixed(3) ?? 'Unknown',
              ),
              _buildInfoRow(
                'Is Portrait',
                '${(currentStatus.aspectRatio ?? 1.0) < 1.0}',
              ),
              _buildInfoRow(
                'Is Landscape',
                '${(currentStatus.aspectRatio ?? 1.0) > 1.0}',
              ),
              _buildInfoRow(
                'Is Square',
                '${((currentStatus.aspectRatio ?? 1.0) - 1.0).abs() < 0.01}',
              ),
            ] else ...[
              _buildInfoRow('Video Dimensions', 'Not yet available'),
              _buildInfoRow('Aspect Ratio', 'Calculating...'),
            ],
          ] else ...[
            _buildInfoRow('Video Status', 'Not available'),
          ],

          const SizedBox(height: 12),
          const Text(
            'TEXTURE IDS',
            style: TextStyle(
              color: Colors.yellow,
              fontSize: 10,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 4),
          ..._textureIds.entries.map(
            (entry) =>
                _buildInfoRow('Index ${entry.key}', 'Texture ${entry.value}'),
          ),

          const SizedBox(height: 12),
          const Text(
            'VIDEO URLS',
            style: TextStyle(
              color: Colors.yellow,
              fontSize: 10,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 4),
          Text(
            'Total Videos: ${_videoUrls.length}',
            style: const TextStyle(color: Colors.white70, fontSize: 10),
          ),

          if (playbackInfo != null) ...[
            const SizedBox(height: 12),
            const Text(
              'PLAYBACK INFO',
              style: TextStyle(
                color: Colors.yellow,
                fontSize: 10,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 4),
            ...playbackInfo.entries.map((entry) {
              String value = entry.value.toString();
              if (entry.value is double) {
                value = (entry.value as double).toStringAsFixed(2);
              }
              return _buildInfoRow(entry.key, value);
            }),
          ],

          const SizedBox(height: 12),
          const Text(
            'APP INFO',
            style: TextStyle(
              color: Colors.yellow,
              fontSize: 10,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 4),
          _buildInfoRow('Flutter', 'Debug Mode'),
          _buildInfoRow('Plugin', 'Zhabby Shorts with Adaptive Video'),
          _buildInfoRow(
            'Build Time',
            DateTime.now().toString().split('.').first,
          ),
        ],
      ),
    );
  }

  Widget _buildInfoRow(String label, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 1),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            width: 80,
            child: Text(
              '$label:',
              style: const TextStyle(color: Colors.white54, fontSize: 9),
            ),
          ),
          Expanded(
            child: Text(
              value,
              style: const TextStyle(color: Colors.white70, fontSize: 9),
            ),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _pageController.dispose();
    _fadeController.dispose();
    _player.release();
    super.dispose();
  }
}

// VideoPlayerWidget has been replaced with AdaptiveVideoFeedPlayer
0
likes
130
points
0
downloads

Publisher

unverified uploader

Weekly Downloads

Shorts for Flutter.

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on zhabby_shorts

Packages that implement zhabby_shorts