Замикання відео на AVFoundation AVPlayer?


142

Чи є відносно простий спосіб зациклювання відео в 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, оскільки він має деякі серйозні обмеження. Я хочу мати можливість відтворювати декілька відеопотоків одночасно.


1
Дивіться цей відповідь для посилання на фактичний робочий код: stackoverflow.com/questions/7822808 / ...
MoDJ

Відповіді:


275

Ви можете отримати сповіщення, коли гравець закінчиться. Перевірка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];
}

це перемотає фільм.

Не забувайте скасувати реєстрацію сповіщення, коли випускаєте програвач.

Швидкий

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

Швидкий 4+

@objc func playerItemDidReachEnd(notification: Notification) {
    if let playerItem = notification.object as? AVPlayerItem {
        playerItem.seek(to: CMTime.zero, completionHandler: nil)
    }
}

6
... і якщо ви хочете відтворити його відразу після [p tražiToTime: kCMTimeZero] ("перемотування"), просто зробіть [p play] ще раз.
томакс

24
це не повинно бути необхідним ... якщо ти avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;це не зупиниш, тож не потрібно налаштовувати його знову
Бастіан

Щоб знову зробити звук, вам потрібно зателефонувати [player play];після перемотування.
Кенні Вінкер

12
Це рішення працює, але воно не зовсім безшовне. У мене дуже маленька пауза. Чи я щось роблю не так?
Joris van Liempd iDeveloper

3
@Praxiteles вам потрібно скасувати реєстрацію, коли вигляд зруйнований, або коли ви видалите відеоплеєр або все, що ви робите,) Ви можете використовувати, [[NSNotificationCenter defaultCenter] removeObserver:self];наприклад, під час selfпрослуховування сповіщень.
Бастіан

63

Якщо це допомагає, в 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 до тривалості звукової доріжки)

Виявляється, Прем'єра експортувала файли з аудіозаписом трохи іншої довжини, ніж відео. У моєму випадку було чудово видалити аудіо повністю, і це вирішило проблему.


2
Більше нічого не працювало для мене. Я використовую AVPlayerLooper, і ця помилка і виправлення невідповідності між довжиною відео / аудіо вирішив проблему.
Кевін Хіп

1
Дякую за інформацію про Прем'єру. Я додав timeRange до петлі, і це вирішило мою проблему "миготливого відео".
Олександр Фленнікен

@Nabha чи можна використовувати це протягом певного періоду часу у відео? Наприклад, відео триває 60 секунд, але я хочу зробити цикл лише за перші 10 секунд
Lance Samaria

29

У Свіфті :

Ви можете отримати сповіщення, коли плеєр закінчиться ... перевірте 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)
    }
}

Швидкий3

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

це перемотає фільм.

Не забувайте скасувати реєстрацію сповіщення, коли випускаєте програвач.


4
Я бачу невеликий гикавку між петлями за допомогою цього методу. Я відкрив своє відео в Adobe Premier і переконався, що у ньому немає дублікатів кадрів, тому короткий іккап, безумовно, відтворюється. Хтось знайшов спосіб швидко зробити цикл відео?
SpaceManGalaxy

@SpaceManGalaxy Я також помітив гикавку. Ви знайшли спосіб виправити цю проблему?
Lance Samaria

18

Ось що я зробив, щоб запобігти проблемі паузи-ікоти:

Швидкий:

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 ви пробували на фактичному пристрої чи різних відео?
Іслам Q.

@IslamQ. Я записую файл MP4, а потім намагаюся відтворити його у певній формі, як це робить snapchat.
Костя Домбровський

@KostiaDombrovsky Ви порівнювали ваше відтворення з snapchat поруч? Я думаю, тому що початок і кінцеві кадри не збігаються, здається, що це було призупинено, але воно ніколи не призупиняється.
Іслам 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];

Я спробував це, і це не показує жодних покращень щодо запропонованого методу @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];

Я думаю, ви маєте на увазі це посилання devbrief.blogspot.se/2011/12/…
flame3

2

це працювало для мене без проблем з іконою , суть у тому, щоб призупинити програвач перед тим, як викликати метод searchToTime:

  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. реєстрація повідомлення

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

1

моє рішення в об'єкті-c wth 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


1

Швидкий 5:

Я вніс невеликі корективи з попередніх відповідей, як-от додавання гравцяItem в чергу, перш ніж додати його до 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, інакше відео може відтворюватися лише один раз.


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

На ваш поглядWillDisappear метод:

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_APLVint_APLVEnt_APLVEnt_APLVEEnt_APLVEnt_APLintEAPEntEAPEnt_APLintLAPLeet_APLViewOAPTELLEAPLEVLOWEEntLagleEntlent_APLViewOAPlent_APLViewOAPTELLEAPLELLOAPTELLEAPLELLOAPTELLOAPTELLEAPLETLOWLOTEEEEEEEEEEEEEEEEEEEEEEYNYTYEEEEEEYLYNDLYEWEEEEYLYEWEE_LockSeeee

У моєму додатку, який використовує цей самий код, немає ніякої паузи між кінцем відео та початком. Насправді, залежно від відео, я не можу сказати, що це відео на початку, крім збереження відображення часового коду.


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

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)

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

Швидкий 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() }
            }
        }
    }
}

-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.


8
це для аудіо, а не для AVPlayer
Ярів Ніссім
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.