判断 video 或 audio 是否正在播放且在视口内,需同时满足:元素处于播放态(!paused && !ended && readyState ≥ HAVE_CURRENT_DATA)、被 IntersectionObserver 检测为可见(isIntersecting 且 intersectionRatio > 0.1)、视频需 videoWidth/videoHeight > 0,音频需 unmuted 且 volume > 0。

如何判断 video 或 audio 元素正在播放且在视口内
浏览器没有“当前屏幕播放的多媒体”这种现成 API,得靠组合判断:是否在播放(playing 状态) + 是否在视口可见(IntersectionObserver) + 是否未被用户暂停或静音压制。单独看 play() 成功不代表真在播——可能被策略拦截、被静音、或刚加载完还没解码出帧。
-
video.played返回已播放时间范围,但空数组不等于没播,可能是刚播 1ms 就暂停了 - 用
video.paused === false && !video.ended && !video.waiting && !video.seeking比只看paused更稳妥 -
IntersectionObserver的isIntersecting必须为true,且intersectionRatio > 0.1(防边缘擦过) - 注意
visibility: hidden或display: none会让IntersectionObserver不触发,但元素仍可能在 DOM 中“逻辑播放”
document.visibilityState 和 pagehide 对多媒体的影响
页面切到后台时,浏览器可能主动暂停视频(尤其移动端),但行为不统一:Chrome 通常暂停,Safari 可能继续解码但不渲染,Firefox 更倾向保持状态。不能依赖 visibilityState === 'visible' 推断媒体是否在播,它只说明页面焦点,不反映媒体实际输出。
- 监听
visibilitychange事件后,应主动检查video.paused,而不是假设“页面不可见 → 视频已停” -
pagehide触发时,video很可能已被冻结,此时video.currentTime可能卡住,video.duration可能为NaN - 恢复可见时,需手动调用
video.play().catch(e => console.warn('resume play failed:', e)),因为自动播放策略限制依然生效
为什么 getComputedStyle(video).display !== 'none' 不足以判断“正在播放”
CSS 显示状态和媒体播放状态完全解耦。一个 display: none 的 video 仍可调用 play()、触发 timeupdate、甚至下载数据——只是不渲染帧也不输出音频。所谓“屏幕播放”,本质是“有画面/声音输出给用户”,这取决于三件事:是否解码成功、是否在视口、是否未被系统级静音或策略抑制。
-
getComputedStyle查不到音频是否在输出,也查不到 GPU 是否丢帧 - 用
video.videoWidth > 0 && video.videoHeight > 0可辅助判断视频帧是否就绪,但对纯音频无效 - 音频元素需结合
audio.muted === false && audio.volume > 0,否则即使paused === false也无声 - Web Audio API 的
AudioContext.state若为suspended,所有音频都会静音,和 CSS 无关
推荐的实时检测函数写法(含兼容性处理)
别写轮询,用 requestAnimationFrame + IntersectionObserver + 事件监听组合。下面这个函数返回布尔值,表示“该媒体正被用户看到且有有效输出”:
立即学习“前端免费学习笔记(深入)”;
function isMediaActivelyPlaying(mediaEl) {
if (!mediaEl || !(mediaEl instanceof HTMLMediaElement)) return false;
const isVisible = observerMap.get(mediaEl)?.isIntersecting ?? false;
const isPlaying = !mediaEl.paused && !mediaEl.ended &&
mediaEl.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA;
if (mediaEl.tagName === 'VIDEO') {
return isVisible && isPlaying && mediaEl.videoWidth > 0;
} else {
return isVisible && isPlaying && !mediaEl.muted && mediaEl.volume > 0;
}
}
注意:observerMap 是你用 IntersectionObserver 维护的弱映射表;HAVE_CURRENT_DATA 表示至少有一帧可渲染/播放,比 HAVE_METADATA 更可靠;移动端需额外处理 webkit-playsinline 属性缺失导致全屏劫持的问题。


















