## Parent [#85](https://github.com/wt159/SoundBridge/issues/85) — [Spec] 播放器状态与公开 API 契约实现计划。 ## What to build **同一曲目会话内 `onPositionChanged` 单调不回退、换曲时归零**。现状位置回调经二次 `asyncRunTask` 回投 WorkQueue 的 `priority_queue`,同刻任务不保证 FIFO,乱序会让 UI 进度条倒退;这不是调用方该自己处理的事。 - 在位置上报的出口按「曲目会话 + 上次已上报值」做单调钳制(乱序的小值丢弃,不重发、不改查询值);换曲时随曲目会话重置。 - `position()` 实时查询始终是真相源,钳制只作用于**事件通知**,不得回写查询值或影响 seek 精度判据。 - 用既有时序断言骨架补一条乱序 / 换曲归零的可判定用例,不裸 sleep。 ## Acceptance criteria - [ ] 同曲内回调序列单调不回退,换曲后第一个 position 属于新曲且从零附近开始 - [ ] `position()` 与回调差仍 ≤50ms,seek 精度 `max(1%,150ms)` 不变 - [ ] 既有 `test_playback_position.cpp` 与 `TestSdkSuite` 位置用例全绿 ## Blocked by [#86](https://github.com/wt159/SoundBridge/issues/86)(GitHub 原生 blocked_by 已连接)。. **必须遵守(来自 spec、ADR 0002/0003/0004 与 `doc/bad-input-spec.md`):** - 七个控制入口保持 `void`;不新增错误码、不改 `onError` 签名、不新增状态机、不扩状态集合(对外仍 `Playing/Paused/Stopped` 三态)。 - 判据**不新造数值**:沿用 `max(1%,150ms)`、`r × good`、5pp、零容差差分、getter 与回调 ≤50ms、状态序列无相邻重复、每曲一次 duration。 - 坏输入 55 例 / 396 断言与覆盖度矩阵 15 行**一字不动**;14 个 `sdk_*` ctest 目标、TIMEOUT、`-j4`、快集 / 全量集划分全部不动。 - 新增用例若写文件必须带 pid(`-j4` 撞名假红的教训);线程契约用 `promise/future` 或轮询验证,不裸 sleep。 - 事实基线见 [#76](https://github.com/wt159/SoundBridge/issues/76) 的 `research/public-api-contract-facts` 分支;术语见 `GLOSSARY.md`。
Activity
fix: position 回调同曲单调不回退(#88 / T03)
fix: 重播会话重置位置单调基准(#88 / T03 回归修正)
wt159 commentedon Nov 8, 2026
Resolution(2026-11-08,图外实现 effort #85 / PR #93)
新增
reportPosition(positionMs, trackIndex, resetFloor, epoch),统一_setPosition(seek 合法回退)、consumeAudioData的二次asyncRunTask、putMusicPlayListCurBuf(换曲归零)三个位置出口。用m_positionFloor钳制乱序旧值、m_positionTrackIndex隔离曲目,并用m_positionEpoch(code-review M1 修复)让 seek / 换曲 / 从停机重播之前已投递的旧任务整体失效 —— 单靠 trackIndex 无法区分同曲的两次会话,旧值会把新基准重新抬高。钳制只作用于事件通知:不回写
signalProperties.curPositionMs、不改position()查询口径、不动 seek 精度判据。_play()从 Stopped 起步时bumpPositionSession(),修掉了中途发现的「stop 后重播位置被 floor 钳在旧值」回归。判据:稳态同曲单调、seek 合法回退不被吞、回退后新会话仍单调;全量 14/14 绿。