Get name of AirPlay device using AVPlayer
After searching in other frameworks to get the name of the Apple TV you're connected to, I finally found this information in the AudioToolbox framework. There may be other ways to get this, but so far I have not found another way. Hope this helps.
You'll need to import the AudioToolbox framework:
#import <AudioToolbox/AudioToolbox.h>
and then a method to call when you want to detect if airplay is available
- (BOOL)isAirplayActive {
CFDictionaryRef currentRouteDescriptionDictionary = nil;
UInt32 dataSize = sizeof(currentRouteDescriptionDictionary);
AudioSessionGetProperty(kAudioSessionProperty_AudioRouteDescription, &dataSize, ¤tRouteDescriptionDictionary);
self.deviceOutputType = nil;
self.airplayDeviceName = nil;
if (currentRouteDescriptionDictionary) {
CFArrayRef outputs = CFDictionaryGetValue(currentRouteDescriptionDictionary, kAudioSession_AudioRouteKey_Outputs);
if(CFArrayGetCount(outputs) > 0) {
CFDictionaryRef currentOutput = CFArrayGetValueAtIndex(outputs, 0);
//Get the output type (will show airplay / hdmi etc
CFStringRef outputType = CFDictionaryGetValue(currentOutput, kAudioSession_AudioRouteKey_Type);
//If you're using Apple TV as your ouput - this will get the name of it (Apple TV Kitchen) etc
CFStringRef outputName = CFDictionaryGetValue(currentOutput, @"RouteDetailedDescription_Name");
self.deviceOutputType = (NSString *)outputType;
self.airplayDeviceName = (NSString *)outputName;
return (CFStringCompare(outputType, kAudioSessionOutputRoute_AirPlay, 0) == kCFCompareEqualTo);
}
}
return NO;
}
How to get the AirPlay device name without using iOS7 deprecated methods
Starting from iOS6 AudioSession exposes currentRoute to you, which allows retrieving it's port information as well as listening to audioRouteChangeNotification.
(so you no longer need to use C-based API from AudioToolbox framework):
NSString* airplayName = [self activeAirplayOutputRouteName];
if (airplayName) {
//airplay is active
}
(what you want to get is the portDescription of currentAudioRoute):
- (NSString*)activeAirplayOutputRouteName
{
AVAudioSession* audioSession = [AVAudioSession sharedInstance];
AVAudioSessionRouteDescription* currentRoute = audioSession.currentRoute;
for (AVAudioSessionPortDescription* outputPort in currentRoute.outputs){
if ([outputPort.portType isEqualToString:AVAudioSessionPortAirPlay])
return outputPort.portName;
}
return nil;
}
Airplay with AVPlayer?
It's not possible in iOS4, but will be in iOS5.
iOS Airplay - Playing on your TV
This is how I implemented it. It works like a charm !
//Airplay constants
private var observerContextAirplay = 1
private var propertyToObserveAirplay = "externalPlaybackActive"
// MARK: AirPlay Key-value Observing
func startObservingForAirPlayStatusChanges()
{
self.player.moviePlayer.addObserver(self, forKeyPath: propertyToObserveAirplay, options: .New, context: &observerContextAirplay)
}
func stopObservingForAirPlayStatusChanges()
{
self.player.moviePlayer.removeObserver(self, forKeyPath: propertyToObserveAirplay)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &observerContextAirplay {
if self.player.moviePlayer.externalPlaybackActive == true
{
self.setUpAirPlayView()
}
else if self.player.moviePlayer.externalPlaybackActive == false
{
self.resetPlayerView()
}
}
else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
//Setup AirplayView
func setUpAirPlayView()
{
//Airplay Button
if self.airPlay_PlayBtn == nil
{
let playImage = UIImage(named: "player_play.png")!
if let _ = self.airPlay_PlayBtnFrame
{
self.airPlay_PlayBtn = UIButton(frame: self.airPlay_PlayBtnFrame)
self.airPlay_PlayBtn.setBackgroundImage(playImage, forState: UIControlState.Normal)
self.airPlay_PlayBtn.addTarget(self, action: #selector(ICFPlayerViewController.airPlayButtonAction), forControlEvents: UIControlEvents.TouchUpInside)
self.airPlay_PlayBtn.setBackgroundImage(UIImage(named: "player_play.png"), forState: .Normal)
self.airPlay_PlayBtn.setBackgroundImage(UIImage(named: "player_pause.png"), forState: .Selected)
self.airPlay_PlayBtn.center = self.view.center
self.view.addSubview(self.airPlay_PlayBtn)
if let _ = self.player
{
self.player.playPauseButton.hidden = true
}
}
}
else
{
self.airPlay_PlayBtn.hidden = false
if let _ = self.player
{
self.player.playPauseButton.hidden = true
}
}
// Airplay Label
if self.airPlayLabel == nil
{
self.airPlayLabel = UILabel()
self.airPlayLabel.frame = CGRectMake(0, 0, 280, 20)
self.airPlayLabel.text = "Your video is now playing on Apple TV"
self.airPlayLabel.textAlignment = NSTextAlignment.Center
self.airPlayLabel.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.6)
self.airPlayLabel.textColor = UIColor.whiteColor()
self.airPlayLabel.sizeToFit()
self.airPlayLabel.center = self.view.center
self.airPlayLabel.center.y = self.view.center.y - self.activityIndicator.frame.size.height*1.5
self.view.addSubview(self.airPlayLabel)
}
else
{
self.airPlayLabel.hidden = false
}
// Thumbnail
self.setupContentThumbnailImageView() //Fetch Thumbnail image
if let _ = self.thumbnailImage
{
self.view.addSubview(self.thumbnailImage)
}
self.view.bringSubviewToFront(bottomToolbar)
self.view.bringSubviewToFront(topToolbar)
if let _ = self.airPlayLabel
{
self.view.bringSubviewToFront(self.airPlayLabel)
}
if let _ = self.airPlay_PlayBtn
{
self.view.bringSubviewToFront(self.airPlay_PlayBtn)
}
}
iOS: Airplay picker MPVolumeView alternative
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 volumeSlider
and 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)
Directing Audio to Air Play Speakers from a MacOS Swift Application Using AVPlayer
Instantiate an AVRoutePickerView
in your view hierarchy then assign your AVPlayer
instance to the AVRoutePickerView
's player
property. You'll then have a GUI for choosing the AirPlay route(s) for your AVPlayer
.
Related Topics
iOS Check If Application Has Access to Microphone
Xcode 4.3.2, Issue with Running on Simulator
How to Use Unsafemutablepointer in Swift 3
Fix Cordova Geolocation Ask for Location Message
Audiokit: Using the New Aksequencer with Any Variety of the Callback Instruments
How to Check If a Static Library Is Built for 64-Bit
Xcode Error While Validation - "Your Binary Is Not Optimized for iPhone 5"
Changing Uitableview's Section Header/Footer Title Without Reloading the Whole Table View
Swift: Navigate to New Viewcontroller Using Button
Why Would a 'Scheduledtimer' Fire Properly When Setup Outside a Block, But Not Within a Block
Determine User's "Temperature Unit" Setting on iOS 10 (Celsius/Fahrenheit)
How to Send Data from iPhone to Apple Watch in Os2 in Objective-C