1.开启后台模式
-
通过capabilities添加
-
通过
info.plist代码添加
<!-- Info.plist 添加后台模式权限 -->
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>picture-in-picture</string>
</array>
2.导入框架
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
3. 代码实现
使用系统播放器 AVPlayerViewController
//
// ViewController.m
// VideoInVideo
//
// Created by 彭彬峰 on 2025/2/26.
//
#import "ViewController.h"
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
// 定义播放器及画中画控制器
@interface ViewController ()
@property (nonatomic, strong) AVPlayerViewController *playerVC;
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) AVPlayerLayer *playerLayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setupPlayer];
}
#pragma mark - 初始化播放器
- (void)setupPlayer {
// 1. 创建播放器
// 视频 URL
// NSURL *videoURL = [NSURL URLWithString:@"https://www.example.com/path/to/your/video.mp4"]; // 请提供有效的视频 URL
// 获取本地视频 URL
NSURL *videoURL = [[NSBundle mainBundle] URLForResource:@"747a499aaea1ecb890c14912bdf864cf" withExtension:@"MP4"];
// 创建 AVPlayer
self.player = [AVPlayer playerWithURL:videoURL];
// 2. 配置播放器视图
self.playerVC = [[AVPlayerViewController alloc] init];
self.playerVC.player = self.player;
self.playerVC.allowsPictureInPicturePlayback = YES; // 启用画中画
self.playerVC.requiresLinearPlayback = NO; // 允许后台播放
self.playerVC.canStartPictureInPictureAutomaticallyFromInline = YES;
self.playerVC.view.frame = CGRectMake(0, 200, self.view.bounds.size.width, 400);
[self.view addSubview:self.playerVC.view];
if ([AVPictureInPictureController isPictureInPictureSupported]) {
NSLog(@"该设备支持画中画功能");
//开启画中画后台声音权限
NSError *error = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
if (error) {
NSLog(@"请求权限失败的原因为%@",error);
}
} else {
NSLog(@"该设备不支持画中画功能");
}
// 启动播放
[self.player play];
}
@end
使用 AVPictureInPictureController
//1.判断是否支持画中画功能
if ([AVPictureInPictureController isPictureInPictureSupported]) {
//2.开启权限
@try {
NSError *error = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionOrientationBack error:&error];
[[AVAudioSession sharedInstance] setActive:YES error:&error];
} @catch (NSException *exception) {
NSLog(@"AVAudioSession发生错误");
}
self.pipVC = [[AVPictureInPictureController alloc] initWithPlayerLayer:self.player];
self.pipVC.delegate = self;
}
// 开启或关闭画中画
if (self.pipVC.isPictureInPictureActive) {
[self.pipVC stopPictureInPicture];
} else {
[self.pipVC startPictureInPicture];
}
代理 AVPictureInPictureControllerDelegate
// 即将开启画中画
- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController;
// 已经开启画中画
- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController;
// 开启画中画失败
- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error;
// 即将关闭画中画
- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController;
// 已经关闭画中画
- (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController;
// 关闭画中画且恢复播放界面
- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL restored))completionHandler;
关闭画中画会执行 pictureInPictureController:restoreUserInterfaceForPictureInPictureStopWithCompletionHandler: 这个代理方法,用来恢复播放界面的
代码实现:
//
// ViewController.m
// VideoInVideo
//
// Created by 彭彬峰 on 2025/2/26.
//
#import "ViewController.h"
#import <AVKit/AVKit.h>
#import <AVFoundation/AVFoundation.h>
// 定义播放器及画中画控制器
@interface ViewController ()<AVPictureInPictureControllerDelegate>
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) AVPlayerLayer *playerLayer;
@property (nonatomic, strong) AVPictureInPictureController *pipController;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setupPlayer];
// 注册进入后台的通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
// 注册即将进入前台的通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
#pragma mark - 初始化播放器
- (void)setupPlayer {
// 1. 创建播放器
// 视频 URL
// NSURL *videoURL = [NSURL URLWithString:@"https://www.example.com/path/to/your/video.mp4"]; // 请提供有效的视频 URL
// 获取本地视频 URL
NSURL *videoURL = [[NSBundle mainBundle] URLForResource:@"747a499aaea1ecb890c14912bdf864cf" withExtension:@"MP4"];
// 创建 AVPlayer
self.player = [AVPlayer playerWithURL:videoURL];
// 2. 创建 AVPlayerLayer
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.frame = CGRectMake(0, 200, self.view.bounds.size.width, 400);
[self.view.layer addSublayer:self.playerLayer];
// 检查可用性并初始化 AVPictureInPictureController
if ([AVPictureInPictureController isPictureInPictureSupported]) {
if ([AVPictureInPictureController isPictureInPictureSupported]) {
NSLog(@"该设备支持画中画功能");
//开启画中画后台声音权限
NSError *error = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
if (error) {
NSLog(@"请求权限失败的原因为%@",error);
}
} else {
NSLog(@"该设备不支持画中画功能");
}
// 3. 初始化画中画控制器
if (@available(iOS 15.0, *)) {
// 初始化 AVPictureInPictureControllerContentSource
AVPictureInPictureControllerContentSource *contentSource = [[AVPictureInPictureControllerContentSource alloc] initWithPlayerLayer:self.playerLayer];
// 初始化 AVPictureInPictureController
self.pipController = [[AVPictureInPictureController alloc] initWithContentSource:contentSource];
} else {
// iOS 14 及以下版本的初始化(直接用 playerLayer)
self.pipController = [[AVPictureInPictureController alloc] initWithPlayerLayer:self.playerLayer];
}
// 设置代理(可选)
self.pipController.delegate = self;
self.pipController.requiresLinearPlayback = YES;
self.pipController.canStartPictureInPictureAutomaticallyFromInline = YES;
//在点击画中画按钮的时候 开启画中画
[self togglePiPMode];
// 启动播放
[self.player play];
} else {
NSLog(@"Picture in Picture is not supported on this device.");
}
}
#pragma mark - 画中画开关
- (void)togglePiPMode {
if (![AVPictureInPictureController isPictureInPictureSupported]) {
// [self showAlert:@"设备不支持画中画"];
NSLog(@"设备不支持画中画");
return;
}
if (self.pipController.isPictureInPictureActive) {
[self.pipController stopPictureInPicture];
} else {
[self.pipController startPictureInPicture];
}
}
#pragma mark - PiP代理方法
// 即将开启画中画
- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController;{
NSLog(@"pictureInPictureControllerWillStartPictureInPicture");
}
// 已经开启画中画
- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController;{
NSLog(@"pictureInPictureControllerDidStartPictureInPicture");
}
// 开启画中画失败
- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error;{
NSLog(@"failedToStartPictureInPictureWithError");
}
// 即将关闭画中画
- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController;{
NSLog(@"pictureInPictureControllerWillStopPictureInPicture");
}
// 已经关闭画中画
- (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController;{
NSLog(@"pictureInPictureControllerDidStopPictureInPicture");
}
// 关闭画中画且恢复播放界面
- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL restored))completionHandler;{
NSLog(@"restoreUserInterfaceForPictureInPictureStopWithCompletionHandler");
}
#pragma mark - 进入前台和后台
// 处理进入后台的逻辑
- (void)applicationDidEnterBackground:(NSNotification *)notification {
NSLog(@"App did enter background");
// 在这里加入你希望在应用进入后台时执行的代码
// [self togglePiPMode];
}
// 处理即将进入前台的逻辑
- (void)applicationWillEnterForeground:(NSNotification *)notification {
NSLog(@"App will enter foreground");
// 在这里加入你希望在应用即将进入前台时执行的代码
// [self togglePiPMode];
}
// 移除通知监听
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
@end
全局画中画注意点
- 通过一个全局变量持有画中画控制器,可以在pictureInPictureControllerWillStartPictureInPicture持有,pictureInPictureControllerDidStopPictureInPicture释放;
- 有可能不是点画中画按钮,而是从其它途径来打开当前画中画控制器,可以在viewWillAppear 进行判断并关闭;
- 已有画中画的情况下开启新的画中画,需要等完全关闭完再开启新的,防止有未知的错误出现,因为关闭画中画是有过程的;
- 如果创建AVPictureInPictureController并同时开启画中画功能,有可能会失效,出现这种情况延迟开启画中画功能即可。
- 画中画中如何屏蔽原生播放按钮:controlsStyle属性设置为1。
- 切后台如何自动唤起画中画:canStartPictureInPictureAutomaticallyFromInline属性设置为YES,并将视频一直循环播放,切后台就会自动唤起画中画。另外需要注意,视频播放的layer的frame一定要在可视区域,当不在可是区域时,也无法唤起画中画。