mcpField
M
media-playback-footguns

media-playback-footguns

github.com/maxrave-dev开发1.7k90 次安装1 周前更新
代码仓库

关于 media-playback-footguns

面向 Kotlin 和 Compose Multiplatform 开发者的媒体播放排错技能,汇集生产应用中发现的播放陷阱及对应验证方法,按主题索引供实现或排查时查阅。

主要功能

  1. 01收录 33 个媒体播放陷阱
  2. 02每个主题提供独立短文档
  3. 03说明常见故障及成因
  4. 04提供针对项目代码的验证命令

适用场景

  1. 01实现双播放器交叉淡化
  2. 02排查队列切换或后台播放中断
  3. 03调试音频处理与音量渐变
  4. 04验证多平台播放行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/maxrave-dev/kotlin-footguns --skill media-playback-footguns

安装后在 Claude Code 中输入/media-playback-footguns确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
audio-fade-separate-gain-line.md
audio-focus-multiplayer.md
beatmatched-automix.md
crossfade-dual-player.md
crossfade-exclusion-heuristics.md
custom-shuffle-order.md
derive-seek-from-progress-flow.md
dual-source-queue-sync.md
endless-queue-management.md
fgs-state-ended-trap.md
filter-chain-two-owners-one-writer.md
follower-item-built-from-shared-payload.md
follower-transport-stays-local.md
forwarding-player-hot-swap.md
guard-on-every-trigger-path.md
intent-flag-not-observed-state.md
joiner-catches-up-by-asking.md
media3-custom-audio-processor.md
one-setting-two-backends.md
optional-engine-feature-degrade-in-tiers.md
per-track-loudness-normalization.md
play-intent-decided-before-load.md
playback-position-persist-restore.md
polymorphic-load-media-entry-point.md
position-based-group-sync.md
queue-index-vs-shuffle-space.md
queue-rebuild-state-machine.md
readiness-barrier-needs-every-answer.md
realtime-biquad-dsp.md
runtime-override-not-preference-write.md

详细介绍

媒体播放中的陷阱

从一款生产环境中的 Kotlin 和 Compose Multiplatform 应用中挖掘出的 33 个陷阱,每个陷阱对应 references/ 下的一个短文件。每个文件都介绍可行模式、陷阱(实践中会导致失败的具体情况及原因),以及可对用户自己的代码树运行命令的验证方式部分。

如何使用此索引。 将即将编写的代码或正在排查的症状与下方条目进行匹配:每个条目都注明主题,以及遇到什么症状时应查看该条目。在提出代码或修复方案前,使用 Read 工具阅读所有可能适用的文件;每个文件都少于 150 行。如果验证命令中写有源项目的路径,请替换为用户代码树中对应的路径。

