programing

AVFundation AVPlayer를 사용한 비디오 루프

showcode 2023. 4. 15. 09:41
반응형

AVFundation AVPlayer를 사용한 비디오 루프

AVFundation에서 비디오를 비교적 쉽게 루프할 수 있는 방법이 있습니까?

다음과 같이 AVPlayer와 AVPLayer를 작성했습니다.

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

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

그리고 비디오를 재생합니다.

[avPlayer play];

비디오는 정상적으로 재생되지만, 마지막에 정지한다.에서는 MPMoviePlayerController를 .repeatMode올바른 값으로 속성을 설정합니다.AVPlayer는 AVPlayer를 사용합니다.영화가 언제 끝났는지 알려주는 콜백도 없는 것 같아서 처음부터 다시 재생할 수 있어요.

MPMoviePlayerController는 심각한 제한이 있기 때문에 사용하지 않습니다.한 번에 여러 비디오 스트림을 재생할 수 있으면 좋겠습니다.

플레이어가 종료되면 알림을 받을 수 있습니다. 마크를 켜주세요.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)
    }
}

iOS/tvOS 10에는 비디오의 심리스 루프(Swift)를 작성하기 위해 사용할 수 있는 새로운 AVPlayerLooper()가 있습니다.

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

이는 WWDC 2016에서 "Advanceds in AVFundation Playback"에 소개되었습니다.https://developer.apple.com/videos/play/wwdc2016/503/

이 코드를 사용해도 문제가 있었습니다만, Apple에 버그 리포트를 제출하고, 다음의 회신을 받았습니다.

동영상 재생 시간이 오디오/비디오 트랙보다 길면 문제가 발생.FigPlayer_File은 오디오 트랙 편집 시간이 동영상 재생 시간(15.682 vs 15.787)보다 짧기 때문에 공백 없는 전환을 비활성화합니다.

동영상 지속 시간과 트랙 지속 시간이 같도록 동영상 파일을 수정하거나 AVPayerLooper의 시간 범위 매개 변수(오디오 트랙 시간 범위 0 ~ 지속 시간 설정)를 사용할 수 있습니다.

Premiere는 비디오와 약간 다른 길이의 오디오 트랙을 가진 파일을 내보내고 있었던 것으로 밝혀졌다.제 경우 오디오를 완전히 삭제해도 문제가 해결되지 않았습니다.

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

스위프트3

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

이게 영화를 되감아줄 거예요.

플레이어를 해제할 때 알림 등록을 취소하는 것을 잊지 마십시오.

일시정지(pause-hiccup) 문제를 방지하기 위해 다음과 같이 했습니다.

신속:

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:

플레이어를 추가하는 등 이전 답변에서 약간 수정했습니다.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()
    })
}

AVQue Player를 사용하여 동영상을 심리스하게 루프할 것을 권장합니다.통지 옵서버 추가

AVPlayerItemDidPlayToEndTimeNotification

그 셀렉터에서 비디오를 루프합니다.

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

비디오를 되감을 때의 갭을 피하기 위해서, 같은 자산의 카피를 1개의 컴포지션으로 복수 사용하는 것이 나에게 있어서 효과적이었습니다.여기서 찾았습니다.www.developers-life.com/avplayer-looping-video-without-hiccupdelays.html (link now dead).

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

딸꾹질 문제 없이 작동하며, 포인트는 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()
    }
    

목적 c의 솔루션 - wth AVQuePlayer - AVPlayerItem을 복제하고 첫 번째 요소의 재생이 끝나면 즉시 다른 복사본을 추가해야 할 것 같습니다."Cody of"는 말이 되므로 문제없이 사용할 수 있습니다.

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

Swift 3 & 4의 경우

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.avPlayer?.currentItem, queue: .main) { _ in
     self.avPlayer?.seek(to: kCMTimeZero)
     self.avPlayer?.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()
    })
}

비디오를 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];
    }];
}

viewWillDisplay 메서드:

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_APLViewController_m-DontLinkElementID_8

이 코드를 사용하고 있는 제 앱에서는, 동영상의 끝과 시작 사이에 전혀 정지가 없습니다.사실 동영상에 따라서는 동영상이 다시 시작임을 알 수 없습니다. 타임코드 표시를 저장하십시오.

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

다음은 WK Web View의 Swift 4.1 WK Web View의 주요 부분 구성입니다.

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)

아래 코드와 같이 루프 재생을 했습니다.

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

Xcode 10.1의 Swift 4.2.

, 비교적 쉽게 비디오를 루핑할 수 있는 방법이 있습니다.AVKit/AVFoundation사용.AVQueuePlayer()Key-Value Observation(KVO; 키 값 관찰) 기법 및 그 토큰.

이것은 CPU의 부하를 최소한으로 억제하고, 많은 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() }
            }
        }
    }
}

스위프트 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()
    }
}

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")!)

코드 아래의 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을 사용하고 있습니다.

언급URL : https://stackoverflow.com/questions/5361145/looping-a-video-with-avfoundation-avplayer

반응형