Зацикливаете видео с AVFoundation AVPlayer?

143

Есть ли относительно простой способ зациклить видео в AVFoundation?

Я создал свои AVPlayer и AVPlayerLayer следующим образом:

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

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

а затем я воспроизвожу свое видео с помощью:

[avPlayer play];

Видео воспроизводится нормально, но останавливается в конце. С MPMoviePlayerController все, что вам нужно сделать, это установить для его repeatModeсвойства правильное значение. Похоже, что в AVPlayer нет аналогичного свойства. Также, похоже, нет обратного вызова, который сообщит мне, когда фильм закончится, поэтому я могу перейти к началу и воспроизвести его снова.

Я не использую MPMoviePlayerController, потому что у него есть серьезные ограничения. Я хочу иметь возможность воспроизводить несколько видеопотоков одновременно.

orj
источник
1
См. Этот ответ для получения ссылки на фактический рабочий код: stackoverflow.com/questions/7822808/…
MoDJ

Ответы:

276

Вы можете получить уведомление, когда игра закончится. ПроверьтеAVPlayerItemDidPlayToEndTimeNotification

При настройке плеера:

ObjC

  avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone; 

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

это предотвратит паузу игрока в конце.

в уведомлении:

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

это перемотает фильм.

Не забудьте отменить регистрацию уведомления при освобождении плеера.

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)
    }
}
Bastian
источник
6
... и если вы хотите воспроизвести его сразу после [p seekToTime: kCMTimeZero] (своего рода «перемотка назад»), просто выполните [p play] снова.
thomax
24
в этом не должно быть необходимости ... если вы сделаете avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;это, это не остановится, поэтому не нужно настраивать его для воспроизведения снова
Бастиан
Чтобы звук снова заиграл, нужно [player play];после перемотки позвонить .
Kenny Winker
12
Это решение работает, но не совсем безупречно. У меня очень маленькая пауза. Я делаю что-то неправильно?
Joris van Liempd iDeveloper
3
@Praxiteles, вам нужно отменить регистрацию, когда представление разрушено, или когда вы удаляете видеоплеер или что-то еще, что вы делаете). Вы можете использовать, [[NSNotificationCenter defaultCenter] removeObserver:self];например, когда selfслушаете уведомления.
Bastian
66

Если это поможет, в iOS / tvOS 10 есть новый AVPlayerLooper (), который вы можете использовать для создания бесшовного цикла видео (Swift):

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

Это было представлено на WWDC 2016 в разделе «Достижения в области воспроизведения AVFoundation»: https://developer.apple.com/videos/play/wwdc2016/503/

Даже при использовании этого кода у меня возникала икота, пока я не отправил отчет об ошибке в Apple и не получил такой ответ:

Проблема с файлом фильма, продолжительность которого превышает продолжительность аудио / видео. FigPlayer_File отключает переход без пауз, потому что редактирование звуковой дорожки короче, чем продолжительность фильма (15,682 против 15,787).

Вам нужно либо исправить файлы фильмов, чтобы продолжительность фильма и продолжительность треков были одинаковой длины, либо вы можете использовать параметр временного диапазона AVPlayerLooper (установите временной диапазон от 0 до продолжительности звуковой дорожки)

Оказалось, что Premiere экспортировал файлы со звуковой дорожкой, длина которой немного отличалась от длины видео. В моем случае можно было полностью удалить звук, и это устранило проблему.

Набха
источник
3
Больше у меня ничего не работало. Я использую AVPlayerLooper, и эта ошибка, и устранение несоответствия между длинами видео / аудио решило проблему.
Кевин Хип
1
Спасибо за информацию о Premiere. Я добавил timeRange в лупер, и это устранило мою проблему с "мигающим видео".
Александр Фленникен
1
@ Набха, можно ли использовать это в течение определенного периода времени в видео? Например, видео длится 60 секунд, но я хочу зациклить только первые 10 секунд
Лэнс Самария,
@LanceSamaria Похоже! Инициализация принимает диапазон времени для прохождения цикла, см .: developer.apple.com/documentation/avfoundation/avplayerlooper/ ...
Набха
@ Набха, спасибо за помощь, очень признателен. Ура!
Лэнс Самария,
28

