mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-28 15:44:32 +00:00
fix: stabilize Bluetooth polling and dedupe lid-cycle lock chimes
BluetoothAudioManager - Replace Timer.scheduledTimer polling with DispatchSourceTimer on a utility queue so main-thread back-pressure no longer stalls the 3s connect-detection cadence. - Distinguish "host controller unavailable" from "powered off" via a new bluetoothIsPoweredOn() helper, so transient IOBluetooth registry blips at launch no longer clear connectedDevices. - Re-read device metadata (name, type, battery) at +500ms and +2s after IOBluetoothDeviceConnectedNotification to catch IORegistry's late-population window; the existing HUD wait-task then updates the shown battery in place. - Declare BluetoothAudioDeviceType: Equatable to support the change-detection diff in the re-read pass. LockSoundPlayer - Add a 1.2s cross-type cooldown so a rapid lid close->open no longer plays lock-then-unlock chimes as a doubled sound (the per-type throttle never covered this case). - Bump per-type throttle 0.25s -> 0.6s. - Reset throttle state when enableLockSounds is toggled back on, so the first chime after re-enable is never suppressed by a stale timestamp. StatsManager - Shorten notchCloseStopDelay 3.0s -> 1.0s so the monitoring timer is invalidated sooner after the user closes the notch, freeing the main-thread tick budget for animation work. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
6acb98aadb
commit
d139742660
3 changed files with 141 additions and 27 deletions
|
|
@ -312,11 +312,31 @@ extension LockScreenManager {
|
|||
@MainActor
|
||||
final class LockSoundPlayer {
|
||||
static let shared = LockSoundPlayer()
|
||||
private let throttleInterval: TimeInterval = 0.25
|
||||
// Same-type throttle defends against repeat same-state notifications
|
||||
// (e.g. distributed + workspace observers both firing screenLocked).
|
||||
private let throttleInterval: TimeInterval = 0.6
|
||||
// Cross-type cooldown defends against the lid close→open scenario where
|
||||
// macOS fires screenIsLocked and screenIsUnlocked within a few hundred ms
|
||||
// of each other — without this, the user hears lock-chime-then-unlock-chime
|
||||
// as a single doubled sound.
|
||||
private let crossTypeCooldown: TimeInterval = 1.2
|
||||
private var players: [SoundType: AVAudioPlayer] = [:]
|
||||
private var lastPlaybackDates: [SoundType: Date] = [:]
|
||||
private var lastPlayedType: SoundType?
|
||||
private var lastAnyPlaybackDate: Date?
|
||||
private var lockSoundsEnabledCancellable: AnyCancellable?
|
||||
|
||||
private init() {}
|
||||
private init() {
|
||||
// Reset throttle state when the user toggles enableLockSounds off then on,
|
||||
// so the first chime after re-enable isn't suppressed by a stale timestamp.
|
||||
lockSoundsEnabledCancellable = Defaults.publisher(.enableLockSounds)
|
||||
.sink { [weak self] change in
|
||||
guard change.newValue else { return }
|
||||
Task { @MainActor in
|
||||
self?.resetThrottleState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func playLockChime() {
|
||||
play(.lock)
|
||||
|
|
@ -333,12 +353,31 @@ final class LockSoundPlayer {
|
|||
|
||||
player.currentTime = 0
|
||||
player.play()
|
||||
lastPlaybackDates[type] = Date()
|
||||
let now = Date()
|
||||
lastPlaybackDates[type] = now
|
||||
lastAnyPlaybackDate = now
|
||||
lastPlayedType = type
|
||||
}
|
||||
|
||||
private func shouldPlay(_ type: SoundType) -> Bool {
|
||||
guard let last = lastPlaybackDates[type] else { return true }
|
||||
return Date().timeIntervalSince(last) >= throttleInterval
|
||||
let now = Date()
|
||||
if let last = lastPlaybackDates[type],
|
||||
now.timeIntervalSince(last) < throttleInterval {
|
||||
return false
|
||||
}
|
||||
if let lastAny = lastAnyPlaybackDate,
|
||||
let lastType = lastPlayedType,
|
||||
lastType != type,
|
||||
now.timeIntervalSince(lastAny) < crossTypeCooldown {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func resetThrottleState() {
|
||||
lastPlaybackDates.removeAll()
|
||||
lastAnyPlaybackDate = nil
|
||||
lastPlayedType = nil
|
||||
}
|
||||
|
||||
private func resolvePlayer(for type: SoundType) -> AVAudioPlayer? {
|
||||
|
|
@ -362,7 +401,7 @@ final class LockSoundPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
private enum SoundType: String {
|
||||
private enum SoundType: String, Equatable {
|
||||
case lock
|
||||
case unlock
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue