Следуйте за мной шаг за шагом, чтобы внедрить плагин воспроизведения видео Flutter (1)

Flutter

Когда команда готовилась к приложению, мы нацелены на трепетание, тем более что популярность трепетара в последнее время увеличивается. Поскольку я впервые использовал трепетание, я хотел улучшить свои способности через свою собственную практику.

При создании приложения мы использовали видеоплеер, в настоящее время используя официальный плагин «Video_Player»GitHub.com/flutter/running…, за границей с этим плагином может и не быть проблем, но многие отечественные видеоплееры очень хорошо сделаны, а пользовательские функции очень полны.

Чтобы привести пример: когда домашние приложения воспроизводят видео в полноэкранном режиме, почти все они горизонтальные и полноэкранные, но официальный плагин работает вертикально на стороне iOS, и эффект очень плохой.

Поэтому я хотел сделать плагин для воспроизведения видео:

Требовать

  1. И Android, и iOS используют нативную разработку, и это хорошо;
  2. Используйте верхнюю стороннюю стороннюю табличку GitHub Star, чтобы уменьшить свою собственную рабочую нагрузку;

Согласно вышеуказанным требованиям «2», я в основном нашелlipangit/JiaoZiVideoPlayerа такжеnewyjp/JPVideoPlayer

Что ж, все предзнаменования сделаны, давайте приступим к разработке плагина шаг за шагом~

1. Создайте плагин

flutter create --org com.***.test --template=plugin bms_video_player

2. Создайте класс ассоциации

существуетlib/bms_video_player.dartфайл созданBmsVideoPlayerControllerКласс, используемый для связи с собственным кодом:

class BmsVideoPlayerController {

  MethodChannel _channel;
  
  BmsVideoPlayerController.init(int id) {
    _channel =  new MethodChannel('bms_video_player_$id');
  }

  Future<void> loadUrl(String url) async {
    assert(url != null);
    return _channel.invokeMethod('loadUrl', url);
  }
}

существует здесьMethodChannelОжидание дальнейших исследований.

3. Создайте обратный вызов

typedef void BmsVideoPlayerCreatedCallback(BmsVideoPlayerController controller);

4. Создайте макет виджета

Создайте виджет для добавления собственных макетов:

class BmsVideoPlayer extends StatefulWidget {

  final BmsVideoPlayerCreatedCallback onCreated;
  final x;
  final y;
  final width;
  final height;

  BmsVideoPlayer({
    Key key,
    @required this.onCreated,
    @required this.x,
    @required this.y,
    @required this.width,
    @required this.height,
  });

  @override
  State<StatefulWidget> createState() => _VideoPlayerState();

}
  
class _VideoPlayerState extends State<BmsVideoPlayer> {

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      behavior: HitTestBehavior.opaque,
      child: nativeView(),
      onHorizontalDragStart: (DragStartDetails details) {
        print("onHorizontalDragStart: ${details.globalPosition}");
        // if (!controller.value.initialized) {
        //   return;
        // }
        // _controllerWasPlaying = controller.value.isPlaying;
        // if (_controllerWasPlaying) {
        //   controller.pause();
        // }
      },
      onHorizontalDragUpdate: (DragUpdateDetails details) {
        print("onHorizontalDragUpdate: ${details.globalPosition}");
        print(details.globalPosition);
        // if (!controller.value.initialized) {
        //   return;
        // }
        // seekToRelativePosition(details.globalPosition);
      },
      onHorizontalDragEnd: (DragEndDetails details) {
        print("onHorizontalDragEnd");
        // if (_controllerWasPlaying) {
        //   controller.play();
        // }
      },
      onTapDown: (TapDownDetails details) {
        print("onTapDown: ${details.globalPosition}");
      },
    );
  }

  nativeView() {
    if (defaultTargetPlatform == TargetPlatform.android) {
      return AndroidView(
        viewType: 'plugins.bms_video_player/view',
        onPlatformViewCreated: onPlatformViewCreated,
        creationParams: <String,dynamic>{
          "x": widget.x,
          "y": widget.y,
          "width": widget.width,
          "height": widget.height,
        },
        creationParamsCodec: const StandardMessageCodec(),
      );
    } else {
      return UiKitView(
        viewType: 'plugins.bms_video_player/view',
        onPlatformViewCreated: onPlatformViewCreated,
        creationParams: <String,dynamic>{
          "x": widget.x,
          "y": widget.y,
          "width": widget.width,
          "height": widget.height,
        },
        creationParamsCodec: const StandardMessageCodec(),
      );
    }
  }

  Future<void> onPlatformViewCreated(id) async {
    if (widget.onCreated == null) {
      return;
      }
    
    widget.onCreated(new BmsVideoPlayerController.init(id));
  }
}

здесьAndroidViewа такжеUiKitViewБуквально, разные системы используют разные виджеты.

в,AndroidViewа такжеUiKitViewЕсть несколько параметров, таких как:

  1. viewType: используется для различения разных имен плагинов и источников;
  2. onPlatformViewCreated: используется для вызова своей функции после создания виджета (onPlatformViewCreated);
  3. createParams: используется для передачи параметров собственным элементам управления.

