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 即可。

添加 View 的时机

pictureInPictureControllerWillStartPictureInPicture 或者 pictureInPictureControllerDidStartPictureInPicture 代理方法回调时添加。

但 willStart 时,window 的实际大小还不确定,更推荐在 didStart 时添加

获取 Window 的时机

在添加 view 的时候获取,即 pictureInPictureControllerWillStartPictureInPicture: 代理方法。

ini 复制代码
UIWindow *window = [UIApplication sharedApplication].windows.firstObject;

此方法有一定的风险,如果创建了多个 AVPictureInPictureController,对应的会存在多个 PGHostedWindow

可以监听 UIWindowDidBecomeVisibleNotification 通知,在收到通知后持有下这个 window 的引用。

ini 复制代码
- (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

objectivec 复制代码
@implementation PIPCustomView

+ (Class)layerClass {
    return [AVSampleBufferDisplayLayer class];
}

@end

再通过 initWithSampleBufferDisplayLayer 创建 AVPictureInPictureControllerContentSource

ini 复制代码
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 及以上则没有任何反应

去除播放相关按钮

css 复制代码
[controller setValue:[NSNumber numberWithInt:1] forKey:@"controlsStyle"];

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

css 复制代码
[controller setValue:[NSNumber numberWithInt:2] forKey:@"controlsStyle"];

控制画中画窗口大小

无法直接控制画中画窗口的大小,只能控制窗口的比例,系统会根据所播放视频的比例自动适配到相应的大小。并且在同一个视频在不同的机型,实际显示大小也不一致。

视频大小 336 x 456

iPhone 8 窗口实际大小 191 x 259.5

iPhone 15 窗口实际大小 164.333 x 223

画中画开启时机

应用在前台✅

直接调用 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.

相关推荐
桂月二二24 分钟前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
胖虎11 小时前
实现 iOS 自定义高斯模糊文字效果的 UILabel(文末有Demo)
ios·高斯模糊文字·模糊文字
hunter2062062 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb2 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角2 小时前
CSS 颜色
前端·css
浪浪山小白兔3 小时前
HTML5 新表单属性详解
前端·html·html5
lee5763 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579653 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me4 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者4 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架