video_controls.dart 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. // @dart=2.9
  2. import 'dart:async';
  3. import 'package:chewie/chewie.dart';
  4. import 'package:chewie/src/material/material_progress_bar.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:photos/ente_theme_data.dart';
  7. import 'package:photos/utils/date_time_util.dart';
  8. import 'package:video_player/video_player.dart';
  9. class VideoControls extends StatefulWidget {
  10. const VideoControls({Key key}) : super(key: key);
  11. @override
  12. State<StatefulWidget> createState() {
  13. return _VideoControlsState();
  14. }
  15. }
  16. class _VideoControlsState extends State<VideoControls> {
  17. VideoPlayerValue _latestValue;
  18. bool _hideStuff = true;
  19. Timer _hideTimer;
  20. Timer _initTimer;
  21. Timer _showAfterExpandCollapseTimer;
  22. bool _dragging = false;
  23. bool _displayTapped = false;
  24. final barHeight = 120.0;
  25. final marginSize = 5.0;
  26. VideoPlayerController controller;
  27. ChewieController chewieController;
  28. @override
  29. Widget build(BuildContext context) {
  30. if (_latestValue.hasError) {
  31. return chewieController.errorBuilder != null
  32. ? chewieController.errorBuilder(
  33. context,
  34. chewieController.videoPlayerController.value.errorDescription,
  35. )
  36. : Center(
  37. child: Icon(
  38. Icons.error,
  39. color: Theme.of(context).colorScheme.onSurface,
  40. size: 42,
  41. ),
  42. );
  43. }
  44. return MouseRegion(
  45. onHover: (_) {
  46. _cancelAndRestartTimer();
  47. },
  48. child: GestureDetector(
  49. onTap: () => _cancelAndRestartTimer(),
  50. child: AbsorbPointer(
  51. absorbing: _hideStuff,
  52. child: Stack(
  53. children: <Widget>[
  54. Column(
  55. children: [
  56. _latestValue != null &&
  57. !_latestValue.isPlaying &&
  58. _latestValue.duration == null ||
  59. _latestValue.isBuffering
  60. ? const Center(
  61. child: CircularProgressIndicator(),
  62. )
  63. : _buildHitArea(),
  64. ],
  65. ),
  66. Align(
  67. alignment: Alignment.bottomCenter,
  68. child: _buildBottomBar(context),
  69. ),
  70. ],
  71. ),
  72. ),
  73. ),
  74. );
  75. }
  76. @override
  77. void dispose() {
  78. _dispose();
  79. super.dispose();
  80. }
  81. void _dispose() {
  82. controller.removeListener(_updateState);
  83. _hideTimer?.cancel();
  84. _initTimer?.cancel();
  85. _showAfterExpandCollapseTimer?.cancel();
  86. }
  87. @override
  88. void didChangeDependencies() {
  89. final oldController = chewieController;
  90. chewieController = ChewieController.of(context);
  91. controller = chewieController.videoPlayerController;
  92. if (oldController != chewieController) {
  93. _dispose();
  94. _initialize();
  95. }
  96. super.didChangeDependencies();
  97. }
  98. Widget _buildBottomBar(
  99. BuildContext context,
  100. ) {
  101. final iconColor = Theme.of(context).textTheme.button.color;
  102. return Container(
  103. padding: const EdgeInsets.only(bottom: 60),
  104. child: AnimatedOpacity(
  105. opacity: _hideStuff ? 0.0 : 1.0,
  106. duration: const Duration(milliseconds: 300),
  107. child: Container(
  108. height: barHeight,
  109. color: Colors.transparent,
  110. child: Row(
  111. children: <Widget>[
  112. _buildCurrentPosition(iconColor),
  113. chewieController.isLive ? const SizedBox() : _buildProgressBar(),
  114. _buildTotalDuration(iconColor),
  115. ],
  116. ),
  117. ),
  118. ),
  119. );
  120. }
  121. Expanded _buildHitArea() {
  122. return Expanded(
  123. child: GestureDetector(
  124. onTap: () {
  125. if (_latestValue != null) {
  126. if (_displayTapped) {
  127. setState(() {
  128. _hideStuff = true;
  129. });
  130. } else {
  131. _cancelAndRestartTimer();
  132. }
  133. } else {
  134. _playPause();
  135. setState(() {
  136. _hideStuff = true;
  137. });
  138. }
  139. },
  140. child: Container(
  141. color: Colors.transparent,
  142. child: Center(
  143. child: AnimatedOpacity(
  144. opacity:
  145. _latestValue != null && !_hideStuff && !_dragging ? 1.0 : 0.0,
  146. duration: const Duration(milliseconds: 300),
  147. child: GestureDetector(
  148. onTap: _playPause,
  149. child: Padding(
  150. padding: const EdgeInsets.all(12.0),
  151. child: Icon(
  152. _latestValue.isPlaying ? Icons.pause : Icons.play_arrow,
  153. color: Colors.white, // same for both themes
  154. size: 64.0,
  155. ),
  156. ),
  157. ),
  158. ),
  159. ),
  160. ),
  161. ),
  162. );
  163. }
  164. Widget _buildCurrentPosition(Color iconColor) {
  165. final position = _latestValue != null && _latestValue.position != null
  166. ? _latestValue.position
  167. : Duration.zero;
  168. return Container(
  169. margin: const EdgeInsets.only(left: 20.0, right: 16.0),
  170. child: Text(
  171. formatDuration(position),
  172. style: const TextStyle(
  173. fontSize: 12.0,
  174. color: Colors.white,
  175. ),
  176. ),
  177. );
  178. }
  179. Widget _buildTotalDuration(Color iconColor) {
  180. final duration = _latestValue != null && _latestValue.duration != null
  181. ? _latestValue.duration
  182. : Duration.zero;
  183. return Padding(
  184. padding: const EdgeInsets.only(right: 20.0),
  185. child: Text(
  186. formatDuration(duration),
  187. style: const TextStyle(
  188. fontSize: 12.0,
  189. color: Colors.white,
  190. ),
  191. ),
  192. );
  193. }
  194. void _cancelAndRestartTimer() {
  195. _hideTimer?.cancel();
  196. _startHideTimer();
  197. setState(() {
  198. _hideStuff = false;
  199. _displayTapped = true;
  200. });
  201. }
  202. Future<void> _initialize() async {
  203. controller.addListener(_updateState);
  204. _updateState();
  205. if ((controller.value != null && controller.value.isPlaying) ||
  206. chewieController.autoPlay) {
  207. _startHideTimer();
  208. }
  209. if (chewieController.showControlsOnInitialize) {
  210. _initTimer = Timer(const Duration(milliseconds: 200), () {
  211. setState(() {
  212. _hideStuff = false;
  213. });
  214. });
  215. }
  216. }
  217. void _playPause() {
  218. final bool isFinished = _latestValue.position >= _latestValue.duration;
  219. setState(() {
  220. if (controller.value.isPlaying) {
  221. _hideStuff = false;
  222. _hideTimer?.cancel();
  223. controller.pause();
  224. } else {
  225. _cancelAndRestartTimer();
  226. if (!controller.value.isInitialized) {
  227. controller.initialize().then((_) {
  228. controller.play();
  229. });
  230. } else {
  231. if (isFinished) {
  232. controller.seekTo(const Duration(seconds: 0));
  233. }
  234. controller.play();
  235. }
  236. }
  237. });
  238. }
  239. void _startHideTimer() {
  240. _hideTimer = Timer(const Duration(seconds: 2), () {
  241. setState(() {
  242. _hideStuff = true;
  243. });
  244. });
  245. }
  246. void _updateState() {
  247. setState(() {
  248. _latestValue = controller.value;
  249. });
  250. }
  251. Widget _buildProgressBar() {
  252. return Expanded(
  253. child: Padding(
  254. padding: const EdgeInsets.only(right: 16.0),
  255. child: MaterialVideoProgressBar(
  256. controller,
  257. onDragStart: () {
  258. setState(() {
  259. _dragging = true;
  260. });
  261. _hideTimer?.cancel();
  262. },
  263. onDragEnd: () {
  264. setState(() {
  265. _dragging = false;
  266. });
  267. _startHideTimer();
  268. },
  269. colors: chewieController.materialProgressColors ??
  270. ChewieProgressColors(
  271. playedColor: Theme.of(context).colorScheme.greenAlternative,
  272. handleColor: Colors.white,
  273. bufferedColor: Colors.white,
  274. backgroundColor: Theme.of(context).disabledColor,
  275. ),
  276. ),
  277. ),
  278. );
  279. }
  280. }