이전 MP3
파일에서 전달 된 파일 을 재생하려고합니다 .UIView
UIView
NSURL *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
가) 생성 된 것이 아니라 플레이 할 준비가 되었는지 어떻게 확인할 수 있습니까?)
답변
원격 파일을 재생하고 있습니다. 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
}
}
}
답변
의 상태를 파악하는 데 많은 어려움이있었습니다 AVPlayer
. 이 status
속성은 항상 끔찍한 도움이되는 것 같지는 않았고, 이로 인해 오디오 세션 중단을 처리하려고 할 때 끝없는 좌절감이 생겼습니다. 때때로는 실제로 보이지 않을 때 AVPlayer
(와 함께 AVPlayerStatusReadyToPlay
) 재생할 준비가되었다고 말했습니다 . Jilouc의 KVO 방법을 사용했지만 모든 경우에 작동하지 않았습니다.
상태 속성이 도움이되지 않는 때 보완하기 위해, 나는 AVPlayer를가보고로드했다고 스트림의 양 쿼리 loadedTimeRanges
의 재산 AVPlayer
의 currentItem
을 인을 (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
}
답변
신속한 솔루션
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
답변
많은 연구를하고 여러 가지 방법을 시도한 후 일반적으로 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
}
}
답변
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입니다.
답변
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
}
}
답변
콜백을받지 못하는 문제가있었습니다.
스트림을 만드는 방법에 따라 다릅니다. 제 경우에는 초기화를 위해 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"];
}