Skip to content

Commit 177700e

Browse files
committed
✨ add support for bluetooth devices
1 parent eb630a4 commit 177700e

File tree

2 files changed

+46
-1
lines changed

2 files changed

+46
-1
lines changed

Sources/AudioInput.swift

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public extension Notification.Name {
88

99
final class AudioInput {
1010
private var callback: (Bool) -> Void
11+
private var timer: Timer?
1112
private var devices: Set<AudioDevice> {
1213
didSet {
1314
let added = devices.subtracting(oldValue)
@@ -18,10 +19,23 @@ final class AudioInput {
1819
}
1920
}
2021
}
22+
private var hasBluetooth: Bool {
23+
didSet {
24+
guard hasBluetooth != oldValue else { return }
25+
26+
timer?.invalidate()
27+
guard hasBluetooth == true else { return }
28+
29+
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
30+
NotificationCenter.default.post(name: .deviceIsRunningSomewhereDidChange, object: nil)
31+
}
32+
}
33+
}
2134

2235
init(_ callback: @escaping (Bool) -> Void) {
2336
self.devices = Set()
2437
self.callback = callback
38+
self.hasBluetooth = false
2539
}
2640

2741
private func updateDeviceList() {
@@ -30,11 +44,12 @@ final class AudioInput {
3044
guard devices != self.devices else { return }
3145

3246
self.devices = devices
47+
self.hasBluetooth = devices.contains(where: \.isBluetooh)
3348
}
3449

3550
private lazy var listener = Debouncer(delay: 0.5) { [weak self] in
3651
guard let self else { return }
37-
self.callback(self.devices.isRunningSomewhere() ?? false)
52+
self.callback(self.isRunningSomewhere)
3853
}
3954

4055
func startListener() {
@@ -49,3 +64,16 @@ final class AudioInput {
4964
}
5065
}
5166
}
67+
68+
extension AudioInput {
69+
private func hasOrangeDot() -> Bool {
70+
let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID) as? [[CFString: Any]]
71+
return (windows ?? []).contains { $0[kCGWindowName] as? String == "StatusIndicator" }
72+
}
73+
74+
private var isRunningSomewhere: Bool {
75+
// FB12081267: bluetooth input devices always report that isRunningSomewhere == false
76+
if hasBluetooth { return hasOrangeDot() }
77+
return devices.isRunningSomewhere() ?? false
78+
}
79+
}

Sources/SFBAudioEngine/AudioDevice.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ public class AudioDevice: AudioObject {
4848

4949
// MARK: - Audio Device Base Properties
5050

51+
extension AudioDevice {
52+
/// Returns the transport type
53+
/// - remark: This corresponds to the property `kAudioDevicePropertyTransportType`
54+
public func transportType() throws -> UInt32 {
55+
return try getProperty(PropertyAddress(kAudioDevicePropertyTransportType), type: UInt32.self)
56+
}
57+
}
58+
5159
// MARK: - Audio Device Properties
5260

5361
extension AudioDevice {
@@ -109,6 +117,15 @@ extension AudioDevice {
109117
guard let inputs else { return nil }
110118
return Set(inputs)
111119
}
120+
121+
var isBluetooh: Bool {
122+
guard let transport = try? transportType() else { return false }
123+
switch(transport){
124+
case(kAudioDeviceTransportTypeBluetooth): return true
125+
case(kAudioDeviceTransportTypeBluetoothLE): return true
126+
default: return false
127+
}
128+
}
112129
}
113130

114131
extension Set where Element == AudioDevice {

0 commit comments

Comments
 (0)