Adds ability to stop sounds in SoundSelectionView

main
Laurent 3 years ago
parent f521f35cba
commit 528c3d0af9
  1. 4
      LeCountdown.xcodeproj/project.pbxproj
  2. 19
      LeCountdown/Conductor.swift
  3. 51
      LeCountdown/Sound/SoundPlayer.swift
  4. 28
      LeCountdown/Utils/AppError.swift
  5. 46
      LeCountdown/Views/Reusable/SoundSelectionView.swift

@ -37,6 +37,7 @@
C415D3F729C378D10037B215 /* QP01 0037 Tropical forest morning.wav in Resources */ = {isa = PBXBuildFile; fileRef = C415D3F629C378D10037B215 /* QP01 0037 Tropical forest morning.wav */; };
C415D3FB29C37A460037B215 /* QP01 0096 Wetland lake early morning.wav in Resources */ = {isa = PBXBuildFile; fileRef = C415D3FA29C37A460037B215 /* QP01 0096 Wetland lake early morning.wav */; };
C415D3FD29C37AA40037B215 /* QP01 0118 Riparian Zone thrush.wav in Resources */ = {isa = PBXBuildFile; fileRef = C415D3FC29C37AA40037B215 /* QP01 0118 Riparian Zone thrush.wav */; };
C4286E962A14EC4E0070D075 /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4286E952A14EC4E0070D075 /* AppError.swift */; };
C42E96FB29E59E72005B1B8C /* BackgroundBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E96FA29E59E72005B1B8C /* BackgroundBlurView.swift */; };
C42E96FD29E5B06D005B1B8C /* ActivityCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42E96FC29E5B06D005B1B8C /* ActivityCalendarView.swift */; };
C42E96FE29E5B5CD005B1B8C /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B6429A3C37D00CB4FBA /* Filter.swift */; };
@ -370,6 +371,7 @@
C415D3FA29C37A460037B215 /* QP01 0096 Wetland lake early morning.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = "QP01 0096 Wetland lake early morning.wav"; sourceTree = "<group>"; };
C415D3FC29C37AA40037B215 /* QP01 0118 Riparian Zone thrush.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = "QP01 0118 Riparian Zone thrush.wav"; sourceTree = "<group>"; };
C418A14F298428CB00C22230 /* LeCountdown.0.1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.1.xcdatamodel; sourceTree = "<group>"; };
C4286E952A14EC4E0070D075 /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; };
C42E96FA29E59E72005B1B8C /* BackgroundBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundBlurView.swift; sourceTree = "<group>"; };
C42E96FC29E5B06D005B1B8C /* ActivityCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityCalendarView.swift; sourceTree = "<group>"; };
C42E970129E6B32B005B1B8C /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = "<group>"; };
@ -759,6 +761,7 @@
C4060DF4297AE9A7003FAB80 /* TimeInterval+Extensions.swift */,
C473C33B29ACEC4F0056B38A /* Tip.swift */,
C4BA2B7829A65C1400CB4FBA /* UIDevice+Extensions.swift */,
C4286E952A14EC4E0070D075 /* AppError.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -1235,6 +1238,7 @@
C4F8B1BF298ACA0B005C86A5 /* StopwatchDialView.swift in Sources */,
C438C807298195E600BF3EF9 /* Model+Extensions.swift in Sources */,
C4BA2B6829A3C4AC00CB4FBA /* Context+Calculations.swift in Sources */,
C4286E962A14EC4E0070D075 /* AppError.swift in Sources */,
C4E5D67A29B8C5A1008E7465 /* VolumeView.swift in Sources */,
C4BA2B04299A42EF00CB4FBA /* NewDataView.swift in Sources */,
C4BA2B4C299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift in Sources */,

