SuperAPI AI 工具集

play-intent-decided-before-load

编程与开发

查看项目

更新于 10-17 16:17

技能介绍

该条目讨论 Kotlin(Android 媒体播放)中的一个典型“脚枪”问题:在跟随远端播放指令时,加载与传输状态修正之间的顺序不确定会引入竞态。

用途 用于指导实现远程同步播放(如多人房间、共同收听场景)中的 apply/follow 路径:应当在构建和加载条目之前就决定“是否开始播放”和“从哪里开始播放”,并将这两个决策作为加载调用的参数传入,而不是先以“播放”状态加载、事后再修正。

主要能力与要点

  • 指出错误写法:load(item, startPlaying = true) 之后再调用 applyTransport(isPlaying, position)。由于“加载调用返回”与“输出打开”之间没有顺序保证,加载路径可能在修正之前、之中或之后就开始播放,造成在暂停的房间中播出声音,且结果取决于网络,表现为“偶发”问题。
  • 推荐写法:先用 intentForThisChange(remoteIsPlaying) 得到 playing,再按三种位置情形计算 startAt,最后调用 loadItem(item, keepPosition = startAt, startPlaying = playing)。
  • 位置三种情形:同一条目因队列后到而被重建时保留 player.currentPosition;远端位置小于等于 0 时使用 0L(“从头开始”是一个值,不是过期时钟);其余情况按房间进度计算加入位置。
  • false 是有效值:在加载边界把参数设为非可空,让编译器收集所有调用点,避免可空默认“播放”重新引入缺陷。
  • 零位置不是过期时间戳:需要在时钟修正之前特判非正值,否则空闲房间可能被定位到条目末尾之后。
  • 只有队首条目携带播放标志:追加其余队列时不得再带,否则会重新主张播放并撤销已做出的决策;建议队首与队尾走不同调用。
  • 加载后不要用组件状态复核意图:“是否正在播放”是观测状态,“是否应当播放”是意图,二者在流解析完成前不一致,据组件状态确认会导致正确决策被回滚。
  • 播放头恢复必须放在条目添加之后,作为加载代码块的最后一条语句;对尚无条目的组件发起 seek 会被丢弃或钳制为零。这是“事后修正”真正正确的唯一场景。
  • 线程约束:桥接代码常在后台作用域运行,而受 UI 线程约束的组件被跨线程访问会抛异常,因此主线程包装是必要的,恢复播放头的 seek 也必须在同一块内。
  • 默认参数即策略:fun addItem(item: T, startPlaying: Boolean = true) 看似便利,实则让所有未思考该问题的调用点都开始播放,且按字面量搜索不到;应在关键边界避免默认值。
  • 队列后到导致的重建场景最容易漏测:若走“新条目”分支,会在播放中把同一条目从零重启,且只在两条消息按该顺序到达时出现,本地测试通常无法复现。
  • 加载点硬编码字面量是可搜索的缺陷形式:应用/跟随路径中的字面量是缺陷,用户主动操作中的字面量则合理。

使用方式(验证) 先用 grep 列出硬编码开始标志的加载点并逐一判断由谁决策: grep -rnE "(playWhenReady|startPlaying|autoPlay|shouldPlay) = (true|false)" --include=".kt" .
| grep -v "/build/" 再检查加载 API 是否本身携带该决策: grep -rnE "fun [a-zA-Z]
[Ll]oad[A-Za-z]*(|fun add[A-Za-z]Item(" -A6 --include=".kt" .
| grep -v "/build/" | grep -iE "playWhenReady|startPlaying|autoPlay" 行为验证需要两个客户端:在源端暂停后切换条目,跟随端必须加载并保持静音(任何可听突发都是竞态);在源端播放后切换,跟随端必须无卡顿开始(过度纠正会表现为加载后永不开始);加入一个已进行到一半的会话,跟随端必须落在其他人旁边,而不是零或超过末尾。

适用场景 适用于实现远程同步播放、多人共同收听、跟随控制指令加载媒体条目的 Android/Kotlin 播放器代码,以及相关代码审查与测试设计。

限制 条目未提供具体库版本、完整工程配置或性能数据;示例代码名称已泛化,需结合具体播放器 API 适配。部分结论基于特定播放器行为(如 seek 丢弃、线程约束),其他实现可能不同,需自行验证。