繼上篇:iOS swift 后臺運行應用嘗試失敗-CSDN博客
為什么想到畫中畫,起初是看到后臺模式里有一個picture in picture,去了解了后發現這個就是小窗口視頻播放,方便用戶執行多任務。看小窗口視頻的同時,可以作其他的事情。
畫中畫功能在20世紀80年代開始在電視機中應用,使得用戶可以在一個屏幕上同時觀看兩個頻道的內容。?
這個技術在安卓里已經非常普遍了。各種視頻內容網站都有類似功能。
而蘋果支持畫中畫是在ios14已經開始支持。目前在使用的大多數機型,比如iphone 8p,升級系統后可到ios16.7,都能支持畫中畫技術。
后臺任務蘋果管理太嚴格,不好搞,那么使用畫中畫這種技術,直接做成多任務,這樣也是另一條可以嘗試的路徑。
1、標準PIP使用
首先,標準寫法是采用AVPlayer,輸入url需要是MP4等視頻文件,如果是加密后的網址,無法播放,比如b站的網址。
創建AVPlayer,使用AVPlayerLayer來初始化AVPictureInPictrueController。
// 創建 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.boundsview.layer.addSublayer(playerLayer)// 設置畫中畫控制器pipController = AVPictureInPictureController(playerLayer: playerLayer)pipController.delegate = selfplayer.play() // 直接播放
按home鍵退出,就會自動啟動小窗口繼續播放視頻。
這里有一個坑,需要初始化音頻,否則播放mp4無聲音,且畫中畫也不會觸發。
do {// 設置AVAudioSession為后臺模式try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)try AVAudioSession.sharedInstance().setActive(true)} catch {print("無法設置AVAudioSession: \(error)")}
視頻畫中畫,iphone 8p
2、攝像頭預覽
攝像頭捕捉并預覽,這個算法也很容易找到,使用AVCaptureSession
func setupCamera(forgroundFlag: Bool, view: UIView) {farView = viewflag = forgroundFlagcaptureSession = AVCaptureSession()captureSession?.beginConfiguration()captureSession?.sessionPreset = .highguard 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 = truevideoOutput!.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))captureSession!.addOutput(videoOutput!)if (forgroundFlag) {videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)guard let preLayer = videoPreviewLayer else { return }preLayer.frame = view.frameview.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接口,如下:
func setupSampleBufferDisplayLayer() {sampleBufferDisplayLayer = AVSampleBufferDisplayLayer()sampleBufferDisplayLayer.frame = view.boundssampleBufferDisplayLayer.videoGravity = .resizeAspectview.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。
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 = selfif pipController.isPictureInPicturePossible {pipController.startPictureInPicture()}}}
4、攝像頭多任務需要硬件支持
前面生成的工程,在iphone 8p上測試效果如下,進入畫中畫模式時,在1s以內視頻捕獲就停止了。
攝像頭畫中畫,iphone 8p
原因在于蘋果對攝像頭硬件管理非常嚴格,攝像頭開小窗口,那么用戶就可能用攝像頭打開另一個任務,意味著攝像頭需要支持多任務。
參考官方說明:
增加代碼檢查如下:
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,沒有其他新機型,所以后面的調試驗證沒法繼續下去了。在沒有訂單推動情況下,也不會投入了。
如果有相關項目需求的,可以找我咨詢合作。
? ? ?