ios swift画中画技术尝试

继上篇:iOS swift 后台运行应用尝试失败-CSDN博客

为什么想到画中画,起初是看到后台模式里有一个picture in picture,去了解了后发现这个就是小窗口视频播放,方便用户执行多任务。看小窗口视频的同时,可以作其他的事情。

画中画功能在20世纪80年代开始在电视机中应用,使得用户可以在一个屏幕上同时观看两个频道的内容。‌

这个技术在安卓里已经非常普遍了。各种视频内容网站都有类似功能。

而苹果支持画中画是在ios14已经开始支持。目前在使用的大多数机型,比如iphone 8p,升级系统后可到ios16.7,都能支持画中画技术。

后台任务苹果管理太严格,不好搞,那么使用画中画这种技术,直接做成多任务,这样也是另一条可以尝试的路径。

1、标准PIP使用

首先,标准写法是采用AVPlayer,输入url需要是MP4等视频文件,如果是加密后的网址,无法播放,比如b站的网址。

创建AVPlayer,使用AVPlayerLayer来初始化AVPictureInPictrueController。

Swift 复制代码
        // 创建 AVPlayer 对象
        let videoURL = URL(string: "https://media.w3.org/2010/05/sintel/trailer.mp4")!
        player = AVPlayer(url: videoURL)
        
        // 创建 AVPlayerLayer 并添加到视图层上
        playerLayer = AVPlayerLayer(player: player)
        playerLayer.frame = view.bounds
        view.layer.addSublayer(playerLayer)
        
        // 设置画中画控制器
        pipController = AVPictureInPictureController(playerLayer: playerLayer)
        pipController.delegate = self

        player.play()   // 直接播放

按home键退出,就会自动启动小窗口继续播放视频。

这里有一个坑,需要初始化音频,否则播放mp4无声音,且画中画也不会触发。

Swift 复制代码
    do {
        // 设置AVAudioSession为后台模式
        try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
        try AVAudioSession.sharedInstance().setActive(true)
    } catch {
        print("无法设置AVAudioSession: \(error)")
    }

视频画中画,iphone 8p_哔哩哔哩_bilibili

2、摄像头预览

摄像头捕捉并预览,这个算法也很容易找到,使用AVCaptureSession

Swift 复制代码
func setupCamera(forgroundFlag: Bool, view: UIView) {
        farView = view
        flag = forgroundFlag
        captureSession = AVCaptureSession()
        captureSession?.beginConfiguration()
        captureSession?.sessionPreset = .high
        
        guard let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) else {
            print("No front camera available")
            return
        }
        
        guard let input = try? AVCaptureDeviceInput(device: captureDevice) else {
            print("Unable to access front camera")
            return
        }
        
        captureSession!.addInput(input)
        
        videoOutput = AVCaptureVideoDataOutput()
        videoOutput?.automaticallyConfiguresOutputBufferDimensions = true
        videoOutput!.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
        captureSession!.addOutput(videoOutput!)
        
        if (forgroundFlag) {
            videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
            guard let preLayer = videoPreviewLayer else { return }
            preLayer.frame = view.frame
            view.layer.addSublayer(preLayer)
            
            // 设置 UILabel 的属性
            label = UILabel()
            label!.text = "Hello, Swift!" // 设置文本内容
            label!.textColor = UIColor.systemBlue // 设置文本颜色
            label!.font = UIFont.systemFont(ofSize: 20) // 设置字体和大小
            label!.textAlignment = .left // 设置文本对齐方式
            label!.numberOfLines = 0 // 设置行数,0表示自动换行
            
            // 设置 UILabel 的位置和大小
            label!.frame = CGRect(x: 8, y: 20, width: 200, height: 30)
        }
        captureSession?.commitConfiguration()
    }

AVCaptureSession输出的是AVCaptureVideoPreviewLayer,这个layer无法直接用来初始化AVPictureInPictrueController。

3、摄像头输出PIP

swift代码在标准案例上实现都很简洁,但要自定义实现一些功能时,就会发现材料很难找。

比如怎样把摄像头预览与PIP结合。

深度搜索AI给出了一个结果,看上去好像可以,实践下来编辑都不能通过。但是它给出了一个提示,就是AVSampleBufferDisplayLayer。

这里吐槽下AI,最喜欢把不可用的东西包装成很好看的样子,在技术搜索方面,某些时候还不如原始的搜索引擎来得方便。

搜索AVCaptureVideoPreviewLayer转为AVSampleBufferDisplayLayer,也有方法,但是算法看上去稍显复杂。

