Looping video dengan AVFoundation AVPlayer?


142

Apakah ada cara yang relatif mudah untuk mengulang video di AVFoundation?

Saya telah membuat AVPlayer dan AVPlayerLayer seperti:

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

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

dan kemudian saya memutar video saya dengan:

[avPlayer play];

Video diputar dengan baik tetapi berhenti pada akhirnya. Dengan MPMoviePlayerController yang harus Anda lakukan adalah mengatur repeatModepropertinya ke nilai yang tepat. Tampaknya tidak ada properti serupa di AVPlayer. Sepertinya juga tidak ada panggilan balik yang akan memberi tahu saya ketika film telah selesai sehingga saya dapat mencari ke awal dan memutarnya lagi.

Saya tidak menggunakan MPMoviePlayerController karena memiliki beberapa keterbatasan serius. Saya ingin dapat memutar ulang beberapa aliran video sekaligus.


1
Lihat jawaban ini untuk tautan ke kode kerja aktual: stackoverflow.com/questions/7822808/…
MoDJ

Jawaban:


275

Anda bisa mendapatkan Pemberitahuan saat pemain berakhir. MemeriksaAVPlayerItemDidPlayToEndTimeNotification

Saat mengatur pemain:

ObjC

  avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone; 

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

ini akan mencegah pemain untuk berhenti di akhir.

dalam pemberitahuan:

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

ini akan memundurkan film.

Jangan lupa batalkan batalkan pemberitahuan saat melepaskan pemain.

Cepat

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

6
... dan jika Anda ingin memainkannya tepat setelah [p seekToTime: kCMTimeZero] ("semacam" macam), cukup lakukan [p play] lagi.
thomax

24
ini seharusnya tidak perlu ... jika Anda melakukannya avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;tidak akan berhenti, jadi tidak perlu mengaturnya untuk bermain lagi
Bastian

Untuk membuat suara diputar kembali, Anda harus memanggil [player play];setelah memutar ulang.
Kenny Winker

12
Solusi ini berfungsi, tetapi tidak sepenuhnya mulus. Saya memiliki jeda yang sangat kecil. Apakah saya melakukan sesuatu yang salah?
Joris van Liempd iDeveloper

3
@Praxiteles Anda harus membatalkan registrasi saat tampilan dihancurkan, atau ketika Anda menghapus videoplayer atau apa pun yang Anda lakukan,) Anda dapat menggunakan [[NSNotificationCenter defaultCenter] removeObserver:self];misalnya, saat selfmendengarkan pemberitahuan.
Bastian

63

Jika itu membantu, di iOS / tvOS 10, ada AVPlayerLooper () baru yang dapat Anda gunakan untuk membuat perulangan video yang mulus (Swift):

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

Ini disajikan di WWDC 2016 dalam "Kemajuan dalam Pemutaran AVFoundation": https://developer.apple.com/videos/play/wwdc2016/503/

Bahkan dengan menggunakan kode ini, saya memiliki masalah sampai saya mengajukan laporan bug dengan Apple dan mendapat respons ini:

File film yang memiliki durasi film lebih lama dari trek audio / video adalah masalahnya. FigPlayer_File menonaktifkan transisi tanpa celah karena pengeditan track audio lebih pendek dari durasi film (15.682 vs 15.787).

Anda harus memperbaiki file film agar durasi film dan durasi lamanya menjadi sama atau Anda dapat menggunakan parameter rentang waktu AVPlayerLooper (atur rentang waktu dari 0 hingga durasi trek audio)

Ternyata Premiere telah mengekspor file dengan trek audio dengan panjang yang sedikit berbeda dari video. Dalam kasus saya itu baik-baik saja untuk menghapus audio sepenuhnya, dan itu memperbaiki masalah.


2
Tidak ada yang bekerja untuk saya. Saya menggunakan AVPlayerLooper dan memiliki bug ini dan memperbaiki perbedaan antara panjang video / audio menyelesaikan masalah.
Kevin Heap

1
Terima kasih atas informasi tentang Premiere. Saya menambahkan timeRange ke looper dan itu memperbaiki masalah "flashing video" saya.
Alexander Flenniken

@Nabha mungkinkah menggunakan ini untuk jangka waktu tertentu dalam video? Sebagai contoh video adalah 60 detik tetapi saya ingin mengulang 10 detik pertama saja
Lance Samaria

29

Dalam Swift :

Anda bisa mendapatkan Pemberitahuan saat pemain berakhir ... periksa AVPlayerItemDidPlayToEndTimeNotification

saat mengatur pemain:

avPlayer.actionAtItemEnd = AVPlayerActionAtItemEnd.None

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

ini akan mencegah pemain untuk berhenti di akhir.

dalam pemberitahuan:

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)

ini akan memundurkan film.

Jangan lupa membatalkan registrasi notifikasi saat melepaskan pemain.


4
Saya melihat cegukan kecil antara loop dengan metode ini. Saya membuka video saya di Adobe Premier dan memverifikasi tidak ada frame duplikat dalam video, sehingga cegukan singkat pasti saat pemutaran. Adakah yang menemukan cara untuk membuat loop video mulus di cepat?
SpaceManGalaxy

@ SpaceManGalaxy Saya juga melihat cegukan. Sudahkah Anda menemukan cara untuk memperbaiki kesalahan ini?
Lance Samaria

18

Inilah yang akhirnya saya lakukan untuk mencegah masalah jeda-cegukan:

Cepat:

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

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

CATATAN: Saya tidak menggunakannya avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNonekarena tidak diperlukan.


2
@KostiaDombrovsky apakah Anda mencoba perangkat yang sebenarnya atau video yang berbeda?
Islam Q.

