यह जानकर कि AVPlayer ऑब्जेक्ट कब खेलने के लिए तैयार है


79

मैं एक ऐसी 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**

मैं देखता हूं कि सेकंड टाइम, player.statusलगता है कि 1 पकड़ रहा है, जिसका मैं अनुमान लगा रहा हूं AVPlayerReadyToPlay

पहली बार मैं नाटक पर क्लिक करने के लिए खेलने के लिए क्या कर सकता हूं UIButton? (यानी, मैं यह कैसे सुनिश्चित कर सकता हूं कि AVPlayerयह सिर्फ बनाया नहीं गया है, बल्कि खेलने के लिए भी तैयार है?)

जवाबों:


127

आप एक दूरस्थ फ़ाइल खेल रहे हैं। AVPlayerपर्याप्त डेटा को बफर करने और फ़ाइल को चलाने के लिए तैयार होने में कुछ समय लग सकता है (देखें ए वी फाउंडेशन प्रोग्रामिंग गाइड )

लेकिन आप प्ले बटन को टैप करने से पहले खिलाड़ी के तैयार होने का इंतजार नहीं करते। मुझे इस बटन को अक्षम करना होगा और खिलाड़ी के तैयार होने पर ही इसे सक्षम करना होगा।

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 ऐसे हैं जो सिर्फ खेलते नहीं हैं, वे मौजूद हैं, लेकिन वे काम नहीं करते हैं (उदाहरण के लिए iTunes उन्हें नहीं खेलेंगे)। आप उस व्यवहार को कैसे प्रबंधित करते हैं? AVPlayer में कोई टाइमआउट नहीं है।
Fabrizio

10
मेरे अनुभव player.currentItem.statusमें सटीक है जब player.statusनहीं है। निश्चित नहीं है कि अंतर क्या है।
बेंडाईट्री

1
@iOSAppDev IOS7 पर AVPlayerItem एडऑब्जर्वर का उपयोग करें
पीटर झाओ

4
वाह, यह AVPlayer इतना खराब डिज़ाइन किया गया है जो मुझे रोता है। क्यों एक onLoad हैंडलर ब्लॉक नहीं जोड़ रहा है? Apple पर आओ, अपना सामान सरल करो!
बतख

30

मुझे स्थिति का पता लगाने में बहुत परेशानी हुई AVPlayerstatusसंपत्ति हमेशा बहुत मददगार नहीं मालूम था, और यह जब मैं ऑडियो सत्र रुकावट को संभालने के लिए कोशिश कर रहा था अंतहीन निराशा का नेतृत्व किया। कभी-कभी AVPlayerमुझे बताया गया था कि यह खेलने के लिए तैयार था ( AVPlayerStatusReadyToPlayतब) जब यह वास्तव में प्रतीत नहीं होता था। मैंने Jilouc की KVO विधि का उपयोग किया, लेकिन यह सभी मामलों में काम नहीं किया।

पूरक करने के लिए, जब स्थिति संपत्ति उपयोगी नहीं हो रही थी, तो मैंने एवीप्लेयर ने उस धारा की राशि को क्वेर किया था जो 's (जो कि एक है ) की loadedTimeRangesसंपत्ति को देखकर भरी हुई थी ।AVPlayercurrentItemAVPlayerItem

यह सब थोड़ा भ्रमित करने वाला है, लेकिन यहाँ यह जैसा दिखता है:

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
एवी फाउंडेशन के साथ आने वाले NSValue प्रकार के अतिरिक्त हैं। उन सहायकों में से कुछ आपको NSValue से CMTimeXxx मूल्यों में आगे और पीछे जाने की अनुमति देते हैं। जैसे CMTimeRangeValue
सुपरजोस

सेकंड पाने के लिए इसी तरह की कहानी (मुझे लगता है कि क्या timeLoadedहै) CMTime से बाहर: CMTimeGetSeconds
superjos

2
दुर्भाग्य से, यह एक स्वीकृत उत्तर होना चाहिए। यह वास्तव में खेलने के लिए तैयार नहीं होने पर बहुत जल्दी AVPlayerसेट होने लगता है status == AVPlayerStatusReadyToPlay। इस काम को करने के लिए, आप उपरोक्त कोड को NSTimerमंगलाचरण में लपेट सकते हैं , उदाहरण के लिए।
मैक्सकोनोवालोव

