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

SwiftUI中AVPlayer仅连接第二屏幕时正常播放问题求助

问题:iPad端未连接AirPlay时AVPlayer无法播放视频,连接后正常

我在SwiftUI中开发演示功能,需求是iPad端显示幻灯片,同时通过AirPlay在第二屏幕同步展示独立视图。通过screenDidConnectPublisher监听第二屏幕连接:

ContentView()
    .onReceive(screenDidConnectPublisher, perform: screenDidConnect)

iPad端通过PresentView展示包含图片和视频的幻灯片;连接第二屏幕后,PresentScreenView同步展示当前选中的幻灯片。

问题:未连接第二屏幕时,iPad端的AVPlayer有时无法播放视频,仅显示空白界面;连接第二屏幕后,两端视频均可正常播放。


Present View(iPad端)代码

ZStack {
    if let media = mediaCacheManager.mediaCache[appModel.currentSlide] {
        if let player = media as? AVPlayer {
            PlayerView(player: player)
                .aspectRatio(16 / 10, contentMode: .fit)
                .frame(width: UIScreen.main.bounds.width * 0.8)
                .background(Color.gray.opacity(0.2))
                .clipShape(RoundedRectangle(cornerRadius: 40))
                .overlay(
                    RoundedRectangle(cornerRadius: 40)
                        .stroke(Color.gray.opacity(0.5), lineWidth: 1)
                )
                .onDisappear {
                    player.pause()
                }
        } else if let image = media as? Image {
            image
                .resizable()
                .scaledToFit()
                .frame(width: UIScreen.main.bounds.width * 0.8)
                .background(Color.gray.opacity(0.2))
                .clipShape(RoundedRectangle(cornerRadius: 40))
                .overlay(
                    RoundedRectangle(cornerRadius: 40)
                        .stroke(Color.gray.opacity(0.5), lineWidth: 1)
                )
                .padding(.vertical, 10)
        }
    }
}

PresentScreenView(第二屏幕端)代码

//
//  PresentScreenView.swift
//  Interactive
//
//  Created by Vikrant Gurav on 07/11/2024.
//

import SwiftUI
import AVKit

struct PresentScreenView: View {
    @EnvironmentObject private var appModel: AppModel
    @EnvironmentObject var currentPresentation: CurrentPresentation
    @EnvironmentObject var mediaCacheManager: MediaCacheManager
    
    @State var currentMedia: Image?

    var body: some View {
        if let selectedPresentation = currentPresentation.currentPresentation {
            if appModel.controlScreenSlide {
            TabView(selection: $appModel.currentSlideScreen) {
                ForEach(selectedPresentation.slides.indices, id: \.self) { index in
                    if let media = mediaCacheManager.mediaCache[index] {
                        if let player = media as? AVPlayer {
                            VideoPlayer(player: player)
                                .frame( maxHeight: .infinity)
                                .ignoresSafeArea(.all)
                        }
                    }
                }
            }
            .opacity(currentMedia == nil ? 1 : 0)
            .overlay {
                if let currentMedia {
                    currentMedia
                        .resizable()
                        .scaledToFill()
                    
                }
            }
            .ignoresSafeArea(.all)
            .task(id: appModel.currentSlideScreen, {
                let newValue = appModel.currentSlideScreen
                if mediaCacheManager.typeCache[newValue] == .image {
                    currentMedia = mediaCacheManager.mediaCache[newValue] as? Image
                } else {
                    currentMedia = nil
                }
            })
            .frame(maxWidth: .infinity , maxHeight: .infinity)
            .tabViewStyle(.page(indexDisplayMode: .never))
        }
            else {
                HStack{
                    Text("Enable Screen Control")
                        .font(.largeTitle)
                        .fontWeight(.bold)
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                }
                    .overlay {
                        if let currentMedia {
                            currentMedia
                                .resizable()
                                .scaledToFill()
                            
                        }
                    }
                    .ignoresSafeArea(.all)
                    .frame(maxWidth: .infinity , maxHeight: .infinity)
            }
                }
    }
}

解决思路

  • 主动触发AVPlayer播放
    未连接第二屏幕时,缓存的AVPlayer可能未自动启动播放。自定义PlayerView时,在视图更新阶段检查player状态并主动调用播放:

    struct PlayerView: UIViewRepresentable {
        let player: AVPlayer
        
        func makeUIView(context: Context) -> UIView {
            let view = UIView()
            let playerLayer = AVPlayerLayer(player: player)
            playerLayer.frame = view.bounds
            playerLayer.videoGravity = .resizeAspectFit
            view.layer.addSublayer(playerLayer)
            context.coordinator.playerLayer = playerLayer
            return view
        }
        
        func updateUIView(_ uiView: UIView, context: Context) {
            context.coordinator.playerLayer?.frame = uiView.bounds
            if player.rate == 0 && player.currentItem?.status == .readyToPlay {
                player.play()
            }
        }
        
        func makeCoordinator() -> Coordinator {
            Coordinator()
        }
        
        class Coordinator {
            var playerLayer: AVPlayerLayer?
        }
    }
    

    同时在PlayerView的onAppear中恢复播放,避免onDisappear的暂停操作导致状态残留:

    PlayerView(player: player)
        .onAppear {
            if player.currentItem?.status == .readyToPlay {
                player.play()
            }
        }
        // 其他修饰符...
    
  • 检查缓存中AVPlayer的有效性
    从缓存获取AVPlayer时,验证currentItem的加载状态,若状态异常则重新初始化并更新缓存:

    if let player = media as? AVPlayer {
        if player.currentItem?.status != .readyToPlay {
            let newPlayer = AVPlayer(url: yourVideoURL)
            mediaCacheManager.mediaCache[appModel.currentSlide] = newPlayer
            PlayerView(player: newPlayer)
                // 修饰符...
        } else {
            PlayerView(player: player)
                // 修饰符...
        }
    }
    
  • 强制视图重建更新
    给PlayerView绑定与当前幻灯片索引关联的id,确保切换幻灯片时视图完全重建,避免旧player状态干扰:

    PlayerView(player: player)
        .id(appModel.currentSlide)
        // 其他修饰符...
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 09:42:08