简体   繁体   English

最后重播 AVQueuePlayer 中的项目

[英]Replay items in AVQueuePlayer after last

I need to create something like an infinite loop in my AVQueuePlayer .我需要在我的AVQueuePlayer创建类似无限循环的AVQueuePlayer Especially, I want to replay the whole NSArray of AVPlayerItem s once the last component finishes playing.特别是,我想在最后一个组件播放完毕后重播AVPlayerItem的整个NSArray

I must admit that I actually do not have any idea how to achieve this and hope you can give me some clues.我必须承认,我实际上不知道如何实现这一点,希望你能给我一些线索。

best way to loop a sequence of videos in AVQueuePlayer.在 AVQueuePlayer 中循环播放一系列视频的最佳方式。

observe for each playeritem in AVQueuePlayer.观察 AVQueuePlayer 中的每个 playeritem。

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.在每个 nextVideo 上再次插入 currentItem 以将其排队播放。 make sure to seek to zero for each item.确保为每个项目寻求零。 after advanceToNextItem the AVQueuePlayer will remove the currentItem from queue.在 AdvanceToNextItem 之后,AVQueuePlayer 将从队列中删除 currentItem。

-(void) nextVideo:(NSNotification*)notif {
    AVPlayerItem *currItem = notif.userInfo[@"object"];
    [currItem seekToTime:kCMTimeZero];
    [queuePlayer advanceToNextItem];
    [queuePlayer insertItem:currItem afterItem:nil];
}

This is it pretty much from scratch.这几乎是从头开始的。 The components are:组件是:

  1. Create a queue that is an NSArray of AVPlayerItems.创建一个队列,它是 AVPlayerItems 的 NSArray。
  2. As each item is added to the queue, set up a NSNotificationCenter observer to wake up when the video reaches the end.随着每个项目被添加到队列中,设置一个 NSNotificationCenter 观察者以在视频结束时唤醒。
  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.在观察者的选择器中,告诉 AVPlayerItem 您要循环回到开头,然后告诉播放器播放。

(NOTE: The AVPlayerDemoPlaybackView comes from the Apple "AVPlayerDemo" sample. Simply a subclass of UIView with a setter) (注意:AVPlayerDemoPlaybackView 来自 Apple 的“AVPlayerDemo”示例。只是带有 setter 的 UIView 的子类)

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 :首先我初始化了我的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:在 Swift 4 中,您可以使用以下内容:

        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!据我了解AVQueuePlayer将删除最后播放的项目,因此您需要继续将刚刚播放的视频添加到队列中,否则实际上会用完视频播放! You can't just .seek to 0 and .play() again, that doesn't work.你不能只是.seek到 0 和.play()再次,这是行不通的。

working on iOS 14 as of March 2021截至 2021 年 3 月在 iOS 14 上工作

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.我的解决方案在不使用 AVLooper 的情况下在 UIView 内循环了一个 avqueuplayer。 The idea came from raywenderlich .这个想法来自raywenderlich You can ignore the UIView wrapper ect.您可以忽略 UIView 包装器等。 The idea is to use the NSKeyValeObservation and not the notification center as everyone else suggests.这个想法是使用 NSKeyValeObservation 而不是像其他人建议的那样使用通知中心。

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

} }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM