SuperAPI AI 工具集

dual-source-queue-sync

编程与开发

查看项目

更新于 10-17 16:15

技能介绍

该技能面向 Kotlin 播放器应用中的队列状态管理问题:播放引擎保存一条媒体时间线(媒体 id、uri、元数据),应用保存一份更丰富的领域列表供 UI 使用(封面、歌手、点赞状态等引擎没有字段的信息)。两者共同构成队列,但顺序权威只能是引擎,因为真正决定下一首播放什么的是引擎。

用途与思路 核心做法是在引擎侧发生任何变更之后,按媒体 id 从引擎时间线重新派生领域列表。示例函数 reorderShuffledQueue 接收时间线,用 listTracks.firstOrNull { it.trackId == item.mediaId } 逐项匹配,若匹配数量与列表数量不一致则直接返回,否则把排序结果写回队列数据。作者强调应把它读作一次 join:时间线提供顺序,领域列表提供内容,两侧都不臆造数据。

主要陷阱

  1. 反向派生:在 shuffle 后把领域列表顺序推回引擎,等于把随机计算两次(引擎一次、代码一次),两者不会一致,因为引擎会按自己的节奏重新洗牌;引擎的顺序不是可以被推翻的意见,而是下一首的实际结果。
  2. 数量检查是拒绝而非修复:时间线在重新编目、恢复、分块插入期间可能只到达部分内容;用部分映射写“尽力而为”的顺序会静默丢弃尚未加入的行。直接返回没有代价,补全时间线的变更会再次触发回调,下一次完整遍历会写入正确顺序。
  3. 空时间线缺少提前返回会把清空变成抹除:对空列表做 mapNotNull 得到空列表,若领域列表此刻也为空则数量检查通过、写入无害;但在引擎已清空而领域列表未清空的那一帧会造成数据丢失,须在顶部显式判断 timeline.isEmpty()。
  4. 按 id 匹配会把重复项坍缩到第一次出现:firstOrNull 把同一曲目的两条时间线记录映射到同一领域对象,数量检查无法发现(每条时间线记录仍对应一条输出)。内容相同时无害,内容不同时(不同播放列表位置、不同逐条状态)则是真实丢失;若条目带逐条状态,应以条目唯一键而非内容唯一键做 join,并注意不要用 id 解析正在播放的条目。
  5. 用户重排序必须按顺序同时改两侧:先调用 moveMediaItem 改引擎,再交换领域列表中的两项并写回,最后用 player.currentMediaItemIndex 重新发布当前索引,因为移动改变了该值。只改列表会出现“显示新顺序、播放旧顺序”;只改引擎会在下一次重派生时把行闪回原位;漏掉索引重发布,会让“当前索引”看起来只在拖动时更新、切歌时从不更新。
  6. shuffle 开启时,按位置插入涉及两个不同整数:“下一首播放”在引擎中是 player.currentMediaItemIndex + 1(时间线空间),而领域列表按播放顺序维护,该整数只在 shuffle 关闭时才能正确索引列表;第二次写入前需转换为播放顺序位置。把引擎整数同时写入两侧在 shuffle 下会把行记到错误的列表位置,且在后续某次完整重派生偶然修复前一直不可见。
  7. 触发重派生的回调不止一个,且必须全部触发:追加和移除会改变时间线,切换 shuffle 会在时间线不变的情况下改变顺序,两者调用同一函数;每条加载路径在翻转状态标志后也要显式调用,用 grep 检查每个加载、分页和错误分支是否都出现该调用,缺少分支会让 UI 停留在旧顺序直到无关事件触发。
  8. 不要在 composable 中做重派生:多个界面(sheet、迷你播放器、封面翻页器)渲染队列,各自做 join 会各自复制这些陷阱;应在 handler 中只做一次 join,让所有界面收集同一份列表。

验证方式 开启 shuffle 并保持队列可见,确认列表顺序与播放一致,而不只是播放变随机;然后拖动一行,检查列表顺序、引擎顺序和正在播放标记三者一致;应直接读取引擎顺序(player.getCurrentMediaTimeLine())而非相信屏幕,因为屏幕正是被测试的一方。

限制 资料仅给出上述思路、代码片段和陷阱说明,未提供安装命令、依赖配置、完整项目集成步骤、兼容的 Kotlin 或引擎版本、性能数据与授权信息,均暂未提供。适用场景为 Kotlin 编写的、引擎与 UI 各自持有队列的媒体播放应用;不涉及具体第三方播放引擎的接口细节。