main.dart 17 KB

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