PRD-ios-playback-parity.md 22 KB

现场记录详情页左侧:iOS 回放功能等价复刻 PRD

版本:v1.0
日期:2026-07-25
目标项目:/Users/yuxin/local/code/celestia-trace/frontend

1. 结论

网页端当前左侧仅实现了“看起来像播放器和事件列表”的基础框架,尚未与 iOS 回放页功能等价。必须重做为真实数据驱动版本:

  • 只播放服务器实际音频;不得生成模拟音频掩盖文件缺失或下载失败。
  • 波形必须从实际音频按与 iOS 相同的规则提取;不得使用正弦函数绘制装饰波形。
  • 必须复刻三轨时间线、可拖动播放头、真实静音分析与跳过、照片/笔记/位置的增删改查、事件详情、全屏图片、标题持久化、同步状态和分享。
  • 网页端所有编辑必须写回云端,并能被 iOS 下一次同步正确拉取。
  • 页面结构、配色、字号、边框、圆角、图标语义和空状态应与 iOS 保持统一;网页右侧 AI 区域不在本 PRD 范围内。

2. 产品目标

用户在网页端打开一条已同步现场记录后,能够在左侧完成与 iOS SessionDetailView 相同的查看、回放、定位和事件编辑任务;同一条记录在网页与 iOS 之间应保持数据、时间位置和操作结果一致。

3. 范围与基准

3.1 iOS 功能基准

以当前工作区源码为准:

  • SessionDetailView.swift
  • MultiTrackTimeline.swift
  • PlaybackViewModel.swift
  • ChronoFeedRow.swift
  • ImagePicker.swift
  • TimelineLocationPicker.swift
  • CelestiaSession.swift
  • CelestiaTimelineEvent.swift
  • RemoteNetworkService.swift

注意:审计时 iOS 工作区的 SessionDetailView.swiftMultiTrackTimeline.swift 存在未提交修改,本 PRD按当前文件内容而非仅按 master 最新提交定义功能。

3.2 页面范围

网页详情页继续保留左右双栏:

  • 左侧:完整复刻 iOS 回放与事件管理能力,本 PRD范围。
  • 右侧:AI 逐字稿与报告区域,维持现状,不得影响左侧布局和滚动。
  • 顶部全局栏可保留“返回/关闭”,但记录标题、同步、Info、分享的布局和状态表达必须与 iOS 统一。

4. 信息架构与页面顺序

左侧内容从上到下固定为:

  1. 记录标题 + 紧凑同步状态。
  2. 三轨时间线:音频、图片、笔记。
  3. 回放控制卡。
  4. 事件列表。

顶部或标题区同时提供:

  • 记录信息 Info。
  • 分享录音。
  • 返回/关闭网页详情。

不得继续在左侧增加“与 iOS App 保持一致”等自我说明标签,也不得用独立的“添加网页端标记”表单替代轨道点击交互。

5. PRD 功能清单

A. 页面加载与真实数据

IOS-WEB-001 获取最新详情

  • 打开详情时必须调用 GET /sessions/{id},不得只依赖列表页传入的旧对象。
  • 加载期间显示骨架或进度状态;失败时显示可重试错误。
  • 成功后统一建立 session / events / assets / revision 页面状态。
  • 事件按 relativeTimeMs 升序排列。

验收:

  • 在另一端修改记录后,重新打开网页能读取新版本。
  • 详情请求失败时不展示伪造事件、音频、统计或同步状态。

IOS-WEB-002 实际音频加载

  • assets 中筛选 kind=AUDIO 的资源。
  • 若存在多个音频资产,按 iOS 同步规则选择最新有效资产,不得直接使用数组第一个。
  • 通过鉴权接口下载或流式读取真实音频。
  • 音频不存在、下载失败或解码失败时,显示明确不可播放状态。
  • 禁止创建合成 WAV 作为回退。