Приступаем, регистрируем плагины и реализуем функции согласно iOS и Android, сначала Android.

5.1 Регистрация ViewFactory

существуетBmsVideoPlayerPluginрегистрация классаViewFactory:new VideoViewFactory(registrar)и назовите его «plugins.bms_video_player/view»:

public static void registerWith(Registrar registrar) {
    registrar.platformViewRegistry()
             .registerViewFactory("plugins.bms_video_player/view", new VideoViewFactory(registrar));
  }

5.2 Создание VideoViewFactory

ДолженVideoViewFactoryкласс требует класса интеграцииPlatformViewFactory, который реализует функцию:create(Context context, int viewId, Object args):

public class VideoViewFactory extends PlatformViewFactory {
    private final Registrar registrar;

    public VideoViewFactory(Registrar registrar) {
        super(StandardMessageCodec.INSTANCE);
        this.registrar = registrar;
    }

    @Override
    public PlatformView create(Context context, int viewId, Object args) {
        return new VideoView(context, viewId, args, this.registrar);
    }
}

Начнем наш обед, создадим класс реализацииVideoView.

5.3 VideoView

public class VideoView implements PlatformView, MethodCallHandler  {
    private final JzvdStd jzvdStd;
    private final MethodChannel methodChannel;
    private final Registrar registrar;

    VideoView(Context context, int viewId, Object args, Registrar registrar) {
        this.registrar = registrar;
        this.jzvdStd = getJzvStd(registrar, args);
        this.methodChannel = new MethodChannel(registrar.messenger(), "bms_video_player_" + viewId);
        this.methodChannel.setMethodCallHandler(this);
    }

    @Override
    public View getView() {
        return jzvdStd;
    }

    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
        switch (methodCall.method) {
            case "loadUrl":
                String url = methodCall.arguments.toString();
                jzvdStd.setUp(url, "", Jzvd.SCREEN_NORMAL);
                break;
            default:
                result.notImplemented();
        }

    }

    @Override
    public void dispose() {}

    private JzvdStd getJzvStd(Registrar registrar, Object args) {
        JzvdStd view = (JzvdStd) LayoutInflater.from(registrar.activity()).inflate(R.layout.jz_video, null);
        return view;
    }
}

Проанализируйте код напрямую:

  1. Реализованы интерфейсы: PlatformView и MethodCallHandler, первый интерфейс "PlatformView" используется дляreturnNative View, то есть используемый нами сторонний плагин: JzvdStd. Второй интерфейс «MethodCallHandler» используется для обработки функции запроса, отправленной из Dart, такой как функция, созданная в этой статье:loadUrl
  2. здесьreturnизJzvdStd, используя xml:
<?xml version="1.0" encoding="utf-8"?>
<cn.jzvd.JzvdStd
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/jz_video"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

5.4 Внедрение сторонних подключаемых модулей

Конечно, нам нужноbuild.gradleНаконец добавьте плагин:

dependencies {
    implementation 'cn.jzvd:jiaozivideoplayer:7.0_preview'
}

На этом наша сторона Android завершена, давайте взглянем на сторону iOS.

6.1 Регистрация ViewFactoryТак же и в классеBmsVideoPlayerPluginзарегистрирован вVideoViewFactory

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  VideoViewFactory* factory =
      [[VideoViewFactory alloc] initWithMessenger:registrar.messenger];
  [registrar registerViewFactory:factory withId:@"plugins.bms_video_player/view"];
}

6.2 Создание VideoViewFactory

#import "VideoViewFactory.h"
#import "BMSVideoPlayerViewController.h"

@implementation VideoViewFactory {
  NSObject<FlutterBinaryMessenger>* _messenger;
}

- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
  self = [super init];
  if (self) {
    _messenger = messenger;
  }
  return self;
}

- (NSObject<FlutterMessageCodec>*)createArgsCodec {
  return [FlutterStandardMessageCodec sharedInstance];
}

- (nonnull NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame
                                            viewIdentifier:(int64_t)viewId
                                                 arguments:(id _Nullable)args {
    BMSVideoPlayerViewController* viewController =
      [[BMSVideoPlayerViewController alloc] initWithWithFrame:frame
                                       viewIdentifier:viewId
                                            arguments:args
                                      binaryMessenger:_messenger];
    return viewController;
}

@end

Код по-прежнему очень прост, сосредоточьтесь на том, чтобы смотреть внизBMSVideoPlayerViewController

6.3 BMSVideoPlayerViewController

#import "BMSVideoPlayerViewController.h"
#import <JPVideoPlayer/JPVideoPlayerKit.h>

@interface BMSVideoPlayerViewController ()<JPVideoPlayerDelegate>

@end

@implementation BMSVideoPlayerViewController {
    UIView * _videoView;
    int64_t _viewId;
    FlutterMethodChannel* _channel;
}

#pragma mark - life cycle

- (instancetype)initWithWithFrame:(CGRect)frame
                   viewIdentifier:(int64_t)viewId
                        arguments:(id _Nullable)args
                  binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
  if ([super init]) {
    _viewId = viewId;
    _videoView = [UIView new];
    _videoView.backgroundColor = [UIColor greenColor];
    NSDictionary *dic = args;
    CGFloat x = [dic[@"x"] floatValue];
    CGFloat y = [dic[@"y"] floatValue];
    CGFloat width = [dic[@"width"] floatValue];
    CGFloat height = [dic[@"height"] floatValue];
    _videoView.frame = CGRectMake(x, y, width, height);
    _videoView.jp_videoPlayerDelegate = self;
    NSString* channelName = [NSString stringWithFormat:@"bms_video_player_%lld", viewId];
    _channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:messenger];
    __weak __typeof__(self) weakSelf = self;
    [_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
      [weakSelf onMethodCall:call result:result];
    }];

  }
  return self;
}

