如何通过编程将本地音频发送至指定AirPlay音箱?
问题描述
我希望将本地MP3文件播放至家中已知的支持AirPlay的音箱。我可以通过编程调出AVRoutePickerView,但更希望获取已知AirPlay音箱列表,通过名称查找目标音箱并编程选择它来播放文件。这仅用于概念验证,不会用于实际发布。我曾尝试破解选择器获取设备列表并触发对应按钮的touchUpInside事件,但一无所获。
2023年3月7日更新
我尝试发现AirPlay设备,希望通过MCPeerID进行连接,但目前还不知道具体方法。当我实现以下类时,我得到了NetService名称列表,但仍不清楚如何连接到目标设备并播放本地MP3文件。
let apd = AirPlaySpeakerDiscovery() apd.start()
这会在控制台输出服务名称,非常好。现在如何将本地MP3文件路由至其中一个服务?比如"Kitchen HomePod"?
import Foundation import Network class AirPlaySpeakerDiscovery: NSObject { private var browser: NetServiceBrowser? private var airplayServices = [NetService]() override init() { super.init() self.browser = NetServiceBrowser() self.browser?.delegate = self } func start() { self.browser?.searchForServices(ofType: "_airplay._tcp.", inDomain: "local.") } func stop() { self.browser?.stop() } func printAirPlayServices() { for service in self.airplayServices { print("Service Name: \(service.name)") // How to connect to one of them now? if (service.name == "Kitchen HomePod") { // Play local mp3 file on it? } } } } extension AirPlaySpeakerDiscovery: NetServiceBrowserDelegate { func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) { self.airplayServices.append(service) if !moreComing { self.printAirPlayServices() } } func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) { if let index = self.airplayServices.firstIndex(of: service) { self.airplayServices.remove(at: index) } if !moreComing { self.printAirPlayServices() } } func netServiceBrowser(_ browser: NetServiceBrowser, didNotSearch errorDict: [String : NSNumber]) { print("Error searching for AirPlay services: \(errorDict)") } }
解决方案
针对概念验证场景,可以结合NetService解析和AVFoundation框架实现需求,步骤如下:
1. 完善NetService解析,获取设备完整信息
当前代码仅获取了服务名称,需要进一步解析服务的IP、端口和AirPlay设备标识。修改原类,添加服务解析逻辑:
// 在AirPlaySpeakerDiscovery类中添加方法和扩展 func printAirPlayServices() { for service in self.airplayServices { if service.name == "Kitchen HomePod" { // 解析服务的地址和TXT记录 service.resolve(withTimeout: 5.0) service.delegate = self } } } extension AirPlaySpeakerDiscovery: NetServiceDelegate { func netServiceDidResolveAddress(_ sender: NetService) { guard let addresses = sender.addresses, let firstAddr = addresses.first else { print("无法获取设备地址") return } // 解析IP和端口 var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) var portStr = [CChar](repeating: 0, count: Int(NI_MAXSERV)) getnameinfo(firstAddr.bytes, socklen_t(firstAddr.count), &hostname, socklen_t(hostname.count), &portStr, socklen_t(portStr.count), NI_NUMERICHOST | NI_NUMERICSERV) let ip = String(cString: hostname) let port = String(cString: portStr) // 从TXT记录中获取设备唯一标识 if let txtData = sender.txtRecordData() { let txtDict = NetService.dictionary(fromTXTRecord: txtData) if let deviceIDData = txtDict[b"deviceid"], let deviceID = String(data: deviceIDData, encoding: .utf8) { print("设备信息:IP=\(ip),端口=\(port),DeviceID=\(deviceID)") // 触发播放逻辑 playMP3OnTargetSpeaker(deviceName: sender.name) } } } func netService(_ sender: NetService, didNotResolve errorDict: [String : NSNumber]) { print("解析服务失败:\(errorDict)") } }
2. 用AVFoundation实现路由选择与播放
利用AVAudioSession的路由管理功能,匹配目标设备后播放本地MP3:
import AVFoundation func playMP3OnTargetSpeaker(deviceName: String) { let audioSession = AVAudioSession.sharedInstance() do { // 配置音频会话为播放模式 try audioSession.setCategory(.playback, mode: .default) try audioSession.activate() } catch { print("音频会话初始化失败:\(error)") return } // 遍历可用输出路由,匹配目标设备 for route in audioSession.availableOutputRoutes { if route.portName == deviceName { do { // 设置首选输出路由 try audioSession.setPreferredOutputAudioPort(route.portType) // 初始化AVPlayer播放本地MP3 guard let mp3URL = Bundle.main.url(forResource: "你的MP3文件名", withExtension: "mp3") else { print("找不到本地MP3文件") return } let player = AVPlayer(url: mp3URL) player.play() } catch { print("设置路由失败:\(error)") } break } } }
注意事项
- 公开API无法直接通过IP指定AirPlay路由,只能通过设备名称匹配;若需要更直接的控制,可尝试AirPlay私有框架(仅适用于概念验证,违反App Store规则)。
- 需在
Info.plist中添加NSAppleMusicUsageDescription权限描述,确保应用能访问音频资源。 - 若存在同名设备,建议结合解析到的
DeviceID做更精确的匹配。
内容的提问来源于stack exchange,提问作者Eric Dolecki
相关产品推荐
相关产品推荐

