rendering
Note: brightness adjustment and volume adjustment GIF can not be reflected, the function is OK, secondly, the default Icon lock close and open is really difficult to distinguish.
Vertical screen:
Landscape:
GIF details:
Most people have a lot of ideas about how to play Flutter videos, and there are many good plug-ins on Pub. But it’s always hard to make a project that fits you perfectly, and most of it needs to be packaged by yourself. I, too, decided to package a Flutter video player for my project after a long delay. No doubt I chose the official player plugin video_Player to package and customize it. The process is not complicated, as long as you watch carefully, I believe that every developer can customize their own video player!
Environment: Flutter 2.8.1 channel stable; The Dart 2.15.1
Third-party plug-ins
# player video_Player: ^2.2.11 # Screen rotation auto_orientation: ^2.2.1 # Brightness and volume adjustment brightness_volume: ^1.0.3Copy the code
The project structure
video_player_utils
Focus on the utility class. Because video playback involves a lot of state changes, THE author initially chose to use InheritedWidget to share data among many widgets. But the total feeling is a bit cumbersome, and not very elegant!
Here is non-advertising if it is usedGetX
Is very simple, the author also usedGetX
Encapsulate it! It’s a big kick! But I will say the same thing: Developers who are new to Flutter do not recommend itGetX
Before using Flutter, familiarize yourself with the basic principles of Flutter state management. And for the sake of brevity, let’s not introduce any other third parties.
We chose to encapsulate third-party plug-ins for several reasons:
- Convenient to call
- Adapting to business needs
- High cohesion and low coupling
- Late iteration maintenance
So I wrote a utility class, VideoPlayerUtils, specifically to handle all the business of the player. Including pause, play, jump, adjust volume, adjust brightness, switch video and other operations. No information about Video_Player or other third party plug-ins is referenced in any widget, and VideoPlayerUtils is responsible for all operational interactions between the widget and the player. In subsequent iterations of optimization or replacement of player plug-ins, only this tool class can be modified, and all widgets will be decoupled.
Public attribute
static String get url => _instance._url; Static VideoPlayerState get state => _instance._state; // Current state static bool get isInitialized => _instance._isInitialized; Static Duration get Duration => _instance._duration; Static Duration get position => _instance._position; Static double get aspectRatio => _instance._aspectRatio; // Video playback ratioCopy the code
The VideoPlayerState:
/// Playback status enum VideoPlayerState{stopped, // initial state, paused or error playing, // paused completed // Playback ended}Copy the code
Provide the above public attributes, you can use VideoPlayerUtils to obtain the corresponding value, use get read-only, so that the outside world will not mistakenly modify these attributes, to ensure the security of the value. Developers can add attributes according to their own needs.
Public methods
// Play, pause, switch the video and other operations, internal judgment whether to play or pause, Static void playerHandle(String URL,{bool autoPlay = true,bool looping = false}) async{ SeekTo ({required Duration position}) async{} 1. Whether the initialization is successful. 2. SetState () static void initializedListener({Required Dynamic Key, Required Function(bool,Widget) Listener}){ Remove the initialization results to monitor the static void removeInitializedListener (dynamic key) {} / / play state monitoring, Stopped, playing, paused, completed etc static void statusListener({required dynamic key,required Function(VideoPlayerState)) Static void removeStatusListener(dynamic key){} static void removeStatusListener(dynamic key){} static void removeStatusListener(dynamic key){ PositionListener ({Required Dynamic Key, Required Function(int) Listener}){} // Remove playback progress listener static void Static Future<double> getVolume() async{} static Future<void> SetVolume (double volume) async{} // getBrightness static Future<double> getBrightness() async{} // set brightness static Future<void> SetBrightness (Double Brightness) Async {} // Set the playback speed. Static Future<void> setSpeed(double speed) Async {} // Set whether to loop static Future<void> setLooping(bool looping) async{} set static setLandscape(){} // set static setPortrait(){} // Time formatting mm: SS (more than 1 hour can process hh:mm:ss, Static String formatDuration(int second){} // Dispose of resources static dispose(){} // Developers can add such as: brightness, volume change listening callback, etc.Copy the code
Provide the above methods to handle all the business of the player. The same developers can add or modify them as they see fit.
playerHandle
static void playerHandle(String url,{bool autoPlay = true,bool looping = false}) async{}
Copy the code
This method, just to highlight, is the core method of the whole business, controlling the play or pause of the video. Developers can call this method as long as they encounter play or pause, the specific is play or pause, internal according to the incoming URL, developers do not need to care.
This method is also used to switch to a new video. If the INCOMING URL is inconsistent with the last one, the new video will be switched automatically. The author can handle his own logic by listening for changes in the playback state according to statusListener.
initializedListener
Static void initializedListener({Required Dynamic Key, Required Function(bool,Widget) Listener}){}Copy the code
It is also worth mentioning that the video player will initialize the new video asynchronously when it plays the video. Generally, we will initialize initState() and setState() after it succeeds. Here I have a painful problem:
Let’s look at the use of video_player:
AspectRatio(
aspectRatio: controller.aspectRatio,
child: VideoPlayer(controller),
);
Copy the code
VideoPlayer(Controller) : Controller is already held in the widget. The purpose of the wrapper is to decouple the widget from the controller. But this writer…
Give up is not impossible to give up, this life will not give up!
InitializedListener (bool,Widget, initialized successfully); Widget returns the player UI to display on success and const SizedBox() on failure.
use
Here you can use it simply:
class _VideoPlayerUIState extends State<VideoPlayerUI> { Widget? _playerUI; @override void initState() { // TODO: implement initState super.initState(); // Play the video VideoPlayerUtils.playerHandle("http://flv3.bn.netease.com/tvmrepo/2018/6/9/R/EDJTRAD9R/SD/EDJTRAD9R-mobile.mp4"); / / new video playback, initialize monitor VideoPlayerUtils. InitializedListener (key: this listener: (initialize,widget){if(initialize){// After initialization, update UI _playerUI = widget; if(! mounted) return; setState(() {}); }}); } @ override void the dispose () {/ / TODO: implement the dispose / / removing listening VideoPlayerUtils removeInitializedListener (this); super.dispose(); } @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, width: 414, height: 414*9/16, color: Colors.black26, child: _playerUI ?? const CircularProgressIndicator( strokeWidth: 3, ) ); }}Copy the code
That’s right. Video playback is that simple.
Widget
If there are more business functions, THE author has also written a set according to his own needs, the same developers can add or modify according to their own needs.
video_player_gestures
VideoPlayerGestures is mainly about gestures, like fast forward, fast back, etc. Slide up and down on the left to adjust brightness; Slide up and down on the right to adjust the volume; Click whether to enable immersive play and hide and display all widgets. Double click to play, pause, etc.
GestureDetector(onTap: _onTap, // Click up and down widgets to hide and display onDoubleTap: _onDoubleTap, // Double-click to pause and play onVerticalDragStart:_onVerticalDragStart, // according to the start position. OnVerticalDragUpdate: _onVerticalDragUpdate: _onVerticalDragUpdate: _onVerticalDragUpdate: _onVerticalDragEnd, // Hide percentage prompt widget onHorizontalDragStart after completion: OnHorizontalDragUpdate: onHorizontalDragUpdate: onHorizontalDragUpdate: SeekTo child: _onHorizontalDragUpdate, // Update onHorizontalDragEnd: _onHorizontalDragEnd Stack( children: _children, ), );Copy the code
Oh, and PercentageWidget is in this file, too, which is this:
Because the percentage displayed is gesture dependent, it is updated as the gesture moves. It’s up to the developer to handle.
video_player_top
For simplicity, I named the entire UI according to its location. You know what it is when you look at it.
The same developers can add or modify them as they see fit.
video_player_center
Here it is:
The same developers can add or modify them as they see fit. The lock Icon’s open and close are really hard to distinguish.
video_player_bottom
Here it is:
The same developers can add or modify them as they see fit.
video_player_slider
This thing is custom, don’t ask, ask is to fight against the product
The main thing is to customize this stuff:
SliderThemeData( trackHeight: 8, inactiveTrackColor: Colors.grey, activeTrackColor: Colors.greenAccent, thumbShape: SliderThumbImage(image: _customImage),// CustomTrackShape: const CustomTrackShape(), // custom),Copy the code
The same developers can customize it to their own needs.
Note: there is no progress to add buffering here, the development can be viewedvideo_player
The source codeVideoProgressIndicator
, according to the service definition.
video_player_page
This is the integration of the above widgets, and consider the full screen security zone, nothing. Developers can handle it!
SafeArea( top: ! _isFullScreen, bottom: ! _isFullScreen, left: ! _isFullScreen, right: ! _isFullScreen, child: SizedBox( height: _height, width: _width, child: _playerUI ! = null ? VideoPlayerGestures( appearCallback: (appear){ _top! .opacityCallback(appear); _lockIcon! .opacityCallback(appear); _bottom! .opacityCallback(appear); }, children: [ Center( child: _playerUI, ), _top!, _lockIcon!, _bottom! ] , ) : Container( alignment: Alignment.center, color: Colors.black26, child: const CircularProgressIndicator( strokeWidth: 3, ), ) ), );Copy the code
RCFlutterVideoPlayer
See here for an idea of how to implement listeners.
That’s the end of a beautiful Flutter video player. If you find it helpful, welcome Star!