- (nonnull UIView *)view {
    return _videoView;
}

- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  if ([[call method] isEqualToString:@"loadUrl"]) {
    [self onLoadUrl:call result:result];
  } else {
    result(FlutterMethodNotImplemented);
  }
}

- (void)onLoadUrl:(FlutterMethodCall*)call result:(FlutterResult)result {
  NSString* url = [call arguments];
  if (![self loadUrl:url]) {
    result([FlutterError errorWithCode:@"loadUrl_failed"
                               message:@"Failed parsing the URL"
                               details:[NSString stringWithFormat:@"URL was: '%@'", url]]);
  } else {
    result(nil);
  }
}

- (bool)loadUrl:(NSString*)url {
  NSURL* nsUrl = [NSURL URLWithString:url];
  if (!nsUrl) {
    return false;
  }
  
  [_videoView jp_playVideoWithURL:nsUrl
                           bufferingIndicator:nil
                                  controlView:nil
                                 progressView:nil
                                configuration:^(UIView *view, JPVideoPlayerModel *playerModel) {
                                    // self.muteSwitch.on = ![self.videoContainer jp_muted];
                                }];
  return true;
}

#pragma mark - JPVideoPlayerDelegate

- (BOOL)shouldAutoReplayForURL:(nonnull NSURL *)videoURL {
    return true;
}
@end

На самом деле реализация кода очень простая.Единственное отличие со стороны Android это создание контролов.Для Android я использую xml напрямую.Для iOS главное определить размер фрейма.Попробовал использовать фрейм значение, переданное функцией, но, похоже, оно не работает. . Если кто знает в чем проблема, подскажите!

Наконец, как и в случае с Android, импортируйте сторонние плагины, которые мы используем:

6.4 Знакомство с JPVideoPlayer

в файлеbms_video_player.podspecПредставлять:

s.dependency 'JPVideoPlayer'

7. Цепочка вызовов

Посмотрите на функцию обратного вызова после создания виджета в «4»:

Future<void> onPlatformViewCreated(id) async {
    if (widget.onCreated == null) {
      return;
      }
    
    widget.onCreated(new BmsVideoPlayerController.init(id));
  }

непосредственныйnew BmsVideoPlayerController.init(id), что создаетchannel:

MethodChannel _channel;
  
BmsVideoPlayerController.init(int id) {
_channel =  new MethodChannel('bms_video_player_$id');
}

Future<void> loadUrl(String url) async {
assert(url != null);
return _channel.invokeMethod('loadUrl', url);
}

имеютchannelЕстественный и нативный код объединяются, создавая одновременноloadUrlФункция вызывается внешним миром.

8. Пробное использование

Таким образом, наш плагин реализовал основные функции, напишите демо и протестируйте эффект:

import 'package:flutter/material.dart';

import 'package:bms_video_player/bms_video_player.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  var viewPlayerController;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    var x = 0.0;
    var y = 0.0;
    var width = 400.0;
    var height = width * 9.0 / 16.0;

    BmsVideoPlayer videoPlayer = new BmsVideoPlayer(
      onCreated: onViewPlayerCreated,
      x: x,
      y: y,
      width: width,
      height: height
    );
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Container(
          child: videoPlayer,
          width: width,
          height: height
        )
      ),
    );
  }

  void onViewPlayerCreated(viewPlayerController) {
    this.viewPlayerController = viewPlayerController;
    this.viewPlayerController.loadUrl("https://www.****.com/****.mp4");
  }
}

Я считаю, что этот код не нуждается в дополнительном объяснении, представьте наш виджет плагина, а затем вызовитеloadUrlфункцию, перейдите по ссылке на наше видео и начните играть.

iOS-эффект

Android-эффект

Суммировать

В первый раз, когда я использовал Flutter, в первый раз, когда я понял основные функции плагина, написание было относительно грубым, но я считаю, что все основные методы написания в нем. Следующим шагом будет реализация всех функций воспроизведения видео, таких как: пауза/воспроизведение, воспроизведение в маленьком окне, полноэкранное воспроизведение, кеширование, отключение звука и так далее.

Кроме того, как реализовать связь между Dart и нативным кодом.

Продолжение следует, ждите