video_controls.dart 7.6 KB

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