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

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

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

一、概述

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

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

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

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

二、创建 Plugin

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

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

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 文件,添加

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,否则会找不到依赖库

diff 复制代码
// build.gradle

allprojects {
    repositories {
+        maven { url "https://jitpack.io" }
+        maven { url 'https://maven.aliyun.com/repository/public' }
        google()
        mavenCentral()
    }
}
...
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 依赖

yaml 复制代码
dev_dependencies:
  pigeon: ^17.3.0

定义通信接口

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

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

shell 复制代码
.
├── lib
│   ├── ...
│   └── ...
├── pigeons
│   ├── cache.dart
│   └── ...
├── ...

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

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 来生成相应的繁杂且重要的交互代码。

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

坑点

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

shell 复制代码
Type FlutterError is defined multiple times

自定义 kotlinOptions 里的 errorClassName

dart 复制代码
@ConfigurePigeon(PigeonOptions(
  ...
  kotlinOptions: KotlinOptions(
    // https://github.com/fluttercommunity/wakelock_plus/issues/18
    errorClassName: "LXFVideoCacheFlutterError"
  ),
  ...
))

五、编写原生代码

iOS

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

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

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

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 项目开始编写原生代码

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 依赖

yaml 复制代码
dependencies:
  video_cache: latest_version

使用

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 去生成相应的原生代码后编译,可能会遇到这个错误

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

添加如下拓展即可

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

八、资料

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

相关推荐
AAI机器之心1 小时前
LLM大模型:开源RAG框架汇总
人工智能·chatgpt·开源·大模型·llm·大语言模型·rag
杨荧2 小时前
【JAVA开源】基于Vue和SpringBoot的洗衣店订单管理系统
java·开发语言·vue.js·spring boot·spring cloud·开源
FIT2CLOUD飞致云7 小时前
测试管理新增视图与高级搜索功能,测试计划支持一键生成缺陷详情,MeterSphere开源持续测试工具v3.3版本发布
开源·接口测试·metersphere·团队协作·持续测试·测试管理
helloxmg7 小时前
鸿蒙harmonyos next flutter通信之MethodChannel获取设备信息
flutter
helloxmg8 小时前
鸿蒙harmonyos next flutter混合开发之开发package
flutter·华为·harmonyos
杨荧9 小时前
【JAVA开源】基于Vue和SpringBoot的旅游管理系统
java·vue.js·spring boot·spring cloud·开源·旅游
西柚与蓝莓12 小时前
任务【浦语提示词工程实践】
github
Good_Starry18 小时前
Git介绍--github/gitee/gitlab使用
git·gitee·gitlab·github
云端奇趣1 天前
探索 3 个有趣的 GitHub 学习资源库
经验分享·git·学习·github
杨荧1 天前
【JAVA开源】基于Vue和SpringBoot的水果购物网站
java·开发语言·vue.js·spring boot·spring cloud·开源