Explorar el Código

Merge pull request #488 from loopandlearn/bluetooth-button-snooze

Add snooze with bluetooth play-pause control
Marion Barker hace 6 meses
padre
commit
b76fa6a681

+ 5 - 4
LoopFollow/Controllers/AlarmSound.swift

@@ -88,7 +88,7 @@ class AlarmSound {
             audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
             audioPlayer!.delegate = audioPlayerDelegate
 
-            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category(rawValue: convertFromAVAudioSessionCategory(AVAudioSession.Category.playback)))
+            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
             try AVAudioSession.sharedInstance().setActive(true)
 
             audioPlayer?.numberOfLoops = 0
@@ -126,7 +126,7 @@ class AlarmSound {
             audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
             audioPlayer!.delegate = audioPlayerDelegate
 
-            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category(rawValue: convertFromAVAudioSessionCategory(AVAudioSession.Category.playback)))
+            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
             try AVAudioSession.sharedInstance().setActive(true)
 
             // Only use numberOfLoops if we're not using delay-based repeating
@@ -213,7 +213,7 @@ class AlarmSound {
             audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
             audioPlayer!.delegate = audioPlayerDelegate
 
-            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category(rawValue: convertFromAVAudioSessionCategory(AVAudioSession.Category.playback)))
+            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
             try AVAudioSession.sharedInstance().setActive(true)
 
             // Play endless loops
