I have developed a WatchOS app that records the voice while user is sleeping. I have noticed that this app is getting closed after several hours. How can I prevent this?
PS. The app can be running on the user's iPhone at the same time as user Apple watc, in case app running on iPhone could somehow prevent this.
Full code for audio recorder part:
import SwiftUI
import AVFoundation
import WatchKit
import CryptoKit
class AudioRecorder: NSObject, ObservableObject, AVAudioRecorderDelegate {
@Published var isRecording = false
@Published var recordings: [Recording] = []
private var audioRecorder: AVAudioRecorder?
private var currentRecordingURL: URL?
private var encryptionKey : SymmetricKey = SymmetricKey(size: .bits256)
private var audioBuffer: Data = Data()
private let bufferSizeThreshold = 4096
private var recordingTimer: Timer?
private let recordingChunkDuration: TimeInterval = 10
private let pauseDuration: TimeInterval = 5
private var audioSessionInitialized = false // Flag to track session setup
override init(){
super.init()
recordings = loadRecordings()
encryptionKey = getEncryptionKey()
}
func startRecording() {
setupAudioSession() // Ensure session is set up before recording
let fileManager = FileManager.default
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd_HHmmss"
let recordingName = dateFormatter.string(from: Date()) + ".m4a"
currentRecordingURL = documentsDirectory.appendingPathComponent(recordingName)
let settings: [String: Any] = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 22050.0,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.min.rawValue
]
do {
audioRecorder = try AVAudioRecorder(url: currentRecordingURL!, settings: settings)
audioRecorder?.delegate = self
// Remove intermittent recording for now to simplify debugging
audioRecorder?.record() // Record continuously instead of in chunks
isRecording = true
print("Started recording to:", currentRecordingURL!) // Debug print
} catch {
print("Error starting recording:", error)
isRecording = false
}
}
private func setupAudioSession() {
do {
let audioSession = AVAudioSession.sharedInstance()
// Specify .record for recording-only
//
// [.mixWithOthers, .defaultToSpeaker] .defaultToSpeaker is unavailablke in WatchOS:
try audioSession.setCategory(.playAndRecord, mode: .default, options: [.mixWithOthers]) //
try audioSession.setActive(true)
print("Audio session activated.")
} catch {
print("Error setting up audio session:", error)
}
}
func stopRecording() {
audioRecorder?.stop()
isRecording = false
currentRecordingURL = nil
audioBuffer = Data()
}
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
guard flag else { return }
let tempURL = recorder.url
let encryptionEnabled = UserDefaults.standard.bool(forKey: "encryptionEnabled")
do {
if encryptionEnabled {
let data = try Data(contentsOf: tempURL)
let encryptedData = try ChaChaPoly.seal(data, using: encryptionKey)bined
let encryptedFileName = "encrypted_\(tempURL.lastPathComponent)"
let encryptedURL = tempURL.deletingLastPathComponent().appendingPathComponent(encryptedFileName)
try encryptedData.write(to: encryptedURL)
DispatchQueue.main.async {
self.recordings.insert(Recording(url: encryptedURL, createdAt: Date()), at: 0)
}
try FileManager.default.removeItem(at: tempURL)
} else {
DispatchQueue.main.async {
self.recordings.insert(Recording(url: tempURL, createdAt: Date()), at: 0)
}
}
} catch {
print("Failed to process recording:", error)
}
}
func getDecryptedData(from url: URL) throws -> Data? {
let fileData = try Data(contentsOf: url)
if url.lastPathComponent.hasPrefix("encrypted_") {
let sealedBox = try ChaChaPoly.SealedBox(combined: fileData)
let decryptedData = try ChaChaPoly.open(sealedBox, using: encryptionKey)
return decryptedData
} else {
return fileData
}
}
func loadRecordings() -> [Recording] {
let fileManager = FileManager.default
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
do {
let files = try fileManager.contentsOfDirectory(at: documentsDirectory, includingPropertiesForKeys: nil)
let recordingFiles = files.filter { $0.pathExtension == "m4a" }
return recordingFiles.map { fileURL in
let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path)
let createdAt = attributes?[.creationDate] as? Date ?? Date()
return Recording(url: fileURL, createdAt: createdAt)
}.sorted(by: { $0.createdAt > $1.createdAt })
} catch {
print("Error loading recordings: \(error)")
return []
}
}
func getEncryptionKey() -> SymmetricKey {
let keychainKey = "ziziapps.guardianangel.encryptionKey"
let keychainHelper = KeychainHelper()
if let keyData = keychainHelper.read(key: keychainKey) {
return SymmetricKey(data: keyData)
} else {
// Generate a new key if one doesn't exist, then store it
let newKey = SymmetricKey(size: .bits256)
let keyData = newKey.withUnsafeBytes { Data($0) }
keychainHelper.save(keyData, key: keychainKey)
return newKey
}
}
}