HTML视频无法真正防投屏,所谓“防投屏”仅为无效前端限制;真实观看日志应基于document.visibilityState、document.hasFocus()和video播放状态三者叠加判断,并每10秒采样记录。

HTML 视频本身无法阻止投屏,所谓“防投屏”只是前端限制(如禁用右键、隐藏控件、监听 pictureInPictureElement),但这些对系统级投屏(如 macOS AirPlay、Windows 投影、Chromecast)完全无效。真正能记录观看日志的,是用户在当前页面内的实际播放行为——和是否投屏无关。
为什么监听 webkitendfullscreen 或 exitpictureinpicture 不能判断投屏
这两个事件只响应浏览器内部的画中画或全屏退出,并不触发于系统投屏。用户点了 AirPlay 或无线投屏按钮,页面 DOM 完全无感知,video 元素仍处于播放状态,timeupdate 照常触发。强行用它们当“投屏检测”会导致日志严重失真。
- Chrome / Edge 下投屏时,
document.hidden仍为false,页面未被冻结 - Safari 在 macOS 投屏时可能触发
visibilitychange,但 iOS Safari 基本不触发,不可靠 - 所有主流浏览器均未提供
navigator.isScreenMirrored类似 API,不存在标准检测方式
真正可记录的观看行为:聚焦 + 播放 + 可见性
你只能记录用户“在当前标签页里是否真实观看了视频”。核心依据是三个信号叠加,而非幻想拦截投屏:
-
document.visibilityState === 'visible':页面处于前台且未被最小化 -
document.hasFocus():窗口获得焦点(排除后台标签页自动播放) -
video.paused === false && !video.ended:视频确实在播,且未结束
三者同时为真时,才认为是一次有效观看。建议每 10 秒采样一次并写入日志缓存(非实时 localStorage 写入),避免高频抖动。
立即学习“前端免费学习笔记(深入)”;
生成可在 deepbi 沙箱 iframe 内运行或作为独立文件运行的交互式 HTML 页面,使用 `window.deepbi` JS SDK 实现数据查询、空间管理、文件浏览及用户操作。当用户提出以下需求时启用该能力: - 创建 KPI 仪表盘、数据表格、文件浏览器、成员列表、管理页面; - 明确要求“转成 dhtml”、“转译成 HTML”、“把 XX 页面转成……”、“控制台页面”、“管理后台”、“DeepBI 页面迁移”; - 提及位于 `apps/console/src/featur
日志字段设计要避开“投屏陷阱”
别在日志里记 isScreenMirrored: false 这种伪字段——你根本不知道。应聚焦可验证事实:
-
timestamp:用performance.now(),毫秒级精度,比Date.now()更准 -
url:当前页面 URL,用于回溯上下文 -
video_id:稳定标识,推荐从video.dataset.id读取,而非video.src -
playback_state:枚举值"playing"/"paused"/"ended" -
visibility:取值"visible"/"hidden"/"prerender" -
focus:布尔值,来自document.hasFocus()
这样一条日志,既能反映真实观看状态,又不会因投屏误判而污染数据源。
sendBeacon 上报时必须处理页面卸载场景
用户关掉标签页或切走再不回来,timeupdate 停了,但最后几秒观看没上报。此时依赖 beforeunload 或 pagehide 并不可靠(iOS Safari 会截断,Chrome 有 50ms 限制)。唯一稳的方式是:
- 用
setInterval每 15 秒把缓存日志 push 到一个队列数组 - 在
pagehide事件中调用navigator.sendBeacon('/log', JSON.stringify(queue)) - 若
sendBeacon返回false(极少数旧浏览器),降级为fetch(..., { keepalive: true })
注意:不要在 beforeunload 里做任何异步操作,它已不支持 Promise 和 await。
投屏不可防,也不该防;日志的价值在于还原真实用户行为路径。只要聚焦页面内可测量信号,不虚构“投屏状态”,日志就能支撑后续的完播率、跳出点、二次打开等关键分析——这才是工程上真正可控的部分。


















