Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] #371 전화 수신, 발신 시 소리 재생 오류 해결 #374

Merged
merged 7 commits into from
Jan 29, 2025
4 changes: 4 additions & 0 deletions Macro/Domain/UseCase/Interface/PlaySoundInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
// Created by Yunki on 10/1/24.
//

import Combine

// 소리내는 UseCase용
protocol PlaySoundInterface {
var callInterruptPublisher: AnyPublisher<Bool, Never> { get }
func audioEngineStart()
func beep(_ accent: Accent)
func setSoundType()
}
8 changes: 8 additions & 0 deletions Macro/Domain/UseCase/MetronomeOnOffImplement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@
self.timer?.schedule(deadline: .now() + nextStartTime, repeating: self.interval, leeway: .nanoseconds(1))
}
.store(in: &self.cancelBag)

self.soundManager.callInterruptPublisher.sink {[weak self] callInterrupt in
guard let self else { return }
callInterrupt == true ? self.stop() : nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bool값을 == true 인지 검증하는게 조금 어색해 보입니다
isCalling 같이 자체적으로 Bool값을 암시하는 변수명으로 대체하는게 어떨가요

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

받는 값이 false일때 (전화가 끊겼을때) 아무 일도 하지 않는다고 하면
Bool값 대신 Void 타입으로 이벤트 발생만 전달하는 방안도 있을것 같습니다.
전화가 올때 재생이 멈추고 유저가 전화를 끊고 다시 재생을 눌러야 하는 방식으로 구현될것 같아요

Copy link
Collaborator Author

@l1004ga l1004ga Jan 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

임시변수를 만들어도 해당 코드 내부에서 사용처가 없기 때문에 안만드는 것이 더 적합하다고 생각합니다. 방식은 말씀하신 것처럼 현재 전화 오면 재생이 멈추고 전화를 끊고 유저가 다시 재생을 누르는 식으로 구현되어 있습니다.

처리 방식을 변수로 처리하기보다 callInterrupt ? self.stop : nil 로 수정하는게 더 나을 것 같고,
이벤트 발생 여부만 전달하는 것이 가장 좋을 것 같습니다. 이벤트 여부 전달 방식으로 바꿔도 괜찮을까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MetronomeOnOffUseCase의 tickPublisher가 Void 타입 Publisher로 구현되어 있습니다
참고가 되면 좋겠네여

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@YunKi-H 수정했습니다~ 확인 부탁드리며, 추가적으로 피드백 주실 부분이 있을지 확인 후 머지하겠습니다!

}
.store(in: &self.cancelBag)
}
}

Expand All @@ -84,11 +90,13 @@
}

func play() {
// AudioEngine start()
self.soundManager.audioEngineStart()
// 데이터 갱신
self.currentBeatIndex = 0
UIApplication.shared.isIdleTimerDisabled = true
// Timer 설정
if let timer { self.stop() }

Check notice on line 99 in Macro/Domain/UseCase/MetronomeOnOffImplement.swift

View check run for this annotation

Xcode Cloud / Macro | Dev-PR | Analyze - iOS

Macro/Domain/UseCase/MetronomeOnOffImplement.swift#L99

Value 'timer' was defined but never used; consider replacing with boolean test

Check notice on line 99 in Macro/Domain/UseCase/MetronomeOnOffImplement.swift

View check run for this annotation

Xcode Cloud / Macro | Dev-PR | Build - iOS

Macro/Domain/UseCase/MetronomeOnOffImplement.swift#L99

Value 'timer' was defined but never used; consider replacing with boolean test
self.timer = DispatchSource.makeTimerSource(queue: self.queue)
self.timer?.schedule(deadline: .now(), repeating: self.interval, leeway: .nanoseconds(1))
self.timer?.setEventHandler { [weak self] in
Expand Down
55 changes: 49 additions & 6 deletions Macro/Service/SoundManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import SwiftUI
import Combine
import AVFoundation

class SoundManager {
Expand All @@ -17,6 +18,8 @@ class SoundManager {
private let audioSession = AVAudioSession.sharedInstance()
private var soundType: SoundType

private var publisher: PassthroughSubject<Bool, Never> = .init()

init?(appState: AppState) {
self.appState = appState
self.soundType = .beep
Expand All @@ -40,15 +43,40 @@ class SoundManager {
self.engine.connect(dummyNode, to: self.engine.mainMixerNode, format: nil)

// 엔진 시작
do {
try engine.start()
} catch {
print("SoundManager: 오디오 엔진 시작 중 에러 발생 - \(error)")
return nil
}
self.audioEngineStart()

// 더미 노드 분리
self.engine.detach(dummyNode)

// 전화 송/수신 시 interrupt 여부를 감지를 위한 notificationCenter 생성
self.setupNotifications()
}

@objc func handleInterruption(notification: Notification) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 메서드는 private으로 설정하지 않으신 이유가 궁금합니다.
하단에서 selector로 등록할때 문제가 생길까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아뇽,, 마지막에 private 체크했는데 objc가 앞에 있어서 당연히 뒤에 달려있다고 생각했나봅니다~ 수정해서 올리겠습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0723f0b 해당 커밋을 통해서 수정해두었습니다.

guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
return
}
switch type {
case .began:
self.publisher.send(true)
self.engine.stop()

case .ended:
self.audioEngineStart()
self.publisher.send(false)
default: ()
}
}

private func setupNotifications() {
let callInterruptNotificationCenter = NotificationCenter.default
callInterruptNotificationCenter.addObserver(self,
selector: #selector(handleInterruption),
name: AVAudioSession.interruptionNotification,
object: self.audioSession
)
}

private func configureSoundPlayers(weak: String, medium: String, strong: String) throws {
Expand Down Expand Up @@ -85,6 +113,21 @@ class SoundManager {

extension SoundManager: PlaySoundInterface {

var callInterruptPublisher: AnyPublisher<Bool, Never> {
publisher.eraseToAnyPublisher()
}

func audioEngineStart() {
self.engine.stop()
if !self.engine.isRunning {
do {
try self.engine.start()
} catch {
print("오디오 엔진 시작 및 재시작 실패: \(error.localizedDescription)")
}
}
}

func beep(_ accent: Accent) {

guard let buffer = self.audioBuffers[accent] else { return }
Expand Down