Looping a video with AVFoundation AVPlayer?

Is there a relatively easy way of looping a video in AVFoundation?

I've created my AVPlayer and AVPlayerLayer like so:

avPlayer = [[AVPlayer playerWithURL:videoUrl] retain];
avPlayerLayer = [[AVPlayerLayer playerLayerWithPlayer:avPlayer] retain];

avPlayerLayer.frame = contentView.layer.bounds;
[contentView.layer addSublayer: avPlayerLayer];

and then I play my video with:

[avPlayer play];

The video plays fine but stops at the end. With the MPMoviePlayerController all you have to do is set its repeatMode property to the right value. There doesn't appear to be a similar property on AVPlayer. There also doesn't seem to be a callback that will tell me when the movie has finished so I can seek to the beginning and play it again.

I'm not using MPMoviePlayerController because it has some serious limitations. I want to be able to play back multiple video streams at once.


You can get a Notification when the player ends. Check AVPlayerItemDidPlayToEndTimeNotification

When setting up the player:

ObjC

  avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone; 

  [[NSNotificationCenter defaultCenter] addObserver:self
                                           selector:@selector(playerItemDidReachEnd:)
                                               name:AVPlayerItemDidPlayToEndTimeNotification
                                             object:[avPlayer currentItem]];

this will prevent the player to pause at the end.

in the notification:

- (void)playerItemDidReachEnd:(NSNotification *)notification {
    AVPlayerItem *p = [notification object];
    [p seekToTime:kCMTimeZero];
}

this will rewind the movie.

Don't forget un unregister the notification when releasing the player.

Swift

avPlayer?.actionAtItemEnd = .none

NotificationCenter.default.addObserver(self,
                                       selector: #selector(playerItemDidReachEnd(notification:)),
                                       name: .AVPlayerItemDidPlayToEndTime,
                                       object: avPlayer?.currentItem)

@objc func playerItemDidReachEnd(notification: Notification) {
    if let playerItem = notification.object as? AVPlayerItem {
        playerItem.seek(to: kCMTimeZero)
    }
}

Swift 4+

@objc func playerItemDidReachEnd(notification: Notification) {
    if let playerItem = notification.object as? AVPlayerItem {
        playerItem.seek(to: CMTime.zero, completionHandler: nil)
    }
}

If it helps, in iOS / tvOS 10, there's a new AVPlayerLooper() that you can use to create seamless looping of video (Swift):

player = AVQueuePlayer()
playerLayer = AVPlayerLayer(player: player)
playerItem = AVPlayerItem(url: videoURL)
playerLooper = AVPlayerLooper(player: player, templateItem: playerItem)
player.play()    

This was presented at WWDC 2016 in "Advances in AVFoundation Playback": https://developer.apple.com/videos/play/wwdc2016/503/

Even using this code, I had a hiccup until I filed a bug report with Apple and got this response:

The movie file having movie duration longer than audio/video tracks is the problem. FigPlayer_File is disabling gapless transition because audio track edit is shorter than the movie duration (15.682 vs 15.787).

You need to either fix the movie files to have the movie duration and track durations to be same length or you can use the time range parameter of AVPlayerLooper (set time range from 0 to duration of audio track)

It turns out that Premiere had been exporting files with an audio track of a slightly different length than the video. In my case it was fine to remove the audio entirely, and that fixed the problem.