hq_video_player 0.1.0
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.
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,
),
),
),
),
],
),
);
},
),
),
],
);
}
}