From b99fb2a59a97735503617c9764fdacdf2ce0c30d Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 8 Mar 2023 12:25:16 +0100 Subject: [PATCH] Changing notification to player to trigger audio --- LeCountdown.xcodeproj/project.pbxproj | 8 ++++ LeCountdown/Conductor.swift | 22 +++++++++- LeCountdown/CountdownScheduler.swift | 37 +++++++++-------- LeCountdown/Info.plist | 9 ++-- LeCountdown/Model/Model+Extensions.swift | 5 +-- LeCountdown/Sound/DelaySoundPlayer.swift | 52 ++++++++++++++++++++++++ LeCountdown/Views/DialView.swift | 32 ++++++++------- 7 files changed, 123 insertions(+), 42 deletions(-) create mode 100644 LeCountdown/Sound/DelaySoundPlayer.swift diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index 1b3ccbb..5b2f1ef 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -160,6 +160,9 @@ C4E5D66729B73AED008E7465 /* TimerIdentifierAppEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66529B73AED008E7465 /* TimerIdentifierAppEntity.swift */; }; C4E5D66A29B73FC6008E7465 /* TimerShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66929B73FC6008E7465 /* TimerShortcuts.swift */; }; C4E5D66D29B753D7008E7465 /* AppShortcuts.strings in Resources */ = {isa = PBXBuildFile; fileRef = C4E5D66F29B753D7008E7465 /* AppShortcuts.strings */; }; + C4E5D67429B88734008E7465 /* DelaySoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D67329B88734008E7465 /* DelaySoundPlayer.swift */; }; + C4E5D67729B88BB5008E7465 /* DelaySoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D67329B88734008E7465 /* DelaySoundPlayer.swift */; }; + C4E5D67829B88BB5008E7465 /* DelaySoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D67329B88734008E7465 /* DelaySoundPlayer.swift */; }; C4F8B1532987FE6F005C86A5 /* LaunchWidgetLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7D72981216200BF3EF9 /* LaunchWidgetLiveActivity.swift */; }; C4F8B15729891271005C86A5 /* Conductor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B15629891271005C86A5 /* Conductor.swift */; }; C4F8B15929891528005C86A5 /* forest_stream.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C4F8B15829891528005C86A5 /* forest_stream.mp3 */; }; @@ -371,6 +374,7 @@ C4E5D66929B73FC6008E7465 /* TimerShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerShortcuts.swift; sourceTree = ""; }; C4E5D66E29B753D7008E7465 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/AppShortcuts.strings; sourceTree = ""; }; C4E5D67029B753DC008E7465 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppShortcuts.strings; sourceTree = ""; }; + C4E5D67329B88734008E7465 /* DelaySoundPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelaySoundPlayer.swift; sourceTree = ""; }; C4F8B15629891271005C86A5 /* Conductor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conductor.swift; sourceTree = ""; }; C4F8B15829891528005C86A5 /* forest_stream.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = forest_stream.mp3; sourceTree = ""; }; C4F8B15E298961A7005C86A5 /* ReorderableForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReorderableForEach.swift; sourceTree = ""; }; @@ -626,6 +630,7 @@ children = ( C445FA912987CC8A0054D761 /* Sound.swift */, C445FA8E2987B83B0054D761 /* SoundPlayer.swift */, + C4E5D67329B88734008E7465 /* DelaySoundPlayer.swift */, ); path = Sound; sourceTree = ""; @@ -1002,6 +1007,7 @@ C4BA2B6829A3C4AC00CB4FBA /* Context+Calculations.swift in Sources */, C4BA2B04299A42EF00CB4FBA /* NewDataView.swift in Sources */, C4BA2B4C299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift in Sources */, + C4E5D67429B88734008E7465 /* DelaySoundPlayer.swift in Sources */, C438C7FF2981300500BF3EF9 /* IntentDataProvider.swift in Sources */, C4E5D66629B73AED008E7465 /* StartTimerIntent.swift in Sources */, C4BA2AD62993F62700CB4FBA /* SoundSelectionView.swift in Sources */, @@ -1107,6 +1113,7 @@ C4BA2B4A299FCE0C00CB4FBA /* IntervalGroup+CoreDataProperties.swift in Sources */, C4BA2B4D299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift in Sources */, C4F8B194298AC288005C86A5 /* Record+CoreDataProperties.swift in Sources */, + C4E5D67729B88BB5008E7465 /* DelaySoundPlayer.swift in Sources */, C438C8152982BD9000BF3EF9 /* IntentDataProvider.swift in Sources */, C473C31929A926F50056B38A /* LaunchWidget.intentdefinition in Sources */, C4F8B15B29892D40005C86A5 /* SoundPlayer.swift in Sources */, @@ -1175,6 +1182,7 @@ C473C2F429A8DAE70056B38A /* Model+Extensions.swift in Sources */, C445FA86298448720054D761 /* CoolPic.swift in Sources */, C4BA2B4E299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift in Sources */, + C4E5D67829B88BB5008E7465 /* DelaySoundPlayer.swift in Sources */, C4BA2B23299BE82E00CB4FBA /* AbstractSoundTimer+CoreDataProperties.swift in Sources */, C473C2F529A8DAF30056B38A /* PropertyWrappers.swift in Sources */, C473C2F029A8CFFC0056B38A /* TimerRouter.swift in Sources */, diff --git a/LeCountdown/Conductor.swift b/LeCountdown/Conductor.swift index 7dbe5a7..0c76a8b 100644 --- a/LeCountdown/Conductor.swift +++ b/LeCountdown/Conductor.swift @@ -21,6 +21,7 @@ class Conductor: ObservableObject { static let maestro: Conductor = Conductor() @Published var soundPlayer: SoundPlayer? = nil + @Published var delaySoundPlayer: DelaySoundPlayer? = nil @UserDefault(PreferenceKey.countdowns.rawValue, defaultValue: [:]) static var savedCountdowns: [String : DateInterval] @UserDefault(PreferenceKey.stopwatches.rawValue, defaultValue: [:]) static var savedStopwatches: [String : Date] @@ -148,6 +149,25 @@ class Conductor: ObservableObject { } } + func prepareAlarm(countdown: Countdown, handler: @escaping (Result) -> Void) { + + DispatchQueue.main.async { + + do { + let date = Date(timeIntervalSinceNow: countdown.duration) + let soundFile = try SoundFile(fullName: countdown.soundName) + self.delaySoundPlayer = DelaySoundPlayer(soundFile: soundFile) + try self.delaySoundPlayer?.start(in: countdown.duration, + repeatCount: Int(countdown.repeatCount)) + + self.startCountdown(date, countdown: countdown) + handler(.success(date)) + } catch { + handler(.failure(error)) + } + } + } + // MARK: - Stopwatch func startStopwatch(_ stopwatch: Stopwatch) { @@ -199,7 +219,7 @@ class Conductor: ObservableObject { let timer = context.object(stringId: timerId) switch timer { case let cd as Countdown: - coolSound = cd.coolSound + coolSound = cd.someSound case let sw as Stopwatch: coolSound = sw.coolSound default: diff --git a/LeCountdown/CountdownScheduler.swift b/LeCountdown/CountdownScheduler.swift index f4e58d5..21ecf90 100644 --- a/LeCountdown/CountdownScheduler.swift +++ b/LeCountdown/CountdownScheduler.swift @@ -16,6 +16,7 @@ class CountdownScheduler { func scheduleIfPossible(countdown: Countdown, handler: @escaping (Result) -> Void) { self.cancelCurrentNotifications(countdownId: countdown.stringId) + Conductor.maestro.prepareAlarm(countdown: countdown, handler: handler) self._scheduleCountdownNotification(countdown: countdown, handler: handler) } @@ -36,20 +37,20 @@ class CountdownScheduler { content.body = body let sound = countdown.soundName + self._createNotification(countdown: countdown, content: content, handler: handler) print("Selected sound = \(sound)") - content.sound = UNNotificationSound.criticalSoundNamed(UNNotificationSoundName(rawValue: sound), withAudioVolume: 1.0) - content.interruptionLevel = .critical - content.relevanceScore = 1.0 +// content.sound = UNNotificationSound.criticalSoundNamed(UNNotificationSoundName(rawValue: sound), withAudioVolume: 1.0) +// content.interruptionLevel = .critical +// content.relevanceScore = 1.0 - let notificationCount = 1 + countdown.repeatCount +// let notificationCount = 1 + countdown.repeatCount -// self._createNotification(countdown: countdown, content: content, handler: handler) - for i in 0.. + BGTaskSchedulerPermittedIdentifiers + + com.staxriver.lecountdown.refresh + INAlternativeAppNames @@ -15,10 +19,6 @@ Gogo - BGTaskSchedulerPermittedIdentifiers - - com.staxriver.lecountdown.refresh - NSUserActivityTypes LaunchTimerIntent @@ -35,7 +35,6 @@ UIBackgroundModes audio - fetch diff --git a/LeCountdown/Model/Model+Extensions.swift b/LeCountdown/Model/Model+Extensions.swift index 017c097..25177de 100644 --- a/LeCountdown/Model/Model+Extensions.swift +++ b/LeCountdown/Model/Model+Extensions.swift @@ -9,7 +9,6 @@ import Foundation import SwiftUI import CoreData - extension AbstractSoundTimer { var sounds: Set { @@ -23,7 +22,7 @@ extension AbstractSoundTimer { self.soundList = sounds.stringRepresentation } - var coolSound: Sound { + var someSound: Sound { var sounds = self.sounds // remove last played sound if the playlist has at least 3 sounds @@ -42,7 +41,7 @@ extension AbstractSoundTimer { } var soundName: String { - return self.coolSound.soundName + return self.someSound.soundName } } diff --git a/LeCountdown/Sound/DelaySoundPlayer.swift b/LeCountdown/Sound/DelaySoundPlayer.swift new file mode 100644 index 0000000..e5e6264 --- /dev/null +++ b/LeCountdown/Sound/DelaySoundPlayer.swift @@ -0,0 +1,52 @@ +// +// BackgroundSoundPlayer.swift +// LeCountdown +// +// Created by Laurent Morvillier on 08/03/2023. +// + +import Foundation +import AVFoundation + +@objc class DelaySoundPlayer: NSObject, AVAudioPlayerDelegate { + + fileprivate var _player: AVAudioPlayer? + + private var soundFile: SoundFile + + init(soundFile: SoundFile) { + self.soundFile = soundFile + } + + func start(in duration: TimeInterval, repeatCount: Int) throws { + + guard let url = self.soundFile.url else { + throw SoundPlayerError.missingResourceError(file: self.soundFile) + } + + let audioSession: AVAudioSession = AVAudioSession.sharedInstance() + try audioSession.setCategory(.playback) + try audioSession.setActive(true) + + let player = try AVAudioPlayer(contentsOf: url) + _player = player + + _player?.prepareToPlay() + _player?.volume = 1.0 + _player?.delegate = self + player.numberOfLoops = repeatCount + + _player?.play(atTime: player.deviceCurrentTime + duration) + } + + func stop() { + self._player?.stop() + } + + // MARK: - Delegate + + func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { + Logger.log("audioPlayerDidFinishPlaying: successfully = \(flag)") + } + +} diff --git a/LeCountdown/Views/DialView.swift b/LeCountdown/Views/DialView.swift index d27094d..9a3026c 100644 --- a/LeCountdown/Views/DialView.swift +++ b/LeCountdown/Views/DialView.swift @@ -17,7 +17,7 @@ struct DialView: View { @State var timer: AbstractTimer var isEditingBinding: Binding - @State var showSilentModeAlert: Bool = false +// @State var showSilentModeAlert: Bool = false var frameSize: CGFloat @@ -33,11 +33,13 @@ struct DialView: View { switch self.isEditingBinding.wrappedValue { case false: Button { - if Preferences.hideSilentModeAlerts { - self._launchTimer() - } else { - self.showSilentModeAlert = true - } + self._launchTimer() + +// if Preferences.hideSilentModeAlerts { +// self._launchTimer() +// } else { +// self.showSilentModeAlert = true +// } } label: { VStack { Spacer() @@ -79,15 +81,15 @@ struct DialView: View { } .frame(width: frameSize, height: 80.0) .cornerRadius(20.0) - .alert("Make sure your device is not on silent mode", isPresented: $showSilentModeAlert) { - Button("OK", role: .cancel) { - self._launchTimer() - } - Button("Don't show this again", role: .destructive) { - self._launchTimer() - Preferences.hideSilentModeAlerts(true) - } - } +// .alert("Make sure your device is not on silent mode", isPresented: $showSilentModeAlert) { +// Button("OK", role: .cancel) { +// self._launchTimer() +// } +// Button("Don't show this again", role: .destructive) { +// self._launchTimer() +// Preferences.hideSilentModeAlerts(true) +// } +// } } @ViewBuilder