omni_player 2.1.0 copy "omni_player: ^2.1.0" to clipboard
omni_player: ^2.1.0 copied to clipboard

Flutter媒体播放器插件,在Android/iOS上支持视频和音频播放和后台播放。支持MKV、MP4、HLS等.

OmniPlayer #

Flutter 跨平台音视频播放器插件,基于 media_kit(libmpv / FFmpeg)封装,专为 iOS 与 Android 生产环境设计。

核心能力:边下边播(本地代理流式缓存)、后台长时间稳定播放、锁屏 / 通知栏控制、断线自动重连、播放列表预缓存。


特性 #

播放能力 #

  • 广泛格式:MP4、MKV、AVI、MOV、FLV、WebM、MP3、AAC、FLAC、OGG、WAV 及 FFmpeg 可解码的任何格式
  • 流媒体协议:HLS(.m3u8)、DASH(.mpd)、RTSP、RTMP
  • 精确 Seek:libmpv 高精度帧级跳转,任意倍速下延迟 < 200 ms
  • 倍速播放:0.25x ~ 4.0x,倍速正确同步到锁屏进度条
  • 视频截图:调用 screenshot-raw 获取当前帧,支持 JPEG / PNG 编码输出

边下边播(Streaming Cache Proxy) #

  • 首次播放通过本地 HTTP 代理127.0.0.1)桥接远端资源
  • 代理同步下载文件到磁盘、同步服务给 libmpv,实现真正的边下边播
  • MP4 moov atom 在末尾(非 faststart)时自动透传 Range 请求,不影响主下载
  • 下载完成后原子重命名写入缓存,下次播放直接读本地 file://,完全无网络请求

后台稳定播放 #

  • iOS:所有播放请求发往 127.0.0.1(loopback),iOS 永不回收本地连接,彻底规避 ENOTCONN / ffurl_read 错误
  • Android:前台服务(Foreground Service)持续持有 CPU 唤醒锁,回前台时自动修复黑屏问题(EGL context 重建)
  • 网络错误时指数退避自动重连(1 → 2 → 4 → 8 → 16 秒,最多 5 次),重连后从断点续播

系统集成 #

  • iOSAVAudioSession.playback 类别)+ MPRemoteCommandCenter + MPNowPlayingInfoCenter
  • AndroidMediaSessionCompat + MediaStyle 通知栏,支持播放 / 暂停 / 上一首 / 下一首 / 进度拖动
  • 锁屏大图、标题、艺术家、进度、倍速均正确同步

缓存管理 #

  • LRU 淘汰策略,总大小超过上限时自动删除最久未访问的条目
  • 防抖元数据写入(500 ms debounce),避免频繁 I/O
  • 支持自定义缓存目录、动态调整容量上限、单条 / 全量清理

API 设计 #

  • 与 media_kit player.state.* / player.stream.* 命名完全一致,零改动迁移
  • 单例 OmniPlayer.instance,全局唯一,自动管理生命周期

平台支持 #

平台 播放引擎 视频渲染 后台 / 控制
iOS ≥ 12.0 libmpv (FFmpeg) Metal Texture AVAudioSession + MPRemoteCommandCenter
Android API ≥ 21 libmpv (FFmpeg) OpenGL ES Texture Foreground Service + MediaSessionCompat

仅支持 iOS 与 Android,不支持 macOS、Windows、Web。


安装 #

dependencies:
  omni_player: ^2.0.0

  # 消费端 App 必须引入媒体解码库(包含 FFmpeg)
  media_kit_libs_video: ^1.0.4
flutter pub get

平台配置 #

iOS — Info.plist #

<!-- 后台音频播放(必须) -->
<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
</array>

<!-- 播放 HTTP 明文资源时添加(可选) -->
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
</dict>

Android — AndroidManifest.xml #

插件已在自身 manifest 内声明前台服务与 MediaSession,宿主 App 只需保留网络权限:

<uses-permission android:name="android.permission.INTERNET" />

快速开始 #

1. 全局初始化 #

main() 中调用一次,早于任何播放操作:

import 'package:media_kit/media_kit.dart';
import 'package:omni_player/omni_player.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  MediaKit.ensureInitialized(); // media_kit 要求
  runApp(const MyApp());
}

2. 初始化播放器 #

在页面 initState 中调用,传入缓存配置以启用边下边播:

final player = OmniPlayer.instance;

await player.initialize(
  cacheConfig: const CacheConfig(
    enabled: true,
    maxSize: 500 * 1024 * 1024, // 500 MB
  ),
);

