Looping a video with AVFoundation AVPlayer?

IosObjective CSwiftAvfoundationAvplayer

Ios Problem Overview


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.

Ios Solutions


Solution 1 - Ios

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

Solution 2 - Ios

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.

Solution 3 - Ios

In Swift:

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

when setting up the player:

avPlayer.actionAtItemEnd = AVPlayerActionAtItemEnd.None
        
NSNotificationCenter.defaultCenter().addObserver(self, 
                                                 selector: "playerItemDidReachEnd:", 
                                                 name: AVPlayerItemDidPlayToEndTimeNotification, 
                                                 object: avPlayer.currentItem)

this will prevent the player to pause at the end.

in the notification:

func playerItemDidReachEnd(notification: NSNotification) {
    if let playerItem: AVPlayerItem = notification.object as? AVPlayerItem {
        playerItem.seekToTime(kCMTimeZero)
    }
}

Swift3

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

this will rewind the movie.

Do not forget to unregister the notification when releasing the player.

Solution 4 - Ios

Here's what I ended up doing to prevent the pause-hiccup issue:

Swift:

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
                                       object: nil,
                                       queue: nil) { [weak self] note in
                                        self?.avPlayer.seek(to: kCMTimeZero)
                                        self?.avPlayer.play()
}

Objective C:

__weak typeof(self) weakSelf = self; // prevent memory cycle
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
[noteCenter addObserverForName:AVPlayerItemDidPlayToEndTimeNotification
						object:nil
						 queue:nil
					usingBlock:^(NSNotification *note) {
						[weakSelf.avPlayer seekToTime:kCMTimeZero];
						[weakSelf.avPlayer play];
					}];

NOTE: I didn't use avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone as it's not needed.

Solution 5 - Ios

Swift 5:

I've made some slight adjustments from previous answers like adding the playerItem to the queue before add it to the playerLayer.

let playerItem = AVPlayerItem(url: url)
let player = AVQueuePlayer(playerItem: playerItem)
let playerLayer = AVPlayerLayer(player: player)

playerLooper = AVPlayerLooper(player: player, templateItem: playerItem)

playerLayer.frame = cell.eventImage.bounds
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill

// Add the playerLayer to a UIView.layer

player.play()

And make playerLooper a property of your UIViewController, otherwise the video may only play once.

Solution 6 - Ios

I recommend using AVQueuePlayer to loop your videos seamlessly. Add the notification observer

AVPlayerItemDidPlayToEndTimeNotification

and in its selector, loop your video

AVPlayerItem *video = [[AVPlayerItem alloc] initWithURL:videoURL];
[self.player insertItem:video afterItem:nil];
[self.player play];

Solution 7 - Ios

Solution 8 - Ios

SWIFT 5:

private var player: AVPlayer?

override func viewDidLoad() {
    super.viewDidLoad()

    NotificationCenter.default.addObserver(self,
                                           selector: #selector(restartVideo),
                                           name: .AVPlayerItemDidPlayToEndTime,
                                           object: self.player?.currentItem)
}

@objc func restartVideo() {
    player?.pause()
    player?.currentItem?.seek(to: CMTime.zero, completionHandler: { _ in
        self.player?.play()
    })
}

Solution 9 - Ios

this worked for me without hiccup issues, point is in pausing the player before calling seekToTime method:

  1. init AVPlayer

     let url = NSBundle.mainBundle().URLForResource("loop", withExtension: "mp4")
     let playerItem = AVPlayerItem(URL: url!)
    
     self.backgroundPlayer = AVPlayer(playerItem: playerItem)
     let playerLayer = AVPlayerLayer(player: self.backgroundPlayer)
    
     playerLayer.frame = CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height)
     self.layer.addSublayer(playerLayer)
     self.backgroundPlayer!.actionAtItemEnd = .None
     self.backgroundPlayer!.play()
    
  2. registering notification

     NSNotificationCenter.defaultCenter().addObserver(self, selector: "videoLoop", name: AVPlayerItemDidPlayToEndTimeNotification, object: self.backgroundPlayer!.currentItem)
    
  3. videoLoop function

     func videoLoop() {
       self.backgroundPlayer?.pause()
       self.backgroundPlayer?.currentItem?.seekToTime(kCMTimeZero)
       self.backgroundPlayer?.play()
     }
    

Solution 10 - Ios

my solution in objective-c wth AVQueuePlayer - it seems you have to duplicate the AVPlayerItem and upon finishing playback of first element instantly add another copy. "Kind of" makes sense and works for me without any hiccup

NSURL *videoLoopUrl; 
// as [[NSBundle mainBundle] URLForResource:@"assets/yourVideo" withExtension:@"mp4"]];
AVQueuePlayer *_loopVideoPlayer;

