iOS端的画中画(Picture in Picture)功能从iOS 9开始引入,原生开发者可以通过AVPictureInPictureController很方便地控制小窗口的行为。但当视频播放发生在WKWebView内部时,事情就变得复杂了:网页中的video标签由WebKit进程负责渲染,画中画窗口的启动、尺寸、背景色都由系统接管,开发者几乎没有直接的API可以干预。其中最常被问到的就是背景色问题——为什么画中画窗口有时是黑色,有时又想让它是透明的却做不到。本文就从原理到实践,把这个问题的来龙去脉讲清楚。

一、先搞清楚:WKWebView画中画窗口的背景色由谁决定
很多开发者第一反应是通过CSS去控制,比如给video标签设置background-color,或者用CSS属性background配合透明度。但要理解的是,一旦视频进入画中画模式,渲染权就从WKWebView转移到了系统的画中画控制器。此时窗口的背景并不是网页的一部分,而是系统图层的内容。
具体来说,画中画窗口的层级结构大致是:最外层是系统提供的PiP窗口(AVPictureInPictureControllerLayer),中间是视频渲染层(AVSampleBufferDisplayLayer或类似的渲染表面),当视频帧的实际宽高比与窗口宽高比不一致时,多出来的区域就由渲染层的背景填充。原生开发中可以通过AVPlayerLayer的backgroundColor属性控制这个填充色,默认是黑色。
但在WKWebView场景下,开发者拿不到这个layer。WebKit内部在创建画中画会话时使用的是自己管理的视频渲染管道,填充色是硬编码的黑色,没有暴露任何修改接口。这就是为什么你在CSS里写什么颜色都不会生效的原因——那个背景根本不在网页的渲染树里。
二、网页层能做的事:控制视频帧与窗口的比例
既然直接改背景色行不通,那换个思路:让视频帧完全填满画中画窗口,背景自然就看不到了。画中画窗口的宽高比默认跟随视频的宽高比,所以只要视频源本身的宽高比是标准的,背景区域几乎不会出现。问题往往出在视频源有黑边(letterbox)或者播放器容器比例与视频不一致。
网页端可以通过object-fit属性控制视频在容器中的填充方式。默认值是contain,保持宽高比留出空白;改为cover则会裁切填满,代价是可能切掉画面边缘内容:
<video id="myVideo" playsinline webkit-playsinline
style="object-fit: cover; width: 100%; height: 100%;">
<source src="video.mp4" type="video/mp4"/>
</video>
需要注意,这里的object-fit影响的是视频在网页容器里的显示。进入画中画后,如果视频源本身宽高比固定,窗口会按源比例缩放,理论上不会露出背景。真正露出黑色背景的情况,多半是视频流中本身就带了黑边,或者是转码时容器比例与像素比例不一致导致的。排查时可以用ffprobe检查视频的实际分辨率和sar(采样宽高比)参数。
另外一个网页层的技巧是利用canvas重新绘制视频画面,把黑边裁掉再输出。虽然性能开销不小,但对于无法改变视频源的存量项目,这是一个可行的兜底方案。绘制时先在离屏canvas上按目标比例裁切绘制,再用canvas的captureStream输出流,配合画中画API使用。
三、原生层混合方案:接管画中画会话
如果背景色控制是硬性需求,最彻底的方案是让原生层接管画中画。思路是禁用WKWebView内部的自动画中画,改由原生代码监听视频状态,用AVPictureInPictureController自己启动会话。这样你就拥有了对AVPlayerLayer的完全控制权。
关键代码思路如下。首先通过WKScriptMessageHandler监听网页中视频的播放事件,网页端在视频进入全屏或用户点击画中画按钮时,把视频地址传给原生。原生侧用AVPlayer重新加载同一个地址(对于直播流或DRM内容这种方案不适用),然后启动画中画:
// 原生侧创建播放器并控制画中画背景色
self.player = [AVPlayer playerWithURL:videoURL];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
playerLayer.frame = CGRectMake(0, 0, 480, 270);
// 关键:设置画中画窗口的背景色,这里设为透明
playerLayer.backgroundColor = [UIColor clearColor].CGColor;
self.pipController = [[AVPictureInPictureController alloc] initWithPlayerLayer:playerLayer];
self.pipController.delegate = self;
if ([AVPictureInPictureController isPictureInPictureSupported]) {
[self.pipController startPictureInPicture];
}
这里有个细节值得展开:把背景色设成clearColor,画中画窗口会呈现半透明效果,视频未覆盖的区域透出的是桌面壁纸而不是黑色。这在iOS 14之后支持得比较好,但在iOS 13及更早版本上,系统会强制使用黑色背景,透明设置会被忽略。所以如果你的App还兼容老系统,要做好降级处理。
接管方案的缺点也很明显:播放进度需要原生和网页双向同步,否则用户从画中画返回网页时会看到进度跳变。同步逻辑可以通过evaluateJavaScript把currentTime回写给网页video标签,实现上不复杂但细节多,比如缓冲状态、倍速、字幕轨道都需要考虑。团队里如果有条件,建议把这套逻辑封装成独立的播放模块统一维护。
四、调试与验证的几个实用技巧
调试画中画问题时,建议先在Safari里验证网页行为,排除CSS和视频源本身的问题。Safari的开发工具可以直接检查video元素的渲染状态,确认object-fit是否生效、视频实际分辨率是多少。如果Safari里表现正常而WKWebView里异常,问题基本出在原生配置上。
WKWebView这边要确认allowsPictureInPicturePlayback属性已开启(iOS 14.5之前叫allowsPictureInPictureMediaPlayback)。有些开发者反馈升级后画中画突然不可用,多半是Xcode里Background Modes的Audio, AirPlay, and Picture in Picture Capability没勾选,这个在Capabilities面板检查即可。
最后验证背景色效果时,注意用真实设备测试。模拟器对画中画的支持不完整,某些版本上窗口表现与真机差异较大,尤其是透明背景相关的合成行为,只有真机才能反映最终效果。测试时可以准备几个不同宽高比的视频源,比如16:9、4:3、2.35:1的电影宽幅,逐一确认各种比例下窗口的表现是否符合预期。
五、方案选型建议
综合来看,三种路径各有适用场景。如果只是想消除黑边观感,优先从视频源和object-fit入手,成本最低;如果需要透明或自定义颜色的画中画窗口且内容是普通点播视频,原生接管方案是唯一能精确控制背景色的方式;如果是直播或DRM加密内容,原生接管不可行,只能接受系统默认的黑色背景,转而通过产品层面的设计去弱化这个问题,比如保证视频源比例标准化。
iOS 16之后系统对画中画的行为又做了一些调整,窗口记忆位置、按钮样式都有变化,但背景色的控制策略没有开放新接口。持续关注每年WWDC的WebKit和AVKit session更新,一旦苹果开放相关能力,第一时间迁移到官方方案是最稳妥的。
WKWebView画中画AVPictureInPictureController修改时间:2026-09-06 10:32:40