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_playerbased 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

  1. Tap on-screen pop-up controls (progress bar, full screen play button, pause play button, title)
  2. Swipe right to control volume
  3. Swipe left to control brightness
  4. Swipe horizontally to fast forward and rewind
  5. Double tap to pause playback
  6. The screen is always on during playback

2. Install the components

  1. video_player: ^0.10.5
  2. auto_orientation : ^ 1.0 .5 //Control horizontal and vertical screen controls
  3. screen : ^ 0.0 .5 //Control screen brightness and screen always-on components
  4. common_utils : ^ 1.1 .3 //Format time and date components
  5. 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 aInheritedWidgetShared data:

  1. import 'package:flutter/material.dart';
  2. import 'package:video_player/video_player.dart';
  3. import 'video_player_control.dart';
  4. class ControllerWidget extends InheritedWidget {
  5. ControllerWidget({
  6. this.controlKey,
  7. this.child,
  8. this.controller,
  9. this.videoInit,
  10. this.title
  11. });
  12. final String title;
  13. final GlobalKey<VideoPlayerControlState> controlKey;
  14. final Widget child;
  15. final VideoPlayerController controller;
  16. final bool videoInit;
  17. //Define a convenience method to facilitate the widgets in the subtree to obtain shared data
  18. static ControllerWidget of(BuildContext context) {
  19. return context.dependOnInheritedWidgetOfExactType<ControllerWidget>();
  20. }
  21. @override
  22. bool updateShouldNotify(InheritedWidget oldWidget) {
  23. // TODO: implement updateShouldNotify
  24. return false;
  25. }
  26. }
  27. copy code

hereVideoPlayerControllerThis 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:

  1. class VideoPlayerUI extends StatefulWidget {
  2. VideoPlayerUI.network ({
  3. Key key,
  4. @required String url, // The current address that needs to be played
  5. this .width: double.infinity, // player size (greater than or equal to the video playback area)
  6. this.height: double.infinity,
  7. this .title = '', // the title of the video that needs to be displayed
  8. }) : type = VideoPlayerType.network,
  9. url = url,
  10. super(key: key);
  11. VideoPlayerUI .asset ({
  12. Key key,
  13. @required String dataSource, // The address that currently needs to be played
  14. this .width: double.infinity, // player size (greater than or equal to the video playback area)
  15. this.height: double.infinity,
  16. this .title = '', // the title of the video that needs to be displayed
  17. }) : type = VideoPlayerType.asset,
  18. url = dataSource,
  19. super(key: key);
  20. VideoPlayerUI.file({
  21. Key key,
  22. @required File file, // The address that currently needs to be played
  23. this .width: double.infinity, // player size (greater than or equal to the video playback area)
  24. this.height: double.infinity,
  25. this .title = '', // the title of the video that needs to be displayed
  26. }) : type = VideoPlayerType.file,
  27. url = file,
  28. super(key: key);
  29. final url;
  30. final VideoPlayerType type;
  31. final double width;
  32. final double height;
  33. final String title;
  34. @override
  35. _VideoPlayerUIState createState() => _VideoPlayerUIState();
  36. }
  37. copy code

4.2. Initialize the video

4.2.1. Initialization

First we need toinitStateIn 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.

  1. void _urlChange() async {
  2. if (widget.url == null || widget.url == '') return;
  3. if (_controller != null) {
  4. /// If the controller exists, clean up and recreate it
  5. _controller. removeListener (_videoListener);
  6. _controller.dispose();
  7. }
  8. setState (() {
  9. /// Reset the component parameters
  10. _videoInit = false ;
  11. _videoError = false ;
  12. });
  13. if (widget.type == VideoPlayerType.file) {
  14. _controller = VideoPlayerController.file(widget.url);
  15. } else if (widget.type == VideoPlayerType.asset) {
  16. _controller = VideoPlayerController.asset(widget.url);
  17. } else {
  18. _controller = VideoPlayerController.network(widget.url);
  19. }
  20. /// When the loading of the resource is completed, monitor the playback progress and mark _videoInit=true to complete the loading
  21. _controller. addListener (_videoListener);
  22. await _controller.initialize();
  23. setState (() {
  24. _videoInit = true ;
  25. _videoError = false ;
  26. _controller.play ( );
  27. });
  28. }
  29. 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.

  1. void _videoListener() async {
  2. if (_controller.value.hasError) {
  3. setState (() {
  4. _videoError = true ;
  5. });
  6. } else {
  7. Duration res = await _controller.position;
  8. if (res >= _controller.value.duration) {
  9. await _controller.seekTo(Duration(seconds: 0));
  10. await _controller.pause();
  11. }
  12. if (_controller.value.isPlaying && _key.currentState != null) {
  13. /// Reduce build times
  14. _key.currentState.setPosition(
  15. position: res,
  16. totalDuration: _controller.value.duration,
  17. );
  18. }
  19. }
  20. }
  21. copy code

