Vòng lặp video với AVPloundation AVPlayer?


142

Có cách nào tương đối dễ dàng để lặp video trong AVFoundation không?

Tôi đã tạo AVPlayer và AVPlayerLayer của mình như vậy:

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

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

và sau đó tôi phát video của mình với:

[avPlayer play];

Video phát tốt nhưng dừng lại ở cuối. Với MPMoviePlayerControll, tất cả những gì bạn phải làm là đặt thuộc tính của nó repeatModethành đúng giá trị. Dường như không có một thuộc tính tương tự trên AVPlayer. Dường như cũng không có một cuộc gọi lại sẽ cho tôi biết khi bộ phim kết thúc để tôi có thể tìm kiếm từ đầu và phát lại.

Tôi không sử dụng MPMoviePlayerControll vì nó có một số hạn chế nghiêm trọng. Tôi muốn có thể phát lại nhiều luồng video cùng một lúc.


1
Xem câu trả lời này để biết liên kết đến mã làm việc thực tế: stackoverflow.com/questions/7822808/ trên
MoDJ

Câu trả lời:


275

Bạn có thể nhận được Thông báo khi người chơi kết thúc. Kiểm traAVPlayerItemDidPlayToEndTimeNotification

Khi thiết lập trình phát:

ObjC

  avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone; 

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

Điều này sẽ ngăn người chơi tạm dừng ở cuối.

trong thông báo:

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

điều này sẽ tua lại bộ phim.

Đừng quên hủy đăng ký thông báo khi phát hành trình phát.

Nhanh

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
... và nếu bạn muốn chơi nó ngay sau khi [p seekToTime: kCMTimeZero] (một "tua lại" các loại), chỉ cần thực hiện lại [p play].
thomax

24
điều này không cần thiết ... nếu bạn làm điều avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;đó sẽ không dừng lại, vì vậy không cần thiết lập lại để chơi lại
Bastian

Để làm cho âm thanh phát lại, bạn phải gọi [player play];sau khi tua lại.
Kenny Winker

12
Giải pháp này hoạt động, nhưng nó không hoàn toàn liền mạch. Tôi có một khoảng dừng rất nhỏ. Tôi có làm điều gì sai?
Joris van Liempd iDeveloper

3
@Praxiteles bạn cần hủy đăng ký khi chế độ xem bị hủy hoặc khi bạn gỡ bỏ videoplayer hoặc bất cứ điều gì bạn làm,) Bạn có thể sử dụng [[NSNotificationCenter defaultCenter] removeObserver:self];ví dụ khi selfnghe thông báo.
Bastian

63

Nếu nó giúp, trong iOS / tvOS 10, có AVPlayerLooper () mới mà bạn có thể sử dụng để tạo vòng lặp video liền mạch (Swift):

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

Điều này đã được trình bày tại WWDC 2016 trong "Những tiến bộ trong phát lại AVFoundation": https://developer.apple.com/ideo/play/wwdc2016/503/

Ngay cả khi sử dụng mã này, tôi đã bị trục trặc cho đến khi tôi nộp báo cáo lỗi với Apple và nhận được phản hồi này:

Các tập tin phim có thời lượng phim dài hơn âm thanh / video là vấn đề. FigPlayer_File đang vô hiệu hóa quá trình chuyển đổi không có khoảng cách vì chỉnh sửa đoạn âm thanh ngắn hơn thời lượng phim (15.682 so với 15.787).

Bạn cần sửa các tệp phim để có thời lượng phim và thời lượng theo dõi có cùng độ dài hoặc bạn có thể sử dụng tham số phạm vi thời gian của AVPlayerLooper (đặt phạm vi thời gian từ 0 đến thời lượng của đoạn âm thanh)

Hóa ra Premiere đã xuất các tệp có đoạn âm thanh có độ dài hơi khác so với video. Trong trường hợp của tôi, việc loại bỏ hoàn toàn âm thanh là ổn, và điều đó đã khắc phục vấn đề.


