| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /// An example of using the plugin, controlling lifecycle and playback of the |
| /// video. |
| |
| import 'package:flutter/cupertino.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:video_player/video_player.dart'; |
| |
| /// Controls play and pause of [controller]. |
| /// |
| /// Toggles play/pause on tap (accompanied by a fading status icon). |
| /// |
| /// Plays (looping) on initialization, and mutes on deactivation. |
| class VideoPlayPause extends StatefulWidget { |
| VideoPlayPause(this.controller); |
| |
| final VideoPlayerController controller; |
| |
| @override |
| State createState() { |
| return _VideoPlayPauseState(); |
| } |
| } |
| |
| class _VideoPlayPauseState extends State<VideoPlayPause> { |
| _VideoPlayPauseState() { |
| listener = () { |
| setState(() {}); |
| }; |
| } |
| |
| FadeAnimation imageFadeAnim = |
| FadeAnimation(child: const Icon(Icons.play_arrow, size: 100.0)); |
| VoidCallback listener; |
| |
| VideoPlayerController get controller => widget.controller; |
| |
| @override |
| void initState() { |
| super.initState(); |
| controller.addListener(listener); |
| controller.setVolume(1.0); |
| controller.play(); |
| } |
| |
| @override |
| void deactivate() { |
| controller.setVolume(0.0); |
| controller.removeListener(listener); |
| super.deactivate(); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| final List<Widget> children = <Widget>[ |
| GestureDetector( |
| child: VideoPlayer(controller), |
| onTap: () { |
| if (!controller.value.initialized) { |
| return; |
| } |
| if (controller.value.isPlaying) { |
| imageFadeAnim = |
| FadeAnimation(child: const Icon(Icons.pause, size: 100.0)); |
| controller.pause(); |
| } else { |
| imageFadeAnim = |
| FadeAnimation(child: const Icon(Icons.play_arrow, size: 100.0)); |
| controller.play(); |
| } |
| }, |
| ), |
| Align( |
| alignment: Alignment.bottomCenter, |
| child: VideoProgressIndicator( |
| controller, |
| allowScrubbing: true, |
| ), |
| ), |
| Center(child: imageFadeAnim), |
| Center( |
| child: controller.value.isBuffering |
| ? const CircularProgressIndicator() |
| : null), |
| ]; |
| |
| return Stack( |
| fit: StackFit.passthrough, |
| children: children, |
| ); |
| } |
| } |
| |
| class FadeAnimation extends StatefulWidget { |
| FadeAnimation( |
| {this.child, this.duration = const Duration(milliseconds: 500)}); |
| |
| final Widget child; |
| final Duration duration; |
| |
| @override |
| _FadeAnimationState createState() => _FadeAnimationState(); |
| } |
| |
| class _FadeAnimationState extends State<FadeAnimation> |
| with SingleTickerProviderStateMixin { |
| AnimationController animationController; |
| |
| @override |
| void initState() { |
| super.initState(); |
| animationController = |
| AnimationController(duration: widget.duration, vsync: this); |
| animationController.addListener(() { |
| if (mounted) { |
| setState(() {}); |
| } |
| }); |
| animationController.forward(from: 0.0); |
| } |
| |
| @override |
| void deactivate() { |
| animationController.stop(); |
| super.deactivate(); |
| } |
| |
| @override |
| void didUpdateWidget(FadeAnimation oldWidget) { |
| super.didUpdateWidget(oldWidget); |
| if (oldWidget.child != widget.child) { |
| animationController.forward(from: 0.0); |
| } |
| } |
| |
| @override |
| void dispose() { |
| animationController.dispose(); |
| super.dispose(); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return animationController.isAnimating |
| ? Opacity( |
| opacity: 1.0 - animationController.value, |
| child: widget.child, |
| ) |
| : Container(); |
| } |
| } |
| |
| typedef Widget VideoWidgetBuilder( |
| BuildContext context, VideoPlayerController controller); |
| |
| abstract class PlayerLifeCycle extends StatefulWidget { |
| PlayerLifeCycle(this.dataSource, this.childBuilder); |
| |
| final VideoWidgetBuilder childBuilder; |
| final String dataSource; |
| } |
| |
| /// A widget connecting its life cycle to a [VideoPlayerController] using |
| /// a data source from the network. |
| class NetworkPlayerLifeCycle extends PlayerLifeCycle { |
| NetworkPlayerLifeCycle(String dataSource, VideoWidgetBuilder childBuilder) |
| : super(dataSource, childBuilder); |
| |
| @override |
| _NetworkPlayerLifeCycleState createState() => _NetworkPlayerLifeCycleState(); |
| } |
| |
| /// A widget connecting its life cycle to a [VideoPlayerController] using |
| /// an asset as data source |
| class AssetPlayerLifeCycle extends PlayerLifeCycle { |
| AssetPlayerLifeCycle(String dataSource, VideoWidgetBuilder childBuilder) |
| : super(dataSource, childBuilder); |
| |
| @override |
| _AssetPlayerLifeCycleState createState() => _AssetPlayerLifeCycleState(); |
| } |
| |
| abstract class _PlayerLifeCycleState extends State<PlayerLifeCycle> { |
| VideoPlayerController controller; |
| |
| @override |
| |
| /// Subclasses should implement [createVideoPlayerController], which is used |
| /// by this method. |
| void initState() { |
| super.initState(); |
| controller = createVideoPlayerController(); |
| controller.addListener(() { |
| if (controller.value.hasError) { |
| print(controller.value.errorDescription); |
| } |
| }); |
| controller.initialize(); |
| controller.setLooping(true); |
| controller.play(); |
| } |
| |
| @override |
| void deactivate() { |
| super.deactivate(); |
| } |
| |
| @override |
| void dispose() { |
| controller.dispose(); |
| super.dispose(); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return widget.childBuilder(context, controller); |
| } |
| |
| VideoPlayerController createVideoPlayerController(); |
| } |
| |
| class _NetworkPlayerLifeCycleState extends _PlayerLifeCycleState { |
| @override |
| VideoPlayerController createVideoPlayerController() { |
| return VideoPlayerController.network(widget.dataSource); |
| } |
| } |
| |
| class _AssetPlayerLifeCycleState extends _PlayerLifeCycleState { |
| @override |
| VideoPlayerController createVideoPlayerController() { |
| return VideoPlayerController.asset(widget.dataSource); |
| } |
| } |
| |
| /// A filler card to show the video in a list of scrolling contents. |
| Widget buildCard(String title) { |
| return Card( |
| child: Column( |
| mainAxisSize: MainAxisSize.min, |
| children: <Widget>[ |
| ListTile( |
| leading: const Icon(Icons.airline_seat_flat_angled), |
| title: Text(title), |
| ), |
| // TODO(jackson): Remove when deprecation is on stable branch |
| // ignore: deprecated_member_use |
| ButtonTheme.bar( |
| child: ButtonBar( |
| children: <Widget>[ |
| FlatButton( |
| child: const Text('BUY TICKETS'), |
| onPressed: () { |
| /* ... */ |
| }, |
| ), |
| FlatButton( |
| child: const Text('SELL TICKETS'), |
| onPressed: () { |
| /* ... */ |
| }, |
| ), |
| ], |
| ), |
| ), |
| ], |
| ), |
| ); |
| } |
| |
| class VideoInListOfCards extends StatelessWidget { |
| VideoInListOfCards(this.controller); |
| |
| final VideoPlayerController controller; |
| |
| @override |
| Widget build(BuildContext context) { |
| return ListView( |
| children: <Widget>[ |
| buildCard("Item a"), |
| buildCard("Item b"), |
| buildCard("Item c"), |
| buildCard("Item d"), |
| buildCard("Item e"), |
| buildCard("Item f"), |
| buildCard("Item g"), |
| Card( |
| child: Column(children: <Widget>[ |
| Column( |
| children: <Widget>[ |
| const ListTile( |
| leading: Icon(Icons.cake), |
| title: Text("Video video"), |
| ), |
| Stack( |
| alignment: FractionalOffset.bottomRight + |
| const FractionalOffset(-0.1, -0.1), |
| children: <Widget>[ |
| AspectRatioVideo(controller), |
| Image.asset('assets/flutter-mark-square-64.png'), |
| ]), |
| ], |
| ), |
| ])), |
| buildCard("Item h"), |
| buildCard("Item i"), |
| buildCard("Item j"), |
| buildCard("Item k"), |
| buildCard("Item l"), |
| ], |
| ); |
| } |
| } |
| |
| class AspectRatioVideo extends StatefulWidget { |
| AspectRatioVideo(this.controller); |
| |
| final VideoPlayerController controller; |
| |
| @override |
| AspectRatioVideoState createState() => AspectRatioVideoState(); |
| } |
| |
| class AspectRatioVideoState extends State<AspectRatioVideo> { |
| VideoPlayerController get controller => widget.controller; |
| bool initialized = false; |
| |
| VoidCallback listener; |
| |
| @override |
| void initState() { |
| super.initState(); |
| listener = () { |
| if (!mounted) { |
| return; |
| } |
| if (initialized != controller.value.initialized) { |
| initialized = controller.value.initialized; |
| setState(() {}); |
| } |
| }; |
| controller.addListener(listener); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| if (initialized) { |
| return Center( |
| child: AspectRatio( |
| aspectRatio: controller.value.aspectRatio, |
| child: VideoPlayPause(controller), |
| ), |
| ); |
| } else { |
| return Container(); |
| } |
| } |
| } |
| |
| void main() { |
| runApp( |
| MaterialApp( |
| home: DefaultTabController( |
| length: 3, |
| child: Scaffold( |
| appBar: AppBar( |
| title: const Text('Video player example'), |
| bottom: const TabBar( |
| isScrollable: true, |
| tabs: <Widget>[ |
| Tab( |
| icon: Icon(Icons.cloud), |
| text: "Remote", |
| ), |
| Tab(icon: Icon(Icons.insert_drive_file), text: "Asset"), |
| Tab(icon: Icon(Icons.list), text: "List example"), |
| ], |
| ), |
| ), |
| body: TabBarView( |
| children: <Widget>[ |
| SingleChildScrollView( |
| child: Column( |
| children: <Widget>[ |
| Container( |
| padding: const EdgeInsets.only(top: 20.0), |
| ), |
| const Text('With remote m3u8'), |
| Container( |
| padding: const EdgeInsets.all(20), |
| child: NetworkPlayerLifeCycle( |
| 'http://184.72.239.149/vod/smil:BigBuckBunny.smil/playlist.m3u8', |
| (BuildContext context, |
| VideoPlayerController controller) => |
| AspectRatioVideo(controller), |
| ), |
| ), |
| ], |
| ), |
| ), |
| SingleChildScrollView( |
| child: Column( |
| children: <Widget>[ |
| Container( |
| padding: const EdgeInsets.only(top: 20.0), |
| ), |
| const Text('With assets mp4'), |
| Container( |
| padding: const EdgeInsets.all(20), |
| child: AssetPlayerLifeCycle( |
| 'assets/Butterfly-209.mp4', |
| (BuildContext context, |
| VideoPlayerController controller) => |
| AspectRatioVideo(controller)), |
| ), |
| ], |
| ), |
| ), |
| AssetPlayerLifeCycle( |
| 'assets/Butterfly-209.mp4', |
| (BuildContext context, VideoPlayerController controller) => |
| VideoInListOfCards(controller)), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |