AI 채팅봇과 대화할 수 있는 채팅 페이지를 만드는데, 타이핑뿐만 아니라 음성으로도 메시지를 입력할 수 있게 해야 한다.
speech_to_text 플러터 패키지를 사용해 구현할 수 있다.
https://pub.dev/packages/speech_to_text
speech_to_text | Flutter package
A Flutter plugin that exposes device specific speech to text recognition capability.
pub.dev
1. 설치
pubspec.yaml에 의존성 추가해주고 pub get
dependencies:
speech_to_text: ^6.6.2
공식문서상에 따르면 가장 최신 버전은 7.0.0인 듯하나 나는 플러터 sdk 버전 문제로 인해 7버전은 설치할 수 없어서 조금 낮은 버전을 사용했다.
코드에서 사용시 아래 import를 추가해주면 사용 가능하다.
import 'package:speech_to_text/speech_to_text.dart';
2. 권한 설정, init
StatefulWidget에서 initState는 처음 한 번만 실행된다.
이때 initSpeech라는 함수를 불러 stt를 initialize해주자.
class _ChatScreenState extends State<ChatScreen> {
final List<Map<String, String>> _messages = [
{'sender': 'bot', 'text': '안녕! 오늘은 이 그림에 대해 이야기해볼까? 먼저 그림을 천천히 감상해보자!'}
]; // 발신자-메시지 저장
final TextEditingController _controller = TextEditingController();
final SpeechToText _speech = SpeechToText();
bool _speechEnabled = false;
String _ttsText = '';
@override
void initState() {
super.initState();
_initSpeech();
}
void _initSpeech() async {
await Permission.microphone.status;
_speechEnabled = await _speech.initialize(
onStatus: (status) => print('Speech status: $status'),
onError: (error) => print('Speech error: $error'),
);
print('Speech enabled: $_speechEnabled');
setState(() {});
}
//...생략
리스트는 채팅 메시지를 담는 부분이라 stt와는 별 상관없다
stt를 사용하려면 먼저 SpeechTotext() 를 통해 SpeechToText 객체를 만들어주어야 한다. 나는 _speech라는 이름으로 만들었다.
initSpeech는 공식문서와 챗지피티를 참고했다
우선 마이크 기능을 사용하려면 디바이스에서 권한 허용을 해줘야 한다. 그 권한허용을 기다리기 위해 await을 넣었고, 이후에 initialize를 호출해 초기화해준다.
권한허용 관련 패키지는 퍼미션핸들러를 사용했다. 근데 이건 아직 테스트를 덜 해봐서 추가 확인이 필요할 듯...
https://pub.dev/packages/permission_handler
permission_handler | Flutter package
Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
pub.dev
그리고 마이크권한 사용을 위해 프로젝트에서 추가적인 설정이 필요하다.
android/app/src/main/AndroidManifest.xml 에 레코드 오디오 권한을 설정해준다.
내폰이 갤럭시라 iOS는 아직 따로 설정을 안했다..
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
또한 에뮬레이터에서는 음성인식이 똑바로 작동하지 않는 경우가 많아, 실제 디바이스를 연결해 돌렸다.
3. stt 시작하기/끝내기 함수
마이크 버튼을 누르면 듣기를 시작하고, 다시 누르면 듣기를 멈춘다.
들을 때는 listen, 멈출 때는 stop 메서드를 사용한다.
listen에서 들은 결과를 가지고 할 일은 onResult에, 리슨할 최대 길이는 listenFor, 리슨하고 기다리는 대기시간은 pauseFor에 지정해준다. 그리고 나는 한국어를 들을 것이기 때문에 localeId를 한국으로 설정해줘야 한국어로 인식한다.
void _startListening() async {
await _speech.listen(
onResult: _onSpeechResult,
listenFor: const Duration(seconds: 30),
pauseFor: const Duration(seconds: 5),
localeId: 'ko_KR',
);
print('mic clicked');
setState(() {});
}
void _stopListening() async {
await _speech.stop();
print('mic stopped');
setState(() {});
}
void _onSpeechResult(SpeechRecognitionResult result) {
print('speech result');
print(result);
setState(() {
_ttsText = result.recognizedWords;
_controller.text = _ttsText;
});
}
setState를 함수 끝에 호출해주는 이유는, _speech가 듣는 상태냐 아니냐에 따라서 마이크 아이콘의 모양을 바꿔주어야 하기 때문이다. StatefulWidget은 state가 바뀔 때마다 다시 렌더링하므로 이런 과정이 필요한 것 :/
리슨한 결과를 채팅 텍스트필드에 세팅해줄 것이므로, onSpeechResult 함수에는 들은 결과를 _ttsText에 저장하고, 텍스트필드 컨트롤러에 설정해준다.
이렇게 설정해주면 잘 작동한다.
마지막으로 위젯은 대충 아래처럼 생겼다.
원래는 마이크 모양이나 텍스트필드 placeholder를 isListening이라는 bool변수를 따로 만들어 그를 통해 관리하려 했는데, 이미 speechtotext에 isListening / isNotListening이 내장되어 있어서 굳이 필요없게 되었다.
//...생략
Container(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: InputDecoration(
hintText:
_speech.isListening ? '음성 인식 중입니다...' : '메시지를 입력하세요',
border: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.onPrimary,
),
borderRadius: BorderRadius.circular(10),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.secondary,
),
),
contentPadding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 10,
),
),
style: const TextStyle(
fontSize: 16,
),
cursorColor: Theme.of(context).colorScheme.onPrimary,
),
),
IconButton(
onPressed:
_speech.isNotListening ? _startListening : _stopListening,
icon: Icon(
_speech.isListening ? Icons.mic : Icons.mic_none,
color: Theme.of(context).colorScheme.secondary,
size: 30,
),
),
IconButton(
onPressed: _sendMessage,
icon: Icon(
Icons.send,
color: Theme.of(context).colorScheme.secondary,
size: 30,
),
),
],
),
),
//...생략
테스트 몇 번 해봤는데 꽤 잘 듣는다.
이제 AI 메시지에도 tts를... 구현해야 하는데 플러터 패키지에 text_to_speech가 있긴하다. 근데 이걸 쓸지 OpenAI 등에서 제공하는 걸 사용할지는 생각해봐야 할 듯
'기록' 카테고리의 다른 글
[에이닷(A.)] 하이 멀티LLM, 잼얘해줘! (5) | 2024.09.19 |
---|---|
[Flutter] OpenAI Text to speech로 챗봇 채팅 음성으로 읽어주기(tts) (0) | 2024.09.11 |
[React] 카카오API로 내 위치에서 지도 검색하기(1) (2) | 2024.08.15 |
레오니드 팬 페이지 만들기(2) (1) | 2024.02.23 |
레오니드 팬 페이지 만들기 (1) (0) | 2024.02.20 |