Когда команда готовилась к приложению, мы нацелены на трепетание, тем более что популярность трепетара в последнее время увеличивается. Поскольку я впервые использовал трепетание, я хотел улучшить свои способности через свою собственную практику.
При создании приложения мы использовали видеоплеер, в настоящее время используя официальный плагин «Video_Player»GitHub.com/flutter/running…, за границей с этим плагином может и не быть проблем, но многие отечественные видеоплееры очень хорошо сделаны, а пользовательские функции очень полны.
Чтобы привести пример: когда домашние приложения воспроизводят видео в полноэкранном режиме, почти все они горизонтальные и полноэкранные, но официальный плагин работает вертикально на стороне iOS, и эффект очень плохой.
Поэтому я хотел сделать плагин для воспроизведения видео:
Требовать
- И Android, и iOS используют нативную разработку, и это хорошо;
- Используйте верхнюю стороннюю стороннюю табличку 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
Есть несколько параметров, таких как:
- viewType: используется для различения разных имен плагинов и источников;
- onPlatformViewCreated: используется для вызова своей функции после создания виджета (
onPlatformViewCreated
); - 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;
}
}
Проанализируйте код напрямую:
- Реализованы интерфейсы: PlatformView и MethodCallHandler, первый интерфейс "PlatformView" используется для
return
Native View, то есть используемый нами сторонний плагин: JzvdStd. Второй интерфейс «MethodCallHandler» используется для обработки функции запроса, отправленной из Dart, такой как функция, созданная в этой статье:loadUrl
- здесь
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 и нативным кодом.
Продолжение следует, ждите