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

SwiftUI中集成AVPlayerViewController与Google IMA SDK全屏过渡广告适配的官方安全方案咨询

SwiftUI中集成AVPlayerViewController与Google IMA SDK全屏过渡广告适配的官方安全方案咨询

你遇到的这个问题真的是SwiftUI结合AVPlayerViewController和Google IMA SDK时的典型痛点——iOS系统在处理AVPlayerViewController全屏时会偷偷用AVFullScreenViewController把它包起来,这时候IMA广告的父层级没跟上,UIKit直接就抛异常了,我之前帮好几个开发者排查过类似的问题,下面给你几个经过验证的官方安全方案:

方案一:监听系统全屏通知,动态更新IMA的父控制器与容器视图

iOS系统提供了专门的通知来监听AVPlayerViewController的全屏状态变化,我们可以利用这些通知,在进入/退出全屏时手动调整IMA广告的层级和父控制器,完美匹配系统的包装逻辑:

  1. 添加全屏状态监听
    在你封装AVPlayerViewController的UIViewControllerRepresentable实现里,注册AVPlayerViewController.didEnterFullScreenNotification和AVPlayerViewController.didExitFullScreenNotification通知:
struct PlayerContainer: UIViewControllerRepresentable {
    @Binding var player: AVPlayer?
    
    func makeUIViewController(context: Context) -> AVPlayerViewController {
        let playerVC = AVPlayerViewController()
        playerVC.player = player
        
        // 初始化IMA广告容器(初始用playerVC作为父控制器)
        let adDisplayContainer = IMAAdDisplayContainer(adContainer: playerVC.view, viewController: playerVC)
        context.coordinator.adDisplayContainer = adDisplayContainer
        
        // 注册全屏通知
        NotificationCenter.default.addObserver(
            context.coordinator,
            selector: #selector(context.coordinator.handleEnterFullScreen),
            name: AVPlayerViewController.didEnterFullScreenNotification,
            object: playerVC
        )
        NotificationCenter.default.addObserver(
            context.coordinator,
            selector: #selector(context.coordinator.handleExitFullScreen),
            name: AVPlayerViewController.didExitFullScreenNotification,
            object: playerVC
        )
        
        // 初始化IMA广告加载器
        let adLoader = IMAAdLoader(settings: nil)
        adLoader.delegate = context.coordinator
        context.coordinator.adLoader = adLoader
        
        return playerVC
    }
    
    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
        uiViewController.player = player
    }
    
    func makeCoordinator() -> Coordinator { Coordinator() }
    
    class Coordinator: NSObject, IMAAdLoaderDelegate {
        var adDisplayContainer: IMAAdDisplayContainer?
        var adViewController: IMAAdViewController?
        var adLoader: IMAAdLoader?
        
        @objc func handleEnterFullScreen(_ notification: Notification) {
            guard let playerVC = notification.object as? AVPlayerViewController,
                  let fullScreenParent = playerVC.parentViewController as? AVFullScreenViewController else { return }
            
            // 关键:更新IMA广告的父控制器为系统的全屏容器
            adDisplayContainer?.viewController = fullScreenParent
            adViewController?.parentViewController = fullScreenParent
            
            // 把广告视图移到全屏容器的视图层级中
            if let adView = adViewController?.view {
                fullScreenParent.view.addSubview(adView)
                adView.frame = playerVC.view.frame // 匹配原播放器视图位置
            }
        }
        
        @objc func handleExitFullScreen(_ notification: Notification) {
            guard let playerVC = notification.object as? AVPlayerViewController else { return }
            
            // 退出全屏后恢复原父控制器和视图层级
            adDisplayContainer?.viewController = playerVC
            adViewController?.parentViewController = playerVC
            
            if let adView = adViewController?.view {
                playerVC.view.addSubview(adView)
                adView.frame = playerVC.view.frame
            }
        }
        
        // IMA广告加载成功回调,保存adViewController
        func adLoader(_ adLoader: IMAAdLoader, didReceive adResponse: IMAAdResponse) {
            guard let adDisplayContainer = adDisplayContainer else { return }
            adViewController = IMAAdViewController(adContainer: adDisplayContainer)
            // 后续广告展示逻辑
        }
        
        // 其他IMAAdLoaderDelegate回调实现
        func adLoader(_ adLoader: IMAAdLoader, didFailWithError error: IMAAdLoadingError) {
            print("广告加载失败:\(error.localizedDescription)")
        }
    }
}

方案二:自定义全屏过渡,绕过系统的AVFullScreenViewController

如果不想跟系统的全屏容器打交道,完全可以自己控制AVPlayerViewController的全屏逻辑,这样就不会触发系统的自动包装:

  1. 在SwiftUI视图中用fullScreenCover实现自定义全屏:
struct VideoPlayerView: View {
    @State private var isFullScreen = false
    @State private var player: AVPlayer? = AVPlayer(url: URL(string: "your-video-url")!)
    
    var body: some View {
        VStack {
            // 内嵌的播放器
            PlayerContainer(player: $player)
                .frame(height: 300)
                .cornerRadius(8)
            
            Button(isFullScreen ? "退出全屏" : "进入全屏") {
                isFullScreen.toggle()
            }
        }
        .fullScreenCover(isPresented: $isFullScreen) {
            // 全屏状态下的播放器
            PlayerContainer(player: $player)
                .edgesIgnoringSafeArea(.all)
        }
    }
}

这种方式下,AVPlayerViewController始终在你自己控制的层级里,IMA广告的父控制器不会出现不匹配的情况,完全避开了系统的AVFullScreenViewController坑。

方案三:使用IMA官方的IMAAVPlayerVideoDisplay适配

Google IMA SDK专门提供了IMAAVPlayerVideoDisplay来和AVPlayer深度集成,它会自动跟踪播放器的视图层级变化,包括全屏场景,不用你手动处理父控制器:

  1. 用IMAAVPlayerVideoDisplay替代直接用playerVC的view作为容器:
func makeUIViewController(context: Context) -> AVPlayerViewController {
    let playerVC = AVPlayerViewController()
    let player = AVPlayer(url: URL(string: "your-video-url")!)
    playerVC.player = player
    
    // 初始化IMA的视频显示组件
    let videoDisplay = IMAAVPlayerVideoDisplay(avPlayer: player)
    let adDisplayContainer = IMAAdDisplayContainer(adContainer: playerVC.view, viewController: playerVC)
    
    // 加载广告
    let adLoader = IMAAdLoader(settings: nil)
    let adRequest = IMAAdRequest(adTagUrl: "your-ad-tag-url")
    adLoader.requestAds(with: adRequest, adDisplayContainer: adDisplayContainer, videoDisplay: videoDisplay, delegate: context.coordinator)
    
    return playerVC
}

这个方案是Google官方推荐的AVPlayer集成方式,IMA SDK内部已经处理了各种视图层级变化的情况,包括全屏过渡,能最大程度避免UIKit异常。

总结

优先推荐方案三(用IMA官方的视频显示组件),因为这是SDK原生支持的适配逻辑;如果需要保留系统默认的全屏按钮,那么方案一是最稳妥的;如果对全屏交互有自定义需求,方案二会给你最大的控制权。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 10:44:35