cupertino_controls.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. import 'dart:async';
  2. import 'dart:math' as math;
  3. import 'dart:ui';
  4. import 'package:chewie_example/cupertino_progress_bar.dart';
  5. import 'package:chewie_example/utils.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:meta/meta.dart';
  8. import 'package:open_iconic_flutter/open_iconic_flutter.dart';
  9. import 'package:video_player/video_player.dart';
  10. class CupertinoControls extends StatefulWidget {
  11. final Color backgroundColor;
  12. final Color iconColor;
  13. final VideoPlayerController controller;
  14. final Future<dynamic> Function() onExpandCollapse;
  15. final bool fullScreen;
  16. CupertinoControls({
  17. @required this.backgroundColor,
  18. @required this.iconColor,
  19. @required this.controller,
  20. @required this.onExpandCollapse,
  21. @required this.fullScreen,
  22. });
  23. @override
  24. State<StatefulWidget> createState() {
  25. return new _CupertinoControlsState();
  26. }
  27. }
  28. class _CupertinoControlsState extends State<CupertinoControls> {
  29. VideoPlayerValue _latestValue;
  30. double _latestVolume;
  31. bool _hideStuff = true;
  32. bool _disposed = false;
  33. Timer _hideTimer;
  34. final marginSize = 5.0;
  35. @override
  36. Widget build(BuildContext context) {
  37. final backgroundColor = widget.backgroundColor;
  38. final iconColor = widget.iconColor;
  39. final controller = widget.controller;
  40. final orientation = MediaQuery.of(context).orientation;
  41. final barHeight = orientation == Orientation.portrait ? 30.0 : 47.0;
  42. final buttonPadding = orientation == Orientation.portrait ? 16.0 : 24.0;
  43. return new Column(
  44. children: <Widget>[
  45. _buildTopBar(
  46. backgroundColor, iconColor, controller, barHeight, buttonPadding),
  47. _buildHitArea(),
  48. _buildBottomBar(backgroundColor, iconColor, controller, barHeight),
  49. ],
  50. );
  51. }
  52. @override
  53. void dispose() {
  54. widget.controller.removeListener(_updateState);
  55. _disposed = true;
  56. super.dispose();
  57. }
  58. @override
  59. void initState() {
  60. _initialize();
  61. super.initState();
  62. }
  63. AnimatedOpacity _buildBottomBar(
  64. Color backgroundColor,
  65. Color iconColor,
  66. VideoPlayerController controller,
  67. double barHeight,
  68. ) {
  69. return new AnimatedOpacity(
  70. opacity: _hideStuff ? 0.0 : 1.0,
  71. duration: new Duration(milliseconds: 300),
  72. child: new Container(
  73. color: Colors.transparent,
  74. alignment: Alignment.bottomCenter,
  75. margin: new EdgeInsets.all(marginSize),
  76. child: new ClipRect(
  77. child: new BackdropFilter(
  78. filter: new ImageFilter.blur(
  79. sigmaX: 10.0,
  80. sigmaY: 10.0,
  81. ),
  82. child: new Container(
  83. height: barHeight,
  84. decoration: new BoxDecoration(
  85. color: backgroundColor,
  86. borderRadius: new BorderRadius.all(
  87. new Radius.circular(10.0),
  88. ),
  89. ),
  90. child: new Row(
  91. children: <Widget>[
  92. _buildSkipBack(iconColor, barHeight),
  93. _buildPlayPause(controller, iconColor, barHeight),
  94. _buildSkipForward(iconColor, barHeight),
  95. _buildPosition(iconColor),
  96. _buildProgressBar(),
  97. _buildRemaining(iconColor)
  98. ],
  99. ),
  100. ),
  101. ),
  102. ),
  103. ),
  104. );
  105. }
  106. GestureDetector _buildExpandButton(
  107. Color backgroundColor,
  108. Color iconColor,
  109. double barHeight,
  110. double buttonPadding,
  111. ) {
  112. return new GestureDetector(
  113. onTap: _onExpandCollapse,
  114. child: new AnimatedOpacity(
  115. opacity: _hideStuff ? 0.0 : 1.0,
  116. duration: new Duration(milliseconds: 300),
  117. child: new ClipRect(
  118. child: new BackdropFilter(
  119. filter: new ImageFilter.blur(sigmaX: 10.0),
  120. child: new Container(
  121. height: barHeight,
  122. padding: new EdgeInsets.only(
  123. left: buttonPadding,
  124. right: buttonPadding,
  125. ),
  126. decoration: new BoxDecoration(
  127. color: backgroundColor,
  128. borderRadius: new BorderRadius.all(
  129. new Radius.circular(10.0),
  130. ),
  131. ),
  132. child: new Center(
  133. child: new Icon(
  134. widget.fullScreen
  135. ? OpenIconicIcons.fullscreenExit
  136. : OpenIconicIcons.fullscreenEnter,
  137. color: iconColor,
  138. size: 12.0,
  139. ),
  140. ),
  141. ),
  142. ),
  143. ),
  144. ),
  145. );
  146. }
  147. Expanded _buildHitArea() {
  148. return new Expanded(
  149. child: new GestureDetector(
  150. onTap: _latestValue != null && _latestValue.isPlaying
  151. ? _cancelAndRestartTimer
  152. : () {
  153. _hideTimer?.cancel();
  154. setState(() {
  155. _hideStuff = false;
  156. });
  157. },
  158. child: new Container(
  159. color: Colors.transparent,
  160. ),
  161. ),
  162. );
  163. }
  164. GestureDetector _buildMuteButton(
  165. VideoPlayerController controller,
  166. Color backgroundColor,
  167. Color iconColor,
  168. double barHeight,
  169. double buttonPadding,
  170. ) {
  171. return new GestureDetector(
  172. onTap: () {
  173. _cancelAndRestartTimer();
  174. if (_latestValue.volume == 0) {
  175. controller.setVolume(_latestVolume ?? 0.5);
  176. } else {
  177. _latestVolume = controller.value.volume;
  178. controller.setVolume(0.0);
  179. }
  180. },
  181. child: new AnimatedOpacity(
  182. opacity: _hideStuff ? 0.0 : 1.0,
  183. duration: new Duration(milliseconds: 300),
  184. child: new ClipRect(
  185. child: new BackdropFilter(
  186. filter: new ImageFilter.blur(sigmaX: 10.0),
  187. child: new Container(
  188. decoration: new BoxDecoration(
  189. color: backgroundColor,
  190. borderRadius: new BorderRadius.all(
  191. new Radius.circular(10.0),
  192. ),
  193. ),
  194. child: new Container(
  195. height: barHeight,
  196. padding: new EdgeInsets.only(
  197. left: buttonPadding,
  198. right: buttonPadding,
  199. ),
  200. child: new Icon(
  201. (_latestValue != null && _latestValue.volume > 0)
  202. ? Icons.volume_up
  203. : Icons.volume_off,
  204. color: iconColor,
  205. size: 16.0,
  206. ),
  207. ),
  208. ),
  209. ),
  210. ),
  211. ),
  212. );
  213. }
  214. GestureDetector _buildPlayPause(
  215. VideoPlayerController controller,
  216. Color iconColor,
  217. double barHeight,
  218. ) {
  219. return new GestureDetector(
  220. onTap: _playPause,
  221. child: new Container(
  222. height: barHeight,
  223. color: Colors.transparent,
  224. padding: new EdgeInsets.only(
  225. left: 6.0,
  226. right: 6.0,
  227. ),
  228. child: new Icon(
  229. controller.value.isPlaying
  230. ? OpenIconicIcons.mediaPause
  231. : OpenIconicIcons.mediaPlay,
  232. color: iconColor,
  233. size: 16.0,
  234. ),
  235. ),
  236. );
  237. }
  238. Widget _buildPosition(Color iconColor) {
  239. final position =
  240. _latestValue != null ? _latestValue.position : new Duration(seconds: 0);
  241. return new Padding(
  242. padding: new EdgeInsets.only(right: 12.0),
  243. child: new Text(
  244. formatDuration(position),
  245. style: new TextStyle(
  246. color: iconColor,
  247. fontSize: 12.0,
  248. ),
  249. ),
  250. );
  251. }
  252. Widget _buildRemaining(Color iconColor) {
  253. final position = _latestValue != null && _latestValue.duration != null
  254. ? _latestValue.duration - _latestValue.position
  255. : new Duration(seconds: 0);
  256. return new Padding(
  257. padding: new EdgeInsets.only(right: 12.0),
  258. child: new Text(
  259. '-${formatDuration(position)}',
  260. style: new TextStyle(color: iconColor, fontSize: 12.0),
  261. ),
  262. );
  263. }
  264. GestureDetector _buildSkipBack(Color iconColor, double barHeight) {
  265. return new GestureDetector(
  266. onTap: _skipBack,
  267. child: new Container(
  268. height: barHeight,
  269. color: Colors.transparent,
  270. margin: new EdgeInsets.only(left: 10.0),
  271. padding: new EdgeInsets.only(
  272. left: 6.0,
  273. right: 6.0,
  274. ),
  275. child: new Transform(
  276. alignment: Alignment.center,
  277. transform: new Matrix4.skewY(0.0)
  278. ..rotateX(math.pi)
  279. ..rotateZ(math.pi),
  280. child: new Icon(
  281. OpenIconicIcons.reload,
  282. color: iconColor,
  283. size: 12.0,
  284. ),
  285. ),
  286. ),
  287. );
  288. }
  289. GestureDetector _buildSkipForward(Color iconColor, double barHeight) {
  290. return new GestureDetector(
  291. onTap: _skipForward,
  292. child: new Container(
  293. height: barHeight,
  294. color: Colors.transparent,
  295. padding: new EdgeInsets.only(
  296. left: 6.0,
  297. right: 8.0,
  298. ),
  299. margin: new EdgeInsets.only(
  300. right: 8.0,
  301. ),
  302. child: new Icon(
  303. OpenIconicIcons.reload,
  304. color: iconColor,
  305. size: 12.0,
  306. ),
  307. ),
  308. );
  309. }
  310. Widget _buildTopBar(
  311. Color backgroundColor,
  312. Color iconColor,
  313. VideoPlayerController controller,
  314. double barHeight,
  315. double buttonPadding,
  316. ) {
  317. return new Container(
  318. height: barHeight,
  319. margin: new EdgeInsets.only(
  320. top: marginSize,
  321. right: marginSize,
  322. left: marginSize,
  323. ),
  324. child: new Row(
  325. children: <Widget>[
  326. _buildExpandButton(
  327. backgroundColor, iconColor, barHeight, buttonPadding),
  328. new Expanded(child: new Container()),
  329. _buildMuteButton(
  330. controller, backgroundColor, iconColor, barHeight, buttonPadding),
  331. ],
  332. ),
  333. );
  334. }
  335. _cancelAndRestartTimer() {
  336. _hideTimer?.cancel();
  337. setState(() {
  338. _hideStuff = false;
  339. _startHideTimer();
  340. });
  341. }
  342. Future<Null> _initialize() async {
  343. widget.controller.addListener(_updateState);
  344. _updateState();
  345. _startHideTimer();
  346. new Timer(new Duration(milliseconds: 200), () {
  347. setState(() {
  348. _hideStuff = false;
  349. });
  350. });
  351. }
  352. void _onExpandCollapse() {
  353. setState(() {
  354. _hideStuff = true;
  355. widget.onExpandCollapse().then((_) {
  356. new Timer(new Duration(milliseconds: 300), () {
  357. if (!_disposed) {
  358. setState(() {
  359. _cancelAndRestartTimer();
  360. });
  361. }
  362. });
  363. });
  364. });
  365. }
  366. Widget _buildProgressBar() {
  367. return new Expanded(
  368. child: new Padding(
  369. padding: new EdgeInsets.only(right: 12.0),
  370. child: new CupertinoVideoProgressBar(
  371. widget.controller,
  372. onDragStart: () {
  373. _hideTimer?.cancel();
  374. },
  375. onDragEnd: () {
  376. _startHideTimer();
  377. },
  378. colors: new VideoProgressColors(
  379. playedColor: new Color.fromARGB(
  380. 120,
  381. 255,
  382. 255,
  383. 255,
  384. ),
  385. handleColor: new Color.fromARGB(
  386. 255,
  387. 255,
  388. 255,
  389. 255,
  390. ),
  391. bufferedColor: new Color.fromARGB(
  392. 60,
  393. 255,
  394. 255,
  395. 255,
  396. ),
  397. disabledColor: new Color.fromARGB(
  398. 20,
  399. 255,
  400. 255,
  401. 255,
  402. ),
  403. ),
  404. ),
  405. ),
  406. );
  407. }
  408. void _playPause() {
  409. setState(() {
  410. if (widget.controller.value.isPlaying) {
  411. _hideStuff = false;
  412. _hideTimer?.cancel();
  413. widget.controller.pause();
  414. } else {
  415. _cancelAndRestartTimer();
  416. widget.controller.play();
  417. }
  418. });
  419. }
  420. void _skipBack() {
  421. final beginning = new Duration(seconds: 0).inMicroseconds;
  422. final skip =
  423. (_latestValue.position - new Duration(seconds: 15)).inMicroseconds;
  424. widget.controller
  425. .seekTo(new Duration(milliseconds: math.max(skip, beginning)));
  426. }
  427. void _skipForward() {
  428. final end = _latestValue.duration.inMicroseconds;
  429. final skip =
  430. (_latestValue.position + new Duration(seconds: 15)).inMicroseconds;
  431. widget.controller.seekTo(new Duration(milliseconds: math.min(skip, end)));
  432. }
  433. void _startHideTimer() {
  434. _hideTimer = new Timer(const Duration(seconds: 3), () {
  435. if (!_disposed) {
  436. setState(() {
  437. _hideStuff = true;
  438. });
  439. }
  440. });
  441. }
  442. void _updateState() {
  443. setState(() {
  444. _latestValue = widget.controller.value;
  445. });
  446. }
  447. }