I need to create something like an infinite loop in my AVQueuePlayer
. Especially, I want to replay the whole NSArray
of AVPlayerItem
s
This is it pretty much from scratch. The components are:
(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.
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]];
}
}
}
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.
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];
}