This article is based on the official video playback plugin package github.com/flutter/plu…

In daily development, it is inevitable to meet the needs of video development; As Flutter technology becomes more and more active, there will inevitably be a need for video function. If the official function of The Video_player is directly incorporated into Flutter, it will be found that further encapsulation is needed in many places.

Integrated video playback function

First of all, since the company’s Android uses iJKPlayer for video playback and the official plug-in uses ExoPlayer, So during the integration, we changed the class corresponding to the VideoPlayerPlugin and changed the related ExoPlayer to iJkPlayer in our own App. The benefits of this are not only reduced because the introduction of ExoPlayer brings a larger package size to the App. It is also possible to reuse native code.

Secondly, you need to know about the principle of the official video player plugin, and the principle can be summarized in a word: external Texture; The Textture class in the Flutter end is defined as follows:

class Texture extends LeafRenderObjectWidget { const Texture({ Key key, @required this.textureId, }) : assert(textureId ! = null), super(key: key); }Copy the code

So each texture corresponds to a required textureId, which is the key point to achieve video playback; To learn how to generate textureId natively, take a look at the official plugin source code:

TextureRegistry textures = registrar.textures();
TextureRegistry.SurfaceTextureEntry textureEntry = textures.createSurfaceTexture();
textureId = textureEntry.id()
Copy the code

Question 1: When will the textureId be generated? When a video initialization is called on the Flutter side, the plugin’s create method is called:

final Map<dynamic, dynamic> response = await _channel.invokeMethod(
  'create',
  dataSourceDescription,
);
_textureId = response['textureId'];
Copy the code

Question 2: How does textureId transfer data? When the Flutter endpoint calls create, the native endpoint generates a textureId and registers a new EventChannel. Relevant data of video playback so far are as follows: Initialized, completed, bufferingUpdate, bufferingStart, bufferingEnd will call back to the Flutter end for each specific video Widge T, so as to realize the next logic.

How the native generates a new EventChannel:

TextureRegistry.SurfaceTextureEntry textureEntry = textures.createSurfaceTexture();
String eventChannelName = "flutter.io/videoPlayer/videoEvents" + textureEntry.id();
EventChannel eventChannel =
        new EventChannel(
                registrar.messenger(), eventChannelName);
Copy the code

How to listen to the Flutter end:

void eventListener(dynamic event) {
   final Map<dynamic, dynamic> map = event;
   switch (map['event']) {
     case 'initialized':
       value = value.copyWith(
         duration: Duration(milliseconds: map['duration']),
         size: Size(map['width']? .toDouble() ?? 0.0, the map ['height']? .toDouble() ?? 0.0)); initializingCompleter.complete(null); _applyLooping(); _applyVolume(); _applyPlayPause();break; . } } void errorListener(Object obj) { final PlatformException e = obj; LogUtil.d("----------- ErrorListener Code = ${e.code}"); value = VideoPlayerValue.erroneous(e.code); _timer? .cancel(); } _eventSubscription = _eventChannelFor(_textureId) .receiveBroadcastStream() .listen(eventListener, onError: errorListener);return initializingCompleter.future;
}

EventChannel _eventChannelFor(int textureId) {
 return EventChannel('flutter.io/videoPlayer/videoEvents$textureId');
}
Copy the code

Of course for things like pause/play/fast forward… The logic for some actions that need to trigger is a little different; Use the same plugin that called the create method (the plugin corresponding to “flutter. IO /videoPlayer”) :

Final MethodChannel _channel = const MethodChannel()'flutter.io/videoPlayer')
_channel.invokeMethod( 'play', <String, dynamic>{'textureId': _textureId});
Copy the code

At this point. This will enable Flutter to play video.

Second, video list interface implementation

Flutter can be implemented using a sliding control called ListView, just like a normal list-based interface. However, the video interface is slightly different in that it needs to be handled when the currently playing item is not visible, and when clicking on another video, the previous video needs to be paused.

Firstly, when to click another video in a play before video need to suspend the handling: this kind of situation is better, my way is to give each here video widgets are registered a click callback, when another click play traversal callback, it found that the current video is in play is executed to suspend operations:

PlayCallback = () {playCallback = () {if (controller.value.isPlaying) {
    setState(() { controller.pause(); }); }}; VideoPlayerController.playCallbacks.add(playCallback);Copy the code

Second, when sliding, you need to stop playing when item is not visible. In this case, the main principle is to get the Rect(region) of the slideable view and then pause when the bottom of the video Rect is less than the top of the slideable view or the top of the current video view is less than the bottom of the slideable view. Question 1: How do I get the Rect of a Widget?

// return the corresponding Rect area... static Rect getRectFromKey(BuildContext currentContext) { var object = currentContext? .findRenderObject(); var translation = object? .getTransformTo(null)? .getTranslation(); var size = object? .semanticBounds? .size;if(translation ! = null && size ! = null) {return new Rect.fromLTWH(translation.x, translation.y, size.width, size.height);
    } else {
      returnnull; }}Copy the code

Question 2: How can you tell if you are sliding off the screen? Register a listener for the video Widget to call back when sliding:

/// 滑动ListView的时候进行回调给视频Widget
scrollController = ScrollController();
scrollController.addListener(() {
  if (videoScrollController.scrollOffsetCallbacks.isNotEmpty) {
    for (ScrollOffsetCallback callback invideoScrollController.scrollOffsetCallbacks) { callback(); }}});Copy the code

When the video Widget receives a slide callback:

scrollOffsetCallback = () { itemRect = VideoScrollController.getRectFromKey(videoBuildContext); / / / status bar + the height of the title bar, there is a little deviation int toolBarAndStatusBarHeight = 44 + 25;if(itemRect ! = null && videoScrollController.rect ! = null && (itemRect.top > videoScrollController.rect.bottom || itemRect.bottom - toolBarAndStatusBarHeight < videoScrollController.rect.top)) {if (controller.value.isPlaying) {
          setState(() {
            LogUtil.d("=============== is playing, need to pause when removed from screen ======");
            controller.pause();
          });
        }
      }
    };
videoScrollController?.scrollOffsetCallbacks?.add(scrollOffsetCallback);
Copy the code

So far, a real tabular video interface has solved the problem of pausing/playing when swiping or clicking.

Three, full screen switch

A common requirement for video is the need for full screen, so the Flutter should have this feature as well; For full screen functions refer to the open source library github.com/brianegan/c… The train of thought. And the main principle is the only principle of using each texture textureId texture, with the native buckle View way there are some differences, according to the overall code of the open source library is relatively simple without very many trouble problems:

Exit full screen_popFullScreenWidget() { Navigator.of(context).pop(); Pushfullscreenwidget () Async {final TransitionRoute<Null> route = new PageRouteBuilder<Null>(Settings: new RouteSettings(isInitialRoute:false),
      pageBuilder: _fullScreenRoutePageBuilder,
    );

    SystemChrome.setEnabledSystemUIOverlays([]);
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.landscapeLeft,
      DeviceOrientation.landscapeRight,
    ]);
    await Navigator.of(context).push(route);
    SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
      DeviceOrientation.portraitDown,
      DeviceOrientation.landscapeLeft,
      DeviceOrientation.landscapeRight,
    ]);
  }
Copy the code

So, these are probably the common requirements for Flutter development; Of course, specific or more abnormal video needs need to be further improved based on this scheme. Attached are some demo renderings: