hq_video_player 0.1.0 copy "hq_video_player: ^0.1.0" to clipboard
hq_video_player: ^0.1.0 copied to clipboard

A high-performance Flutter video player package powered by media_kit & flutter_bloc with gesture controls, subtitle rendering, and modern video controls.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:hq_video_player/hq_video_player.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  MediaKitService.ensureInitialized();
  runApp(const HqVideoPlayerExampleApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'HQ Video Player Showcase',
      debugShowCheckedModeBanner: false,
      themeMode: ThemeMode.dark,
      darkTheme: ThemeData.dark(useMaterial3: true).copyWith(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF7C4DFF),
          brightness: Brightness.dark,
        ),
        scaffoldBackgroundColor: const Color(0xFF0F0F16),
        cardTheme: CardThemeData(
          color: const Color(0xFF181824),
          elevation: 0,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(16),
            side: BorderSide(
              color: Colors.white.withValues(alpha: 0.08),
              width: 1,
            ),
          ),
        ),
      ),
      home: const ExampleHomeScreen(),
    );
  }
}

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

  @override
  State<ExampleHomeScreen> createState() => _ExampleHomeScreenState();
}

class _ExampleHomeScreenState extends State<ExampleHomeScreen> {
  int _selectedTab = 0;

  bool _showGestures = true;
  bool _showControls = true;
  bool _allowFullscreen = true;
  bool _showSpeedButton = true;
  bool _showLockButton = true;
  final bool _autoPlay = false;
  final bool _looping = false;

  bool _showMuteButton = false;
  final bool _showWatermark = true;
  int _doubleTapSeekSec = 10;

  bool _thickTimeline = false;
  bool _enablePinchZoom = true;
  bool _enableLongPressSpeed = true;
  bool _flipHorizontal = false;
  bool _volumeBoost = false;

  bool _grayscale = false;
  bool _sepia = false;
  bool _enableHaptics = true;
  bool _enableABLoop = false;
  bool _greenLoading = false;

  String _lastEventLog = 'No events captured yet';

  final String _sampleVideoUrl = 'assets/pubg.mp4';
  final String _samplePosterUrl =
      'https://images.unsplash.com/photo-1536440136628-849c177e76a1?q=80&w=800&auto=format&fit=crop';

