96

Simple question I can't seem to find an answer to for some reason.

How do you loop AVPlayer in Swift?

numberOfLoops = -1 only works for AVAudioPlayer

I do need it to loop without any delay / black flash etc. That's why I'm not using MPMoviePlayerViewController.

Thanks for any help.

Code:

    let url_1 = NSURL.fileURLWithPath(outputFilePath_1)

    let asset_1 = AVAsset.assetWithURL(url_1) as? AVAsset
    let playerItem_1 = AVPlayerItem(asset: asset_1)

    let player_1 = AVPlayer(playerItem: self.playerItem_1)

    let playerLayer_1 = AVPlayerLayer(player: self.player_1)

    playerLayer_1!.frame = self.view.frame

    self.view.layer.addSublayer(self.playerLayer_1)

    player_1!.play()
Jonathan Plackett
  • 2,346
  • 2
  • 20
  • 33
  • 3
    AVPlayer uses notifications rather than properties. Essentially you need to listen for the video did end event and then seek to the beginning of the video. Here's an example (objective c, but you get the idea): http://stackoverflow.com/questions/5361145/looping-a-video-with-avfoundation-avplayer – Msencenb Jan 06 '15 at 22:25

8 Answers8

213

Swift 5 (iOS 10.0+)

var playerLooper: AVPlayerLooper! // should be defined in class
var queuePlayer: AVQueuePlayer!
...

let asset: AVAsset = ... // AVAsset with its 'duration' property value loaded
let playerItem = AVPlayerItem(asset: asset)
self.queuePlayer = AVQueuePlayer(playerItem: playerItem)

// Create a new player looper with the queue player and template item
self.playerLooper = AVPlayerLooper(player: queuePlayer, templateItem: playerItem)

< iOS 10.0

All below works in any iOS version. But, for < iOS 10.0 it's the only solution.

Swift 4

var player: AVPlayer!

...

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: .main) { [weak self] _ in
    self?.player?.seek(to: CMTime.zero)
    self?.player?.play()
}

Swift 3

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: .main) { [weak self] _ in
    self?.player?.seek(to: kCMTimeZero)
    self?.player?.play()
}

Swift 2

After AVPlayerItem is configured and player is created:

var player: AVPlayer!

...

// Invoke after player is created and AVPlayerItem is specified
NSNotificationCenter.defaultCenter().addObserver(self,
    selector: "playerItemDidReachEnd:",
    name: AVPlayerItemDidPlayToEndTimeNotification,
    object: self.player.currentItem)

...
 
func playerItemDidReachEnd(notification: NSNotification) {
    self.player.seekToTime(kCMTimeZero)
    self.player.play()
}

Don't forget to import AVFoundation

Alexander Volkov
  • 7,904
  • 1
  • 47
  • 44
  • 4
    You could add in `queue: .main` and then you wouldn't need to dispatch onto the main queue. – David Beck Mar 07 '17 at 16:04
  • The is the truly answer, other answers are not working for me (swift 3). – changbenny Mar 27 '17 at 06:17
  • first time this notification call after 5 sec. How to handle this? – Shahbaz Akram Mar 28 '17 at 05:27
  • Why do I need to add `self.player.currentItem` on the object if I'm not going to use it on `playerItemDidReachEnd`? – Zonily Jame Sep 07 '17 at 05:47
  • @ZonilyJame I'm not sure what you mean. There is no currentItem assignment to the object. – Alexander Volkov Sep 07 '17 at 17:15
  • I mean on This part of the code `NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerItemDidReachEnd:", name: AVPlayerItemDidPlayToEndTimeNotification, object: self.player.currentItem)` you are passing self.player.currentItem to the object parameter. – Zonily Jame Sep 08 '17 at 01:19
  • 1
    in the swift 3 solution using self. inside the dispatch block will cause to retain the object. – glm4 Sep 13 '17 at 19:32
  • @ZonilyJame Because it is the source of the events you subscribe to. – Alexander Volkov Sep 16 '17 at 22:30
  • But isn't the `object` just an item you pass to and from the `observer` and `sender` that you can use later like this. `let someObject = notification.object as? SomeDataType` – Zonily Jame Sep 18 '17 at 02:51
  • 1
    Swift 4: `NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem, queue: .main) { _ in self.player?.seek(to: CMTime.zero) self.player?.play() }` – Jordan Oct 31 '18 at 19:10
  • This solution doesn't provide seamless looping - there's a judder/pause at the end of the video (especially on older devices). This is because the seek doesn't happen until after playback finishes (vs preheating the seek). Use the `AVPlayerLooper` solution below for seamless looping (iOS 10+). – xaphod Jul 09 '20 at 18:28
  • @Nikunj Kumbhani why you edited? – Alexander Volkov Nov 03 '21 at 11:56
