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.gradle 和 settings.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.yaml 的 dev_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()
    }
}

六、开源库

上述视频缓存插件已开源,并发布至 GitHub:github.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,而如果你如果在原生端去调用 Flutter 的 api,则使用 @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 技术,还有 Android,Flutter,Python 等文章, 可能有你想要了解的技能知识点哦~

相关推荐
沐曦股份MetaX4 分钟前
沐曦股份携手FlagOS完成面壁智能MiniCPM-V4.7端侧模型Day 0适配
科技·搜索引擎·自然语言处理·开源·开源软件·gpu算力
thinking_talk1 小时前
开源AI记忆框架选型与对比
人工智能·开源·ai记忆
分布式存储与RustFS1 小时前
RustFS 上生产第一步:从单机到 4 节点多盘集群(MNMD)部署实操
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
FungLeo1 小时前
成为全栈·Flutter App 篇·内置 WebView:网页历史、App 返回与外链安全
flutter·网络安全·移动开发·webview·路由管理·成为全栈
u1301301 小时前
GitHub 热榜项目:日榜(2026-10-14)
人工智能·github
openEuler社区2 小时前
2026年度openEuler社区贡献之星评选启动!
开源·openeuler
qq_401700412 小时前
CAN 开源库大全(嵌入式/上位机,适配STM32、Linux、Qt,附带Github地址)
linux·stm32·开源
自律懒人2 小时前
REA 从零上手:81K Star 逆向工具,5 步接进 AI 编程 Agent
开源
java_logo2 小时前
Docker 部署 Halo:轻松搭建开源个人博客
docker·容器·开源·博客·开源博客·halo·轩辕镜像
一次旅行2 小时前
2026.10.13 AI产业深度解读|硬件互联标准更新;Agent工程启示;开源社区AI政策转向;游戏与科研AI双线突破
人工智能·游戏·开源