AVPlayer 개체를 재생할 준비가되었는지 확인


79

이전 MP3파일에서 전달 된 파일 을 재생하려고합니다 .UIViewUIViewNSURL *fileURL 변수에 .

다음으로 초기화 중 AVPlayer입니다.

player = [AVPlayer playerWithURL:fileURL];

NSLog(@"Player created:%d",player.status);

그만큼 NSLog인쇄 Player created:0,내가 방법을 생각 아직 플레이 할 준비가되지 않았습니다.

재생을 클릭하면 UIButton실행되는 코드는 다음과 같습니다.

-(IBAction)playButtonClicked
{
    NSLog(@"Clicked Play. MP3:%@",[fileURL absoluteString]);

    if(([player status] == AVPlayerStatusReadyToPlay) && !isPlaying)
//  if(!isPlaying)
    {
        [player play];
        NSLog(@"Playing:%@ with %d",[fileURL absoluteString], player.status);
        isPlaying = YES;
    }
    else if(isPlaying)
    {

        [player pause];
        NSLog(@"Pausing:%@",[fileURL absoluteString]);
        isPlaying = NO;
    }
    else {
        NSLog(@"Error in player??");
    }

}

이것을 실행하면 항상 Error in player??콘솔에 들어갑니다. 그러나 플레이 할 준비가되었는지 if확인 하는 조건을 AVPlayer간단한if(!isPlaying) ...로 바꾸면 재생을 클릭하면 두 번째로 음악이 재생 UIButton됩니다.

콘솔 로그는 다음과 같습니다.

Clicked Play. MP3:http://www.nimh.nih.gov/audio/neurogenesis.mp3
Playing:http://www.nimh.nih.gov/audio/neurogenesis.mp3 **with 0**

Clicked Play. MP3:http://www.nimh.nih.gov/audio/neurogenesis.mp3
Pausing:http://www.nimh.nih.gov/audio/neurogenesis.mp3

Clicked Play. MP3:http://www.nimh.nih.gov/audio/neurogenesis.mp3
2011-03-23 11:06:43.674 Podcasts[2050:207] Playing:http://www.nimh.nih.gov/audio/neurogenesis.mp3 **with 1**

SECOND TIME player.status은 1을 유지 하는 것 같습니다.AVPlayerReadyToPlay .

처음 플레이를 클릭했을 때 제대로 플레이하려면 UIButton어떻게해야하나요? (예 :이 ( AVPlayer가) 생성 된 것이 아니라 플레이 할 준비가 되었는지 어떻게 확인할 수 있습니까?)

답변:


127

원격 파일을 재생하고 있습니다. AVPlayer충분한 데이터를 버퍼링하고 파일을 재생할 준비가되는 데 시간이 걸릴 수 있습니다 ( AV Foundation 프로그래밍 가이드 참조). ).

하지만 플레이 버튼을 누르기 전에 플레이어가 준비 될 때까지 기다리지 않는 것 같습니다. 내가 원하는 것은이 버튼을 비활성화하고 플레이어가 준비되었을 때만 활성화하는 것입니다.

KVO를 사용하면 플레이어 상태 변경에 대한 알림을받을 수 있습니다.

playButton.enabled = NO;
player = [AVPlayer playerWithURL:fileURL];
[player addObserver:self forKeyPath:@"status" options:0 context:nil];   

이 메서드는 상태가 변경 될 때 호출됩니다.

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                        change:(NSDictionary *)change context:(void *)context {
    if (object == player && [keyPath isEqualToString:@"status"]) {
        if (player.status == AVPlayerStatusReadyToPlay) {
            playButton.enabled = YES;
        } else if (player.status == AVPlayerStatusFailed) {
            // something went wrong. player.error should contain some information
        }
    }
}

감사합니다!! 그것은 매력처럼 작동했습니다. (내가 그것을 문제없이 오프라인 파일을 연주했다봤을 때 비록 짐작한다)
mvishnu

재생되지 않는 URL이 있지만 존재하지만 작동하지 않는 URL이 있습니다 (예 : iTunes에서도 재생되지 않음). 그 행동을 어떻게 관리합니까? AVPlayer에는 시간 제한이 없습니다.
Fabrizio

