video_controls.dart 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  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:
  143. _latestValue != null && !_hideStuff && !_dragging ? 1.0 : 0.0,
  144. duration: Duration(milliseconds: 300),
  145. child: GestureDetector(
  146. onTap: _playPause,
  147. child: Padding(
  148. padding: EdgeInsets.all(12.0),
  149. child: Icon(
  150. _latestValue.isPlaying ? Icons.pause : Icons.play_arrow,
  151. color: Colors.white, // same for both themes
  152. size: 64.0,
  153. ),
  154. ),
  155. ),
  156. ),
  157. ),
  158. ),
  159. ),
  160. );
  161. }
  162. Widget _buildCurrentPosition(Color iconColor) {
  163. final position = _latestValue != null && _latestValue.position != null
  164. ? _latestValue.position
  165. : Duration.zero;
  166. return Container(
  167. margin: EdgeInsets.only(left: 20.0, right: 16.0),
  168. child: Text(
  169. formatDuration(position),
  170. style: TextStyle(
  171. fontSize: 12.0,
  172. color: Colors.white,
  173. ),
  174. ),
  175. );
  176. }
  177. Widget _buildTotalDuration(Color iconColor) {
  178. final duration = _latestValue != null && _latestValue.duration != null
  179. ? _latestValue.duration
  180. : Duration.zero;
  181. return Padding(
  182. padding: EdgeInsets.only(right: 20.0),
  183. child: Text(
  184. formatDuration(duration),
  185. style: TextStyle(
  186. fontSize: 12.0,
  187. color: Colors.white,
  188. ),
  189. ),
  190. );
  191. }
  192. void _cancelAndRestartTimer() {
  193. _hideTimer?.cancel();
  194. _startHideTimer();
  195. setState(() {
  196. _hideStuff = false;
  197. _displayTapped = true;
  198. });
  199. }
  200. Future<Null> _initialize() async {
  201. controller.addListener(_updateState);
  202. _updateState();
  203. if ((controller.value != null && controller.value.isPlaying) ||
  204. chewieController.autoPlay) {
  205. _startHideTimer();
  206. }
  207. if (chewieController.showControlsOnInitialize) {
  208. _initTimer = Timer(Duration(milliseconds: 200), () {
  209. setState(() {
  210. _hideStuff = false;
  211. });
  212. });
  213. }
  214. }
  215. void _playPause() {
  216. bool isFinished = _latestValue.position >= _latestValue.duration;
  217. setState(() {
  218. if (controller.value.isPlaying) {
  219. _hideStuff = false;
  220. _hideTimer?.cancel();
  221. controller.pause();
  222. } else {
  223. _cancelAndRestartTimer();
  224. if (!controller.value.isInitialized) {
  225. controller.initialize().then((_) {
  226. controller.play();
  227. });
  228. } else {
  229. if (isFinished) {
  230. controller.seekTo(Duration(seconds: 0));
  231. }
  232. controller.play();
  233. }
  234. }
  235. });
  236. }
  237. void _startHideTimer() {
  238. _hideTimer = Timer(const Duration(seconds: 2), () {
  239. setState(() {
  240. _hideStuff = true;
  241. });
  242. });
  243. }
  244. void _updateState() {
  245. setState(() {
  246. _latestValue = controller.value;
  247. });
  248. }
  249. Widget _buildProgressBar() {
  250. return Expanded(
  251. child: Padding(
  252. padding: EdgeInsets.only(right: 16.0),
  253. child: MaterialVideoProgressBar(
  254. controller,
  255. onDragStart: () {
  256. setState(() {
  257. _dragging = true;
  258. });
  259. _hideTimer?.cancel();
  260. },
  261. onDragEnd: () {
  262. setState(() {
  263. _dragging = false;
  264. });
  265. _startHideTimer();
  266. },
  267. colors: chewieController.materialProgressColors ??
  268. ChewieProgressColors(
  269. playedColor: Theme.of(context).buttonColor,
  270. handleColor: Colors.white,
  271. bufferedColor: Colors.white,
  272. backgroundColor: Theme.of(context).disabledColor,
  273. ),
  274. ),
  275. ),
  276. );
  277. }
  278. }