Flutter - 使用Pigeon实现视频缓存插件 🐌

欢迎关注微信公众号:FSA全栈行动 👋

BiliBili: www.bilibili.com/video/BV1mt…

一、概述

Pigeon 是一个可以帮助我们生成 Flutter原生 的通信代码的工具,我们只需要关注其两侧主要的数据处理逻辑即可,从而提升效率。

Flutter 端对于视频缓存功能主要还是依赖原生端比较成熟的实现方案,如下两个开源库

其功能是:丢给它一个视频链接,它将生成一个具备缓存功能的播放代理链接。

接下来我们一起看看,如何使用 Pigeon 并结合上述两个库来实现视频缓存插件。

二、创建 Plugin

使用如下命令生成插件项目,这里我指定iOS使用的是 Swift,安卓使用的是 Kotlin

1
2
3
4
shell复制代码flutter create --template=plugin --platforms=android,ios -i swift -a kotlin 项目名

# 如:
# flutter create --template=plugin --platforms=android,ios -i swift -a kotlin video_cache

三、原生依赖

iOS

打开在 ios 目录下的 podspec 文件(这里是 video_cache.podspec),添加相关的第三方库依赖,比如我这里依赖的是 KTVHTTPCache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
diff复制代码#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
# Run `pod lib lint video_cache.podspec` to validate before publishing.
#
Pod::Spec.new do |s|
s.name = 'video_cache'
s.version = '0.0.1'
s.summary = 'A new Flutter plugin project.'
s.description = <<-DESC
A new Flutter plugin project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.platform = :ios, '11.0'

# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'

+ # KTVHTTPCache
+ s.dependency 'KTVHTTPCache', '~> 3.0.0'
end

安卓

打开在 android 目录下的 build.gradle 文件,添加

1
2
3
4
5
6
7
8
9
10
11
12
diff复制代码...
android {
...

dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.mockito:mockito-core:5.0.0'
+ implementation 'com.danikula:videocache:2.7.1'
}

...
}

然后在 example/android 目录下的 build.gradlesettings.gradle 文件添加如下 maven,否则会找不到依赖库

1
2
3
4
5
6
7
8
9
10
11
diff复制代码// build.gradle

allprojects {
repositories {
+ maven { url "https://jitpack.io" }
+ maven { url 'https://maven.aliyun.com/repository/public' }
google()
mavenCentral()
}
}
...
1
2
3
4
5
6
7
8
9
10
11
12
13
diff复制代码// settings.gradle

pluginManagement {
...
repositories {
+ maven { url "https://jitpack.io" }
+ maven { url 'https://maven.aliyun.com/repository/public' }
google()
mavenCentral()
gradlePluginPortal()
}
}
...

四、Pigeon

添加依赖

pubspec.yamldev_dependencies 下添加 pigeon 依赖

1
2
yaml复制代码dev_dependencies:
pigeon: ^17.3.0

定义通信接口

lib 目录外创建一个用来定义通信接口的 dart 文件。

这里我们新建了一个与 lib 目录同级的 pigeons 文件夹,来存放与 Pigeon 相关的文件

1
2
3
4
5
6
7
8
shell复制代码.
├── lib
│   ├── ...
│   └── ...
├── pigeons
│   ├── cache.dart
│   └── ...
├── ...

cache.dart 就是我用来定义视频缓存功能相关的通信接口的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
dart复制代码import 'package:pigeon/pigeon.dart';

// https://github.com/flutter/packages/blob/main/packages/pigeon/example/README.md
@ConfigurePigeon(PigeonOptions(
dartOut: 'lib/plugin/pigeon.g.dart',
kotlinOut:
'android/src/main/kotlin/com/lxf/video_cache/VideoCacheGeneratedApis.g.kt',
kotlinOptions: KotlinOptions(
// https://github.com/fluttercommunity/wakelock_plus/issues/18
errorClassName: "LXFVideoCacheFlutterError",
),
swiftOut: 'ios/Classes/LXFVideoCacheGeneratedApis.g.swift',
))
@HostApi()
abstract class LXFVideoCacheHostApi {
/// 转换为缓存代理URL
String convertToCacheProxyUrl(String url);
}

生成交互代码

再执行如下命令,指定根据 cache.dart 来生成相应的繁杂且重要的交互代码。

1
shell复制代码flutter pub run pigeon --input pigeons/cache.dart

坑点

一定一定,一定要自定义 kotlinOptions 里的 errorClassName,不然它会给你生成默认的 FlutterError,单单自己的插件编译可能不会怎样,但是一旦集成的项目里也有用到其它用 Pigeon 生成了 FlutterError 的插件时,就会报如下错误了

1
shell复制代码Type FlutterError is defined multiple times

