Question

Been trying to solve this issue for 2 days now and I give up. Im trying to implement a customized airplay button (I have to beacuse the background is white and the button must be black). Ive added a view in interfacebuilder and chose mpVolumeView for it. Then I made the connections and wrote the following code;

viewDidLoad.. {
    .....

    [_volumeView setShowsVolumeSlider:NO];
    for (UIButton *button in _volumeView.subviews) {
        if ([button isKindOfClass:[UIButton class]]) {
            [button setImage:[UIImage imageNamed:@"airplay_icon.png"] forState:UIControlStateNormal];
            [button addObserver:self forKeyPath:@"alpha" options:NSKeyValueObservingOptionNew context:nil];
            [button addTarget:self action:@selector(switchAirplayButton) forControlEvents:UIControlEventTouchUpInside];
            [button sizeToFit];
        }
    }
    [_volumeView sizeToFit];

}

-(void)switchAirplayButton {

    for (UIButton *button in _volumeView.subviews) {
        if ([button isKindOfClass:[UIButton class]]) {

            NSLog(@"%d", _controlBar.player.airPlayVideoActive);

            if(_controlBar.player.airPlayVideoActive) {
                [button setImage:[UIImage imageNamed:@"airplay_icon_pressed.png"] forState:UIControlStateNormal];
            } else  [button setImage:[UIImage imageNamed:@"airplay_icon.png"] forState:UIControlStateNormal];

            [button sizeToFit];
        }
    }


}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([object isKindOfClass:[UIButton class]] && [[change valueForKey:NSKeyValueChangeNewKey] intValue] == 1) {
        [(UIButton *)object setImage:[UIImage imageNamed:@"airplay_icon.png"] forState:UIControlStateNormal];
        [(UIButton *)object sizeToFit];
    }
}

The "player" is a singelton based on AVPLayer. However, it always returns false when checking if airPlay is active. Maybe it's just beacuse im using sound, not video.

So my question is, how could I change the button to... lets say an orange one (just to match the rest of the interface) when airplay is streaming (just like apple is making it blue). I have tried everything and it's just not working at all. Please help me.

Was it helpful?

Solution

Edit:

Though the code below works on both iOS 5 and 6, starting with iOS 6.0 there is an official way to do this, which is much much easier. Just look at the documentation of MPVolumeView, specifically – setRouteButtonImage:forState:.

==== Old answer: ====

This is pretty hard to accomplish, but I found a way for iOS 5.0+. First of all, add the following line to your ViewController:

#import <AudioToolbox/AudioToolbox.h>

In your viewDidLoad, you're already doing most of the things right, this is my code:

for (id current in self.volumeView.subviews){
    if([current isKindOfClass:[UIButton class]]) {
        UIButton *airPlayButton = (UIButton*)current;
        self.airPlayButton = airPlayButton;
        [self setAirPlayButtonSelected:[self isAirPlayActive]];
        [airPlayButton addObserver:self forKeyPath:@"alpha" options:NSKeyValueObservingOptionNew context:nil];
    }
}

Here's the helper setAirPlayButtonSelected method, it just sets the image:

- (void)setAirPlayButtonSelected:(BOOL)selected {
    UIImage* image;
    if (selected) {
        image = [UIImage imageNamed:@"button-airplay-selected"];
    }else {
        image = [UIImage imageNamed:@"button-airplay"];
    }
    [self.airPlayButton setImage:image forState:UIControlStateNormal];
    [self.airPlayButton setImage:image forState:UIControlStateHighlighted];
    [self.airPlayButton setImage:image forState:UIControlStateSelected];
}

For completion's sake, the observeValueForKeyPath:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if (object == self.airPlayButton && [[change valueForKey:NSKeyValueChangeNewKey] intValue] == 1) {
        [self setAirPlayButtonSelected:[self isAirPlayActive]];
    }
}

And now comes the interesting part. Here's the isAirPlayActive helper method. It uses the AudioSession framework to determine the currently playing audioSource.

- (BOOL)isAirPlayActive{
    CFDictionaryRef currentRouteDescriptionDictionary = nil;
    UInt32 dataSize = sizeof(currentRouteDescriptionDictionary);
    AudioSessionGetProperty(kAudioSessionProperty_AudioRouteDescription, &dataSize, &currentRouteDescriptionDictionary);
    if (currentRouteDescriptionDictionary) {
        CFArrayRef outputs = CFDictionaryGetValue(currentRouteDescriptionDictionary, kAudioSession_AudioRouteKey_Outputs);
        if(CFArrayGetCount(outputs) > 0) {
            CFDictionaryRef currentOutput = CFArrayGetValueAtIndex(outputs, 0);
            CFStringRef outputType = CFDictionaryGetValue(currentOutput, kAudioSession_AudioRouteKey_Type);
            return (CFStringCompare(outputType, kAudioSessionOutputRoute_AirPlay, 0) == kCFCompareEqualTo);
        }
    }

    return NO;
}

So all this code changes the AirPlay Button correctly on app launch. What about updates? We need to listen for AudioSource changes. Add the following line to your viewDidLoad:

    AudioSessionAddPropertyListener (kAudioSessionProperty_AudioRouteChange, audioRouteChangeCallback, (__bridge void*)self);

Don't forget to unregister in dealloc:

- (void)dealloc {
    [self.airPlayButton removeObserver:self forKeyPath:@"alpha"];

    AudioSessionRemovePropertyListenerWithUserData(kAudioSessionProperty_AudioRouteChange, audioRouteChangeCallback, (__bridge void*)self);

}

And add this C function above your ViewController's @implementation:

void audioRouteChangeCallback (void                   *inUserData,
                                       AudioSessionPropertyID inPropertyID,
                                       UInt32                 inPropertyValueSize,
                                       const void             *inPropertyValue) {

    if (inPropertyID != kAudioSessionProperty_AudioRouteChange) {
        return;
    }

    CFDictionaryRef routeChangeDictionary = inPropertyValue;

    CFDictionaryRef currentRouteDescriptionDictionary = CFDictionaryGetValue(routeChangeDictionary, kAudioSession_AudioRouteChangeKey_CurrentRouteDescription);
    CFArrayRef outputs = CFDictionaryGetValue(currentRouteDescriptionDictionary, kAudioSession_AudioRouteKey_Outputs);
    if(CFArrayGetCount(outputs) > 0) {
        CFDictionaryRef currentOutput = CFArrayGetValueAtIndex(outputs, 0);
        CFStringRef outputType = CFDictionaryGetValue(currentOutput, kAudioSession_AudioRouteKey_Type);

        [(__bridge SettingsViewController*)inUserData setAirPlayButtonSelected:CFStringCompare(outputType, kAudioSessionOutputRoute_AirPlay, 0) == kCFCompareEqualTo];
    }

}

As you can see, all it does is determine whether or not an AirPlay output source is active and calls the setAirPlayButtonSelected method accordingly.

See Apple's Audio Session Programming Guide, specifically this section for detailed information on how the callbacks exactly work, etc.

OTHER TIPS

Check out this similar link here.

I'm not sure if this would work, but try making a black button with the airplay symbol, then put it on top of airplay button that's hard to see. You should set user interaction to disabled. Try it, it might be an easier solution.

*don't forget to set user interaction disabled on the attributes inspector and the identity inspector.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top