Important Notes

v3.1.7부터 오디오 라우트 처리 및 세션 옵션 커스터마이징이 추가되었습니다.

Audio Route Change Handling

1. 새로운 Delegate Methods

오디오 경로 변경(블루투스 연결/해제, 헤드폰 연결 등) 시 호출되는 delegate가 추가되었습니다.

protocol AsleepSleepTrackingManagerDelegate {
    // 라우트 변경 시작 (Engine 재시작 전)
    func willChangeAudioRoute(from oldRoute: AVAudioSessionRouteDescription,
                              to newRoute: AVAudioSessionRouteDescription)

    // 라우트 변경 완료 (Engine 재시작 후)
    func didChangeAudioRoute(to newRoute: AVAudioSessionRouteDescription)
}

특징:

  • Optional 메서드 (구현하지 않아도 기존 동작 유지)
  • 블루투스, 헤드폰 연결/해제 시 호출
  • Engine 재구성 전후로 분리되어 호출

2. Interruption vs Route Change

Delegate발생 조건Engine 재시작처리 속도
didInterrupt() / didResume()전화, 알람, VOIP없음빠름 (~수 밀리초)
willChangeAudioRoute() / didChangeAudioRoute()블루투스, 헤드폰 연결/해제있음느림 (100-500ms)

3. 사용 예시

음악 재생 앱

extension YourViewController: AsleepSleepTrackingManagerDelegate {
    func willChangeAudioRoute(from oldRoute: AVAudioSessionRouteDescription,
                              to newRoute: AVAudioSessionRouteDescription) {
        // 라우트 변경 시작 -> 음악 일시정지
        if musicPlayer?.isPlaying == true {
            musicPlayer?.pause()
        }
    }

    func didChangeAudioRoute(to newRoute: AVAudioSessionRouteDescription) {
        // 라우트 변경 완료 -> 음악 재개
        musicPlayer?.play()
    }
}

라우트 정보 로깅

func willChangeAudioRoute(from oldRoute: AVAudioSessionRouteDescription,
                          to newRoute: AVAudioSessionRouteDescription) {
    let oldOutput = oldRoute.outputs.first?.portType.rawValue ?? "unknown"
    let newOutput = newRoute.outputs.first?.portType.rawValue ?? "unknown"

    print("Audio route changing: \(oldOutput) -> \(newOutput)")
}

Audio Session Options Customization

설계 목적

v3.1.7부터 SDK가 오디오 세션을 전담 관리합니다. 클라이언트가 직접 AVAudioSession.setActive() 또는 setCategory()를 호출하면 SDK와 충돌이 발생할 수 있습니다. 필요한 옵션은 startTracking()에 전달하여 SDK가 적절한 타이밍에 적용하도록 해야 합니다.


1. API 변경

startTracking() 메서드에 오디오 세션 옵션을 전달할 수 있습니다.

// 기존 (여전히 동작)
manager?.startTracking()

// 신규: 추가 옵션 전달
manager?.startTracking(additionalAudioSessionOptions: [.duckOthers])

중요:

  • 트래킹 중 AVAudioSession.sharedInstance().setActive() 직접 호출 금지
  • 트래킹 중 AVAudioSession.sharedInstance().setCategory() 직접 호출 금지
  • 필요한 옵션만 additionalAudioSessionOptions로 전달

2. SDK 기본 설정

SDK는 오디오 세션을 다음과 같이 설정합니다:

// Category (변경 불가)
.playAndRecord  // 녹음 + 재생 동시 지원

// Options (변경 불가)
[.mixWithOthers, .allowBluetoothA2DP]

additionalAudioSessionOptions에 전달한 옵션은 기본값에 추가됩니다:

manager?.startTracking(additionalAudioSessionOptions: [.duckOthers])
// 최종 적용: Category = .playAndRecord, Options = [.mixWithOthers, .allowBluetoothA2DP, .duckOthers]

3. 주요 사용 사례

음악 볼륨 자동 감소

func startTrackingWithMusic() {
    // 다른 앱 오디오 볼륨 자동 감소
    manager?.startTracking(additionalAudioSessionOptions: [.duckOthers])

    // 음악 재생
    musicPlayer?.play()
}

AirPlay 지원

