Initial commit
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
# media3_engine
|
||||
|
||||
`smplayer` 项目本地 Flutter plugin。**仅 Android**,包名 `com.smplayer.media3_engine`,
|
||||
不发布到 pub.dev。Dart 侧通过约定好的 MethodChannel / EventChannel 与 native 通讯。
|
||||
|
||||
引擎实现位于主项目:
|
||||
- Dart:`lib/core/infra/player_engine/media3_player_engine.dart`
|
||||
- Kotlin:`plugins/media3_engine/android/src/main/kotlin/com/smplayer/media3_engine/`
|
||||
|
||||
## 版本锁定
|
||||
|
||||
| 组件 | 版本 |
|
||||
|------|------|
|
||||
| androidx.media | `1.10.1` |
|
||||
| FFmpeg | `release/6.0` |
|
||||
| Android NDK | `r27d` LTS (27.3.13750724) |
|
||||
| Android `minSdk` | 24 |
|
||||
| Android `compileSdk` | 36 |
|
||||
| AGP / Kotlin / JDK | 8.11.1 / 2.2.20 / 17 |
|
||||
| ABI | **arm64-v8a only**(包体最小化) |
|
||||
| License | LGPL(不开 `--enable-gpl` / `--enable-nonfree`) |
|
||||
|
||||
升 Media3 / FFmpeg 版本前,先核对 `androidx/media @ <tag>/libraries/decoder_ffmpeg/README.md`
|
||||
推荐的 NDK 与 FFmpeg branch,再调整 `scripts/build_media3_ffmpeg.sh` 默认值。
|
||||
|
||||
## 通道协议
|
||||
|
||||
### MethodChannel `smplayer/media3_engine`
|
||||
|
||||
| 方法 | 参数 | 返回 |
|
||||
|------|------|------|
|
||||
| `create` | – | `{playerId:int, textureId:int, ffmpegAvailable:bool, ffmpegVersion:string}` |
|
||||
| `open` | `playerId, url, headers, startMs, play, generation` | 首次 `STATE_READY` 或 `PlaybackException` 后 complete |
|
||||
| `play` / `pause` | `playerId` | – |
|
||||
| `stop` | `playerId, generation` | – |
|
||||
| `seek` | `playerId, positionMs` | – |
|
||||
| `setRate` | `playerId, rate` (float) | – |
|
||||
| `setVolume` | `playerId, volume` (0..200) | – |
|
||||
| `setAudioTrack` | `playerId, id` (`audio:<g>:<t>` / `auto` / `no`) | – |
|
||||
| `setSubtitleTrack` | `playerId, id` (`text:<g>:<t>` / `auto` / `no`) | – |
|
||||
| `setSubtitleStyle` | `playerId, fontSize, bgOpacity, position` | – (首版 no-op,cue 由 Dart 渲染) |
|
||||
| `getProperty` | `playerId, name` | `string`(`ffmpegAvailable` / `ffmpegVersion` / `audioRenderer` / `playbackState`) |
|
||||
| `dispose` | `playerId` | – |
|
||||
|
||||
### EventChannel `smplayer/media3_engine/events`
|
||||
|
||||
所有事件载荷形如 `{playerId, generation, type, ...}`,Dart 侧按 `playerId` 过滤、
|
||||
按 `generation` 丢弃过期事件。
|
||||
|
||||
| `type` | payload |
|
||||
|--------|---------|
|
||||
| `position` | `{positionMs, bufferMs}` |
|
||||
| `duration` | `{durationMs}` |
|
||||
| `playing` | `{playing}` |
|
||||
| `completed` | `{}` |
|
||||
| `videoSize` | `{width, height}` |
|
||||
| `tracks` | `{audio:[{id,index,title,language}], subtitle:[…]}` |
|
||||
| `subtitle` | `{lines:[string]}` |
|
||||
| `volume` | `{volume}`(保留,目前 native 不主动 emit) |
|
||||
| `error` | `{code, message}` |
|
||||
|
||||
Generation token 语义:Dart 端每次 `open / stop / dispose` ++ generation,native
|
||||
回调先比对此值,过期事件丢弃。这保证了 cancel / reopen 时不会有迟到的 STATE_READY
|
||||
污染新 session 的状态。
|
||||
|
||||
## FFmpeg 软解 codec 列表
|
||||
|
||||
`scripts/build_media3_ffmpeg.sh` 的 `ENABLED_DECODERS` 默认:
|
||||
|
||||
| 解码器名(FFmpeg) | 实际格式 |
|
||||
|-------------------|----------|
|
||||
| `dca` | DTS / DTS-HD / DTS-HD MA |
|
||||
| `ac3` | AC3 |
|
||||
| `eac3` | E-AC3 (Dolby Digital Plus) |
|
||||
| `truehd` | Dolby TrueHD(含 Atmos 元数据,但 Atmos 渲染依赖宿主 audio sink) |
|
||||
| `opus` | Opus |
|
||||
| `vorbis` | Vorbis |
|
||||
| `flac` | FLAC |
|
||||
|
||||
视频解码不在范围——交给平台 MediaCodec 硬解。容器解析与音频帧 parser 由 ExoPlayer
|
||||
自身处理,FFmpeg 扩展只补 decoder(无需 `--enable-parser` / `--enable-demuxer`)。
|
||||
|
||||
## 编译 AAR
|
||||
|
||||
> **前置硬约束**:`android/app/libs/media3-decoder-ffmpeg-1.10.1-arm64.aar` 是
|
||||
> Android 构建的硬依赖。仓库默认不提交该 AAR 二进制;缺文件时 plugin 的
|
||||
> `preBuild` 任务 `verifyMedia3FfmpegAar` 会 fail-fast 抛错并给出本节的恢复
|
||||
> 指令。CI / Clean checkout 都必须先编出 AAR 才能 `flutter build apk`。
|
||||
|
||||
### 前置
|
||||
|
||||
- macOS / Linux host(Apple Silicon 建议通过 Docker `linux-x86_64` 容器编译以规避
|
||||
Media3 README 未文档化的 darwin-arm64 行为)
|
||||
- Android NDK r27d (LTS):从 https://developer.android.com/ndk/downloads 安装;Media3 1.10.1 README 实测版本是 r26b,r27 ABI 兼容、社区已验证可编
|
||||
- `perl`, `make`, `yasm`, `git`, `python3`, `shasum`(macOS 自带;Linux 需手装 yasm)
|
||||
- JDK 17 + Android SDK(命令行工具足够,Android Studio 非必需)
|
||||
|
||||
### 步骤
|
||||
|
||||
```sh
|
||||
# 1. 设置 NDK 路径(或用 --ndk-path 参数)
|
||||
export ANDROID_NDK_HOME=/path/to/android-ndk-r27d
|
||||
|
||||
# 2. 跑脚本(首次约 30-60 分钟,取决于 host CPU / IO)
|
||||
./scripts/build_media3_ffmpeg.sh
|
||||
|
||||
# 3. 产物
|
||||
# android/app/libs/media3-decoder-ffmpeg-1.10.1-arm64.aar
|
||||
# android/app/libs/CHECKSUMS.txt 自动追加 / 替换该 AAR 的 SHA-256
|
||||
#
|
||||
# 4. 核对 SHA-256 → git add 上述两个文件 → flutter build apk --debug 验证加载
|
||||
```
|
||||
|
||||
### 重新编译 / 升级版本
|
||||
|
||||
```sh
|
||||
# 完全清空工作区重新拉取(覆盖 androidx/media + FFmpeg 缓存)
|
||||
./scripts/build_media3_ffmpeg.sh --clean
|
||||
|
||||
# 升 Media3 tag
|
||||
./scripts/build_media3_ffmpeg.sh --clean --media3-tag 1.11.0
|
||||
|
||||
# 升 FFmpeg branch
|
||||
./scripts/build_media3_ffmpeg.sh --clean --ffmpeg-branch release/6.1
|
||||
```
|
||||
|
||||
脚本会在 `libraries/decoder_ffmpeg/src/main/jni/build_ffmpeg.sh` 上幂等地打一次
|
||||
sed patch,把上游默认的 4 ABI 限制到 arm64-v8a only。`--clean` 会清空 patch 与缓存。
|
||||
|
||||
### Docker 复现编译(推荐 Apple Silicon)
|
||||
|
||||
```sh
|
||||
docker run --rm -it \
|
||||
-v "$(pwd)":/work \
|
||||
-e ANDROID_NDK_HOME=/opt/android-ndk-r27d \
|
||||
-w /work \
|
||||
cimg/android:2024.05.1-ndk \
|
||||
scripts/build_media3_ffmpeg.sh
|
||||
```
|
||||
|
||||
镜像版本以 r27d NDK 实际可用性为准;上面只是一个起点示意。
|
||||
|
||||
## 已知限制
|
||||
|
||||
| 限制 | 说明 / 解决路径 |
|
||||
|------|----------------|
|
||||
| **位图字幕**(PGS / VobSub) | onCues 检出后 emit 空文本 + `error: 'subtitle_bitmap_unsupported'`;建议改外挂 SRT/ASS 或切回 fvp |
|
||||
| **音量 boost**(100 < volume ≤ 200) | ExoPlayer `volume` 上限为 1.0;本插件把 100..200 段静默截断到 1.0。Dart 端 `state.volume` 保留原值(UI 一致),但物理音量不会更大 |
|
||||
| **HDR / DV** | 路由层显式排除(`resolvePlayerEngineKind` 在 DV/ProRes 下回退 fvp);media3 视频走 MediaCodec,DV 元数据未被特殊处理 |
|
||||
| **iOS / 桌面** | plugin 仅声明 Android 平台,构造层 `Platform.isAndroid` 守卫;UI 层桌面隐藏 media3 选项 |
|
||||
| **PiP / MediaSession / Cast** | 不实现(范围外);播放在前台 Activity 内完成 |
|
||||
| **DRM / Widevine** | 不实现(范围外) |
|
||||
| **FFmpeg AAR 体积** | 约 12-15 MB / arm64-v8a;release APK 增量约 ≤ 30 MB |
|
||||
|
||||
## 平台守卫四层
|
||||
|
||||
1. **枚举层**:`PlayerEngineOverride.media3` / `PlayerEngineKind.media3` 在所有平台编译通过(inert)
|
||||
2. **路由层**:`resolvePlayerEngineKind` 在 desktop 把 media3 降级为 mpv
|
||||
3. **构造层**:`Media3PlayerEngine` 构造函数 `assert(Platform.isAndroid)`,否则抛 `UnsupportedError`
|
||||
4. **UI 层**:`PlayerEngineRow.visibleOptions` 过滤 + `coerceValue` 持久化值降级
|
||||
|
||||
任一层失守,其它层仍能阻止 native crash。
|
||||
Reference in New Issue
Block a user