В Swift :

Вы можете получить уведомление, когда проигрыватель закончится ... проверьте AVPlayerItemDidPlayToEndTimeNotification

при настройке плеера:

avPlayer.actionAtItemEnd = AVPlayerActionAtItemEnd.None

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

это предотвратит паузу игрока в конце.

в уведомлении:

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)

это перемотает фильм.

Не забудьте отменить регистрацию уведомления при отпускании плеера.

Король-волшебник
источник
4
Я вижу небольшой сбой между циклами при использовании этого метода. Я открыл свое видео в Adobe Premier и убедился, что в нем нет дублирующихся кадров, поэтому кратковременное прерывание определенно происходит при воспроизведении. Кто-нибудь нашел способ быстро и быстро сделать цикл видео?
SpaceManGalaxy
@SpaceManGalaxy Я тоже заметил икоту. Вы нашли способ исправить этот глюк?
Lance Samaria
19

Вот что я в итоге сделал, чтобы предотвратить проблему паузы и икоты:

Swift:

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

Цель 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];
                    }];

ПРИМЕЧАНИЕ: я не использовал, так avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNoneкак он не нужен.

Ислам К.
источник
2
@KostiaDombrovsky вы пробовали на реальном устройстве или разных видео?
Islam Q.
@IslamQ. Я записываю файл MP4, а затем пытаюсь воспроизвести его в цикле, как в Snapchat.
Костя Домбровский
@KostiaDombrovsky вы сравнивали свое воспроизведение со снэпчатом бок о бок? Я думаю, поскольку начало и конец кадра не совпадают, кажется, что он был приостановлен, но он никогда не останавливается.
Islam Q.
У меня тоже не получилось. У меня есть 6-секундное видео с непрерывным звуком, и я продолжаю слышать доли секунды тишины с помощью этого метода
Cbas
Я наблюдаю утечку памяти при использовании этого подхода. Это связано со [weakSelf.avPlayer seekToTime:kCMTimeZero]; [weakSelf.avPlayer play];строками - когда я закомментировал эти строки, утечки памяти больше нет. Я проанализировал это в инструментах.
Solsma Dev
3

Я рекомендую использовать AVQueuePlayer для плавного зацикливания ваших видео. Добавить наблюдателя уведомлений

AVPlayerItemDidPlayToEndTimeNotification

и в его селекторе зациклите ваше видео

AVPlayerItem *video = [[AVPlayerItem alloc] initWithURL:videoURL];
[self.player insertItem:video afterItem:nil];
[self.player play];
Kevinnguy
источник
Я пробовал это, и он не показал никаких улучшений по сравнению с методом, предложенным @Bastian. Удалось ли этим полностью убрать икоту?
amadour
2
@amadour, что вы можете сделать, это добавить 2 одинаковых видео в проигрыватель AVQueuePlayer при инициализации, а когда проигрыватель отправит AVPlayerItemDidPlayToEndTimeNotification, добавить это же видео в очередь проигрывателя.
kevinnguy
3

Чтобы избежать разрыва при перемотке видео, у меня хорошо сработало использование нескольких копий одного и того же объекта в композиции. Я нашел его здесь: www.developers-life.com/avplayer-looping-video-without-hiccupdelays.html (ссылка теперь мертва).

