先说清楚使用场景
本文讨论的不是“电脑投屏到电视”,也不是“电视自己播放视频”,而是:
一台 Windows 电脑通过 HDMI 连接电视,并把电视直接作为 Windows 显示器使用。
电脑上的 PotPlayer 负责播放视频,电视只承担显示输出。这个问题在电视是唯一显示器时最明显,在电视作为复制或扩展显示目标时也可能出现。
典型情况是:
- 电视完全断电
- 电脑先启动并自动播放视频
- 之后电视重新上电
- Windows 完成显示目标切换
- 播放器没有重新适配,只占屏幕一小块
如果电视先开机,或者使用始终保持连接的普通显示器,通常不会遇到这个时序问题。
问题从哪里来
有一个很常见但容易被低估的场景:
- Windows 电脑通过 HDMI 连接电视,并将电视作为显示器使用。
- 先给电脑开机,电视还处于完全断电状态。
- 播放器按 Windows 当前的默认分辨率启动。
- 之后再给电视上电。
- Windows 识别到电视的 EDID,桌面分辨率发生变化。
- 播放器却没有重新适配,只占屏幕的一小块。
问题表面看起来是“播放器没有全屏”,实际上涉及三个不同层次:
- Windows 在显示目标切换时,会先进入临时显示模式。
- 播放器在窗口创建时记录了一套尺寸。
- 外部脚本如果抢在显示切换完成之前操作,反而会放大问题。

