简体   繁体   中英

iOS AVPlayer streaming music issues

I'm having some really weird issues with AVPlayer . I have a very simple music player (which streams sample music from the iTunes store) working with it correctly on the simulator (both iPhone and iPad with iOS 5.1) but it behaves abnormally on a real device.

On an iPad 2 with iOS 5.1.1 it plays correctly even if I connect my headphones to the device. But as soon as I disconnect them it won't play a sound through the speakers (even though if I connect them again I can listen to the song).

On an iPhone 4 with iOS 5.1 installed it doesn't seem to play at all through the speakers but I can listen to the music through my headphones. Although it doesn't seem to be playing through the speakers I can hear now and then for a very brief moment the music playing (and can confirm that it in fact is playing because my UI responds accordingly) although it seems to be random.

I'm using AVPlayer since it seems to fit the requirements. Should I use another library? Do I need to manually route the sound? Why am I having these types of problems? And am I using Audio Sessions correctly?

Media.h:

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>

#define NOT_PLAYING -1

@protocol MediaDelegate <NSObject>
@optional
- (void) didEndPlayingAtIndex:(int) index;
- (void) startedToPlayAtIndex:(int) index;
- (void) stoppedToPlayAtIndex:(int) index;
- (void) startedToPlayAtIndex:(int) to fromIndex:(int) from;
- (void) pausedAtIndex:(int) index;
@end

@interface Media : NSObject <AVAudioSessionDelegate>

@property (nonatomic, assign) int currentItem;
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, assign) id<MediaDelegate> delegate;

- (void) toggle:(int) index;
- (void) stop;

@end

Media.m:

#import "Media.h"

@implementation Media

@synthesize currentItem;
@synthesize player;
@synthesize delegate;
@synthesize url;

- (id)init
{
    self = [super init];
    if (self) {
        NSError *activationError = nil;
        NSError *setCategoryError = nil;
        AVAudioSession *session = [AVAudioSession sharedInstance];
        session.delegate = self;
        [session setActive:YES error:&activationError];
        [session setCategory:AVAudioSessionCategoryPlayback error:&setCategoryError];

        if(activationError || setCategoryError)
            NSLog(@"ERROR: %@, %@",activationError,setCategoryError);

        self.currentItem = NOT_PLAYING;
    }
    return self;
}

- (void)dealloc{
    NSError *activationError = nil;
    [[AVAudioSession sharedInstance] setActive:NO error:&activationError];

    if(activationError)
        NSLog(@"ERROR: %@",activationError);

    [self.player removeObserver:self forKeyPath:@"status"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{    
    switch (player.status) {
        case AVPlayerItemStatusReadyToPlay:
            [self.player play];
            if([self.delegate respondsToSelector:@selector(startedToPlayAtIndex:)])
                [self.delegate startedToPlayAtIndex:self.currentItem];
            break;
        default:
            break;
    }
}

- (void) toggle:(int) index{
    if (self.currentItem == NOT_PLAYING) {
        self.player = [AVPlayer playerWithPlayerItem:[AVPlayerItem playerItemWithURL:self.url]];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(didEnd:)
                                                     name:AVPlayerItemDidPlayToEndTimeNotification 
                                                   object:self.player];
        self.currentItem = index;
        [self.player addObserver:self forKeyPath:@"status" options:0 context:nil];
    }
    else {
        if (self.currentItem == index) {
            [self.player pause];
            if([self.delegate respondsToSelector:@selector(stoppedToPlayAtIndex:)])
                [self.delegate stoppedToPlayAtIndex:index];
            self.currentItem = NOT_PLAYING;
            [self.player removeObserver:self forKeyPath:@"status"];
            self.player = nil;
            [[NSNotificationCenter defaultCenter] removeObserver:self];
        }
        else{
            [self.player replaceCurrentItemWithPlayerItem:[AVPlayerItem playerItemWithURL:self.url]];
            if([self.delegate respondsToSelector:@selector(startedToPlayAtIndex:fromIndex:)])
                [self.delegate startedToPlayAtIndex:index fromIndex:self.currentItem];
            self.currentItem = index;
        }
    }
}

- (void) stop{
    [self.player pause];
    if([self.delegate respondsToSelector:@selector(stoppedToPlayAtIndex:)])
        [self.delegate stoppedToPlayAtIndex:self.currentItem];
}

-(void) didEnd:(id)sender{
    if([self.delegate respondsToSelector:@selector(didEndPlayingAtIndex:)])
        [self.delegate didEndPlayingAtIndex:self.currentItem];
    self.currentItem = NOT_PLAYING;
    [self.player removeObserver:self forKeyPath:@"status"];
    self.player = nil;
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

#pragma mark - AVAudioSession delegate

-(void)beginInterruption{
    NSLog(@"Interruption");
    if(self.currentItem != NOT_PLAYING){
        [self.player pause];
        if([self.delegate respondsToSelector:@selector(pausedAtIndex:)])
            [self.delegate pausedAtIndex:self.currentItem];
    }
}

-(void)endInterruption{
    NSLog(@"Ended interruption");
    if(self.currentItem != NOT_PLAYING){
        [self.player play];
        if([self.delegate respondsToSelector:@selector(startedToPlayAtIndex:)])
            [self.delegate startedToPlayAtIndex:self.currentItem];
    }
}

@end

It looks like you have a problem with correct audio routing. I suggest to add an audio route change listener callback. First of all declare a method:

void audioRouteChangeListenerCallback(void *inUserData, AudioSessionPropertyID inPropertyID, UInt32 inPropertyValueSize, const void *inPropertyValue);

Then in your init method add a callback:

// Prevent from audio issues when you pull out earphone
AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, audioRouteChangeListenerCallback, (__bridge void *)(self));

And finally, add a callback implementation:

void audioRouteChangeListenerCallback(void *inUserData, AudioSessionPropertyID inPropertyID, UInt32 inPropertyValueSize, const void *inPropertyValue) {
    if (inPropertyID != kAudioSessionProperty_AudioRouteChange) {
        return;
    }

    CFDictionaryRef routeChangeDictionary = inPropertyValue;
    CFNumberRef     routeChangeReasonRef  = CFDictionaryGetValue(routeChangeDictionary, CFSTR(kAudioSession_AudioRouteChangeKey_Reason));
    SInt32          routeChangeReason;
    CFNumberGetValue(routeChangeReasonRef, kCFNumberSInt32Type, &routeChangeReason);

    if (routeChangeReason == kAudioSessionRouteChangeReason_OldDeviceUnavailable) {
        // Headset is unplugged..
        NSLog(@"Headset is unplugged..");
        // Delayed play: 0.5 s.
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            [CORE.player play]; // NOTE: change this line according your current player implementation
            NSLog(@"resumed play");
        });
    }
    if (routeChangeReason == kAudioSessionRouteChangeReason_NewDeviceAvailable) {
        // Headset is plugged in..
        NSLog(@"Headset is plugged in..");
    }
}

Hope this help! At least for other people it will be helpful tip.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM