// Copyright 2013 The Flutter 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/material.dart'; import 'package:video_player/video_player.dart'; void main() { runApp( MaterialApp( home: _App(), ), ); } class _App extends StatelessWidget { @override Widget build(BuildContext context) { return DefaultTabController( length: 3, child: Scaffold( key: const ValueKey('home_page'), appBar: AppBar( title: const Text('Video player example'), actions: [ IconButton( key: const ValueKey('push_tab'), icon: const Icon(Icons.navigation), onPressed: () { Navigator.push<_PlayerVideoAndPopPage>( context, MaterialPageRoute<_PlayerVideoAndPopPage>( builder: (BuildContext context) => _PlayerVideoAndPopPage(), ), ); }, ) ], bottom: const TabBar( isScrollable: true, tabs: [ 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: [ _BumbleBeeRemoteVideo(), _ButterFlyAssetVideo(), _ButterFlyAssetVideoInList(), ], ), ), ); } } class _ButterFlyAssetVideoInList extends StatelessWidget { @override Widget build(BuildContext context) { return ListView( children: [ const _ExampleCard(title: 'Item a'), const _ExampleCard(title: 'Item b'), const _ExampleCard(title: 'Item c'), const _ExampleCard(title: 'Item d'), const _ExampleCard(title: 'Item e'), const _ExampleCard(title: 'Item f'), const _ExampleCard(title: 'Item g'), Card( child: Column(children: [ Column( children: [ const ListTile( leading: Icon(Icons.cake), title: Text('Video video'), ), Stack( alignment: FractionalOffset.bottomRight + const FractionalOffset(-0.1, -0.1), children: [ _ButterFlyAssetVideo(), Image.asset('assets/flutter-mark-square-64.png'), ]), ], ), ])), const _ExampleCard(title: 'Item h'), const _ExampleCard(title: 'Item i'), const _ExampleCard(title: 'Item j'), const _ExampleCard(title: 'Item k'), const _ExampleCard(title: 'Item l'), ], ); } } /// A filler card to show the video in a list of scrolling contents. class _ExampleCard extends StatelessWidget { const _ExampleCard({Key? key, required this.title}) : super(key: key); final String title; @override Widget build(BuildContext context) { return Card( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.airline_seat_flat_angled), title: Text(title), ), ButtonBar( children: [ TextButton( child: const Text('BUY TICKETS'), onPressed: () { /* ... */ }, ), TextButton( child: const Text('SELL TICKETS'), onPressed: () { /* ... */ }, ), ], ), ], ), ); } } class _ButterFlyAssetVideo extends StatefulWidget { @override _ButterFlyAssetVideoState createState() => _ButterFlyAssetVideoState(); } class _ButterFlyAssetVideoState extends State<_ButterFlyAssetVideo> { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); _controller.addListener(() { setState(() {}); }); _controller.setLooping(true); _controller.initialize().then((_) => setState(() {})); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: [ Container( padding: const EdgeInsets.only(top: 20.0), ), const Text('With assets mp4'), Container( padding: const EdgeInsets.all(20), child: AspectRatio( aspectRatio: _controller.value.aspectRatio, child: Stack( alignment: Alignment.bottomCenter, children: [ VideoPlayer(_controller), _ControlsOverlay(controller: _controller), VideoProgressIndicator(_controller, allowScrubbing: true), ], ), ), ), ], ), ); } } class _BumbleBeeRemoteVideo extends StatefulWidget { @override _BumbleBeeRemoteVideoState createState() => _BumbleBeeRemoteVideoState(); } class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> { late VideoPlayerController _controller; Future _loadCaptions() async { final String fileContents = await DefaultAssetBundle.of(context) .loadString('assets/bumble_bee_captions.vtt'); return WebVTTCaptionFile( fileContents); // For vtt files, use WebVTTCaptionFile } @override void initState() { super.initState(); _controller = VideoPlayerController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', closedCaptionFile: _loadCaptions(), videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), ); _controller.addListener(() { setState(() {}); }); _controller.setLooping(true); _controller.initialize(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: [ Container(padding: const EdgeInsets.only(top: 20.0)), const Text('With remote mp4'), Container( padding: const EdgeInsets.all(20), child: AspectRatio( aspectRatio: _controller.value.aspectRatio, child: Stack( alignment: Alignment.bottomCenter, children: [ VideoPlayer(_controller), ClosedCaption(text: _controller.value.caption.text), _ControlsOverlay(controller: _controller), VideoProgressIndicator(_controller, allowScrubbing: true), ], ), ), ), ], ), ); } } class _ControlsOverlay extends StatelessWidget { const _ControlsOverlay({Key? key, required this.controller}) : super(key: key); static const List _exampleCaptionOffsets = [ Duration(seconds: -10), Duration(seconds: -3), Duration(seconds: -1, milliseconds: -500), Duration(milliseconds: -250), Duration(milliseconds: 0), Duration(milliseconds: 250), Duration(seconds: 1, milliseconds: 500), Duration(seconds: 3), Duration(seconds: 10), ]; static const List _examplePlaybackRates = [ 0.25, 0.5, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0, ]; final VideoPlayerController controller; @override Widget build(BuildContext context) { return Stack( children: [ AnimatedSwitcher( duration: const Duration(milliseconds: 50), reverseDuration: const Duration(milliseconds: 200), child: controller.value.isPlaying ? const SizedBox.shrink() : Container( color: Colors.black26, child: const Center( child: Icon( Icons.play_arrow, color: Colors.white, size: 100.0, semanticLabel: 'Play', ), ), ), ), GestureDetector( onTap: () { controller.value.isPlaying ? controller.pause() : controller.play(); }, ), Align( alignment: Alignment.topLeft, child: PopupMenuButton( initialValue: controller.value.captionOffset, tooltip: 'Caption Offset', onSelected: (Duration delay) { controller.setCaptionOffset(delay); }, itemBuilder: (BuildContext context) { return >[ for (final Duration offsetDuration in _exampleCaptionOffsets) PopupMenuItem( value: offsetDuration, child: Text('${offsetDuration.inMilliseconds}ms'), ) ]; }, child: Padding( padding: const EdgeInsets.symmetric( // Using less vertical padding as the text is also longer // horizontally, so it feels like it would need more spacing // horizontally (matching the aspect ratio of the video). vertical: 12, horizontal: 16, ), child: Text('${controller.value.captionOffset.inMilliseconds}ms'), ), ), ), Align( alignment: Alignment.topRight, child: PopupMenuButton( initialValue: controller.value.playbackSpeed, tooltip: 'Playback speed', onSelected: (double speed) { controller.setPlaybackSpeed(speed); }, itemBuilder: (BuildContext context) { return >[ for (final double speed in _examplePlaybackRates) PopupMenuItem( value: speed, child: Text('${speed}x'), ) ]; }, child: Padding( padding: const EdgeInsets.symmetric( // Using less vertical padding as the text is also longer // horizontally, so it feels like it would need more spacing // horizontally (matching the aspect ratio of the video). vertical: 12, horizontal: 16, ), child: Text('${controller.value.playbackSpeed}x'), ), ), ), ], ); } } class _PlayerVideoAndPopPage extends StatefulWidget { @override _PlayerVideoAndPopPageState createState() => _PlayerVideoAndPopPageState(); } class _PlayerVideoAndPopPageState extends State<_PlayerVideoAndPopPage> { late 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 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( future: started(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.data ?? false) { return AspectRatio( aspectRatio: _videoPlayerController.value.aspectRatio, child: VideoPlayer(_videoPlayerController), ); } else { return const Text('waiting for video to load'); } }, ), ), ); } }