How to detect when AVPlayer video ends playing?

IosIphoneSwiftAvplayer

Ios Problem Overview


I'am using AVPlayer for playing local video file (mp4) in Swift. Does anyone know how to detect when video finish with playing? Thanks

Ios Solutions


Solution 1 - Ios

To get the AVPlayerItemDidPlayToEndTimeNotification your object needs to be an AVPlayerItem.

To do so, just use the .currentItem property on your AVPlayer

Now you will get a notification once the video ends!

See my example:

let videoPlayer = AVPlayer(URL: url)       

NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerDidFinishPlaying:",
        name: AVPlayerItemDidPlayToEndTimeNotification, object: videoPlayer.currentItem)

func playerDidFinishPlaying(note: NSNotification) {
    print("Video Finished")
}

Swift 3

let videoPlayer = AVPlayer(URL: url)       

NotificationCenter.default.addObserver(self, selector: Selector(("playerDidFinishPlaying:")), 
       name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: videoPlayer.currentItem)
    
func playerDidFinishPlaying(note: NSNotification) {
    print("Video Finished")
}

Don't forget to remove the Observer in your deinit

Swift 4, 5

NotificationCenter.default
    .addObserver(self,
    selector: #selector(playerDidFinishPlaying),
    name: .AVPlayerItemDidPlayToEndTime,
    object: videoPlayer.currentItem
)

Solution 2 - Ios

Swift 3.0

let videoPlayer = AVPlayer(URL: url)

NotificationCenter.default.addObserver(self, selector:#selector(self.playerDidFinishPlaying(note:)),name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)

@objc func playerDidFinishPlaying(note: NSNotification){
        print("Video Finished")
    }

Solution 3 - Ios

Swift 4.2 Version:

var player: AVPlayer!
  //
  //
// Configure Player
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    let filepath: String? = Bundle.main.path(forResource: "selectedFileName", ofType: "mp4")
    if let filepath = filepath {
        let fileURL = URL.init(fileURLWithPath: filepath)
        player = AVPlayer(url: fileURL)
        let playerLayer = AVPlayerLayer(player: player)
        // Register for notification
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(playerItemDidReachEnd),
                                                         name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
                                                         object: nil) // Add observer

        playerLayer.frame = self.view.bounds
        self.view.layer.addSublayer(playerLayer)
        player.play()
    }
}
// Notification Handling
@objc func playerItemDidReachEnd(notification: NSNotification) {
    player.seek(to: CMTime.zero)
    player.play()
}
// Remove Observer
deinit {
    NotificationCenter.default.removeObserver(self)
}

Solution 4 - Ios

For SWIFT 3.0 This is working fine

class PlayVideoViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PlayVideoViewController.finishVideo), name: NSNotification.Name.AVPlayerItemDidPlayToEndTimeNotification, object: nil)
    }
    
    func finishVideo()
    {
        print("Video Finished")
    }
}

Solution 5 - Ios

Swift 4.0

This one works for me. Thanks to @Channel

	private func playVideo(fileURL: String) {
			
			// Create RUL object
			let url = URL(string: fileURL)
			
			// Create Player Item object
			let playerItem: AVPlayerItem = AVPlayerItem(url: url!)
			// Assign Item to Player
			let player = AVPlayer(playerItem: playerItem)
			
			// Prepare AVPlayerViewController
			let videoPlayer = AVPlayerViewController()
			// Assign Video to AVPlayerViewController
			videoPlayer.player = player
			
			NotificationCenter.default.addObserver(self, selector: #selector(myViewController.finishVideo), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
			
			// Present the AVPlayerViewController
			present(videoPlayer, animated: true, completion: {
					// Play the Video
					player.play()
			})
			
	}

	@objc func finishVideo()
	{
			print("Video Finished")
	}

Solution 6 - Ios

If you fancy using Combine:

private var cancelBag: Set<AnyCancellable> = []

NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime)
.sink { _ in
    player.seek(to: CMTime.zero)
    player.play()
}
.store(in: &cancelBag)

Solution 7 - Ios

2019

It's really this simple

NotificationCenter.default.addObserver(
    self,
    selector: #selector(fileComplete),
    name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
    object: nil
)