显示目标切换完成后,窗口没有重新适配,只占屏幕一角;目标是自动铺满。
这次改造的目标很明确:电视无论什么时候上电,最终都能自动全屏,不需要人工重新打开视频。
因此,后文提到的“电视连接”“电视上电”和“显示目标重新接入”,都指电视作为电脑显示器重新进入 Windows 显示链路,而不是电视播放网络视频或接收投屏。
先明确一个关键前提
电视不使用待机,而是每次彻底断电。
因此每次开电视,对 Windows 来说都相当于一次显示器重新接入。不能把“遥控器关机”和“拔掉电源”混为一谈。前者可能仍保留 HDMI 信号和 EDID,后者才会真正触发显示目标消失。
改造开始前,现场实测过一次完全断电状态:
- 活动 PnP 显示器数量为 0
- 系统退回默认的 1024×768
- 真实电视不在设备列表中
- 播放器仍保持进程,但窗口跟随默认桌面
这一步很重要。只有把断电状态和上电状态都测出来,才能知道脚本到底应该等待什么。
为什么“主动切分辨率”不可靠
最初的脚本采用了一个很直觉的思路:
- 每 4 秒读取一次当前分辨率
- 发现分辨率变化就重启播放器
- 同时主动调用显示模式切换接口,尝试切到期望分辨率
实机运行后,这个方案暴露了明显问题。
电视重新握手时,分辨率并不会直接从旧值跳到最终值。它可能短暂经过默认分辨率、临时模式或低分辨率状态。脚本只采样其中一个瞬间,就可能做出错误判断。
历史日志复盘后发现:
- 分辨率变化共检测到 19 次
- 其中 13 次是向下切到低分辨率
- 脚本却在这 13 次低分辨率瞬态中重启了播放器
- 真正从低分辨率恢复高分辨率的次数,反而只有 4 次
- 主动切换接口的失败次数远高于成功次数
这说明脚本看起来“有时有效”,实际只是偶尔命中了正确的时序。
可靠方案首先要放弃一个危险想法:监视器脚本不应该负责修改分辨率。
分辨率的协商交给 Windows、显卡驱动和电视完成。监听器只负责观察最终状态,然后决定是否处理播放器。
驱动层 EDID 模拟为什么没采用
EDID 模拟是一个理论上的方案,可以让电脑在没有真实电视时仍看到一个固定的显示目标。
但现场核显驱动没有提供“EDID 模拟”“强制常驻输出”等可用能力。驱动控制面板里也没有适合直接启用的对应选项。
在没有更新驱动、加硬件或引入其他设备的前提下,这个方案无法可靠落地。
于是重点转向应用层的状态观察。
DPI 感知为什么必须单独处理
这次排查里还遇到一个很容易误判的问题:不同进程读到的 DPI 不一致。
服务会话中的 WMI 和注册表值,可能返回默认值、缓存值或过期配置。它们不一定等于当前登录会话里真实生效的 DPI。
最终使用的是 Per-Monitor V2 DPI 感知,并在登录用户会话中读取有效 DPI:
SetProcessDpiAwarenessContext(
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
)
随后使用:
GetAwarenessFromDpiAwarenessContextGetDpiForMonitorEnumDisplaySettingsEx
这样读取到的才是当前显示目标的物理分辨率和有效缩放,而不是服务会话里的推测值。
最终实测中,两个测试环境的有效 DPI 都显示为 144,也就是 150%。早期报告里出现的 100% 和 300%,被确认是混用了非交互会话数据,不能作为可靠依据。
最终方案:只观察,不修改
最终脚本的核心原则只有一句话:
Windows 负责显示模式,脚本只负责等待稳定状态并管理播放器。
完整流程如下:
读取物理分辨率和 DPI
|
v
连续 5 次相同?
|
否 -> 继续等待
|
是
v
达到目标分辨率?
|
否 -> 等待电视或显示设置就绪
|
是
v
再等待 3 秒并复核
|
v
比较播放器窗口和目标屏矩形
|
+---- 已全屏 -> 不动作
|
+---- 窗口不符 -> 同一代次最多重启一次
|
+---- 播放器不存在 -> 启动,受频率限制
参数设置为:
- 每 2 秒读取一次
- 连续 5 次一致才视为稳定
- 稳定后再等待 3 秒复核
- 目标分辨率按机器配置
- 窗口矩形误差允许 2 像素
从电视首次达到目标分辨率,到确认全屏,实际测试在 8 到 17 秒之间,满足 20 秒内完成的目标。
为什么不只监听显示变化事件
显示变化事件能及时告诉我们“有东西变了”,但不能告诉我们“现在是否已经稳定”。
一次电视重新接入,可能连续产生多个模式变化。只依赖事件,很容易在第一个临时状态就执行动作。
因此最终方案采用连续采样。事件可以作为未来优化项,但稳定判定必须依赖一组连续一致的读数。
播放器窗口矩形比对
只看分辨率还不够,因为播放器进程存在,不代表窗口已经铺满。
方案使用两层检查:
- 找出播放器进程的所有可见窗口。
- 选择面积最大的窗口,读取其可见边界。
窗口矩形读取优先使用:
DwmGetWindowAttribute(DWMWA_EXTENDED_FRAME_BOUNDS)
失败时回退到:
GetWindowRect
只有窗口四条边和目标屏矩形一致,才认为已经全屏。
这样做避免了一个常见误区:
“播放器进程正在运行,所以应该不用处理。”
进程存在和画面铺满并不是同一件事。
用“分辨率代次”限制重启
如果每轮都判断为尺寸不符,脚本会陷入反复重启。
解决方案是给每次分辨率变化分配一个代次:
- 分辨率发生变化,代次加一。
- 同一代次内已经处理过窗口不符,就不再重复重启。
- 只有新的稳定分辨率出现,才允许新的处理。
这样可以保证一次显示切换最多触发一次播放器重启。
同时,如果当前窗口已经和目标屏完全一致,动作就是“无”。实测中,很多断电重连场景下播放器会自动适配,因此并不需要重启。
播放器不存在时怎么办
还有一种情况:电视已经就绪,但播放器进程不存在。
这时脚本允许启动播放器,但增加滚动时间窗限制:
- 每 60 分钟最多启动 3 次
- 超过限制后只写日志,不再继续拉起
这个规则避免了播放器因文件损坏、解码器异常等问题反复启动,形成风暴。
循环播放怎么处理
播放器的命令行参数可以提供全屏参数,但循环播放通常依赖播放器自身的设置。
最终脚本采用两个动作:
- 启动时传入全屏参数。
- 启动前只读检查循环播放设置。
如果循环设置缺失或不匹配,脚本记录警告,但不会拒绝启动。这样可以保证主功能优先,同时把配置问题明确留在日志中。
日志应该记录什么
日志不能只写“成功”或“失败”,而应足够还原一次恢复过程。
最终日志包含:
- 时间
- 当前分辨率
- 刷新率
- DPI
- 是否稳定
- 首次达到目标分辨率的时间
- 距首次达到目标分辨率经过的秒数
- 播放器是否存在
- 播放器窗口矩形
- 执行的动作和原因
- 是否确认全屏
一条简化后的日志类似:
resolution=3840x2160@30 dpi=144 stable=false action=none reason=debouncing
resolution=3840x2160@30 dpi=144 stable=true action=none reason=already_fullscreen
fullscreen_confirmed=true target_age_seconds=8.1
这比只记录“最终成功”更有价值,因为可以清楚看到等待、防抖和动作之间的因果关系。
自启动入口必须统一
这类问题很容易被多个历史脚本进一步放大。
现场曾有多个入口同时存在:
- 播放器启动任务
- 分辨率监听任务
- 验证任务
- 视频看门狗
- 启动文件夹中的监听脚本
它们会互相启动、重启和覆盖状态,最终结果取决于时间竞争。
改造后的原则是:
- 只保留一个监听任务
- 只由监听器启动播放器
- 删除旧启动项和旧任务
- 启动前备份原任务 XML、旧脚本和启动项
部署完成后,同时检查:
- 新监听器进程数量为 1
- 播放器进程数量为 1
- 旧任务数量为 0
- 旧启动项数量为 0
验证结果
两个不同分辨率环境都完成了三项测试。
冷启动
电视完全断电,主机启动,进入桌面一分钟后再给电视上电。
结果:
- 从首次达到目标分辨率到确认全屏:16.7 至 16.8 秒
- 播放器启动一次
- 没有重复重启
播放中完全断电再上电
电视播放过程中直接断电,再重新上电。
结果:
- 约 8 秒确认全屏
- 播放器进程保持稳定
- 没有不必要的重启
手动切换分辨率
把分辨率降到较低值,再恢复到目标值。
结果:
- 低分辨率阶段只等待
- 恢复目标值后约 8 秒确认全屏
- 播放器进程保持稳定
这个方案最重要的三个经验
1. 不要用监听器修改显示模式
监听器应该解决“何时处理应用”,而不是替操作系统和驱动做显示协商。
2. 一次变化不等于稳定
电视握手会产生多个中间状态。连续稳定样本比单次事件可靠得多。
3. 进程存在不等于画面正确
必须检查窗口矩形,并允许“什么都不做”也是成功结果。
结语
这类问题的难点并不在于“怎么重启播放器”,而在于判断什么时候应该重启、什么时候不应该动。
最终方案没有使用复杂的驱动技巧,也没有继续尝试强制切换分辨率。它做的是把显示变化当成一个状态机,通过 DPI 感知、连续采样、窗口矩形比对和单次动作限制,把原本依赖人工和运气的操作变成可观察、可回滚、可验证的流程。
对于电视完全断电、显示目标重新接入的场景,这种“只观察、防抖、确认后再动作”的思路,也适用于其他桌面播放器和展示终端。