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

在onChange事件中设置后,AVPlayer的currentItem始终为nil的问题排查

AVPlayer渲染阶段currentItem始终为nil的原因及解决方法

我尝试播放从onChange事件中设置的流媒体URL,闭包内打印的URL正常(非nil),但在View的body渲染阶段,AVPlayer的currentItem却始终为nil。相关代码如下:

struct VideoPlayerContainerView: View {
    @EnvironmentObject private var store: AppStore
    private var player = AVPlayer()
    @State private var urlToPlay: URL?
    @State private var playingUrl: URL?
    
    var body: some View {

            let currentPlayerUrl = ((player.currentItem?.asset) as? AVURLAsset)?.url
            print("asset loaded in the VideoPlayer ⬇")
            print(currentPlayerUrl ?? "no asset!") // 始终为nil
        
        return ZStack {
            VideoPlayerView(player: player)
        }
        .onChange(of: self.store.state.videos.playingVideo?.stream?.best.url) {
            [oldValue = self.store.state.videos.playingVideo?.stream?.best.url] newValue in

            print("- - - - - - - - - - - - ")
            
            self.urlToPlay = newValue
            if (self.urlToPlay != self.playingUrl) {
                let newPlayerItem = self.urlToPlay != nil ? AVPlayerItem(url: self.urlToPlay!) : nil
                player.replaceCurrentItem(with: newPlayerItem)
                self.playingUrl = self.urlToPlay
                
                print("newPlayerItem ⬇")
                print(newPlayerItem ?? "nil")
                print("url that should play now ⬇")
                let currentPlayerUrl = ((player.currentItem?.asset) as? AVURLAsset)?.url
                print(currentPlayerUrl ?? "nil") // 不为nil
            }
        }
    }
}

struct VideoPlayerView: View {
    let player: AVPlayer
    var body: some View {
        VideoPlayer(player: player)
    }
}

原因分析

核心问题在于你声明的private var player = AVPlayer()是普通存储属性。在SwiftUI的View生命周期里,每次View因为状态变化重新渲染时,这个普通属性都会被重新初始化——也就是说,你在onChange闭包里给旧player设置的currentItem,到了渲染阶段已经变成了一个全新的player实例,自然没有任何currentItem,所以打印结果始终是nil。

拆解来看:

  1. onChange触发时,你操作的是当前View实例的player,设置完currentItem后打印正常;
  2. 但onChange执行后,playingUrl或urlToPlay的更新会触发View重新计算body,此时新的player实例被创建,旧的player被丢弃,渲染阶段打印的是新player的状态,所以显示nil。

解决方案

要解决这个问题,关键是让player实例在View重新渲染时保持不变,有两种常用实现方式:

方法一:用@StateObject包装AVPlayer

因为AVPlayer本身不是ObservableObject,所以需要先做一层包装:

class PlayerWrapper: ObservableObject {
    let player = AVPlayer()
}

struct VideoPlayerContainerView: View {
    @EnvironmentObject private var store: AppStore
    @StateObject private var playerWrapper = PlayerWrapper() // @StateObject保证实例不被重置
    @State private var playingUrl: URL?
    
    var body: some View {
        let currentPlayerUrl = ((playerWrapper.player.currentItem?.asset) as? AVURLAsset)?.url
        print("asset loaded in the VideoPlayer ⬇")
        print(currentPlayerUrl ?? "no asset!")
        
        return ZStack {
            VideoPlayerView(player: playerWrapper.player)
        }
        .onChange(of: store.state.videos.playingVideo?.stream?.best.url) { newValue in
            guard let newValue = newValue, newValue != playingUrl else { return }
            
            let newPlayerItem = AVPlayerItem(url: newValue)
            playerWrapper.player.replaceCurrentItem(with: newPlayerItem)
            playingUrl = newValue
            
            print("- - - - - - - - - - - - ")
            print("newPlayerItem ⬇")
            print(newPlayerItem)
            print("url that should play now ⬇")
            let currentPlayerUrl = ((playerWrapper.player.currentItem?.asset) as? AVURLAsset)?.url
            print(currentPlayerUrl ?? "nil")
        }
    }
}

方法二:直接用@State持有AVPlayer

利用@State可以在View生命周期中保持引用类型实例的特性,直接把player声明为@State属性:

struct VideoPlayerContainerView: View {
    @EnvironmentObject private var store: AppStore
    @State private var player = AVPlayer() // @State保证实例不被重置
    @State private var playingUrl: URL?
    
    var body: some View {
        let currentPlayerUrl = ((player.currentItem?.asset) as? AVURLAsset)?.url
        print("asset loaded in the VideoPlayer ⬇")
        print(currentPlayerUrl ?? "no asset!")
        
        return ZStack {
            VideoPlayerView(player: player)
        }
        .onChange(of: store.state.videos.playingVideo?.stream?.best.url) { newValue in
            guard let newValue = newValue, newValue != playingUrl else { return }
            
            let newPlayerItem = AVPlayerItem(url: newValue)
            player.replaceCurrentItem(with: newPlayerItem)
            playingUrl = newValue
            
            print("- - - - - - - - - - - - ")
            print("newPlayerItem ⬇")
            print(newPlayerItem)
            print("url that should play now ⬇")
            let currentPlayerUrl = ((player.currentItem?.asset) as? AVURLAsset)?.url
            print(currentPlayerUrl ?? "nil")
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 23:27:16