10
내 경험상 player.currentItem.status정확 player.status하지 않을 때 정확 합니다. 차이점이 무엇인지 확실하지 않습니다.
bendytree 2012

1
@iOSAppDev IOS7에서 AVPlayerItem addObserver 사용
Peter Zhao

4
와우,이 AVPlayer는 너무 형편 없어서 저를 울게 만듭니다. onLoad 핸들러 블록을 추가하지 않는 이유는 무엇입니까? Apple로 오세요, 당신의 물건을 단순화하세요!
오리

30

의 상태를 파악하는 데 많은 어려움이있었습니다 AVPlayer. 이 status속성은 항상 끔찍한 도움이되는 것 같지는 않았고, 이로 인해 오디오 세션 중단을 처리하려고 할 때 끝없는 좌절감이 생겼습니다. 때때로는 실제로 보이지 않을 때 AVPlayer(와 함께 AVPlayerStatusReadyToPlay) 재생할 준비가되었다고 말했습니다 . Jilouc의 KVO 방법을 사용했지만 모든 경우에 작동하지 않았습니다.

상태 속성이 도움이되지 않는 때 보완하기 위해, 나는 AVPlayer를가보고로드했다고 스트림의 양 쿼리 loadedTimeRanges의 재산 AVPlayercurrentItem을 인을 (AVPlayerItem ).

모두 약간 혼란 스럽지만 다음은 다음과 같습니다.

NSValue *val = [[[audioPlayer currentItem] loadedTimeRanges] objectAtIndex:0];
CMTimeRange timeRange;
[val getValue:&timeRange];
CMTime duration = timeRange.duration;
float timeLoaded = (float) duration.value / (float) duration.timescale; 

if (0 == timeLoaded) {
    // AVPlayer not actually ready to play
} else {
    // AVPlayer is ready to play
}

2
AV Foundation과 함께 제공되는 NSValue 유형에 대한 추가 사항이 있습니다. 이러한 도우미 중 일부를 사용하면 NSValue에서 CMTimeXxx 값으로 앞뒤로 변환 할 수 있습니다. CMTimeRangeValue 처럼 .
superjos

timeLoadedCMTime 에서 초를 얻기위한 비슷한 이야기 ​​(그게 뭐라고 생각합니다 ) : CMTimeGetSeconds
superjos

2
불행히도 이것은 받아 들여진 대답이어야합니다. 실제로 플레이 할 준비가되지 않은 경우 너무 일찍 AVPlayer설정되는 것 같습니다 status == AVPlayerStatusReadyToPlay. 이 작업을 수행하려면 NSTimer예를 들어 호출 에서 위 코드를 래핑 할 수 있습니다 .
maxkonovalov 2015

로드 된 시간 범위가 2 초 이상 (wlog) 있지만 플레이어 또는 playerItem의 상태가 ReadyToPlay가 아닌 경우 일 수 있습니까? IOW, 확인해야합니까?
danielhadar

29

신속한 솔루션

var observer: NSKeyValueObservation?

func prepareToPlay() {
    let url = <#Asset URL#>
    // Create asset to be played
    let asset = AVAsset(url: url)
    
    let assetKeys = [
        "playable",
        "hasProtectedContent"
    ]
    // Create a new AVPlayerItem with the asset and an
    // array of asset keys to be automatically loaded
    let playerItem = AVPlayerItem(asset: asset,
                              automaticallyLoadedAssetKeys: assetKeys)
    
    // Register as an observer of the player item's status property
    self.observer = playerItem.observe(\.status, options:  [.new, .old], changeHandler: { (playerItem, change) in
        if playerItem.status == .readyToPlay {
            //Do your work here
        }
    })

    // Associate the player item with the player
    player = AVPlayer(playerItem: playerItem)
}

또한 이런 식으로 관찰자를 무효화 할 수 있습니다.

self.observer.invalidate()

중요 : 관찰자 변수를 유지해야합니다 . 그렇지 않으면 할당이 해제되고 changeHandler가 더 이상 호출되지 않습니다. 따라서 관찰자를 함수 변수로 정의하지 말고 주어진 예제와 같이 인스턴스 변수로 정의하십시오.

이 키 값 관찰자 구문은 Swift 4의 새로운 기능입니다.