@IslamQ. Saya merekam file MP4 dan kemudian mencoba memainkannya dalam satu lingkaran seperti halnya snapchat.
Kostia Dombrovsky

@KostiaDombrovsky apakah Anda membandingkan pemutaran Anda dengan snapchat berdampingan? Saya pikir karena frame awal dan akhir tidak cocok sepertinya seolah-olah dijeda, tetapi tidak pernah berhenti.
Islam Q.

Juga tidak bekerja untuk saya. Saya memiliki video 6 detik dengan audio tanpa henti dan saya terus mendengar sepersekian detik keheningan dengan metode ini
Cbas

Saya melihat kebocoran memori saat menggunakan pendekatan ini. Ini ada hubungannya dengan [weakSelf.avPlayer seekToTime:kCMTimeZero]; [weakSelf.avPlayer play];garis - ketika saya berkomentar garis-garis ini tidak ada lagi kebocoran memori. Saya sudah membuat profil ini dalam instrumen.
Solsma Dev

3

Saya sarankan menggunakan AVQueuePlayer untuk mengulang video Anda dengan mulus. Tambahkan pengamat pemberitahuan

AVPlayerItemDidPlayToEndTimeNotification

dan di pemilihnya, putar video Anda

AVPlayerItem *video = [[AVPlayerItem alloc] initWithURL:videoURL];
[self.player insertItem:video afterItem:nil];
[self.player play];

Saya mencoba ini dan tidak menunjukkan perbaikan apa pun atas metode yang disarankan @Bastian. Apakah Anda berhasil menghapus cegukan dengan ini?
amadour

2
@amadour yang dapat Anda lakukan adalah menambahkan 2 video yang sama di pemutar AVQueuePlayer saat diinisialisasi dan ketika pemain memposting AVPlayerItemDidPlayToEndTimeNotification, tambahkan video yang sama ke antrian pemain.
kevinnguy

3

Untuk menghindari kesenjangan saat video diputar ulang, menggunakan banyak salinan dari aset yang sama dalam komposisi berfungsi baik untuk saya. Saya menemukannya di sini: www.developers-life.com/avplayer-looping-video-without-hiccupdelays.html (tautan sekarang mati).

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

Saya kira maksud Anda tautan ini devbrief.blogspot.se/2011/12/...
flame3

2

ini bekerja untuk saya tanpa masalah cegukan, intinya adalah menjeda pemain sebelum memanggil metode seekToTime:

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

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "videoLoop", name: AVPlayerItemDidPlayToEndTimeNotification, object: self.backgroundPlayer!.currentItem)
  3. fungsi videoLoop

    func videoLoop() {
      self.backgroundPlayer?.pause()
      self.backgroundPlayer?.currentItem?.seekToTime(kCMTimeZero)
      self.backgroundPlayer?.play()
    }

3
Terima kasih - saya mencoba ini, tetapi masih ada jeda untuk saya.
Nabha

2

Untuk Swift 3 & 4

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.avPlayer?.currentItem, queue: .main) { _ in
     self.avPlayer?.seek(to: kCMTimeZero)
     self.avPlayer?.play()
}

1

solusi saya di objektif-c dengan AVQueuePlayer - sepertinya Anda harus menduplikasi AVPlayerItem dan setelah menyelesaikan pemutaran elemen pertama langsung menambahkan salinan lain. "Agak" masuk akal dan bekerja untuk saya tanpa cegukan

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

Swift 5:

Saya telah membuat sedikit penyesuaian dari jawaban sebelumnya seperti menambahkan playerItem ke antrian sebelum menambahkannya ke 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()

Dan jadikan playerLooper properti dari UIViewController Anda, jika tidak, video hanya dapat diputar sekali.


0

Setelah memuat video ke dalam AVPlayer (tentu saja melalui AVPlayerItem-nya):

 [self addDidPlayToEndTimeNotificationForPlayerItem:item];

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

Dalam metode viewWillDisappear Anda:

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

Dalam deklarasi antarmuka pengontrol tampilan Anda dalam file implementasi:

id _notificationToken;

Perlu melihat ini berjalan sebelum Anda mencoba? Unduh dan jalankan aplikasi sampel ini:

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

Di aplikasi saya, yang menggunakan kode ini, tidak ada jeda apa pun antara akhir video dan awal. Bahkan, tergantung pada videonya, tidak ada cara bagiku untuk mengatakan bahwa videonya ada di awal lagi, simpan tampilan kode waktu.


0

Anda dapat menambahkan pengamat AVPlayerItemDidPlayToEndTimeNotification dan memutar ulang video dari awal di pemilih, kode seperti di bawah ini

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

Berikut ini berfungsi untuk saya di WKWebView di swift 4.1 Bagian utama dari WKWebView di 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

Apa yang saya lakukan adalah membuatnya diputar, seperti kode saya di bawah ini:

[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 dalam Xcode 10.1.

Ya , ada cara yang relatif mudah untuk mengulang video dalam AVKit/ AVFoundationmenggunakan teknik AVQueuePlayer()Key-Value Observation (KVO) dan token untuk itu.

Ini pasti bekerja untuk banyak video H.264 / HEVC dengan beban minimal untuk CPU.

Ini kode:

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

gunakan kode AVPlayerViewController di bawah ini, ini berfungsi untuk saya

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

Semua kontrol harus ditunjukkan, semoga bermanfaat


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

Properti ini sudah ditentukan di dalam AVAudioPlayer. Semoga ini bisa membantu Anda. Saya menggunakan Xcode 6.3.


8
itu untuk audio, bukan untuk AVPlayer
Yariv Nissim
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.