Skip to content

[Feature Request] 优化 Web 播放器底部 EPG 时间轴刻度与交互体验 #770

Closed

Description

@CGG888
Image

期望行为(Expected Behavior)及实现细节

1. 拖拽时间指示线精准定位(Drag Playhead for Time-Shift)

交互逻辑:

  • 鼠标悬停在红色指示线上方时,光标变为 grab;按下时变为 grabbing。
  • 鼠标水平拖动时,指示线跟随鼠标 X 坐标实时移动,同时播放器内部 currentTime 更新为对应时间点。
  • 拖动过程中,在指示线顶部显示一个悬浮气泡(Tooltip),内容为当前拖拽到的精确时间(格式 HH:mm:ss)以及该时间点对应的节目名称。
  • 松开鼠标后,播放器正式执行 seek 操作,若该时间点属于历史时段则加载时移流,若属于未来时段则忽略或停留在当前直播。

前端实现参考:

  • 监听 mousedown / touchstart 事件在指示线元素上。
  • 在 mousemove 中通过 clientX 与时间轴总宽度比例计算出对应的时间偏移量。
  • 利用 requestAnimationFrame 更新指示线位置和 Tooltip 内容,避免频繁 DOM 操作导致性能问题。

2. 节目块悬停详情浮窗(Hover Tooltip on Program Block)

交互逻辑:

  • 鼠标悬停在任意节目块(矩形区域)上超过 300ms 后,弹出一个半透明浮窗。
  • 浮窗内容包含:节目完整名称、开始时间 – 结束时间、节目简介(若有)、节目类别标签(如“新闻”“电视剧”)。
  • 浮窗位置跟随鼠标指针,超出屏幕边界时自动翻转方向。
  • 鼠标移出节目块后延迟 200ms 关闭浮窗,防止闪烁。

前端实现参考:

  • 使用 mouseenter / mouseleave 事件,配合 setTimeout 实现延迟显示。
  • 浮窗采用绝对定位,动态计算 left/top,必要时使用 getBoundingClientRect() 判断边界。
  • 数据来源:EPG JSON 中的 description、category 等字段。

3. 视觉状态区分(Visual State Differentiation)

设计建议:

状态 | 颜色/样式 -- | -- 历史节目(已播完) | 灰色或浅蓝色,透明度 0.7 当前正在播放的节目 | 高亮色(如蓝色边框 + 白色背景) 未来节目(未开始) | 半透明虚线边框,或淡绿色
  • 当前时间指示线(红色竖线)加粗至 2px,并在底部增加一个小三角形或圆点标识。
  • 指示线与当前节目块交叉处增加发光阴影效果。