자세한 내용은 여기 https://github.com/ole/whats-new-in-swift-4/blob/master/Whats-new-in-Swift-4.playground/Pages/Key%20paths.xcplaygroundpage/를 참조하십시오. Contents.swift


감사합니다.이 방법은 KVO를 제거하는 데 매우 간단합니다.
ZAFAR007

11

많은 연구를하고 여러 가지 방법을 시도한 후 일반적으로 status관찰자가 AVPlayer개체를 재생할 준비가 된 시점을 실제로 아는 것이 더 좋지 않다는 것을 알게 되었습니다 . 왜냐하면 개체가 재생 준비가 될 수 있지만 이것이 즉시 재생된다는 의미는 아니기 때문입니다.

이것을 아는 더 좋은 아이디어는 loadedTimeRanges.

등록 관찰자 용

[playerClip addObserver:self forKeyPath:@"currentItem.loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];

관찰자 듣기

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (object == playerClip && [keyPath isEqualToString:@"currentItem.loadedTimeRanges"]) {
        NSArray *timeRanges = (NSArray*)[change objectForKey:NSKeyValueChangeNewKey];
        if (timeRanges && [timeRanges count]) {
            CMTimeRange timerange=[[timeRanges objectAtIndex:0]CMTimeRangeValue];
            float currentBufferDuration = CMTimeGetSeconds(CMTimeAdd(timerange.start, timerange.duration));
            CMTime duration = playerClip.currentItem.asset.duration;
            float seconds = CMTimeGetSeconds(duration);

            //I think that 2 seconds is enough to know if you're ready or not
            if (currentBufferDuration > 2 || currentBufferDuration == seconds) {
                // Ready to play. Your logic here
            }
        } else {
            [[[UIAlertView alloc] initWithTitle:@"Alert!" message:@"Error trying to play the clip. Please try again" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil] show];
        }
    }
}

관찰자 제거 (dealloc, viewWillDissapear 또는 관찰자 등록 전)의 경우 호출하기에 좋은 장소입니다.

- (void)removeObserverForTimesRanges
{
    @try {
        [playerClip removeObserver:self forKeyPath:@"currentItem.loadedTimeRanges"];
    } @catch(id anException){
        NSLog(@"excepcion remove observer == %@. Remove previously or never added observer.",anException);
        //do nothing, obviously it wasn't attached because an exception was thrown
    }
}

감사합니다. 이것은 저에게도 효과적이었습니다. 그러나 "currentBufferDuration == seconds"평가를 사용하지 않았습니다. 어떤 용도로 사용되는지 말씀해 주시겠습니까?
andrei

경우currentBufferDuration < 2
jose920405

로드 된 시간 범위가 2 초 이상 (wlog) 있지만 플레이어 또는 playerItem의 상태가 ReadyToPlay가 아닌 경우 일 수 있습니까? IOW, 확인해야합니까?
danielhadar

11
private var playbackLikelyToKeepUpContext = 0

등록 관찰자 용

avPlayer.addObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp",
        options: .new, context: &playbackLikelyToKeepUpContext)

관찰자 듣기

 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if context == &playbackLikelyToKeepUpContext {
        if avPlayer.currentItem!.isPlaybackLikelyToKeepUp {
           // loadingIndicatorView.stopAnimating() or something else
        } else {
           // loadingIndicatorView.startAnimating() or something else
        }
    }
}

관찰자 제거 용

deinit {
    avPlayer.removeObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp")
}

코드의 핵심은 인스턴스 속성 isPlaybackLikelyToKeepUp입니다.


3
좋은 대답입니다. 나는 함께 KVO을 향상시킬 것forKeyPath: #keyPath(AVPlayer.currentItem.isPlaybackLikelyToKeepUp)
미로슬라프 Hrivik

2019 년에는 완벽하게 작동합니다 . 복사 및 붙여 넣기 :) @MiroslavHrivik 모드를 사용했습니다. 감사합니다!
Fattie

7

Tim Camber 답변을 기반으로 내가 사용하는 Swift 함수는 다음과 같습니다.

