Wiedząc, kiedy obiekt AVPlayer jest gotowy do odtwarzania
Próbuję odtworzyć plik MP3
, który zostanie przekazany do UIView
z poprzedniej UIView
(przechowywanej w zmiennej NSURL *fileURL
).
Inicjalizuję AVPlayer
z:
player = [AVPlayer playerWithURL:fileURL];
NSLog(@"Player created:%d",player.status);
Wydruki NSLog
Player created:0,
, które, jak myślałem, oznaczają, że nie jest jeszcze gotowy do gry.
Kiedy klikam play UIButton
, uruchamiam kod:
-(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??");
}
}
Kiedy to uruchamiam, zawsze dostaję Error in player??
w konsoli.
Jeśli jednak zastąpię if
warunek sprawdzający, czy AVPlayer
jest gotowy do gry, prostym if(!isPlaying)
..., potem muzyka gra drugi raz klikam na play UIButton
.
Dziennik konsoli to:
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**
Widzę, że za drugim razem player.status
wydaje się trzymać 1, Co zgaduję jest AVPlayerReadyToPlay
.
Co mogę zrobić, aby gra działała poprawnie po pierwszym kliknięciu play UIButton
?
(czyli jak mogę się upewnić, że AVPlayer
jest nie tylko stworzony, ale także gotowy do gry?)
9 answers
Odtwarzasz zdalny plik. Może upłynąć trochę czasu, zanim AVPlayer
zbuforuje wystarczającą ilość danych i będzie gotowy do odtworzenia pliku (Zobacz Av Foundation Programming Guide)
Ale nie wydaje się czekać, aż gracz będzie gotowy przed naciśnięciem przycisku Odtwórz. Chciałbym wyłączyć ten przycisk i włączyć go tylko wtedy, gdy odtwarzacz jest gotowy.
Za pomocą KVO można być powiadamianym o zmianach statusu gracza:
playButton.enabled = NO;
player = [AVPlayer playerWithURL:fileURL];
[player addObserver:self forKeyPath:@"status" options:0 context:nil];
Ta metoda zostanie wywołana, gdy zmiany statusu:
- (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
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-19 14:00:11
Miałem wiele problemów próbując dowiedzieć się status AVPlayer
. Właściwość status
nie zawsze wydawała się bardzo pomocna, a to doprowadziło do niekończącej się frustracji, gdy próbowałem poradzić sobie z przerwami w sesji audio. Czasami AVPlayer
powiedział mi, że jest gotowy do gry (z AVPlayerStatusReadyToPlay
), Kiedy tak naprawdę nie wydaje się być. Użyłem metody KVO, ale nie we wszystkich przypadkach.
Aby uzupełnić, gdy właściwość status nie była użyteczna, zapytałem o ilość strumienia że AVPlayer załadował patrząc na właściwość loadedTimeRanges
AVPlayer
's currentItem
(która jest AVPlayerItem
).
To wszystko jest trochę mylące, ale tak to wygląda:
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
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-17 03:19:26
Po zbadaniu wielu i wypróbowaniu wielu sposobów zauważyłem, że normalnie status
obserwator nie jest tym lepiej wiedzieć kiedy AVPlayer
obiekt jest gotowy do gry, ponieważ obiekt może być gotowy do gry, ale nie oznacza to, że będzie natychmiast odtwarzany.
Lepiej wiedzieć, że to jest z loadedTimeRanges
.
Dla obserwatora rejestru
[playerClip addObserver:self forKeyPath:@"currentItem.loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
Listen the observer
- (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];
}
}
}
For remove observer (dealloc, viewWillDissapear lub przed rejestr observer) its a good places for called
- (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
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-10 20:13:50
Swift Solution
var observer: NSKeyValueObservation?
func prepareToPlay() {
let url = <#Asset URL#>
// Create asset to be played
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
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)
}
Możesz również unieważnić obserwatora w ten sposób
self.observer.invalidate()
Ważne: należy zachować zmienną observer, w przeciwnym razie zostanie ona wyłączona i zmienna changeHandler nie będzie już wywoływana. Więc nie Definiuj obserwatora jako zmiennej funkcji, ale zdefiniuj ją jako zmienną instancji, jak w podanym przykładzie.
Ta składnia obserwatora wartości klucza jest nowa w Swift 4.
Aby uzyskać więcej informacji, zobacz tutaj https://github.com/ole/whats-new-in-swift-4/blob/master/Whats-new-in-Swift-4.playground/Pages/Key%20paths.xcplaygroundpage/Contents.swift
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-27 03:26:51
private var playbackLikelyToKeepUpContext = 0
Dla obserwatora rejestru
avPlayer.addObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp",
options: .new, context: &playbackLikelyToKeepUpContext)
Listen the observer
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
}
}
}
Do usunięcia obserwatora
deinit {
avPlayer.removeObserver(self, forKeyPath: "currentItem.playbackLikelyToKeepUp")
}
Kluczowym punktem w kodzie jest właściwość instancji isplaybacklikelytokeepup.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-16 13:18:42
Miałem problemy z nie otrzymywaniem żadnych połączeń zwrotnych.
Okazuje się, że to zależy od tego, jak stworzysz strumień. W moim przypadku użyłem playerItem do inicjalizacji, a więc musiałem dodać obserwatora do elementu zamiast.
Na przykład:
- (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"];
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-08 13:29:39
W oparciu o odpowiedź Tima Cambera , Oto funkcja Swift, której używam:
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
}
Lub jako rozszerzenie
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
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:03:02
Sprawdź status aktualnego gracza:
if (player.currentItem.status == AVPlayerItemStatusReadyToPlay)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-23 06:27:46
Myślę, że lepiej będzie, jeśli gracz użyje initWithUrl
. Nie sądzę, że to rozwiąże twój problem, ale jest lepiej.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-23 06:23:40