Search code examples
iosarraysavplayerairplaympremotecommandcenter

How to detect if AirPlay is active before playing a video with AVPlayer?


I was using the following code to detect whether or not AirPlay is the current selected route:

let airPlayActive = AVAudioSession
  .sharedInstance()
  .currentRoute
  .outputs
  .first?
  .portType == .airPlay
print("AirPlay active: \(airPlayActive)")

However, that doesn't work in a very specific scenario. My use case is knowing that air play is selected before playing a video. I have two view controllers, say A and B. For testing purposes, I have a timer on A that prints if AirPlay is active every second using the code above.

From A, I present modally view controller B, which basically plays a video using AVPlayer and controls the current selected route using a MPVolumeView. If I present B, waits for the video to play and then change the route to AirPlay, I can see that change being reflected on A (it will start printing true). Then, if I close B and then reopens it, it's possible to tell if AirPlay is active even before playing the video, and that's exactly what I want.

But there's a scenario where this doesn't work, though.

The scenario is (as weird as it may seem), if I add targets to remote commands using MPRemoteCommandCenter, that behavior I described above simply changes! Here's how I'm adding the targets (in view controller B):

let center = MPRemoteCommandCenter.shared()
let pauseCommand = center.pauseCommand
pauseCommand.isEnabled = true
pauseCommand.addTarget(handler: { _ -> MPMPRemoteCommandHandlerStatus in
  // ... doing some business logic
  return .success
})

I am also removing that target in B's deinit:

let center = MPRemoteCommandCenter.shared()
let pauseCommand = center.pauseCommand
pauseCommand.isEnabled = false
pauseCommand.addTarget(nil)

Well, this just messes entirely with all AirPlay related stuff:

  1. If I activate AirPlay on view controller B and then dismiss it, AirPlay gets deactivated. Now current portType becomes .builtInSpeaker again. Thus, I can not tell if AirPlay is active before reopening B (playing the video).

  2. Even though portType is .builtInSpeaker after closing B, I still get true if I call MPVolumeView().isWirelessRouteActive, which tells me that a wireless route is active, but that's not necessarily AirPlay (it could be a Bluetooth headphone, for instance).

  3. If I reopen B, portType goes from .builtInSpeaker to .airPlay after the video starts playing.

  4. The audio route change notification also stops working properly. When observing AVAudioSession.routeChangeNotification, it doesn't trigger sometimes and when it does, the user info at AVAudioSessionRouteChangeReasonKey most of the times is .unknown.

For me, this is clearly a bug on Apple side, but would any of you happen to know an alternative? I basically need to know if AirPlay is active before playing the video.

Thanks!


Solution

  • Well, I found a workaround. It's as weird as the issue itself, but it's working...

    I just added a target to a remote command right before playing video on view controller B. Then I just wait for a audio route change notification (or a timeout in case is not active). That signals it is "safe" to play the video.