验收:

  • 播放内容与用户实际现场录音一致。
  • 缺少音频时播放按钮不可伪装成功,并显示“当前记录没有可播放的录音文件”。

B. 标题、信息与分享

IOS-WEB-010 记录标题展示与编辑

  • 标题左对齐,单行显示,过长截断。
  • 点击标题进入独立编辑浮层/弹窗,不使用常驻行内输入框挤压布局。
  • 自动聚焦;若标题以“现场记录”结尾,首次进入时只选中这四个字。
  • 保存前去除首尾空白;空标题禁止保存。
  • 保存后写回云端并更新 revision,不能只修改前端内存对象。
  • 取消时恢复原值。

IOS-WEB-011 记录信息 Info

Info 面板必须展示:

  • 开始日期与时间,中文格式,精确到秒。
  • 结束日期与时间;无结束时间时显示“进行中”。
  • 总时长。
  • 图片数量。
  • 笔记数量;普通 MARKER 计入,续录标记不计入。
  • 当前本地内容版本/网页正在编辑的版本。
  • “续录”入口。

视觉要求:

  • 内容为紧凑卡片,不在主页面正文中常驻展开。
  • 分隔线、10–13px 辅助字号、等宽数字、1px 低对比边框与 iOS 一致。

IOS-WEB-012 分享/导出实际录音

  • 分享文件名取记录标题,替换 /:\?%*|"<>、控制字符和换行,清除尾部句点与空白。
  • 空结果回退为“现场记录”。
  • 支持 Web Share API 时调用系统分享;不支持时直接下载。
  • 分享/下载的是实际音频文件。
  • 无音频时显示不可分享原因;不得仅显示“资源已准备”Toast。

C. 同步状态

IOS-WEB-020 紧凑同步按钮

状态至少包括:

  • 正在同步:圆环进度 + 旋转同步图标 + 百分比。
  • 已同步:金色云端完成图标。
  • 未同步:灰色上传云图标。
  • 失败:错误状态与重试入口。
  • 冲突:明确提示版本冲突,禁止静默覆盖。

行为:

  • 未同步点击后执行保存/上传。
  • 正在同步点击后允许暂停或取消当前上传。
  • 已同步点击后打开同步信息。
  • 其他记录正在同步时,本记录按钮禁用或排队。

IOS-WEB-021 同步信息面板

展示:

  • 最近同步时间,中文,精确到秒。
  • 云端版本 v{revision}
  • 云端记录 ID,可选择复制。

IOS-WEB-022 网络、登录和大文件边界

  • 无网络:提示连接网络后重试。
  • 登录失效:进入重新登录流程,不得丢失未提交草稿。
  • 预计上传量达到 70 MiB:同步前显示文件大小、网络类型和确认。
  • 版本冲突:使用 baseRevision 触发 409 保护,不做最后写入者静默覆盖。

D. 三轨时间线

IOS-WEB-030 总体结构

时间线包含:

  1. 时间刻度。
  2. 音频轨:真实波形、静音背景、续录标记。
  3. 图片轨:照片记录点。
  4. 笔记轨:笔记和普通标记。
  5. 一条贯穿三轨的播放头。
  6. 下方图例:音频、图片、笔记;存在续录标记时增加“续录”。

轨道左侧保留约 24–30px 图标区,分别使用波形、相机、文档图标。

IOS-WEB-031 横向尺寸与刻度

  • 时间线最小内容宽度 400px。
  • 基准比例为约 2.5px/秒,并在左右各保留约 30px。
  • 长记录允许横向滚动,隐藏原生滚动条但保留触控板、滚轮和触摸拖动。
  • 小于等于 60 秒:每 5 秒刻度。
  • 60–300 秒:每 10 秒刻度。
  • 大于 300 秒:每 60 秒刻度。
  • 刻度格式为 m:ss