交叉引用。 如果某个文件中反引号括起的陷阱名称未列于此处,它位于相邻的领域技能中:使用 glob ../*/references/<name>.md 打开它。

播放器、交叉淡化与引擎生命周期

  • crossfade-dual-player — 使用每个媒体项对应一个播放器实例,并在混音期间启用第二个实时实例,在两个播放器同时有声音时,正确处理传输命令和播放设置。适用于为播放器添加淡入淡出,或暂停时旧曲目仍在后台播放、跳转似乎不起作用、跳过后播放速度恢复为 1.0x,或音量滑块与渐变效果相互干扰的情况。
  • guard-on-every-trigger-path — 确保由两个入口触发的功能(通常是轮询循环和单项播放结束回调)在两条路径上的启动条件一致,因为只加到其中一条路径的条件会成为死代码,不报错、不记录日志,也不会崩溃。适用于新添加的守卫条件、排除规则或功能标志似乎完全不起作用,或某项功能大多数时候表现正常,却在某个特定时机表现异常的情况。
  • crossfade-exclusion-heuristics — 判断何时不应执行淡入淡出:项目以视频形式播放、项目时长不足以完成淡入淡出,或相邻两个项目属于同一张专辑;并以能兼容随机播放、自动时长淡入淡出和持续增长的队列的方式实现每条规则。适用于淡入淡出导致歌曲提前结束、视频在上一首曲目尚未结束时跳到第一帧、25 秒的间奏有一半时间都在淡入淡出,或原本应连续播放的专辑在每首曲目之间都被打断的情况。
  • beatmatched-automix — 根据两首曲目之间的差异,像 DJ 一样推算自动淡入淡出时长并匹配节奏和调性:比较节奏前先进行半速归一化、将时长量化到节拍、采用前置渐变,以及量化增益和速度变化步长。适用于自动转场时长感觉不合理、节奏相差一倍的曲目被视为差距很大、节奏匹配直到上一首曲目已听不见后才生效,或渐变调速会产生杂音的情况。
  • forwarding-player-hot-swap — 在运行时替换 ForwardingPlayer 底层的播放器,同时让媒体会话和 UI 保持同一个稳定引用;重新附加监听器和视频输出,并处理单项时间线无法回答的播放列表查询。适用于系统通知中的下一首/上一首按钮消失、首次切换后没有任何更新、切换曲目后视频停止渲染,或报告播放列表索引导致应用停止的情况。
  • fgs-state-ended-trap — 在转发播放器的边界处抑制“已结束”播放状态,同时替换底层播放器,避免媒体服务在一位播放器结束到下一位播放器启动的间隙被拆除。适用于某些设备上后台播放会在队列中途停止,但在开发手机上始终正常、播放通知在曲目之间消失,或应用在队列播放过程中被系统冻结的情况。
  • polymorphic-load-media-entry-point — 用一个通用的“加载此项目并开始播放”函数,将多种项目类型归一化为单一内部格式,根据判别字段选择队列初始化策略,并在同一位置处理所有入队前策略。适用于播放可从多个界面启动,而某项规则(内容筛选、去重、分析、恢复播放但不自动播放)总会在某条路径上被遗漏的情况。
  • play-intent-decided-before-load — 在构建新加载的项目之前,先决定是否应开始播放以及从何处开始,然后将这两项传入加载调用,而不是先用硬编码的“开始播放”进行加载,再过一会儿纠正。适用于同步房间中的跟随者在其他人都已暂停的会话中突然发出声音,或新加载的项目先响起、随后又停止的情况。
  • intent-flag-not-observed-state — 已决定运行但尚未实际运行的组件会报告“未运行”,因此表示意图的逻辑必须读取意图标志,而非观测状态;在状态转换期间,还必须等待意图标志并设置超时,而不是直接内联采样。适用于某个客户端的缓冲卡顿导致整个同步群组停止、后台向队列追加内容时即将开始的曲目被静音、每个时钟周期都发出恢复命令,或状态读取恰好在它本应处理的状态转换期间出错的情况。
  • derive-seek-from-progress-flow — 利用各平台都会发出的进度流,检测播放头是否移动得比墙上时间所能解释的更远,以此识别拖动进度条;平台自带的跳转回调只存在于一种后端,而且拖动进度条时,项目流和传输流都不会触发事件。适用于共享会话中的一位成员跳转后该操作始终未发送,或跨平台层需要某个平台才提供的事件的情况。
  • optional-engine-feature-degrade-in-tiers — 媒体引擎的可选构建时依赖可能缺失于其他平台的程序包中;引擎遇到未知阶段时会拒绝整条链字符串。因此,应在不包含可选阶段的情况下重试,而不是丢失必需阶段,并返回哪些层级已被接受,以免调用方启用不存在的阶段。适用于某项功能在一个平台的程序包中正常工作,却在另一个平台上静默失效的情况。

音频处理:焦点、淡入淡出、响度与 DSP

  • media3-custom-audio-processor — 为 Media3/ExoPlayer 音频管线编写自定义音频处理器,例如滤波器、淡入淡出或增益级,并在运行时切换,供多个并发播放器共享。当你添加的处理器直到下一首曲目才生效、播放卡住却没有报错、put(ByteBuffer) 在你自己的输出缓冲区上抛出异常,或两个同时运行的播放器需要接收同一个参数时使用。
  • realtime-biquad-dsp — 使用音频 EQ cookbook 公式,纯用 Kotlin 构建小型实时 IIR 滤波器:低通、高通或一组峰值滤波段;支持级联以获得更陡的斜率、各声道独立状态、保持状态大小固定的中性滤波段,以及延迟系数重算。当需要在音频回调中使用可扫频滤波器、立体声滤波器导致立体声声像收窄、滤波器输出为静音或 NaN,或扫动截止频率、拖动频段时出现咔嗒声时使用。
  • audio-fade-separate-gain-line — 在独立的增益线上实现程序化音频淡变,例如睡眠定时器、闹钟渐强或闪避,而不是改变用户音量;并从播放器自身的完成路径恢复该增益。当淡变导致 UI 中的音量滑块被拉低、应用恢复后滑块显示为满格但永久静音、淡变结束时仍有可听见的咔嗒声,或淡变完成后音频短暂回升时使用。
  • audio-focus-multiplayer — 当多个播放器实例同时运行(例如双播放器交叉淡变、预缓存播放器)时,在应用级别只持有一次 Android 音频焦点,并避免焦点驱动的闪避影响淡变已控制的增益线。当后台播放在曲目之间停止、自动播放在第一项后卡住、闪避始终未生效或未解除,或过渡过程中音量突然恢复到最大时使用。
  • per-track-loudness-normalization — 当每首曲目使用各自的播放器时,借助平台的响度增强器进行逐曲目响度归一化:由于效果会附着到单个音频会话,因此每首曲目都重新创建效果;远程播放会话处于活动状态时跳过;并限制元数据提供的增益。当归一化只对第一首曲目生效,之后便悄无声息地失效、未播放任何内容时启用归一化却抛出异常,或队列中每隔一首曲目音量就更大时使用。
  • filter-chain-two-owners-one-writer — 两个独立功能都需要在某个引擎属性中添加条目,但该属性保存的是整个链,因此写入会替换全部内容。将每个功能的条目保存在各自字段中,由一个写入器统一组合,并让“清除”只移除自身层级。当需要在现有效果旁添加第二个效果,或两个效果中有一个生效后,在某次过渡后又随机失效时使用。
  • sampled-supplier-vs-per-handle-reapply — 两种播放后端需要用相反的方式配置同一个设置:一种后端的各流消费者会读取共享字段,因此无需推送;另一种后端的每个新句柄初始值为空,因此需要在每个创建位置显式重新应用。当设置作用于当前曲目却不作用于下一首、在一个平台上有效而在另一个平台上无效,或在多个句柄上设置的级别不断恢复原值时使用。
  • one-setting-two-backends — 通过一次性定义频段中心、宽度和范围,让一个存储值在两个互不相关的音频后端上表示相同含义;对照参考实现验证两者,而不是凭耳朵判断;并避免使用参数因设备而异的平台内置效果。当需要在多个平台上添加音调或增益设置,或相同的已保存设置在不同平台上听起来不同时使用。

队列、随机播放与位置

  • queue-index-vs-shuffle-space — 播放器的“当前索引”会显示在 UI 中,但切换曲目时会冻结,或者开启随机播放后高亮错误的行。这是因为引擎的时间线顺序与随机播放顺序属于两个不同的索引空间。当队列列表中的正在播放标记卡住、偏移一行,或同一曲目的每个副本都被点亮时,请使用此方案。
  • custom-shuffle-order — 替换媒体引擎的默认随机播放顺序,使播放期间添加的曲目紧接当前曲目之后排列,而不是散落在队列其余位置。当开启随机播放时,“下一首播放”或追加的后续页面被随机放置,或编写自定义随机播放顺序并需要确保插入、删除和克隆操作的约定保持一致时,请使用此方案。
  • endless-queue-management — 一个 StateFlow 持有不断增长的播放队列,但有意保留两条写入路径:完整 setter 会重置派生快照,而追加后续内容时则直接写入底层字段,以保留这些快照。当依赖“队列来源”的功能在队列自动扩展后失效,或准备将两条队列写入路径重构为一条时,请使用此方案。
  • queue-rebuild-state-machine — 队列上的重建状态标记用于区分“正在重建”和“稳定”状态(枚举中定义的两个有效值),使重入的加载请求提前返回,并防止在队列尚未构建完成时对其进行快照。当一次滚动触发两次分页、恢复的队列缺少正在播放的曲目,或发生错误后加载状态始终无法清除时,请使用此方案。
  • dual-source-queue-sync — 面向 UI 的曲目列表和播放引擎的时间线都持有队列,因此每次引擎侧发生变更后,都会根据媒体 ID 从引擎时间线重新生成 UI 列表;当两侧数量不一致时会拒绝更新;用户重新排序时则会同时修改两侧。当屏幕上的队列顺序与实际播放顺序不同、随机播放打乱列表却没有打乱播放顺序,或准备新增一个队列写入位置时,请使用此方案。
  • playback-position-persist-restore — 以低开销方式保存和恢复播放位置:在生命周期边界完整保存队列,而轻量的位置更新每五秒执行一次并复用现有循环;队列重建期间跳过保存;发出会改变值的播放器命令前先创建值快照。当进程被终止后,后台播放从曲目开头恢复,或恢复到错误的曲目或位置时,请使用此方案。

共享聆听会话

  • sync-room-bridge-echo-guard — 一个桥接器将本地组件接入共享房间,并根据角色决定数据流向:源端发布自己的操作,跟随端应用收到的内容且不发布任何内容;此外,在应用期间设置一个标志,避免将远程命令在本地触发的副作用直接反馈回去。适用于同步会话中的两个客户端互相来回发送消息、应用远程暂停后立即重新发布暂停命令,或跟随端对自己未触发的状态变化作出反应的情况。
  • server-default-overrides-client-intent — 中继服务拥有共享状态,会将自己的默认值写入命令未设置的字段——最棘手的是,每次条目变更都会强制设为“未运行”——因此,逐字遵循消息的跟随端会停止刚加载的内容。跨变更保留先前的意图,并通过第二条命令发布缺失字段。适用于同步房间中的跟随端每次切换到下一项、上一项或结束当前条目时都会静默,而源端仍在播放的情况。
  • position-based-group-sync — 每次命令都发布播放头位置,并根据命令传输所花的时间修正位置;只有本地差距超过容差时才跳转,以此让一组客户端保持同步,而不是让所有人等待最慢的成员。适用于同步会话明显不同步、跟随端持续卡顿以追赶源端,或每台设备自行解析流、因此就绪时间各不相同的情况。
  • joiner-catches-up-by-asking — 中继服务推送给新成员的状态,是重放源端的最后一条命令,因此其中的位置可能已经过时;照此执行会让新成员从其他人已经看到一半的内容开头开始播放。加入后立即询问当前实时位置,并在此客户端每次重新加入共享时间线时再次询问。适用于会话中途加入的成员从头开始播放,或从自己上次在本地停下的位置重新开始的情况。
  • follower-transport-stays-local — 共享会话中的跟随端拥有自己的停止按钮:暂停仅在本地生效且不发送消息;按下播放时会询问会话的当前位置,而不是恢复此设备停止时的位置。适用于跟随端无法暂停,因为下一次状态更新会立即恢复播放;跟随端暂停会让所有人停止;或恢复播放后落后群组数分钟的情况。
  • runtime-override-not-preference-write — 在某个模式持续期间,使用组件上的运行时覆盖来关闭面向用户的功能,绝不要写入已保存的偏好设置——如果进程在模式中途退出,用户的实际设置就会被永久更改。适用于进入某个模式时必须禁用现有功能、某项设置莫名其妙地自行关闭并保持关闭,或只有模式清理流程才能避免用户丢失偏好设置的情况。
  • follower-item-built-from-shared-payload — 根据共享会话携带的有效负载构建跟随端的可播放条目,而不要从自己的目录中重新解析——根据封面形状推断媒体版本的本地解析器会选错版本,而应用收集器中的逐条目网络往返会卡住后续所有命令。适用于同步房间中的跟随端收到没有音频的视频,而源端有音频,或某次缓慢查询冻结客户端整个命令流的情况。
  • readiness-barrier-needs-every-answer — 让屏障等待所有成员报告就绪后再放行整个群组:根据缓冲状态而非播放状态响应;只有在确实有人等待你时才响应;在界面中说明正在等待谁;并意识到只要有一名成员始终不响应,所有人都会被卡住。适用于一台较慢的设备加入后共享会话中的所有参与者都停滞、播放始终没有开始却毫无提示,或“正在加载”状态没有附带任何说明的情况。

评论

支持 Markdown