niuma_player 0.5.2
niuma_player: ^0.5.2 copied to clipboard
Flutter video player SDK with unified API across iOS / Android / Web, plus automatic ExoPlayer to IJK fallback on Android.
Changelog #
All notable changes to this project will be documented in this file.
Format follows Keep a Changelog; versioning follows Semantic Versioning.
0.5.2 - 2026-07-27 #
Changed #
- IJK 产物改用
c++_static链接:不再随包分发libc++_shared.so。 实测五个 ijk.so里只有libijksoundtouch真用 C++ 运行时(0.1 → 0.4 MB), 其余体积不变,不存在「每个 so 各带一份 libc++」的膨胀。 装机体积 arm64-v8a 9.9 → 8.5 MB、armeabi-v7a 7.5 → 6.4 MB, aar 7.7 → 6.9 MB。相对 GSY 官方产物的净增从 +2.7 MB 压到 +1.3 MB。
Fixed #
- example 的默认测试流误提交成了内部签名地址(0.5.1 随包发出), 改回公开示例源。
0.5.1 - 2026-07-27 #
Fixed #
- Android IJK 软解兜底重建:改用自编 FFmpeg 8.1,修好 fMP4 分片 HLS 的三处硬伤。
产物从 GSY 官方(FFmpeg 4.x)换回自编
ijkplayer-0.8.8-ff8.1-20260727(arm64-v8a + armeabi-v7a,minSdk 保持 21)。fork 自带的私货与旧假设逐条修掉:- fMP4 位置恒 0 / seek 后永久卡 buffering:
ffp_get_current_position_l()对ic->start_time的假设不适用于 fMP4(start_time取自分片的baseMediaDecodeTime,是个很大的值,而 master clock 不含此偏移),pos < start_diff恒成立直接返回 0。画面其实一直在正常播,坏的只是位置上报。 - 起播黑屏 2.4~5.7 秒:fork 在
ffp_check_buffering_l()里为「逼出首帧」 做seek_to(0),而show_first_frame只有首帧显示后才清零、缓冲期间又 显示不出来 → 每轮缓冲检查都 seek 回 0 → 分片反复重拉。实测起播要读 2326 个分片(约 140 秒内容);移除后降到 12 片。 - H.264 有声无画:fork 在
stream_component_open()里无条件优先取*_mediacodec解码器,无视mediacodec选项。niuma 显式设了mediacodec=0仍会走硬解 wrapper,H.264 上avcodec_open2失败 (EINVAL)导致视频组件根本没打开。改为尊重选项。 PlayerSession心跳补一道 buffering 出口兜底:位置在推进即视为帧在流, snap 回 playing——不再依赖BUFFERING_END事件(fMP4 上它不发)。- 编译链、补丁(
patches/0001~0003)、VERSIONS.lock、module-niuma-ff8-slim.sh全部回归本仓;踩坑记录见doc/ijk-fmp4-hls.md。
- fMP4 位置恒 0 / seek 后永久卡 buffering:
- 播完后拖回进度可能永远转圈:video_player 在 completed 事件里调的是
自己的
pause(),绕过VideoPlayerBackend.pause(),播放意图停在 true; 而拖动进度会把isCompleted翻回 false,底层重新缓冲就被推导成buffering——spinner 不灭、按钮显示正在播、实际没播。现在观察到 completed 即作废播放意图。触发要求 seek 目标处发生重缓冲,真机上表现为 间歇(HLS / 长视频 / 弱网更易命中)。
0.5.0 - 2026-07-25 #
Changed #
- Android 主内核切换为官方
video_player(2.0 架构):三端主路径统一走 官方 video_player(iOS AVPlayer / Android ExoPlayer / Web<video>),由 Flutter 官方维护、可随意升级;niuma 退居「编排壳」——多线路 failover / retry / middleware / 手势 / 全屏 / wakelock / PiP 编排 + Android IJK 软解兜底(video_player 初始化失败时当次会话自动接管,forceIjkOnAndroid可显式强制)。自研 ExoPlayer 会话(ExoPlayerSession.kt)与 media3 直接 依赖删除——ExoPlayer 升级、Android 新版本适配从此归官方 video_player。- 多线路 auto-failover 逻辑对 Android 主路径同样生效(此前仅 iOS / Web)
- vp 初始化 wall-clock 超时补发
FallbackTriggered(timeout)(对齐原 native 路径的排障信号) BackendSelected.kind:Android 主路径成功时为videoPlayer(原为native);IJK 兜底成功时仍为nativeuseAndroidPlatformView: true在主路径映射为 video_player 的VideoViewType.platformView(0.3.3 根治有声黑屏的渲染路径在 vp 主路径 等价保留;IJK 兜底路径仍走自家 Hybrid Composition)
- 新增 Dependabot(pub / gradle / actions):video_player 等依赖出新版 自动提 PR 并由 CI(analyze + test + example apk 构建)验证兼容性。
- BREAKING:环境门槛提升——
video_player >=2.10(viewType)要求 Flutter >=3.27 / Dart >=3.6,pubspec environment 同步收紧。
Fixed #
- 暂停被误报成
buffering:vp 的pause()只翻isPlaying、不动isBuffering,而 ExoPlayer 暂停只改playWhenReady、不改playbackState(收不到bufferingEnd)——在缓冲窗口内按暂停会永久停在buffering:effectivelyPlaying恒 true 导致播放/暂停按钮不翻转,spinner 也不灭。VideoPlayerBackend现在自己记录播放意图,derivePhaseFor据此推导, 不变量是无播放意图时永不产出buffering——与PlayerPhase.buffering的既有文档契约(「播放意图为真但解码器拿不到数据」)一致,也与 1.x native 路径的PlayerSession.userWantsPlay同语义。web 路径按<video>的paused真值推导,本就不受影响。 - Android platformView 退出全屏后画面冻结:
useAndroidPlatformView: true时 vp 的平台视图构造即setVideoSurfaceView(自己)抢走 ExoPlayer 的独占 输出 surface,销毁时只释放自己的 surface 不归还。宿主用共享 controller 实现无缝全屏(inline 那份全屏期间仍挂在树上)时,退全屏后 player 绑在已 释放的 surface 上——声音和进度正常,画面停在最后一帧。现在同一 controller 下只有最后 mount 的NiumaPlayerView构造 vp 渲染 widget,它 unmount 时 所有权回退给仍存活的上一个并重建,绑定自然恢复(与 1.x 自研 native 路径PlayerSession.surfaceStack同语义)。接入方零改动;texture 路径与 iOS 不受影响。 - Android 9(API 28)上 vp 主路径自动降级 textureView:该版本 video_player
的平台视图走
setupSurfaceWithCallback——surfaceCreated里无条件seekTo(1)(surface 每次重建都跳回开头,切后台再回来也会)、surfaceDestroyed里无条件setVideoSurface(null)(不判断当前绑的是不是 自己)。多 surface 交接必然黑屏,useAndroidPlatformView在 API 28 上因此 自动失效。IJK 兜底路径走自家PlayerSession.surfaceStack,不受此限。 - 倍速状态不再被打回 1.0:vp / IJK 两后端的 value 映射漏带
playbackSpeed,setPlaybackSpeed(2.0)后下一条底层通知会把公开value.playbackSpeed覆盖回 1.0(实际播放仍 2x,UI 倍速指示闪回)。 - switchLine +
forceIjkOnAndroid播错线路:IJK 路径此前固定拉默认 线路,目标线路被忽略(事件却报 LineSwitched 成功);现按目标线路拉起。 vp 多线路全失败落 IJK 兜底后activeLineId也同步校正为实际播放的默认线。 - switchLine 重试改为重建式:原实现在同一个已失败的 backend 上反复
initialize()(vp 失败后不重建必然再失败);现与 init 路径共用同一 拉起序列(dispose → middleware → 重建 → init)。 - 三处生命周期竞态:dispose 后仍在退避重试中的 init 链会把新建的平台
播放器 attach 到已销毁的 controller 上(feed 快滑泄漏源);init 未完成时
load()换源会出现双活 init 链互相拆台;NativeBackend.dispose()不 settle 等待中的 initialize(只能干等 initTimeout)。现分别以代际号 / attach 前置检查 / completeError 处理。 - 播放器池并发窗口:
preload未 await 时立刻acquire会拿到未 初始化的半成品 controller(或在 evict 窗口重复建导致泄漏)——entry 插入与 pending 赋值改为同一同步段完成,附回归测试。 - 手势亮度/音量起手闪谷底:锁定方向后基准值异步读回期间的指针事件 以 0.0 为基准直接把系统亮度/音量设到谷底;现读回完成前丢弃。 水平 seek 结束改为提交拖动中算好的目标,不再从 HUD 进度反算(丢精度)。
- Android PlatformView 会话 id 与 texture id 可能撞号:两套分配器 空间重叠,混用两种渲染模式时后建会话会顶掉先建的(泄漏);PV id 起点 抬到 1e12 隔离。
- web 后端:移除必然 MissingPluginException 的 PiP 总线订阅(每会话
一条控制台红错);
timeupdate双 listener 合并为一次 value 广播。
Removed #
- BREAKING:
PlatformBridge.deviceFingerprint()删除——设备记忆策略 (0.4.0 移除)的最后残留,SDK 内已无任何消费方;自定义PlatformBridge实现删掉该 override 即可。nativecreate协议同步移除fingerprint/fromMemory返回字段与deviceFingerprinthandler。 - BREAKING:
PlayerBackend.webFullscreenState删除——从未被写入 (恒 false)也无消费方;web 全屏状态由webFullscreenRouteCountListenable承载。 - BREAKING:投屏(Cast)整体移出核——值类型(
CastDevice/CastSession/CastConnectionState/CastEndReason)、controller 的connectCast/disconnectCast/castSession、事件CastStarted/CastEnded/CastError全部删除(协议实现本就在 git 历史参考皮)。 核收敛为"干净的播放":需要投屏的业务自持会话对象,播放侧只需pause()本地播放器。 - BREAKING:
danmakuVisibility删除——弹幕引擎 0.1.0 已随参考皮出核, 该遗留字段核内零消费;业务自持一个ValueNotifier<bool>即可。 - BREAKING:
BackendFactory.createNative移除forceIjk参数(2.0 起 native 只有 IJK 会话,参数恒 true 无意义);NativeBackend构造与 create 协议同步移除。NiumaPlayerValue.copyWith移除零调用的clearOpeningStage;GestureFeedbackState移除零调用的copyWith。 - 全库死代码清理:backend 级
FallbackTriggered发射(controller 一律 丢弃,权威版本由 controller 自发)、native 全局 channel 的 play/pause/seek/setSpeed/setVolume 死分支与forward()、NativeBackend.selectedVariant公开 getter、PlayerSession 的 Exo 时代钩子(onHeartbeatTick 等)、iOS/Android 的 getPlatformVersion 模板残留、gradle 的 no-op abiFilters / packagingOptions。
0.4.0 - 2026-07-22 #
Changed #
- Android IJK 产物切换为 GSY 官方 MavenCentral 分发
(
io.github.carguo:gsyvideoplayer-java/-ex_so:11.3.0,bilibili ijkplayer 0.8.8 血统 + FFmpeg 4.3 全量:h264 / h265 / mp4 / HLS 全支持,minSdk 21)。 替换原自编 ShikinChen ff7.1 slim aar——后者存在 fork 私货(缓冲期反复seek(0)的 show_first_frame hack)与高码率流软解不出帧的问题,真机表现 为「有声无画 / 无限 buffering」。换装后同一条 2568×1440 加密 HLS 在 IJK 软解下正常出画(OPPO 真机验证)。 - 移除设备记忆策略(Try-Fail-Remember):一次 ExoPlayer 失败不再被持久
化、不再影响后续会话的内核选择——SDK / 源侧问题修复后设备立即回到硬解
快路径。内核选择完全由
forceIjkOnAndroid显式决定;会话内 Exo→IJK 的 单次兜底重试保留(不落盘)。- BREAKING:删除
NiumaPlayerController.clearDeviceMemory();BackendSelected.fromMemory字段保留但恒为false(废弃语义)。
- BREAKING:删除
- IJK
framedrop固定为 0:软解跟不上实时的临界场景下framedrop>0会把 所有帧 early-drop 掉(有声无画黑屏);0 的代价是可能渐进失步,两害取轻。
Added #
EngineFallbackFailure:Android 双内核(Exo + IJK 兜底)都失败时抛出 的组合异常,同时携带两段原始错误——修复「只报最后一环 IJK 错误、把 Exo 的根因(如 HTTP 403)掩盖」的排障陷阱。- example 新增「内核切换测试」页:运行时切 ExoPlayer / IJK 对比同一条流。
Fixed #
- IJK
probesize曾被设为 100KB「加速 prepare」——对 6Mbps+ 高码率 TS 连 一个视频关键帧都探不齐 → 视频流缺失(-10000/ 无限 buffering)。改回 FFmpeg 默认(probesize 是上限而非必读量,调大零成本)。 - IJK 移除
reconnect_streamed=1:它把 HLS 分片的正常 EOF 当断连,无限重 连同一分片导致播放卡死。
Removed #
android/localmaven/自编 ijkplayer aar 与android/scripts/FFmpeg 编译链整体退役(GSY 产物上线后不再需要;git 历史可寻)。
0.3.4 - 2026-07-21 #
Fixed #
- 初始化失败「无限转圈、无失败提示」:
initialize()失败(如 IJK prepare 卡死触发 30sinitTimeout)时,错误此前只经initialize()返回的 future 传播,value.phase从不进入 error 态——调用方若未 catch future, 靠value驱动的错误层永远收不到失败,用户只见无限 buffering(真机实测)。 现失败同步落入value(phase=error+value.error按既有规则分类), 错误 UI 自然显示,接入方零改动受益。
Added #
- H.265 / HEVC 全链路支持:
- 新增
NiumaCapabilities.supportsHevc()能力检测——Android 查MediaCodecList是否存在video/hevc硬件解码器(纯软解不算), iOS 恒true(系统级支持),web 走MediaSource.isTypeSupported+canPlayType双探测。典型用途:源协商——业务据此决定向服务端请求 H.265 还是 H.264 源(协议字段业务自定,SDK 不自动附加任何请求头)。 结果进程内缓存。 - IJK 软解兜底重新支持 HEVC(撤销 0.2.1 的裁剪):重编 ijkplayer aar
加回
hevcdecoder / parser /hevc_mp4toannexbbsf /hevc_mediacodec。 此前兜底链对 H.265 是断的——硬解失败切 IJK 后彻底播不了;现在闭环。 体积代价:aar 7.37 → 7.67 MB(arm64libijkplayer.so6.5 → 6.9 MiB)。 - 播放侧无需任何配置:流是 h264 还是 h265 由解封装自动识别。
- vendored hls.js 1.5.20 → 1.6.16:补齐 web(Chrome / Edge 等 MSE
浏览器)的 HLS H.265——TS 分片里的 HEVC 是 hls.js 1.6.0 才支持的,
1.5.x 下即使浏览器能解也播不了。升级后
NiumaCapabilities.supportsHevc()的检测结果与实际播放链路对齐。 已回归:h264 TS-HLS(mux 测试流)正常;HEVC fmp4 HLS(bitmovin 测试流)Chrome 实测出画。API 面(isSupported/attachMedia/loadSource/destroy/xhrSetup/hlsError)1.6 全兼容。
- 新增
0.3.3 - 2026-06-16 #
Fixed #
- Android PlatformView「有声黑屏」根治(
useAndroidPlatformView = true): 渲染从裸AndroidView改为真正的 Hybrid Composition(PlatformViewLinkPlatformViewsService.initExpensiveAndroidView)。裸AndroidView走 TLHC / Virtual Display——靠把原生 view 拷进 Flutter 纹理再合成,而 SurfaceView 的像素画在独立 Surface 上、纹理拷贝抓不到 → 合成出来是黑的 (声音/状态机正常但画面全黑,进/退全屏、锁屏解锁、切后台最易触发; flutter#128920 / #172641 / #144219)。HC 把 SurfaceView 插进原生视图层级 直接渲染,画面正常显示,原生缩放 / HDR 保留,Flutter 控件仍可叠层。
- 用
initExpensiveAndroidView(纯 HC,不回退 VD),不用initAndroidView/initSurfaceAndroidView(不兼容场景会回退 Virtual Display 又黑回去,flutter#107313) - Kotlin 侧零改动;surface 栈 /
PlayerSurfaceView/PlayerSession全部 保留(它们管退全屏 codec error,与黑屏正交) - OPPO Android 16 真机验证:进 / 退全屏画面正常
- 注:Hybrid Composition 在多实例 + 频繁创建销毁(feed)下开销大于单 播放器;feed 等场景如性能吃紧可继续用默认 Texture 路径
0.3.2 - 2026-06-14 #
Fixed #
- Android IJK 软解音画失步:
framedrop由0改为1。被钉死走 IJK 软解 的机型上,高码率 / 高帧率 / 倍速视频解不过实时,framedrop=0让落后的视频 帧一帧不丢 → 相对音频主钟渐进失步(画面比声音慢、对不上嘴,越看越脱节)。 改为允许丢晚帧让视频追上音频主钟(解不动时偶尔跳帧,但保持同步)。 - Android ExoPlayer 开启解码器回退:
DefaultRenderersFactory .setEnableDecoderFallback(true)。硬解 codec 初始化失败时同会话内自动尝试 下一个(含软件)解码器,而非直接抛错绕 Dart 重试 → IJK(少一次错误闪)。
Changed #
- codec 失败记忆
NO_EXPIRY→ 7 天 TTL:此前首帧前硬解 codec 失败会把该 设备永久钉死走 IJK 软解(哪怕只是单条视频 / 单次 codec 抽风),导致本可 硬解的视频长期走软解(卡 / 发热 / 音画失步)。改为 7 天有效期:近期重试仍直 接走 IJK 避免反复撞坏 codec,到期后回 ExoPlayer 硬解重试。读取判定与到期清理 Dart 侧早已就绪。
0.3.1 - 2026-06-12 #
Added #
- 播放中自动保持屏幕常亮(wakelock):修复「播到一半自动熄屏」。
NiumaPlayerController在 playing 边沿自动保持 / 释放亮屏(暂停、结束、 出错、dispose 都会释放),多实例(feed / 池)以进程级计数归并——任一在播 即亮屏,全部停了才释放。Android 走FLAG_KEEP_SCREEN_ON(窗口级、无需 权限、退后台自动失效),iOS 走isIdleTimerDisabled,web 无操作(浏览器 播<video>自身防熄屏)。- 新增
NiumaPlayerOptions.manageScreenWakelock(默认true;音频类 业务想允许熄屏可置false) PlatformBridge接口新增setKeepScreenOn(bool)——自定义PlatformBridge实现方需补该方法(0.x 下随 minor 发布)
- 新增
0.3.0 - 2026-06-12 #
Added #
- Android PlatformView(SurfaceView)渲染路径(opt-in):
NiumaPlayerOptions.useAndroidPlatformView = true时,Android 视频改由 PlatformView(SurfaceView 原生缩放)渲染,替代 Flutter Texture——从根 上解决 Texture 路径的画质模糊,且不吃filterQuality的每帧采样开销。 默认false(行为与 0.2.x 完全一致),iOS / web 忽略此选项。- 原生侧新增
PlayerSurfaceView/PlayerSurfaceViewFactory(viewTypecn.niuma/player_surface);NiumaPlayerView自动按 backend 选AndroidView渲染分支 - surface 栈:全屏路由 push 时第二个 SurfaceView 抢绑定、销毁时自动 回退绑定到 inline 仍存活的 surface——退全屏不再输出到 dead Surface 导致 codec 报错(OPPO Android 16 真机验证)
- prepare 不等 surface:ExoPlayer / IJK 无 surface 即可 prepare (音频与状态机照常推进),surface 何时到画面何时出——feed 类 「initialize → play → 才渲染激活页」的 mount 顺序不会死锁
- 接入建议:详情页 / 单播放器开启收益最大;feed 每滑一条重建
SurfaceView 有黑闪,建议维持默认 Texture。
NiumaPlayerView外请保持 松约束(如包Center),全屏路由建议快淡/瞬切(参考 example)
- 原生侧新增
PlayerBackend.androidPlatformViewIdgetter(默认null)。
Changed #
BackendFactory.createNative增加useAndroidPlatformView可选参数—— 自定义BackendFactory实现方需同步该签名(0.x 下随 minor 发布)。
0.2.3 - 2026-06-07 #
Changed #
- Android Texture 路径默认提一档画质:
NiumaPlayerView新增filterQuality参数,默认FilterQuality.medium(双三次插值),替代 FlutterTexture硬编码的默认FilterQuality.low(双线性)。修复反馈 「视频有点花、不够高清」(多见于小米15 等大屏 / 高 DPI 现代机型,原默认 low 在拉伸时糊感明显)。medium 在 2020+ 中端机以上无可感性能开销。极致性能场景 (feed 多实例 + 低端机)可显式传FilterQuality.low降回旧默认。 iOS 不受影响(VideoPlayerwidget 内走 AVPlayer 原生 scaling);web 同样不受影响(浏览器直接缩放)。 - 下一步预告:长期方案是把 Android 渲染改为 PlatformView(SurfaceView
原生缩放),从根上去掉 Texture 路径的每帧 filterQuality 开销。已起
feat/android-platform-view分支,待真机回归后发 0.3.0。
0.2.2 - 2026-06-07 #
Fixed #
- web rapid seek 卡 buffering(Chrome/Firefox/Edge + hls.js):
WebVideoBackend.seekTo改为合并模式(latest-wins)——已有 seek 在路上时新调用只更新目标,待'seeked'事件后再 fire 最新值,避免反复 seek 把 hls.js 的SourceBuffer卡进updating=true永不释放、'playing'永不来。配 3s 安全 timer 兜底(极端 case 浏览器漏发'seeked'时也能解锁)。 - Safari + hls.js 在已 buffered 区间 seek 后 phase 卡 buffering:新增
'seeked'事件监听,按 video 真值兜底校准 phase(仅当 phase 为 buffering 时纠正,playing/paused/ended/error 等明确状态保持不动)。修复多次快进快退 后 UI spinner 不消失、底栏图标错乱、点击屏幕才恢复的 quirk。 load()换源时残留 seek 状态:换源即作废_isSeeking/_pendingSeek/_seekSafetyTimer,避免锁跨源残留导致换源后第一次 seek 被吞。
0.2.1 - 2026-06-07 #
Fixed #
- web HLS:hls.js xhrSetup 跳过浏览器 forbidden request headers:
WebVideoBackend给 hls.js 配xhrSetup时把dataSource.headers里的referer/host/origin/user-agent/cookie等浏览器禁止 JS 设置 的请求头跳过,避免 hls.js 抛Refused to set unsafe header "referer"并中断 HLS 加载。鉴权 token 等正常 header 仍透传。
Changed #
- Android ijkplayer aar 砍掉 HEVC(H.265)软解兜底,进一步精简:
libijkplayer.soarm64 6.9→6.5 MiB / armv7 5.5→5.1 MiB,aar 7.6→7.0 MiB, 对应 APK 单 arm64 ABI 省 ~400KB。decoder 仅留h264 / aac / aac_latm / mp3* + h264_mediacodec / mp3_mediacodec;HEVC 解封装/parser/bsf 同步移除。 影响:IJK 软解兜底路径不再支持 H.265;Android ExoPlayer 主路径仍可硬解 H.265,仅在「ExoPlayer 翻车 + 视频是 H.265」双重场景才会播不了。点播 mp4+HLS(主流 H.264 编码)不受影响。编译配置见android/scripts/compile/modules/module-niuma-ff7-slim.sh。
0.2.0 - 2026-06-05 #
Added #
NiumaPlayerController.load(NiumaMediaSource)原地换源(+PlayerBackend的supportsSourceSwap/load):复用当前 backend 换到新源。web 后端复用 同一个<video>元素换 src(supportsSourceSwap=true),保住 iOS Safari 的 有声播放激活——"滑到才知道下一条 URL"的 feed 可用一个 controller 反复换源, 而非每条新建 controller、每条丢激活导致只能静音。backend 不支持换源时自动 dispose + 重建兜底。- web 全屏分流原语(
NiumaWebFullscreenMode/webFullscreenMode/requestBrowserFullscreen/exitBrowserFullscreen/onBrowserFullscreenChange, 自web_fullscreen_coordination):把「浏览器全屏能力检测(安全读fullscreenEnabled、绕开 iOS Safariundefined抛TypeError)+ 画布真全屏 进出 + 全屏状态监听」收进核,接入方据webFullscreenMode分流即可、不必碰 DOM:nativeVideoElement(iOS Safari,走系统 player 全屏)/browserElement(Chrome 等,走画布真全屏 + 自家全屏页)/notWeb。 NiumaPlayerController.setWebNativeControls(bool)(+PlayerBackend同名 方法):web-only,开 / 关底层<video>的浏览器原生控件。iOS Safari 上 Flutter 自定义控件叠在<video>上会被浏览器吞、点不动,接入方可开原生控件兜底 (播放 / 进度 / 全屏交给浏览器)。非 web backend 为空操作。
Fixed #
- web video 被 DOM reparent 后自动续播:
WebVideoBackend维护「播放意图」, video 因全屏搬迁等被浏览器自发暂停时,只要意图仍在播就自动续播——修复 「Chrome 进全屏 / 退全屏后视频卡停」。用户主动pause()不受影响。 - iOS Safari 退出原生全屏后自动恢复播放:
WebVideoBackend.enterNativeFullscreen()走webkitEnterFullscreen时记住进全屏前的播放态,监听webkitendfullscreen, 退出系统 player 后自动play()(iOS 退出系统 player 默认会把 video 暂停)。 - web
enterNativeFullscreen回归「浏览器原生全屏」语义:WebVideoBackend此前把enterNativeFullscreen()实现成只翻一个NiumaPlayerView并不读取的 内部 flag(等于 web 上调用它没有任何可见效果);现在按PlayerBackend接口 文档契约真正调用<video>.webkitEnterFullscreen()(iOS Safari,进系统原生 video player UI)/requestFullscreen()(桌面 Safari / Chrome / Firefox / Android Chrome)。exitNativeFullscreen()同步走webkitExitFullscreen/document.exitFullscreen()。
0.1.0 #
BREAKING CHANGE: 重定位为 headless 播放内核 #
niuma_player 现在是纯 headless 视频播放内核——只导出
NiumaPlayerController + NiumaPlayerView(无样式渲染面)+ 全部纯 Dart 编排
逻辑(多线路 / auto-failover / retry policy / source middleware)+ 手势 / 全屏
的 headless controller(NiumaGestureController /
NiumaFullscreenController)+ cast 值类型。接入方监听
controller.value(ValueNotifier<NiumaPlayerValue>)自己拼 UI,或让 AI 按需
生成。
所有 UI 全部出核,移入 git 历史(曾经的 88 文件 niuma_ui 参考皮):
- 一体化播放器壳
NiumaPlayer+ 22 个原子控件 + 控件条(NiumaControlBar/ControlBarConfig/ButtonOverride)+ 全屏页NiumaFullscreenPage+ 三态 反馈 UI + 主题NiumaPlayerTheme。 - 弹幕引擎与 UI(
NiumaDanmakuController/ overlay / painter / settings panel /DanmakuTrackAllocator)。 - 广告调度(
NiumaAdSchedule/AdSchedulerOrchestrator/NiumaAdOverlay+ analytics 事件模型)。 - 缩略图取帧逻辑与 widget(
ThumbnailFrame/WebVttParser/ThumbnailResolver/NiumaThumbnailView/NiumaScrubPreview)。 - 投屏协议实现与 UI(DLNA SSDP/SOAP、AirPlay RoutePicker、
NiumaCastRegistry/CastServiceSPI、cast 按钮 / picker 面板)。 - 短视频整套(5 个
NiumaShortVideo*widget +NiumaShortVideoTheme)。 - 本地续播(resume position)。
需要参考实现:git log --all -- 'example/lib/niuma_ui/**' 定位 commit,
git show <sha>:example/lib/niuma_ui/... 取文件,或喂给 AI 当参考。
核仍保留的 cast 值类型:CastDevice / CastSession /
CastConnectionState / CastEndReason——controller.connectCast(session) /
disconnectCast(...) + castSession getter + CastStarted / CastEnded
事件依赖它们。具体协议由接入方实现 CastService 产出 CastSession 交给核。
Removed (依赖瘦身) #
- 三方依赖从重定位前的一大堆砍到 5 个。移除
shared_preferences(Android 设备记忆改走 Kotlin 侧SharedPreferences,Dart 不再依赖)/flutter_svg(UI 出核)/http(核不再 fetch VTT,缩略图取帧出核)。
Changed (平台引擎) #
- Web 后端从已废弃的
dart:html迁到package:web+dart:js_interop, wasm-ready——可随flutter build web --wasm编译。 - Android IJK 升级到 FFmpeg 7.1.1 slim 重编(vendored
.aar), ExoPlayer ↔ IJK 自动回退路径不变。
Changed (example) #
example/精简为 100 行最小 demo(example/lib/main.dart):NiumaPlayerController.dataSource+NiumaPlayerView+ValueListenableBuilder<NiumaPlayerValue>自拼 play/pause + 进度 + 时间。 原 8 个 demo 页随参考皮移入 git 历史。
0.0.4 - 2026-05-25 #
Fixed #
- Vendored the custom-compiled
ijkplayer.aar(13 MB) into git and the published package. It was previously git-ignored and fetched by a download script whose release URL no longer exists, so neither git nor pub.dev consumers received the binary and every Android build failed to resolvetv.danmaku.ijk:ijkplayer. The aar now ships underandroid/localmaven/, so Android builds work out of the box. Removed the dead download script.
0.0.3 - 2026-05-09 #
Fixed #
- Bumped
video_playerlower bound to>=2.10.0. The 2.8.0 lower bound failed pana downgrade analysis becauseVideoPlayerController.playerId(used by the iOS PiP bridge to map a Flutter texture id to its native AVPlayer instance) was only added invideo_player 2.10.0. - Declared web platform support in
pubspec.yamlplugin manifest with a no-opNiumaPlayerWebRegistrarstub. Web behavior is implemented in pure Dart via conditional imports (WebVideoBackend); the stub exists only so Flutter's web plugin discovery can satisfy the platform declaration. - Trimmed
CHANGELOG.mdto public 0.0.x entries only. The full internal-preview history (0.1.0 through 0.9.1) — which is mostly Chinese prose and was tripping pub.dev's non-ASCII content check — moved todoc/CHANGELOG_zh_internal_preview.md.
0.0.2 - 2026-05-09 #
Fixed #
- Replaced
dart:js_util(removed in Dart SDK 3.11) withdart:js_interop/dart:js_interop_unsafeinweb_video_backend.dart. Fixes pub.dev pana static analysis failure that previously zeroed out platform support score. - Shortened pubspec.yaml description to fit pub.dev 60-180 char limit.
- Added English-language summaries to
CHANGELOG.md.
0.0.1 - 2026-05-09 #
First public pub.dev release. Version reset from internal-preview 0.9.x to 0.0.1 as the inaugural public SDK version. Feature set equivalent to internal 0.9.1, including:
- 3-tier backend abstraction (VideoPlayerBackend for iOS/Web, NativeBackend for Android) plus Android Try-Fail-Remember device memory.
- Orchestration layer (multi-line, retry policy, source middleware, resume position, WebVTT thumbnails, danmaku bucket loader, auto-failover).
- All-in-one
NiumaPlayerwidget plus 22 atomic control widgets and a configurableNiumaControlBar. - Picture-in-Picture (iOS via reflection bridge, Android native).
- Cast: DLNA and AirPlay auto-registration via
NiumaCastRegistry. - Feedback UI builder slots:
loadingBuilder,errorBuilder,endedBuilder. - Short-video player with TikTok-style gestures, scrubber, speed control.
- Web fullscreen, cross-backend swap coordination, iOS Safari quirk fixes.
For the detailed history of internal-preview iterations leading up to this
release (0.1.x through 0.9.1), see
doc/CHANGELOG_zh_internal_preview.md
(Chinese).