Chewie introduction
- chewie
- video_player
The Video_Player plug-in provides low-level access to video playback. Chewie wraps video_Player into a user-friendly control UI.
Chewie is really great and very simple to use. Chewie offers two complete sets of UI styles and a great deal of customization. But in some cases, it doesn’t meet our needs, so we need to extend the functionality. To customize UI styles and functionality extensions, you need to have some knowledge of the library source code.
Chewie’s way of using
First post an effect picture: the left is the default effect, the right is the fixed size effect.
Add dependency pubspec.yaml
Dependencies: ^0.9.8+1 video_player: ^0.10.2+5Copy the code
Encapsulating a widget
class ChewieVideoWidget1 extends StatefulWidget {
//https://nico-android-apk.oss-cn-beijing.aliyuncs.com/landscape.mp4
final String playUrl;
ChewieVideoWidget1(this.playUrl);
@override
_ChewieVideoWidget1State createState() => _ChewieVideoWidget1State();
}
class _ChewieVideoWidget1State extends State<ChewieVideoWidget1> {
VideoPlayerController _videoPlayerController;
ChewieController _chewieController;
@override
void initState() {
super.initState();
_videoPlayerController = VideoPlayerController.network(widget.playUrl);
_chewieController = ChewieController(
videoPlayerController: _videoPlayerController,
autoPlay: true, //aspectRatio: 3/2.0, //customControls: customControls (),); } @override voiddispose() {
_videoPlayerController.dispose();
_chewieController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
returnContainer( child: Chewie(controller: _chewieController,), ); }}Copy the code
After the last note, Android9.0 unable to broadcast video resources of HTTP in AndroidManifest. XML configuration android: usesCleartextTraffic = “true”
<application
android:name="io.flutter.app.FlutterApplication"
android:label="flutter_sample"
android:usesCleartextTraffic="true"
tools:targetApi="m"
android:icon="@mipmap/ic">
Copy the code
This is where we can happily use Chewie’s videos.
Of course, we can’t be satisfied.
Chewie source code parsing
Dart file, we find that there is only one controller argument, and that the constructor argument is placed in ChewieController.
Chewie({ Key key, this.controller, }) : assert(controller ! = null,'You must provide a chewie controller'),
super(key: key);
Copy the code
The chewie_player.dart code scroll down to the ChewieController class. There is a comment at the beginning of the class that describes the functionality of the ChewieController.
At the same time, it also mentioned that the change of the playback state is not its responsibility, the playback state has to find the VideoPlayerController. In fact, interaction with VideoPlayerController author in the MaterialControls/CupertinoControls.
So let’s take a look at this and we’ve got the ChewieController class, and we’re going to intercept some code. From the constructor, we can see that there are almost all video configurations, such as autoplay, loop, full screen, seekTo, mute, etc. You can also configure progress bar colors and even custom control widgets.
///ChewieController Is used to configure and drive the Chewie player component. // it provides methods to control playback, such as [pause] and [play], and methods to control player rendering, such as [enterFullScreen] or [exitFullScreen]. In addition, you can also listen for ChewieController rendering changes, such as entering and exiting full-screen mode. /// If you want to listen for changes in the playback state (such as changes in the player's progress), you also need to rely on the VideoPlayerController. Class ChewieController extends ChangeNotifier {ChewieController({// Video_Player Requires the VideoPlayerController in the video_player library Enclosing videoPlayerController, ratio of high to width / / container. If not, the default aspect ratio is calculated based on the screen. // Think about how to set the video aspect ratio. this.aspectRatio, this.autoInitialize =false,
this.autoPlay = false,
this.startAt,
this.looping = false, enclosing materialProgressColors, / / progress bar color enclosing customControls, / / custom control layer style enclosing allowFullScreen =true// Whether to allow full screen this.allowMuting =true, / /... this.routePageBuilder = null, ... }) : assert(videoPlayerController ! = null,'You must provide a controller to play a video') {
_initialize();
}
Copy the code
_initialize() is called in the ChewieController constructor, omitting part of the code. As you can see, if autoPlay is set, the video will play at this point. The VideoPlayerController configuration is complete. Now the video_player is in use!
Future _initialize() async {
await videoPlayerController.setLooping(looping);
if((autoInitialize || autoPlay) && ! videoPlayerController.value.initialized) { await videoPlayerController.initialize(); }if (autoPlay) {
await videoPlayerController.play();
}
if(startAt ! = null) { await videoPlayerController.seekTo(startAt); }... }Copy the code
Then came the question:
- Where is the ControlWidget? How do you initialize it?
- How is the control widget with ChewieController/VideoPlayerController interaction?
Go back to the Chewie class and find its build(BuildContext) method. Look at the source code of a Widget class. The constructor method is the first, and the build method is the second. We see two things:
- _ChewieControllerProvider
- PlayerWithControls
@override
Widget build(BuildContext context) {
return _ChewieControllerProvider(
controller: widget.controller,
child: PlayerWithControls(),
);
}
Copy the code
_ChewieControllerProvider. When you see a Provider, that’s necessarily an InheritedWidget. This place is very clear. ChewieController is shared with the InheritedWidget. Child (PlayerWithControls), you can use the widget tree to go up to the ChewieController and get the VideoPlayerController in the ChewieController
static ChewieController of(BuildContext context) {
final chewieControllerProvider =
context.inheritFromWidgetOfExactType(_ChewieControllerProvider)
as _ChewieControllerProvider;
returnchewieControllerProvider.controller; } class _ChewieControllerProvider extends InheritedWidget { const _ChewieControllerProvider({ Key key, @required this.controller, @required Widget child, }) : assert(controller ! = null), assert(child ! = null), super(key: key, child: child); final ChewieController controller; @override bool updateShouldNotify(_ChewieControllerProvider old) => controller ! = old.controller; }Copy the code
Now that the controller access is resolved, look at the control widget: PlayerWithControls. Selected part of the code, and annotated interpretation.
class PlayerWithControls extends StatelessWidget { @override Widget build(BuildContext context) { _ChewieControllerProvider ChewieController Final ChewieController ChewieController = ChewieController. Of (context);returnCenter(child: Container(// Set the width: use the screen width width: Mediaquery.of (context).sie.width, // use the configured aspectRatio aspectRatio, calculated by screen size if there is no default value. / / we can see that it is always with fixed width than child: AspectRatio (AspectRatio: chewieController AspectRatio?? _calculateAspectRatio(context), child: _buildPlayerWithControls(chewieController, context), ), ), ); } Container _buildPlayerWithControls( ChewieController chewieController, BuildContext context) {returnContainer( child: Stack( children: <Widget>[// Build the player Widget, // The VideoPlayer construct uses the videoPlayerController in chewieController. AspectRatio( aspectRatio: chewieController.aspectRatio ?? _calculateAspectRatio(context), child: VideoPlayer(chewieController.videoPlayerController), ), ), / / build control widget, customControls meterialControls/cupertinoControls _buildControls (context, chewieController)],),); } // Calculate the aspect ratio. double _calculateAspectRatio(BuildContext context) { final size = MediaQuery.of(context).size; final width = size.width; final height = size.height;returnwidth > height ? width / height : height / width; }}Copy the code
We go to _buildControls(BuildContext). Enter one of the Controls: MaterialControls. All the start/pause/full screen/progress bar controls you see on the screen are here. Intercepted part of the source code, there are omitted. The interaction between the UI and the underlying layer is actually in two parts: first, it reads the underlying data and renders the UI and sets the control events for the underlying layer. The second is to capture the underlying data changes to redraw the UI.
1. Play button widgets using VideoPlayerController. VideoPlayerValue data to construct, and then click on the button VideoPlayerController operation process.
/// In build(BuildContext), we find the content to build the Widget. @override Widget build(BuildContext context) { child: Column( children: <Widget>[_buildHitArea(), _buildBottomBar(context),} /// go to _buildBottomBar(BuildContext), AnimatedOpacity _buildBottomBar(BuildContext context,) {returnAnimatedOpacity( child: Row( children: <Widget>[ _buildPlayPause(controller), _buildProgressBar(), } // go to _buildPlayPause(Controller) GestureDetector _buildPlayPause(VideoPlayerController Controller) {returnGestureDetector( onTap: _playPause, child: Container( child: Icon( controller.value.isPlaying ? Icons. Pause: Icons. Play_arrow,} // Plays the click events triggered by the widget. void_playPause() {
setState(() {
if (controller.value.isPlaying) {
controller.pause();
} else{ controller.play(); }}); }Copy the code
2. Through VideoPlayerController. AddListener change monitoring, when changes, remove VideoPlayerController. VideoPlayerValue. SetState, update _latestValue to refresh the UI.
@override
void didChangeDependencies() {
if(_oldController ! = chewieController) { _dispose(); _initialize(); } super.didChangeDependencies(); } Future<Null> _initialize() async { controller.addListener(_updateState); _updateState(); } void_updateState() {
setState(() {
_latestValue = controller.value;
});
}
Copy the code
VideoPlayerValue contains real-time player data.
VideoPlayerValue({
@required this.duration,
this.size,
this.position = const Duration(),
this.buffered = const <DurationRange>[],
this.isPlaying = false,
this.isLooping = false,
this.isBuffering = false, this.volume = 1.0, this.errorDescription,});Copy the code
Here we have analyzed part of the main process code. Of course, there’s more to the Chewie library than that.
With that in mind, we can get started. It’s time to try to solve the following problem.
How do I customize the control layer style?
This problem is simpler, we can set customControls when we build ChewieController. We can refer to MaterialControls and write our own controls. In this repository is the reference code custom_controls.dart. Here’s the code
ChewieController(
videoPlayerController: _videoPlayerController,
autoPlay: true,
customControls: CustomControls(),
);
Copy the code
How to display video in a fixed-size container?
Imagine a scenario where we want to play video in a 300 by 300 container, and the video should not be distorted.
A review of the source code shows that Chewie only offers the opportunity to change aspectRatio. Container widgets are written dead PlayerWithControls. The width visible from the source code above is the width of the fixed screen. My guess is that the authors do not want the external calls to break the internal structure, as much as possible to ensure atomicity and high reuse.
So the only way to make it work is to copy Chewie’s code and change it. The transformation is as follows. 0. Copy all code from the Chewie library and remove Chewie dependencies from pubspec.yaml
Video_player: ^ 0.10.2 + 5 screen: 0.0.5# chewie: ^ 0.9.8 + 1
Copy the code
1.chewie_player.dart
Class Chewie extends StatefulWidget {/// Adds a child Widget child to a class configuration; Chewie({ Key key, this.controller, this.child, }) : assert(controller ! = null,'You must provide a chewie controller'),
super(key: key);
}
class ChewieState extends State<Chewie> {
@override
Widget build(BuildContext context) {
return_ChewieControllerProvider(Controller: Widget. controller, // Not dead PlayerWithControls Child: Widget.child?? PlayerWithControls(), ); }}Copy the code
Add child to Chewie
Chewie(
controller: _chewieController,
child: CustomPlayerWithControls(),
)
Copy the code
3. Custom control widgets. Custom_player_with_controls. Dart here
class CustomPlayerWithControls extends StatelessWidget { final double width; final double height; {Key Key, this.width = 300, this.height = 300,}) : super(Key: Key); @override Widget build(BuildContext context) { final ChewieController chewieController = ChewieController.of(context);return _buildPlayerWithControls(chewieController, context);
}
Container _buildPlayerWithControls(
ChewieController chewieController, BuildContext context) {
returnContainer( width: width, height: height, child: Stack( children: VideoPlayerContainer(width, height), _buildControls(context, chewieController),],); } Widget _buildControls( BuildContext context, ChewieController chewieController, ) {returnchewieController.showControls && chewieController.customControls ! = null ? chewieController.customControls : Container(); } // VideoPlayerContainer inherits the StatefulWidget from PlayerWithControls. It listens for changes in the videoPlayerController to get the video aspect ratio. class VideoPlayerContainer extends StatefulWidget { final double maxWidth; final double maxHeight; Double _viewRatio; double _viewRatio; VideoPlayerContainer( this.maxWidth, this.maxHeight, { Key key, }) : _viewRatio = maxWidth / maxHeight, super(key: key); @override _VideoPlayerContainerState createState() => _VideoPlayerContainerState(); } class _VideoPlayerContainerState extends State<VideoPlayerContainer> { double _aspectRatio; ChewieController chewieController; @override voiddispose() {
_dispose();
super.dispose();
}
void _dispose() {
chewieController.videoPlayerController.removeListener(_updateState);
}
@override
void didChangeDependencies() {
final _oldController = chewieController;
chewieController = ChewieController.of(context);
if(_oldController ! = chewieController) { _dispose(); chewieController.videoPlayerController.addListener(_updateState); _updateState(); } super.didChangeDependencies(); } void_updateState() { VideoPlayerValue value = chewieController? .videoPlayerController? .value;if(value ! = null) { double newAspectRatio = value.size ! = null ? value.aspectRatio : null;if(newAspectRatio ! = null && newAspectRatio ! = _aspectRatio) {setState(() {
_aspectRatio = newAspectRatio;
});
}
}
}
@override
Widget build(BuildContext context) {
if (_aspectRatio == null) {
returnContainer(); } double width; double height; /// Compare the two aspect ratios to ensure that the VideoPlayer does not exceed the container and does not distortif (_aspectRatio > widget._viewRatio) {
width = widget.maxWidth;
height = width / _aspectRatio;
} else {
height = widget.maxHeight;
width = height * _aspectRatio;
}
returnCenter( child: Container( width: width, height: height, child: VideoPlayer(chewieController.videoPlayerController), ), ); }}Copy the code
So much for the use of Chewie. All the code is here. Entry file: main_video.dart