@ -26,7 +26,7 @@ class Conductor: ObservableObject {
static let maestro: Conductor = Conductor()
@Published var soundPlayer: SoundPlayer? = nil
@ObservedObject var soundPlayer: SoundPlayer = SoundPlayer()
fileprivate var _delayedSoundPlayers: [TimerID : DelaySoundPlayer] = [:]
@ -335,16 +335,8 @@ class Conductor: ObservableObject {
}
fileprivate func _playSound(_ filename: String, duration: TimeInterval? = nil) {
self.soundPlayer?.stop()
do {
let soundFile = try SoundFile(fullName: filename)
let soundPlayer = SoundPlayer()
self.soundPlayer = soundPlayer
if let duration {
try soundPlayer.play(soundFile: soundFile, for: duration)
} else {
try soundPlayer.playSound(soundFile: soundFile)
}
try self.soundPlayer.playOrPauseSound(filename, duration: duration)
} catch {
Logger.error(error)
// TODO: manage error
@ -371,9 +363,12 @@ class Conductor: ObservableObject {
}
}
// func isSoundPlaying(_ sound: Sound) -> Bool {
// return self.soundPlayer.isSoundPlaying(sound)
// }
func stopMainPlayersIfPossible() {
self.soundPlayer?.stop()
self.soundPlayer = nil
self.soundPlayer.stop()
}
// MARK: - Intent

@ -34,12 +34,49 @@ enum SoundPlayerError : Error {
case playReturnedFalse
}
@objc class SoundPlayer: NSObject, AVAudioPlayerDelegate {
@objc class SoundPlayer: NSObject, AVAudioPlayerDelegate, ObservableObject {
fileprivate var _player: AVAudioPlayer?
fileprivate var _timer: Timer? = nil
@Published var currentFileName: String? = nil
func playSound(_ sound: Sound) throws {
try self._playSound(sound)
}
func playSound(_ sound: Sound, duration: TimeInterval) throws {
try self._playSound(sound, duration: duration)
}
fileprivate func _playSound(_ sound: Sound, duration: TimeInterval? = nil) throws {
try self.playOrPauseSound(sound.fileName, duration: duration)
}
func playOrPauseSound(_ file: String, duration: TimeInterval? = nil) throws {
if file == self.currentFileName {
if self._player?.isPlaying ?? false {
self._player?.stop()
self.currentFileName = nil
} else {
self._player?.play()
self.currentFileName = file
}
return
}
self.currentFileName = file
self._player?.stop()
let soundFile = try SoundFile(fullName: file)
if let duration {
try self.play(soundFile: soundFile, for: duration)
} else {
try self.playSound(soundFile: soundFile)
}
}
func play(soundFile: SoundFile, for duration: TimeInterval) throws {
try self.playSound(soundFile: soundFile)
self._timer = Timer(timeInterval: duration, repeats: false, block: { _ in
@ -51,10 +88,6 @@ enum SoundPlayerError : Error {
guard let url = soundFile.url else {
throw SoundPlayerError.missingResourceError(file: soundFile)
}
// let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
// try audioSession.setCategory(.playback)
// try audioSession.setActive(true)
let player = try AVAudioPlayer(contentsOf: url)
player.prepareToPlay()
@ -63,19 +96,23 @@ enum SoundPlayerError : Error {
self._player = player
// Logger.log("Plays \(url) on player: \(String(describing: self._player))")
// Logger.log("SoundPlayer > .deviceCurrentTime = \(player.deviceCurrentTime)")
player.play()
}
func stop() {
self._player?.stop()
self.currentFileName = nil
}
// func isSoundPlaying(_ sound: Sound) -> Bool {
// return sound.fileName == self.currentFileName && (self._player?.isPlaying ?? false)
// }
// MARK: - Delegate
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
self.currentFileName = nil
Conductor.maestro.deactivateAudioSessionIfPossible()
self.stop()
}

@ -0,0 +1,28 @@
//
// MyError.swift
// LeCountdown
//
// Created by Laurent Morvillier on 17/05/2023.
//
import Foundation
enum AppError: LocalizedError {
case defaultError(error: Error)
var errorDescription: String? {
switch self {
case .defaultError(let error):
return error.localizedDescription
}
}
var errorMessage: String? {
switch self {
case .defaultError(let error):
return error.localizedDescription
}
}
}

@ -28,18 +28,28 @@ struct PlaylistsView: View {
struct PlaylistSectionView: View {
@StateObject var model: SoundModel
@ObservedObject fileprivate var _player = SoundPlayer()
var playlist: Playlist
@State private var error: AppError?
@State private var isShowingError: Bool = false
var body: some View {
Section {
let sounds = SoundCatalog.main.sounds(for: self.playlist)
ForEach(sounds) { sound in
let isPlaying = sound.fileName == self._player.currentFileName
HStack {
HStack {
Image(systemName: "play.circle")
let image = isPlaying ? "stop.circle" : "play.circle"
Image(systemName: image)
.foregroundColor(Color.accentColor)
Text(sound.localizedString)
}.onTapGesture {
@ -63,14 +73,36 @@ struct PlaylistSectionView: View {
self.model.selectPlaylist(self.playlist, selected: selected)
}
}
.alert(isPresented: $isShowingError, error: error) { _ in
// buttons
} message: { error in
if let message = error.errorMessage {
Text(message)
}
}
}
// fileprivate func _imageForSound(_ sound: Sound) -> String {
// if Conductor.maestro.isSoundPlaying(sound) {
// return "pause.circle"
// } else {
// return "play.circle"
// }
// }
fileprivate func _playSound(_ sound: Sound) {
if AppGuard.main.isSubscriber {
Conductor.maestro.playSound(sound)
} else {
Conductor.maestro.playSound(sound, duration: 30.0)
do {
if AppGuard.main.isSubscriber {
try self._player.playSound(sound)
// Conductor.maestro.playSound(sound)
} else {
try self._player.playSound(sound, duration: 30.0)
// Conductor.maestro.playSound(sound, duration: 30.0)
}
} catch {
self.error = AppError.defaultError(error: error)
Logger.error(error)
}
}

Loading…
Cancel
Save