programing

AVPlayer 개체를 재생할 준비가 된 시점 확인

mailnote 2023. 9. 3. 16:31
반응형

AVPlayer 개체를 재생할 준비가 된 시점 확인

저는 게임을 하려고 합니다.MP3에되는파일에 UIView의 전터에서.UIViewa 표 시됨로로 됨)NSURL *fileURL변수).

초기화하고 AVPlayer매개 변수:

player = [AVPlayer playerWithURL:fileURL];

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

NSLogPlayer 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**

나는 두 번째로,player.status을 들고 있는 것 추측하기로는 제생번는 1을들 1있고는것같다습니각에,▁seems같다것▁is▁1니습▁i▁which번'있▁hold▁guessing제▁to는들.AVPlayerReadyToPlay.

했을 때 요?UIButton(즉, 어떻게 확인할 수 있습니까?AVPlayer단순하게 생성된 것이 아니라 재생할 준비가 되었습니까?)

원격 파일을 재생하고 있습니다.시간이 좀 걸릴 수 있습니다.AVPlayer충분한 데이터를 버퍼링하고 파일을 재생할 준비가 되어 있어야 합니다(AV Foundation Programming Guide 참조)

하지만 당신은 재생 버튼을 누르기 전에 플레이어가 준비되기를 기다리지 않는 것처럼 보입니다.내가 하고 싶은 것은 이 버튼을 비활성화하고 플레이어가 준비되었을 때만 활성화하는 것입니다.

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

스위프트 솔루션

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

나는 그의 상태를 파악하는 데 많은 어려움을 겪었습니다.AVPlayer.status속성이 항상 끔찍하게 도움이 되는 것 같지는 않았고, 오디오 세션 중단을 처리하려고 할 때 이것은 끝없는 좌절로 이어졌습니다.은 은끔가은.AVPlayer)로 .AVPlayerStatusReadyToPlay 않을 실제로 그렇게 보이지 않았을 때.저는 질룩의 KVO 방식을 사용했지만, 모든 경우에 효과가 없었습니다.

보충하기 위해 상태 속성이 유용하지 않을 때 AVP Player가 로드한 스트림의 양을 확인했습니다.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
}
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")
}

코드의 핵심은 인스턴스 속성이 재생이라는 것입니다.따라잡을 가능성이 높습니다.

많은 연구를 하고 많은 방법을 시도한 결과 관찰자는 보통 물체가 언제 재생할 준비가 되었는지를 아는 것이 좋지 않다는 것을 알게 되었습니다. 왜냐하면 물체는 재생할 준비가 되어 있지만 이것이 바로 재생된다는 것을 의미하지는 않기 때문입니다.

이것을 아는 것이 더 나은 생각입니다.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];
        }
    }
}

관찰자를 제거하는 경우(할당 해제, willDisspaire 또는 관찰자 등록 전 보기) 호출하기에 좋은 위치입니다.

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

팀 캠버의 답변을 토대로 제가 사용하는 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
    }
}

저는 어떤 콜백도 받지 못하는 문제가 있었습니다.

스트림을 생성하는 방법에 따라 다릅니다.제 경우에는 플레이어를 사용했습니다.초기화할 항목이기 때문에 관찰자를 항목에 대신 추가해야 했습니다.

예:

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

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

플레이어의 현재 항목 상태를 확인합니다.

if (player.currentItem.status == AVPlayerItemStatusReadyToPlay)

@조시 번펠트의 대답은 저에게 통하지 않았습니다.왜 그런지 모르겠어요.그는 관찰했습니다.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

언급URL : https://stackoverflow.com/questions/5401437/knowing-when-avplayer-object-is-ready-to-play

반응형