Search code examples
swiftswiftuiavplayeravkitavqueueplayer

Observer on AvPlayer works only on the first item


I've a SwiftUi app with a View in which I want to play a sequence of videos from remote. I've used an AVQueuePlayer.

At the end of any reproduction I want to play the next video after a defined pause from an array of pauses.

This code work only after the first played item, and then the observer not intercept nothing. Any suggestion?

struct ExercisesPlay: View {
    
    @State var urls: [URL]
    @State var pauses: [Int]
    
    var player: AVQueuePlayer
    
    @State var showToast: Bool = false
    
    init(urls: [URL], pauses: [Int]) {
        self.urls = urls
        self.pauses = pauses
        
        var array: [AVPlayerItem] = []
        urls.forEach { URL in
            array.append(AVPlayerItem(url: URL))
        }
        self.player = AVQueuePlayer(items: array)
    }
    
    
    var body: some View {
        VideoPlayer(player:player)
            .onAppear{
                player.play()
                addObserver()
            }
            .onDisappear{
                removeObserver()
            }
            .toast(message: LocalizedStringKey("Pause").stringValue()!,
                   isShowing: self.$showToast, duration: Toast.long)
        
    }
    
    func addObserver() {
        NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue:nil ) { notif in

            self.showToast.toggle()
            
            if(!pauses.isEmpty){
                
                print(player.currentItem)
                
                player.pause()
                
                DispatchQueue.main.asyncAfter(deadline: (.now() + DispatchTimeInterval.seconds(pauses.first!))) {
                    player.seek(to: .zero)
                    player.play()
                    pauses.remove(at: 0)
                    
                }
            }
        }
    }
    
    func removeObserver() {
        NotificationCenter.default.removeObserver(self,name: .AVPlayerItemDidPlayToEndTime, object: nil)
    }
    
    
}

Solution

  • The observer is only being added to the current item when the view appears.

    You can add an observer to each item before passing them to the AVQueuePlayer. If you include a reference to the object, you can more easily remove the observer later.

    urls.forEach { URL in
        let item = AVPlayerItem(url: URL)
        NotificationCenter.default.addObserver(self, selector: #selector(itemFinishedPlaying), name: .AVPlayerItemDidPlayToEndTime, object: item)
        array.append(item)
    }
    

    In the selector method, you can do your setup and cleanup the observer.

    @objc private func itemFinishedPlaying(_ notification: NSNotification) {
    NotificationCenter.default.removeObserver(self, name: notification.name, object: notification.object)
    
    // additional code you want to perform goes here
    
    }
    

    If you want to cleanup all of the observers when the view disappears, you can iterate through the queued items.

    for item in player.items() {
        NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: item)
    }