以下是一篇关于在 iOS 中实现画中画(PiP)功能的技术博客:
iOS 画中画(PiP)功能实现指南
效果

简介
画中画(Picture in Picture, PiP)是一项允许用户在使用其他应用时继续观看视频内容的功能。本文将详细介绍如何在 iOS 应用中实现 PiP 功能,包括自定义内容渲染和控制系统控件的显示。
项目地址
系统要求
- iOS 15.0 及以上版本
- AVKit 框架
核心组件
实现 PiP 功能主要涉及以下几个核心组件:
AVPictureInPictureController
- 负责管理 PiP 会话AVPictureInPictureControllerContentSource
- 定义 PiP 内容源AVSampleBufferDisplayLayer
- 用于显示视频内容的图层AVPictureInPictureSampleBufferPlaybackDelegate
- 处理视频播放相关的回调
实现步骤
1. 检查设备支持
首先需要检查设备是否支持 PiP 功能:
objc
- (BOOL)isSupported {
if (@available(iOS 15.0, *)) {
return [AVPictureInPictureController isPictureInPictureSupported];
}
return NO;
}
2. 创建 PiP 视图
需要创建一个自定义视图来显示 PiP 内容:
objc
@interface PipView : UIView
@property(nonatomic, strong) AVSampleBufferDisplayLayer *sampleBufferDisplayLayer;
@end
@implementation PipView
+ (Class)layerClass {
return [AVSampleBufferDisplayLayer class];
}
- (instancetype)init {
self = [super init];
if (self) {
_sampleBufferDisplayLayer = (AVSampleBufferDisplayLayer *)self.layer;
}
return self;
}
@end
3. 配置 PiP 控制器
设置 PiP 控制器需要以下步骤:
objc
- (BOOL)setup:(PipOptions *)options {
if (!self.isSupported) {
return NO;
}
if (@available(iOS 15.0, *)) {
// 创建 PiP 视图
_pipView = [[PipView alloc] init];
_pipView.translatesAutoresizingMaskIntoConstraints = NO;
// 设置内容视图
_contentView = (UIView *)options.contentView;
// 创建内容源
AVPictureInPictureControllerContentSource *contentSource =
[[AVPictureInPictureControllerContentSource alloc]
initWithSampleBufferDisplayLayer:_pipView.sampleBufferDisplayLayer
playbackDelegate:self];
// 初始化 PiP 控制器
_pipController = [[AVPictureInPictureController alloc]
initWithContentSource:contentSource];
_pipController.delegate = self;
_pipController.canStartPictureInPictureAutomaticallyFromInline =
options.autoEnterEnabled;
// 设置控制样式
_pipController.requiresLinearPlayback = options.controlStyle > 0;
return YES;
}
return NO;
}
4. 实现播放代理
通过实现 AVPictureInPictureSampleBufferPlaybackDelegate
协议来处理视频播放:
objc
- (void)pictureInPictureController:
(nonnull AVPictureInPictureController *)pictureInPictureController
didTransitionToRenderSize:(CMVideoDimensions)newRenderSize {
// 处理渲染尺寸变化
}
- (void)pictureInPictureController:
(nonnull AVPictureInPictureController *)pictureInPictureController
setPlaying:(BOOL)playing {
// 处理播放状态变化
}
- (BOOL)pictureInPictureControllerIsPlaybackPaused:
(nonnull AVPictureInPictureController *)pictureInPictureController {
return NO;
}
- (CMTimeRange)pictureInPictureControllerTimeRangeForPlayback:
(nonnull AVPictureInPictureController *)pictureInPictureController {
return CMTimeRangeMake(kCMTimeZero, kCMTimePositiveInfinity);
}
5. 控制 PiP 会话
启动 PiP:
objc
- (BOOL)start {
if (!self.isSupported) {
return NO;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 0.1),
dispatch_get_main_queue(), ^{
if (self->_pipController.isPictureInPicturePossible) {
[self->_pipController startPictureInPicture];
}
});
return YES;
}
停止 PiP:
objc
- (void)stop {
if (self->_pipController.isPictureInPictureActive) {
[self->_pipController stopPictureInPicture];
}
}
释放资源:
objc
- (void)dispose {
if (@available(iOS 15.0, *)) {
self->_pipController.contentSource = nil;
}
if (self->_isPipActived) {
self->_isPipActived = NO;
[self->_pipStateDelegate pipStateChanged:PipStateStopped error:nil];
}
}
控制样式
PiP 窗口支持四种不同的控制样式:
- Style 0: 显示所有系统控件(默认)
- Style 1: 隐藏前进和后退按钮
- Style 2: 隐藏播放/暂停按钮和进度条(推荐)
- Style 3: 隐藏所有系统控件,包括关闭和恢复按钮
自定义内容渲染
- 通过
contentView
参数指定要在 PiP 窗口中显示的自定义视图 - 在
pictureInPictureControllerDidStartPictureInPicture
回调中将自定义视图添加到 PiP 窗口 - 使用
AVSampleBufferDisplayLayer
来显示视频内容 - 通过
pictureInPictureControllerTimeRangeForPlayback
返回正确的时间范围来避免加载指示器
注意事项
- PiP 功能仅支持 iOS 15.0 及以上版本
- 启动 PiP 时需要适当延迟以确保正常显示
- 自动进入 PiP 模式需要在 setup 时配置
autoEnterEnabled
选项 - 释放资源时建议使用
contentSource = nil
而不是直接调用stopPictureInPicture
- PiP 窗口的默认大小建议设置为至少 100x100,否则可能导致启动失败
- 使用
kCMTimePositiveInfinity
作为时间范围的 duration 可以避免加载指示器 - 自定义内容视图需要正确处理布局约束
最佳实践
- 在初始化时检查设备是否支持 PiP 功能
- 实现适当的错误处理和状态回调
- 在应用进入后台时,如果启用了自动进入选项,PiP 会自动启动
- 注意内存管理,及时释放不需要的资源
- 根据需求选择合适的控制样式
- 确保自定义内容视图的渲染性能
总结
iOS 的 PiP 功能实现主要依赖于 AVKit 框架,通过合理配置 AVPictureInPictureController
及其相关组件,可以为用户提供流畅的画中画体验。在实现过程中需要注意版本兼容性、状态管理和资源释放等问题。同时,通过自定义内容渲染和控制系统控件的显示,可以提供更好的用户体验。
参考
PS
这个文档我偷懒了,让cursor自己更新了下,主要是效果展示。另外有个坑点是,用了两个没有公开的接口,如果影响上架了,一定通知我,我也想知道会不会影响上架。