IOS-WEB-032 真实波形

  • 从实际音频以 50ms 为时间窗提取 RMS。
  • 振幅按 iOS 相同的 -50dB → 0...1 线性规则归一化。
  • 按可见宽度聚合采样,每列取该区间最大振幅。
  • 视觉规则:1px 柱宽、2px 间距、围绕中心线绘制、最大约占音轨高度 75%。
  • 音轨高度约 90px;事件轨高度约 34px。
  • 音频实际时长是回放、波形和时间线的权威总时长,不能优先使用墙钟时长。

IOS-WEB-033 静音区间

  • 音频分析同一遍同时产出波形和静音区间。
  • 静音区间在音频轨用低对比背景矩形标示。
  • 分析中显示加载状态。
  • 分析完成显示“检测到 N 处静音”;没有静音时不伪造数量。

IOS-WEB-034 播放头

  • 播放头是 1px 高对比竖线,贯穿时间线。
  • 可直接拖动;有效拖动热区至少 28px。
  • 拖动位置限制在 0...实际音频时长
  • 拖动时实时同步音频位置和下方进度条。
  • 正常播放时播放头自动滚动到可视区域中央。
  • 用户拖动期间关闭自动居中和缓动,避免跳动。
  • 键盘可聚焦,方向键每次调整 1 秒,并暴露当前时间给辅助技术。

IOS-WEB-035 轨道事件点

  • 同一毫秒位置的多张照片合并成一个相机点,并显示数量徽标。
  • 笔记显示文档图标。
  • 普通 MARKER 显示警示/标记图标。
  • 文本以“续录时间:”开头的 MARKER 显示在音频轨,使用续录图标。
  • 点击事件点先跳到对应音频时间,再打开事件详情。
  • 事件按钮应覆盖空白轨点击层,不能误触发新增。

E. 回放控制

IOS-WEB-040 时间与进度

  • 左侧显示当前时间,右侧显示总时长。
  • 小于一小时为 MM:SS;达到一小时为 H:MM:SS
  • 提供独立进度 Slider,与时间线播放头和 <audio> 双向同步。
  • 进度范围以实际解码音频时长为准。

IOS-WEB-041 播放操作

  • 快退 10 秒,最低到 0。
  • 播放/暂停主按钮,48px 圆形高对比样式。
  • 快进 10 秒,最高到实际音频末尾。
  • 播放结束后停止播放并停在末尾,与 iOS 一致。
  • 播放或暂停状态必须由实际音频元素状态驱动。

IOS-WEB-042 跳过静音

  • 默认开启,与 iOS 一致。
  • 播放进入静音区间时,立即跳到该区间结束时间。
  • 开关必须实际影响播放逻辑。
  • 图标、文案、加载状态和静音区间数量与 iOS 一致。

F. 事件列表

IOS-WEB-050 列表排序与分组

  • 所有事件按 relativeTimeMs 升序。
  • 同一时间点的照片在列表中合并成一个记录项。
  • 不同时间点分别显示。
  • 无事件时显示托盘图标和“暂无会话事件”。

IOS-WEB-051 事件卡片

卡片展示:

  • 时间徽标。
  • 类型图标和中文类型:照片、笔记、标记、音频、续录。
  • 笔记/标记内容,最多两行。
  • 照片缩略图最多叠放三张,并显示总张数。
  • 第一条非空照片备注。
  • 地点名称。
  • 右侧进入箭头。

点击卡片:

  • 音频跳转到事件时间。
  • 打开相应事件详情。

G. 新增笔记

IOS-WEB-060 从笔记轨空白处新增

  • 点击笔记轨空白处,将横坐标换算为记录时间。
  • 立即跳转播放器到该时间。
  • 打开“添加笔记”弹窗,顶部显示“添加到 MM:SS”。
  • 多行文本输入,占位文案“这一刻的想法”。
  • 可添加位置。
  • 空白内容禁止保存。
  • 保存后创建 NOTE 事件、更新版本、刷新时间线和事件列表。
  • 失败时回滚页面乐观更新并保留草稿。

H. 新增照片