क्या यह मामला हो सकता है जहाँ लोड समय सीमा के 2 सेकंड से अधिक (wlog) हो, लेकिन खिलाड़ी या खिलाड़ी का नाम Itemem की स्थिति ReadyToPlay नहीं है? IOW, क्या इसकी पुष्टि की जानी चाहिए?
दानीलधर जूल

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

महत्वपूर्ण: आपको ऑब्जर्वर वैरिएबल को संभाल कर रखना चाहिए अन्यथा यह डील हो जाएगा और चेंजलर अब नहीं मिलेगा। तो पर्यवेक्षक को फ़ंक्शन चर के रूप में परिभाषित न करें, लेकिन इसे दिए गए उदाहरण की तरह एक उदाहरण चर के रूप में परिभाषित करें।

यह मुख्य मान प्रेक्षक सिंटैक्स स्विफ्ट 4 के लिए नया है।

अधिक जानकारी के लिए, यहाँ देखें Contents.swift


धन्यवाद, केवीओ को हटाने के लिए यह विधि बहुत सरल है।
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];
        }
    }
}

प्रेक्षक को हटाने के लिए (निपटाएं, देखें)

- (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" मूल्यांकन का उपयोग नहीं किया था। क्या आप मुझे बता सकते हैं कि इसका क्या उपयोग है?
औररी

ऐसे मामलों के लिए जबcurrentBufferDuration < 2
jose920405

क्या यह मामला हो सकता है जहाँ लोड समय सीमा के 2 सेकंड से अधिक (wlog) हो, लेकिन खिलाड़ी या खिलाड़ी का नाम Itemem की स्थिति ReadyToPlay नहीं है? IOW, क्या इसकी पुष्टि की जानी चाहिए?
दानियालधर

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

कोड में प्रमुख बिंदु उदाहरण संपत्ति है ।PlaybackLikelyToKeepUp।


3
अच्छा उत्तर। मैं forKeyPath: #keyPath(AVPlayer.currentItem.isPlaybackLikelyToKeepUp)
केवीओ में

2019 के लिए, यह पूरी तरह से काम करता है - कॉपी और पेस्ट :) मैंने @MiroslavHrivik के मॉड का उपयोग किया, धन्यवाद!
फेटी

7

टिम काम्बर जवाब के आधार पर , यहां मैं स्विफ्ट फ़ंक्शन का उपयोग कर रहा हूं:

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

विस्तार के साथ, मुझे लगता है कि केवीओ के लिए तैयार संपत्ति का निरीक्षण करना संभव नहीं है। किसी भी तरह से चारों ओर?
जॉनी

मैं नोटिफिकेशन AVPlayerItemNewAccessLogEntryऔर AVPlayerItemDidPlayToEndTimeअपने प्रोजेक्ट में सुनता हूं। अफाक यह काम करता है।
एक्सल गिल्मिन

ठीक है, मैं सुनकर समाप्त हुआ loadedTimeRanges
जॉनी

5

मेरे पास कोई कॉलबैक न मिलने के मुद्दे थे।

यह पता चलता है कि आप स्ट्रीम कैसे बनाते हैं, यह निर्भर करता है। मेरे मामले में मैंने प्रारंभ करने के लिए एक खिलाड़ी का उपयोग किया था, और इस प्रकार मुझे इसके स्थान पर पर्यवेक्षक को जोड़ना था।

उदाहरण के लिए:

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

3

खिलाड़ी की वर्तमान स्थिति की स्थिति की जाँच करें:

if (player.currentItem.status == AVPlayerItemStatusReadyToPlay)

2
player.currentItem.status AVPlayerItemStatusUnkown देता है। मुझे नहीं पता कि आगे क्या करना है। :(
मविष्णु

प्रारंभ में यह मान है 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

@ जोशबर्नफेल्ड का जवाब मेरे काम नहीं आया। यकीन नहीं है कि क्यों। उसने अवलोकन किया 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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.