Flutter - 使用Pigeon实现视频缓存插件 🐌
# 一、概述
Pigeon
是一个可以帮助我们生成 Flutter
与 原生
的通信代码的工具,我们只需要关注其两侧主要的数据处理逻辑即可,从而提升效率。
Flutter
端对于视频缓存功能主要还是依赖原生端比较成熟的实现方案,如下两个开源库
- iOS: https://github.com/ChangbaDevs/KTVHTTPCache (opens new window)
- 安卓: https://github.com/danikula/AndroidVideoCache (opens new window)
其功能是:丢给它一个视频链接,它将生成一个具备缓存功能的播放代理链接。
接下来我们一起看看,如何使用 Pigeon
并结合上述两个库来实现视频缓存插件。
# 二、创建 Plugin
使用如下命令生成插件项目,这里我指定iOS使用的是 Swift
,安卓使用的是 Kotlin
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
。
#
# 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
文件,添加
...
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.gradle
和 settings.gradle
文件添加如下 maven
,否则会找不到依赖库
// build.gradle
allprojects {
repositories {
+ maven { url "https://jitpack.io" }
+ maven { url 'https://maven.aliyun.com/repository/public' }
google()
mavenCentral()
}
}
...
// settings.gradle
pluginManagement {
...
repositories {
+ maven { url "https://jitpack.io" }
+ maven { url 'https://maven.aliyun.com/repository/public' }
google()
mavenCentral()
gradlePluginPortal()
}
}
...
# 四、Pigeon
# 添加依赖
在 pubspec.yaml
的 dev_dependencies
下添加 pigeon
依赖
dev_dependencies:
pigeon: ^17.3.0
# 定义通信接口
在 lib
目录外创建一个用来定义通信接口的 dart
文件。
这里我们新建了一个与 lib
目录同级的 pigeons
文件夹,来存放与 Pigeon
相关的文件
.
├── lib
│ ├── ...
│ └── ...
├── pigeons
│ ├── cache.dart
│ └── ...
├── ...
cache.dart
就是我用来定义视频缓存功能相关的通信接口的文件。
import 'package:pigeon/pigeon.dart';
// https://github.com/flutter/packages/blob/main/packages/pigeon/example/README.md
(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',
))
()
abstract class LXFVideoCacheHostApi {
/// 转换为缓存代理URL
String convertToCacheProxyUrl(String url);
}
# 生成交互代码
再执行如下命令,指定根据 cache.dart
来生成相应的繁杂且重要的交互代码。
flutter pub run pigeon --input pigeons/cache.dart
# 坑点
一定一定,一定要自定义 kotlinOptions
里的 errorClassName
,不然它会给你生成默认的 FlutterError
,单单自己的插件编译可能不会怎样,但是一旦集成的项目里也有用到其它用 Pigeon
生成了 FlutterError
的插件时,就会报如下错误了
Type FlutterError is defined multiple times
自定义 kotlinOptions
里的 errorClassName
:
(PigeonOptions(
...
kotlinOptions: KotlinOptions(
// https://github.com/fluttercommunity/wakelock_plus/issues/18
errorClassName: "LXFVideoCacheFlutterError"
),
...
))
# 五、编写原生代码
# iOS
进入到 example/ios
目录下,安装依赖
cd example/ios
pod install --repo-update
使用 Xcode
打开 Runner.xcworkspace
开始编写原生代码
// 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
项目开始编写原生代码
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()
}
}
# 六、开源库
上述视频缓存插件已开源,并发布至 GitHub
:https://github.com/LinXunFeng/flutter_video_cache (opens new window)
你可以通过如下步骤集成使用:
在 pubspec.yaml
中添加 video_cache
依赖
dependencies:
video_cache: latest_version
使用
// 导入
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
,而如果你如果在原生端去调用 Flutter
的 api
,则使用 @FlutterApi
去标注相关抽象类即可,使用方法是差不多的。
需要注意的是,当你使用 Swift
去写插件,且使用了 @FlutterApi
去生成相应的原生代码后编译,可能会遇到这个错误
type 'FlutterError' does not conform to protocol 'Error'
添加如下拓展即可
// https://github.com/flutter/flutter/issues/136081
extension FlutterError: Error {}
# 八、资料
- 01
- Flutter - 危!3.24版本苹果审核被拒!11-13
- 02
- Flutter - 轻松搞定炫酷视差(Parallax)效果09-21
- 03
- Flutter - 轻松实现PageView卡片偏移效果09-08