Replay items in AVQueuePlayer after last
Asked Answered
L

5

7

I need to create something like an infinite loop in my AVQueuePlayer. Especially, I want to replay the whole NSArray of AVPlayerItems once the last component finishes playing.

I must admit that I actually do not have any idea how to achieve this and hope you can give me some clues.

Lamb answered 22/6, 2012 at 12:16 Comment(8)
Are you stuck at this point or you just need to create it from starting point ?Enfield
I actually now how to create it and play all the AVQueuePlayers, I'm now looking for restart the player when the last QVPlayerItem's done.Lamb
-(void) playVideoAtIndex:(NSInteger)index{ [self performSelector:@selector(setObservationInfo)]; currentIndex = index; AVPlayerItem *videoItem = [AVPlayerItem playerItemWithURL:[NSURL fileURLWithPath:[arrVideoList objectAtIndex:index]]]; } where you need to check, if (currentIndex < [arrVideoList count]-1) { currentIndex++; } else { currentIndex = 0; } [self playVideoAtIndex:currentIndex];Enfield
So you are using an array with videos? This array contains the path to the video right?Lamb
Yes exactly,you just need to pass the path,and when you get last one,you can reset back to first index.Enfield
I going to studie this more further, really nice from you. Thank you.Lamb
If you face any problem,do ask,will clear you problem. :)Enfield
@Edelweiss, I am also looking for the same. did you find any solution for this?Douglas
M
4

best way to loop a sequence of videos in AVQueuePlayer.

observe for each playeritem in AVQueuePlayer.

queuePlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
for(AVPlayerItem *item in items) {
    [[NSNotificationCenter defaultCenter] addObserver:self 
            selector:@selector(nextVideo:) 
            name:AVPlayerItemDidPlayToEndTimeNotification 
            object:item ];
}

on each nextVideo insert the currentItem again to queue it for playback. make sure to seek to zero for each item. after advanceToNextItem the AVQueuePlayer will remove the currentItem from queue.

-(void) nextVideo:(NSNotification*)notif {
    AVPlayerItem *currItem = notif.userInfo[@"object"];
    [currItem seekToTime:kCMTimeZero];
    [queuePlayer advanceToNextItem];
    [queuePlayer insertItem:currItem afterItem:nil];
}
Marniemaro answered 18/2, 2015 at 12:35 Comment(1)
This worked for me after changing notif.userInfo[@"object"] to notif.object (iOS 9).Dinosaurian
A
2

This is it pretty much from scratch. The components are:

  1. Create a queue that is an NSArray of AVPlayerItems.
  2. As each item is added to the queue, set up a NSNotificationCenter observer to wake up when the video reaches the end.
  3. In the observer's selector, tell the AVPlayerItem that you want to loop to go back the the beginning, then tell the player to play.

(NOTE: The AVPlayerDemoPlaybackView comes from the Apple "AVPlayerDemo" sample. Simply a subclass of UIView with a setter)

BOOL videoShouldLoop = YES;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSMutableArray *videoQueue = [[NSMutableArray alloc] init];
AVQueuePlayer *mPlayer;
AVPlayerDemoPlaybackView *mPlaybackView;

// You'll need to get an array of the files you want to queue as NSARrray *fileList:
for (NSString *videoPath in fileList) {
    // Add all files to the queue as AVPlayerItems
    if ([fileManager fileExistsAtPath: videoPath]) {
        NSURL *videoURL = [NSURL fileURLWithPath: videoPath];
        AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL: videoURL];
        // Setup the observer
        [[NSNotificationCenter defaultCenter] addObserver: self
                                                 selector: @selector(playerItemDidReachEnd:)
                                                     name: AVPlayerItemDidPlayToEndTimeNotification
                                                   object: playerItem];
        // Add the playerItem to the queue
        [videoQueue addObject: playerItem];
    }
}
// Add the queue array to the AVQueuePlayer
mPlayer = [AVQueuePlayer queuePlayerWithItems: videoQueue];
// Add the player to the view
[mPlaybackView setPlayer: mPlayer];
// If you should only have one video, this allows it to stop at the end instead of blanking the display
if ([[mPlayer items] count] == 1) {
    mPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
}
// Start playing
[mPlayer play];


- (void) playerItemDidReachEnd: (NSNotification *)notification
{
    // Loop the video
    if (videoShouldLoop) {
        // Get the current item
        AVPlayerItem *playerItem = [mPlayer currentItem];
        // Set it back to the beginning
        [playerItem seekToTime: kCMTimeZero];
        // Tell the player to do nothing when it reaches the end of the video
        //  -- It will come back to this method when it's done
        mPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
        // Play it again, Sam
        [mPlayer play];
    } else {
        mPlayer.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
    }
}

