Skip to content

Improve channel menu, background startup refresh, and 4K hardware decoding - #10

Merged
yunnysunny merged 4 commits into
mainfrom
feature/channel-menu-startup-4k-fixes
Oct 11, 2026
Merged

yunnysunny merged 4 commits into
mainfrom
feature/channel-menu-startup-4k-fixes

Conversation

@yunnysunny

@yunnysunny yunnysunny commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

概述

这个 PR 包含三个相互独立的改动,都已在 p230(Android 7.1,S905X)真机上验证;第四个提交是针对 Codex 审查意见的修复。

1. 频道菜单更适合遥控器操作(12f960d)

  • 焦点行改成实心蓝底加白色描边,和"当前在播"行的浅色底区分开;在播行另加 ▶ 标记
  • 每行显示全局频道号,和数字键换台用的是同一套编号
  • 副标题改为该频道正在播出的节目名,在后台逐行查询;没有节目单时仍显示分组名
  • 右侧节目单区底部加了按键提示
  • 修复以下问题:
    • 切换分组后焦点掉到播放画面上:原来用的是 notifyDataSetChanged,改为只刷新新旧两行
    • 频道列表按右键时焦点会落到右侧 ScrollView 上,这时按返回会直接关掉整个菜单
    • 换台后 5 秒内打开的菜单,会被底部信息条的自动隐藏计时器一起关掉

面板宽度,以及焦点在面板内时不自动隐藏,这两点是有意保持的设计,这次没有改。

2. 启动时刷新 M3U 改为后台进行(651f096)

  • 先用本地缓存的频道列表起播,M3U 刷新放到独立线程。刷新成功后静默换上新列表,不打断正在播的节目;如果频道菜单开着,不会重建菜单
  • 缓存为空(首次使用)时仍等刷新结果,期间提示"正在更新频道列表…"
  • 如果正在用刷新前的旧地址播放,并且旧地址全部失败,会用刷新后的新地址自动重播一次
  • ChannelRepository.loadSource 的写库操作包进了事务

真机结果:冷启动从近 50 秒不出画面缩短到约 1 秒起播,菜单也能马上打开。

3. 4K HEVC 只是 Level 标高时仍走硬解(933dc72)

CCTV-4K 的码流标为 hvc1.2.4.L180(Main10 Level 6.0),而 Amlogic 硬解只声明支持到 Level 5.1,结果轨道选择把视频交给了 FFmpeg 软解,4K 10bit 几秒就丢一两百帧。求索纪录 4K 标的是 Level 5.1,所以一直正常。

新增 LevelTolerantVideoRenderer:在"硬解优先"和"音频软解 + 视频硬解"两种模式下,如果硬件解码器支持该 mime 和 Profile,分辨率也在能力范围内,只是 Level 超出,就按"支持"处理。Profile 不支持这类真正解不了的情况,仍然交给 FFmpeg。

真机结果:CCTV-4K 改为 OMX.amlogic.hevc.decoder.awesome 硬解,没有丢帧;求索 4K 和普通高清台照常走硬解。

4. M3U 刷新解析为空时不清空缓存(9a31ae0,针对审查意见)

原来 loadSource 只要 HTTP 返回 200,就会删掉解析结果里没出现的所有频道;源站临时返回空页面时,本地缓存会被清空。现在启动改成先用缓存起播,这份缓存就更不能被一次坏响应清掉。改为解析出 0 个频道时抛出 IOException、不写库,所有调用方本来就会把它当作刷新失败、保留原有数据。新增 ChannelRepositoryTest,用内存数据库和桩 OkHttpClient 验证这一点。

测试

  • 前三个提交分别都通过了 ./gradlew testDebugUnitTest 和 assembleDebug
  • 新增 LevelTolerantVideoRendererTest(4 个用例);WiTVRenderersFactoryTest 改为按 getName() 判断,并新增"哪些模式会包装渲染器"的断言

未在真机上验证

  • 后台刷新成功之后的路径,也就是换上新列表、旧地址失败后用新地址重播。测试用的源每次都超时,没跑到这条路径
  • 频道菜单副标题显示节目名。盒子上没有可用的 EPG 数据,只验证了退回显示分组名的情况

🤖 Generated with Claude Code

yunnysunny and others added 3 commits October 11, 2026 18:07
- Stronger focus highlight (filled blue + white outline) that stays
  distinct from the "selected" row, plus a now-playing marker
- Show the global channel number used by number-key zapping
- Use the currently airing program as each row's subtitle, loaded off
  the main thread and refreshed per row; fall back to the group title
