Audiomixer zeigt Programme statt Prozesse
Core Audio meldet Prozesse, nicht Programme. Angezeigt wurden deshalb Kennungen wie „com.apple.WebKit.GPU" und Dutzende Systemdienste, die theoretisch Ton ausgeben könnten, es aber nie tun. ProcessNaming fragt erst das laufende Programm, kürzt sonst bekannte Hilfsprozess-Endungen weg und schlägt das übergeordnete Programm nach — Chromes Ton kommt aus einem Helfer, angezeigt gehört Chrome. Symbole gehen denselben Weg, sonst steht neben dem Namen ein Platzhalter. ProcessVisibility zeigt, was klingt, kürzlich klang oder geregelt wird. Die Nachlaufzeit von zwei Minuten ist der eigentliche Punkt: ohne sie verschwindet Spotify beim Pausieren aus der Liste, und man kann die Lautstärke genau dann nicht mehr einstellen, wenn man es will. Geregelte Programme bleiben immer — wer etwas leiser gedreht hat, muss es auch zurückdrehen können. Die Prozentzahl wandert vom Namen zum Regler. Oben war sie genau die Breite, die den Namen abgeschnitten hat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,9 @@ public final class AudioMixer {
|
||||
private var refreshTimer: Timer?
|
||||
private var watchdogTimer: Timer?
|
||||
private let defaults: UserDefaults
|
||||
/// Wann ein Prozess zuletzt Ton ausgegeben hat. Ohne dieses Gedächtnis
|
||||
/// verschwindet ein pausiertes Programm sofort aus der Liste.
|
||||
private var lastHeard: [String: Date] = [:]
|
||||
|
||||
private enum Keys {
|
||||
static let volumes = "onyx.audio.volumes"
|
||||
@@ -81,7 +84,20 @@ public final class AudioMixer {
|
||||
// MARK: - Programme
|
||||
|
||||
private func refresh() {
|
||||
processes = Self.currentProcesses()
|
||||
let all = Self.currentProcesses()
|
||||
let now = Date()
|
||||
for process in all where process.isPlaying { lastHeard[process.bundleID] = now }
|
||||
|
||||
// Core Audio führt Dutzende Systemdienste, die theoretisch Ton ausgeben
|
||||
// könnten. In einem Mischpult sind sie nur Rauschen — gezeigt wird, was
|
||||
// klingt, kürzlich klang oder geregelt wird.
|
||||
processes = all.filter { process in
|
||||
ProcessVisibility.shouldShow(
|
||||
isPlaying: process.isPlaying,
|
||||
secondsSinceHeard: lastHeard[process.bundleID]
|
||||
.map { now.timeIntervalSince($0) },
|
||||
isManaged: managed.contains(process.bundleID))
|
||||
}
|
||||
syncTaps()
|
||||
}
|
||||
|
||||
@@ -194,6 +210,7 @@ public final class AudioMixer {
|
||||
|
||||
// MARK: - Core Audio
|
||||
|
||||
@MainActor
|
||||
static func currentProcesses() -> [AudioProcess] {
|
||||
var address = AudioObjectPropertyAddress(
|
||||
mSelector: kAudioHardwarePropertyProcessObjectList,
|
||||
@@ -225,7 +242,7 @@ public final class AudioMixer {
|
||||
guard !bundleID.hasPrefix("com.apple.audio"),
|
||||
!bundleID.hasPrefix("com.apple.core") else { return nil }
|
||||
|
||||
let name = NSRunningApplication(processIdentifier: pid)?.localizedName ?? bundleID
|
||||
let name = ProcessNaming.displayName(bundleID: bundleID, pid: pid)
|
||||
let playing = (value(object, kAudioProcessPropertyIsRunningOutput, UInt32(0)) ?? 0) != 0
|
||||
return AudioProcess(objectID: object, pid: pid, bundleID: bundleID,
|
||||
name: name, isPlaying: playing)
|
||||
|
||||
Reference in New Issue
Block a user