You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现iOS画中画(PiP)窗口自动弹出?Flutter调用iOS PiP问题求助

iOS Flutter MethodChannel PiP 自动弹出问题修复

问题现象

通过Flutter MethodChannel调用iOS PiP功能播放视频,视频可正常播放,但PiP窗口无法主动弹出;手动将App切换至后台时,PiP窗口才会出现。尝试在pictureInPictureControllerDidStartPictureInPicture回调中调用自定义的background()方法让App进入后台,但该回调未执行。

原代码片段

private var player: AVPlayer?
private var playerLayer: AVPlayerLayer?
private var pipController : AVPictureInPictureController?
private var channel: FlutterMethodChannel
private var flutterView: UIView?
private var flutterWindow: UIWindow?

public func background() {
    let targetSelect = #selector(NSXPCConnection.suspend)
    if UIApplication.shared.responds(to: targetSelect) {
        UIApplication.shared.perform(targetSelect)
    }
}

public func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
    background()
}

public func enterPiPMode(arguments: Any?,result: @escaping FlutterResult){
    dispose()
    if(isAvailable()){
        
        flutterWindow = windows()?.filter { window in
                      window.isKeyWindow
                  }.first
        
        flutterView = flutterWindow?.rootViewController?.view
        
        if let args = arguments as? [String: Any] {
            
            let iOSPlayUrl = args["iOSPlayUrl"] as? String ?? ""
            
            if(iOSPlayUrl.isEmpty){
                print("PiPVideoPlugin error : iOSPlayUrl is Empty")
                result(false)
                return
            }
            do {
                let audioSession = AVAudioSession.sharedInstance()
                try audioSession.setCategory(.playAndRecord, options:.mixWithOthers)
                try audioSession.setActive(true)
            } catch {
                print("PiPVideoPlugin error : AVAudioSession.sharedInstance")
                result(false)
                return
            }
            
            playerLayer = AVPlayerLayer()
            playerLayer!.frame = .init(x: 0, y: 0, width: 1, height: 1)
            
            player = AVPlayer(playerItem: AVPlayerItem(url: URL(string: iOSPlayUrl)!))
            player!.isMuted = false
            player!.allowsExternalPlayback = true
            player!.accessibilityElementsHidden = true
            
            playerLayer!.player = player
            
            pipController = AVPictureInPictureController(playerLayer: playerLayer!)
            pipController!.delegate = self
            
            pipController!.setValue(0, forKey: "controlsStyle")
            pipController!.setValue(0, forKey: "requiresLinearPlayback")
            
            if #available(iOS 14.2, *) {
                pipController!.canStartPictureInPictureAutomaticallyFromInline = true
            }
            
            player!.play()
            flutterView?.layer.addSublayer(playerLayer!)
            
            DispatchQueue.main.async {
                self.pipController!.startPictureInPicture()
                result(true)
            }
        }
        else {
            print("PiPVideoPlugin error : arguments error")
            result(false)
            return
        }
    }else{
        print("PiPVideoPlugin error : PiP is not available")
        result(false)
        return
    }
}

问题分析

  1. 回调未触发:pictureInPictureControllerDidStartPictureInPicture仅在PiP成功启动后回调,若前台启动PiP时系统未立即触发该状态(iOS前台默认不会自动显示PiP窗口),回调不会执行。
  2. 私有API风险:使用NSXPCConnection.suspend强制后台是私有API行为,存在审核不通过风险,且执行不稳定。
  3. 系统限制:iOS默认要求用户主动切换后台才会显示PiP窗口,canStartPictureInPictureAutomaticallyFromInline仅针对inline视频场景生效,无法直接触发App后台。

修复方案

1. 替换合规的后台触发方法

将私有API替换为公开的resignActive方法,合法让App进入非活跃状态:

public func background() {
    UIApplication.shared.perform(#selector(UIApplication.resignActive))
}

2. 调整PiP启动与后台触发时机

延迟调用startPictureInPicture()确保视频渲染完成,再延迟触发后台操作,避免PiP未启动时执行后台:

// 替换原DispatchQueue代码
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
    self.pipController?.startPictureInPicture()
    // 延迟确保PiP状态已激活
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
        self.background()
    }
    result(true)
}

3. 优化音频会话配置

若无需录音功能,将音频会话类别改为.playback,减少权限依赖:

do {
    let audioSession = AVAudioSession.sharedInstance()
    try audioSession.setCategory(.playback, options: .mixWithOthers)
    try audioSession.setActive(true)
} catch {
    print("PiPVideoPlugin error : AVAudioSession setup failed")
    result(false)
    return
}

4. 监听PiP状态变化(可选)

添加状态监听确保PiP启动后再执行后台:

// 在初始化pipController后添加监听
if #available(iOS 13.0, *) {
    pipController?.addObserver(self, forKeyPath: "isPictureInPictureActive", options: [.new], context: nil)
}

// 实现监听方法
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "isPictureInPictureActive", let isActive = change?[.newKey] as? Bool, isActive {
        background()
        // 移除监听避免内存泄漏
        pipController?.removeObserver(self, forKeyPath: "isPictureInPictureActive")
    }
}

内容的提问来源于stack exchange,提问作者lycstar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 09:32:08