4

I'm using MPVolumeView to pick airplay device for avplayer airplay playback. Is there any possible non-private API alternative for doing this, so I would be able to provide my own UI Controls for picking airplay device?

By referring to the API, I mean, that all I need is:

  1. Ability to reroute audio to airplay-device specific audioRoute.
  2. Retrive airplay-device names. (get all available audioRoutes, then get descriptions for airplay audioRoutes)

I know AudioToolbox framework provides some additional API to deal AudioSession, but the only way I found to reroute audio is AVAudioSession's:

- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride  error:(NSError **)outError`

which only allows to reroute audio to build-in speakers. Maybe there is some other way how to achieve it there? (I also did only found the way how to retrieve the name of AirplayDevice as a description of the currentAudioRoute - Get name of AirPlay device using AVPlayer)

Community
  • 1
  • 1
ambientlight
  • 7,212
  • 3
  • 49
  • 61

2 Answers2

6

So the precise answer to my question:

(i) It's not possible to switch audioRoutes programatically with public API except switching to build-in speakers.

[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];

(ii) You can only retrieve a name of airplay-device if it's active AudioRoute. Get name of AirPlay device using AVPlayer

So the practical solution to presenting customised UI Controls for selecting airplay would be:

To customise MPVolumeView, where you can disable volumeSliderand customise routeButton. However you have no other option as picking airplayDevice among list of apple-compatible wireless devices (airPlay, bluetooth, etc) in UIActionSheet that pop ups when you tap on routeButton, but you can observe when user will make a selection there by subscribing to audioRouteChangeNotification:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteHasChangedNotification:) name:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance]];

(also note that if you will plug in/out headphones, it also will trigger this notification)

If you are interested how to retrieve all available audioRoutes and switch programmatically with private API:

MPMediaPlayer framework contains a private class MPAVRoutingController, which allows you exactly that:

Class MPAVRoutingController = NSClassFromString(@"MPAVRoutingController");
Class MPAVRoute = NSClassFromString(@"MPAVRoute");

id routingController = [[MPAVRoutingController alloc] init];
NSArray* availableRoutes = [routingController performSelector:@selector(availableRoutes)];
BOOL isSwitchSuccesful = [[routingController performSelector:@selector(pickRoute:) withObject:availableRoutes.lastObject] boolValue];

(if you want to then access audioRoute info and check if it is Airplay: Detecting airplayRoute)

Community
  • 1
  • 1
ambientlight
  • 7,212
  • 3
  • 49
  • 61
  • I had a [similar question](http://stackoverflow.com/questions/31864398/ios-airplay-action-sheet-for-public-app-store-publishing-like-in-spotify) and was wondering if even a regular Action Sheet without customizations would be possible without resorting to the private API? – Roberto Andrade Aug 06 '15 at 19:40
  • I am unaware if they did add any new API to it, but it seems that we cannot do anything with their ActionSheet(now it is called AlertController) directly. You can try to play with it - like try accessing its view subviews and try sendActionsForControlEvents to see if you can programmatically fake user touches, if you can then you probably may be to switch routes programmatically without private API – ambientlight Aug 07 '15 at 02:26
  • Can u check if MPAVRoutingController is still working in iOS 11.3.1, for me its not working anymore. – Jas_meet May 09 '18 at 08:24
1

One should use AVRoutePickerView to manage all audio outputs

For instance, one can make subclass as follows

final class AMRoutePickerView: AVRoutePickerView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        translatesAutoresizingMaskIntoConstraints = false
        tintColor = .clear
        activeTintColor = .clear
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Here we hide visual content of this view by setting .clear. Now one can put this view as top subview of any view or button from touchable UI.

malex
  • 9,874
  • 3
  • 56
  • 77