private func isPlayerReady(_ player:AVPlayer?) -> Bool {

    guard let player = player else { return false }

    let ready = player.status == .readyToPlay

    let timeRange = player.currentItem?.loadedTimeRanges.first as? CMTimeRange
    guard let duration = timeRange?.duration else { return false } // Fail when loadedTimeRanges is empty
    let timeLoaded = Int(duration.value) / Int(duration.timescale) // value/timescale = seconds
    let loaded = timeLoaded > 0

    return ready && loaded
}

또는 확장으로

extension AVPlayer {
    var ready:Bool {
        let timeRange = currentItem?.loadedTimeRanges.first as? CMTimeRange
        guard let duration = timeRange?.duration else { return false }
        let timeLoaded = Int(duration.value) / Int(duration.timescale) // value/timescale = seconds
        let loaded = timeLoaded > 0

        return status == .readyToPlay && loaded
    }
}

확장을 통해 KVO가 준비된 속성을 관찰하는 것은 불가능하다고 생각합니다. 주변에?
Jonny 2017 년

나는 알림을 청취 AVPlayerItemNewAccessLogEntry하고 AVPlayerItemDidPlayToEndTime내 프로젝트에. Afaik 작동합니다.
Axel Guilmin

좋아, 나는 loadedTimeRanges.
조니

5

콜백을받지 못하는 문제가있었습니다.

스트림을 만드는 방법에 따라 다릅니다. 제 경우에는 초기화를 위해 playerItem을 사용했기 때문에 대신에 옵저버를 항목에 추가해야했습니다.

예를 들면 :

- (void) setup
{
    ...
    self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
    self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
    ... 

     // add callback
     [self.player.currentItem addObserver:self forKeyPath:@"status" options:0 context:nil];
}

// the callback method
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
                    change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"[VideoView] player status: %i", self.player.status);

    if (object == self.player.currentItem && [keyPath isEqualToString:@"status"])
    {
        if (self.player.currentItem.status == AVPlayerStatusReadyToPlay)
        {
           //do stuff
        }
    }
}

// cleanup or it will crash
-(void)dealloc
{
    [self.player.currentItem removeObserver:self forKeyPath:@"status"];
}

와 함께하면 안되는 경우 AVPlayerItemStatusReadyToPlay?
jose920405

@ jose920405 위의 해결책이 작동하는지 확인할 수 있지만 좋은 질문입니다. 나는 정말로 모른다. 테스트하면 알려주세요.
dac2009

3

플레이어의 currentItem 상태 확인 :

if (player.currentItem.status == AVPlayerItemStatusReadyToPlay)

2
player.currentItem.status는 AVPlayerItemStatusUnkown을 반환합니다. 다음에 무엇을해야할지 모르겠습니다. :(
mvishnu 2011 년

처음에이 값은 AVPlayerItemStatusUnkown입니다. 만 잠시 후, 경우 알 수있을 것입니다 AVPlayerItemStatusReadyToPlay또는AVPlayerItemStatusFailed
구스타보 바르보사

3

스위프트 4 :

var player:AVPlayer!

override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(self, 
               selector: #selector(playerItemDidReadyToPlay(notification:)),
               name: .AVPlayerItemNewAccessLogEntry, 
               object: player?.currentItem)
}

@objc func playerItemDidReadyToPlay(notification: Notification) {
        if let _ = notification.object as? AVPlayerItem {
            // player is ready to play now!!
        }
}

1

@JoshBernfeld의 대답 은 나를 위해 작동하지 않았습니다. 이유가 확실하지 않습니다. 그는 관찰했다 playerItem.observe(\.status. 나는 관찰해야했다 player?.observe(\.currentItem?.status. 그들은 같은 물건 인 것 같습니다 playerItem status.

var playerStatusObserver: NSKeyValueObservation?

player?.automaticallyWaitsToMinimizeStalling = false // starts faster

playerStatusObserver = player?.observe(\.currentItem?.status, options: [.new, .old]) { (player, change) in
        
    switch (player.status) {
    case .readyToPlay:
            // here is where it's ready to play so play player
            DispatchQueue.main.async { [weak self] in
                self?.player?.play()
            }
    case .failed, .unknown:
            print("Media Failed to Play")
    @unknown default:
         break
    }
}

플레이어 세트 사용이 끝나면 playerStatusObserver = nil

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.