+(void) nextVideoInstance:(NSNotification*)notif
{
 AVPlayerItem *currItem = [AVPlayerItem playerItemWithURL: videoLoopUrl];

[[NSNotificationCenter defaultCenter] addObserver:self
                                      selector:@selector(nextVideoInstance:)
                                      name:AVPlayerItemDidPlayToEndTimeNotification
                                      object: currItem];

 [_loopVideoPlayer insertItem:currItem afterItem:nil];
 [_loopVideoPlayer advanceToNextItem];

}
 
+(void) initVideoPlayer {
 videoCopy1 = [AVPlayerItem playerItemWithURL: videoLoopUrl];
 videoCopy2 = [AVPlayerItem playerItemWithURL: videoLoopUrl];
 NSArray <AVPlayerItem *> *dummyArray = [NSArray arrayWithObjects: videoCopy1, videoCopy2, nil];
 _loopVideoPlayer = [AVQueuePlayer queuePlayerWithItems: dummyArray];

 [[NSNotificationCenter defaultCenter] addObserver: self
                                      selector: @selector(nextVideoInstance:)
                                      name: AVPlayerItemDidPlayToEndTimeNotification
                                      object: videoCopy1];

 [[NSNotificationCenter defaultCenter] addObserver: self
                                      selector: @selector(nextVideoInstance:)
                                      name: AVPlayerItemDidPlayToEndTimeNotification
                                      object: videoCopy2];
}

https://gist.github.com/neonm3/06c3b5c911fdd3ca7c7800dccf7202ad

Solution 11 - Ios

For Swift 3 & 4

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

Solution 12 - Ios

I couldn't find my solution among answers. It can be helpful to observe boundary time specified to asset duration. When observer is triggered, seek to the start and replay.

player?.addBoundaryTimeObserver(forTimes: [NSValue(time: asset.duration)], queue: .main) { [weak self] in
    self?.player?.seek(to: .zero, completionHandler: { [weak self] _ in
        self?.player?.play()
    })
}

Solution 13 - Ios

After loading the video into the AVPlayer (via its AVPlayerItem, of course):

 [self addDidPlayToEndTimeNotificationForPlayerItem:item];

The addDidPlayToEndTimeNotificationForPlayerItem method:

- (void)addDidPlayToEndTimeNotificationForPlayerItem:(AVPlayerItem *)item
{
    if (_notificationToken)
        _notificationToken = nil;
    
    /*
     Setting actionAtItemEnd to None prevents the movie from getting paused at item end. A very simplistic, and not gapless, looped playback.
     */
    _player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
    _notificationToken = [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification object:item queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
        // Simple item playback rewind.
        [[_player currentItem] seekToTime:kCMTimeZero];
    }];
}

In your viewWillDisappear method:

if (_notificationToken) {
        [[NSNotificationCenter defaultCenter] removeObserver:_notificationToken name:AVPlayerItemDidPlayToEndTimeNotification object:_player.currentItem];
        _notificationToken = nil;
    }

In your view controller's interface declaration within the implementation file:

id _notificationToken;

Need to see this up-and-running before you try? Download and run this sample app:

https://developer.apple.com/library/prerelease/ios/samplecode/AVBasicVideoOutput/Listings/AVBasicVideoOutput_APLViewController_m.html#//apple_ref/doc/uid/DTS40013109-AVBasicVideoOutput_APLViewController_m-DontLinkElementID_8

In my app, which uses this very code, there is no pause whatsoever between the end of the video and the beginning. In fact, depending on the video, there's no way for me to tell the video is at the beginning again, save the timecode display.

Solution 14 - Ios

you can add a AVPlayerItemDidPlayToEndTimeNotification observer and replay video from start in selector, code like below

 //add observer
[[NSNotificationCenter defaultCenter] addObserver:self                                                 selector:@selector(playbackFinished:)                                                     name:AVPlayerItemDidPlayToEndTimeNotification
object:_aniPlayer.currentItem];

-(void)playbackFinished:(NSNotification *)notification{
    [_aniPlayer seekToTime:CMTimeMake(0, 1)];//replay from start
    [_aniPlayer play];
}

Solution 15 - Ios

What I did is to make it loop playing, like my code below:

[player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0)
queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
    float current = CMTimeGetSeconds(time);
    float total = CMTimeGetSeconds([playerItem duration]);
    if (current >= total) {
        [[self.player currentItem] seekToTime:kCMTimeZero];
        [self.player play];
    }
}];

Solution 16 - Ios

The following is working for me in WKWebView in swift 4.1 The main part of the WKWebView in WKwebviewConfiguration

wkwebView.navigationDelegate = self
wkwebView.allowsBackForwardNavigationGestures = true
self.wkwebView =  WKWebView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height))
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
wkwebView = WKWebView(frame: wkwebView.frame, configuration: config)
self.view.addSubview(wkwebView)
self.wkwebView.load(NSURLRequest(url: URL(string: self.getUrl())!) as URLRequest)

Solution 17 - Ios

Swift 4.2 in Xcode 10.1.

