main.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. import 'dart:async';
  2. import 'dart:ui';
  3. import 'dart:math' as math;
  4. import 'package:flutter/cupertino.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter/services.dart';
  7. import 'package:video_player/video_player.dart';
  8. import 'package:open_iconic_flutter/open_iconic_flutter.dart';
  9. const String butterflyUri =
  10. 'https://flutter.github.io/assets-for-api-docs/videos/butterfly.mp4';
  11. void main() => runApp(new MyApp());
  12. class MyApp extends StatelessWidget {
  13. // This widget is the root of your application.
  14. @override
  15. Widget build(BuildContext context) {
  16. return new MaterialApp(
  17. title: 'Chewie Demo',
  18. theme: new ThemeData(
  19. primarySwatch: Colors.blue,
  20. ),
  21. home: new MyHomePage(title: 'Chewie Controls Demo'),
  22. );
  23. }
  24. }
  25. class MyHomePage extends StatefulWidget {
  26. MyHomePage({Key key, this.title}) : super(key: key);
  27. final String title;
  28. @override
  29. _MyHomePageState createState() => new _MyHomePageState();
  30. }
  31. class _MyHomePageState extends State<MyHomePage> {
  32. final controller = new VideoPlayerController(
  33. butterflyUri,
  34. );
  35. bool _enableFrame;
  36. @override
  37. void initState() {
  38. _initialize();
  39. super.initState();
  40. }
  41. Widget fullScreenRoutePageBuilder(BuildContext context,
  42. Animation<double> animation, Animation<double> secondaryAnimation) {
  43. animation.addListener(() {
  44. if (animation.isCompleted) {
  45. new Timer(new Duration(milliseconds: 300), () {
  46. SystemChrome.setEnabledSystemUIOverlays([]);
  47. });
  48. }
  49. });
  50. return new AnimatedBuilder(
  51. animation: animation,
  52. builder: (BuildContext context, Widget child) {
  53. return _buildFullScreenVideo(context, animation);
  54. },
  55. );
  56. }
  57. Future<dynamic> pushFullScreenWidget(BuildContext context) {
  58. final TransitionRoute<Null> route = new PageRouteBuilder<Null>(
  59. settings: new RouteSettings(isInitialRoute: false),
  60. pageBuilder: fullScreenRoutePageBuilder,
  61. );
  62. _enableFrame = false;
  63. return Navigator.of(context).push(route).then((_) {
  64. new Timer(new Duration(milliseconds: 800), () {
  65. SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
  66. });
  67. _enableFrame = false;
  68. });
  69. }
  70. @override
  71. Widget build(BuildContext context) {
  72. return new Scaffold(
  73. appBar: new AppBar(
  74. title: new Text(widget.title),
  75. ),
  76. body: new VideoPlayerWithControls(controller, () {
  77. return pushFullScreenWidget(context);
  78. }),
  79. );
  80. }
  81. _buildFullScreenVideo(BuildContext context, Animation<double> animation) {
  82. return new Scaffold(
  83. resizeToAvoidBottomPadding: false,
  84. body: new Container(
  85. color: Colors.black,
  86. child: new VideoPlayerWithControls(controller, () {
  87. return new Future.value(Navigator.of(context).pop());
  88. }),
  89. ),
  90. );
  91. }
  92. Future _initialize() async {
  93. await controller.setLooping(true);
  94. await controller.initialize();
  95. await controller.play();
  96. }
  97. }
  98. class ProgressBar extends StatelessWidget {
  99. final VideoPlayerController controller;
  100. ProgressBar(this.controller);
  101. @override
  102. Widget build(BuildContext context) {
  103. return new Expanded(
  104. child: new Padding(
  105. padding: new EdgeInsets.only(right: 12.0),
  106. child: new ClipRRect(
  107. borderRadius: new BorderRadius.all(new Radius.circular(10.0)),
  108. child: new Container(
  109. height: 5.0,
  110. child: new VideoProgressBar(
  111. controller,
  112. colors: new VideoProgressColors(
  113. playedColor: new Color.fromARGB(
  114. 255,
  115. 255,
  116. 255,
  117. 255,
  118. ),
  119. handleColor: new Color.fromARGB(
  120. 255,
  121. 255,
  122. 255,
  123. 255,
  124. ),
  125. bufferedColor: new Color.fromARGB(
  126. 140,
  127. 255,
  128. 255,
  129. 255,
  130. ),
  131. ),
  132. ),
  133. ),
  134. ),
  135. ),
  136. );
  137. }
  138. }
  139. class VideoPlayerWithControls extends StatefulWidget {
  140. final VideoPlayerController controller;
  141. final Future<dynamic> Function() onExpandCollapse;
  142. final Duration hideDuration;
  143. VideoPlayerWithControls(this.controller, this.onExpandCollapse,
  144. {this.hideDuration: const Duration(seconds: 3)});
  145. @override
  146. State createState() {
  147. return new _VideoPlayerWithControlsState();
  148. }
  149. }
  150. class _VideoPlayerWithControlsState extends State<VideoPlayerWithControls> {
  151. VideoPlayerValue _latestValue;
  152. double _latestVolume;
  153. bool _hideStuff = true;
  154. bool _disposed = false;
  155. Timer _hideTimer;
  156. void _updateState() {
  157. setState(() {
  158. _latestValue = widget.controller.value;
  159. });
  160. }
  161. Future<Null> _initialize() async {
  162. widget.controller.addListener(_updateState);
  163. _updateState();
  164. _startHideTimer();
  165. new Timer(new Duration(milliseconds: 200), () {
  166. setState(() {
  167. _hideStuff = false;
  168. });
  169. });
  170. }
  171. void _startHideTimer() {
  172. _hideTimer = new Timer(widget.hideDuration, () {
  173. if (!_disposed) {
  174. setState(() {
  175. _hideStuff = true;
  176. });
  177. }
  178. });
  179. }
  180. @override
  181. void dispose() {
  182. widget.controller.removeListener(_updateState);
  183. _disposed = true;
  184. super.dispose();
  185. }
  186. @override
  187. void initState() {
  188. _initialize();
  189. super.initState();
  190. }
  191. @override
  192. Widget build(BuildContext context) {
  193. final iconColor = new Color.fromARGB(255, 200, 200, 200);
  194. final backgroundColor = new Color.fromRGBO(41, 41, 41, 0.7);
  195. final controller = widget.controller;
  196. return new Center(
  197. child: new Container(
  198. width: MediaQuery.of(context).size.width,
  199. child: new AspectRatio(
  200. aspectRatio: 3 / 2,
  201. child: new Container(
  202. child: new Stack(
  203. children: <Widget>[
  204. new Hero(
  205. tag: controller,
  206. child: new VideoPlayer(controller),
  207. ),
  208. new Column(
  209. children: <Widget>[
  210. new GestureDetector(
  211. onTap: _onExpandCollapse,
  212. child: new Container(
  213. height: 30.0,
  214. margin: new EdgeInsets.only(
  215. top: 5.0,
  216. right: 5.0,
  217. left: 5.0,
  218. ),
  219. child: new Row(
  220. children: <Widget>[
  221. new AnimatedOpacity(
  222. opacity: _hideStuff ? 0.0 : 1.0,
  223. duration: new Duration(
  224. milliseconds: _hideStuff ? 300 : 100),
  225. child: new ClipRect(
  226. child: new BackdropFilter(
  227. filter: new ImageFilter.blur(sigmaX: 10.0),
  228. child: new Container(
  229. decoration: new BoxDecoration(
  230. color: backgroundColor,
  231. borderRadius: new BorderRadius.all(
  232. new Radius.circular(10.0),
  233. ),
  234. ),
  235. child: new Container(
  236. height: 30.0,
  237. padding: new EdgeInsets.only(
  238. left: 16.0,
  239. right: 16.0,
  240. ),
  241. child: new Icon(
  242. OpenIconicIcons.fullscreenEnter,
  243. color: iconColor,
  244. size: 12.0,
  245. ),
  246. ),
  247. ),
  248. ),
  249. ),
  250. ),
  251. new Expanded(child: new Container()),
  252. new GestureDetector(
  253. onTap: () {
  254. _cancelAndRestartTimer();
  255. if (_latestValue.volume == 0) {
  256. controller.setVolume(_latestVolume ?? 0.5);
  257. } else {
  258. _latestVolume = controller.value.volume;
  259. controller.setVolume(0.0);
  260. }
  261. },
  262. child: new AnimatedOpacity(
  263. opacity: _hideStuff ? 0.0 : 1.0,
  264. duration: new Duration(
  265. milliseconds: _hideStuff ? 300 : 100),
  266. child: new ClipRect(
  267. child: new BackdropFilter(
  268. filter: new ImageFilter.blur(sigmaX: 10.0),
  269. child: new Container(
  270. decoration: new BoxDecoration(
  271. color: backgroundColor,
  272. borderRadius: new BorderRadius.all(
  273. new Radius.circular(10.0),
  274. ),
  275. ),
  276. child: new Container(
  277. height: 30.0,
  278. padding: new EdgeInsets.only(
  279. left: 16.0,
  280. right: 16.0,
  281. ),
  282. child: new Icon(
  283. (_latestValue != null &&
  284. _latestValue.volume > 0)
  285. ? Icons.volume_up
  286. : Icons.volume_mute,
  287. color: iconColor,
  288. size: 16.0,
  289. ),
  290. ),
  291. ),
  292. ),
  293. ),
  294. ),
  295. ),
  296. ],
  297. ),
  298. ),
  299. ),
  300. new Expanded(
  301. child: new GestureDetector(
  302. onTap: _latestValue != null && _latestValue.isPlaying
  303. ? _cancelAndRestartTimer
  304. : () {
  305. _hideTimer.cancel();
  306. setState(() {
  307. _hideStuff = false;
  308. });
  309. },
  310. child: new Container(
  311. color: Colors.transparent,
  312. ),
  313. ),
  314. ),
  315. new AnimatedOpacity(
  316. opacity: _hideStuff ? 0.0 : 1.0,
  317. duration:
  318. new Duration(milliseconds: _hideStuff ? 300 : 100),
  319. child: new Container(
  320. color: Colors.transparent,
  321. alignment: Alignment.bottomCenter,
  322. margin: new EdgeInsets.all(5.0),
  323. child: new ClipRect(
  324. child: new BackdropFilter(
  325. filter: new ImageFilter.blur(
  326. sigmaX: 10.0,
  327. sigmaY: 10.0,
  328. ),
  329. child: new Container(
  330. height: 30.0,
  331. decoration: new BoxDecoration(
  332. color: backgroundColor,
  333. borderRadius: new BorderRadius.all(
  334. new Radius.circular(10.0),
  335. ),
  336. ),
  337. child: new Row(
  338. children: <Widget>[
  339. new GestureDetector(
  340. onTap: _skipBack,
  341. child: new Padding(
  342. padding: new EdgeInsets.only(
  343. left: 16.0,
  344. right: 12.0,
  345. ),
  346. child: new Transform(
  347. alignment: Alignment.center,
  348. transform: new Matrix4.skewY(0.0)
  349. ..rotateX(math.pi)
  350. ..rotateZ(math.pi),
  351. child: new Icon(
  352. OpenIconicIcons.reload,
  353. color: iconColor,
  354. size: 12.0,
  355. ),
  356. ),
  357. ),
  358. ),
  359. new GestureDetector(
  360. onTap: _playPause,
  361. child: new Padding(
  362. padding: new EdgeInsets.only(
  363. right: 10.0,
  364. ),
  365. child: new Icon(
  366. controller.value.isPlaying
  367. ? OpenIconicIcons.mediaPause
  368. : OpenIconicIcons.mediaPlay,
  369. color: iconColor,
  370. size: 16.0,
  371. ),
  372. ),
  373. ),
  374. new GestureDetector(
  375. onTap: _skipForward,
  376. child: new Padding(
  377. padding: new EdgeInsets.only(
  378. right: 16.0,
  379. ),
  380. child: new Icon(
  381. OpenIconicIcons.reload,
  382. color: iconColor,
  383. size: 12.0,
  384. ),
  385. ),
  386. ),
  387. position(iconColor),
  388. new ProgressBar(controller),
  389. remaining(iconColor)
  390. ],
  391. ),
  392. ),
  393. ),
  394. ),
  395. ),
  396. ),
  397. ],
  398. ),
  399. ],
  400. ),
  401. ),
  402. ),
  403. ),
  404. );
  405. }
  406. _cancelAndRestartTimer() {
  407. _hideTimer.cancel();
  408. setState(() {
  409. _hideStuff = false;
  410. _startHideTimer();
  411. });
  412. }
  413. void _onExpandCollapse() {
  414. setState(() {
  415. _hideStuff = true;
  416. widget.onExpandCollapse().then((_) {
  417. _cancelAndRestartTimer();
  418. new Timer(new Duration(milliseconds: 300), () {
  419. if (!_disposed) {
  420. setState(() {
  421. _hideStuff = false;
  422. });
  423. }
  424. });
  425. });
  426. });
  427. }
  428. Widget position(Color iconColor) {
  429. final position =
  430. _latestValue != null ? _latestValue.position : new Duration(seconds: 0);
  431. return new Padding(
  432. padding: new EdgeInsets.only(right: 12.0),
  433. child: new Text(
  434. _formatDuration(position),
  435. style: new TextStyle(color: iconColor, fontSize: 12.0),
  436. ),
  437. );
  438. }
  439. Widget remaining(Color iconColor) {
  440. final position = _latestValue != null && _latestValue.duration != null
  441. ? _latestValue.duration - _latestValue.position
  442. : new Duration(seconds: 0);
  443. return new Padding(
  444. padding: new EdgeInsets.only(right: 12.0),
  445. child: new Text(
  446. '-${_formatDuration(position)}',
  447. style: new TextStyle(color: iconColor, fontSize: 12.0),
  448. ),
  449. );
  450. }
  451. String _formatDuration(Duration position) {
  452. final ms = position.inMilliseconds;
  453. int seconds = ms ~/ 1000;
  454. final int hours = seconds ~/ 3600;
  455. seconds = seconds % 3600;
  456. var minutes = seconds ~/ 60;
  457. seconds = seconds % 60;
  458. final hoursString = hours > 10 ? '$hours' : hours == 0 ? '00' : '0$hours';
  459. final minutesString =
  460. minutes > 10 ? '$minutes' : minutes == 0 ? '00' : '0$minutes';
  461. final secondsString =
  462. seconds > 10 ? '$seconds' : seconds == 0 ? '00' : '0$seconds';
  463. final formattedTime = '${hoursString == '00' ? '' : hoursString +
  464. ':'}$minutesString:$secondsString';
  465. return formattedTime;
  466. }
  467. void _playPause() {
  468. setState(() {
  469. if (widget.controller.value.isPlaying) {
  470. _hideStuff = false;
  471. _hideTimer.cancel();
  472. widget.controller.pause();
  473. } else {
  474. _cancelAndRestartTimer();
  475. widget.controller.play();
  476. }
  477. });
  478. }
  479. void _skipBack() {
  480. final beginning = new Duration(seconds: 0).inMicroseconds;
  481. final skip =
  482. (_latestValue.position - new Duration(seconds: 15)).inMicroseconds;
  483. widget.controller
  484. .seekTo(new Duration(milliseconds: math.max(skip, beginning)));
  485. }
  486. void _skipForward() {
  487. final end = _latestValue.duration.inMicroseconds;
  488. final skip =
  489. (_latestValue.position + new Duration(seconds: 15)).inMicroseconds;
  490. widget.controller.seekTo(new Duration(milliseconds: math.min(skip, end)));
  491. }
  492. }