3. 打开媒体 #

await player.open(
  const MediaItem(
    url: 'https://example.com/video.mp4',
    title: '示例视频',
    artist: '作者',
    coverUrl: 'https://example.com/cover.jpg',
    isVideo: true,
    headers: {
      'Authorization': 'Bearer token', // 可选的 HTTP 请求头
    },
  ),
  autoPlay: true,
);

缓存行为:

  • 已缓存 → 直接读本地 file://,无网络请求
  • 未缓存 → 通过本地代理边下边播,下载完成后自动写入缓存
  • skipCache: true → 强制走网络,忽略缓存

4. 视频渲染 #

使用内置 VideoWidget,自带加载指示器:

VideoWidget(
  player: OmniPlayer.instance,
  fit: BoxFit.contain,         // 填充模式
  backgroundColor: Colors.black,
)

或使用 media_kit 原生 Video Widget(需要 player.videoController):

import 'package:media_kit_video/media_kit_video.dart';

Video(controller: player.videoController!)

5. 播放控制 #

await player.play();
await player.pause();
await player.stop();
await player.seek(const Duration(seconds: 30));
await player.setVolume(80.0);      // 0.0 ~ 100.0
await player.setRate(1.5);         // 倍速,同 media_kit setRate()
await player.setSpeed(1.5);        // setRate() 的别名
await player.setLooping(true);

截取当前视频帧(media_kit 1.2.6+ 内置,委托 libmpv screenshot-raw):

final bytes = await player.captureVideoFrame(); // JPEG(默认)
// or
final pngBytes = await player.captureVideoFrame(format: 'image/png');
// or 获取 BGRA 裸像素数据
final rawPixels = await player.captureVideoFrame(format: null);

if (bytes != null) {
  await File('frame.jpg').writeAsBytes(bytes);
}

6. 状态读取 #

快照方式(与 media_kit player.state.* 一致):

final player = OmniPlayer.instance;

player.state.playing           // bool
player.state.buffering         // bool
player.state.completed         // bool
player.state.position          // Duration
player.state.duration          // Duration
player.state.buffer            // Duration(已缓冲时长)
player.state.bufferingPercentage // double 0.0~1.0
player.state.volume            // double 0~100
player.state.rate              // double
player.state.width             // int?
player.state.height            // int?
player.state.error             // String
player.state.status            // PlaybackStatus 枚举

流订阅方式(与 media_kit player.stream.* 一致):

player.stream.playing.listen((v) => print('playing: $v'));
player.stream.position.listen((p) => print('pos: $p'));
player.stream.duration.listen((d) => print('dur: $d'));
player.stream.buffering.listen((v) => print('buffering: $v'));
player.stream.bufferingPercentage.listen((p) => print('buf: ${(p*100).toInt()}%'));
player.stream.completed.listen((v) { if (v) playNext(); });
player.stream.error.listen((e) => print('error: $e'));
player.stream.status.listen((s) => print('status: $s'));

// 锁屏 / 通知栏的上一首 / 下一首命令(media_kit 无对应)
player.stream.previousTrack.listen((_) => playPrevious());
player.stream.nextTrack.listen((_) => playNext());

7. 播放列表预缓存 #

在播放当前视频时,提前缓存下一首,实现切换时秒开:

// 开始播放 videos[index] 后立即预缓存 videos[index+1]
player.preCache(videos[index + 1].url);

preCache 通过本地代理在后台下载,不影响当前播放,完成后自动写入 LRU 缓存。

8. 缓存管理 #

final size = await player.getCacheSizeString(); // "128.5 MB"
final bytes = await player.getCacheSize();       // int(字节)

await player.clearCacheItem('https://example.com/video.mp4');
await player.clearCache(); // 清空所有缓存

9. 释放资源 #

// 通常在页面 dispose() 中调用
await player.dispose();

API 参考 #

OmniPlayer #

