mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Update Feedback for Volume Change
This commit is contained in:
parent
ab6877e59c
commit
a3a80ca3d4
5 changed files with 76 additions and 0 deletions
|
|
@ -3382,6 +3382,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Audio feedback" : {
|
||||||
|
"comment" : "A section header that lists audio feedback settings.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Authorize" : {
|
"Authorize" : {
|
||||||
"comment" : "A button that authorizes an app extension.",
|
"comment" : "A button that authorizes an app extension.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
|
@ -15194,6 +15198,10 @@
|
||||||
},
|
},
|
||||||
"Pinned Items" : {
|
"Pinned Items" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Plays the supplied feedback clip whenever you press the hardware volume keys." : {
|
||||||
|
"comment" : "A help text describing the audio feedback feature.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Please configure your API key for the selected AI provider in model settings." : {
|
"Please configure your API key for the selected AI provider in model settings." : {
|
||||||
|
|
||||||
|
|
@ -16032,6 +16040,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Requires Accessibility permission so Dynamic Island can intercept the hardware volume keys." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Reset" : {
|
"Reset" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
|
||||||
BIN
DynamicIsland/audio-feedback.m4a
Normal file
BIN
DynamicIsland/audio-feedback.m4a
Normal file
Binary file not shown.
|
|
@ -1859,6 +1859,18 @@ struct HUD: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
Defaults.Toggle("Play feedback when volume is changed", key: .playVolumeChangeFeedback)
|
||||||
|
.settingsHighlight(id: highlightID("Play feedback when volume is changed"))
|
||||||
|
.help("Plays the supplied feedback clip whenever you press the hardware volume keys.")
|
||||||
|
} header: {
|
||||||
|
Text("Audio feedback")
|
||||||
|
} footer: {
|
||||||
|
Text("Requires Accessibility permission so Dynamic Island can intercept the hardware volume keys.")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Defaults.Toggle("Color-coded volume display", key: .useColorCodedVolumeDisplay)
|
Defaults.Toggle("Color-coded volume display", key: .useColorCodedVolumeDisplay)
|
||||||
.disabled(colorCodingDisabled)
|
.disabled(colorCodingDisabled)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import Foundation
|
||||||
import CoreGraphics
|
import CoreGraphics
|
||||||
import Defaults
|
import Defaults
|
||||||
import AppKit
|
import AppKit
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
final class SystemChangesObserver: MediaKeyInterceptorDelegate {
|
final class SystemChangesObserver: MediaKeyInterceptorDelegate {
|
||||||
private weak var coordinator: DynamicIslandViewCoordinator?
|
private weak var coordinator: DynamicIslandViewCoordinator?
|
||||||
|
|
@ -9,6 +10,7 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
|
||||||
private let brightnessController = SystemBrightnessController.shared
|
private let brightnessController = SystemBrightnessController.shared
|
||||||
private let keyboardBacklightController = SystemKeyboardBacklightController.shared
|
private let keyboardBacklightController = SystemKeyboardBacklightController.shared
|
||||||
private let mediaKeyInterceptor = MediaKeyInterceptor.shared
|
private let mediaKeyInterceptor = MediaKeyInterceptor.shared
|
||||||
|
private let volumeFeedbackPlayer = VolumeFeedbackPlayer.shared
|
||||||
|
|
||||||
private static let headsetIconSymbols: Set<String> = [
|
private static let headsetIconSymbols: Set<String> = [
|
||||||
"airpods",
|
"airpods",
|
||||||
|
|
@ -140,6 +142,7 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
|
||||||
let baseStep = stepSize(for: step, base: standardVolumeStep)
|
let baseStep = stepSize(for: step, base: standardVolumeStep)
|
||||||
let delta = direction == .up ? baseStep : -baseStep
|
let delta = direction == .up ? baseStep : -baseStep
|
||||||
volumeController.adjust(by: delta)
|
volumeController.adjust(by: delta)
|
||||||
|
volumeFeedbackPlayer.playIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
func mediaKeyInterceptorDidToggleMute(_ interceptor: MediaKeyInterceptor) {
|
func mediaKeyInterceptorDidToggleMute(_ interceptor: MediaKeyInterceptor) {
|
||||||
|
|
@ -325,4 +328,53 @@ private extension SystemChangesObserver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class VolumeFeedbackPlayer {
|
||||||
|
static let shared = VolumeFeedbackPlayer()
|
||||||
|
|
||||||
|
private let queue = DispatchQueue(label: "com.dynamicisland.volume-feedback-player")
|
||||||
|
private var player: AVAudioPlayer?
|
||||||
|
private var lastPlayDate: Date = .distantPast
|
||||||
|
private let minimumInterval: TimeInterval = 0.08
|
||||||
|
private var didLogMissingAsset = false
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
func playIfNeeded() {
|
||||||
|
guard Defaults[.playVolumeChangeFeedback] else { return }
|
||||||
|
queue.async { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
let now = Date()
|
||||||
|
guard now.timeIntervalSince(self.lastPlayDate) >= self.minimumInterval else { return }
|
||||||
|
self.lastPlayDate = now
|
||||||
|
self.preparePlayerIfNeeded()
|
||||||
|
guard let player = self.player else { return }
|
||||||
|
player.currentTime = 0
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func preparePlayerIfNeeded() {
|
||||||
|
if player != nil { return }
|
||||||
|
guard let url = Bundle.main.url(forResource: "audio-feedback", withExtension: "m4a") else {
|
||||||
|
if !didLogMissingAsset {
|
||||||
|
NSLog("⚠️ audio-feedback.m4a is missing from the app bundle; volume feedback disabled.")
|
||||||
|
didLogMissingAsset = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let audioPlayer = try AVAudioPlayer(contentsOf: url)
|
||||||
|
audioPlayer.volume = 1
|
||||||
|
audioPlayer.prepareToPlay()
|
||||||
|
player = audioPlayer
|
||||||
|
} catch {
|
||||||
|
if !didLogMissingAsset {
|
||||||
|
NSLog("⚠️ Failed to initialize volume feedback player: \(error.localizedDescription)")
|
||||||
|
didLogMissingAsset = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -871,6 +871,7 @@ extension Defaults.Keys {
|
||||||
static let enableBrightnessHUD = Key<Bool>("enableBrightnessHUD", default: true)
|
static let enableBrightnessHUD = Key<Bool>("enableBrightnessHUD", default: true)
|
||||||
static let enableKeyboardBacklightHUD = Key<Bool>("enableKeyboardBacklightHUD", default: true)
|
static let enableKeyboardBacklightHUD = Key<Bool>("enableKeyboardBacklightHUD", default: true)
|
||||||
static let systemHUDSensitivity = Key<Int>("systemHUDSensitivity", default: 5)
|
static let systemHUDSensitivity = Key<Int>("systemHUDSensitivity", default: 5)
|
||||||
|
static let playVolumeChangeFeedback = Key<Bool>("playVolumeChangeFeedback", default: false)
|
||||||
|
|
||||||
// MARK: Custom OSD Window Feature
|
// MARK: Custom OSD Window Feature
|
||||||
static let enableCustomOSD = Key<Bool>("enableCustomOSD", default: false)
|
static let enableCustomOSD = Key<Bool>("enableCustomOSD", default: false)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue