material_controls.dart 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. import 'dart:async';
  2. import 'package:chewie/src/chewie_player.dart';
  3. import 'package:chewie/src/chewie_progress_colors.dart';
  4. import 'package:chewie/src/material_progress_bar.dart';
  5. import 'package:chewie/src/utils.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:video_player/video_player.dart';
  8. class MaterialControls extends StatefulWidget {
  9. const MaterialControls({Key key}) : super(key: key);
  10. @override
  11. State<StatefulWidget> createState() {
  12. return _MaterialControlsState();
  13. }
  14. }
  15. class _MaterialControlsState extends State<MaterialControls> {
  16. VideoPlayerValue _latestValue;
  17. double _latestVolume;
  18. bool _hideStuff = true;
  19. Timer _hideTimer;
  20. Timer _showTimer;
  21. Timer _showAfterExpandCollapseTimer;
  22. bool _dragging = false;
  23. final barHeight = 48.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: Colors.white,
  39. size: 42,
  40. ),
  41. );
  42. }
  43. return GestureDetector(
  44. onTap: () => _cancelAndRestartTimer(),
  45. child: AbsorbPointer(
  46. absorbing: _hideStuff,
  47. child: Column(
  48. children: <Widget>[
  49. _latestValue != null &&
  50. !_latestValue.isPlaying &&
  51. _latestValue.duration == null ||
  52. _latestValue.isBuffering
  53. ? const Expanded(
  54. child: const Center(
  55. child: const CircularProgressIndicator(),
  56. ),
  57. )
  58. : _buildHitArea(),
  59. _buildBottomBar(context),
  60. ],
  61. ),
  62. ),
  63. );
  64. }
  65. @override
  66. void dispose() {
  67. _dispose();
  68. super.dispose();
  69. }
  70. void _dispose() {
  71. controller.removeListener(_updateState);
  72. _hideTimer?.cancel();
  73. _showTimer?.cancel();
  74. _showAfterExpandCollapseTimer?.cancel();
  75. }
  76. @override
  77. void didChangeDependencies() {
  78. final _oldController = chewieController;
  79. chewieController = ChewieController.of(context);
  80. controller = chewieController.videoPlayerController;
  81. if (_oldController != chewieController) {
  82. _dispose();
  83. _initialize();
  84. }
  85. super.didChangeDependencies();
  86. }
  87. AnimatedOpacity _buildBottomBar(
  88. BuildContext context,
  89. ) {
  90. final iconColor = Theme.of(context).textTheme.button.color;
  91. return AnimatedOpacity(
  92. opacity: _hideStuff ? 0.0 : 1.0,
  93. duration: Duration(milliseconds: 300),
  94. child: Container(
  95. height: barHeight,
  96. color: Theme.of(context).dialogBackgroundColor,
  97. child: Row(
  98. children: <Widget>[
  99. _buildPlayPause(controller),
  100. chewieController.isLive
  101. ? Expanded(child: const Text('LIVE'))
  102. : _buildPosition(iconColor),
  103. chewieController.isLive ? const SizedBox() : _buildProgressBar(),
  104. chewieController.allowMuting
  105. ? _buildMuteButton(controller)
  106. : Container(),
  107. chewieController.allowFullScreen
  108. ? _buildExpandButton()
  109. : Container(),
  110. ],
  111. ),
  112. ),
  113. );
  114. }
  115. GestureDetector _buildExpandButton() {
  116. return GestureDetector(
  117. onTap: _onExpandCollapse,
  118. child: AnimatedOpacity(
  119. opacity: _hideStuff ? 0.0 : 1.0,
  120. duration: Duration(milliseconds: 300),
  121. child: Container(
  122. height: barHeight,
  123. margin: EdgeInsets.only(right: 12.0),
  124. padding: EdgeInsets.only(
  125. left: 8.0,
  126. right: 8.0,
  127. ),
  128. child: Center(
  129. child: Icon(
  130. chewieController.isFullScreen
  131. ? Icons.fullscreen_exit
  132. : Icons.fullscreen,
  133. ),
  134. ),
  135. ),
  136. ),
  137. );
  138. }
  139. Expanded _buildHitArea() {
  140. return Expanded(
  141. child: GestureDetector(
  142. onTap: _latestValue != null && _latestValue.isPlaying
  143. ? _cancelAndRestartTimer
  144. : () {
  145. _playPause();
  146. setState(() {
  147. _hideStuff = true;
  148. });
  149. },
  150. child: Container(
  151. color: Colors.transparent,
  152. child: Center(
  153. child: AnimatedOpacity(
  154. opacity:
  155. _latestValue != null && !_latestValue.isPlaying && !_dragging
  156. ? 1.0
  157. : 0.0,
  158. duration: Duration(milliseconds: 300),
  159. child: GestureDetector(
  160. child: Container(
  161. decoration: BoxDecoration(
  162. color: Theme.of(context).dialogBackgroundColor,
  163. borderRadius: BorderRadius.circular(48.0),
  164. ),
  165. child: Padding(
  166. padding: EdgeInsets.all(12.0),
  167. child: Icon(Icons.play_arrow, size: 32.0),
  168. ),
  169. ),
  170. ),
  171. ),
  172. ),
  173. ),
  174. ),
  175. );
  176. }
  177. GestureDetector _buildMuteButton(
  178. VideoPlayerController controller,
  179. ) {
  180. return GestureDetector(
  181. onTap: () {
  182. _cancelAndRestartTimer();
  183. if (_latestValue.volume == 0) {
  184. controller.setVolume(_latestVolume ?? 0.5);
  185. } else {
  186. _latestVolume = controller.value.volume;
  187. controller.setVolume(0.0);
  188. }
  189. },
  190. child: AnimatedOpacity(
  191. opacity: _hideStuff ? 0.0 : 1.0,
  192. duration: Duration(milliseconds: 300),
  193. child: ClipRect(
  194. child: Container(
  195. child: Container(
  196. height: barHeight,
  197. padding: EdgeInsets.only(
  198. left: 8.0,
  199. right: 8.0,
  200. ),
  201. child: Icon(
  202. (_latestValue != null && _latestValue.volume > 0)
  203. ? Icons.volume_up
  204. : Icons.volume_off,
  205. ),
  206. ),
  207. ),
  208. ),
  209. ),
  210. );
  211. }
  212. GestureDetector _buildPlayPause(VideoPlayerController controller) {
  213. return GestureDetector(
  214. onTap: _playPause,
  215. child: Container(
  216. height: barHeight,
  217. color: Colors.transparent,
  218. margin: EdgeInsets.only(left: 8.0, right: 4.0),
  219. padding: EdgeInsets.only(
  220. left: 12.0,
  221. right: 12.0,
  222. ),
  223. child: Icon(
  224. controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
  225. ),
  226. ),
  227. );
  228. }
  229. Widget _buildPosition(Color iconColor) {
  230. final position = _latestValue != null && _latestValue.position != null
  231. ? _latestValue.position
  232. : Duration.zero;
  233. final duration = _latestValue != null && _latestValue.duration != null
  234. ? _latestValue.duration
  235. : Duration.zero;
  236. return Padding(
  237. padding: EdgeInsets.only(right: 24.0),
  238. child: Text(
  239. '${formatDuration(position)} / ${formatDuration(duration)}',
  240. style: TextStyle(
  241. fontSize: 14.0,
  242. ),
  243. ),
  244. );
  245. }
  246. void _cancelAndRestartTimer() {
  247. _hideTimer?.cancel();
  248. _startHideTimer();
  249. setState(() {
  250. _hideStuff = false;
  251. });
  252. }
  253. Future<Null> _initialize() async {
  254. controller.addListener(_updateState);
  255. _updateState();
  256. if ((controller.value != null && controller.value.isPlaying) ||
  257. chewieController.autoPlay) {
  258. _startHideTimer();
  259. }
  260. _showTimer = Timer(Duration(milliseconds: 200), () {
  261. setState(() {
  262. _hideStuff = false;
  263. });
  264. });
  265. }
  266. void _onExpandCollapse() {
  267. setState(() {
  268. _hideStuff = true;
  269. chewieController.toggleFullScreen();
  270. _showAfterExpandCollapseTimer = Timer(Duration(milliseconds: 300), () {
  271. setState(() {
  272. _cancelAndRestartTimer();
  273. });
  274. });
  275. });
  276. }
  277. void _playPause() {
  278. setState(() {
  279. if (controller.value.isPlaying) {
  280. _hideStuff = false;
  281. _hideTimer?.cancel();
  282. controller.pause();
  283. } else {
  284. _cancelAndRestartTimer();
  285. if (!controller.value.initialized) {
  286. controller.initialize().then((_) {
  287. controller.play();
  288. });
  289. } else {
  290. controller.play();
  291. }
  292. }
  293. });
  294. }
  295. void _startHideTimer() {
  296. _hideTimer = Timer(const Duration(seconds: 3), () {
  297. setState(() {
  298. _hideStuff = true;
  299. });
  300. });
  301. }
  302. void _updateState() {
  303. setState(() {
  304. _latestValue = controller.value;
  305. });
  306. }
  307. Widget _buildProgressBar() {
  308. return Expanded(
  309. child: Padding(
  310. padding: EdgeInsets.only(right: 20.0),
  311. child: MaterialVideoProgressBar(
  312. controller,
  313. onDragStart: () {
  314. setState(() {
  315. _dragging = true;
  316. });
  317. _hideTimer?.cancel();
  318. },
  319. onDragEnd: () {
  320. setState(() {
  321. _dragging = false;
  322. });
  323. _startHideTimer();
  324. },
  325. colors: chewieController.materialProgressColors ??
  326. ChewieProgressColors(
  327. playedColor: Theme.of(context).accentColor,
  328. handleColor: Theme.of(context).accentColor,
  329. bufferedColor: Theme.of(context).backgroundColor,
  330. backgroundColor: Theme.of(context).disabledColor),
  331. ),
  332. ),
  333. );
  334. }
  335. }