Search code examples
swiftswiftuiavkit

How to solve the issue of "AttributeGraph: cycle detected through attribute" with playing video in SwiftUI?


This is my test code for playing video in SwiftUI, the video start playing in first time, but when I update the url for other video it shows like multiple (10-20 times) line of:

"AttributeGraph: cycle detected through attribute"

in Xcode console. From what I see I did not made wrong code for this issue, how ever I also could not find an answer in internet for this logs. The videos are playing after click but each time after updating the url this logs comes out in Xcode.

import SwiftUI
import AVKit

struct ContentView: View {

    @State private var AVPlayerURL: URL? = nil
    
    var body: some View {
        VStack {
            
            if let AVPlayerURL {
                VideoPlayer(player: AVPlayer(url: AVPlayerURL))
            }

            HStack {
                
                Button("Play video 1") {
                    AVPlayerURL = Bundle.main.url(forResource: "myVideo1", withExtension: "mp4")
                }
                
                Button("Play video 2") {
                    AVPlayerURL = Bundle.main.url(forResource: "myVideo2", withExtension: "mp4")
                }
                
                Button("Play video 3") {
                    AVPlayerURL = Bundle.main.url(forResource: "myVideo3", withExtension: "mp4")
                }
                
            }

        }
        .padding()
        
    }
}

Solution

  • Here I find a way for solving the issue of Xcode logs.

    import SwiftUI
    import AVKit
    
    @main
    struct AVPlayer_TestApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView().environmentObject(AVPlayerModel.shared)
            }
        }
    }
    
    struct ContentView: View {
        
        @EnvironmentObject var myAVPlayerModel: AVPlayerModel
        
        var body: some View {
            
            VStack {
                
                Group {
                    
                    if let unwrappedPlayer: AVPlayer = myAVPlayerModel.player {
                        VideoPlayer(player: unwrappedPlayer)
                    }
                    else {
                        Color.black
                    }
                    
                }
                .cornerRadius(5.0)
                
                ButtonView()
                
            }
            .padding()
            
        }
        
    }
    
    struct ButtonView: View {
        
        @EnvironmentObject var myAVPlayerModel: AVPlayerModel
        
        var body: some View {
            
            HStack {
                
                Button("Play video 1") {
                    myAVPlayerModel.AVPlayerURL = Bundle.main.url(forResource: "myVideo1", withExtension: "mp4")
                }
                
                Button("Play video 2") {
                    myAVPlayerModel.AVPlayerURL = Bundle.main.url(forResource: "myVideo2", withExtension: "mp4")
                }
                
                Button("Play video 3") {
                    myAVPlayerModel.AVPlayerURL = Bundle.main.url(forResource: "myVideo3", withExtension: "mp4")
                }
                
            }
            
        }
    }
    
    class AVPlayerModel: ObservableObject {
        
        static let shared: AVPlayerModel = AVPlayerModel()
        
        @Published var AVPlayerURL: URL? = nil {
            
            didSet(oldValue) {
                
                if let unwrappedAVPlayerURL: URL = self.AVPlayerURL {
                    
                    if let unwrappedOldValue: URL = oldValue {
                        
                        if (unwrappedAVPlayerURL != unwrappedOldValue) {
                            self.player?.pause()
                            self.player = nil
                        }
                        else {
                            
                            let seekToZero = CMTime(seconds: 0, preferredTimescale: 60000)
                            self.player?.seek(to: seekToZero, toleranceBefore: .zero, toleranceAfter: .zero)
    
                            return
                        }
                        
                    }
                    else {
                        self.player?.pause()
                        self.player = nil
                    }
                    
                    DispatchQueue.main.async {
                        if let unwrappedAVPlayerURL: URL = self.AVPlayerURL {
                            self.player = AVPlayer(url: unwrappedAVPlayerURL)
                            self.player?.play()
                        }
                    }
                    
                }
                
            }
            
        }
        
        @Published var player: AVPlayer? = nil
        
    }