Зацикливание видео с AVFoundation AVPlayer?
Есть ли относительно простой способ зацикливания видео в 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, потому что он имеет некоторые серьезные ограничения. Я хочу иметь возможность воспроизводить несколько видеопотоков одновременно.
22 ответа
Вы можете получить уведомление, когда игрок заканчивается. Проверьте 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];
}
это перемотает фильм.
Не забудьте отменить уведомление при освобождении плеера.
стриж
NotificationCenter.default.addObserver(self,
selector: #selector(playerItemDidReachEnd(notification:)),
name: Notification.Name.AVPlayerItemDidPlayToEndTime,
object: avPlayer?.currentItem)
@objc func playerItemDidReachEnd(notification: Notification) {
if let playerItem: AVPlayerItem = notification.object as? AVPlayerItem {
playerItem.seek(to: kCMTimeZero, completionHandler: nil)
}
}
Если это поможет, в 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 в разделе "Достижения в воспроизведении AV-основ": https://developer.apple.com/videos/play/wwdc2016/503/
Даже используя этот код, у меня была ошибка, пока я не отправил отчет об ошибке в Apple и не получил следующий ответ:
Проблема заключается в том, что файл фильма имеет продолжительность фильма больше, чем аудио- или видеодорожки. FigPlayer_File отключает переход без промежутков, потому что редактирование звуковой дорожки короче, чем продолжительность фильма (15,682 против 15,787).
Вам нужно либо исправить файлы фильма, чтобы длительность фильма и длительность дорожки были одинаковыми, либо вы можете использовать параметр временного диапазона AVPlayerLooper (установить диапазон времени от 0 до продолжительности звуковой дорожки)
Оказывается, Premiere экспортировал файлы с аудиотреком немного другой длины, чем видео. В моем случае было нормально удалить звук полностью, и это решило проблему.
В Свифте:
Вы можете получить уведомление, когда игрок заканчивается... проверьте 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)
это перемотает фильм.
Не забудьте отменить уведомление при освобождении плеера.
Вот что я в итоге сделал, чтобы предотвратить проблему pause-hiccup:
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
как это не нужно.
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, иначе видео может воспроизводиться только один раз.
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()
})
}
Я рекомендую использовать AVQueuePlayer для беспрепятственного зацикливания видео. Добавить наблюдателя уведомления
AVPlayerItemDidPlayToEndTimeNotification
и в своем селекторе зациклите видео
AVPlayerItem *video = [[AVPlayerItem alloc] initWithURL:videoURL];
[self.player insertItem:video afterItem:nil];
[self.player play];
Чтобы избежать разрыва при перемотке видео, использование нескольких копий одного и того же актива в композиции помогло мне. Я нашел это здесь: 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];
Для Swift 3 и 4
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.avPlayer?.currentItem, queue: .main) { _ in
self.avPlayer?.seek(to: kCMTimeZero)
self.avPlayer?.play()
}
У меня это работало без проблем с ошибкой, смысл в том, чтобы остановить проигрыватель перед вызовом метода seekToTime:
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()
регистрация уведомления
NSNotificationCenter.defaultCenter().addObserver(self, selector: "videoLoop", name: AVPlayerItemDidPlayToEndTimeNotification, object: self.backgroundPlayer!.currentItem)
функция videoLoop
func videoLoop() { self.backgroundPlayer?.pause() self.backgroundPlayer?.currentItem?.seekToTime(kCMTimeZero) self.backgroundPlayer?.play() }
Я не нашел своего решения среди ответов. Может быть полезно соблюдать граничное время, указанное для продолжительности актива. Когда наблюдатель запускается, перейдите к началу и воспроизведите.
player?.addBoundaryTimeObserver(forTimes: [NSValue(time: asset.duration)], queue: .main) { [weak self] in
self?.player?.seek(to: .zero, completionHandler: { [weak self] _ in
self?.player?.play()
})
}
Мое решение в target-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
Я сделал так, чтобы он воспроизводился в цикле, как показано в моем коде ниже:
[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];
}
}];
Вы можете добавить наблюдателя 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];
}
Пример SwiftUI:
// VideoLooper.swift
import SwiftUI
import AVKit
struct VideoLooper: UIViewRepresentable {
private let player: AVQueuePlayer
private let videoURL: URL
init(resourceInBundle name: String, ofType type: String = "mp4") {
self.init(url: URL(fileURLWithPath: Bundle.main.path(forResource: name, ofType: type)!))
}
init(url: URL) {
self.videoURL = url
self.player = AVQueuePlayer()
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<VideoLooper>) {
}
func makeUIView(context: Context) -> UIView {
return PlayerUIView(player: player, videoURL: videoURL)
}
}
class PlayerUIView: UIView {
private let playerLayer: AVPlayerLayer
private let playerItem: AVPlayerItem
private let playerLooper: AVPlayerLooper
init(player: AVQueuePlayer, videoURL: URL) {
playerLayer = AVPlayerLayer(player: player)
playerItem = AVPlayerItem(url: videoURL)
playerLooper = AVPlayerLooper(player: player, templateItem: playerItem)
super.init(frame: .zero)
playerLayer.player = player
layer.addSublayer(playerLayer)
player.play()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
}
Использование:
// Video-Resource in Bundle
VideoLooper(resourceInBundle: "moviename", ofType: "mp4")
// Video-Resource from URL
VideoLooper(url: URL(string: "https://my-video-server.local/christmas.mp4")!)
Следующее работает для меня в WKWebView в Swift 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)
На 2023 год.
Если конкретно использовать AVPlayerViewController.
Измените эту строкуplayer = AVPlayer(url: u)
к этим двум строкам:
// player = AVPlayer(url: u)
player = AVQueuePlayer()
loopy = AVPlayerLooper(player: player as! AVQueuePlayer,
templateItem: AVPlayerItem(url: u))
Добавлять
var loopy: AVPlayerLooper?
в ВК. Вот и все, все готово.
Swift 4.2 в Xcode 10.1.
Да, есть относительно простой способ зацикливания видео в AVKit
/AVFoundation
с помощью AVQueuePlayer()
, Метод Key-Value Observation (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() }
}
}
}
}
Swift 5
import UIKit
import AVKit
import AVFoundation
class VideoViewControler: UIViewController {
// init video background and its path
var player: AVPlayer?
let videoURL: NSURL = Bundle.main.url(forResource: "farmer_watering", withExtension: "mp4")! as NSURL
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// begin implementing the avplayer
player = AVPlayer(url: videoURL as URL)
player?.actionAtItemEnd = .none
player?.isMuted = true
let playerLayer = AVPlayerLayer(player: player)
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspect
playerLayer.zPosition = -1
playerLayer.frame = view.frame
view.layer.addSublayer(playerLayer)
player?.play()
// add observer to watch for video end in order to loop video
NotificationCenter.default.addObserver(
self,
selector: #selector(loopVideo),
name: .AVPlayerItemDidPlayToEndTime,
object: self.player?.currentItem
)
}
// if video ends, will restart
func playerItemDidReachEnd() {
player?.seek(to: CMTime.zero)
}
// add this loop at the end, after viewDidLoad
@objc func loopVideo() {
playerItemDidReachEnd()
player?.play()
}
}
После загрузки видео в 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;
Нужно посмотреть, как это работает, прежде чем пытаться? Загрузите и запустите этот пример приложения:
В моем приложении, в котором используется именно этот код, нет никакой паузы между концом видео и началом. На самом деле, в зависимости от видео, я не могу сказать, что видео снова в начале, сохранить отображение временного кода.
Используйте код 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()
Все элементы управления, которые будут показаны, надеюсь, что это полезно
/* "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.