背景
产品要求实现类似QQ音乐和网易音乐歌词的画中画悬浮窗显示。网上搜罗了一下相关资料,都没有完整的方案,于是自己进行了相关的调研。
效果
实现步骤
1. 项目开启画中画后台模式
2. 配置画中画相关参数和组件
- 首先我们需要知道,苹果的画中画功能本身就是给播放视频用的,将它用来做歌词显示,使用起来限制会较多,比如无法直接自定义窗口大小,有播放按钮遮挡等问题。但是可以通过取巧的方式实现。实现方式是在画中画最底层播放一个占位视频,顶层实现我们需要显示的交互。窗口大小由底层视频的尺寸比例决定(间接实现宽高调整)。
- 播放视频的尺寸有一定的要求,当视频宽度较小时或者当高宽比较大时,双击画中画会自动缩小/放大,而我们显示歌词时往往不希望缩放;自己多次验证过,当视频宽度2000,高度小于700较为合适。
- controlsStyle设置为1,屏蔽画中画的播放按钮。这个是私有属性。
- canStartPictureInPictureAutomaticallyFromInline = YES,表示后台时自动打开画中画。
- 后台切回前台时,画中画缩放的位置由playerLayer.frame决定。
- (void)preparePicInPicOnView:(UIView *)view {
/// 设置播放模式
NSError *error = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
[[AVAudioSession sharedInstance] setActive:YES error:&error];
/// 设置播放的视频
NSURL *url = [[NSBundle mainBundle] URLForResource:@"0920-2000400" withExtension:@"mp4"];
AVPlayerItem * item = [[AVPlayerItem alloc] initWithAsset:[AVAsset assetWithURL:url]];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:item];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame = self.screenView.bounds;
playerLayer.backgroundColor = [UIColor blackColor].CGColor;
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[view.layer insertSublayer:playerLayer atIndex:0];
/// 画中画功能
self.pipController = [[AVPictureInPictureController alloc] initWithPlayerLayer:playerLayer];
self.pipController.requiresLinearPlayback = YES;
[self.pipController setValue:@1 forKey:@"controlsStyle"];
self.pipController.delegate = self;
self.pipController.canStartPictureInPictureAutomaticallyFromInline = YES;
[player play];
/// 播放结束自动循环播放
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
/// 监听获取画中画Window
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeVisible:) name:UIWindowDidBecomeVisibleNotification object:nil];
}
3. 获取画中画顶层window
监听UIWindowDidBecomeVisibleNotification通知,获取画中画最顶层的Window,不要在pictureInPictureControllerWillStartPictureInPicture:代理中获取,很多时候会有两个window,导致获取的Window不正确!
- (void)windowDidBecomeVisible:(NSNotification *)notification {
id object = notification.object;
if ([object isKindOfClass:NSClassFromString(@"PGHostedWindow")]) {
self.firstWindow = notification.object;
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIWindowDidBecomeVisibleNotification
object:nil];
} else if ([object isKindOfClass:[UIWindow class]]) {
/// 不要在这里判断window的宽高,这里有可能window都还没渲染,如果还没渲染,则通过level,topwindow,遍历所有windows都不准确
UIWindow *targetWindow = (UIWindow *)object;
[self.suspectedWindows addObject:targetWindow];
}
}
/// 某些机型下,会画中画的Windows在windowDidBecomeVisible:通知监听下,是普通的UIWindow,不是PGHostedWindow,而且windowLevel也是正常的等级,不是-10000000。这种情况下需要通过Windows的高度去判断这个Windows是不是我们获取到的画中画Windows。(因为Windows高度是由播放视频的高度来决定的,建议将高度稍微放大点,这个Windows在pro max机型和普通机型下,高度会有变化)
- (UIWindow *)filterTargetWindow {
for (UIWindow *window in self.suspectedWindows) {
if ([window isKindOfClass:NSClassFromString(@"PGHostedWindow")]) {
return window;
}
}
for (UIWindow *window in self.suspectedWindows) {
if (window.windowLevel == -10000000) {
return window;
}
}
for (UIWindow *window in self.suspectedWindows) {
//这里只是根据视频源来大致判断高度,如果视频源更换了高度较大的,要更改这个高度
if (window.frame.size.height < 300) {
return window;
}
}
return self.suspectedWindows.firstObject;
}
4. 监听代理
启动画中画时,将显示的容器放到画中画的Window中。
- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
}
- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
if (!self.firstWindow) {
self.firstWindow = [self filterTargetWindow];
[self.suspectedWindows removeAllObjects];
}
if (self.firstWindow) {
[self.firstWindow addSubview:self.picInPicView];
[self.picInPicView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.firstWindow);
}];
}
}
总结
此次调研,主要是解决的问题点是:
- 画中画的尺寸如何自定义:苹果没有接口直接调整大小,但可以通过播放的视频尺寸来改变大小,即当你画中画播放2000*500的视频时,尺寸就和歌词显示尺寸差不多。
- 画中画窗口如何禁用双击缩放手势:当视频尺寸宽度较大,且高度较小时,系统会自动屏蔽双击缩放手势。较合适的尺寸为宽度2000,高度小于700。
- 画中画中如何屏蔽原生播放按钮:controlsStyle属性设置为1。
- 切后台如何自动唤起画中画:canStartPictureInPictureAutomaticallyFromInline属性设置为YES,并将视频一直循环播放,切后台就会自动唤起画中画。另外需要注意,视频播放的layer的frame一定要在可视区域,当不在可是区域时,也无法唤起画中画。
demo链接:github.com/yuchern/Pic…