62

Starting from iOS 10, there's no need to use notifications to loop a video, simply use a AVPlayerLooper, just like this:

let asset = AVAsset(url: videoURL)
let item = AVPlayerItem(asset: asset)
let player = AVQueuePlayer(playerItem: item)
videoLooper = AVPlayerLooper(player: player, templateItem: item)

make sure this looper is created outside of a function scope, in case it stops when function returns.

Bright
  • 5,699
  • 2
  • 50
  • 72
  • 4
    thank god for this. `KVO` was simply a hackish way of doing things. – Anjan Biswas Jan 14 '18 at 03:58
  • 1
    This method does indeed work, however I found that any `AVPlayerItemMetadataOutput` attached to the playerItem will not be triggered (as of iOS 11.4.1 at least), which forced me to return to the `AVPlayerItemDidPlayToEndTimeNotification` method – Jaysen Marais Aug 13 '18 at 12:26
  • Should be `let videoLooper` or you'll get `Use of unresolved identifier 'videoLooper'`. – Alexander Flenniken Aug 27 '18 at 08:46
  • 4
    @AlexanderFlenniken the reason BrightFuture doesn't have "let videoLooper" is because as they note, the looper needs to be retained outside the function's scope. So there would be a "var videoLooper" somewhere in the class setup and inside the function here you're just assigning it. If you did "let" inside the setup function and didn't assign it to a property outside the function, it would not be available for the looping to occur when the video is played. – Ben Stahl Apr 20 '19 at 00:31
  • hi i can find method play of videoLooper? – famfamfam Oct 05 '20 at 05:40
  • Also it's possible to write `AVPlayerItem(url: videoURL)` instead of `AVPlayerItem(asset: ...)`. – Roman Aliyev Jun 27 '21 at 11:08
  • How to switch between looping and not looping? I see a disableLooping() in AVPlayerLooper but no function to enable it back? – chitgoks Nov 26 '22 at 04:10
41

@Christopher Pickslay's answer updated for Swift 4:

func loopVideo(videoPlayer: AVPlayer) {
    NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: nil) { notification in
        videoPlayer.seek(to: CMTime.zero)
        videoPlayer.play()
    }
}

But, as I mentioned below his answer, be sure to specify the object as the AVPlayer's player item if you have multiple players.

Ben Stahl
  • 1,139
  • 11
  • 11
31

Alternatively, you can use the block-based NSNotificationCenter API:

func loopVideo(videoPlayer: AVPlayer) {
    NSNotificationCenter.defaultCenter().addObserverForName(AVPlayerItemDidPlayToEndTimeNotification, object: nil, queue: nil) { notification in
        videoPlayer.seekToTime(kCMTimeZero)
        videoPlayer.play()
    }
}
Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92
  • Great answer, very simple. If you have multiple players, make sure you specify the AVPlayer as the "object" parameter, otherwise every registered player will get the notification when any player finished. – Ben Stahl Nov 03 '16 at 08:14
  • I'm playing a video in avplayer and its running in background and when i would like to play another video but its not playing video – Mahesh Narla Jan 25 '17 at 13:17
  • 2
    Correction to @BenStahl 's answer, you're supposed to specify the `AVPlayerItem` as the "object" instead of the `AVPlayer`. So it should be `videoPlayer.currentItem` – Chan Jing Hong Oct 22 '17 at 05:01