- Keep the EPG preview pane out of the focus path so Back returns to the
  channel list instead of closing the whole menu
- Add a key hint at the bottom of the EPG pane
- Fix group selection calling notifyDataSetChanged, which dropped focus
  to the player view
- Stop the info overlay's auto-hide timer from closing the channel menu

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With "refresh M3U on startup" enabled, playback used to wait for the
refresh, and the refresh occupied the main executor. A slow source kept
the screen blank and the channel menu unresponsive for nearly 50s.

Start playing from the cached channel list right away and refresh on a
dedicated executor. On success, swap in the new list without
interrupting playback or rebuilding an open channel menu. Only wait for
the refresh when the cache is empty. If the pre-refresh URLs all fail,
retry once with the refreshed ones.

Wrap the database writes in ChannelRepository.loadSource in a
transaction, since sources are deleted and re-inserted and a channel
switch during the background refresh could otherwise see none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CCTV-4K is tagged hvc1.2.4.L180 (Main10, level 6.0), while the p230's
Amlogic HEVC decoder only advertises level 5.1. MediaCodecVideoRenderer
then reported FORMAT_EXCEEDS_CAPABILITIES and the FFmpeg renderer
FORMAT_HANDLED, so track selection sent 4K 10-bit video to the CPU and
it dropped frames constantly. The same channel plays fine with
"hardware only", so the level tag is just conservative.

In modes where video is hardware-first with FFmpeg fallback, wrap the
MediaCodec video renderer and report FORMAT_HANDLED when a hardware
decoder supports the mime type and profile and the resolution fits, and
only the level exceeds. Real gaps such as an unsupported profile still
fall back to FFmpeg.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-11T10:18:00.775648Z 933dc72 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 933dc729fb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +84 to +89
return false;
}

private static boolean supportsProfile(MediaCodecInfo info, int profile) {
for (CodecProfileLevel profileLevel : info.getProfileLevels()) {
if (profileLevel.profile == profile) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict level relaxation to verified decoder cases

When a stream genuinely exceeds a decoder's advertised level, matching the profile and resolution/frame rate does not establish that the decoder can handle it: codec levels also constrain properties such as bitrate, sample rate, and decoded-picture buffering. This loop therefore promotes every over-level AVC/HEVC/etc. format to FORMAT_HANDLED, causing the hardware renderer to win over FFmpeg even on devices whose level declaration is accurate; those streams can then fail or stutter instead of using the previously selected software fallback. Limit the workaround to known affected codecs/decoders or otherwise verify the constraints represented by the level before returning true.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不修改。确实存在码流真超出 Level、硬解却被判为支持的情况,但只在 1080p 及以下才有实际影响,因为 4K 交给软解在电视盒子上本来就跑不动。会出现这种情况的是硬解只声明到 Level 4.1 一类的老设备,年代已经比较久,项目不再支持,所以保留目前的放宽条件。

Comment on lines +581 to +584
if (isFinishing() || refreshed.isEmpty()) {
return;
}
allChannels = refreshed;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reconcile state when the refreshed playlist is empty

When a successful HTTP response parses to zero channels, ChannelRepository.loadSource has already committed deletion of every channel and source, but this early return leaves allChannels and currentChannel pointing at the deleted rows. For an intentionally emptied subscription or a transient 200 response with empty/malformed content, the current session continues showing stale channels, and selecting one queries an empty source list and immediately fails; update the in-memory state to reflect the empty database or reject the empty refresh before committing it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已在 9a31ae0 修复,并且是从源头修的:ChannelRepository.loadSource 在解析出 0 个频道时抛出 IOException,不再写库。这样源站临时返回空页面时,本地缓存不会被清掉,内存里的 allChannels 和数据库也保持一致。所有调用方(启动刷新、设置页、Web 管理页)本来就会捕获这个异常,按刷新失败处理。新增的 ChannelRepositoryTest 覆盖了这种情况。

Comment thread app/src/main/java/com/whyun/witv/ui/PlayerActivity.java
loadSource deletes every channel missing from the parsed playlist, so a
transient empty or error page from the source wiped the local cache.
Now that startup plays from the cache first, that cache matters more.
Throw an IOException instead of writing when nothing was parsed; all
callers already handle refresh failures by keeping the existing data.

Add a test-only constructor so the repository can run against an
in-memory database and a stubbed OkHttpClient.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yunnysunny
yunnysunny merged commit 7c4d1ee into main Oct 11, 2026
1 check passed
@yunnysunny
yunnysunny deleted the feature/channel-menu-startup-4k-fixes branch October 11, 2026 10:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant