Picture-in-Picture (PiP) — 一种在其他应用程序上方的浮动窗口中播放视频的模式,允许用户在最小化应用或切换程序时继续观看内容。PiP 窗口自动定位在屏幕角落,用户可以移动它。根据 Apple AVPictureInPictureController documentation (2026),PiP 模式在 iOS 14 及以上版本和 Android 8.0 及以上版本中受支持。
关键要点
画中画 (PiP) 是一种在小型浮动窗口中显示视频的模式,该窗口保持在所有其他窗口和应用程序上方。用户可以在屏幕上移动 PiP 窗口,更改其大小(在某些平台上),并在其他应用程序中工作时继续观看内容。
PiP 的概念来自电视:早在 20 世纪 90 年代,电视就能在屏幕角落显示第二个频道。在移动设备中,PiP 首次出现在 iPad 的 iOS 9(2015 年)中,用于 Safari 中的视频,而完整的系统级 PiP 在 iOS 14(2020 年)中面向应用程序开放。在 Android 上,PiP 支持更早出现——在 8.0 Oreo(2017 年)版本中,但仅适用于视频,从 Android 12 开始适用于所有类型的内容。
PiP 与后台播放的不同之处在于视频仍显示在屏幕上,而不仅仅在音频流中播放。后台播放(background audio)在两个平台上都可用,但 PiP 为用户提供了对内容的视觉控制:可以看到画面,可以暂停、后退或关闭窗口。这对于视频课程、直播和视频通话尤其重要,因为视觉内容与音频同样重要。
从架构上看,PiP 通过系统窗口管理器实现,该管理器创建一个具有较低显示优先级的独立窗口。应用程序将视频输出委托给系统服务,即使应用程序进入后台模式或最小化后,该服务仍继续渲染视频。
当用户最小化带有活动视频的应用程序或按下 PiP 按钮(在 iOS 上)或系统自动将 Activity 切换到 PiP 模式(在 Android 上)时,过程开始。系统窗口管理器捕获视频流并创建一个具有固定比例的浮动窗口。窗口大小取决于原始视频的宽高比和平台限制:在 iOS 上,PiP 窗口约占屏幕宽度的 1/6–1/4,在 Android 上——对于移动设备至少 108 dp 宽和 240 dp 高。
当 PiP 窗口处于活动状态时,应用程序可以处于三种状态之一:后台(最小化)、活动状态(用户返回到应用程序)或等待状态(系统因资源不足而暂停了 PiP)。在切换到 PiP 时,应用程序应暂停不必要的 UI 操作(动画、界面渲染)并释放内存,因为在多任务模式下系统资源分配更严格。iOS 自动向应用程序发送 AVPictureInPictureControllerWillStartNotification 通知,Android 则发送 onPictureInPictureModeChanged 回调。
PiP 窗口有显著限制:无法在其上显示标准的 UI 控制元素(暂停按钮、进度滑块)——只有带有基本元素的最小系统覆盖层:播放/暂停、关闭、展开到全屏。iOS 上的 PiP 系统 UI 包括暂停和关闭按钮,Android 上则是相同元素加上一个额外的设置按钮。与 PiP 内部内容的交互(快退、选择字幕)是不可能的——为此需要将应用程序打开到全屏。
在 iOS 上,PiP 通过 AVKit 框架和 AVPictureInPictureController 类实现。此 API 在 iOS 14+ 上适用于 iPhone 和 iPad,但要求不同:在 iPad 上,PiP 通过 AVPlayerLayer 工作,在 iPhone 上——仅通过 AVPlayerViewController。
要使 PiP 在 iOS 上工作,必须满足几个条件:应用程序必须使用 AVPlayer 进行视频播放,音频会话必须配置为 .playback 或 .playAndRecord 类别,并且应用程序必须具有后台音频权限(UIBackgroundModes = audio)。没有这些设置,PiP 将不会启动——系统将拒绝创建 PiP 会话的请求,因为它无法保证在切换到后台后正确播放。
在 iOS 上,如果视频正在主动播放且用户未在设置中禁用此功能,则在最小化应用程序时会自动显示 PiP 窗口。用户也可以通过 AVPlayerViewController 中的按钮手动将视频最小化到 PiP。iOS 上 PiP 窗口的大小是固定的,由系统确定——开发人员无法更改。PiP 窗口的宽高比与原始视频的宽高比一致,但最大大小限制为 iPhone 屏幕宽度的 1/4 和 iPad 的 1/3。
iOS 上 PiP 的主要限制:无法在 PiP 窗口中自定义 UI,同时限于一个 PiP 流,以及 PiP 工作需要活动的 AVPlayer。多 PiP——同时播放多个 PiP 窗口——在 iOS 上不支持。当尝试启动第二个 PiP 时,第一个会自动关闭。这是硬件限制:由于 DMA 和视频内存的限制,视频处理器无法同时服务两个独立的 PiP 通道。
另一个重要限制——后台播放时长。如果用户不与 PiP 窗口交互,系统可能会在一段时间后暂停播放以节省能源。iOS 上 PiP 的自动暂停在 10–15 分钟不活动后发生,如果应用程序没有通过后台任务实现 keep-alive 机制。对于视频通话和直播,建议使用 PushKit 和 VoIP 证书,它们可以绕过此限制。
在 Android 上,PiP 实现为内置的 Activity 模式,通过 enterPictureInPictureMode 方法激活。从 Android 8.0(API 26)开始,任何 Activity 都可以切换到 PiP 模式,从 Android 12(API 31)开始,SurfaceView 和 TextureView 的 PiP 支持出现,无需使用 MediaCodec。
为了在 Android 清单中支持 PiP,需要在
在 Android 中,PiP 窗口默认没有控制元素。开发人员可以通过 setPictureInPictureParams 方法中的 RemoteAction 添加自定义操作。最多可设置 3 个操作(例如:暂停/播放、快退/快进、关闭)。每个操作在 PiP 系统覆盖层中显示为图标。与 iOS 中所有 UI 元素严格固定不同,Android 为基础控件提供了更大的灵活性。
Android 上的 PiP 根据操作系统版本具有不同的功能。在 Android 8.0–8.1 上,PiP 仅适用于通过 MediaPlayer 或 MediaCodec 配合 SurfaceView 播放的视频。从 Android 9 开始,可以使用 PictureInPictureArgs.Builder 配置 PiP 窗口的宽高比。Android 12 添加了对自定义 SurfaceView 和 TextureView 的 PiP 支持,以及全屏和 PiP 模式之间过渡的改进管理。Android 13+ 允许 PiP 窗口即使在锁定屏幕上也能显示,如果应用程序具有相应的权限。
| Android 版本 | PiP 功能 | API |
|---|---|---|
| 8.0–8.1 | MediaPlayer/MediaCodec 的基本 PiP | 26–27 |
| 9–11 | 宽高比配置,自定义操作 | 28–30 |
| 12 | PiP 中的 SurfaceView/TextureView 支持 | 31 |
| 13+ | 锁定屏幕上的 PiP,改进的动画 | 33+ |
Android PiP 与 iOS 的关键区别——多 PiP 能力。从 Android 12 开始,如果应用程序支持且设备性能允许,系统可以同时显示多个 PiP 窗口。然而在实践中,多 PiP 受 SoC 能力限制:由于解码器的硬件限制,大多数设备仅支持一个 PiP 窗口,因为每个 PiP 窗口需要自己的视频流和独立的解码会话。
让我们看看考虑到最新 API 变化的两个移动平台上的 PiP 实际实现。
import AVKit
import AVFoundation
class VideoPlayerViewController: UIViewController {
var player: AVPlayer!
var pipController: AVPictureInPictureController?
override func viewDidLoad() {
super.viewDidLoad()
let playerLayer = AVPlayerLayer(player: player)
playerLayer.videoGravity = .resizeAspect
view.layer.addSublayer(playerLayer)
guard AVPictureInPictureController.isPictureInPictureSupported()
else { return }
pipController = AVPictureInPictureController(playerLayer: playerLayer)
pipController?.delegate = self
}
@IBAction func startPiPTapped() {
pipController?.startPictureInPicture()
}
}
extension VideoPlayerViewController: AVPictureInPictureControllerDelegate {
func pictureInPictureControllerWillStart(
_ pictureInPictureController: AVPictureInPictureController
) {
// 隐藏 UI 元素,释放内存
}
func pictureInPictureControllerDidStop(
_ pictureInPictureController: AVPictureInPictureController
) {
// 恢复 UI,继续渲染
}
}
在示例中,AVPictureInPictureController 在检查 isPictureInPictureSupported 后使用 playerLayer 初始化(PiP 在第一代 iPhone SE 和一些内存不足的 iPad 上不受支持)。委托通知应用程序 PiP 的开始和结束——在这些回调中需要隐藏和恢复 UI 元素,因为在 PiP 模式下应用程序界面不可见。在切换到 PiP 时,建议停止所有动画,隐藏播放器控件并释放未使用的内存,以防止系统强制卸载应用程序。
class PipVideoActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupVideoPlayer()
}
private fun enterPipMode() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val aspectRatio = Rational(16, 9)
val pipParams = PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
.setAutoEnterEnabled(true)
.build()
enterPictureInPictureMode(pipParams)
}
}
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
) {
if (isInPictureInPictureMode) {
// 隐藏 UI,仅专注于媒体
binding.controlsGroup.visibility = View.GONE
} else {
// 恢复 UI
binding.controlsGroup.visibility = View.VISIBLE
}
}
}
Kotlin 中的示例使用 PictureInPictureParams.Builder 配置 PiP。setAspectRatio 方法设置 PiP 窗口的宽高比(典型视频为 16:9)。setAutoEnterEnabled(true) 启用应用程序最小化时自动切换到 PiP。onPictureInPictureModeChanged 回调在进入和退出 PiP 时调用——在其中需要隐藏或显示 UI 元素。对于 SurfaceView 上的视频,需要通过添加 android:configChanges="screenSize|smallestScreenSize" 到清单来额外处理配置,以防止在切换到 PiP 模式时重新创建 Activity。
PiP 是一个强大的工具,用于改善内容在切换到其他任务时仍然相关的应用程序中的用户体验。然而,PiP 的实现应该有充分的理由,并且不能分散用户注意力。
视频通话和会议——PiP 的主要场景之一。在 Zoom、FaceTime、Google Meet 中,PiP 允许在其他应用程序中工作时看到对话者:阅读笔记、查看演示文稿或检查邮件。用于视频通话的 PiP 需要后台摄像头支持和正确配置音频会话以在后台继续捕获音频。在 iOS 上,为此使用 AVSampleBufferDisplayLayer 代替 AVPlayerLayer,因为视频通话不使用 AVPlayer。
流媒体服务(YouTube、Netflix、Twitch)积极使用 PiP 在搜索新内容时继续观看。YouTube Premium 将 PiP 作为付费功能提供,Netflix 也因内容许可限制将 PiP 限制在特定套餐计划中。要在流媒体应用程序中实现 PiP,需要与支持 PiP 模式下安全管道的 DRM 系统(FairPlay、Widevine)集成。
PiP 不适合需要用户交互的交互式视频内容的应用程序:播放器内带有测试的教育平台、带有聊天的游戏直播、视频中带有产品链接的购物应用程序。在这些情况下,PiP 窗口太小而无法显示附加信息,并且 PiP 中的交互元素不受系统支持。建议仅在不需要与内容交互的被动观看时使用 PiP。
对于音乐和播客应用程序,PiP 是多余的——没有视觉窗口的后台音频就足够了。PiP 消耗额外的 GPU 资源来在浮动窗口中渲染视频,这会缩短电池寿命。如果内容为音频(音乐、播客、有声书)——使用不带 PiP 的后台播放。如果是视觉内容——实现 PiP 以改善用户体验。
常见问题
iOS 上的 PiP 需要 iPhone 6s+、iOS 14+ 和支持的地区(美国、加拿大、澳大利亚、欧盟、俄罗斯等)。对于应用程序,需要将 audio session 配置为 .playback 类别并添加 UIBackgroundModes = audio。还要检查设置:设置 > 通用 > 画中画。
在 iOS 上,PiP 窗口的大小完全由系统决定,开发人员无法调整。在 Android 上,只能通过 PictureInPictureParams.Builder 中的 setAspectRatio 设置宽高比,但窗口的确切大小由系统决定。用户可以在 Android 12+ 上通过 pinch-to-zoom 手势更改 PiP 窗口的大小。
是的,PiP 适用于受 DRM 保护的内容(iOS 上的 FairPlay,Android 上的 Widevine L1),前提是 DRM 会话支持 PiP 模式下的安全管道。Widevine L3 可能不支持 PiP,因为它不能保证浮动窗口中解码内容的安全。在测试阶段检查 DRM 与 PiP 的兼容性。
在 iOS 上——只有一个 PiP 窗口。在 Android 12+ 上,理论上支持多 PiP,但在实践中,由于硬件限制,大多数设备限于一个窗口。顶级设备(Samsung Galaxy S24、Pixel 8)可能支持 2 个 PiP 窗口,但性能会降低。
是的,生命周期处理至关重要。在 iOS 上,切换到 PiP 时,应用程序会收到 willStart 通知,其中需要隐藏 UI 并释放内存。在 Android 上,onPictureInPictureModeChanged 在进入/退出 PiP 时调用。如果生命周期处理不当,系统可能会从内存中卸载应用程序,从而中断播放。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。