35

What is the east way to track when song finishes playing with AVPlayer in Swift?

Is there any function which is called when AVPlayer finishes playing, or I should combine timer with AVPlayer class references?

stackich
  • 3,607
  • 3
  • 17
  • 41
Mega4alik
  • 597
  • 3
  • 10
  • 18
  • 7
    **Don't confuse AVPlayer with AVAudioPlayer** - some of the answers below are for AVAudioPlayer. – Fattie Sep 17 '19 at 18:34

8 Answers8

71

Something like this works:

func play(url: NSURL) {
    let item = AVPlayerItem(URL: url)

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

    let player = AVPlayer(playerItem: item)
    player.play()
}

func playerDidFinishPlaying(note: NSNotification) {
    // Your code here
}

Don't forget to remove the observer when you're done (or in deinit)!

Jernej Strasner
  • 4,590
  • 29
  • 22
  • 1
    When I tested it I got this error: "Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[myApp.MyViewController playerDidFinishPlaying]: unrecognized selector sent to instance". Can you help me please? – Ne AS Nov 04 '16 at 15:13
  • @Llg forgot the : in "playerDidFinishPlaying:" – LightMan Jul 18 '17 at 11:27
21

You need to create an object that implements the AVAudioPlayerDelegate protocol, and use that as the delegate of the AVAudioPlayer object. Then link them together, for example:

audioPlayer = try! AVAudioPlayer(contentsOf: audioFileUrl)
audioPlayer.delegate = self

The delegate can implement methods that responds to certain events. This one fires when the audio finishes playing:

func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
    // ...
}
Flimm
  • 136,138
  • 45
  • 251
  • 267
10

for Swift 4.2

func play(url: URL) {
    let item = AVPlayerItem(url: url)
    NotificationCenter.default.addObserver(self, selector: #selector(self.playerDidFinishPlaying(sender:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item)

    let player = AVPlayer(playerItem: item)
    player.play() 
}

@objc func playerDidFinishPlaying(sender: Notification) {
    // Your code here
}
laka
  • 644
  • 8
  • 23
Return Zero
  • 424
  • 4
  • 15
7

Another version for Swift 3

NotificationCenter.default.addObserver(self, selector: #selector(self.playerDidFinishPlaying(sender:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item)

func playerDidFinishPlaying(sender: Notification) {

    // Do Something
}
Mike Carpenter
  • 410
  • 4
  • 12
6
import AVFoundation

var AVPlayerCustom:AVAudioPlayer = AVAudioPlayer()


class PlayerModule: NSObject, AVAudioPlayerDelegate {

    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        print("Finish")
    }

    func playWithData(data: Data, proc: Int) {
        //print(data)

        do {

            AVPlayerCustom = try AVAudioPlayer(data: data)

            AVPlayerCustom.delegate = self as! AVAudioPlayerDelegate

            AVPlayerCustom.prepareToPlay()
            AVPlayerCustom.play()


        }
        catch {
            print("error1")
        }
    }
}
kometen
  • 6,536
  • 6
  • 41
  • 51
Lizzz
  • 79
  • 1
  • 1
4

a more complete solution is here:

import UIKit
import AVFoundation
import MediaPlayer


class ViewController: UIViewController,AVAudioPlayerDelegate {

    var player: AVAudioPlayer = AVAudioPlayer()

    @IBAction func play(_ sender: UIButton) {
        player.play()
        player.currentTime=14*60-10
        print(player.currentTime)
    }
    @IBAction func pause(_ sender: UIButton) {
        player.pause()
    }
    @IBAction func replay(_ sender: UIButton) {
        player.currentTime=0
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        do{
            let audioPath = Bundle.main.path(forResource: "elon", ofType: "mp3")
            player = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: audioPath!))
            player.prepareToPlay()
            player.delegate = self
        }
        catch{
            print(error)
        }
    }

    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool){
        print(flag)
        print("here")
        if flag == true{

        }
    }


}
MartianMartian
  • 1,753
  • 1
  • 18
  • 26
3

For Swift3 you will need to change as follows:

func play(url: NSURL) {
    let item = AVPlayerItem(URL: url)
    NotificationCenter.default.addObserver(self,selector:Selector("playerDidFinishPlaying"), name:  NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item)

    let player = AVPlayer(playerItem: item)
    player.play() 
}

func playerDidFinishPlaying() {
// Your code here
} 
stackich
  • 3,607
  • 3
  • 17
  • 41
Mohd Ahmed
  • 307
  • 1
  • 9
0

SwiftUI

In SwiftUI I cannot use @objc functions or delegates. @objc exposed functions do not work in structs. One way would be to create a class or much simpler to use the AVAudioPlayer instance property isPlaying. See the Apple documentation.

This is the code I use to check if the audio finished playing to update the play/pause button accordingly. First I keep the value in a @State property:

@State private var playing: Bool = false

Then in onAppear I use a scheduled timer to check if the player is still playing. I need the timer anyway to update the progress of the audio.

.onAppear {
    [...]
    Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
        if !audioPlayer.isPlaying {
            playing = false
        }
    }
}

And so the play/pause button gets automatically updated when the track finishes playing

Button(action: {
    if audioPlayer.isPlaying {
        playing = false
        self.audioPlayer.pause()
    } else if !audioPlayer.isPlaying {
        playing = true
        self.audioPlayer.play()
    }
}) {
Image(systemName: playing ? "pause.circle.fill" : "play.circle.fill")
    .resizable()
    .frame(width: 50, height: 50)
    .aspectRatio(contentMode: .fit)
}
stackich
  • 3,607
  • 3
  • 17
  • 41
multitudes
  • 2,898
  • 2
  • 22
  • 29