포스트

Web Speech API로 TTS 및 STT 구현하기

Text-to-Speech (TTS)




Speech-to-Text (STT)

마이크 권한 상태: 확인 중...

인식된 텍스트:


TTS 코드

html

1
2
3
4
5
6
7
8
9
10
<div class="container">
    <h2>Speech-to-Text (STT)</h2>
    <p id="status-message">마이크 권한 상태:
        <span id="permission-status">확인 중...</span>
    </p>
    <button id="start-record-button">녹음 시작</button>
    <button id="stop-record-button" disabled>녹음 중지</button>
    <p>인식된 텍스트: <span id="recognized-text"></span></p>
</div>

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
    const speakButton = document.getElementById('speak-button');
    const pauseButton = document.getElementById('pause-button');
    const resumeButton = document.getElementById('resume-button');
    const stopButton = document.getElementById('stop-button');
    const ttsText = document.getElementById('tts-text');

    let currentSpeech = null; // 현재 진행 중인 SpeechSynthesisUtterance 객체

    // TTS 시작
    speakButton.addEventListener('click', () => {
        const text = ttsText.value.trim();
        if (!text) {
            alert("읽을 텍스트를 입력하세요.");
            return;
        }

        // 현재 진행 중인 TTS가 있다면 중지
        if (window.speechSynthesis.speaking || window.speechSynthesis.paused) {
            window.speechSynthesis.cancel();
        }

        // 새로운 TTS 시작
        currentSpeech = new SpeechSynthesisUtterance(text);
        currentSpeech.lang = 'ko-KR'; // 한국어 설정
        currentSpeech.lang = 'ko-KR'; // 한국어 설정
        currentSpeech.rate = 1; // 음성 속도, 0.1 ~ 10 사이의 값
        currentSpeech.pitch = 1; // 음높이, 0 ~ 2 사이의 값
        currentSpeech.volume = 1; // 음량, 0 ~ 1 사이의 값


        // TTS가 끝날 때 버튼 상태 초기화
        currentSpeech.onend = () => {
            speakButton.disabled = false;
            pauseButton.disabled = true;
            resumeButton.disabled = true;
            stopButton.disabled = true;
        };

        window.speechSynthesis.speak(currentSpeech);

        // 버튼 상태 업데이트
        speakButton.disabled = true;
        pauseButton.disabled = false;
        resumeButton.disabled = true;
        stopButton.disabled = false;
    });

    // TTS 일시 중지
    pauseButton.addEventListener('click', () => {
        if (window.speechSynthesis.speaking) {
            window.speechSynthesis.pause();
            pauseButton.disabled = true;
            resumeButton.disabled = false;
        }
    });

    // TTS 다시 재생
    resumeButton.addEventListener('click', () => {
        if (window.speechSynthesis.paused) {
            window.speechSynthesis.resume();
            pauseButton.disabled = false;
            resumeButton.disabled = true;
        }
    });

    // TTS 중지
    stopButton.addEventListener('click', () => {
        if (window.speechSynthesis.speaking || window.speechSynthesis.paused) {
            window.speechSynthesis.cancel();
            speakButton.disabled = false;
            pauseButton.disabled = true;
            resumeButton.disabled = true;
            stopButton.disabled = true;
        }
    });

STT 코드

html

1
2
3
4
5
6
7
8
<div class="container">
    <h2>Text-to-Speech (TTS)</h2>
    <textarea id="tts-text" rows="3" cols="50" placeholder="여기에 텍스트를 입력하세요"></textarea><br>
    <button id="speak-button">읽기</button>
    <button id="pause-button" disabled>일시 중지</button>
    <button id="resume-button" disabled>다시 재생</button>
    <button id="stop-button" disabled>중지</button>
</div>

먼저 textarea 태그에 TTS 를 적용시킬 텍스트를 입력 받을 준비를 합니다. button 을 눌러 입력한 텍스트를 재생시키위한 코드를 작성합니다.

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
    const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
    recognition.lang = 'ko-KR'; // 한국어 설정
    recognition.continuous = true; // 연속 모드 활성화
    recognition.interimResults = false; // 중간 결과 비활성화


    const startButtonRec = document.getElementById('start-record-button');
    const stopButtonRec = document.getElementById('stop-record-button');
    const recognizedText = document.getElementById('recognized-text');
    const permissionStatusSpan = document.getElementById('permission-status');


    startButtonRec.addEventListener('click', () => {
        recognition.start();
        startButtonRec.disabled = true;
        stopButtonRec.disabled = false;
    });

    stopButtonRec.addEventListener('click', () => {
        recognition.stop();
        startButtonRec.disabled = false;
        stopButtonRec.disabled = true;
    });

    recognition.addEventListener('result', (event) => {
        const transcript = event.results[0][0].transcript; // 인식된 텍스트
        recognizedText.textContent = transcript;
    });

    recognition.addEventListener('end', () => {
        currentSpeech = new SpeechSynthesisUtterance(recognizedText.textContent);
        window.speechSynthesis.speak(currentSpeech);
        setTimeout(()=>{
            recognizedText.textContent = '';
        }, 1000); // 1초 후에 빈 텍스트로 업데이트
    });

    // 마이크 권한 확인 및 상태 업데이트
    async function checkMicrophonePermission() {
        try {
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
            if (stream) {
                updateUI('granted');
            }
        } catch (error) {
            updateUI('denied');
        }
    }

    // UI 업데이트 함수
    function updateUI(status) {
        if (status === 'granted') {
            permissionStatusSpan.textContent = '허용됨';
            permissionStatusSpan.style.color = 'green';
            startButtonRec.disabled = false;
            stopButtonRec.disabled = true;
        } else if (status === 'denied') {
            permissionStatusSpan.textContent = '거부됨';
            permissionStatusSpan.style.color = 'red';
            startButtonRec.disabled = true;
            stopButtonRec.disabled = true;
        } else {
            permissionStatusSpan.textContent = '알 수 없음';
            permissionStatusSpan.style.color = 'orange';
            startButtonRec.disabled = true;
            stopButtonRec.disabled = true;
        }
    }

    // 권한 변경 감지
    async function monitorPermissionChanges() {
        try {
            const permissionStatus = await navigator.permissions.query({ name: 'microphone' });
            updateUI(permissionStatus.state); // 초기 상태 설정

            // 권한 변경 이벤트 리스너 등록
            permissionStatus.onchange = () => {
                updateUI(permissionStatus.state);
            };
        } catch (error) {
            console.error('권한 상태를 확인할 수 없습니다.', error);
            updateUI('unknown');
        }
    }

    // 윈도우 로드 시 실행
    window.addEventListener('load', () => {
        checkMicrophonePermission(); // 권한 초기 확인
        monitorPermissionChanges(); // 권한 변경 감지
    });

마이크 권한을 허용할때만 STT가 실행되게 합니다.



🧐 오늘의 소감은?

플러터에서 패키지로 받아서 사용했던 것처럼 node.js 에서 패키지 받아서 사용하는 줄 알았는데 Web Speech API가 있어서 자바스크립트 내에서 해결을 할 수 있어서 신기했습니다. 물론 네이버 클로바처럼 자연스러운 말투는 아니지만 나름 만족하면서 기능구현했습니다.


이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.