I list videos from api on my page in Flutter.I used the PreloadPageView ‘preload_page_view’ package to list the videos.The videos are in mp4 format. Video sizes average between 100-200 mb. I make the api request on the previous page before going to this page and the data comes immediately. But on the screen where I show the videos, the videos can take a long time to load. What method should I follow for this?
@RoutePage()
class _VideoScrollViewState extends State<VideoScrollView> {
late final List<String> videoUrls;
final List<VideoPlayerController> _controllers = [];
final Map<int, ValueNotifier<DurationState>> _durationNotifiers = {};
late final PreloadPageController _pageController;
int _currentPage = 0;
Timer? _overlayTimer;
bool _isOverlayVisible = true;
@override
void initState() {
super.initState();
_pageController = PreloadPageController();
videoUrls = widget.state.episodesPageResponseModel.results
?.map((result) => result.videoFile ?? '')
.where((url) => url.isNotEmpty)
.toList() ??
[];
_initializeVideoControllers();
}
@override
void dispose() {
for (final controller in _controllers) {
controller.dispose();
}
_pageController.dispose();
_overlayTimer?.cancel();
super.dispose();
}
void _initializeVideoControllers() {
for (var i = 0; i < videoUrls.length; i++) {
final controller = VideoPlayerController.network(videoUrls[i]);
controller.initialize().timeout(
const Duration(seconds: 60),
onTimeout: () {
debugPrint("Video yükleme zaman aşımına uğradı: ${videoUrls[i]}");
throw Exception("Video yükleme zaman aşımı.");
},
).then((_) {
setState(() {});
}).catchError((error) {
debugPrint("Video yükleme hatası: $error");
_showSnackBar('Video yükleme hatası.');
});
_controllers.add(controller);
_durationNotifiers[i] = ValueNotifier(
const DurationState(
progress: Duration.zero,
buffered: Duration.zero,
total: Duration.zero,
),
);
controller.addListener(() {
if (controller.value.isInitialized) {
_durationNotifiers[i]!.value = DurationState(
progress: controller.value.position,
buffered: controller.value.buffered.isNotEmpty
? controller.value.buffered.last.end
: Duration.zero,
total: controller.value.duration,
);
if (controller.value.position >= controller.value.duration) {
_onVideoEnd(i);
}
}
});
}
if (_controllers.isNotEmpty) {
_controllers[_currentPage].play();
_startOverlayTimer();
}
}
void _startOverlayTimer() {
_overlayTimer?.cancel();
_overlayTimer = Timer(const Duration(seconds: 3), () {
setState(() {
_isOverlayVisible = false;
});
});
}
void _toggleOverlayVisibility() {
setState(() {
_isOverlayVisible = !_isOverlayVisible;
});
if (_isOverlayVisible && _controllers[_currentPage].value.isPlaying) {
_startOverlayTimer();
} else {
_overlayTimer?.cancel();
}
}
void _onPageChanged(int index) {
setState(() {
_controllers[_currentPage].pause();
_currentPage = index;
_controllers[_currentPage].play();
});
}
void _onVideoEnd(int currentIndex) {
if (currentIndex + 1 < _controllers.length) {
_controllers[currentIndex].pause();
_pageController.animateToPage(
currentIndex + 1,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
_controllers[currentIndex + 1].play();
setState(() {
_currentPage = currentIndex + 1;
});
} else {
debugPrint("All videos completed.");
}
}
void _togglePlayPause() {
final controller = _controllers[_currentPage];
setState(() {
if (controller.value.isPlaying) {
controller.pause();
_isOverlayVisible = true;
_overlayTimer?.cancel();
} else {
controller.play();
_isOverlayVisible = false;
_startOverlayTimer();
}
});
}
void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
@override
Widget build(BuildContext context) {
if (videoUrls.isEmpty) {
return const Scaffold(
body: Center(
child: Text(
'No videos found',
style: TextStyle(color: Colors.white),
),
),
);
}
return Scaffold(
backgroundColor: Colors.black,
body: PreloadPageView.builder(
scrollDirection: Axis.vertical,
controller: _pageController,
onPageChanged: _onPageChanged,
itemCount: videoUrls.length,
itemBuilder: (context, index) {
final controller = _controllers[index];
final durationNotifier = _durationNotifiers[index];
final videoData =
widget.state.episodesPageResponseModel.results?[index];
return GestureDetector(
onTap: _toggleOverlayVisibility,
child: Stack(
children: [
if (controller.value.isInitialized)
SizedBox(
width: double.infinity,
height: double.infinity,
child: AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: VideoPlayer(controller),
),
)
else
const Center(
child: CircularProgressIndicator(),
),
if (_isOverlayVisible)
Center(
child: GestureDetector(
onTap: _togglePlayPause,
child: Icon(
controller.value.isPlaying
? Icons.pause
: Icons.play_arrow,
size: 64,
color: Colors.white,
),
),
),
if (durationNotifier != null)
Positioned(
bottom: 20,
left: 20,
right: 20,
child: ValueListenableBuilder<DurationState>(
valueListenable: durationNotifier!,
builder: (context, value, child) {
// Eğer total süre 0 ise progress değeri 0.0 olur
final progressValue = value.total.inMilliseconds > 0
? value.progress.inMilliseconds /
value.total.inMilliseconds
: 0.0;
return Column(
children: [
LinearProgressIndicator(
value:
progressValue, // NaN veya Infinity önleniyor
color: Colors.red,
backgroundColor: Colors.grey,
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_formatDuration(value.progress),
style: const TextStyle(color: Colors.white),
),
Text(
_formatDuration(value.total),
style: const TextStyle(color: Colors.white),
),
],
),
],
);
},
),
),
],
),
);
},
),
);
}
String _formatDuration(Duration duration) {
final minutes = duration.inMinutes.remainder(60);
final seconds = duration.inSeconds.remainder(60);
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}
}