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 之后,系统会创建一个 PGHostedWindow,windowLevel 为 -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.

相关推荐
子兮曰4 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰4 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万4 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝4 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋4 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁4 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95274 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大4 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师4 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学4 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端