func startTrackingWithAirPlay() {
    // AirPlay 허용
    manager?.startTracking(additionalAudioSessionOptions: [.allowAirPlay])
}

여러 옵션 조합

manager?.startTracking(additionalAudioSessionOptions: [
    .duckOthers,     // 음악 볼륨 감소
    .allowAirPlay    // AirPlay 허용
])

4. 사용 가능한 옵션

옵션설명사용 예시
.mixWithOthers다른 앱 오디오와 믹싱 (SDK 기본값)항상 적용됨
.allowBluetoothA2DPA2DP 블루투스 허용 (SDK 기본값)항상 적용됨
.duckOthers다른 앱 오디오 볼륨 감소음악 재생 시 방해 최소화
.allowAirPlayAirPlay 허용HomePod 재생
.defaultToSpeaker스피커 우선 출력스피커폰 사용

주의: .mixWithOthers는 SDK 기본값이므로 별도로 전달할 필요 없습니다.


5. 주의사항

옵션 누적:

  • startTracking() 호출 시 새로 설정됨
  • 이전 옵션은 유지되지 않음
// 첫 번째
manager?.startTracking(additionalAudioSessionOptions: [.duckOthers])

// stopTracking() 후 재시작
manager?.stopTracking()
manager?.startTracking(additionalAudioSessionOptions: [.allowAirPlay])
// -> .duckOthers는 사라지고 .allowAirPlay만 적용

잘못된 사용 예시:

// [금지] 트래킹 중 직접 오디오 세션 제어
func startTracking() {
    manager?.startTracking()

    // SDK와 충돌 발생!
    try? AVAudioSession.sharedInstance().setActive(true)  // <- 하지 마세요
}

Stability Improvements (v3.1.7)

1. 블루투스 안정성 개선

수정된 문제:

  • 블루투스 연결/해제 시 녹음 중단
  • VOIP 통화 후 녹음 재개 실패
  • 블루투스 연결 중 오디오 포맷 변경 크래시

결과:

  • 블루투스 기기 연결 상태 변화 시에도 안정적 녹음 유지
  • 스피커 ↔ 블루투스 자동 전환

2. 인터럽션 복구 개선

자동 복구 메커니즘:

  • 10초 자동 복구 타이머 추가
  • 앱 포그라운드 복귀 시 자동 복구 시도
  • VOIP 감지 및 대기

코드 수정 불필요:

  • SDK 내부에서 자동 처리
  • 기존 didResume() delegate 그대로 호출됨

3. 크래시 수정

다음 상황에서 발생하던 크래시 수정:

  • 인터럽션 중 오디오 라우트 재구성
  • 메모리 접근 오류
  • Race condition

Migration Guide

기존 코드 (변경 불필요)

class MyViewController: UIViewController, AsleepSleepTrackingManagerDelegate {
    var manager: Asleep.SleepTrackingManager?

    func startTracking() {
        Asleep.shared().createSleepTrackingManager(config: config, delegate: self)
        manager?.startTracking()  // 기존 코드 그대로 동작
    }

    // 기존 delegate 메서드들
    func didCreate() { }
    func didUpload(sequence: Int) { }
    func didClose(sessionId: String) { }
    func didFail(error: Asleep.AsleepError) { }
    func didInterrupt() { }
    func didResume() { }
    func micPermissionWasDenied() { }
    func analysing(session: Asleep.Model.Session) { }
}

선택적 개선 1: 음악 재생 제어

extension MyViewController {
    // 신규 delegate 추가 (optional)
    func willChangeAudioRoute(from oldRoute: AVAudioSessionRouteDescription,
                              to newRoute: AVAudioSessionRouteDescription) {
        if musicPlayer?.isPlaying == true {
            musicPlayer?.pause()
        }
    }

    func didChangeAudioRoute(to newRoute: AVAudioSessionRouteDescription) {
        musicPlayer?.play()
    }
}

선택적 개선 2: 오디오 옵션 추가

extension MyViewController {
    func startTrackingWithMusic() {
        Asleep.shared().createSleepTrackingManager(config: config, delegate: self)

        // 음악 볼륨 자동 감소
        manager?.startTracking(additionalAudioSessionOptions: [.duckOthers])

        // 백그라운드 음악 재생
        playBackgroundMusic()
    }
}