方法 / 属性 类型 说明
OmniPlayer.instance OmniPlayer 获取全局单例
initialize({CacheConfig?}) Future<void> 初始化播放器、原生服务、缓存代理
open(MediaItem, {autoPlay, skipCache, audioOnly}) Future<void> 打开并(可选)自动播放媒体
play() Future<void> 播放
pause() Future<void> 暂停
stop() Future<void> 停止并清除当前媒体
seek(Duration) Future<void> 跳转到指定位置
setVolume(double) Future<void> 设置音量(0.0 ~ 100.0)
setRate(double) Future<void> 设置倍速(对应 media_kit setRate()
setSpeed(double) Future<void> setRate() 的别名
setLooping(bool) Future<void> 是否循环播放
setPositionUpdateInterval(Duration) Future<void> 设置进度推送频率(默认 500ms)
captureVideoFrame({String? format}) Future<Uint8List?> 截取当前视频帧;format 支持 'image/jpeg'(默认)、'image/png'null(BGRA 裸数据)。纯音频返回 null
preCache(String url, {Map<String,String>? headers}) void 后台预缓存指定 URL
keepAudioSessionAlive() void 手动唤醒 iOS AVAudioSession(一般无需调用)
state OmniPlayerState 当前状态快照(media_kit 兼容命名)
stream OmniPlayerStream 事件流集合(media_kit 兼容命名)
position Duration 当前播放位置
duration Duration 媒体总时长
buffered double 缓冲进度 0.0 ~ 1.0
currentItem MediaItem? 当前媒体
videoController VideoController? media_kit VideoController(供 Video Widget 使用)
cacheManager CacheManager? 缓存管理器(直接操作缓存时使用)
getCacheSize() Future<int> 缓存大小(字节)
getCacheSizeString() Future<String> 缓存大小(可读字符串,如 "128.5 MB")
clearCache() Future<void> 清空全部缓存
clearCacheItem(String url) Future<void> 清除指定 URL 的缓存
dispose() Future<void> 释放全部资源

MediaItem #

参数 类型 必填 说明
url String 媒体地址(http://https://file://
title String 标题(显示在锁屏 / 通知栏)
artist String? 艺术家
album String? 专辑
coverUrl String? 封面图 URL(锁屏大图)
isVideo bool 是否为视频(默认 false);true 时进后台自动切音频轨
headers Map<String,String>? 自定义 HTTP 请求头

CacheConfig #

参数 类型 默认值 说明
enabled bool true 是否启用缓存与边下边播代理
maxSize int 500 MB 最大缓存空间(字节),超出时 LRU 淘汰
customDirectory String? null 自定义缓存目录;null 使用系统应用缓存目录

PlaybackStatus 枚举 #

说明
idle 未加载任何媒体
loading 正在加载 / 缓冲
playing 正在播放
paused 已暂停
stopped 已停止
completed 播放完毕
error 发生错误

边下边播原理 #

open(url)
  ├─ 已缓存 → file:// 直接播放(零网络请求)
  └─ 未缓存 → http://127.0.0.1:{port}/?u={url}
                       │
                  StreamingProxy
                  ┌────────────────────────────────────┐
                  │  ① 向远端发起单次 HTTP 下载          │
                  │  ② 同步写入 cacheDir/hash.ext.tmp   │ ← 边存
                  │  ③ 从 tmp 文件服务给 libmpv          │ ← 边播
                  │  ④ moov 乱序请求 → 直接透传远端      │
                  │  ⑤ 完成后原子重命名 → hash.ext       │
                  │  ⑥ 通知 CacheManager 建立 LRU 索引  │
                  └────────────────────────────────────┘
下次播放 → 命中缓存 → file:// 直接播放

为什么用 loopback 代理解决 iOS 后台断连:

iOS 在 App 进入后台后会回收非 loopback 的 TCP 连接,libmpv 直连远端时会收到 ENOTCONN(错误码 0xFFFFFC7)导致播放中断。代理运行在 127.0.0.1,iOS 永不回收 loopback 连接;远端的 TCP 由代理管理,即使被系统回收也由代理自行重连,libmpv 完全感知不到网络波动。


注意事项 #

  • media_kit_libs_video 必须由消费端 App 引入,插件本身不内嵌 FFmpeg 库(避免重复打包增大体积)。
  • 缓存与边下边播仅对 http:// / https:// 点播资源生效;HLS(.m3u8)、DASH(.mpd)、RTSP、RTMP 不缓存(分片流不适合整文件缓存)。
  • isVideo: true 时进后台自动禁用视频轨道(保留音频)、回前台自动恢复;isVideo: false 无此切换开销,适用于纯音频场景。
  • setVolume 参数范围 0.0 ~ 100.0(与 media_kit 一致),而非 0.0 ~ 1.0。
  • Android 最低 API 21,iOS 最低 12.0。
2
likes
110
points
90
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter媒体播放器插件,在Android/iOS上支持视频和音频播放和后台播放。支持MKV、MP4、HLS等.

Homepage

License

MIT (license)

Dependencies

crypto, flutter, media_kit, media_kit_video, path_provider, plugin_platform_interface

More

Packages that depend on omni_player

Packages that implement omni_player