| // 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. |
| |
| // ignore_for_file: public_member_api_docs |
| |
| /// 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'; |
| import 'package:flutter/scheduler.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 = () { |
| SchedulerBinding.instance.addPostFrameCallback((_) => 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() { |
| SchedulerBinding.instance.addPostFrameCallback((_) { |
| 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(); |
| } |
| } |
| } |
| |
| class App extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| return DefaultTabController( |
| length: 3, |
| child: Scaffold( |
| key: const ValueKey<String>('home_page'), |
| appBar: AppBar( |
| title: const Text('Video player example'), |
| actions: <Widget>[ |
| IconButton( |
| key: const ValueKey<String>('push_tab'), |
| icon: const Icon(Icons.navigation), |
| onPressed: () { |
| Navigator.push<PlayerVideoAndPopPage>( |
| context, |
| MaterialPageRoute<PlayerVideoAndPopPage>( |
| builder: (BuildContext context) => |
| PlayerVideoAndPopPage()), |
| ); |
| }, |
| ) |
| ], |
| 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 mp4'), |
| Container( |
| padding: const EdgeInsets.all(20), |
| child: NetworkPlayerLifeCycle( |
| 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', |
| (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)), |
| ], |
| ), |
| ), |
| ); |
| } |
| } |
| |
| void main() { |
| runApp( |
| MaterialApp( |
| home: App(), |
| ), |
| ); |
| } |
| |
| class PlayerVideoAndPopPage extends StatefulWidget { |
| @override |
| _PlayerVideoAndPopPageState createState() => _PlayerVideoAndPopPageState(); |
| } |
| |
| class _PlayerVideoAndPopPageState extends State<PlayerVideoAndPopPage> { |
| VideoPlayerController _videoPlayerController; |
| bool startedPlaying = false; |
| |
| @override |
| void initState() { |
| super.initState(); |
| |
| _videoPlayerController = |
| VideoPlayerController.asset('assets/Butterfly-209.mp4'); |
| _videoPlayerController.addListener(() { |
| if (startedPlaying && !_videoPlayerController.value.isPlaying) { |
| Navigator.pop(context); |
| } |
| }); |
| } |
| |
| @override |
| void dispose() { |
| _videoPlayerController.dispose(); |
| super.dispose(); |
| } |
| |
| Future<bool> started() async { |
| await _videoPlayerController.initialize(); |
| await _videoPlayerController.play(); |
| startedPlaying = true; |
| return true; |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return Material( |
| elevation: 0, |
| child: Center( |
| child: FutureBuilder<bool>( |
| future: started(), |
| builder: (BuildContext context, AsyncSnapshot<bool> snapshot) { |
| if (snapshot.data == true) { |
| return AspectRatio( |
| aspectRatio: _videoPlayerController.value.aspectRatio, |
| child: VideoPlayer(_videoPlayerController)); |
| } else { |
| return const Text('waiting for video to load'); |
| } |
| }, |
| ), |
| ), |
| ); |
| } |
| } |