- 音视频
- 直播
- 移动开发
【免费下载链接】pure_live
纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。
Pure Live(纯粹直播)是一个聚合哔哩哔哩、虎牙、斗鱼、快手、抖音、网易 CC、YY、Twitch、SOOP 等平台的跨端直播客户端。本文以仓库 docs/STAGE_UPDATE_2_5_0.md 为骨架,结合lib/源码、Android 原生工程与测试用例,系统拆解 v2.5.0 阶段稳定版(2.5.0+4072,上游基线liuchuancong/pure_live@b84a847d)在首页刷新、Android 刷新率策略、Windows 界面稳定性、依赖治理与自动验证五个维度的具体做法。读完本文,你将掌握有界异步并发池的实现原理与并发参数调优方法、Android 三档刷新率策略的状态机设计与原生桥接细节,以及该版本如何用“事务式发布 + 快照去重”解决首页刷新抖动问题。
一、版本基线:v2.5.0 的定位与上游同步
v2.5.0 阶段稳定版版本号为2.5.0+4072,维护仓库为liuchuancong/pure_live,上游基线锁定在提交b84a847d。该版本的核心目标是收敛此前的刷新、帧率与界面稳定性问题,为后续版本奠定一个可长期运行的基线。仓库当前已演进到 3.x(参见 docs/README.md 的当前工作入口),本阶段更新文档作为对应批次的不可变证据,仍然完整保留着首页刷新与刷新率策略的权威设计。
从文档看,v2.5.0 的同步方式是通过“真实 merge”而非 cherry-pick 或手动搬移,这样做的目的是完整保留维护分支的正式发布地址、数据迁移逻辑和播放器稳定性增强,避免上游修复在搬运中丢失上下文。
二、首页刷新:从固定批次到有界异步工作池
v2.5.0 对首页收藏核验的并发模型做了根本性调整:不再按固定批次等待整批最慢请求,而是由 4 个有界异步工作单元从同一队列领取房间;一个请求完成后立即领取下一个,慢平台只占用自身槽位。
2.1 有界并发池的源码实现
该并发模型对应仓库中的通用工具 lib/core/utils/bounded_async_pool.dart 里的boundedAsyncMap:
Future<List<R?>> boundedAsyncMap<T, R>( Iterable<T> items, { required int maxConcurrent, required Future<R?> Function(T item) task, bool Function()? shouldCancel, }) async { final source = List<T>.of(items, growable: false); if (source.isEmpty) return <R?>[]; final results = List<R?>.filled(source.length, null, growable: false); final workerCount = math.min(math.max(1, maxConcurrent), source.length); var nextIndex = 0; Future<void> worker() async { while (true) { if (shouldCancel?.call() == true) return; final index = nextIndex++; if (index >= source.length) return; results[index] = await task(source[index]); } } await Future.wait(List<Future<void>>.generate(workerCount, (_) => worker())); return results; }这个实现的关键点在于:
- 工作单元自取任务:每个 worker 通过
nextIndex++原子地从共享索引队列领取下一个房间,完成当前任务后立即进入下一轮,而不是像Future.wait批次那样整批等待最慢请求。因此一个慢平台(如网络超时的虎牙接口)只会占用自己的一个槽位,不会阻塞后续批次中的其他平台请求。 - 结果按输入顺序填充:
results[index] = await task(...)把每个结果写回原始下标,保证输出顺序与输入顺序一致,为“排序一致性”提供了底层保障。 - 可取消性:
shouldCancel回调在每个任务开始前检查,配合控制器销毁后的 epoch 机制实现任务隔离(详见 2.4 节)。
该工具在 lib/domains/live/presentation/favorite/favorite_controller.dart 的_refreshRoomDetails中被实际调用:先用RefreshConfigController.normalizeMaxConcurrentRefresh规范化并发数,再复用每个平台一个的LiveSite适配器(siteCache.putIfAbsent),让 cookie、设备与 bootstrap 请求能够使用 single-flight 状态,同时让有界 I/O worker 并发刷新多张卡片。
2.2 超时收紧与启动带宽让行
- 网络超时从 12 秒收紧到 10 秒:对应每个房间刷新任务上的
.timeout(_roomRefreshTimeout),其中_roomRefreshTimeout = Duration(seconds: 10)(见 favorite_controller.dart)。收紧超时让慢平台的槽位更快释放,减少整轮核验的尾延迟。 - 启动更新检查延后到首帧后 2 秒:在 lib/features/home/home_page.dart 的
_checkForStartupUpdate中,启动阶段先完成 PackageInfo 初始化与首屏,再执行VersionUtil().checkUpdate();该函数内部通过RaceHttp.fetchJson(...).timeout(const Duration(seconds: 10))拉取版本信息(见 lib/core/release/version_util.dart)。延迟 2 秒发起是为了避免版本检查与首屏房间状态、封面请求争抢带宽,保证冷启动首帧尽快呈现。
2.3 事务式发布:一次核验只发布一次终态
刷新仍采用事务式发布:保留当前卡片,完成本轮核验后一次性替换。在源码层面体现为三个环节:
- 串行锁:
_runRoomRefresh使用_refreshLock.synchronized让每一轮刷新独占快照事务,生命周期恢复、定时器、手动下拉不会并发覆盖彼此(对应 docs/FAVORITE_REFRESH_DESIGN.md 中“刷新事务通过单锁串行”的规则)。 - 一次 Hive 写入 + 一次界面发布:网络核验结果全部收集完毕后,先经
mutateRoomsDurably持久化合并,再通过applyLocalFilter一次性发布排序后的 online / replay / offline 三个分桶。 - 快照签名去重:
_assignIfSnapshotChanged通过_favoriteSnapshotSignature(对房间 identityKey、直播状态、标题、封面、人数、标签等字段做Object.hash)判断快照是否真的变化,相同快照跳过发布,避免无意义的重建与刷新动画。
这套设计同时兼顾首屏速度、列表稳定性和排序一致性:首屏沿用缓存元数据立即绘制,网络核验结果以整轮终态落定,卡片不会按请求完成顺序反复跳位。
2.4 并发量设置的语义澄清
文档特别澄清了一个常见误解:设置中的并发量代表网络请求数,并非创建同等数量的 CPU 线程。Dart 的异步模型下这些请求仍在事件循环内调度,工作池只限制同时 in-flight 的 I/O 请求数量。默认值 4,通常建议 3–6。
在 lib/core/config/refresh_config_controller.dart 中可以看到完整约束:
static const int defaultMaxConcurrentRefresh = 4; static const int maxAllowedConcurrentRefresh = 20; static int normalizeMaxConcurrentRefresh(Object? value) { final parsed = value is num ? value.toInt() : int.tryParse(value?.toString() ?? ''); return (parsed ?? defaultMaxConcurrentRefresh).clamp(1, maxAllowedConcurrentRefresh); }即并发值在 1–20 之间钳制,缺省回落到 4;该配置同时支持通过toJson/fromJson/extractConfig参与设置备份与恢复。
2.5 空快照终态发布:修复首页无限加载动画
此前本地响应式池把“初始空列表”和“新空列表”误判为无需更新,首页因此在无收藏或全量失败时停留在无限加载动画;Windows 端会持续提交渲染帧,形成“内存上涨且空闲 CPU 偏高”的假象。
v2.5.0 的修复是:首次快照即使为空也会发布一次终态,只有对已经发布过的相同快照才走快速路径跳过。这样空列表也能得到一次明确的“空”终态渲染,加载动画得以关闭;后续相同空快照则被去重,不会反复触发重建。
2.6 失败冷却与失败摘要
刷新路径还内建了两层失败防护(同样可在 favorite_controller.dart 中确认):
- 失败冷却:
_refreshFailureCooldown记录每个房间的失败时间,_refreshFailureRetryAfter = 5 分钟内不再重复上报;但手动下拉、启动核验和超过新鲜度阈值的恢复刷新会bypassFailureCooldown,用户主动操作不会被静默跳过。 - 失败摘要:
summarizeFavoriteRefreshFailures把每轮失败房间压缩成单行摘要(默认最多列 6 条,超出显示…(+N)),避免大量房间同时离线、限流或超时时逐房间输出堆栈淹没日志。对应测试见 test/domains/live/favorite_refresh_failure_summary_test.dart。
三、Android 刷新率策略:省电 / 均衡 / 高性能三档
v2.5.0 在 Android 设置页引入三档刷新率模式,取代旧版单一的“高刷新率”开关。
3.1 三档模式的定义
| 模式 | 行为 | 适用场景 |
|---|---|---|
| 省电(默认) | 交由 Android 系统动态选择刷新率 | 长时间观看、后台音频 |
| 均衡 | 触摸、滚动和页面转场时请求设备最高刷新率,稳定约 1.5 秒后交还系统 | 日常浏览与交互 |
| 高性能 | 应用位于前台时持续请求设备最高刷新率 | 优先操作跟手性,耗电和发热更高 |
源码中三档对应 lib/core/models/app_refresh_rate_mode.dart 的枚举:
enum AppRefreshRateMode { powerSaving('powerSaving'), // 新安装默认 balanced('balanced'), // 仅交互期间请求最高刷新率 performance('performance'); // 前台期间持续请求 ... }3.2 旧开关迁移与新安装默认
旧版“高刷新率”开关开启的状态会迁移为“均衡”,关闭或新安装则使用“省电”。迁移逻辑位于 lib/core/config/app_settings_controller.dart:
static AppRefreshRateMode _legacyRefreshRateMode(Object? enabled) { return enabled == true ? AppRefreshRateMode.balanced : AppRefreshRateMode.powerSaving; } static String _initialRefreshRateMode() { final stored = HivePrefUtil.getString('refreshRateMode'); if (stored != null) return AppRefreshRateMode.parse(stored).storageValue; return _legacyRefreshRateMode(HivePrefUtil.getBool('enableHighRefreshRate')).storageValue; }即优先读取新的refreshRateMode存储键;不存在时回退读取旧的enableHighRefreshRate布尔值,并把true映射为“均衡”。设置页 lib/features/settings/pages/general_settings_page.dart 提供三档单选组,同时展示系统当前刷新率与检测到的最高刷新率。
3.3 协调器状态机:均衡模式的 1.5 秒交还
均衡模式的“交互期间请求、稳定后交还”由 lib/core/widgets/adaptive_refresh_rate_scope.dart 的AdaptiveRefreshRateController实现:
static const settleDelay = Duration(milliseconds: 1500); static void beginPointer() { if (_mode != AppRefreshRateMode.balanced || !_isResumed) return; _activePointers++; _settleTimer?.cancel(); _requestHigh(true); } static void endPointer() { if (_activePointers > 0) _activePointers--; if (_mode == AppRefreshRateMode.balanced && _isResumed && _activePointers == 0) _scheduleSettle(); } static void _scheduleSettle() { _settleTimer?.cancel(); _settleTimer = Timer(settleDelay, () => _requestHigh(false)); }核心机制:
AdaptiveRefreshRateScope通过Listener监听onPointerDown/Move/Up/Cancel,通过NotificationListener监听ScrollStart/Update/End,把触摸、滚动、转场事件转译为对协调器的beginPointer / keepInteractive / endPointer调用。- 请求去抖:指针保持期间持续
keepInteractive维持高刷新率;全部指针抬起后启动 1500 ms 的 settle 定时器,交互稳定后才把窗口交还 Android 动态策略。这是因为 Android 建议不要每秒多次变更窗口帧率提示——物理模式切换本身可能丢帧,所以保留一次交互脉冲的稳定窗口。 - 高性能模式:
_requestHigh中_isResumed && (_mode == performance || (balanced && high))决定了请求目标;前台期间持续为 true,无需指针事件。 - 生命周期协同:
didChangeAppLifecycleState对resumed与inactive都调用resume()——Android PiP 在 Flutter 报inactive时仍可见,保持窗口策略可以让 PiP 弹幕与原生 surface 一致;hidden / paused / detached则释放高刷新率请求。 - 请求合并:高/低请求经
LatestAsyncValueQueue<bool>串行提交,避免频繁往返 MethodChannel。
3.4 原生桥接:preferredRefreshRate 与 DisplayManager 监听
Dart 侧的请求最终通过pure_live/display_mode通道送达原生层。Dart 封装在 lib/core/config/display_mode_service.dart,原生实现在 android/app/src/main/kotlin/com/mystyle/pure_live/MainActivity.kt:
private fun applyPreferredDisplayMode(enabled: Boolean): Map<String, Any> { ... val compatibleModes = activeDisplay.supportedModes.filter { it.physicalWidth == currentMode.physicalWidth && it.physicalHeight == currentMode.physicalHeight } val preferredMode = compatibleModes.maxWithOrNull( compareBy<Display.Mode> { it.refreshRate }.thenBy { it.modeId }, ) ?: currentMode ... val targetModeId = 0 val targetRefreshRate = if (enabled) preferredMode.refreshRate else 0f attributes.preferredDisplayModeId = targetModeId attributes.preferredRefreshRate = targetRefreshRate window.attributes = attributes ... }几个值得注意的实现细节:
- 只改刷新率、不钉死显示模式:代码刻意保留
targetModeId = 0,只设置preferredRefreshRate。注释说明钉死preferredDisplayModeId可能强制厂商模式切换、给调度器在 Flutter、视频与 PiP surface 之间的协调留下更少余地。 - 同分辨率兼容模式:从
supportedModes中过滤出与当前模式物理宽高一致的候选,再取其中刷新率最高者,避免切刷新率时改变分辨率。 - DisplayManager 监听:原生注册
DisplayManager.DisplayListener,在 display 增加、移除或变化时通过displayModeChanged事件把最新信息推回 Dart;Dart 侧DisplayModeInfo.fromMap解析currentRefreshRate / maxRefreshRate / supportedRefreshRates / requestedRefreshRate等字段,并在设置页展示。 - 低版本回退:Android M 以下直接返回 60 Hz 静态信息,避免调用不存在的 API。
3.5 视频帧率与界面刷新率分离
文档强调播放器视频帧率与界面刷新率仍分开管理,避免把 30/60 FPS 直播重复渲染到 120/144 Hz。也就是说:刷新率策略只影响 UI 窗口的绘制节奏(列表滚动、转场动画的跟手性),视频渲染仍按源帧率输出,二者互不耦合,从而在提升交互流畅度的同时控制 GPU 负载与功耗。
四、Windows 与界面稳定性治理
v2.5.0 对 Windows 端和通用界面做了五项稳定性改进:
- MediaKit 视频纹理按需分配:Windows MediaKit 视频纹理按控件可见尺寸和设备像素比设置,并保持源视频比例;小窗不再长期维持超大原生纹理,显著降低小窗播放时的显存占用。
- 180 ms 尺寸去抖:窗口尺寸变化经过 180 ms 去抖,窗口拖动或缩放期间减少重复原生重建(原生侧
scheduleDisplayModeRefresh(delayMillis = 160)也体现了同类“延迟合并”的思路)。 - 分区卡片一致性:分区卡片比例、标题省略规则同步上游;切换房间列表统一使用有边界、平台一致的滚动物理效果(对应仓库中
PureLiveScrollPhysics等平台物理策略,详见 docs/FAVORITE_REFRESH_DESIGN.md 对下拉刷新物理链的讨论)。 - 热门接口失败兜底:热门接口失败时保留上一份可用快照,避免网络波动造成整页清空。
这些措施共同的目标是:无论窗口如何变化、网络如何波动,列表与播放器都保持稳定的呈现状态,不出现闪烁、清空或纹理浪费。
五、依赖与上游治理
5.1 上游同步与依赖版本
- 通过真实 merge 同步上游到
b84a847d,保留维护分支的正式发布地址、数据迁移和播放器稳定性增强。 ffmpeg_kit_extended_flutter更新至 0.6.0,flutter_color更新至 2.1.1。dynamic_color使用 Flutter 3.47 可直接兼容的 1.9.0;文档明确其 2.x 已迁移到独立material_ui.ColorScheme类型,待应用整体 Material UI 迁移时再升级。
从当前仓库的 pubspec.yaml 可以看到这一升级路线已按计划落地:ffmpeg_kit_extended_flutter: 0.6.2、flutter_color: ^2.1.1、dynamic_color: ^2.1.0配合material_ui: ^1.4.0桥接包——正是文档预告的“Material UI 迁移”完成后的状态,可视为该依赖策略的后续验证。
5.2 FFmpeg 原生包 SHA-256 缓存
FFmpeg Android/Windows v0.11.0 原生包在本机构建前按 SHA-256 校验并进入持久缓存,质量门禁不再重复走 Native Assets 网络下载。这是构建资源治理的一部分:二进制依赖先验签再缓存,既防篡改,又避免 CI 每次重复拉取大体积原生包(完整构建策略见 BUILD_POLICY.md)。
5.3 工具链兼容复核
Git 固定依赖、内置 Kotlin 插件和构建工具链均按远端提交、包版本与 AGP 兼容矩阵复核,确保 v2.5.0 的构建环境是可复现的固定组合,而非“当前最新版”。
六、自动验证范围与门禁结果
v2.5.0 的自动验证覆盖六类行为,与上述改动一一对应:
- 首页有界并发:并发上限、顺序保持、任务异常隔离。
- 刷新率三档:新安装默认值、旧设置迁移、前后台切换、交互结束释放。
- 首页/分区刷新:保留旧快照、串行刷新、页面销毁后不发布(epoch 取消机制)。
- 播放器与弹幕:PiP 返回重订阅、横竖屏切换、本地弹幕同步、会话代次隔离、队列和对象池上限。
- Windows 视频尺寸:物理像素换算、比例保持、不放大源视频、无元数据时的临时上限。
- 平台公开接口探测:分类、推荐、搜索、房间信息、播放令牌和弹幕节点。
2026-08-22 本机门禁结果:Flutter Analyze 0 issue、208 项单元/Widget 测试全部通过,平台公开接口探测 26/26 通过。安装包哈希和平台来源以 v2.5.0 Release 中的BUILD_METADATA.json与SHA256SUMS.txt为准。
七、已知边界与验证前提
文档如实列出了该版本无法在源码门禁内覆盖的边界:
- 真机指标:真实帧时、温度、PSS(进程常驻内存)和系统刷新率需要对应 Android 设备采样;源码门禁只负责验证状态机和策略选择。仓库 tool/ 下的大量
android_*、windows_runtime_metrics.ps1等脚本即承担这类实机采样职责。 - 平台接口快照:直播平台接口随服务端策略变化,26 项探测是发布时快照,运行时继续保留超时、旧快照与重连逻辑。换言之,探测结果标注的是“发布时刻的服务端行为”,运行期容错不依赖探测永远正确。
- 构建平台优先级:Linux、macOS、iOS 由对应系统构建;Windows 主机优先完成 Android arm64 和 Windows x64,以减少远程构建额度占用。
八、小结
v2.5.0 阶段稳定版的技术主线可以概括为三条:
- 刷新链路“有界 + 事务化”:用有界异步工作池替代固定批次,让慢平台只占自身槽位;超时收紧、启动更新检查让行,配合单锁串行、快照签名去重与空终态发布,同时解决了首屏速度、排序一致性与无限加载三个问题。
- Android 刷新率“策略化”:从单一开关升级为省电 / 均衡 / 高性能三档,旧设置自动迁移;均衡模式通过指针与滚动事件驱动、1500 ms 稳定后交还系统,原生侧只调
preferredRefreshRate而保留显示模式自由度,并把视频帧率与界面刷新率彻底分离。 - 稳定性与可复现性并重:Windows 视频纹理按需分配、180 ms 去抖、失败保留旧快照;依赖与原生包按固定版本、SHA-256 校验与兼容矩阵治理,全部行为由 208 项测试与 26 项平台探测把关。
对于需要为聚合类直播客户端设计“多源并发刷新 + 高刷新率设备适配”的开发者,本版本提供的boundedAsyncMap工作池、事务式快照发布和AdaptiveRefreshRateController状态机,都是可以直接借鉴、可独立验证的工程样本。进一步可阅读 docs/FAVORITE_REFRESH_DESIGN.md 了解刷新与状态一致性的完整设计约束,或在 docs/README.md 文档索引中查找对应批次的验证证据。
- 音视频
- 直播
- 移动开发
【免费下载链接】pure_live
纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。
相关推荐
pure_live v2.1.4 阶段更新全解读:平台接口稳定化、Windows 高刷新率适配与全平台构建交付
pure_live v2.1.4 阶段更新全解读:平台接口稳定化、Windows 高刷新率适配与全平台构建交付 导读 v2.1.4 是 pure_live(纯粹
音视频直播移动开发Hammer.js版本发布策略:平衡新功能与稳定性
Hammer.js版本发布策略:平衡新功能与稳定性 在Web开发中,手势交互已成为提升用户体验的关键因素。作为最受欢迎的JavaScript手势库之一,Hamm
前端UI组件BongoCat依赖更新频率:安全与稳定性平衡策略
BongoCat依赖更新频率:安全与稳定性平衡策略 引言:依赖管理的双刃剑 你是否曾因依赖更新陷入两难:不更新面临安全漏洞风险,更新又可能引入兼容性问题?Bon
桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考