25

OK I have worked it out. Thanks Msencenb for pointing me in the right direction with an Objective C answer.

player_1?.actionAtItemEnd = .None

//set a listener for when the video ends   
NSNotificationCenter.defaultCenter().addObserver(self,
        selector: "restartVideoFromBeginning",
        name: AVPlayerItemDidPlayToEndTimeNotification,
        object: player_1?.currentItem)


//function to restart the video
func restartVideoFromBeginning()  {

    //create a CMTime for zero seconds so we can go back to the beginning
    let seconds : Int64 = 0
    let preferredTimeScale : Int32 = 1
    let seekTime : CMTime = CMTimeMake(seconds, preferredTimeScale)

    player_1!.seekToTime(seekTime)

    player_1!.play()

}
Jonathan Plackett
  • 2,346
  • 2
  • 20
  • 33
10

I've managed to create a seamless video looping for OSX in swift 3. It should work on iOS and with little modification on swift 2 as well.

var player : AVQueuePlayer!

// Looping video initial call
internal func selectVideoWithLoop(url : URL)
{
    let asset = AVAsset(url: url)
    player.pause()
    let playerItem1 = AVPlayerItem(asset: asset)
    let playerItem2 = AVPlayerItem(asset: asset)
    player.removeAllItems()
    player.replaceCurrentItem(with: playerItem1)
    player.insert(playerItem2, after: playerItem1)
    player.actionAtItemEnd = AVPlayerActionAtItemEnd.advance
    player.play()

    let selector = #selector(ViewController.playerItemDidReachEnd(notification:))
    let name = NSNotification.Name.AVPlayerItemDidPlayToEndTime
    // removing old observer and adding it again for sequential calls. 
    // Might not be necessary, but I like to unregister old notifications.
    NotificationCenter.default.removeObserver(self, name: name, object: nil)
    NotificationCenter.default.addObserver(self, selector: selector, name: name, object: nil)
}

// Loop video with threadmill pattern
// Called by NotificationCenter, don't call directly
func playerItemDidReachEnd(notification: Notification)
{
    let item = player.currentItem!
    player.remove(item)
    item.seek(to: kCMTimeZero)
    player.insert(item, after: nil)
}

When you want to change the video, just call selectVideoWithLoop with a different url again.

Just A Minnion
  • 161
  • 2
  • 9
10

Swift 5.5:

func loopVideo(videoPlayer: AVPlayer) {
  NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: nil) { notification in
    videoPlayer.seek(to: .zero)
    videoPlayer.play()
  }
}
SwiftyLifestyle
  • 416
  • 5
  • 17
  • This is great, just needs an initial call to videoPlayer.play() somewhere in the code, if that wasn't obvious. The code above will take care of the looping after the initial play. I would recommend putting the call to videoPlayer.play() in this function, or just using the insides however you want together with an initial call to videoPlayer.play() in your code. – Andy Weinstein Aug 02 '22 at 15:15
  • great answer, no need for any more detail thanks – Fattie Feb 23 '23 at 17:33
5

Swift 3.0:

First check for video end point:-

NotificationCenter.default.addObserver(self, selector: #selector(LoginViewController.playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem)



func videoDidReachEnd() {

        //now use seek to make current playback time to the specified time in this case (O)
        let duration : Int64 = 0 (can be Int32 also)
        let preferredTimeScale : Int32 = 1
        let seekTime : CMTime = CMTimeMake(duration, preferredTimeScale)
        player!.seek(to: seekTime)
        player!.play()
    }
Gourav Joshi
  • 2,419
  • 2
  • 27
  • 45
Akhilesh Sharma
  • 104
  • 1
  • 8