2
Không có gì khác làm việc cho tôi. Tôi đang sử dụng AVPlayerLooper và gặp lỗi này và khắc phục sự khác biệt giữa độ dài video / âm thanh đã giải quyết được vấn đề.
Kevin Heap

1
Cảm ơn bạn đã thông tin về Premiere. Tôi đã thêm timeRange vào looper và nó đã khắc phục vấn đề "video nhấp nháy" của tôi.
Alexander Flenniken

@Nabha có thể sử dụng điều này trong một khung thời gian nhất định trong video không? Ví dụ: video là 60 giây nhưng tôi chỉ muốn lặp lại 10 giây đầu tiên
Lance Samaria

29

Trong Swift :

Bạn có thể nhận được Thông báo khi trình phát kết thúc ... kiểm tra AVPlayerItemDidPlayToEndTimeNotification

khi thiết lập trình phát:

avPlayer.actionAtItemEnd = AVPlayerActionAtItemEnd.None

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

Điều này sẽ ngăn người chơi tạm dừng ở cuối.

trong thông báo:

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)

điều này sẽ tua lại bộ phim.

Đừng quên hủy đăng ký thông báo khi phát hành trình phát.


4
Tôi đang thấy một trục trặc nhỏ giữa các vòng lặp với phương pháp này. Tôi đã mở video của mình trong Adobe Premier và xác minh không có khung trùng lặp trong video, do đó, tiếng nấc ngắn chắc chắn là khi phát lại. Có ai tìm được cách tạo một vòng lặp video liền mạch nhanh chóng không?
SpaceManGal Wax

@SpaceManGal Wax Tôi cũng nhận thấy tiếng nấc. Bạn đã tìm ra cách khắc phục sự cố này chưa?
Lance Samaria

18

Đây là những gì tôi đã làm để ngăn chặn vấn đề tạm dừng nấc cục:

Nhanh:

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

Mục tiêu 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];
                    }];

LƯU Ý: Tôi không sử dụng avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNonevì không cần thiết.


2
@KostiaDombrovsky bạn đã thử trên một thiết bị thực tế hoặc các video khác nhau?
Hồi giáo Q.

@IslamQ. Tôi ghi lại một tập tin MP4 và sau đó thử chơi nó trong một vòng lặp giống như snapchat.
Kostia Dombrovsky

@KostiaDombrovsky bạn đã so sánh phát lại của mình với snapchat cạnh nhau chưa? Tôi nghĩ bởi vì khung hình bắt đầu và kết thúc không khớp với nhau, dường như nó bị tạm dừng, nhưng nó không bao giờ tạm dừng.
Hồi giáo Q.

Tôi cũng không làm việc. Tôi có một video 6 giây với âm thanh không ngừng và tôi cứ nghe một giây im lặng với phương pháp này
Cbas

Tôi đang thấy rò rỉ bộ nhớ khi sử dụng phương pháp này. Nó liên quan đến các [weakSelf.avPlayer seekToTime:kCMTimeZero]; [weakSelf.avPlayer play];dòng - khi tôi nhận xét những dòng này không còn bị rò rỉ bộ nhớ. Tôi đã mô tả điều này trong các công cụ.
Solsma Dev

3

Tôi khuyên bạn nên sử dụng AVQueuePlayer để lặp lại video của bạn một cách liền mạch. Thêm người quan sát thông báo

AVPlayerItemDidPlayToEndTimeNotification

và trong bộ chọn của nó, lặp video của bạn

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

Tôi đã thử điều này và nó không cho thấy bất kỳ cải thiện nào so với phương pháp @Bastian đề xuất. Bạn đã quản lý để loại bỏ hoàn toàn nấc với điều này?
amadour

2
@amadour những gì bạn có thể làm là thêm 2 video giống nhau trong trình phát AVQueuePlayer khi được khởi tạo và khi trình phát đăng AVPlayerItemDidPlayToEndTimeNotification, hãy thêm cùng một video vào hàng đợi của trình phát.
kevinnguy