That's it! Let me know of something needs further explanation.

Anticlockwise answered 16/1, 2013 at 12:30 Comment(2)
What should i do in case i add added 3video in the player.And after completion of all the 3video the last video will play in infinite loopDaimyo
The logic here does not obtain the OP's desired behavior. It loops the last item not the entire array of player items.Tole
D
0

I figured out a solution to loop over all the videos in my video queue, not just one. First I initialized my AVQueuePlayer:

- (void)viewDidLoad
{
    NSMutableArray *vidItems = [[NSMutableArray alloc] init];
    for (int i = 0; i < 5; i++)
    {
        // create file name and make path
        NSString *fileName = [NSString stringWithFormat:@"intro%i", i];
        NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:@"mov"];
        NSURL *movieUrl = [NSURL fileURLWithPath:path];
        // load url as player item
        AVPlayerItem *item = [AVPlayerItem playerItemWithURL:movieUrl];
        // observe when this item ends
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(playerItemDidReachEnd:)
                                                     name:AVPlayerItemDidPlayToEndTimeNotification
                                                   object:item];
        // add to array
        [vidItems addObject:item];


    }
    // initialize avqueueplayer
    _moviePlayer = [AVQueuePlayer queuePlayerWithItems:vidItems];
    _moviePlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;

    // create layer for viewing
    AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:_moviePlayer];

    layer.frame = self.view.bounds;
    layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    // add layer to uiview container
    [_movieViewContainer.layer addSublayer:layer];
}

When the notification is posted

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

    // keep playing the queue
    [_moviePlayer advanceToNextItem];
    // if this is the last item in the queue, add the videos back in
    if (_moviePlayer.items.count == 1)
    {
        // it'd be more efficient to make this a method being we're using it a second time
        for (int i = 0; i < 5; i++)
        {
            NSString *fileName = [NSString stringWithFormat:@"intro%i", i];
            NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:@"mov"];
            NSURL *movieUrl = [NSURL fileURLWithPath:path];

            AVPlayerItem *item = [AVPlayerItem playerItemWithURL:movieUrl];

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

             // the difference from last time, we're adding the new item after the last item in our player to maintain the order
            [_moviePlayer insertItem:item afterItem:[[_moviePlayer items] lastObject]];
        }
    }
}
Dynamiter answered 13/2, 2015 at 22:34 Comment(0)
I
0

In Swift 4, you can use something like the following:

        NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: nil) {

            notification in

            print("A PlayerItem just finished playing !")

            currentVideo += 1

            if(currentVideo >= videoPaths.count) {

               currentVideo = 0
            }

            let url = URL(fileURLWithPath:videoPaths[currentVideo])
            let playerItem = AVPlayerItem.init(url: url)

            player.insert(playerItem, after: nil)
        }

From what I understand the AVQueuePlayer will remove the last item played, so you need to keep adding the video just played to the queue, otherwise it will actually run out of videos to play! You can't just .seek to 0 and .play() again, that doesn't work.

Isometrics answered 20/5, 2019 at 17:0 Comment(0)
M
0

working on iOS 14 as of March 2021

Just wanted to post my solution as a new coder this took me a while to figure out. my solution loops a avqueuplayer inside of a UIView without using AVLooper. The idea came from raywenderlich. You can ignore the UIView wrapper ect. The idea is to use the NSKeyValeObservation and not the notification center as everyone else suggests.

class QueuePlayerUIView: UIView {
private var playerLayer = AVPlayerLayer()
private var playerLooper: AVPlayerLooper?

@objc let queuePlayer = AVQueuePlayer()

var token: NSKeyValueObservation?

var clips = [URL]()

func addAllVideosToPlayer() {
    for video in clips {
        let asset = AVURLAsset(url: video)
        let item = AVPlayerItem(asset: asset)
        queuePlayer.insert(item, after: queuePlayer.items().last)
      }
}

init(videosArr: [URL]) {
    super.init(frame: .zero)
    
    self.clips = videosArr

    addAllVideosToPlayer()
    playerLayer.player = queuePlayer
    playerLayer.videoGravity = .resizeAspectFill
    layer.addSublayer(playerLayer)
    queuePlayer.play()

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

override func layoutSubviews() {
    super.layoutSubviews()
    playerLayer.frame = bounds
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

}

Motorcycle answered 14/3, 2021 at 3:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.