1
NuguCentralManager.shared.client.asrAgent.add(delegate: self)
자세한 source code 변경사항은 github release note 를 참고해주세요.(https://github.com/nugu-developers/nugu-ios/releases/tag/1.0.)
자세한 source code 변경사항은 github release note 를 참고해주세요.(https://github.com/nugu-developers/nugu-ios/releases/tag/1.0.)
NUGU SDK 의 구성요소에 대한 변경사항을 전달하기 위해 DelegateSet
대신 NotificationCenter
를 사용합니다.
DelegateSet
은 삭제되었으며, 기존에 DelegateSet
이 하던 역할을 delegate pattern 과 observer pattern 으로 구분하였습니다.
1
NuguCentralManager.shared.client.asrAgent.add(delegate: self)
1
2
3
4
5
asrResultObserver = object.observe(NuguAgentNotification.ASR.Result.self, queue: .main) { (notification) in
switch notification.result {
...
}
}
1
client.systemAgent.add(systemAgentDelegate: self)
1
2
3
4
5
6
7
8
9
10
11
systemAgentExceptionObserver = object.observe(NuguAgentNotification.System.Exception.self, queue: nil) { (notification) in
guard let self = self else { return }
switch notification.code {
...
}
}
systemAgentRevokeObserver = object.observe(NuguAgentNotification.System.RevokeDevice.self, queue: nil) { (notification) in
...
}
1
NuguCentralManager.shared.client.dialogStateAggregator.add(delegate: self)
1
2
3
4
5
dialogStateObserver = object.observe(NuguClientNotification.DialogState.State.self, queue: nil) { (notification) in
switch notification.state {
...
}
}
NuguClient 의 필수/선택 요소를 보다 효율적으로 생성하기 위해 builder pattern 이 적용되었습니다.
1
2
3
4
5
6
7
lazy private(set) var client: NuguClient = {
let client = NuguClient(delegate: self)
...
return client
}()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
lazy private(set) var client: NuguClient = {
let nuguBuilder = NuguClient.Builder()
...
// Set DataSource for SoundAgent
nuguBuilder.setDataSource(self)
// Set Delegate for PhoneCallAgent, MessageAgent, MediaPlayerAgent,
// ExtensionAgent, LocationAgent, PermissionAgent
nuguBuilder.setDelegate(self)
let client = nuguBuilder.build()
client.delegate = self
return client
}()
NUGU SDK 의 SpeechRecognizerAggregator
가 MicInputProvider
, ASRAgent
, KeywordDetector
를 통합 관리합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
private var startMicWorkItem: DispatchWorkItem?
// Audio input source
private let micQueue = DispatchQueue(label: "central_manager_mic_input_queue")
private let micInputProvider = MicInputProvider()
func enable() {
...
// Set Last WakeUp Keyword
// If you don't want to use saved wakeup-word, don't need to be implemented
if UserDefaults.Standard.useWakeUpDetector,
let keyword = Keyword(rawValue: UserDefaults.Standard.wakeUpWord) {
client.keywordDetector.keywordSource = keyword.keywordSource
startWakeUpDetector()
startMicWorkItem?.cancel()
startMicWorkItem = DispatchWorkItem(block: { [weak self] in
log.debug("startMicWorkItem start")
self?.startMicInputProvider(requestingFocus: false) { (success) in
guard success else {
log.debug("startMicWorkItem failed!")
return
}
}
})
guard let startMicWorkItem = startMicWorkItem else { return }
// When mic has been activated before interruption end notification has been fired,
// Option's .shouldResume factor never comes in. (even when it has to be)
// Giving small delay for starting mic can be a solution for this situation
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5, execute: startMicWorkItem)
} else {
stopWakeUpDetector()
stopMicInputProvider()
}
}
func startMicInputProvider(requestingFocus: Bool, completion: @escaping (Bool) -> Void) {
startMicWorkItem?.cancel()
DispatchQueue.main.async {
guard UIApplication.shared.applicationState == .active else {
completion(false)
return
}
NuguAudioSessionManager.shared.requestRecordPermission { [unowned self] isGranted in
guard isGranted else {
log.error("Record permission denied")
completion(false)
return
}
self.micQueue.async { [unowned self] in
defer {
log.debug("addEngineConfigurationChangeNotification")
NuguAudioSessionManager.shared.registerAudioEngineConfigurationObserver()
}
self.micInputProvider.stop()
// Control center does not work properly when mixWithOthers option has been included.
// To avoid adding mixWithOthers option when audio player is in paused state,
// update audioSession should be done only when requesting focus
if requestingFocus {
NuguAudioSessionManager.shared.updateAudioSession(requestingFocus: requestingFocus)
}
do {
try self.micInputProvider.start()
completion(true)
} catch {
log.error(error)
completion(false)
}
}
}
}
}
func stopMicInputProvider() {
micQueue.sync {
startMicWorkItem?.cancel()
micInputProvider.stop()
NuguAudioSessionManager.shared.removeAudioEngineConfigurationObserver()
}
}
func startWakeUpDetector() {
client.keywordDetector.start()
}
func stopWakeUpDetector() {
client.keywordDetector.stop()
}
func startRecognition(initiator: ASRInitiator) {
client.asrAgent.startRecognition(initiator: initiator)
}
func stopRecognition() {
client.asrAgent.stopRecognition()
}
1
2
3
4
5
6
7
8
9
10
11
func startListening(initiator: ASRInitiator) {
client.speechRecognizerAggregator.startListening(initiator: initiator)
}
func startListeningWithTrigger() {
client.speechRecognizerAggregator.startListeningWithTrigger()
}
func stopListening() {
client.speechRecognizerAggregator.stopListening()
}
KeywordDetector
, MicInputProvider
, ASRAgent
시작/종료와 관련된 코드를 모두 제거하고SpeechRecognizerAggregator
의 startListening(initiator:)
, startListeningWithTrigger
, stopListening
함수 호출로 대체합니다.
1
2
3
4
5
6
// Set Last WakeUp Keyword
// If you don't want to use saved wakeup-word, don't need to be implemented
if UserDefaults.Standard.useWakeUpDetector,
let keyword = Keyword(rawValue: UserDefaults.Standard.wakeUpWord) {
client.keywordDetector.keywordSource = keyword.keywordSource
}
1
2
3
4
5
6
7
8
9
// Set Last WakeUp Keyword
// If you don't want to use saved wakeup-word, don't need to be implemented.
// Because `aria` is set as a default keyword
if let keyword = Keyword(rawValue: UserDefaults.Standard.wakeUpWord) {
nuguBuilder.keywordDetector.keyword = keyword
}
// If you want to use built-in keyword detector, set this value as true
nuguBuilder.speechRecognizerAggregator.useKeywordDetector = UserDefaults.Standard.useWakeUpDetector
Keyword(e.g. “아리아”) 관련 설정을 KeywordDetector
대신 SpeechRecognizerAggregator
에 적용합니다.
NUGU SDK 를 사용하기 위해서는 시스템, Application, NUGU SDK 의 복합적인 상황에 맞춰 AVAudioSession 의 category
, categoryOptions
와 AVAudioSession.interruptionNotification
, AVAudioSession.routeChangeNotification
등을 적절하게 처리해 주어야 합니다.
SDK 1.0 에 추가된 AudioSessionManager
는 NUGU SDK 를 사용하기 위한 기본적인 AVAudioSession 처리를 구현하고 있습니다.
AVAudioSession 이 이미 Application 에서 충분히 복잡하게 관리되고 있다면, AudioSessionManager
를 사용하는 것보다 기존의 관리 로직에 NUGU SDK 를 위한 로직을 추가하는 방향이 더욱 효율적일 수 있습니다.
Custom AudioSessionManager 를 참고해주세요.
Application 의 NuguAudioSessionManager.swift
파일 및 관련 코드를 삭제합니다.
1
client.audioSessionManager?.enable()
AudioSessionManager
가 AVAudioSession.interruptionNotification
, AVAudioSession.routeChangeNotification
event 를 감지합니다.
NUGU SDK 를 사용하는 동안 event 를 감지하여 SDK 동작에 필요한 기능을 수행합니다.
1
client.audioSessionManager?.disable()
AudioSessionManager
가 AVAudioSession.interruptionNotification
, AVAudioSession.routeChangeNotification
event 감지를 중지합니다.
NUGU SDK 를 사용하지 않을 때 호출하여 SDK 가 불필요한 동작을 수행하지 않도록합니다.
NUGU SDK 의 AudioSessionManager
를 사용하지 않고 Application 에서 AVAudioSession
을 직접 처리할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
lazy private(set) var client: NuguClient = {
let nuguBuilder = NuguClient.Builder()
...
nuguBuilder.audioSessionManager = nil
...
let client = nuguBuilder.build()
client.delegate = self
...
return client
}()
NuguClient
생성시 audioSessionManager
를 nil 처리합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extension NuguCentralManager: NuguClientDelegate {
func nuguClientWillUseMic() {
if requestingFocus {
NuguAudioSessionManager.shared.updateAudioSession(requestingFocus: true)
}
}
func nuguClientWillRequireAudioSession() -> Bool {
return NuguAudioSessionManager.shared.updateAudioSession(requestingFocus: true)
}
func nuguClientDidReleaseAudioSession() {
NuguAudioSessionManager.shared.notifyAudioSessionDeactivation()
}
...
}
NUGU SDK iOS 0.x 의 NuguAudioSessionManager.swift 를 참고하여 Application 에서 AudioSessionManager 를 직접 구현하고 NuguClientDelegate
의 일부 함수를 구현해 주어야 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@objc private extension MainViewController {
func didTapForStopRecognition() {
guard [.listening, .recognizing].contains(NuguCentralManager.shared.client.asrAgent.asrState) else { return }
NuguCentralManager.shared.client.asrAgent.stopRecognition()
}
}
func initializeNugu() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapForStopRecognition))
tapGestureRecognizer.delegate = self
tapGestureRecognizer.cancelsTouchesInView = false
view.addGestureRecognizer(tapGestureRecognizer)
}
extension MainViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let touchLocation = touch.location(in: gestureRecognizer.view)
return !nuguVoiceChrome.frame.contains(touchLocation)
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
UITapGestureRecognizer
관련 기능을 VoiceChromePresenter
에서 처리하기 때문에 Application 에서 관련 코드를 삭제합니다.
1
lazy private(set) var asrBeepPlayer: ASRBeepPlayer = ASRBeepPlayer(focusManager: client.focusManager)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
extension MainViewController: DialogStateDelegate {
func dialogStateDidChange(_ state: DialogState, isMultiturn: Bool, chips: [ChipsAgentItem.Chip]?, sessionActivated: Bool) {
switch state {
case .listening:
DispatchQueue.main.async {
NuguCentralManager.shared.asrBeepPlayer.beep(type: .start)
}
case .thinking:
DispatchQueue.main.async { [weak self] in
self?.nuguButton.pauseDeactivateAnimation()
}
default:
break
}
}
}
extension MainViewController: ASRAgentDelegate {
func asrAgentDidReceive(result: ASRResult, dialogRequestId: String) {
switch result {
case .complete:
DispatchQueue.main.async {
NuguCentralManager.shared.asrBeepPlayer.beep(type: .success)
}
case .error(let error, _):
DispatchQueue.main.async {
switch error {
case ASRError.listenFailed:
NuguCentralManager.shared.asrBeepPlayer.beep(type: .fail)
case ASRError.recognizeFailed:
NuguCentralManager.shared.localTTSAgent.playLocalTTS(type: .deviceGatewayRequestUnacceptable)
default:
NuguCentralManager.shared.asrBeepPlayer.beep(type: .fail)
}
}
default: break
}
}
}
Beep 음 재생과 관련된 기능을 VoiceChromePresenter
에서 처리하기 때문에 Application 의 ASRBeepPlayer.swift
파일 및 asrBeepPlayer.beep(type:)
호출 코드를 삭제합니다.
AudioPlayerAgent 를 사용하여 음악을 재생하는 경우 SDK 의 ControlCenterManager
에서 MPNowPlayingInfoCenter
를 통해 재생정보를 iOS platform 으로 전달합니다.
1
let displayPlayerController = NuguDisplayPlayerController()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extension MainViewController: AudioDisplayViewPresenterDelegate {
func displayControllerShouldUpdateTemplate(template: AudioPlayerDisplayTemplate) {
NuguCentralManager.shared.displayPlayerController.update(template)
}
func displayControllerShouldUpdateState(state: AudioPlayerState) {
NuguCentralManager.shared.displayPlayerController.update(state)
}
func displayControllerShouldUpdateDuration(duration: Int) {
NuguCentralManager.shared.displayPlayerController.update(duration)
}
func displayControllerShouldRemove() {
NuguCentralManager.shared.displayPlayerController.remove()
}
...
}
MPNowPlayingInfoCenter
관련 기능을 AudioDisplayViewPresenter
와 ControlCenterManager
에서 처리하기 때문에 Application 의 NuguDisplayPlayerController.swift
및 관련 코드를 삭제합니다.