-
-
Notifications
You must be signed in to change notification settings - Fork 88
/
Copy pathOutputDevices.swift
97 lines (82 loc) · 3.37 KB
/
OutputDevices.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//
// OutputDevices.swift
// Quality
//
// Created by Vincent Neo on 20/4/22.
//
import Combine
import Foundation
import SimplyCoreAudio
class OutputDevices: ObservableObject {
@Published var defaultOutputDevice: AudioDevice?
@Published var outputDevices = [AudioDevice]()
@Published var currentSampleRate: Float64?
private let coreAudio = SimplyCoreAudio()
private var changesCancellable: AnyCancellable?
private var defaultChangesCancellable: AnyCancellable?
private let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
private var timerCancellable: AnyCancellable?
private var consoleQueue = DispatchQueue(label: "consoleQueue", qos: .userInteractive)
init() {
self.outputDevices = self.coreAudio.allOutputDevices
self.defaultOutputDevice = self.coreAudio.defaultOutputDevice
self.getDeviceSampleRate()
changesCancellable =
NotificationCenter.default.publisher(for: .deviceListChanged).sink(receiveValue: { _ in
self.outputDevices = self.coreAudio.allOutputDevices
})
defaultChangesCancellable =
NotificationCenter.default.publisher(for: .defaultOutputDeviceChanged).sink(receiveValue: { _ in
self.defaultOutputDevice = self.coreAudio.defaultOutputDevice
self.getDeviceSampleRate()
})
timerCancellable = timer.sink(receiveValue: { _ in
self.consoleQueue.async {
self.switchLatestSampleRate()
}
})
}
deinit {
changesCancellable?.cancel()
defaultChangesCancellable?.cancel()
timerCancellable?.cancel()
timer.upstream.connect().cancel()
}
func getDeviceSampleRate() {
let defaultDevice = self.defaultOutputDevice
guard let sampleRate = defaultDevice?.nominalSampleRate else { return }
self.updateSampleRate(sampleRate)
}
func switchLatestSampleRate() {
do {
let musicLog = try Console.getRecentEntries()
let cmStats = CMPlayerParser.parseMusicConsoleLogs(musicLog)
let defaultDevice = self.defaultOutputDevice
if let first = cmStats.first, let supported = defaultDevice?.nominalSampleRates {
let sampleRate = Float64(first.sampleRate)
// https://stackoverflow.com/a/65060134
let nearest = supported.enumerated().min(by: {
abs($0.element - sampleRate) < abs($1.element - sampleRate)
})
if let nearest = nearest {
let nearestSampleRate = nearest.element
if nearestSampleRate != defaultDevice?.nominalSampleRate {
defaultDevice?.setNominalSampleRate(nearestSampleRate)
self.updateSampleRate(nearestSampleRate)
}
}
}
}
catch {
print(error)
}
}
func updateSampleRate(_ sampleRate: Float64) {
DispatchQueue.main.async {
let readableSampleRate = sampleRate / 1000
self.currentSampleRate = readableSampleRate
let delegate = AppDelegate.instance
delegate?.statusItemTitle = String(format: "%.1f kHz", readableSampleRate)
}
}
}