实现逻辑:

  • 根据 EPG 数据的 start_time 和 end_time 与服务器当前时间比较,动态赋予 CSS class。
  • 例如:.program.past { background: #e0e0e0; } .program.live { border: 2px solid #ff6600; }

4. 节目块点击跳转时移(Click to Jump to Program Start)

交互逻辑:

  • 点击历史节目块(非直播、非未来)时,立即将播放器 seek 到该节目的起始时间点。
  • 点击当前直播节目块时,不做跳转或跳转到直播最新位置(即清除时移状态)。
  • 点击未来节目块时,弹出提示“该节目尚未播出”。

实现细节:

  • 在节目块上绑定 click 事件,读取该节目块的 data-start-time 属性(Unix 时间戳)。
  • 调用播放器 API(如 video.currentTime = targetTimestamp - liveEdgeOffset)或通过 rtp2httpd 提供的时移 URL 参数实现。

5. 文本溢出与自适应(Text Overflow Handling)

需求细分:

  • 对于宽度大于 80px 的节目块,正常显示节目名称,超出部分用省略号 … 表示。
  • 对于宽度小于 80px 的节目块,完全隐藏文字,仅在悬停时显示。
  • 当用户鼠标悬停在该节目块上时,启动跑马灯滚动效果(从右向左循环滚动完整名称),移出后停止。

CSS/JS 实现:

  • 默认使用 text-overflow: ellipsis; white-space: nowrap; overflow: hidden;。
  • 跑马灯使用 @keyframes marquee + animation-play-state: paused/running 控制,通过 JS 监听 hover 事件切换。

6. 平滑滚动与虚拟列表(Smooth Scrolling & Virtualization)

问题:当前时间轴平移可能因一次性渲染全部节目块(如 48 小时 EPG)导致 DOM 节点过多,造成滚动卡顿。

解决方案:

  • 虚拟列表:只渲染视口内可见的节目块(加上前后各 20% 的缓冲区)。根据时间轴容器的 scrollLeft 或 translateX 值动态计算需要显示的节目索引。
  • 平滑动画:左右箭头点击时,使用 transform: translateX() + transition: transform 0.3s ease 代替直接修改 scrollLeft,避免重排。
  • 当用户手动拖拽时间轴空白区域(或使用鼠标滚轮)时,同样应用 requestAnimationFrame 驱动平移。

补充建议(Nice to Have)

  • 时间轴底部增加“当前时间”的固定标签,始终显示 YYYY-MM-DD HH:mm:ss,每秒更新。
  • 支持键盘快捷键:左右方向键微调时间(±15秒),上下方向键切换频道(需联动 EPG)。
  • 若 EPG 数据为空,显示友好占位文字“暂无节目信息”。

Activity

  1. CGG888 commented on Sep 16, 2026

    @CGG888
    ContributorAuthor
    Image Image

    在现有最新代码的基础上做了一些调整,增加了epg刻度! @

  2. CGG888 commented on Sep 17, 2026

    @CGG888
    ContributorAuthor

    已在 #771 实现。本改动纯前端(Web 播放器),不含服务端改动、不需要新增配置项,合入后随固件/发布生效。

    需求对照

    # 需求 状态
    1 拖拽指示线精准定位 ✅ 时间带上任意位置按住即可拖(4px 内视为点击);气泡实时显示 HH:mm:ss + 该时刻节目名;拖到直播边缘显示「返回直播」;松手才 seek;目标一律钳制到 now,不会去拉未来流。拖动全程用 requestAnimationFrame 直写 DOM,不触发组件重渲染。
    2 节目块悬停详情浮窗 ✅ 停留 300ms 显示、移出 200ms 关闭(跨块移动不闪)、跟随指针、超出边界自动内收。
    ⚠️ 内容为节目名 + 起止时间:<desc>/<category> 目前在 XMLTV 解析白名单里被丢弃,EPGProgram 也只有 id/title/start/end,补简介/类别需要改数据模型与 worker 传输,本次刻意限定在前端。
    3 视觉状态区分 ✅ 已播压暗 / 正在播蓝色渐变+左侧强调条+发光 / 未播虚线边框;指示线 2px + 顶部圆点 + 底部小三角 + 发光。
    配色沿用播放器既有调色板(进度条同款蓝渐变 + 直播徽标的玫红),未采用红/橙/浅灰(那套是浅色主题写法,播放器是深色玻璃拟态,且需同时适配「简洁」主题)。
    ➕ 额外:时移时「播放位置线」与「当前时刻线」分离,两线之间就是还能回看的区间。
    4 点击节目块跳转 ✅ 历史→回看该节目起点;正在播→回到直播;未来→提示「该节目尚未播出」;无回看源→提示「该频道没有回看源」。
    ➕ 历史节目点击会先弹确认框:M3U 里没有 catchup-days,客户端无法知道上游回看窗口大小,直接请求可能拉不到流。
    5 文本溢出与自适应 ✅ ≥80px 省略号;<80px 隐藏名称、改显示节目开始时间(避免出现空块,该时刻同时也标在刻度上);长名称按实测溢出量持续跑马灯(电视/大屏场景更实用,不必等悬停),prefers-reduced-motion 与「简洁」主题下不滚动。
    6 平滑滚动与虚拟列表 ✅ 目标达成,方案改为有界时间窗 + 平移:窗口为锚点 ±90 分钟并按 15 分钟网格吸附,用 ◀▶ / 滚轮 / 拖拽平移,默认跟随播放、seek 后自动回到跟随。
    原因:单频道 48 小时只有 50–100 个块,DOM 从不是瓶颈,真正的成本是每秒/每帧重渲染——所以细网格改用 CSS 渐变绘制(288 条线 0 个 DOM 节点)、只渲染与窗口相交的块、1Hz 的播放指示线隔离到独立子组件、拖动/悬停/浮窗全部 rAF 直写 DOM。

    补充建议(Nice to have)

    • ✅ 当前时间标签每秒更新(新增了一个全页共享的秒级墙钟,与既有分钟级定时器同款设计,全页仍只有一个定时器)
    • ✅ 无节目数据时不渲染时间带(侧边栏保留原有的「暂无节目单」)
    • ⚠️ 键盘快捷键未改:现在 ←/→ 是 ±5 秒、↑/↓ 换台(原本就有)。改成 ±15 秒还是加长按加速,想听维护者意见。

    顺带修掉/优化的几点

    • 节目时间移到刻度上:刻度轴标签 = 整点 ∪ 每个节目的开始时刻(同刻去重、重叠自动隐藏、首尾贴边内收),块内只留居中的节目名,窄块才回退显示时间。
    • 常驻日期标签:◀▶ 前面固定显示「今天 9/17」;跨天显示「昨天 9/16 – 今天 9/17」。原先把日期拼在 0 点刻度标签里,窗口边缘时会被挤出刻度带。
    • 字号一致:块内节目名与进度条那行「正在播放」节目名完全一致(12px,≥768px 时 14px),并保持水平+垂直居中。
    • 尺寸变化后自动重排:开关侧边栏、进退全屏、画中画、窗口缩放、横竖屏、Web 字体迟到,都会重新实测并排布刻度标签(ResizeObserver + document.fonts.ready)——刻度标签是像素定位、刻度线是百分比定位,缺了这一步就会错位。
    • 响应式:紧凑布局(容器高 ≤320px)工具栏只保留日期,节目带压到 26px;≤220px 整条隐藏,只留原有进度条。
    • 触摸不冲突(播放器手势层是同层下方兄弟节点)、拖动/确认期间控制条不自动隐藏、新增文案已配简/繁/英三语。

    验证

    • pnpm web-ui:test:98 条通过,其中新增 50 条(44 条纯逻辑:时间窗/刻度/轴标签/点击决策表/跨天日期换算;6 条渲染级)
    • pnpm type-check:tsc、biome check、vite build 均干净
    • 另在真实(headless)浏览器里实测过交互路径与几何:修复前宽度变化会让标签最大偏移 307px 并有标签跑出刻度带,修复后中段标签 0 偏差、首尾按半宽内收、无越界

    试用方式

    拉到 PR 分支 feat/epg-timeline 后 pnpm install && pnpm web-ui:dev,或在发布版上直接用(无需改配置)。上机建议重点看:开关侧边栏/进全屏后刻度是否仍然对齐、跨 0 点时日期是否正确、长节目名是否自动滚动、拖拽气泡与松手跳转是否跟手。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions