本文主要介绍媒体库 Web 端在「电视显示模式」上的两层决策:一是信息架构必须换壳——独立 TV Shell(唯一首页 + 顶栏 + overlay),而不是给桌面侧栏多 tab 放大字号、补方向键;二是焦点必须有 Section 模型——区内策略 + 进区 / 出区声明,而不是扁平全局几何再叠业务特例。
以 Octans Web / Windows Host(WebView2 加载同一套 Web)为例。产品需要 10-foot 电视体验,语义对齐 Android TV 壳;浏览器与 Host 共用业务实现,Host 不另做业务 UI。下面把「壳长什么样」和「方向键怎么走」压成可复用的合同表述。
问题从哪来
Windows Host 主线是 WebView2 + 主 Web UI。电视模式一旦立项,最容易走偏的两条捷径是:
- 保留桌面
MainLayout左侧主导航,只放大字号 / 间距。 - 给现网 DOM 批量加 tabindex / 方向键,当作「空间焦点已完成」。
这两者都不是 TV Shell。桌面侧栏、多 tab、电影库 / 剧集库 / 调度台一级入口,与遥控器心智冲突;焦点若服务「旧布局的补丁」,布局就永远不会为 10-foot 重排。
Android 侧已验证的产品边界(语义参考,非像素复刻)是:
- 唯一 root 主屏是首页,无底栏 / 无左栏多 tab;
- 顶栏:品牌 + 搜索 + 头像;
- 电影详情、控制台为 fullscreen overlay,关闭后首页保活;
- 播放为更高层全屏宿主;
- 空间焦点建在 TV 布局树上。
Web 侧应对齐的是这套 IA 与交互语义,不是 Compose 像素。跨端总路线上,Web 做基线与控制面、各端原生 UI 与 native 播放,见 跨端媒体客户端:Web 做基线,原生各自 UI 与 native 播放;Android 单 App 双 Shell 见 Android 媒体客户端:单 App 双 Shell 与 Compose for TV 选型。
决策一:换壳,不是 densify
一句话
TV 模式切换的是「壳 + 信息架构 + 布局」:进入独立 Web TV Shell(唯一首页 + 顶栏 + overlay 详情 / 设置);不是给现网桌面 UI 加焦点。
模式与入口
| 项 | 合同 |
|---|---|
| 内部值 | desktop | tv(与现有布局交互模式偏好对齐) |
| 产品文案 | 「电视显示模式」/「桌面模式」 |
| 默认 | desktop |
| 切换入口 | 桌面:控制台本机设置;TV:控制台 overlay 内本机设置(可改回 desktop) |
| 切换生效 | 即时;无需重装 Host |
| 浏览器与 Host | 同一套 Web 实现;Host 不另做业务 UI |
双壳模型
| 模式 | 壳 | 主导航 |
|---|---|---|
desktop | 现网侧栏 + 多 tab 布局 | 保持现网 |
tv | 新 TV Shell(无侧栏) | 禁止桌面侧栏与 root 多 tab |
硬规则:
- TV 模式下 不得 渲染桌面侧栏、底部 tab、电影库 / 剧集库 / 调度台等桌面一级入口。
- TV 与 Desktop 共享 API client、认证、媒体数据、播放 launch 合同;不共享 页面布局树。
- 不为 TV 把前端栈迁到 WinUI 3;宿主仍是 WebView2。Windows 当前发布主线与 WinUI 候选关系,见 Windows 媒体客户端 UI 路线:WebView2 复用、WinUI 3 候选与当前主线。
TV Shell 图层
TvShell
├─ Home(唯一 root 主屏,保活)
│ ├─ TopChrome:品牌 | 搜索 | 头像
│ └─ 纵向 sections:媒体库入口卡 + 横向海报轨 + 行尾「更多」
├─ 二级(可选,后续):搜索 / 墙「更多」等 push 层(首页不销毁)
├─ 控制台 overlay(头像打开,fullscreen)
├─ 电影详情 overlay(电影海报打开,fullscreen)
└─ 播放层(现网 Playback 路径;盖住浏览壳;退出回详情或首页)
Back / Esc 消费顺序(语义对齐 Android):
- 当前 overlay 内的 dialog / 子层
- 电影详情 overlay
- 控制台 overlay
- 二级页 pop
- 已在首页 root:不退出登录;浏览器可停在首页,Host 不强制退进程
唯一首页与顶栏
| 项 | 合同 |
|---|---|
| Root | 仅 TV 首页;进入 TV 模式默认落在首页 |
| 数据 | 与现网首页同源 section catalog / 墙查询合同 |
| 布局 | 顶栏 + 纵向轨列表;页级无左右侧栏;横轨 10-foot 密度(较大左起边距、横向滚动、焦点环) |
| 首焦 | 进入首页必有 default focus(优先内容区首个可导航项或顶栏约定项,实施时定一处并写死) |
| 返回 | 从 overlay 关闭后恢复首页滚位与海报焦点(允许 v1 简化,但不得整页重载丢光上下文) |
顶栏控件:
| 控件 | 行为 |
|---|---|
| 品牌 | 识别用;不强制跳转 |
| 搜索 | v1 可占位;不得用桌面全站搜索路由顶替为「完成」 |
| 头像 | 打开 控制台 overlay |
详情与控制台:overlay 语义
电影详情 overlay
| 项 | 合同 |
|---|---|
| 入口 | 首页(及后续墙)电影海报确认键 |
| 形态 | fullscreen overlay,盖在首页上;不是桌面详情嵌在侧栏布局里 |
| 布局 | TV 专用详情(沉浸 hero:大标题、播放 CTA、选轨;不搬桌面详情像素) |
| 主路径 | 默认焦点在主播放;确认 → 现网播放 launch(不改 libmpv / session 后端) |
| 关闭 | Esc / Back → 关 overlay,回首页焦点 |
| 非电影 | 剧集 / 合集 v1 必须进占位层(「即将推出」类文案即可),不得静默忽略;完整路径只保证电影 |
控制台 / 设置 overlay
| 项 | 合同 |
|---|---|
| 入口 | 顶栏头像 |
| 形态 | fullscreen overlay |
| v1 最小集 | 本机设置(含切回桌面模式)+ 退出登录 + 关闭 / 返回 |
| v1 不做 | 播放设置、播放诊断、关于;以及桌面完整后台子页 |
| 明确不做(v1) | 媒体库管理、调度台、Root 用户管理、刮削批处理等桌面 Console 工作台 |
「设置」在 TV 合同中指 控制台 overlay 内的本机设置,不是桌面控制台路由树。
媒体类型分流(首页确认键)
| 类型 | v1 行为 |
|---|---|
| 电影 | 打开电影详情 overlay → 可播放(主路径) |
| 剧集 | 占位(fullscreen 或壳内二级均可,须可 Back 关闭) |
| 合集 | 占位(同上) |
| 其他 / 未知 | 忽略或占位;不得当电影打开 |
播放边界
- 从 TV 详情触发 现网 播放入口与 session 合同。
- 不因 TV 模式改 libmpv、选轨后端、转码策略。
- 播放 UI 可继续现网 Playback 路径;TV OSD 完整化不在本决策最小闭环内。
- 浏览壳焦点让位给播放快捷键协调;退出播放后焦点回到详情或首页约定项。
否决的备选
| 方案 | 结论 |
|---|---|
| 桌面壳 densify + 空间焦点 | 否决。不是 TV IA;与 Android 及用户预期冲突 |
| 独立 Web TV Shell(本决策) | 采用。与 Host 边界、Android 语义一致 |
| WinUI 3 整站 TV UI | 否决(主线)。成本高,壳不必为 TV 换 Host 栈 |
| 仅全屏放大桌面页 | 否决。无 10-foot IA,遥控不可用 |
v1 最小可演示闭环
- 切到 TV 模式 → 进入无侧栏 TV 首页。
- 方向键浏览横向轨 → 确认打开电影详情 overlay。
- 详情确认播放 → 现网可播。
- 退出播放 → 回详情或首页。
- 头像打开控制台 overlay → 本机设置可改回桌面模式 → 恢复桌面壳;可退出登录。
- 首页点剧集 / 合集 → 进入占位(可返回),不要求完整详情。
- 浏览器与 Windows Host 行为一致(键盘)。
必须遵守的 PR 约束
- 任何 TV 模式变更:无侧栏;根主屏只能是 TV 首页。
- 详情 / 设置必须是 overlay 语义(首页保活),禁止「push 桌面路由导致首页整树销毁」当作完成。
- 禁止把「给桌面布局加 TV 焦点指令」写成 TV 模式完成。
- Desktop 模式默认路径零回归。
决策二:焦点 = 自研内核 + Section 一等公民
壳定了之后,方向键仍会在「扁平候选池 + 几何打分」上踩坑。常见补丁包括:海报横轨同 index 对齐、短轨 clamp、轨外向下吸附首卡(避免吸到「更多」)、向上禁止跳过中间区块、dialog 作用域、滚动祖先截断、首卡 scrollLeft = 0 等。
这些补丁各自合理,根因相同:缺一层与 Leanback / 工业 TV 焦点一致的「容器 / Section」模型。行长短不齐、进区策略、多行 pill、弹层独占都是 group 语义,不是再调权重能稳定收敛的。
一句话
Web TV 焦点采用「自研内核 + Section 一等公民」:区内策略(geometry / track-index)+ 进区 / 出区声明(enterTo / restrict);不直接依赖 npm 空间导航库,但吸收其 Section 模型与成熟几何思想。
架构分层
遥控器 / 方向键
→ spatialNavigation(仅 TV Shell / 当前 focus scope)
→ ① 当前 Section 内策略
→ ② 无解且允许出区 → 跨 Section 几何选邻 Section
→ ③ 进入目标 Section 时应用 enterTo
→ DOM focus + is-tv-focused + bring-into-view
| 层 | 职责 | 不负责 |
|---|---|---|
| Geometry 内核 | 给定 from + direction + 候选矩形 → 下一个节点 | 业务 if、页面结构 |
| Focus Section | id、根节点、strategy、enterTo、restrict、lastFocused | 滚动实现细节 |
| 页面装配 | 在 DOM 上声明 section | 在内核写「若是关键词…」 |
| BIV / restore | 横轨 padding、首卡滚回、首页硬恢复 | 选邻公式 |
Section 声明(DOM 合同)
| 属性 | 含义 | 默认 |
|---|---|---|
| section 根标记 | 稳定 section id | 无则视为隐式全局几何区 |
| strategy | geometry | track-index | geometry |
| enterTo | first | last-focused | default-element | track-index 倾向 first |
| restrict | self-first | self-only | none | self-first |
硬规则:
- 可焦节点仍用统一焦点标记(如
v-tv-focus/data-tv-focus)。 - 有顶层 overlay dialog 时,焦点 scope 仍为最顶层 dialog;dialog 内可再嵌套 section。
- 禁止在「找最近邻居」内核里新增业务特例(如「若当前是作品信息关键词」);应改为 section 配置或后续
leaveFor。
策略语义
| strategy | 区内左右 | 区内上下 | 典型装配 |
|---|---|---|---|
geometry | 同行投影 + 主轴距离 | 排除 y 重叠同行后,最近一档 + 行内 x | 作品信息 pill、创作人员链接、hero 选轨 / 播放、物理文件 |
track-index | 同行投影(横轨内) | 相邻轨 + 同 DOM index(短轨 clamp) | 海报横轨;首页 / 详情演员·合集轨 |
enterTo(进入该 Section 时)
| 值 | 行为 |
|---|---|
first | 区内第一个可焦节点(DOM 序) |
last-focused | 记忆节点仍可焦则用之,否则 first |
default-element | 区内默认焦点标记,否则 first |
restrict(跨 Section)
| 值 | 行为 |
|---|---|
self-first | 先区内;无解再在 scope 内区外候选中几何选邻,命中后对目标 section 应用 enterTo |
self-only | 只在区内;无解则停住(行首 / 行尾 / 弹层) |
none | 不优先区内,全 scope 几何(少用) |
轨间上下(两轨均为 track-index):优先同 index,而不是屏幕 |dx|(避免各轨独立横向滚动落到行尾)。
与滚动、样式的边界
下列 不属于 Section 选邻,继续由既有逻辑负责,且不得塞进 strategy 字符串:
- 焦点环 / focused 样式类;
- 横轨 bleed padding、首卡滚回、dialog 内滚动截断;
- 首页焦点恢复、详情 hero 回顶;
- 播放路由关闭空间导航。
依赖策略
| 选项 | 结论 |
|---|---|
| 直接依赖旧 npm 空间导航库 | 不采用(上游停更久;轨 index / BIV / restore / Vue 生命周期仍要自研;替换回归面大) |
| vendor 整库 | 非默认;仅当自研几何不足且需对齐经典 9 宫格实现时再评估 |
| 吸收 Section / enterTo / leaveFor / restrict 与 partition 思想 | 采用 |
| 换栈上 React CTV 焦点方案 | 否决为底座(不为焦点换前端栈) |
行业参考库(如 js-spatial-navigation)的价值是 Section 合同与几何思想,不是必须把包名写进 package.json。Web 前端基座本身是自有 ui + headless 原语路线,见 媒体内容产品 + 管理后台混合前端的 UI 基座选型。
首批装配范围(落地最低集)
| 区域 | strategy | enterTo | restrict |
|---|---|---|---|
| 首页 / 详情海报横轨 | track-index | first | self-first |
| 详情 hero 选轨 + 播放行 | geometry | default-element | self-first |
| 详情创作人员链接区 | geometry | first | self-first |
| 详情作品信息 | geometry | first | self-first |
| 详情物理文件触发器 | geometry | first | self-first |
| overlay dialog | 现有 dialog scope(外层等价 self-only) | 打开时 default focus | — |
轨标题按钮宜放在 rail section 外,向下进入轨 section 时走 enterTo = first。
焦点侧否决的备选
| 方案 | 结论 |
|---|---|
| 继续扁平几何 + 场景补丁 | 否决。特例单调增;详情 / 控制台 / 搜索会重复踩坑 |
| 直接依赖 npm 空间导航库作底座 | 否决为默认;模型可借鉴 |
| 自研 Section 层 + 现有 / 演进几何 | 采用。与现壳契合;补丁变声明;可控可测 |
| 为焦点换 React / 其他 CTV 栈 | 否决 |
两层决策如何叠在一起
产品:desktop | tv 交互模式
│
├─ desktop → 现网侧栏布局(方向键不劫持)
└─ tv → 独立 TV Shell
│
├─ IA:唯一首页 + TopChrome + overlay 详情 / 控制台 + 播放层
└─ 焦点:spatialNavigation 仅 TV 树
│
├─ Section(strategy / enterTo / restrict)
├─ Geometry 内核(无业务 if)
└─ BIV / restore / 焦点环(布局配套,非选邻公式)
选择理由可以压成四条:
- Android TV 已验证「唯一首页 + 顶栏 + overlay」在 10-foot 下可走通;Web 对齐语义即可。
- WebView2 路径下业务 UI 本就在 Web;换壳比换 Host 栈正确。
- TV 焦点是「容器策略 + 区内几何」,不是「全局最近点」;扁平补丁不可扩展。
- 项目已有 TV 焦点标记、shell scope、轨数据属性、dialog、restore;演进比推倒换库划算,也不引入长期停更依赖当第二半残子系统。
对同类产品的启示
- 电视模式先定 IA,再谈焦点。 侧栏多 tab 放大版永远补不出 10-foot 壳。
- 共享数据与播放合同,不共享布局树。 Desktop 与 TV 是双壳,不是同一棵 DOM 换 class。
- overlay 保活首页 是返回栈与焦点恢复的前提;push 整页销毁首页不算完成。
- v1 只保证电影主路径 + 本机设置回桌面;剧集 / 合集 / 搜索墙用占位诚实表达,比虚报完成更安全。
- 焦点补丁若越写越像业务 if,就该升成 Section 声明。 内核禁止页面关键词特例。
- 吸收工业模型,不等于绑定停更 npm 包。 Section / enterTo / restrict 可以自研落地。
- Host 只做焦点停靠与输入必要项,不做业务 XYFocus。 业务导航留在 Web TV 树内。
注意事项与局限
- 本文是公开向的架构 / 交互合同说明,不是发布说明,也不承诺具体版本时间表。
- 实现细节(组件名、偏好 key、指令名)以产品实现文档为准;文中用语义描述,不绑定未发布内部路径。
- v1 不包含:完整搜索页与海报墙、剧集 / 合集 / 人物完整 TV 详情、TV 控制台播放设置 / 诊断 / 关于、完整遥控 / 手柄键表、像素级复刻 Android Compose、播放器 TV OSD 完整化。
- Android TV Compose 焦点不强制与 Web 同代码,但 轨 index / 进区 first / dialog 独占 语义应对齐产品手感。
- Geometry 可演进为 9 宫格 partition,属实现优化,不改变 Section 合同;若未来改为默认依赖外部空间导航库,应单独立项并 supersede 本口径。
到这里,可以把 Web 电视显示模式压成三句话:换独立 TV Shell,不 densify 桌面布局;首页 + overlay 对齐 10-foot IA,电影详情与本机设置先闭环;焦点用自研内核加 Section 声明,把补丁变成配置,而不是给全局几何继续加 if。
相关阅读
同系列还可对照:
- 媒体内容产品 + 管理后台混合前端的 UI 基座选型:Web 自有
ui边界与 headless 原语;TV 壳建在同一 Web 产品上,不为焦点换栈。 - Android 媒体客户端:单 App 双 Shell 与 Compose for TV 选型:触控 / TV 交互隔离与 Compose for TV 主线;Web TV IA 的语义对照源。
- 跨端媒体客户端:Web 做基线,原生各自 UI 与 native 播放:Web 基线与各端原生播放边界;TV 不是平板放大版。
- Windows 媒体客户端 UI 路线:WebView2 复用、WinUI 3 候选与当前主线:Host 复用主 Web UI 的当前主线;TV 仍在 Web 侧换壳。
来源
本文综合改写自 Octans 项目决策层「Web / Windows TV Shell 信息架构合同」与「Web TV 焦点 Section 模型决策」。表述面向公开工程讨论,已去掉内部仓库路径、未发布实现文件清单与实现层临时补丁细节;验收以产品侧手测清单与现行实现文档为准。