@@ -262,8 +262,9 @@ class AlarmSound {
 
     fileprivate static func enableAudio() {
         do {
-            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: .mixWithOthers)
+            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
             try AVAudioSession.sharedInstance().setActive(true)
+            LogManager.shared.log(category: .alarm, message: "Audio session configured for alarm playback")
         } catch {
             LogManager.shared.log(category: .alarm, message: "Enable audio error: \(error)")
         }

+ 123 - 0
LoopFollow/Controllers/VolumeButtonHandler.swift

@@ -4,6 +4,7 @@
 import AVFoundation
 import Combine
 import Foundation
+import MediaPlayer
 import UIKit
 
 class VolumeButtonHandler: NSObject {
@@ -30,6 +31,9 @@ class VolumeButtonHandler: NSObject {
     private var lastSignificantVolumeChange: Date?
     private var volumeChangePattern: [TimeInterval] = []
 
+    // Remote command center for handling bluetooth/CarPlay buttons
+    private var remoteCommandsEnabled = false
+
     private var cancellables = Set<AnyCancellable>()
 
     override private init() {
@@ -112,11 +116,127 @@ class VolumeButtonHandler: NSObject {
         volumeChangePattern.removeAll()
     }
 
+    private func setupRemoteCommandCenter() {
+        guard !remoteCommandsEnabled else { return }
+
+        let commandCenter = MPRemoteCommandCenter.shared()
+
+        // Log current audio route to help with debugging
+        let currentRoute = AVAudioSession.sharedInstance().currentRoute
+        LogManager.shared.log(category: .volumeButtonSnooze, message: "Audio route: \(currentRoute.outputs.map { $0.portName }.joined(separator: ", "))")
+
+        // Enable pause command - handles play/pause button on bluetooth devices and CarPlay
+        commandCenter.pauseCommand.isEnabled = true
+        commandCenter.pauseCommand.addTarget { [weak self] _ in
+            guard let self = self else { return .commandFailed }
+
+            LogManager.shared.log(category: .volumeButtonSnooze, message: "Pause command received from remote")
+
+            // Check if alarm is currently active and activation delay has passed
+            if let alarmStartTime = self.alarmStartTime {
+                let timeSinceAlarmStart = Date().timeIntervalSince(alarmStartTime)
+
+                if timeSinceAlarmStart > self.volumeButtonActivationDelay {
+                    // Check cooldown
+                    if let lastPress = self.lastVolumeButtonPressTime {
+                        let timeSinceLastPress = Date().timeIntervalSince(lastPress)
+                        if timeSinceLastPress < self.volumeButtonCooldown {
+                            return .success
+                        }
+                    }
+
+                    LogManager.shared.log(category: .volumeButtonSnooze, message: "Remote command pause received - snoozing alarm")
+                    self.snoozeActiveAlarm()
+                    return .success
+                }
+            }
+
+            return .commandFailed
+        }
+
+        // Enable play command as well for symmetry
+        commandCenter.playCommand.isEnabled = true
+        commandCenter.playCommand.addTarget { [weak self] _ in
+            guard let self = self else { return .commandFailed }
+
+            LogManager.shared.log(category: .volumeButtonSnooze, message: "Play command received from remote")
+
+            if let alarmStartTime = self.alarmStartTime {
+                let timeSinceAlarmStart = Date().timeIntervalSince(alarmStartTime)
+
+                if timeSinceAlarmStart > self.volumeButtonActivationDelay {
+                    if let lastPress = self.lastVolumeButtonPressTime {
+                        let timeSinceLastPress = Date().timeIntervalSince(lastPress)
+                        if timeSinceLastPress < self.volumeButtonCooldown {
+                            return .success
+                        }
+                    }
+
+                    LogManager.shared.log(category: .volumeButtonSnooze, message: "Remote command play received - snoozing alarm")
+                    self.snoozeActiveAlarm()
+                    return .success
+                }
+            }
+
+            return .commandFailed
+        }
+
+        // Enable toggle play/pause command - common on many bluetooth devices
+        commandCenter.togglePlayPauseCommand.isEnabled = true
+        commandCenter.togglePlayPauseCommand.addTarget { [weak self] _ in
+            guard let self = self else { return .commandFailed }
+
+            LogManager.shared.log(category: .volumeButtonSnooze, message: "Toggle play/pause command received from remote")
+
+            if let alarmStartTime = self.alarmStartTime {
+                let timeSinceAlarmStart = Date().timeIntervalSince(alarmStartTime)
+
+                if timeSinceAlarmStart > self.volumeButtonActivationDelay {
+                    if let lastPress = self.lastVolumeButtonPressTime {
+                        let timeSinceLastPress = Date().timeIntervalSince(lastPress)
+                        if timeSinceLastPress < self.volumeButtonCooldown {
+                            return .success
+                        }
+                    }
+
+                    LogManager.shared.log(category: .volumeButtonSnooze, message: "Remote command toggle play/pause received - snoozing alarm")
+                    self.snoozeActiveAlarm()
+                    return .success
+                }
+            }
+
+            return .commandFailed
+        }
+
+        remoteCommandsEnabled = true
+        LogManager.shared.log(category: .volumeButtonSnooze, message: "Remote command center configured for bluetooth/CarPlay button handling")
+    }
+
+    private func disableRemoteCommandCenter() {
+        guard remoteCommandsEnabled else { return }
+
+        let commandCenter = MPRemoteCommandCenter.shared()
+        commandCenter.pauseCommand.isEnabled = false
+        commandCenter.playCommand.isEnabled = false
+        commandCenter.togglePlayPauseCommand.isEnabled = false
+
+        // Remove all targets
+        commandCenter.pauseCommand.removeTarget(nil)
+        commandCenter.playCommand.removeTarget(nil)
+        commandCenter.togglePlayPauseCommand.removeTarget(nil)
+
+        remoteCommandsEnabled = false
+        LogManager.shared.log(category: .volumeButtonSnooze, message: "Remote command center disabled")
+    }
+
     func startMonitoring() {
         guard !isMonitoring else { return }
 
         isMonitoring = true
 
+        // Setup remote command center for bluetooth/CarPlay button handling
+        setupRemoteCommandCenter()
+
         volumeObserver = AVAudioSession.sharedInstance().observe(\.outputVolume, options: [.new]) { [weak self] session, _ in
             guard let self = self, let alarmStartTime = self.alarmStartTime else { return }
 
@@ -176,6 +296,9 @@ class VolumeButtonHandler: NSObject {
         volumeObserver?.invalidate()
         volumeObserver = nil
 
+        // Disable remote command center
+        disableRemoteCommandCenter()
+
         isMonitoring = false
         lastVolume = 0.0 // Reset for the next alarm.
     }