Flutter: Video-related gesture control and full-screen playback based on video_player
Introduction
Recently, the company needs to develop the function of video playback. The officially provided video_player does not provide other control functions except for the video playback function, including the most basic full-screen playback function. At the same time, I also compared the third-party components, which are not very satisfactory. Then we'll have to do it ourselvesvideo_player
based on the transformation. Due to the use of pure flutter for development, the interface on android and ios is the same. Without further ado, go directly to the picture:
1. Main functions
- Tap on-screen pop-up controls (progress bar, full screen play button, pause play button, title)
- Swipe right to control volume
- Swipe left to control brightness
- Swipe horizontally to fast forward and rewind
- Double tap to pause playback
- The screen is always on during playback
2. Install the components
- video_player: ^0.10.5
- auto_orientation : ^ 1.0 .5 //Control horizontal and vertical screen controls
- screen : ^ 0.0 .5 //Control screen brightness and screen always-on components
- common_utils : ^ 1.1 .3 //Format time and date components
- copy code
3. Component structure
For code readability, I split the component into 3 controls, namelycontrol button control
,Gesture swipe controls
,Video playback playback controls
. These three controls are nested in turn and fill the parent control by default. Due to the large number of nested layers, it is a bit troublesome to transfer properties layer by layer, so we use aInheritedWidget
Shared data:
- import 'package:flutter/material.dart';
- import 'package:video_player/video_player.dart';
- import 'video_player_control.dart';
-
- class ControllerWidget extends InheritedWidget {
- ControllerWidget({
- this.controlKey,
- this.child,
- this.controller,
- this.videoInit,
- this.title
- });
-
- final String title;
- final GlobalKey<VideoPlayerControlState> controlKey;
- final Widget child;
- final VideoPlayerController controller;
- final bool videoInit;
-
- //Define a convenience method to facilitate the widgets in the subtree to obtain shared data
- static ControllerWidget of(BuildContext context) {
- return context.dependOnInheritedWidgetOfExactType<ControllerWidget>();
- }
-
- @override
- bool updateShouldNotify(InheritedWidget oldWidget) {
- // TODO: implement updateShouldNotify
- return false;
- }
-
- }
- copy code
hereVideoPlayerController
This controller will be used frequently in the future to call and operate video related APIs.
4. Entry control VideoPlayerUI
4.1. Defining properties
Three ways to read video are defined herenetwork
,asset
,file
, corresponding toweb video
,Engineering video
,local video file
:
- class VideoPlayerUI extends StatefulWidget {
- VideoPlayerUI.network ({
- Key key,
- @required String url, // The current address that needs to be played
- this .width: double.infinity, // player size (greater than or equal to the video playback area)
- this.height: double.infinity,
- this .title = '', // the title of the video that needs to be displayed
- }) : type = VideoPlayerType.network,
- url = url,
- super(key: key);
-
- VideoPlayerUI .asset ({
- Key key,
- @required String dataSource, // The address that currently needs to be played
- this .width: double.infinity, // player size (greater than or equal to the video playback area)
- this.height: double.infinity,
- this .title = '', // the title of the video that needs to be displayed
- }) : type = VideoPlayerType.asset,
- url = dataSource,
- super(key: key);
-
- VideoPlayerUI.file({
- Key key,
- @required File file, // The address that currently needs to be played
- this .width: double.infinity, // player size (greater than or equal to the video playback area)
- this.height: double.infinity,
- this .title = '', // the title of the video that needs to be displayed
- }) : type = VideoPlayerType.file,
- url = file,
- super(key: key);
-
- final url;
- final VideoPlayerType type;
- final double width;
- final double height;
- final String title;
-
- @override
- _VideoPlayerUIState createState() => _VideoPlayerUIState();
- }
- copy code
4.2. Initialize the video
4.2.1. Initialization
First we need toinitState
In the life cycle, the video is initialized, and different UI interfaces are displayed for whether the video is loaded successfully: loading, loading successfully, and loading failure.
- void _urlChange() async {
- if (widget.url == null || widget.url == '') return;
- if (_controller != null) {
- /// If the controller exists, clean up and recreate it
- _controller. removeListener (_videoListener);
- _controller.dispose();
- }
- setState (() {
- /// Reset the component parameters
- _videoInit = false ;
- _videoError = false ;
- });
- if (widget.type == VideoPlayerType.file) {
- _controller = VideoPlayerController.file(widget.url);
- } else if (widget.type == VideoPlayerType.asset) {
- _controller = VideoPlayerController.asset(widget.url);
- } else {
- _controller = VideoPlayerController.network(widget.url);
- }
-
- /// When the loading of the resource is completed, monitor the playback progress and mark _videoInit=true to complete the loading
- _controller. addListener (_videoListener);
- await _controller.initialize();
- setState (() {
- _videoInit = true ;
- _videoError = false ;
- _controller.play ( );
- });
- }
- copy code
Here is a point to note:_controller.addListener (_videoListener);
We must add the listener before initialization, otherwise the subsequent loading state cannot respond. In the listener function, we use the GlobalKey to call the component method and refresh the page display of the child component time display.
- void _videoListener() async {
- if (_controller.value.hasError) {
- setState (() {
- _videoError = true ;
- });
- } else {
- Duration res = await _controller.position;
- if (res >= _controller.value.duration) {
- await _controller.seekTo(Duration(seconds: 0));
- await _controller.pause();
- }
- if (_controller.value.isPlaying && _key.currentState != null) {
- /// Reduce build times
- _key.currentState.setPosition(
- position: res,
- totalDuration: _controller.value.duration,
- );
- }
- }
- }
- copy code
4.2.2. Changing the video source
When the incoming url changes, reinitialize the video, here we need to usedidUpdateWidget
This life cycle:
- @override
- void didUpdateWidget ( VideoPlayerUI oldWidget ) {
- if (oldWidget.url != widget.url) {
- _urlChange (); // re-execute the url load when the url changes
- }
- super.didUpdateWidget(oldWidget);
- }
- copy code
4.3. Complete code
5. Video control button VideoPlayerControl
5.1 Touch the display interface
The main function of this component is that when you touch the screen, the operation button will pop up, and the button will disappear after two seconds. Here we need a Timer timer. Each time the screen is clicked, the previous operation will be canceled and the timing will be restarted:
- void _togglePlayControl() {
- setState (() {
- if (_hidePlayControl) {
- /// Show if hidden
- _hidePlayControl = false;
- _playControlOpacity = 1;
- _startPlayControlTimer (); // start the timer, hide after timing
- } else {
- /// Hide if shown
- if (_timer != null ) _timer. cancel (); // remove the timer first if there is a timer
- _playControlOpacity = 0;
- Future.delayed(Duration(milliseconds: 500)).whenComplete(() {
- _hidePlayControl = true ; // Hide after 500ms delay (transparency animation ends)
- });
- }
- });
- }
-
- void _startPlayControlTimer() {
- /// Timer, usage is similar to front-end js
- if (_timer != null) _timer.cancel();
- _timer = Timer(Duration(seconds: 3), () {
- /// Hide after a delay of 3s
- setState (() {
- _playControlOpacity = 0;
- Future.delayed(Duration(milliseconds: 500)).whenComplete(() {
- _hidePlayControl = true;
- });
- });
- });
- }
- copy code
5.2 Full screen playback
When we click on the full-screen operation, we only need to force the screen to switch to landscape and set the system to full-screen mode at the same time.
- void _toggleFullScreen() {
- setState (() {
- if (_isFullScreen) {
- /// If it is full screen, switch to portrait screen
- AutoOrientation.portraitAutoMode();
-
- ///Display the status bar, with virtual action buttons at the bottom
- SystemChrome.setEnabledSystemUIOverlays(
- [SystemUiOverlay.top, SystemUiOverlay.bottom]);
- } else {
- AutoOrientation.landscapeAutoMode();
-
- ///Close the status bar, with virtual action buttons at the bottom
- SystemChrome.setEnabledSystemUIOverlays([]);
- }
- _startPlayControlTimer (); // After the control is operated, the timer starts to hide
- });
- }
- copy code
5.3 Refresh the progress bar
This method is called in the monitor function of the video to update the progress bar in real time
- // For the parent component to call to refresh the page, reduce the build of the parent component
- void setPosition({position, totalDuration}) {
- setState (() {
- _position = position;
- _totalDuration = totalDuration;
- });
- }
- copy code
5.4 Complete code
VideoPlayerControl complete code
VideoPlayerSlider progress bar complete code
6. Gesture control control VideoPlayerPan
6.1 Gesture control method
There is no difficulty in gesture control here, it is nothing more than throughSliding distance/screen width (height)
Get the percentage plus the current value, and then set the brightness, volume, and progress. Here I need to pay attention to be sure to set a background transparent color for the container of VideoPlayerControl, otherwise the control cannot respond to gestures (I feel that it is not elegant enough to write here, if there is any good solution, please comment and tell me):
- @override
- Widget build(BuildContext context) {
- return GestureDetector(
- onDoubleTap: _playOrPause,
- onTap: _togglePlayControl,
- child: Container(
- width: double.infinity,
- height: double.infinity,
- // The price transparent color is needed here, otherwise it will not be able to respond to gestures, does anyone know a more elegant way
- color: Colors.transparent,
- child: WillPopScope(
- child: Offstage(
- offstage: _hidePlayControl,
- child: AnimatedOpacity(
- // add transparency animation
- opacity: _playControlOpacity,
- duration: Duration(milliseconds: 300),
- child: Column(
- children: <Widget>[_top(), _middle(), _bottom(context)],
- ),
- ),
- ),
- onWillPop: _onWillPop,
- ),
- ),
- );
- }
- copy code
6.2 Complete code
I won't talk much about this control, just go directly to the complete code
7. How to use
- import 'package:flutter/material.dart';
- import 'package:richway_flutter_cli/common/video/video_player_UI.dart';
-
- class VideoPage extends StatelessWidget {
- static final String routerName = '/VideoPage';
-
- // Size get _window => MediaQueryData.fromWindow(window).size;
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- backgroundColor: Colors.black,
- body: Center(
- // The width and height of the component fills the parent control by default, you can also set the width and height yourself
- child: VideoPlayerUI.network(
- url:
- 'https://gss3.baidu.com/6LZ0ej3k1Qd3ote6lo7D0j9wehsv/tieba-smallvideo-transcode-crf/60609889_0b5d29ee8e09fad4cc4f40f314d737ca_0.mp4',
- title: 'Example video',
- ),
- ),
- );
- }
- }
-
- copy code
Epilogue
At this point, the video component is explained, if it happens to be useful to you, please give a start on my github, or give this article a like, thank you everyone, the source code can be used after copying it!
Author: A programmer with lush hair
Link: https://juejin.im/post/5e12d60ce51d45415a66733d
Source: Nuggets
The copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.