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