AVURLAsset *tAsset = [AVURLAsset assetWithURL:tURL];
CMTimeRange tEditRange = CMTimeRangeMake(CMTimeMake(0, 1), CMTimeMake(tAsset.duration.value, tAsset.duration.timescale));
AVMutableComposition *tComposition = [[[AVMutableComposition alloc] init] autorelease];
for (int i = 0; i < 100; i++) { // Insert some copies.
    [tComposition insertTimeRange:tEditRange ofAsset:tAsset atTime:tComposition.duration error:nil];
}
AVPlayerItem *tAVPlayerItem = [[AVPlayerItem alloc] initWithAsset:tComposition];
AVPlayer *tAVPlayer = [[AVPlayer alloc] initWithPlayerItem:tAVPlayerItem];
user2581875
источник
Я полагаю, вы имеете в виду эту ссылку devbrief.blogspot.se/2011/12/…
flame3
2

это сработало для меня без проблем с икотой , дело в том, чтобы приостановить проигрыватель перед вызовом метода seekToTime:

  1. инициализировать 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. регистрация уведомления

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "videoLoop", name: AVPlayerItemDidPlayToEndTimeNotification, object: self.backgroundPlayer!.currentItem)
    
  3. функция videoLoop

    func videoLoop() {
      self.backgroundPlayer?.pause()
      self.backgroundPlayer?.currentItem?.seekToTime(kCMTimeZero)
      self.backgroundPlayer?.play()
    }
    
Войта
источник
3
Спасибо - попробовал, но у меня все еще пауза.
Набха
2

Для Swift 3 и 4

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.avPlayer?.currentItem, queue: .main) { _ in
     self.avPlayer?.seek(to: kCMTimeZero)
     self.avPlayer?.play()
}
vp2698
источник
1

мое решение в objective-c с AVQueuePlayer - кажется, вам нужно продублировать AVPlayerItem и после завершения воспроизведения первого элемента немедленно добавить еще одну копию. "Вид" имеет смысл и работает для меня без сбоев

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

неон M3
источник
1

Swift 5:

Я внес некоторые небольшие изменения из предыдущих ответов, например, добавил playerItem в очередь, прежде чем добавлять его в 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()

И сделайте playerLooper свойством вашего UIViewController, иначе видео может воспроизводиться только один раз.

NSCoder
источник
0

После загрузки видео в AVPlayer (конечно, через его AVPlayerItem):

 [self addDidPlayToEndTimeNotificationForPlayerItem:item];

Метод addDidPlayToEndTimeNotificationForPlayerItem:

- (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];
    }];
}

По вашему мнению, метод viewWillDisappear:

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

В объявлении интерфейса вашего контроллера представления в файле реализации:

id _notificationToken;

Хотите убедиться в этом, прежде чем пытаться? Загрузите и запустите этот образец приложения:

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

В моем приложении, которое использует именно этот код, между концом видео и началом нет никакой паузы. Фактически, в зависимости от видео, я не могу сказать, что видео снова в начале, сохраните отображение временного кода.

Джеймс Буш
источник
0

вы можете добавить наблюдателя AVPlayerItemDidPlayToEndTimeNotification и воспроизводить видео с самого начала в селекторе, код, как показано ниже

 //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];
}
Shujucn
источник
0

Следующее работает для меня в WKWebView в быстром 4.1 Основная часть WKWebView в 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)
Nrv
источник
0

Я сделал так, чтобы он воспроизводился в цикле, как в моем коде ниже:

[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];
    }
}];
Джонни Чжан
источник
0

Swift 4.2 в Xcode 10.1.

Да , есть относительно простой способ зацикливания AVKit/ AVFoundationиспользования видео, AVQueuePlayer()метод наблюдения за ключом (KVO) и токен для него.

Это определенно работает для группы видео H.264 / HEVC с минимальной нагрузкой на процессор.

Вот код:

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() }
            }
        }
    }
}
Энди Федоров
источник
0

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()
    })
}
Николай Харбо
источник
-1

используйте AVPlayerViewController ниже кода, он работает для меня

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

Все элементы управления будут показаны, надеюсь, это поможет

Ийяппан Рави
источник
-2
/* "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;

Это свойство уже определено внутри AVAudioPlayer. Надеюсь, это поможет тебе. Я использую Xcode 6.3.

Julz
источник
8
это для аудио, а не для AVPlayer
Ярив Ниссим