前语

iOS 自界说画中画
iOS 自界说画中画

Picture-In-Picture is coming to ‌iPhone‌ as well, the implementation looking similar to that currently available on the iPad.

画中画是在桌面以浮窗的方式播映视频的功用,从 iOS 14 开端支持。桌面这块宝地肯定是各个App 都眼馋的,就会呈现各种骚操作,本来是只能用来播映视频的,用来展示其他内容,比方某云的桌面歌词。老板看了说也想要,无法只能找找资料看看怎么做。(个人不推荐这种骚操作

以下是一些问题记录。

如何自界说

增加自界说 UIView

创立 AVPictureInPictureController 之后,体系会创立一个 PGHostedWindowwindowLevel 为 -10000000。所以可以经过 UIApplication 获取到这个 window,将自界说的 UIView 增加到这个 window 即可。

iOS 自界说画中画

增加 View 的时机

pictureInPictureControllerWillStartPictureInPicture 或许 pictureInPictureControllerDidStartPictureInPicture 署理办法回调时增加。

但 willStart 时,window 的实践巨细还不确认,更推荐在 didStart 时增加

获取 Window 的时机

在增加 view 的时分获取,即 pictureInPictureControllerWillStartPictureInPicture: 署理办法。

UIWindow *window = [UIApplication sharedApplication].windows.firstObject;

此办法有必定的危险,假如创立了多个 AVPictureInPictureController,对应的会存在多个 PGHostedWindow

可以监听 UIWindowDidBecomeVisibleNotification 告诉,在收到告诉后持有下这个 window 的引证。

- (void)windowDidBecomeVisible:(NSNotification *)notification {
    id object = notification.object;
    if ([object isKindOfClass:NSClassFromString(@"PGHostedWindow")]) {
        self.hostedWindow = notification.object;
        [[NSNotificationCenter defaultCenter] removeObserver:self
                                                        name:UIWindowDidBecomeVisibleNotification
                                                      object:nil];
    }
}

或许用 KVO 监听 isPictureInPicturePossible 特点

设置 contentSource (iOS 15 +)

将自界说 View 的 layerClass 改写成 AVSampleBufferDisplayerLayer

@implementation PIPCustomView
+ (Class)layerClass {
    return [AVSampleBufferDisplayLayer class];
}
@end

再经过 initWithSampleBufferDisplayLayer 创立 AVPictureInPictureControllerContentSource

PIPCustomView *customView = [[PIPCustomView alloc] init];
AVSampleBufferDisplayLayer *layer = (AVSampleBufferDisplayLayer *)customView.layer;
AVPictureInPictureControllerContentSource *source = [[AVPictureInPictureControllerContentSource alloc] initWithSampleBufferDisplayLayer:layer playbackDelegate:customView];
AVPictureInPictureController *controller = [[AVPictureInPictureController alloc] initWithContentSource:source];

再将 CALayer 转换成 CMSampleBuffer

CALayer -> UIImage -> CVPixelBufferRef -> CMSampleBufferRef

再经过 enqueueSampleBuffer 更新到画中画

直接交互

在自界说的 UIView 增加一个 UIButton,iOS 14、15 可正常履行点击回调办法,iOS 16 及以上则没有任何反响

去除播映相关按钮

[controller setValue:[NSNumber numberWithInt:1] forKey:@"controlsStyle"];

在 iOS 16 及以上版别支持点击画中画直接回到 App

[controller setValue:[NSNumber numberWithInt:2] forKey:@"controlsStyle"];

操控画中画窗口巨细

无法直接操控画中画窗口的巨细,只能操控窗口的份额,体系会根据所播映视频的份额主动适配到相应的巨细。并且在同一个视频在不同的机型,实践显现巨细也不一致。

视频巨细 336 x 456

iPhone 8 窗口实践巨细 191 x 259.5

iPhone 15 窗口实践巨细 164.333 x 223

iOS 自界说画中画
iOS 自界说画中画

画中画开启时机

应用在前台✅

直接调用 startPictureInPicture 即可

App 切换到后台时主动开启✅

canStartPictureInPictureAutomaticallyFromInline 设置为 YES 后,可在 App 切换到后台时主动开启。但需要留意的是该特点只在 iOS 14.2 及以上可用,并且视频有必要是在播映中的。

应用在后台

假如想在后台收到某个告诉时翻开,体系是不允许的,会呈现如下过错

Error Domain=AVKitErrorDomain Code=-1001 “Failed to start picture in picture.” UserInfo={NSLocalizedDescription=Failed to start picture in picture., NSLocalizedFailureReason=The UIScene for the content source has an activation state other than UISceneActivationStateForegroundActive, which is not allowed.}

参考

Adopting Picture in Picture in a Custom Player | Apple Developer Documentation
Can I add custom view on AVPictureInPictureController?

Picture in Picture for any UIView

GitHub – CaiWanFeng/PiP: The best way to customize picture-in-picture for iOS.