IOS-WEB-070 从图片轨空白处新增

  • 点击图片轨空白处确定事件时间并打开“添加照片记录”。
  • 支持调用摄像头拍照;浏览器不支持或未授权时显示明确状态。
  • 支持一次从本地选择多张图片。
  • 每张照片独立预览、移除和填写备注。
  • 多张照片共用一个位置。
  • 读取中显示进度;部分读取失败时保留成功项并提示失败数量。
  • 没有照片时显示“还没有照片”空状态。
  • 保存按钮显示“保存 N 张”,无照片或读取中时禁用。
  • 上传前采用与 iOS 等价的 JPEG 质量目标;不得无上限上传原始超大图片。

数据要求:

  • 同一批照片的 relativeTimeMs 相同。
  • 每张照片创建独立 PHOTO 事件和独立照片资产。
  • 照片资产 clientId 必须与事件稳定关联,遵循 {eventClientId}-photo
  • 事件或资源任一步失败时执行补偿,不能留下只有事件没有图片的半成品。

I. 查看、编辑与删除事件

IOS-WEB-080 照片点详情

  • 同一时间点的所有照片在一个详情弹窗中展示。
  • 顶部显示时间、照片总数和共同位置。
  • 每张照片展示序号、图片、独立备注和删除按钮。
  • 备注与位置有变化时才启用保存。
  • 保存成功后更新所有相关事件并刷新版本。
  • 图片资源缺失时显示“图片不可用”,不显示破图。

IOS-WEB-081 全屏图片

  • 点击图片进入全屏黑底预览。
  • 支持 1–5 倍缩放。
  • 放大后支持拖动,并限制在图片可移动边界内。
  • 展示照片创建时间和非空备注。
  • 提供明确关闭按钮,Esc 也可关闭。

IOS-WEB-082 笔记详情

  • 显示事件时间。
  • 笔记内容可编辑。
  • 位置可添加、修改或清除。
  • 内容为空禁止保存。
  • 没有变化时禁用保存。

IOS-WEB-083 标记与续录详情

  • 普通标记内容只读展示,并可删除。
  • 续录标记标题为“续录详情”,展示记录内容,并可删除。
  • 不得把续录标记计入笔记数量。

IOS-WEB-084 删除事件

  • 删除照片、笔记和标记前必须二次确认。
  • 删除照片时同时删除事件记录和对应云端照片资产。
  • 删除失败时恢复 UI,不得先永久移除本地状态。
  • 操作完成后更新照片/笔记计数、版本、时间线和列表。

J. 位置

IOS-WEB-090 位置入口

  • 无位置时显示“所在位置 / 点击后根据 GPS 选择当前位置”。
  • 有位置时显示地点名称和地址。
  • 图标在有位置时使用品牌金色。
  • 支持修改或清除位置。

IOS-WEB-091 位置选择器

  • 进入后请求浏览器定位权限;仅在用户主动打开位置选择器后请求。
  • 展示地图、当前位置、已选地点标记和回到当前位置按钮。
  • 默认加载当前位置 2km 内的附近地点。
  • 搜索范围以当前位置/已选位置为中心约 10km。
  • 提供“不显示位置”选项。
  • 地点条目显示名称、地址和选中状态。
  • 定位拒绝、定位失败、附近地点失败、无搜索结果分别显示对应错误和重试/设置指引。

网页适配:

  • 地图和地点检索服务必须明确选型;不得在 PRD 实施阶段临时用静态占位图替代。
  • 保存字段统一为 locationName / locationAddress / latitude / longitude

K. 续录

IOS-WEB-100 续录入口与环境检查

  • 从 Info 面板点击“续录”。
  • 先暂停当前回放。
  • 网页端使用浏览器麦克风继续录音;如支持多输入设备,允许选择输入源。
  • 麦克风不可用或权限拒绝时明确说明原因。
  • 续录开始时以原音频实际时长作为偏移,创建续录标记:
    • eventType=MARKER
    • relativeTimeMs=原音频时长
    • textContent=续录时间:yyyy年M月d日 HH:mm:ss
  • 结束后生成新的完整或可拼接音频资产,更新时长、波形、静音分析、版本和同步状态。

