Observer on AvPlayer works only on the first item

508 Views Asked by At

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)
    }
    
    
}
2

There are 2 best solutions below

0
matthew On BEST ANSWER

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)
}
3
adams.s On

I think this might be because you're only setting the observer on the currentItem. I would need to investigate myself if it's possible to put it on all items without removing and adding the observer over and over again.

You could set listen to every change of the currentItem. Then everytime an item is changed, you could pause the player for x amount of seconds and start the player again.

let cancellables = Set<AnyCancellable>()

player.publisher(for: \.currentItem).sink(receiveValue: { _ in
   print("New currentItem")
}).store(in: &cancellables)

Hope this helps.