video_controls.dart 7.7 KB

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