3

Để tránh khoảng cách khi video được tua lại, sử dụng nhiều bản sao của cùng một tài sản trong một tác phẩm phù hợp với tôi. Tôi tìm thấy nó ở đây: www.developers-life.com/avplayer-looping-video-without-hiccupdelays.html (liên kết bây giờ đã chết).

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

Tôi đoán bạn có nghĩa là liên kết này devbrief.blogspot.se/2011/12/ trên
ngọn lửa3

2

Điều này làm việc cho tôi mà không gặp vấn đề trục trặc, điểm dừng là tạm dừng trình phát trước khi gọi phương thức seekToTime:

  1. khởi động 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. đăng ký thông báo

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "videoLoop", name: AVPlayerItemDidPlayToEndTimeNotification, object: self.backgroundPlayer!.currentItem)
  3. chức năng videoLoop

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

3
Cảm ơn - Tôi đã thử điều này, nhưng vẫn có một khoảng dừng cho tôi.
Nabha

2

Dành cho Swift 3 & 4

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

1

giải pháp của tôi trong mục tiêu AVQueuePlayer - có vẻ như bạn phải sao chép AVPlayerItem và sau khi hoàn tất phát lại phần tử đầu tiên, ngay lập tức thêm một bản sao khác. "Loại" có ý nghĩa và làm việc cho tôi mà không có bất kỳ trục trặc nào

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:

Tôi đã thực hiện một số điều chỉnh nhỏ từ các câu trả lời trước đó như thêm playerItem vào hàng đợi trước khi thêm nó vào 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()

Và biến playerLooper thành một thuộc tính của UIViewContoder của bạn, nếu không video chỉ có thể phát một lần.


0

Sau khi tải video vào AVPlayer (tất nhiên là thông qua AVPlayerItem):

 [self addDidPlayToEndTimeNotificationForPlayerItem:item];

Phương thức 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];
    }];
}

Trong phương thức viewWillDisappear của bạn:

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

Trong phần khai báo giao diện của trình điều khiển xem của bạn trong tệp triển khai:

id _notificationToken;

Cần phải xem điều này lên và chạy trước khi bạn thử? Tải xuống và chạy ứng dụng mẫu này:

https://developer.apple.com/l Library / prelelease / ios

Trong ứng dụng của tôi, sử dụng chính mã này, không có bất kỳ tạm dừng nào giữa phần cuối của video và phần đầu. Trên thực tế, tùy thuộc vào video, không có cách nào để tôi nói lại video đó ngay từ đầu, hãy lưu màn hình hiển thị mã thời gian.


0

bạn có thể thêm trình quan sát AVPlayerItemDidPlayToEndTimeNotification và phát lại video từ đầu trong bộ chọn, mã như bên dưới

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

Phần sau đây hoạt động với tôi trong WKWebView trong swift 4.1 Phần chính của WKWebView trong 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

Những gì tôi đã làm là làm cho nó lặp lại, như mã của tôi dưới đây:

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

, có một cách tương đối dễ dàng để lặp video trong AVKit/ AVFoundationsử dụng AVQueuePlayer(), kỹ thuật Quan sát giá trị khóa (KVO) và mã thông báo cho video đó.

Điều này chắc chắn hoạt động cho một loạt các video H.264 / HEVC với gánh nặng tối thiểu cho CPU.

Đây là một mã:

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

sử dụng mã AVPlayerViewControll bên dưới, nó hoạt động với tôi

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

Tất cả các điều khiển được hiển thị, hy vọng nó hữu ích


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

Khách sạn này đã được xác định bên trong AVAudioPlayer. Hy vọng điều này có thể giúp bạn. Tôi đang sử dụng Xcode 6.3.


8
đó là cho âm thanh, không phải cho AVPlayer
Yariv Nissim
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.