Dramivio 开发技术路线
本页目录
1. 执行结论2. 组件及修改边界3. App播放器结构与状态4. 首帧、缓冲与画面连续性4.1 加载顺序4.2 媒体与网络策略4.3 缓冲参数:实验起点4.4 恢复与资源限制5. App长按临时倍速6. Web性能与适配6.1 独立而简洁6.2 优化起点7. 框架登录的具体接入7.1 配置起点7.2 仅保留必要的业务接线7.3 邀请的诚实边界8. 接口与状态必须先约定9. 分阶段测试与证据性能实验矩阵10. 对本次设计的增量2026-10-02 · 配合PRD v3.3与已确认设计稿 · 本次交付技术规格,未开发真实App/服务
1. 执行结论
App使用原生视频引擎:iOS AVPlayer、Android ExoPlayer,通过react-native-video接入,RN负责操作层。Web独立使用HTML video;支持原生HLS时优先原生,否则检测MSE能力再动态加载hls.js。Web不接App长按手势,不把RN播放器代码打进Web包。
登录采用Better Auth官方Email OTP和官方Expo客户端集成,不自写验证码、会话或令牌系统。保留邮箱单入口;框架负责身份,本项目只接邮件发送、协议记录、用户资料和业务权限。每日额度、邀请有效观看、奖励账本属于产品规则,不能假设登录框架会代做。
初期只保持一个API进程、PostgreSQL和一个Outbox worker;需要跨实例限流时增加Valkey。API用Hono接Better Auth;数据库先用官方支持的PostgreSQL连接方式,不为认证再搭一套ORM或SSO平台。Hono官方接入示例。
管理后台补充采用Payload开源核心,复用表单、资源管理、管理员认证、访问控制和草稿版本;Hono负责业务API。详细职责与B00接入关口见后端复用方案。
2. 组件及修改边界
| 模块 | 采用 | 本项目负责 | 验证后才能锁定 |
|---|---|---|---|
| App工程 | RN;新项目建议Expo development build/prebuild | 原生构建、导航、界面与业务适配 | RN/Expo/原生依赖兼容矩阵 |
| 原生播放 | react-native-video稳定6.x候选 | PlayerAdapter、控件、授权与生命周期 | Android/iOS真机播放、手势、离线asset |
| 长按手势 | react-native-gesture-handler与工程兼容版本 | 临时倍速意图、控件区域隔离 | 不混用2.x/3.x API;真机取消事件 |
| Web播放 | HTML video + 按需hls.js | 轻控制层、错误恢复、响应式布局 | Safari原生/Chrome MSE两条路径 |
| 邮箱登录 | Better Auth + 官方Email OTP | SMTP接线、协议、额外业务限流 | RN和Web登录/退出/重启/多实例 |
| RN会话 | @better-auth/expo + expo-secure-store | 业务请求复用官方cookie获取能力 | 不在AsyncStorage存会话秘密 |
| 邀请归因 | better-auth-referral社区插件候选 | 绑定确认、资格审核和额度结算 | OTP新建用户适配、幂等和源审查 |
| 离线 | Media3 / AVAssetDownloadURLSession | 小型平台桥接、队列、许可与账号隔离 | 与在线引擎共用可播放asset |
react-native-video官方说明确认iOS/Android原生引擎。当前发布页列v6.19.3为稳定候选,v7仍有预发布版本;本项目不默认追Beta。所有依赖记录精确版本和许可证,再运行兼容PoC,不使用latest作为锁版本策略。
新项目的Expo路线使用本地development build,不要求付费EAS,不用Expo Go验证自定义播放器/下载模块;参见Expo development build与播放器Expo说明。已有bare RN项目则保持原工程,只评估加入必要Expo模块,不能为登录整体迁移。
不fork认证/解码内核;不同时接两套登录;不默认引入全功能播放器UI包。现有设计的选集、追剧和额度控件放在薄业务层。手势库只负责识别,不自写长按计时器。
3. App播放器结构与状态
PlayerScreen
├─ PlaybackController(授权、状态、来源、请求代次)
├─ NativePlayerAdapter(react-native-video)
├─ GestureLayer(App专用)
├─ Controls / EpisodeSheet / RateSheet
├─ ProgressReporter(本地存储、10秒上报、revision)
└─ Telemetry(首帧、缓冲、错误、内存)
只有一个活跃原生播放器。列表只放封面;切集复用当前实例并改变稳定source,控件/进度更新不能重新挂载播放器。source对象、headers、缓冲配置保持稳定引用,只有源/授权确实变化才替换;不能把currentTime当组件key。
基础状态:idle→authorizing→loading→ready→playing;旁支paused/buffering/seeking/ended/error/disposed。播放意图、前台状态和媒体状态分开:ready不等于用户同意播放;buffer结束也不代表应自动恢复被用户暂停的内容。
每次切集生成loadGeneration;异步授权、首帧、seek、错误及重试均带代次。旧请求abort;无法abort的原生事件也检查所属源/代次,迟到事件不能覆盖新剧名、位置或倍速。原生库没有天然代次时由adapter按当前绑定源及加载状态隔离,无法区分的旧事件忽略,不按回调到达先后猜测归属。
页面失焦/后台、来电、耳机拔出或音频焦点丢失:保存可得进度、取消长按、暂停。回来不擅自开声,按之前的用户意图和前台状态恢复。媒体末尾先记完成,再检查下一集授权;最后一集停下,不跨剧。
4. 首帧、缓冲与画面连续性
4.1 加载顺序
- 点击后立即用已有封面铺满播放器,显示当前集信息;不等授权才绘制页面。
- 可并行读取目录元数据和服务器进度,但必须取得当前集play grant后才请求可播放媒体。不得为提速跳过账本提交。
- 授权成功后创建稳定source并加载。若需续播,等待load可接受seek后跳到合法位置;在续播位置可显示画面前保留封面,不先闪出0秒画面。
onLoad用于元数据就绪;onReadyForDisplay用于UI隐藏封面的起点。首帧时钟单独记录,不把封面出现或onLoad当视频首帧。事件定义。- 首帧确认后撤下封面,保留画面上方控件。加载超过300ms再显示小加载提示,避免短暂请求也闪圈;缓冲超过500ms才显示提示。
这些延迟是本项目交互起点,不是库默认值。若选定Android实现的ready回调只代表可播状态,TTFF报告须标“ready代理指标”;严格首个渲染帧用原生首帧观察或录屏核对。恢复seek也需要在目标位置确认显示,不能把seek命令发出当作目标画面完成。
4.2 媒体与网络策略
- 兼容主路径HLS + H.264/AAC;可控授权源提供360/540/720级别,具体码率经样本确认。首版不强制HEVC、1080p或低延迟直播配置。
- 可控点播源可试2–4秒分片、分片边界关键帧对齐;支持的源再验证CMAF。播放器不能修复上游编码/时间戳问题,记录异常源并隔离。
- 默认自动码率,不在未知移动网络直接锁720p;按实际可用档位显示画质。自动档使用吞吐与缓冲反馈,避免业务层每秒强行切档。
- 媒体域名保持少量且固定,减少重复DNS/TLS;CDN正确设置Content-Type/CORS/范围请求及签名过期。私有媒体缓存遵守授权边界,缓存键不能错误复用不同用户凭证;日志不记录完整签名URL。
- 短期凭证续期和失败重取都沿用既有解锁身份;401/403先分类是会话、到期还是内容撤权,不无限换线。恢复后仍从可验证进度继续。
- 首版预取只包含下一集封面/元数据。不要提前下载下一集manifest或片段、创建第二个解码器、提前扣下一集次数。以后要媒体预取,必须单独设计授权及账本规则再启用。
4.3 缓冲参数:实验起点
| 端 | 起点 | 调整依据 |
|---|---|---|
| Android | source.bufferConfig:minBufferMs=8000、maxBufferMs=20000、bufferForPlaybackMs=1000、bufferForPlaybackAfterRebufferMs=2000、backBufferDurationMs=0 | 首帧、重缓冲、内存;1×/2×及弱网分别对照 |
| iOS | automaticallyWaitsToMinimizeStalling=true;preferredForwardBufferDuration先比较默认0与10秒 | AVPlayer会自主决策,不能把preferred当严格上限;不为快首帧全局关闭等待 |
| Android渲染 | 全屏静态播放优先测surfaceView | 叠层/变换确实不兼容再测textureView;DRM另查安全surface,不能通用替换 |
| 通用 | progressUpdateInterval起点250ms,状态小组件局部更新 | 这只是UI采样,不是每250ms全页render或网络上报 |
参数接口见官方props。上表为本项目建议,需要依赖版本与真机验证;不是通用最优值。Android优先验证封面覆盖+shutter设置解决闪黑;不同内容切换不能裸露上一集最后一帧。不要只把黑底设透明就宣布首帧变快。
4.4 恢复与资源限制
普通网络失败最多2次自动重试,退避起点0.5秒/1.5秒并加少量抖动;成功播放后重置计数。长期buffer超过8秒提示重试/换线。解码、格式、内容撤权不走无限网络重试。
同剪辑等价源可换线,保留解锁和映射进度;不等价则明确版本变化,从合法位置重新选择。离开播放页要停止媒体请求并释放原生资源、事件、手势和定时器;不能仅让组件不可见。
5. App长按临时倍速
只在正片画面空白操作区域识别:不覆盖右侧按钮、底部进度条、选集/倍速弹层、系统手势边缘。初始长按阈值350ms、移动容差约16pt,均需真机验证。暂停、加载、广告、后台和无权内容不激活;单指,第二指加入则取消。
| 情况 | 行为 |
|---|---|
| 普通点击/短按 | 原有显示控件/播放暂停逻辑,不触发倍速 |
| 成功长按 | 临时2×;原生确认后显示“2× 倍速播放”/“Playing at 2×” |
| 原本1.5× | 长按2×,松手恢复1.5× |
| 原本2× | 保持2×,不发重复设置、不进一步提到4× |
| 松手/触摸取消/滑走 | 幂等取消临时状态,恢复当前手动选择的baseRate |
| 切集/切线/打开弹层/后台/来电 | 同样取消;新内容不能继承临时长按 |
| 2×失败或不支持 | 回到baseRate;简短提示,不伪显示已经加速 |
| 原本暂停 | 不因长按自动启动或产生声音 |
控制层只维护baseRate与holdActive:effectiveRate=holdActive且正片可播放 ? 2 : baseRate。原生倍速通过单一adapter设置;手势不直接另写一套rate。用户打开倍速选择时先取消hold,再改baseRate;迟到的release不得把新选择恢复成旧值。临时倍速不持久化,不进入下一集偏好。
使用工程已验证的Gesture Handler;回调API按锁定版本使用,不把2.x示例抄到3.x。取消、失败和生命周期离开都需要同一restore操作,不能只处理成功onEnd。无需为这个提示增加复杂动画。无障碍用户仍可从倍速按钮操作。
长按不增加每日消耗;仍按同集解锁一次。邀请只累计可信前台墙钟与未重复内容区间,2×不把观看时间乘2;短集资格沿用PRD的最大资格倍速公式。
6. Web性能与适配
6.1 独立而简洁
同一React/TypeScript Web工程即可,播放业务契约与App共享,播放渲染和手势分开。首页不导入播放器和hls.js,用户进入播放路由且需要MSE时再加载;hls.js及RN原生依赖不进入首页主包。
使用<video playsinline preload="metadata">与自有轻控件。浏览器原生HLS能力检测→原生src;否则Hls.isSupported()→hls.js;都不支持时给出可播放格式的合法后备或下载App,不能假称已支持HLS。hls.js官方能力说明。preload只是浏览器提示;浏览器或hls加载可能产生实际请求,因此直到有grant才附src/loadSource,不能靠metadata设置防止额度绕过。
播放调用检查video.play()的Promise。用户点击后的异步授权可能使浏览器不再认可最初点击:被拒绝时保留画面和播放按钮,请用户再点,不重复扣次,也不循环调用play。浏览器play规则。
6.2 优化起点
- Web只保留倍速菜单、选集、拖动、全屏、追剧和下载引导;不注册长按倍速、竖滑切集或App触摸层。保留浏览器正常滚动/选择/辅助操作,不全局preventDefault。
- hls.js候选起点:maxBufferLength=15、maxMaxBufferLength=30、backBufferLength=0、capLevelToPlayerSize=true;按设备与网络测内存和卡顿,不开启点播无需求的低延迟直播参数。尺寸/比特率策略允许画质被网络降档,不假定15秒等于15秒墙钟,2×要专门测试。配置接口。
- video画面与控制层尺寸稳定,移动浏览器地址栏伸缩时不重建播放器;宽屏桌面侧栏选集,窄屏底部弹层。横屏和iOS全屏需实际测,不强制套统一CSS假全屏。
- 离开路由调用hls.destroy()并移除事件、定时器;video暂停、移除src并按需load释放。页面隐藏暂停,回来不擅自开声。
- 浏览器首帧可用requestVideoFrameCallback作观察;不支持则退到playing/loadeddata并标代理指标。后台时回调不可靠,不能据此算墙钟观看。帧回调定义。
- 封面有width/height/aspect-ratio防布局跳动,首屏两张优先,其余lazy;列表每次10部cursor追加。先测普通双列列表,累计超过约100部有明显DOM/内存压力再按需虚拟化,不先装重型列表框架。
- 支持320/390/768/1280宽度;触控区不因英文长词缩到不可点。Web缓存按钮永远下载引导,不能复用App下载API。
7. 框架登录的具体接入
7.1 配置起点
Better Auth官网当前文档显示1.7.7;锁定时核对发布及Expo插件兼容版本,下面为待编译验证的配置片段,不是已运行代码。
// 保留框架的OTP/会话实现,只配置业务要求。
emailOTP({
otpLength: 6,
expiresIn: 600,
allowedAttempts: 5,
storeOTP: 'hashed',
resendStrategy: 'rotate',
disableSignUp: false,
changeEmail: { enabled: true, verifyCurrentEmail: true },
sendVerificationOTP: sendThroughSmtp,
});
// auth层建议 session.freshAge = 300;数据库采用官方PostgreSQL接入。
插件本身提供自动新用户注册、验证码验证与上述配置;实现前逐项对照官方Email OTP。不要保留默认明文OTP存储;不新增自己的验证码表或随机算法。SMTP适配失败要明确可重试,不吞异常假称发送成功。邮箱投递和验证码端点分别限流,验证码不进日志/分析事件。
Web使用同源/api/auth和框架Cookie会话,生产HTTPS与明确trustedOrigins;不配置通配CORS+credentials、不关闭CSRF。App用官方Expo客户端与SecureStore,业务请求按官方getCookie方式携带已管理的会话。Expo官方集成。App本地缓存的session只用于界面恢复,服务器每次关键动作仍验证会话与emailVerified。
7.2 仅保留必要的业务接线
- 协议同意的版本/时间/账号关联,账号创建后幂等记录;未同意时不能获得新账号业务权限。
- 邮箱/设备/IP共享限流预算,重发不能重置累计失败;部署多API实例也共用存储。框架默认限流不代表已满足本产品跨维度规则。框架限流文档。
- 业务Profile与auth user唯一对应,邮箱不是用户主键。重试profile初始化不重送额度;邀请码失败不破坏正常身份登录。
- 登录后的pendingAction只保存白名单业务意图(追剧/查询/续播/缓存确认),不保存任意跳转URL;执行前再校验权限且幂等,取消返回原页面。
- 会话撤销复用框架;同IP只提高新奖励审核,不整体踢出老用户。用户明细遵守最小暴露。
- 换邮箱优先官方changeEmail及verifyCurrentEmail。注销用框架新鲜会话/删除钩子,加最小业务终态和Outbox清理。
注销复验不发明官方未支持的OTP type。界面仍可要求邮箱OTP重新登录,服务端验证是当前账号、本次敏感意图之后的新会话,并用一次性action token绑定账号/动作/短期nonce。此token不是新认证系统;只约束“验证后允许注销”这一动作,登录证明不能直接拿去执行换邮箱或别的敏感操作。敏感意图nonce和创建时间由服务端生成;签发证明核对同一账号在该意图之后取得的OTP新会话,不接受客户端fresh标志。证明消费与terminal提交须同事务,或以稳定operation_id绑定幂等结果;清理失败查询并继续原操作,不重新消费或重建账户。beforeDelete钩子必须检查该证明,直接调框架删除路由也不得绕过;幂等终态→撤授权/奖励/许可→Outbox清理→框架身份清理,失败可恢复且不重建账户。官方新鲜会话、删除用户与钩子。
7.3 邀请的诚实边界
better-auth-referral是社区候选,不把它称作已验证的成熟奖励系统。只先验证验证码自动注册时的归因、一次绑定、手输邀请码。通过才复用关系层;发奖仍由业务账本驱动。失败时提交PoC证据与选择,不擅自重写一整套营销平台,也不能用演示成功冒充插件已兼容。
8. 接口与状态必须先约定
| 契约 | 最少约定 |
|---|---|
| PlayerAdapter | load、seek、setBaseRate、setTemporaryRate/restore、pause、dispose;ready/buffer/error/progress事件带当前代次 |
| play grant | canonical episode、edition、unlock、session、source version、签名过期、可用线路、端能力、policy version;不含上游秘密 |
| 进度提交 | session、sequence、position、history_epoch、revision;服务端取用户,不信任传入user_id |
| 有效观看 | 前台单会话、去重内容区间、真实墙钟;广告和buffer暂停不累计 |
| 额度读取 | 当前端基础上限/已用、App奖励、下一重置时间、会员状态;跨端不加成60 |
| 下载确认 | 实际选中集合、版权许可预览、设备、到期、已解锁/新增费用、空间估计 |
错误必须区分NETWORK、AUTH_EXPIRED、QUOTA_EXHAUSTED、CONTENT_UNAVAILABLE、DECODE_UNSUPPORTED、LICENSE_EXPIRED、CONCURRENT_SESSION。播放器错误不是统一“请登录”。适配器事件不可直接发奖励;同集重试/换线不重扣。正式接口统一PRD数据字典,不在各端再创一套身份或权益。
9. 分阶段测试与证据
功能按dramivio-model-task-cards.md逐卡完成。认证用真实框架+测试SMTP捕获工具,授权/账本用真实PostgreSQL集成测试;Mock只能用于失败注入或纯控件,不能作为框架兼容/并发/离线的通过证据。
- 纯状态单测:倍速恢复、事件代次、缓存选择/费用、查询草稿恢复、进度冲突;采用现有测试工具,避免为了测试再换整套工程。
- 集成:Auth+DB+邮箱捕获、grant事务/幂等、进度CAS、邀请结算、删除/迟到事件。并发测试保留数据库事实与唯一约束结果。
- 浏览器:Chromium自动化主流程;真实Safari/macOS与iPhone Safari单独测原生HLS、自动播放、全屏;不能拿Playwright WebKit冒充真Safari验收。
- App:至少一台目标最低系统iPhone、一台低内存Android、一台中档Android;Release/profile构建,不拿模拟器Debug帧率作为上线性能。
- 离线:真机下载→断网→系统强退→重启→播放;权限到期、退出/换账号、低空间、Wi-Fi切换、版本恢复。不能用在线播放缓存代替。
性能实验矩阵
受控片:30/60/180秒,360/540/720档,相同编码与源。正常网候选8Mbps/RTT60ms/0丢包;弱网候选1.5Mbps/RTT150ms/1%丢包;另测10秒断网、Wi-Fi↔蜂窝、后台/前台。每端配置对照至少50次启动,标冷/热缓存、1×/2×、是否恢复seek;样本用于工程对照,不声称生产总体分位数已可靠。
| 指标 | 初始目标/断言 |
|---|---|
| 封面占位 | 点击后约100ms内有稳定可见页面,无空白等待授权 |
| TTFF | 正常网受控样本目标P50≤1.5s、P95≤3s,包含grant;另列纯媒体加载耗时 |
| 卡顿比 | 正常网候选≤1%;定义为有效播放期间缓冲墙钟/播放意图墙钟,排除手动暂停/seek/首次加载 |
| 连续切集 | 50次不崩溃/重复声音,最后10次内存无持续阶梯增长;回到空闲后对照基线,阈值按设备预算固定 |
| 长按 | 100次成功/取消混合测试均恢复正确baseRate;无一次把下一集/广告留在临时2× |
| 授权正确 | 没有首帧提前于grant/账本提交;无次数时不能取新媒体;失败/重试不重复扣 |
| Web首页 | 网络记录不下载hls.js/视频分片;核心包独立记录压缩大小、执行耗时和长任务 |
| Web兼容 | 320–1280不水平溢出;触屏正常滚动;拒绝play后可手动恢复且不重扣 |
达不到目标先定位grant、CDN、manifest、片段、解码或渲染哪一步慢,再改一个变量对照;不能只增大buffer或默认预载多播放器。弱网优先正确降档/恢复,不卡死、不误扣,不承诺固定首帧时间。
每张卡交付:锁版本、变更列表、测试命令、实际结果、真机/浏览器/网络、脱敏日志/录屏、已知失败、回滚步骤。无设备或无真实媒体时写“未验证”,不能写通过;兼容失败只阻塞依赖它的卡,继续可独立完成的模块。
10. 对本次设计的增量
App新增临时2×状态与松手恢复;Web保留原倍速菜单,没有长按功能。RN状态层与Web包分开;UI只显示简短2×反馈和必要错误,不把缓冲参数或认证实现说明放进产品页面。
本文件由开发模型先阅读,再接单张任务卡。当前交付是路线,不代表上表性能、框架或安全测试已执行。
进度上传的异步任务固定绑定user_id、session_id、history_epoch;退出/换号后不得用当前账户替换旧任务身份。App端分类沿用PRD的端证明等级;只开源且没有可信平台证明时,安装密钥/请求头可被复制,必须记录仿冒App残余风险并施加共享设备预算、账号限流及成本保护,不能宣称绝对阻止Web伪装。
端证明验收必须列出iOS、带Google服务Android、不带Google服务Android的能力矩阵。签名挑战和安装钥匙仅提供连续性;无Google服务的低风险合法App仍按PRD提供50集,不能一律降到Web10集。可完整模拟握手的攻击者仍可能仿冒,按成本/带宽限流、共享预算与审核控制;媒体访问始终需要合法grant。
性能采样:基线与弱网各选择代表组合,每组至少50次启动;其余片长/清晰度/冷热/倍速/seek组合记录功能样本数量,不把全部组合混成一个P95。D04固定每设备内存基线、工具及阈值,D19不得改统计定义掩盖回归。错误重试预算只有稳定播放一段时间后才重置,不能因播放几帧就无限循环重试。