平台限制:

  • 网页端不得假装支持 iOS 原生 Spark BLE 录音。
  • 若产品要求 Spark 设备完全等价,需另立 Web Bluetooth/设备网关技术方案;不支持的平台显示“当前浏览器不支持 Spark 续录”,并允许改用浏览器麦克风。

6. UI 统一规范

  • 背景:跟随系统浅色/深色,分别接近 iOS systemBackground
  • 卡片:接近 secondarySystemBackground,主内容使用约 15%–50% 的轻透明层级。
  • 边框:主文本色 12% 透明度,1px。
  • 圆角:主卡片 10px,事件卡片/输入 8px。
  • 主色:系统高对比前景色;不把品牌金色用于普通播放进度。
  • 品牌金:#E6C687,只用于已同步、选中位置等强调状态。
  • 录音红:#E53935
  • 字体:系统 SF/等价系统字体;时间、版本和技术数字使用等宽字体。
  • 图标:网页使用 Lucide 中最接近的线性图标;线宽和尺寸保持 9–18px 的克制层级。
  • 禁止装饰性发光、脉冲和紫色 AI 视觉渗入左侧。
  • 左侧桌面宽度下完整显示;窄屏时独立纵向滚动,不允许控制区相互遮挡。

7. 可访问性与键盘

  • 所有纯图标按钮必须有可读名称和 Tooltip。
  • 播放、同步、跳过静音、保存和删除状态须通过 aria-live 或等价方式通知。
  • 所有弹窗具备焦点锁定、Esc 关闭和关闭后的焦点恢复。
  • 时间线事件、播放头、Slider、照片删除和位置选择均可键盘操作。
  • 触控目标建议至少 44×44px;播放头视觉可为 1px,但命中区至少 28px,网页建议 32px。
  • 不依赖颜色单独表达同步、选中、错误或播放状态。
  • 图片提供语义化替代文本;地图存在文本列表替代。
  • 200% 缩放下左侧不得出现不可操作的横向页面溢出;只有时间线内部允许横向滚动。

8. 当前网页实现差距

P0:阻止“完全一致”的问题

  • 使用 createSyntheticFieldAudio() 在无音频或下载失败时生成假音频。
  • 波形由 Math.sin() 生成,与真实录音无关。
  • 没有 iOS 三轨时间线、时间刻度、静音区间和可拖动播放头。
  • “跳过静音”只切换样式,不执行检测或跳转,且默认值与 iOS 不一致。
  • 标题编辑只修改 session.title 内存对象,不写后端。
  • RecordDetailModal 调用不存在的 sessionAPI.addEvent;并且 App.jsx 未传入 onEventAdded
  • 分享按钮只显示 Toast,没有分享或下载实际音频。
  • 缺少照片新增、批量照片、照片备注、照片查看、全屏缩放、位置、编辑和删除。
  • 缺少笔记编辑、删除和位置。
  • 缺少事件按同一照片时间点分组。
  • 缺少续录。

P1:状态和体验不一致

  • 详情未在打开时重新请求最新数据。
  • 同步状态固定显示“已同步”,无真实进度、失败、冲突或暂停。
  • 信息面板字段和结构与 iOS 不一致。
  • 左侧存在额外说明标题、胶囊标签、装饰金色进度和网页专属表单。
  • 事件类型直接显示英文。
  • 音频结束后网页回到 0,iOS 停在末尾。
  • 对大文件使用一次性 Blob 下载,可能造成明显内存压力。

9. 后端与接口缺口

现有后端已具备:

  • 获取列表与详情。
  • POST /sessions 全量幂等 Upsert,支持 baseRevisiondeletedEventClientIds
  • 新增单个事件。
  • 上传/下载 AUDIO 与 PHOTO 资产。
  • 记录软删除。