4.2.2. Changing the video source

When the incoming url changes, reinitialize the video, here we need to usedidUpdateWidgetThis life cycle:

  1. @override
  2. void didUpdateWidget ( VideoPlayerUI oldWidget ) {
  3. if (oldWidget.url != widget.url) {
  4. _urlChange (); // re-execute the url load when the url changes
  5. }
  6. super.didUpdateWidget(oldWidget);
  7. }
  8. copy code

4.3. Complete code

VideoPlayerUI 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:

  1. void _togglePlayControl() {
  2. setState (() {
  3. if (_hidePlayControl) {
  4. /// Show if hidden
  5. _hidePlayControl = false;
  6. _playControlOpacity = 1;
  7. _startPlayControlTimer (); // start the timer, hide after timing
  8. } else {
  9. /// Hide if shown
  10. if (_timer != null ) _timer. cancel (); // remove the timer first if there is a timer
  11. _playControlOpacity = 0;
  12. Future.delayed(Duration(milliseconds: 500)).whenComplete(() {
  13. _hidePlayControl = true ; // Hide after 500ms delay (transparency animation ends)
  14. });
  15. }
  16. });
  17. }
  18. void _startPlayControlTimer() {
  19. /// Timer, usage is similar to front-end js
  20. if (_timer != null) _timer.cancel();
  21. _timer = Timer(Duration(seconds: 3), () {
  22. /// Hide after a delay of 3s
  23. setState (() {
  24. _playControlOpacity = 0;
  25. Future.delayed(Duration(milliseconds: 500)).whenComplete(() {
  26. _hidePlayControl = true;
  27. });
  28. });
  29. });
  30. }
  31. 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.

  1. void _toggleFullScreen() {
  2. setState (() {
  3. if (_isFullScreen) {
  4. /// If it is full screen, switch to portrait screen
  5. AutoOrientation.portraitAutoMode();
  6. ///Display the status bar, with virtual action buttons at the bottom
  7. SystemChrome.setEnabledSystemUIOverlays(
  8. [SystemUiOverlay.top, SystemUiOverlay.bottom]);
  9. } else {
  10. AutoOrientation.landscapeAutoMode();
  11. ///Close the status bar, with virtual action buttons at the bottom
  12. SystemChrome.setEnabledSystemUIOverlays([]);
  13. }
  14. _startPlayControlTimer (); // After the control is operated, the timer starts to hide
  15. });
  16. }
  17. 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

  1. // For the parent component to call to refresh the page, reduce the build of the parent component
  2. void setPosition({position, totalDuration}) {
  3. setState (() {
  4. _position = position;
  5. _totalDuration = totalDuration;
  6. });
  7. }
  8. 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):

  1. @override
  2. Widget build(BuildContext context) {
  3. return GestureDetector(
  4. onDoubleTap: _playOrPause,
  5. onTap: _togglePlayControl,
  6. child: Container(
  7. width: double.infinity,
  8. height: double.infinity,
  9. // The price transparent color is needed here, otherwise it will not be able to respond to gestures, does anyone know a more elegant way
  10. color: Colors.transparent,
  11. child: WillPopScope(
  12. child: Offstage(
  13. offstage: _hidePlayControl,
  14. child: AnimatedOpacity(
  15. // add transparency animation
  16. opacity: _playControlOpacity,
  17. duration: Duration(milliseconds: 300),
  18. child: Column(
  19. children: <Widget>[_top(), _middle(), _bottom(context)],
  20. ),
  21. ),
  22. ),
  23. onWillPop: _onWillPop,
  24. ),
  25. ),
  26. );
  27. }
  28. copy code

6.2 Complete code

I won't talk much about this control, just go directly to the complete code

Full code VideoPlayerPan

7. How to use

  1. import 'package:flutter/material.dart';
  2. import 'package:richway_flutter_cli/common/video/video_player_UI.dart';
  3. class VideoPage extends StatelessWidget {
  4. static final String routerName = '/VideoPage';
  5. // Size get _window => MediaQueryData.fromWindow(window).size;
  6. @override
  7. Widget build(BuildContext context) {
  8. return Scaffold(
  9. backgroundColor: Colors.black,
  10. body: Center(
  11. // The width and height of the component fills the parent control by default, you can also set the width and height yourself
  12. child: VideoPlayerUI.network(
  13. url:
  14. 'https://gss3.baidu.com/6LZ0ej3k1Qd3ote6lo7D0j9wehsv/tieba-smallvideo-transcode-crf/60609889_0b5d29ee8e09fad4cc4f40f314d737ca_0.mp4',
  15. title: 'Example video',
  16. ),
  17. ),
  18. );
  19. }
  20. }
  21. 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!

Source address


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.

Related: Flutter: Video-related gesture control and full-screen playback based on video_player