jellyflood/JellyfinPlayer/OpenCastSwift/Networking/CastDeviceScanner.swift

220 lines
5.4 KiB
Swift

//
// CastDeviceScanner.swift
// OpenCastSwift
//
// Created by Miles Hollingsworth on 4/22/18
// Copyright © 2018 Miles Hollingsworth. All rights reserved.
//
import Foundation
extension CastDevice {
convenience init(service: NetService, info: [String: String]) {
var ipAddress: String?
if let address = service.addresses?.first {
ipAddress = address.withUnsafeBytes { (pointer: UnsafePointer<sockaddr>) -> String? in
var hostName = [CChar](repeating: 0, count: Int(NI_MAXHOST))
return getnameinfo(pointer, socklen_t(address.count), &hostName, socklen_t(NI_MAXHOST), nil, 0, NI_NUMERICHOST) == 0 ? String.init(cString: hostName) : nil
}
}
self.init(id: info["id"] ?? "",
name: info["fn"] ?? service.name,
modelName: info["md"] ?? "Google Cast",
hostName: service.hostName!,
ipAddress: ipAddress ?? "",
port: service.port,
capabilitiesMask: info["ca"].flatMap(Int.init) ?? 0 ,
status: info["rs"] ?? "",
iconPath: info["ic"] ?? "")
}
}
public final class CastDeviceScanner: NSObject {
public weak var delegate: CastDeviceScannerDelegate?
public static let deviceListDidChange = Notification.Name(rawValue: "DeviceScannerDeviceListDidChangeNotification")
private lazy var browser: NetServiceBrowser = configureBrowser()
public var isScanning = false
fileprivate var services = [NetService]()
public fileprivate(set) var devices = [CastDevice]() {
didSet {
NotificationCenter.default.post(name: CastDeviceScanner.deviceListDidChange, object: self)
}
}
private func configureBrowser() -> NetServiceBrowser {
let b = NetServiceBrowser()
b.includesPeerToPeer = true
b.delegate = self
return b
}
public func startScanning() {
guard !isScanning else { return }
browser.stop()
browser.searchForServices(ofType: "_googlecast._tcp", inDomain: "local")
#if DEBUG
NSLog("Started scanning")
#endif
}
public func stopScanning() {
guard isScanning else { return }
browser.stop()
#if DEBUG
NSLog("Stopped scanning")
#endif
}
public func reset() {
stopScanning()
devices.removeAll()
}
deinit {
stopScanning()
}
}
extension CastDeviceScanner: NetServiceBrowserDelegate {
public func netServiceBrowserWillSearch(_ browser: NetServiceBrowser) {
isScanning = true
}
public func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser) {
isScanning = false
}
public func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) {
removeService(service)
service.delegate = self
service.resolve(withTimeout: 30.0)
services.append(service)
#if DEBUG
NSLog("Did find service: \(service) more: \(moreComing)")
#endif
}
public func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) {
guard let service = removeService(service) else { return }
#if DEBUG
NSLog("Did remove service: \(service)")
#endif
guard let deviceId = service.id,
let index = devices.firstIndex(where: { $0.id == deviceId }) else {
#if DEBUG
NSLog("No device")
#endif
return
}
#if DEBUG
NSLog("Removing device: \(devices[index])")
#endif
let device = devices.remove(at: index)
delegate?.deviceDidGoOffline(device)
}
@discardableResult func removeService(_ service: NetService) -> NetService? {
if let index = services.firstIndex(of: service) {
return services.remove(at: index)
}
return nil
}
func addDevice(_ device: CastDevice) {
if let index = devices.firstIndex(where: { $0.id == device.id }) {
let existing = devices[index]
guard existing.name != device.name || existing.hostName != device.hostName else { return }
devices.remove(at: index)
devices.insert(device, at: index)
delegate?.deviceDidChange(device)
} else {
devices.append(device)
delegate?.deviceDidComeOnline(device)
}
}
}
extension CastDeviceScanner: NetServiceDelegate {
public func netServiceDidResolveAddress(_ sender: NetService) {
guard let infoDict = sender.infoDict else {
#if DEBUG
NSLog("No TXT record for \(sender), skipping")
#endif
return
}
#if DEBUG
NSLog("Did resolve service: \(sender)")
NSLog("\(infoDict)")
#endif
guard infoDict["id"] != nil else {
#if DEBUG
NSLog("No id for device \(sender), skipping")
#endif
return
}
addDevice(CastDevice(service: sender, info: infoDict))
}
public func netService(_ sender: NetService, didNotResolve errorDict: [String: NSNumber]) {
removeService(sender)
#if DEBUG
NSLog("!! Failed to resolve service: \(sender) - \(errorDict) !!")
#endif
}
}
extension NetService {
var infoDict: [String: String]? {
guard let data = txtRecordData() else {
return nil
}
var dict = [String: String]()
NetService.dictionary(fromTXTRecord: data).forEach({ dict[$0.key] = String(data: $0.value, encoding: .utf8)! })
return dict
}
var id: String? {
return infoDict?["id"]
}
}
public protocol CastDeviceScannerDelegate: AnyObject {
func deviceDidComeOnline(_ device: CastDevice)
func deviceDidChange(_ device: CastDevice)
func deviceDidGoOffline(_ device: CastDevice)
}