How to customize the Airplay button when airplay is active

爷,独闯天下 提交于 2019-12-21 11:35:17

问题


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.


回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/12318377/how-to-customize-the-airplay-button-when-airplay-is-active

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!