(It's fine for the object to be nil.)

and then

@objc func fileComplete() {
    print("IT'S DONE!")
}

Solution 8 - Ios

Swift 3.0

let videoPlayer = AVPlayer(URL: url)
NotificationCenter.default.addObserver(self, selector:#selector(self.playerDidFinishPlaying(note:)),name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)

func playerDidFinishPlaying(note: NSNotification){
    //Called when player finished playing
}

Solution 9 - Ios

For SWIFT 3.0

Here 'fullUrl' is the URL of the video and make sure that there would be no space in the URL, You should replace 'Space' with '%20' so that URL will work file.

  let videoURL = NSURL(string: fullUrl)
  let player = AVPlayer(url: videoURL! as URL)
  
  playerViewController.delegate = self
  playerViewController.player = player
  self.present(playerViewController, animated: false) {
    
    self.playerViewController.player!.play()
    
    NotificationCenter.default.addObserver(self, selector: #selector(yourViewControllerName.playerDidFinishPlaying), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem)
  }

Add this below given method in your view controller.

func playerDidFinishPlaying(){    
print("Video Finished playing in style")
}

Solution 10 - Ios

I know there are a lot of accepted answers here...

But, another route might be to add a boundary time observer to your AVPlayer. You would have to have the duration of the video, which you can get from your player.currentItem, and then add it as your desired time boundary.

fileprivate var videoEndObserver: Any?

func addVideoEndObserver() {
    guard let player = YOUR_VIDEO_PLAYER else { return }

    // This is just in case you are loading a video from a URL.
    guard let duration = player.currentItem?.duration, duration.value != 0 else {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { [weak self] in
            self?.addVideoEndObserver()
        })
            
        return
    }
        
    let endTime = NSValue(time: duration - CMTimeMakeWithSeconds(0.1, duration.timescale))
    videoEndObserver = player.addBoundaryTimeObserver(forTimes: [endTime], queue: .main, using: {
        self.removeVideoEndObserver()
            
        // DO YOUR STUFF HERE...
    })
}
    
func removeVideoEndObserver() {
    guard let observer = videoEndObserver else { return }
        
    videoPlayer.player?.removeTimeObserver(observer)
    videoEndObserver = nil
}

Solution 11 - Ios

In Swift 3 and RxSwift 3.5 all you have to do is:

override func viewDidLoad() {
    super.viewDidLoad()
    
    NotificationCenter.default.rx.notification(Notification.Name.AVPlayerItemDidPlayToEndTime)
        .asObservable().subscribe(onNext: { [weak self] notification in
            
            //Your action
                
        }).addDisposableTo(disposeBag)
}

Solution 12 - Ios

Using Combine, and also making sure the notification comes from the AVPlayerItem you are interested in and not just any. I am playing multiple items at once, so this would work in that scenario as well.

private var subscriptions: Set<AnyCancellable> = []

NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
    .receive(on: RunLoop.main)
    .sink { [weak self] notification in
        guard let item = notification.object as? AVPlayerItem else { return }
        if item == self?.player.currentItem {
         //.... Here you know it was the item you are interested in that played to end and not just any
        }
    }
    .store(in: &subscriptions)

Solution 13 - Ios

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: nil, queue: .main) { noti in
        guard let item = noti.object as? AVPlayerItem else{
            return
        }
        //DidPlayToEndTime
    }

Solution 14 - Ios

func shareEditedVedio() -> AVPlayer {
    let editedVedioPlayer = AVPlayer(url: self.vedioData.vedioURLWithAddedSounds!)
    NotificationCenter.default.addObserver(self, selector:#selector(self.playerDidFinishPlaying(note:)),name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: editedVedioPlayer.currentItem)
    return editedVedioPlayer
}


@objc func playerDidFinishPlaying(note: NSNotification){
    //Called when player finished playing
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNikView Question on Stackoverflow
Solution 1 - IosjstnView Answer on Stackoverflow
Solution 2 - IosChannelView Answer on Stackoverflow
Solution 3 - IosNishView Answer on Stackoverflow
Solution 4 - Iosmanvendra singhView Answer on Stackoverflow
Solution 5 - IosArash HFView Answer on Stackoverflow
Solution 6 - IosdchakarovView Answer on Stackoverflow
Solution 7 - IosFattieView Answer on Stackoverflow
Solution 8 - Iosjaya rajView Answer on Stackoverflow
Solution 9 - IosAnil shuklaView Answer on Stackoverflow
Solution 10 - IosDesignerdView Answer on Stackoverflow
Solution 11 - IosmkkrolikView Answer on Stackoverflow
Solution 12 - IoszumzumView Answer on Stackoverflow
Solution 13 - IosMikeView Answer on Stackoverflow
Solution 14 - IosKasun Udara JayasekaraView Answer on Stackoverflow