官方文档Adopting Picture in Picture in a Custom Player | Apple Developer Documentation里也提到了可以使用AVSampleBufferDisplayLayer来初始化PIPController。

网上另一篇文章,说在捕获处理接口里,还有一个视频数据

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection)

CMSampleBuffer可以直接转为AVSampleBufferDisplayLayer,使用enqueu接口,如下:

Swift 复制代码
    func setupSampleBufferDisplayLayer() {
        sampleBufferDisplayLayer = AVSampleBufferDisplayLayer()
        sampleBufferDisplayLayer.frame = view.bounds
        sampleBufferDisplayLayer.videoGravity = .resizeAspect
        view.layer.addSublayer(sampleBufferDisplayLayer)
    }


    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
。。。。。。        
        if (sampleBufferDisplayLayer.status == AVQueuedSampleBufferRenderingStatus.failed) {
            sampleBufferDisplayLayer.flush() // 异常处理
        }
        sampleBufferDisplayLayer.enqueue(sampleBuffer)
。。。。。。
        

这样整个流程就能打通了。

通过AVSampleBufferDisplayLayer,可以用任何视频流来初始化AVPictureInPictureController。

Swift 复制代码
    private func setupPiPController() {
        guard AVPictureInPictureController.isPictureInPictureSupported() else {
            print("Picture in Picture is not supported on this device")
            return
        }
        print("support pip!!!")
        
        if pipController == nil {
            pipController = AVPictureInPictureController(contentSource: .init(sampleBufferDisplayLayer: sampleBufferDisplayLayer, playbackDelegate: self))
        }
        
        if let pipController = pipController {
            pipController.delegate = self
            if pipController.isPictureInPicturePossible {
                pipController.startPictureInPicture()
            }
        }
    }

4、摄像头多任务需要硬件支持

前面生成的工程,在iphone 8p上测试效果如下,进入画中画模式时,在1s以内视频捕获就停止了。

摄像头画中画,iphone 8p_哔哩哔哩_bilibili

原因在于苹果对摄像头硬件管理非常严格,摄像头开小窗口,那么用户就可能用摄像头打开另一个任务,意味着摄像头需要支持多任务。

参考官方说明:

增加代码检查如下:

Swift 复制代码
    
    func setupCamera(forgroundFlag: Bool, view: UIView) {
。。。。。。        
        guard let tempcap = captureSession else { return }
        if (tempcap.isMultitaskingCameraAccessSupported) {
            print("camera supp multitask")
            // Enable use of the camera in multitasking modes.
            captureSession?.isMultitaskingCameraAccessEnabled = true
        } else {
            print("camera not supp multitask")
        }
        captureSession?.commitConfiguration()
    }

从调试打印来看,iphone 8p的摄像头不支持多任务。

根据AI查询结果,可能需要iphone 12以上的机型才能支持摄像头多任务。

对于AI返回结果比较存疑,因为多次返回的结果可能会不一样。比如iphone XR,有的说支持,有的说不支持。

参考另外的官方文章,视频通话、直播画中画都是一样的。

Adopting Picture in Picture for video calls | Apple Developer Documentation

因为本人手上只有iphone 8p,没有其他新机型,所以后面的调试验证没法继续下去了。在没有订单推动情况下,也不会投入了。

如果有相关项目需求的,可以找我咨询合作。

相关推荐
丿Wayne4 小时前
iOS Token 刷新实战(Actor 避免并发重复刷新)
ios·swift·token·actor·swift并发
终端安全笔记1 天前
iOS 27 给了租赁一个新工具,但它只认受监督的设备
android·网络·安全·ios
大熊猫侯佩1 天前
独立 App 首发完成 iPhone Duo 展开态适配
ios·swiftui·swift
光影少年1 天前
setImmediate 和 setTimeout(0) 的区别
android·前端·react.js·ios·前端框架
2501_916008891 天前
SSE 和 gRPC 流式接口如何抓包,调试 SSE 与 gRPC 流式接口
网络协议·计算机网络·网络安全·ios·adb·https·udp
HouWan2 天前
Flutter: MediaQuery.of(context) 为什么可能拖慢页面?
android·flutter·ios
SoaringHeart2 天前
Swift 进阶&封装:仿 Get 给SwiftUI 添加路由导航能力
swiftui·swift
用户38034165882972 天前
Broadcast Extension 里的 Live Activity:一次跨进程妥协
ios·音视频开发
匠测AI说2 天前
XCUITest 自动化全解:同进程白盒、XCTest 架构、自动同步机制与 iOS 端选型指南
测试工具·ios·自动化
silianpan2 天前
CAD 文档预览 UTS 插件
android·ios·harmonyos