561 lines
18 KiB
Dart
Executable File
561 lines
18 KiB
Dart
Executable File
import 'dart:async';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:video_player/video_player.dart';
|
|
|
|
class NhanceMultiVideoWrapper extends StatefulWidget {
|
|
final List<String> videoUrls;
|
|
|
|
const NhanceMultiVideoWrapper({
|
|
super.key,
|
|
required this.videoUrls,
|
|
});
|
|
|
|
@override
|
|
State<NhanceMultiVideoWrapper> createState() =>
|
|
_NhanceMultiVideoWrapperState();
|
|
}
|
|
|
|
class _NhanceMultiVideoWrapperState extends State<NhanceMultiVideoWrapper> {
|
|
final PageController _pageController = PageController();
|
|
final List<VideoPlayerController> _controllers = [];
|
|
|
|
int _currentIndex = 0;
|
|
bool _isMuted = false;
|
|
|
|
/// Optional static cover image
|
|
final String coverImage =
|
|
"https://picsum.photos/900/500"; // replace with API thumbnail if available
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_initControllers();
|
|
}
|
|
|
|
Future<void> _initControllers() async {
|
|
for (final url in widget.videoUrls) {
|
|
final controller = VideoPlayerController.networkUrl(Uri.parse(url));
|
|
await controller.initialize();
|
|
controller.setLooping(true);
|
|
controller.setVolume(1);
|
|
_controllers.add(controller);
|
|
}
|
|
if (mounted) setState(() {});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
for (final c in _controllers) {
|
|
c.dispose();
|
|
}
|
|
_pageController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _next() {
|
|
if (_currentIndex < _controllers.length - 1) {
|
|
_pageController.nextPage(
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeInOut,
|
|
);
|
|
}
|
|
}
|
|
|
|
void _previous() {
|
|
if (_currentIndex > 0) {
|
|
_pageController.previousPage(
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeInOut,
|
|
);
|
|
}
|
|
}
|
|
|
|
void _seek(VideoPlayerController c, int seconds) {
|
|
final pos = c.value.position + Duration(seconds: seconds);
|
|
c.seekTo(
|
|
pos < Duration.zero ? Duration.zero : pos,
|
|
);
|
|
}
|
|
|
|
void _toggleMute(VideoPlayerController c) {
|
|
setState(() {
|
|
_isMuted = !_isMuted;
|
|
c.setVolume(_isMuted ? 0 : 1);
|
|
});
|
|
}
|
|
|
|
void _openFullscreen(VideoPlayerController controller) {
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute(
|
|
builder: (_) => Scaffold(
|
|
backgroundColor: Colors.black,
|
|
body: Center(
|
|
child: AspectRatio(
|
|
aspectRatio: controller.value.aspectRatio,
|
|
child: VideoPlayer(controller),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_controllers.isEmpty) {
|
|
return const SizedBox(
|
|
height: 250,
|
|
child: Center(child: CircularProgressIndicator()),
|
|
);
|
|
}
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// const Text(
|
|
// "Videos",
|
|
// style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
|
|
// ),
|
|
// const SizedBox(height: 10),
|
|
|
|
SizedBox(
|
|
height: kIsWeb ? 340 : 240,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
PageView.builder(
|
|
controller: _pageController,
|
|
itemCount: _controllers.length,
|
|
onPageChanged: (index) {
|
|
setState(() {
|
|
_controllers[_currentIndex].pause();
|
|
_currentIndex = index;
|
|
});
|
|
},
|
|
itemBuilder: (context, index) {
|
|
final controller = _controllers[index];
|
|
|
|
return Center(
|
|
child: SizedBox(
|
|
width: kIsWeb ? 520 : double.infinity,
|
|
child: AspectRatio(
|
|
aspectRatio: controller.value.aspectRatio,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
/// 🎥 VIDEO
|
|
VideoPlayer(controller),
|
|
|
|
/// 🖼 COVER IMAGE
|
|
if (!controller.value.isPlaying)
|
|
// Positioned.fill(
|
|
// child: Image.network(
|
|
// coverImage,
|
|
// fit: BoxFit.cover,
|
|
// ),
|
|
// ),
|
|
|
|
/// ▶ PLAY / PAUSE
|
|
IconButton(
|
|
iconSize: 64,
|
|
icon: Icon(
|
|
controller.value.isPlaying
|
|
? Icons.pause_circle
|
|
: Icons.play_circle,
|
|
color: Colors.white,
|
|
),
|
|
onPressed: () {
|
|
setState(() {
|
|
controller.value.isPlaying
|
|
? controller.pause()
|
|
: controller.play();
|
|
});
|
|
},
|
|
),
|
|
|
|
/// 🎛 BOTTOM CONTROLS
|
|
Positioned(
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 10, vertical: 6),
|
|
color: Colors.black.withOpacity(0.55),
|
|
child: Row(
|
|
children: [
|
|
IconButton(
|
|
icon: const Icon(Icons.replay_10,
|
|
color: Colors.white),
|
|
onPressed: () =>
|
|
_seek(controller, -10),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.forward_10,
|
|
color: Colors.white),
|
|
onPressed: () =>
|
|
_seek(controller, 10),
|
|
),
|
|
IconButton(
|
|
icon: Icon(
|
|
_isMuted
|
|
? Icons.volume_off
|
|
: Icons.volume_up,
|
|
color: Colors.white,
|
|
),
|
|
onPressed: () =>
|
|
_toggleMute(controller),
|
|
),
|
|
Expanded(
|
|
child: VideoProgressIndicator(
|
|
controller,
|
|
allowScrubbing: true,
|
|
colors: VideoProgressColors(
|
|
playedColor:
|
|
const Color(0xFF593AFF),
|
|
),
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.fullscreen,
|
|
color: Colors.white),
|
|
onPressed: () =>
|
|
_openFullscreen(controller),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
|
|
/// ⬅ PREVIOUS (WEB)
|
|
if (kIsWeb)
|
|
Positioned(
|
|
left: 12,
|
|
child: _ArrowButton(
|
|
icon: Icons.chevron_left,
|
|
enabled: _currentIndex > 0,
|
|
onTap: _previous,
|
|
),
|
|
),
|
|
|
|
/// ➡ NEXT (WEB)
|
|
if (kIsWeb)
|
|
Positioned(
|
|
right: 12,
|
|
child: _ArrowButton(
|
|
icon: Icons.chevron_right,
|
|
enabled: _currentIndex < _controllers.length - 1,
|
|
onTap: _next,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
/// 🔘 DOT INDICATOR
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: List.generate(
|
|
_controllers.length,
|
|
(i) => AnimatedContainer(
|
|
duration: const Duration(milliseconds: 250),
|
|
margin: const EdgeInsets.symmetric(horizontal: 4),
|
|
height: 8,
|
|
width: _currentIndex == i ? 20 : 8,
|
|
decoration: BoxDecoration(
|
|
color: _currentIndex == i
|
|
? const Color(0xFF593AFF)
|
|
: Colors.grey.shade400,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 🔘 Arrow Button
|
|
class _ArrowButton extends StatelessWidget {
|
|
final IconData icon;
|
|
final bool enabled;
|
|
final VoidCallback onTap;
|
|
|
|
const _ArrowButton({
|
|
required this.icon,
|
|
required this.enabled,
|
|
required this.onTap,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return IgnorePointer(
|
|
ignoring: !enabled,
|
|
child: AnimatedOpacity(
|
|
duration: const Duration(milliseconds: 200),
|
|
opacity: enabled ? 1 : 0.3,
|
|
child: Material(
|
|
color: Colors.black.withOpacity(0.6),
|
|
shape: const CircleBorder(),
|
|
elevation: 4,
|
|
child: InkWell(
|
|
customBorder: const CircleBorder(),
|
|
onTap: onTap,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(10),
|
|
child: Icon(icon, size: 36, color: Colors.white),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
// import 'package:flutter/foundation.dart';
|
|
// import 'package:flutter/material.dart';
|
|
// import 'package:video_player/video_player.dart';
|
|
//
|
|
// class NhanceMultiVideoWrapper extends StatefulWidget {
|
|
// final List<String> videoUrls;
|
|
//
|
|
// const NhanceMultiVideoWrapper({
|
|
// super.key,
|
|
// required this.videoUrls,
|
|
// });
|
|
//
|
|
// @override
|
|
// State<NhanceMultiVideoWrapper> createState() =>
|
|
// _NhanceMultiVideoWrapperState();
|
|
// }
|
|
//
|
|
// class _NhanceMultiVideoWrapperState extends State<NhanceMultiVideoWrapper> {
|
|
// final PageController _pageController = PageController();
|
|
// int _currentIndex = 0;
|
|
//
|
|
// final List<VideoPlayerController> _controllers = [];
|
|
//
|
|
// @override
|
|
// void initState() {
|
|
// super.initState();
|
|
// _initControllers();
|
|
// }
|
|
//
|
|
// Future<void> _initControllers() async {
|
|
// for (final url in widget.videoUrls) {
|
|
// final controller = VideoPlayerController.networkUrl(Uri.parse(url));
|
|
// await controller.initialize();
|
|
// controller.setLooping(true);
|
|
// controller.setVolume(1);
|
|
// _controllers.add(controller);
|
|
// }
|
|
//
|
|
// if (mounted) setState(() {});
|
|
// }
|
|
//
|
|
// @override
|
|
// void dispose() {
|
|
// for (final c in _controllers) {
|
|
// c.dispose();
|
|
// }
|
|
// _pageController.dispose();
|
|
// super.dispose();
|
|
// }
|
|
//
|
|
// void _next() {
|
|
// if (_currentIndex < widget.videoUrls.length - 1) {
|
|
// _pageController.nextPage(
|
|
// duration: const Duration(milliseconds: 300),
|
|
// curve: Curves.easeInOut,
|
|
// );
|
|
// }
|
|
// }
|
|
//
|
|
// void _previous() {
|
|
// if (_currentIndex > 0) {
|
|
// _pageController.previousPage(
|
|
// duration: const Duration(milliseconds: 300),
|
|
// curve: Curves.easeInOut,
|
|
// );
|
|
// }
|
|
// }
|
|
//
|
|
// @override
|
|
// Widget build(BuildContext context) {
|
|
// if (_controllers.isEmpty) {
|
|
// return const SizedBox(
|
|
// height: 250,
|
|
// child: Center(child: CircularProgressIndicator()),
|
|
// );
|
|
// }
|
|
//
|
|
// return Column(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// children: [
|
|
// // Text(
|
|
// // "Videos",
|
|
// // style: Theme.of(context)
|
|
// // .textTheme
|
|
// // .titleMedium
|
|
// // ?.copyWith(fontWeight: FontWeight.w600),
|
|
// // ),
|
|
// // const SizedBox(height: 10),
|
|
//
|
|
// /// 🎥 VIDEO WITH WEB CONTROLS
|
|
// SizedBox(
|
|
// height: kIsWeb ? 320 : 220,
|
|
// child: Stack(
|
|
// alignment: Alignment.center,
|
|
// children: [
|
|
// PageView.builder(
|
|
// controller: _pageController,
|
|
// itemCount: _controllers.length,
|
|
// onPageChanged: (index) {
|
|
// setState(() {
|
|
// _controllers[_currentIndex].pause();
|
|
// _currentIndex = index;
|
|
// });
|
|
// },
|
|
// itemBuilder: (context, index) {
|
|
// final controller = _controllers[index];
|
|
//
|
|
// return Center(
|
|
// child: SizedBox(
|
|
// width: kIsWeb ? 520 : double.infinity,
|
|
// child: AspectRatio(
|
|
// aspectRatio: controller.value.aspectRatio,
|
|
// child: Stack(
|
|
// alignment: Alignment.center,
|
|
// children: [
|
|
// VideoPlayer(controller),
|
|
//
|
|
// IconButton(
|
|
// iconSize: 60,
|
|
// icon: Icon(
|
|
// controller.value.isPlaying
|
|
// ? Icons.pause_circle
|
|
// : Icons.play_circle,
|
|
// color: Colors.white,
|
|
// ),
|
|
// onPressed: () {
|
|
// setState(() {
|
|
// controller.value.isPlaying
|
|
// ? controller.pause()
|
|
// : controller.play();
|
|
// });
|
|
// },
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// );
|
|
// },
|
|
// ),
|
|
//
|
|
// /// ⬅ PREVIOUS (WEB ONLY)
|
|
// if (kIsWeb)
|
|
// Positioned(
|
|
// left: 16,
|
|
// child: _VideoArrowButton(
|
|
// icon: Icons.chevron_left,
|
|
// enabled: _currentIndex > 0,
|
|
// onTap: _previous,
|
|
// ),
|
|
// ),
|
|
//
|
|
//
|
|
// /// ➡ NEXT (WEB ONLY)
|
|
// if (kIsWeb)
|
|
// Positioned(
|
|
// right: 16,
|
|
// child: _VideoArrowButton(
|
|
// icon: Icons.chevron_right,
|
|
// enabled: _currentIndex < _controllers.length - 1,
|
|
// onTap: _next,
|
|
// ),
|
|
// ),
|
|
//
|
|
// ],
|
|
// ),
|
|
// ),
|
|
//
|
|
// const SizedBox(height: 8),
|
|
//
|
|
// /// 🔘 DOT INDICATOR
|
|
// Row(
|
|
// mainAxisAlignment: MainAxisAlignment.center,
|
|
// children: List.generate(
|
|
// _controllers.length,
|
|
// (index) => AnimatedContainer(
|
|
// duration: const Duration(milliseconds: 250),
|
|
// margin: const EdgeInsets.symmetric(horizontal: 4),
|
|
// height: 8,
|
|
// width: _currentIndex == index ? 20 : 8,
|
|
// decoration: BoxDecoration(
|
|
// color: _currentIndex == index
|
|
// ? const Color(0xFF593AFF)
|
|
// : Colors.grey.shade400,
|
|
// borderRadius: BorderRadius.circular(4),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// );
|
|
// }
|
|
//
|
|
//
|
|
// }
|
|
// class _VideoArrowButton extends StatelessWidget {
|
|
// final IconData icon;
|
|
// final bool enabled;
|
|
// final VoidCallback onTap;
|
|
//
|
|
// const _VideoArrowButton({
|
|
// required this.icon,
|
|
// required this.enabled,
|
|
// required this.onTap,
|
|
// });
|
|
//
|
|
// @override
|
|
// Widget build(BuildContext context) {
|
|
// return IgnorePointer(
|
|
// ignoring: !enabled,
|
|
// child: AnimatedOpacity(
|
|
// duration: const Duration(milliseconds: 200),
|
|
// opacity: enabled ? 1 : 0.3,
|
|
// child: Material(
|
|
// color: Colors.black.withOpacity(0.55),
|
|
// shape: const CircleBorder(),
|
|
// elevation: 4,
|
|
// child: InkWell(
|
|
// customBorder: const CircleBorder(),
|
|
// onTap: onTap,
|
|
// child: Padding(
|
|
// padding: const EdgeInsets.all(10),
|
|
// child: Icon(
|
|
// icon,
|
|
// size: 36,
|
|
// color: Colors.white,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// );
|
|
// }
|
|
// }
|