自定义 kotlinOptions 里的 errorClassName

1
2
3
4
5
6
7
8
dart复制代码@ConfigurePigeon(PigeonOptions(
...
kotlinOptions: KotlinOptions(
// https://github.com/fluttercommunity/wakelock_plus/issues/18
errorClassName: "LXFVideoCacheFlutterError"
),
...
))

五、编写原生代码

iOS

进入到 example/ios 目录下,安装依赖

1
2
shell复制代码cd example/ios 
pod install --repo-update

使用 Xcode 打开 Runner.xcworkspace 开始编写原生代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
swift复制代码// VideoCachePlugin.swift

import Flutter
import UIKit
import KTVHTTPCache

// 创建插件时自动生成的类
public class VideoCachePlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
// 注册实现
LXFVideoCacheHostApiSetup.setUp(
binaryMessenger: registrar.messenger(),
api: LXFVideoCacheHostApiImplementation()
)
}
}

class LXFVideoCacheHostApiImplementation: LXFVideoCacheHostApi {
/// 是否可以代理
private var canProxy: Bool?

func convertToCacheProxyUrl(url: String) throws -> String {
// 还未试过开启代理服务
if (self.canProxy == nil) {
self.canProxy = ((try? KTVHTTPCache.proxyStart()) != nil)
}
// 无法代理
if !self.canProxy! { return url }
// 无法转 URL 对象
guard let urlObj = URL(string: url) else { return url }

guard let proxyUrlObj = KTVHTTPCache.proxyURL(withOriginalURL: urlObj) else {
// 代理失败
return url
}
// 代理成功
return proxyUrlObj.absoluteString
}
}

安卓

使用 AndroidStudio 打开 example/android,找到外层的 android 项目开始编写原生代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
kotlin复制代码package com.lxf.video_cache

import LXFVideoCacheHostApi
import com.danikula.videocache.HttpProxyCacheServer

import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result

/** VideoCachePlugin */
class VideoCachePlugin : FlutterPlugin, MethodCallHandler {

private lateinit var videoCacheHostApiImplementation: LXFVideoCacheHostApiImplementation

override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
videoCacheHostApiImplementation = LXFVideoCacheHostApiImplementation(flutterPluginBinding)
// 初始化插件
LXFVideoCacheHostApi.setUp(
flutterPluginBinding.binaryMessenger,
videoCacheHostApiImplementation,
)
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
// 关闭服务
videoCacheHostApiImplementation.shutdown()
}

override fun onMethodCall(call: MethodCall, result: Result) {}
}

class LXFVideoCacheHostApiImplementation(
private val flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
) : LXFVideoCacheHostApi {
/// 懒加载缓存服务
private val cacheServer by lazy { HttpProxyCacheServer.Builder(flutterPluginBinding.applicationContext).build() }

/// 重写并通过 cacheServer 将原 url 转换为具备缓存功能的 url
override fun convertToCacheProxyUrl(url: String): String {
return cacheServer.getProxyUrl(url)
}

/// 关闭服务
fun shutdown() {
cacheServer.shutdown()
}
}

六、开源库

上述视频缓存插件已开源,并发布至 GitHubgithub.com/LinXunFeng/…

你可以通过如下步骤集成使用:

pubspec.yaml 中添加 video_cache 依赖

1
2
yaml复制代码dependencies:
video_cache: latest_version

使用

1
2
3
4
5
6
7
8
9
dart复制代码// 导入
import 'package:video_cache/video_cache.dart';

// 将原视频链接转为缓存代理链接
String url = 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4';
url = await VideoCache().convertToCacheProxyUrl(url);

// url转换结果
// http://localhost:50050/https%3A%2F%2Fflutter%2Egithub%2Eio%2Fassets%2Dfor%2Dapi%2Ddocs%2Fassets%2Fvideos%2Fbee%2Emp4/KTVHTTPCachePlaceHolder/KTVHTTPCacheLastPathComponent.mp4

然后把转换后的 url 丢给播放器就可以了~

七、结尾

以上就是 Flutter 与原生交互拿到代理 url 的例子,使用的是 @HostApi,而如果你如果在原生端去调用 Flutterapi,则使用 @FlutterApi 去标注相关抽象类即可,使用方法是差不多的。

需要注意的是,当你使用 Swift 去写插件,且使用了 @FlutterApi 去生成相应的原生代码后编译,可能会遇到这个错误

1
bash复制代码type 'FlutterError' does not conform to protocol 'Error'

添加如下拓展即可

1
2
swift复制代码// https://github.com/flutter/flutter/issues/136081
extension FlutterError: Error {}

八、资料

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有 iOS 技术,还有 AndroidFlutterPython 等文章, 可能有你想要了解的技能知识点哦~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%