forked from Cutlery/immich
		
	* Remove toggle fullscreen button * Implement custom video player controls * Move Padding into Container
		
			
				
	
	
		
			208 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			208 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:async';
 | |
| 
 | |
| import 'package:chewie/chewie.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:hooks_riverpod/hooks_riverpod.dart';
 | |
| import 'package:immich_mobile/modules/asset_viewer/providers/show_controls.provider.dart';
 | |
| import 'package:immich_mobile/modules/asset_viewer/providers/video_player_controls_provider.dart';
 | |
| import 'package:immich_mobile/modules/asset_viewer/providers/video_player_value_provider.dart';
 | |
| import 'package:immich_mobile/modules/asset_viewer/ui/center_play_button.dart';
 | |
| import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
 | |
| import 'package:video_player/video_player.dart';
 | |
| 
 | |
| class VideoPlayerControls extends ConsumerStatefulWidget {
 | |
|   const VideoPlayerControls({
 | |
|     Key? key,
 | |
|   }) : super(key: key);
 | |
| 
 | |
|   @override
 | |
|   VideoPlayerControlsState createState() => VideoPlayerControlsState();
 | |
| }
 | |
| 
 | |
| class VideoPlayerControlsState extends ConsumerState<VideoPlayerControls>
 | |
|     with SingleTickerProviderStateMixin {
 | |
|   late VideoPlayerController controller;
 | |
|   late VideoPlayerValue _latestValue;
 | |
|   bool _displayBufferingIndicator = false;
 | |
|   double? _latestVolume;
 | |
|   Timer? _hideTimer;
 | |
| 
 | |
|   ChewieController? _chewieController;
 | |
|   ChewieController get chewieController => _chewieController!;
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     ref.listen(videoPlayerControlsProvider.select((value) => value.mute),
 | |
|         (_, value) {
 | |
|       _mute(value);
 | |
|       _cancelAndRestartTimer();
 | |
|     });
 | |
| 
 | |
|     ref.listen(videoPlayerControlsProvider.select((value) => value.position),
 | |
|         (_, position) {
 | |
|       _seekTo(position);
 | |
|       _cancelAndRestartTimer();
 | |
|     });
 | |
| 
 | |
|     if (_latestValue.hasError) {
 | |
|       return chewieController.errorBuilder?.call(
 | |
|             context,
 | |
|             chewieController.videoPlayerController.value.errorDescription!,
 | |
|           ) ??
 | |
|           const Center(
 | |
|             child: Icon(
 | |
|               Icons.error,
 | |
|               color: Colors.white,
 | |
|               size: 42,
 | |
|             ),
 | |
|           );
 | |
|     }
 | |
| 
 | |
|     return GestureDetector(
 | |
|       onTap: () => _cancelAndRestartTimer(),
 | |
|       child: AbsorbPointer(
 | |
|         absorbing: !ref.watch(showControlsProvider),
 | |
|         child: Stack(
 | |
|           children: [
 | |
|             if (_displayBufferingIndicator)
 | |
|               const Center(
 | |
|                 child: ImmichLoadingIndicator(),
 | |
|               )
 | |
|             else
 | |
|               _buildHitArea(),
 | |
|           ],
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void dispose() {
 | |
|     _dispose();
 | |
|     super.dispose();
 | |
|   }
 | |
| 
 | |
|   void _dispose() {
 | |
|     controller.removeListener(_updateState);
 | |
|     _hideTimer?.cancel();
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void didChangeDependencies() {
 | |
|     final oldController = _chewieController;
 | |
|     _chewieController = ChewieController.of(context);
 | |
|     controller = chewieController.videoPlayerController;
 | |
| 
 | |
|     if (oldController != chewieController) {
 | |
|       _dispose();
 | |
|       _initialize();
 | |
|     }
 | |
| 
 | |
|     super.didChangeDependencies();
 | |
|   }
 | |
| 
 | |
|   Widget _buildHitArea() {
 | |
|     final bool isFinished = _latestValue.position >= _latestValue.duration;
 | |
| 
 | |
|     return GestureDetector(
 | |
|       onTap: () {
 | |
|         if (_latestValue.isPlaying) {
 | |
|           ref.read(showControlsProvider.notifier).show = false;
 | |
|         } else {
 | |
|           _playPause();
 | |
|           ref.read(showControlsProvider.notifier).show = false;
 | |
|         }
 | |
|       },
 | |
|       child: CenterPlayButton(
 | |
|         backgroundColor: Colors.black54,
 | |
|         iconColor: Colors.white,
 | |
|         isFinished: isFinished,
 | |
|         isPlaying: controller.value.isPlaying,
 | |
|         show: ref.watch(showControlsProvider),
 | |
|         onPressed: _playPause,
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   void _cancelAndRestartTimer() {
 | |
|     _hideTimer?.cancel();
 | |
|     _startHideTimer();
 | |
|     ref.read(showControlsProvider.notifier).show = true;
 | |
|   }
 | |
| 
 | |
|   Future<void> _initialize() async {
 | |
|     _mute(ref.read(videoPlayerControlsProvider.select((value) => value.mute)));
 | |
| 
 | |
|     controller.addListener(_updateState);
 | |
|     _latestValue = controller.value;
 | |
| 
 | |
|     if (controller.value.isPlaying || chewieController.autoPlay) {
 | |
|       _startHideTimer();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void _playPause() {
 | |
|     final isFinished = _latestValue.position >= _latestValue.duration;
 | |
| 
 | |
|     setState(() {
 | |
|       if (controller.value.isPlaying) {
 | |
|         ref.read(showControlsProvider.notifier).show = true;
 | |
|         _hideTimer?.cancel();
 | |
|         controller.pause();
 | |
|       } else {
 | |
|         _cancelAndRestartTimer();
 | |
| 
 | |
|         if (!controller.value.isInitialized) {
 | |
|           controller.initialize().then((_) {
 | |
|             controller.play();
 | |
|           });
 | |
|         } else {
 | |
|           if (isFinished) {
 | |
|             controller.seekTo(Duration.zero);
 | |
|           }
 | |
|           controller.play();
 | |
|         }
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   void _startHideTimer() {
 | |
|     final hideControlsTimer = chewieController.hideControlsTimer.isNegative
 | |
|         ? ChewieController.defaultHideControlsTimer
 | |
|         : chewieController.hideControlsTimer;
 | |
|     _hideTimer = Timer(hideControlsTimer, () {
 | |
|       ref.read(showControlsProvider.notifier).show = false;
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   void _updateState() {
 | |
|     if (!mounted) return;
 | |
| 
 | |
|     _displayBufferingIndicator = controller.value.isBuffering;
 | |
| 
 | |
|     setState(() {
 | |
|       _latestValue = controller.value;
 | |
|       ref.read(videoPlaybackValueProvider.notifier).value = VideoPlaybackValue(
 | |
|         position: _latestValue.position,
 | |
|         duration: _latestValue.duration,
 | |
|       );
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   void _mute(bool mute) {
 | |
|     if (mute) {
 | |
|       _latestVolume = controller.value.volume;
 | |
|       controller.setVolume(0);
 | |
|     } else {
 | |
|       controller.setVolume(_latestVolume ?? 0.5);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void _seekTo(double position) {
 | |
|     final Duration pos = controller.value.duration * (position / 100.0);
 | |
|     if (pos != controller.value.position) {
 | |
|       controller.seekTo(pos);
 | |
|     }
 | |
|   }
 | |
| }
 |