Yes, there is a relatively easy way of looping a video in AVKit/AVFoundation using AVQueuePlayer(), Key-Value Observation (KVO) technique and a token for it.

This definitely works for a bunch of H.264/HEVC videos with a minimal burden for CPU.

Here's a code:

import UIKit
import AVFoundation
import AVKit

class ViewController: UIViewController {

    private let player = AVQueuePlayer()
    let clips = ["01", "02", "03", "04", "05", "06", "07"]
    private var token: NSKeyValueObservation?
    var avPlayerView = AVPlayerViewController()

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(true)

        self.addAllVideosToPlayer()
        present(avPlayerView, animated: true, completion: { self.player.play() })
    }

    func addAllVideosToPlayer() {
        avPlayerView.player = player

        for clip in clips {
            let urlPath = Bundle.main.path(forResource: clip, ofType: "m4v")!
            let url = URL(fileURLWithPath: urlPath)
            let playerItem = AVPlayerItem(url: url)
            player.insert(playerItem, after: player.items().last)

            token = player.observe(\.currentItem) { [weak self] player, _ in
                if self!.player.items().count == 1 { self?.addAllVideosToPlayer() }
            }
        }
    }
}

Solution 18 - Ios

Swift 5

import UIKit
import AVKit
import AVFoundation

class VideoViewControler: UIViewController {
    
    // init video background and its path
    var player: AVPlayer?
    let videoURL: NSURL = Bundle.main.url(forResource: "farmer_watering", withExtension: "mp4")! as NSURL
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        
        // begin implementing the avplayer
        player = AVPlayer(url: videoURL as URL)
        player?.actionAtItemEnd = .none
        player?.isMuted = true
        
        let playerLayer = AVPlayerLayer(player: player)
        playerLayer.videoGravity = AVLayerVideoGravity.resizeAspect
        playerLayer.zPosition = -1
        
        playerLayer.frame = view.frame
        
        view.layer.addSublayer(playerLayer)
        
        player?.play()
        
        // add observer to watch for video end in order to loop video
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(loopVideo),
            name: .AVPlayerItemDidPlayToEndTime,
            object: self.player?.currentItem
        )
    }
    
    // if video ends, will restart
    func playerItemDidReachEnd() {
        player?.seek(to: CMTime.zero)
    }
    
    // add this loop at the end, after viewDidLoad
    @objc func loopVideo() {
        playerItemDidReachEnd()
        player?.play()
    }
}

Solution 19 - Ios

use AVPlayerViewController below code, its working for me

        let type : String! = "mp4"
        let targetURL : String? = NSBundle.mainBundle().pathForResource("Official Apple MacBook Air Video   YouTube", ofType: "mp4")
        
        let videoURL = NSURL(fileURLWithPath:targetURL!)
        

        let player = AVPlayer(URL: videoURL)
        let playerController = AVPlayerViewController()
        
        playerController.player = player
        self.addChildViewController(playerController)
        self.playView.addSubview(playerController.view)
        playerController.view.frame = playView.bounds
        
        player.play()

All controls to be showed, hope its helpful

Solution 20 - Ios

/* "numberOfLoops" is the number of times that the sound will return to the beginning upon reaching the end. 
A value of zero means to play the sound just once.
A value of one will result in playing the sound twice, and so on..
Any negative number will loop indefinitely until stopped.
*/
@property NSInteger numberOfLoops;

This property is already defined inside AVAudioPlayer. Hope this can help you. I'm using Xcode 6.3.

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
QuestionorjView Question on Stackoverflow
Solution 1 - IosBastianView Answer on Stackoverflow
Solution 2 - IosNabhaView Answer on Stackoverflow
Solution 3 - IosKing-WizardView Answer on Stackoverflow
Solution 4 - IosIslamView Answer on Stackoverflow
Solution 5 - IosNSCoderView Answer on Stackoverflow
Solution 6 - IoskevinnguyView Answer on Stackoverflow
Solution 7 - Iosuser2581875View Answer on Stackoverflow
Solution 8 - IosNicolai HarboView Answer on Stackoverflow
Solution 9 - IosVojtaView Answer on Stackoverflow
Solution 10 - Iosneon M3View Answer on Stackoverflow
Solution 11 - Iosvp2698View Answer on Stackoverflow
Solution 12 - IosRashidView Answer on Stackoverflow
Solution 13 - IosJames BushView Answer on Stackoverflow
Solution 14 - IosshujucnView Answer on Stackoverflow
Solution 15 - IosJohnnyView Answer on Stackoverflow
Solution 16 - IosNrvView Answer on Stackoverflow
Solution 17 - IosAndy JazzView Answer on Stackoverflow
Solution 18 - IosHamid ZandiView Answer on Stackoverflow
Solution 19 - IosIyyappan RaviView Answer on Stackoverflow
Solution 20 - IosJulzView Answer on Stackoverflow