video_controls.dart 7.8 KB

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