  void _updateEventLog(String msg) {
    if (_lastEventLog == msg) return;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (mounted) {
        setState(() => _lastEventLog = msg);
      }
    });
  }

  HqVideoPlayerConfig _resolveConfigForTab() {
    final commonCallbacks = HqVideoPlayerCallbacks(
      onPlayingChanged: (playing) =>
          _updateEventLog('onPlayingChanged: $playing'),
      onSpeedChanged: (speed) => _updateEventLog('onSpeedChanged: ${speed}x'),
      onVolumeChanged: (vol) =>
          _updateEventLog('onVolumeChanged: ${(vol * 100).round()}%'),
      onSeek: (pos) => _updateEventLog('onSeek: ${pos.inSeconds}s'),
      onBufferingChanged: (buffering) =>
          _updateEventLog('onBufferingChanged: $buffering'),
      onLockChanged: (locked) => _updateEventLog('onLockChanged: $locked'),
      onFitModeChanged: (fit) =>
          _updateEventLog('onFitModeChanged: ${fit.name}'),
    );

    if (_selectedTab == 1) {
      // Subtitles Tab
      return HqVideoPlayerConfig(
        subtitleConfig: const HqVideoPlayerSubtitleConfig(
          fontSize: 18.0,
          bottomPadding: 32.0,
          textColor: Colors.yellowAccent,
          backgroundColor: Color(0xDD000000),
        ),
        callbacks: commonCallbacks,
      );
    } else if (_selectedTab == 3) {
      // Theme & Custom Overlay Tab
      return HqVideoPlayerConfig(
        theme: const HqVideoPlayerTheme(
          timelinePlayedColor: Colors.deepPurpleAccent,
          timelineHandleColor: Colors.deepPurpleAccent,
          subtitleBackgroundColor: Color(0xCC1E1E2E),
          pillBackgroundColor: Color(0xEE1E1E2E),
          menuBackgroundColor: Color(0xFF1E1E2E),
        ),
        icons: const HqVideoPlayerIcons(
          play: Icon(Icons.play_arrow_rounded, color: Colors.deepPurpleAccent),
          pause: Icon(Icons.pause_rounded, color: Colors.deepPurpleAccent),
        ),
        gestures: HqVideoPlayerGestures(
          doubleTapSeekSeconds: _doubleTapSeekSec,
          enablePinchToZoom: _enablePinchZoom,
          enableLongPressSpeed: _enableLongPressSpeed,
        ),
        behavior: HqVideoPlayerBehavior(showMuteButton: _showMuteButton),
        timelineConfig: HqVideoPlayerTimelineConfig(
          trackHeight: _thickTimeline ? 8.0 : 4.0,
          thumbRadius: _thickTimeline ? 9.0 : 6.0,
        ),
        transform: HqVideoPlayerTransformConfig(
          flipHorizontal: _flipHorizontal,
        ),
        audio: HqVideoPlayerAudioConfig(maxVolume: _volumeBoost ? 1.5 : 1.0),
        filter: HqVideoPlayerFilterConfig(grayscale: _grayscale, sepia: _sepia),
        haptics: HqVideoPlayerHapticsConfig(
          enableHapticsOnSeek: _enableHaptics,
          enableHapticsOnLock: _enableHaptics,
          enableHapticsOnSpeedBoost: _enableHaptics,
        ),
        loopConfig: HqVideoPlayerLoopConfig(
          enableABLoop: _enableABLoop,
          startPosition: const Duration(seconds: 5),
          endPosition: const Duration(seconds: 15),
        ),
        loadingConfig: HqVideoPlayerLoadingConfig(
          loadingIndicatorColor: _greenLoading ? Colors.greenAccent : null,
          loadingIndicatorSize: _greenLoading ? 48.0 : 36.0,
        ),
        posterConfig: HqVideoPlayerPosterConfig(
          posterSource: HqPosterSource.network(_samplePosterUrl),
          posterFit: BoxFit.cover,
        ),
        seekFeedbackConfig: const HqVideoPlayerSeekFeedbackConfig(
          rippleColor: Colors.deepPurpleAccent,
          rippleDuration: Duration(milliseconds: 700),
        ),
        customWidgets: HqVideoPlayerCustomWidgets(
          watermark: _showWatermark
              ? Container(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 8,
                    vertical: 4,
                  ),
                  decoration: BoxDecoration(
                    color: Colors.deepPurple.withValues(alpha: 0.8),
                    borderRadius: BorderRadius.circular(6),
                  ),
                  child: const Text(
                    'HighQ Pro',
                    style: TextStyle(
                      color: Colors.white,
                      fontWeight: FontWeight.bold,
                      fontSize: 12,
                    ),
                  ),
                )
              : null,
          customOverlay: Positioned(
            bottom: 70,
            right: 16,
            child: Material(
              color: Colors.black.withValues(alpha: 0.75),
              borderRadius: BorderRadius.circular(8),
              child: InkWell(
                onTap: () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Skip Intro Clicked!')),
                  );
                },
                borderRadius: BorderRadius.circular(8),
                child: const Padding(
                  padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Icon(Icons.fast_forward, color: Colors.white, size: 14),
                      SizedBox(width: 6),
                      Text(
                        'SKIP INTRO',
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: 11,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
        ),
        callbacks: commonCallbacks,
      );
    } else if (_selectedTab == 4) {
      // Callbacks & Custom Network Headers Tab
      return HqVideoPlayerConfig(
        network: const HqVideoPlayerNetworkConfig(
          customHeaders: {
            'User-Agent': 'HQVideoPlayerDemo/1.0',
            'Authorization': 'Bearer sample_access_token_123',
          },
        ),
        callbacks: commonCallbacks,
      );
    }

    return HqVideoPlayerConfig(
      gestures: HqVideoPlayerGestures(
        doubleTapSeekSeconds: _doubleTapSeekSec,
        enablePinchToZoom: _enablePinchZoom,
        enableLongPressSpeed: _enableLongPressSpeed,
      ),
      behavior: HqVideoPlayerBehavior(
        autoPlay: _autoPlay,
        looping: _looping,
        showMuteButton: _showMuteButton,
      ),
      timelineConfig: HqVideoPlayerTimelineConfig(
        trackHeight: _thickTimeline ? 8.0 : 4.0,
        thumbRadius: _thickTimeline ? 9.0 : 6.0,
      ),
      transform: HqVideoPlayerTransformConfig(flipHorizontal: _flipHorizontal),
      audio: HqVideoPlayerAudioConfig(maxVolume: _volumeBoost ? 1.5 : 1.0),
      filter: HqVideoPlayerFilterConfig(grayscale: _grayscale, sepia: _sepia),
      haptics: HqVideoPlayerHapticsConfig(
        enableHapticsOnSeek: _enableHaptics,
        enableHapticsOnLock: _enableHaptics,
        enableHapticsOnSpeedBoost: _enableHaptics,
      ),
      loopConfig: HqVideoPlayerLoopConfig(
        enableABLoop: _enableABLoop,
        startPosition: const Duration(seconds: 5),
        endPosition: const Duration(seconds: 15),
      ),
      loadingConfig: HqVideoPlayerLoadingConfig(
        loadingIndicatorColor: _greenLoading ? Colors.greenAccent : null,
        loadingIndicatorSize: _greenLoading ? 48.0 : 36.0,
      ),
      callbacks: commonCallbacks,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            const Icon(Icons.movie_filter_rounded, color: Color(0xFF9D65FF)),
            const SizedBox(width: 10),
            const Text(
              'HQ Video Player',
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 18,
              ),
            ),
            const SizedBox(width: 8),
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
              decoration: BoxDecoration(
                color: const Color(0xFF7C4DFF).withValues(alpha: 0.2),
                borderRadius: BorderRadius.circular(12),
                border: Border.all(
                  color: const Color(0xFF7C4DFF).withValues(alpha: 0.4),
                ),
              ),
              child: const Text(
                'v0.1.0',
                style: TextStyle(
                  color: Color(0xFFB388FF),
                  fontSize: 10,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ],
        ),
        centerTitle: true,
        backgroundColor: const Color(0xFF0F0F16),
        elevation: 0,
      ),
      body: SingleChildScrollView(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              if (_selectedTab != 5) ...[
                // Video Player Surface Container
                AspectRatio(
                  aspectRatio: 16 / 9,
                  child: Container(
                    decoration: BoxDecoration(
                      color: Colors.black,
                      borderRadius: BorderRadius.circular(16),
                      border: Border.all(
                        color: Colors.white.withValues(alpha: 0.12),
                        width: 1,
                      ),
                      boxShadow: [
                        BoxShadow(
                          color: const Color(0xFF7C4DFF).withValues(alpha: 0.15),
                          blurRadius: 20,
                          spreadRadius: 2,
                          offset: const Offset(0, 8),
                        ),
                      ],
                    ),
                    clipBehavior: Clip.antiAlias,
                    child: HqVideoPlayer.asset(
                      key: ValueKey(
                        'player_tab_${_selectedTab}_${_showGestures}_${_showControls}_${_doubleTapSeekSec}_${_showMuteButton}_${_thickTimeline}_${_flipHorizontal}_${_volumeBoost}_${_grayscale}_${_sepia}_${_enableABLoop}_$_greenLoading',
                      ),
                      assetPath: _sampleVideoUrl,
                      title: _selectedTab == 3
                          ? 'PUBG Mobile (Custom Theme)'
                          : (_selectedTab == 0
                                ? 'PUBG Mobile (Asset Sample)'
                                : 'Customized Video Player'),
                      showControls: _showControls,
                      showGestures: _showGestures,
                      allowFullscreen: _allowFullscreen,
                      showSpeedButton: _showSpeedButton,
                      showLockButton: _showLockButton,
                      subtitleSource: _selectedTab == 1
                          ? const HqSubtitleSource.asset('assets/subtitles_sample.srt')
                          : null,
                      config: _resolveConfigForTab(),
                    ),
                  ),
                ),
                const SizedBox(height: 16),

                // Live Event Logger Card
                Container(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 14,
                    vertical: 10,
                  ),
                  decoration: BoxDecoration(
                    color: const Color(0xFF161622),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(
                      color: Colors.greenAccent.withValues(alpha: 0.2),
                    ),
                  ),
                  child: Row(
                    children: [
                      Container(
                        width: 8,
                        height: 8,
                        decoration: const BoxDecoration(
                          color: Colors.greenAccent,
                          shape: BoxShape.circle,
                          boxShadow: [
                            BoxShadow(
                              color: Colors.greenAccent,
                              blurRadius: 6,
                            ),
                          ],
                        ),
                      ),
                      const SizedBox(width: 10),
                      Expanded(
                        child: Text(
                          'Live Event: $_lastEventLog',
                          style: const TextStyle(
                            color: Colors.greenAccent,
                            fontSize: 12,
                            fontWeight: FontWeight.w600,
                            fontFamily: 'monospace',
                          ),
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                        ),
                      ),
                    ],
                  ),
                ),
                const SizedBox(height: 20),
              ],

              // Modern Custom Animated Tab Bar
              _buildCustomTabBar(),

              const SizedBox(height: 20),

              if (_selectedTab == 5) const MultiPlayerAndFeedDemoWidget(),

              // Tab Content Section
              if (_selectedTab == 2) ...[
                Text(
                  'Customize Player Options & Loop',
                  style: Theme.of(context).textTheme.titleMedium?.copyWith(
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                ),
                const SizedBox(height: 12),
                Card(
                  child: Column(
                    children: [
                      SwitchListTile(
                        title: const Text(
                          'A-B Range Loop (HqVideoPlayerLoopConfig)',
                        ),
                        subtitle: const Text(
                          'Loops video continuously between 5s and 15s',
                        ),
                        value: _enableABLoop,
                        onChanged: (v) => setState(() => _enableABLoop = v),
                      ),
                      SwitchListTile(
                        title: const Text(
                          'Green Loading Indicator (HqVideoPlayerLoadingConfig)',
                        ),
                        subtitle: const Text(
                          'Custom loading spinner color and size',
                        ),
                        value: _greenLoading,
                        onChanged: (v) => setState(() => _greenLoading = v),
                      ),
                      SwitchListTile(
                        title: const Text(
                          'Grayscale Color Filter (HqVideoPlayerFilterConfig)',
                        ),
                        subtitle: const Text(
                          'Black and white cinematic video filter',
                        ),
                        value: _grayscale,
                        onChanged: (v) {
                          setState(() {
                            _grayscale = v;
                            if (v) _sepia = false;
                          });
                        },
                      ),
                      SwitchListTile(
                        title: const Text(
                          'Sepia Color Filter (HqVideoPlayerFilterConfig)',
                        ),
                        subtitle: const Text('Warm vintage sepia video filter'),
                        value: _sepia,
                        onChanged: (v) {
                          setState(() {
                            _sepia = v;
                            if (v) _grayscale = false;
                          });
                        },
                      ),
                      SwitchListTile(
                        title: const Text(
                          'Enable Haptic Feedback (HqVideoPlayerHapticsConfig)',
                        ),
                        subtitle: const Text(
                          'Tactile vibration on seek and speed boost',
                        ),
                        value: _enableHaptics,
                        onChanged: (v) => setState(() => _enableHaptics = v),
                      ),
                    ],
                  ),
                ),
              ] else if (_selectedTab == 1) ...[
                Text(
                  'Subtitle Configuration',
                  style: Theme.of(context).textTheme.titleMedium?.copyWith(
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                ),
                const SizedBox(height: 12),
                const Card(
                  child: Padding(
                    padding: EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          'Subtitles Demo Active',
                          style: TextStyle(
                            fontWeight: FontWeight.bold,
                            fontSize: 16,
                          ),
                        ),
                        SizedBox(height: 8),
                        Text(
                          'Subtitles are styled with custom yellow text, dark transparent background pill, and bottom padding.',
                          style: TextStyle(color: Colors.white70),
                        ),
                      ],
                    ),
                  ),
                ),
              ] else if (_selectedTab == 3) ...[
                Text(
                  'Theme & Custom Watermark Demo',
                  style: Theme.of(context).textTheme.titleMedium?.copyWith(
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                ),
                const SizedBox(height: 12),
                const Card(
                  child: Padding(
                    padding: EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          'Custom Theme Active',
                          style: TextStyle(
                            fontWeight: FontWeight.bold,
                            fontSize: 16,
                            color: Colors.deepPurpleAccent,
                          ),
                        ),
                        SizedBox(height: 8),
                        Text(
                          'Primary accent: DeepPurpleAccent\n'
                          'Watermark overlay: "HighQ Pro"\n'
                          'Custom overlay widget: "SKIP INTRO" button',
                          style: TextStyle(color: Colors.white70),
                        ),
                      ],
                    ),
                  ),
                ),
              ] else if (_selectedTab == 4) ...[
                Text(
                  'Callbacks & Custom Network Options',
                  style: Theme.of(context).textTheme.titleMedium?.copyWith(
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                ),
                const SizedBox(height: 12),
                const Card(
                  child: Padding(
                    padding: EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          'HTTP Headers & Callbacks Active',
                          style: TextStyle(
                            fontWeight: FontWeight.bold,
                            fontSize: 16,
                          ),
                        ),
                        SizedBox(height: 8),
                        Text(
                          'Custom headers sent: Authorization & User-Agent\n'
                          'Live events are logged in real-time above the tab bar.',
                          style: TextStyle(color: Colors.white70),
                        ),
                      ],
                    ),
                  ),
                ),
              ] else ...[
                // Standard Tab
                Text(
                  'Gesture & Feature Toggles',
                  style: Theme.of(context).textTheme.titleMedium?.copyWith(
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                ),
                const SizedBox(height: 12),
                Card(
                  child: Column(
                    children: [
                      SwitchListTile(
                        title: const Text('Enable Touch Gestures'),
                        subtitle: const Text(
                          'Double tap seek, volume/brightness swipe, pinch zoom',
                        ),
                        value: _showGestures,
                        onChanged: (v) => setState(() => _showGestures = v),
                      ),
                      SwitchListTile(
                        title: const Text(
                          'Mirror Video Horizontally (HqVideoPlayerTransformConfig)',
                        ),
                        subtitle: const Text(
                          'Flips video surface horizontally',
                        ),
                        value: _flipHorizontal,
                        onChanged: (v) => setState(() => _flipHorizontal = v),
                      ),
                      SwitchListTile(
                        title: const Text(
                          '150% Volume Boost (HqVideoPlayerAudioConfig)',
                        ),
                        subtitle: const Text(
                          'Increases maximum volume limit to 150%',
                        ),
                        value: _volumeBoost,
                        onChanged: (v) => setState(() => _volumeBoost = v),
                      ),
                      SwitchListTile(
                        title: const Text('Enable Pinch Zoom Gesture'),
                        subtitle: const Text(
                          'Pinch with 2 fingers to zoom 1x-4x',
                        ),
                        value: _enablePinchZoom,
                        onChanged: (v) => setState(() => _enablePinchZoom = v),
                      ),
                      SwitchListTile(
                        title: const Text('Enable Long Press 2x Speed'),
                        subtitle: const Text(
                          'Hold finger to temporarily play at 2x speed',
                        ),
                        value: _enableLongPressSpeed,
                        onChanged: (v) =>
                            setState(() => _enableLongPressSpeed = v),
                      ),
                      SwitchListTile(
                        title: const Text('Show Control Bar'),
                        subtitle: const Text(
                          'Top title bar, seek buttons, and timeline',
                        ),
                        value: _showControls,
                        onChanged: (v) => setState(() => _showControls = v),
                      ),
                      SwitchListTile(
                        title: const Text(
                          'Thick Progress Bar (HqVideoPlayerTimelineConfig)',
                        ),
                        subtitle: const Text(
                          'Increases timeline track height and handle radius',
                        ),
                        value: _thickTimeline,
                        onChanged: (v) => setState(() => _thickTimeline = v),
                      ),
                      SwitchListTile(
                        title: const Text('Show Speed Button'),
                        value: _showSpeedButton,
                        onChanged: (v) => setState(() => _showSpeedButton = v),
                      ),
                      SwitchListTile(
                        title: const Text('Show Lock Button'),
                        value: _showLockButton,
                        onChanged: (v) => setState(() => _showLockButton = v),
                      ),
                      SwitchListTile(
                        title: const Text('Allow Fullscreen Toggle'),
                        value: _allowFullscreen,
                        onChanged: (v) => setState(() => _allowFullscreen = v),
                      ),
                      SwitchListTile(
                        title: const Text('Show Mute Button'),
                        value: _showMuteButton,
                        onChanged: (v) => setState(() => _showMuteButton = v),
                      ),
                      Padding(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 16,
                          vertical: 8,
                        ),
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                          children: [
                            const Text(
                              'Double Tap Seek Seconds',
                              style: TextStyle(fontSize: 15),
                            ),
                            DropdownButton<int>(
                              value: _doubleTapSeekSec,
                              dropdownColor: const Color(0xFF1E1E2E),
                              items: const [
                                DropdownMenuItem(value: 5, child: Text('5s')),
                                DropdownMenuItem(value: 10, child: Text('10s')),
                                DropdownMenuItem(value: 15, child: Text('15s')),
                              ],
                              onChanged: (v) {
                                if (v != null) {
                                  setState(() => _doubleTapSeekSec = v);
                                }
                              },
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildCustomTabBar() {
    final tabs = [
      (icon: Icons.play_circle_fill_rounded, label: 'Standard'),
      (icon: Icons.subtitles_rounded, label: 'Subtitles'),
      (icon: Icons.tune_rounded, label: 'Controls & Loops'),
      (icon: Icons.palette_rounded, label: 'Theme & Poster'),
      (icon: Icons.api_rounded, label: 'Callbacks & Network'),
      (icon: Icons.splitscreen_rounded, label: 'Multi-Player & Controllers'),
    ];

    return SizedBox(
      height: 46,
      child: ListView.separated(
        scrollDirection: Axis.horizontal,
        physics: const BouncingScrollPhysics(),
        itemCount: tabs.length,
        separatorBuilder: (_, __) => const SizedBox(width: 10),
        itemBuilder: (context, index) {
          final isSelected = _selectedTab == index;
          final tab = tabs[index];

          return GestureDetector(
            onTap: () => setState(() => _selectedTab = index),
            child: AnimatedContainer(
              duration: const Duration(milliseconds: 250),
              curve: Curves.easeInOut,
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(24),
                gradient: isSelected
                    ? const LinearGradient(
                        colors: [Color(0xFF7C4DFF), Color(0xFF651FFF)],
                        begin: Alignment.topLeft,
                        end: Alignment.bottomRight,
                      )
                    : null,
                color: isSelected ? null : const Color(0xFF161622),
                border: Border.all(
                  color: isSelected
                      ? const Color(0xFFB388FF).withValues(alpha: 0.6)
                      : Colors.white.withValues(alpha: 0.08),
                  width: 1,
                ),
                boxShadow: isSelected
                    ? [
                        BoxShadow(
                          color: const Color(0xFF7C4DFF).withValues(alpha: 0.4),
                          blurRadius: 12,
                          offset: const Offset(0, 4),
                        ),
                      ]
                    : null,
              ),
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Icon(
                    tab.icon,
                    size: 18,
                    color: isSelected
                        ? Colors.white
                        : Colors.white.withValues(alpha: 0.5),
                  ),
                  const SizedBox(width: 8),
                  Text(
                    tab.label,
                    style: TextStyle(
                      color: isSelected
                          ? Colors.white
                          : Colors.white.withValues(alpha: 0.7),
                      fontWeight:
                          isSelected ? FontWeight.bold : FontWeight.w500,
                      fontSize: 13,
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

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

  @override
  State<MultiPlayerAndFeedDemoWidget> createState() =>
      _MultiPlayerAndFeedDemoWidgetState();
}

class _MultiPlayerAndFeedDemoWidgetState
    extends State<MultiPlayerAndFeedDemoWidget> {
  late final HqVideoPlayerController _ctrl1;
  late final HqVideoPlayerController _ctrl2;
  late final HqVideoPoolManager _poolManager;

  final List<String> _feedVideos = [
    'assets/pubg.mp4',
    'assets/pubg.mp4',
    'assets/pubg.mp4',
  ];

  int _feedPageIndex = 0;
  bool _autoPauseOthers = true;

  @override
  void initState() {
    super.initState();
    _ctrl1 = HqVideoPlayerController(
      tag: 'player_card_1',
      videoUrl: 'assets/pubg.mp4',
      sourceType: HqVideoSourceType.asset,
    );

    _ctrl2 = HqVideoPlayerController(
      tag: 'player_card_2',
      videoUrl: 'assets/pubg.mp4',
      sourceType: HqVideoSourceType.asset,
    );

    _poolManager = HqVideoPoolManager(maxPoolSize: 3);
  }

  @override
  void dispose() {
    _ctrl1.dispose();
    _ctrl2.dispose();
    _poolManager.disposeAll();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(
              'Multi-Controller Manager (Auto-Pause)',
              style: Theme.of(context).textTheme.titleMedium?.copyWith(
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                  ),
            ),
            Row(
              children: [
                const Text('Auto-Pause Others', style: TextStyle(fontSize: 12)),
                Switch(
                  value: _autoPauseOthers,
                  onChanged: (val) {
                    setState(() {
                      _autoPauseOthers = val;
                      HqVideoPlayerManager.instance.autoPauseOthers = val;
                    });
                  },
                ),
              ],
            ),
          ],
        ),
        const SizedBox(height: 8),

        // Global Controller Action Bar
        Wrap(
          spacing: 8,
          runSpacing: 8,
          children: [
            ElevatedButton.icon(
              onPressed: () => HqVideoPlayerManager.instance.pauseAll(),
              icon: const Icon(Icons.pause_rounded, size: 16),
              label: const Text('Pause All'),
            ),
            ElevatedButton.icon(
              onPressed: () => HqVideoPlayerManager.instance.muteAll(),
              icon: const Icon(Icons.volume_off_rounded, size: 16),
              label: const Text('Mute All'),
            ),
            ElevatedButton.icon(
              onPressed: () => HqVideoPlayerManager.instance.unmuteAll(),
              icon: const Icon(Icons.volume_up_rounded, size: 16),
              label: const Text('Unmute All'),
            ),
            ElevatedButton.icon(
              onPressed: () =>
                  HqVideoPlayerManager.instance.playOnly(_ctrl1),
              icon: const Icon(Icons.play_arrow_rounded, size: 16),
              label: const Text('Play Card 1 Only'),
            ),
          ],
        ),
        const SizedBox(height: 16),

        // 2 Side by Side Video Player Cards
        Row(
          children: [
            Expanded(
              child: Card(
                clipBehavior: Clip.antiAlias,
                child: Column(
                  children: [
                    AspectRatio(
                      aspectRatio: 16 / 9,
                      child: HqVideoPlayer(
                        controller: _ctrl1,
                        title: 'Card 1 (Controller A)',
                      ),
                    ),
                    ValueListenableBuilder<HqVideoPlayerValue>(
                      valueListenable: _ctrl1,
                      builder: (context, val, _) {
                        return Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: Text(
                            'Status: ${val.isPlaying ? "Playing 🟢" : "Paused 🔴"} | Vol: ${(val.volume * 100).round()}%',
                            style: const TextStyle(fontSize: 11),
                          ),
                        );
                      },
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Card(
                clipBehavior: Clip.antiAlias,
                child: Column(
                  children: [
                    AspectRatio(
                      aspectRatio: 16 / 9,
                      child: HqVideoPlayer(
                        controller: _ctrl2,
                        title: 'Card 2 (Controller B)',
                      ),
                    ),
                    ValueListenableBuilder<HqVideoPlayerValue>(
                      valueListenable: _ctrl2,
                      builder: (context, val, _) {
                        return Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: Text(
                            'Status: ${val.isPlaying ? "Playing 🟢" : "Paused 🔴"} | Vol: ${(val.volume * 100).round()}%',
                            style: const TextStyle(fontSize: 11),
                          ),
                        );
                      },
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
        const SizedBox(height: 24),

        // Vertical Reels Controller Pool Showcase
        Text(
          'Reels/Shorts Feed (HqVideoPoolManager)',
          style: Theme.of(context).textTheme.titleMedium?.copyWith(
                fontWeight: FontWeight.bold,
                color: Colors.white,
              ),
        ),
        const SizedBox(height: 6),
        Text(
          'Preloads adjacent videos & disposes off-screen controllers smoothly to preserve RAM & zero Codec crashes.',
          style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: 0.6)),
        ),
        const SizedBox(height: 12),
        SizedBox(
          height: 320,
          child: PageView.builder(
            scrollDirection: Axis.vertical,
            itemCount: _feedVideos.length,
            onPageChanged: (idx) {
              setState(() => _feedPageIndex = idx);
              _poolManager.onPageChanged(
                idx,
                _feedVideos,
                sourceType: HqVideoSourceType.asset,
              );
            },
            itemBuilder: (context, index) {
              final feedController = _poolManager.getOrCreateController(
                'feed_$index',
                _feedVideos[index],
                sourceType: HqVideoSourceType.asset,
              );

              return Container(
                margin: const EdgeInsets.only(bottom: 12),
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(16),
                  color: Colors.black,
                  border: Border.all(
                    color: const Color(0xFF7C4DFF).withValues(alpha: 0.4),
                  ),
                ),
                clipBehavior: Clip.antiAlias,
                child: Stack(
                  children: [
                    HqVideoPlayer(
                      controller: feedController,
                      title: 'Reel #${index + 1}',
                      showControls: true,
                    ),
                    Positioned(
                      top: 12,
                      right: 12,
                      child: Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 10,
                          vertical: 4,
                        ),
                        decoration: BoxDecoration(
                          color: Colors.black54,
                          borderRadius: BorderRadius.circular(12),
                        ),
                        child: Text(
                          'Reel #${_feedPageIndex + 1} | Pool Size: ${_poolManager.poolSize} / 3',
                          style: const TextStyle(
                            color: Colors.greenAccent,
                            fontSize: 11,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}
1
likes
160
points
--
downloads

Documentation

API reference

Publisher

unverified uploader

A high-performance Flutter video player package powered by media_kit & flutter_bloc with gesture controls, subtitle rendering, and modern video controls.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

equatable, flutter, flutter_bloc, http, media_kit, media_kit_libs_video, media_kit_video

More

Packages that depend on hq_video_player