为了稳定完成网页/iOS 双端编辑,建议:

必须补齐

  1. 前端 API 封装:
    • getSessionDetail
    • syncSession
    • uploadAsset
    • addEvent(若仍保留单事件接口)
    • 统一错误码、409 和 413 处理。
  2. 删除照片资产接口:
    • 当前没有 DELETE /sessions/{sessionId}/assets/{assetId}
    • 仅删除 PHOTO 事件会留下云端孤儿文件,无法达到 iOS“图片文件和记录一起删除”的语义。
  3. 资产与事件关联返回:
    • 网页需用事件 clientId 对应 {clientId}-photo 资产。
    • 建议详情响应直接返回 event.assetevent.assetId,减少前端猜测。
  4. 音频访问优化:
    • 支持 Range/流式响应或安全的鉴权媒体 URL,避免超大录音全部进入内存 Blob。

建议修正

  • POST /sessions/{id}/events 新增事件后应同步更新 photoCount / noteCount / updatedAt / revision
  • 增加事件 PATCH/DELETE 接口,或明确网页统一使用全量 POST /sessions Upsert;不要两套写入语义混用。
  • 照片上传和事件创建提供事务化/补偿协议。
  • 续录后多个 AUDIO 资产的“当前权威音频”规则应在后端明确,不依赖数组顺序。
  • 提供可选的波形/静音分析缓存,减少每次打开大音频都重新解码。

10. 推荐实施顺序

阶段 1:真实回放底座

  • 详情重新获取。
  • 去除假音频。
  • 真实音频选择、错误态与分享下载。
  • 真实波形、静音检测、播放控制和可拖动播放头。
  • 三轨只读展示和事件列表分组。

完成标准:用户看到和听到的内容全部来自真实记录。

阶段 2:事件完整编辑

  • 标题云端保存。
  • 新增/编辑/删除笔记。
  • 新增/查看/编辑/删除照片。
  • 位置选择。
  • 全屏图片。
  • 版本冲突与失败回滚。

完成标准:网页编辑后 iOS 同步能得到完全相同的事件、照片、备注和位置。

阶段 3:同步、续录与大文件

  • 精确同步状态、进度、暂停、认证和大文件确认。
  • 浏览器麦克风续录和音频资产更新。
  • 流式/Range 音频、分析缓存和性能优化。

完成标准:长录音、大照片批次、续录与冲突场景可可靠完成。

11. 总体验收标准

只有同时满足以下条件,才能宣称“网页左侧与 iOS 回放页功能统一”:

  1. 无任何模拟音频、模拟波形、固定同步状态或无效开关。
  2. 同一记录在 iOS 与网页显示相同标题、时长、照片数、笔记数、事件时间、备注、地点和版本。
  3. 两端播放同一实际音频,波形时间位置可对应听到的内容。
  4. 网页拖动三轨播放头、进度条或点击事件,音频位置一致。
  5. 跳过静音在真实静音区间生效。
  6. 网页新增/编辑/删除笔记和照片后,iOS 同步结果一致;反向亦然。
  7. 照片删除不残留云端孤儿资产。
  8. 标题和所有事件编辑均有版本保护、失败提示和回滚。
  9. 信息、同步、分享、位置、全屏图片和续录均具备完整状态,而非视觉占位。
  10. 桌面、窄屏、键盘和辅助技术下核心流程均可完成。

12. 审计证据限制

本 PRD完成了当前 iOS 与网页源码、数据模型、同步逻辑和后端路由的逐项核对。当前已启动模拟器停在麦克风权限请求,未在未获授权的情况下改变权限,因此本轮没有取得回放详情页的完整运行截图。视觉尺寸以 SwiftUI 当前源码为基准,实施时仍需用包含真实音频、照片、笔记、位置和续录标记的测试记录,分别在 iOS 和网页逐状态截图对照验收。