Web Speech API

김동현·2026년 3월 22일

Web Speech API

Web Speech API는 음성 데이터를 웹 앱에 통합할 수 있게 해줘요. Web Speech API는 두 부분으로 구성돼 있어요: SpeechSynthesis (텍스트 음성 변환), 그리고 SpeechRecognition (비동기 음성 인식)이에요.

💡 강사 팁: Web Speech API는 정말 재미있고 유용한 API예요! 음성 명령으로 웹앱을 제어하거나, 텍스트를 소리내어 읽어주는 기능을 쉽게 구현할 수 있어요. 특히 접근성(Accessibility) 측면에서 시각 장애가 있는 사용자들에게 큰 도움이 될 수 있답니다. 제 경험상 음성 인식 기능은 모바일 환경에서 특히 유용했어요. 사용자가 타이핑하기 어려운 상황에서 음성으로 입력받을 수 있거든요. 다만 브라우저 지원 범위를 꼭 확인하세요. Chrome과 Edge는 잘 지원하지만, Firefox와 Safari는 부분적으로만 지원하는 경우가 있어요. 실제 프로덕션에서 사용할 때는 polyfill이나 대체 수단을 준비해두는 게 좋아요!

주요 활용 사례:

  • SpeechSynthesis (TTS): 뉴스 기사 읽어주기, 학습 앱의 발음 도우미, 시각 장애인을 위한 화면 리더 등
  • SpeechRecognition: 음성 검색, 음성 명령 인터페이스, 받아쓰기 기능, 핸즈프리 컨트롤 등

개발할 때는 사용자에게 마이크 권한을 요청해야 하고, 개인정보 보호를 위해 HTTPS 환경에서만 작동한다는 점도 기억하세요!

웹 스피치 개념과 사용법 (Web speech concepts and usage)

안녕하세요, 프론트엔드 개발자 여러분! 오늘 저와 함께 살펴볼 내용은 브라우저에 마법 같은 기능을 더해주는 Web Speech API입니다. 문서가 영어라 읽기 부담스러우셨을 텐데, 제가 아주 쉽고 친절하게, 빼놓는 내용 없이 전부 번역해 드릴게요. 실무에서 바로 써먹을 수 있는 강사만의 꿀팁도 듬뿍 담았으니 기대해 주세요!


Web Speech API는 웹 앱에서 사용자의 음성 데이터(voice data)를 다룰 수 있게 해주는 아주 강력한 도구입니다. 이 API는 크게 두 가지 핵심 컴포넌트로 구성되어 있어요:

  • 음성 인식 (Speech recognition): 이 기능은 SpeechRecognition 인터페이스를 통해 접근할 수 있습니다. 마이크나 오디오 소스로부터 사용자의 음성을 인식해서 텍스트로 변환해주고, 앱이 그 내용에 맞게 적절히 반응할 수 있도록 해줍니다.
    일반적으로 여러분은 이 인터페이스의 생성자(constructor)를 사용해서 새로운 SpeechRecognition 객체를 만들게 됩니다. 이 객체에는 기기의 마이크(또는 오디오 트랙)로 음성이 들어오고 있는지를 감지할 수 있는 다양한 이벤트 핸들러들이 준비되어 있답니다.
    또한, 음성 인식을 수행할 때 사용자의 플랫폼(OS나 브라우저 기본 엔진)에서 제공하는 서비스를 사용할지, 아니면 브라우저 내에서 로컬로 처리할지를 직접 지정할 수도 있습니다. (기본값은 플랫폼 서비스 사용입니다.)

💡 강사의 실무 팁 (음성 인식): > 현업에서 음성 인식(STT, Speech-to-Text)을 구현하실 때 주의할 점이 있어요! 현재 브라우저 지원 상황이 완벽하지 않아서, 크롬 브라우저 등에서는 window.SpeechRecognition 대신 벤더 접두사가 붙은 window.webkitSpeechRecognition을 사용해야 작동한답니다. 크로스 브라우징을 위해 const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; 처럼 폴백(fallback) 코드를 작성하는 센스가 필요해요!

  • 음성 합성 (Speech synthesis): 이 기능은 SpeechSynthesis 인터페이스를 통해 접근합니다. 우리가 흔히 TTS(Text-to-Speech)라고 부르는 기능이죠! 프로그램이 여러분의 텍스트 콘텐츠를 기기의 기본 음성 합성기를 통해 소리 내어 읽어주게 만듭니다.
    다양한 목소리 타입은 SpeechSynthesisVoice 객체로 표현되며, 여러분이 브라우저가 읽어주길 바라는 텍스트의 각 부분들은 SpeechSynthesisUtterance 객체로 만들어집니다.
    이 발화(Utterance) 객체들을 다 만들었다면, SpeechSynthesis.speak() 메서드에 쏙 넘겨주기만 하세요. 그러면 브라우저가 신나게 말을 하기 시작할 거예요!

💡 강사의 실무 팁 (음성 합성): > 음성 합성을 사용할 때 아주 중요한 규칙이 하나 있습니다. 바로 "사용자의 직접적인 상호작용(클릭, 터치 등) 없이 페이지 로드와 동시에 음성을 자동 재생할 수 없다"는 브라우저 보안 정책입니다. 화면에 스피커 버튼 등을 만들어서 사용자가 직접 클릭했을 때 speak() 메서드가 호출되도록 설계하셔야 합니다!

이러한 기능들을 실제로 사용하는 방법에 대한 더 자세한 코드는 Web Speech API 사용하기 문서를 확인해 보세요.


Web Speech API 인터페이스 살펴보기 (Web Speech API Interfaces)

자, 이제 이 API를 구성하는 부품(인터페이스)들을 하나씩 뜯어보겠습니다.

음성 인식 (Speech recognition)

  • SpeechRecognition
    음성 인식 서비스를 제어하는 핵심 컨트롤러 인터페이스입니다. 인식 서비스에서 보내주는 SpeechRecognitionEvent를 받아서 처리하는 일도 여기서 합니다.

  • SpeechRecognitionAlternative
    음성 인식 서비스가 알아들은 하나의 단어(또는 문장 대안)를 나타냅니다. (종종 인식기가 "이 말인 것 같기도 하고, 저 말인 것 같기도 하다"라며 여러 대안을 줄 때 쓰입니다.)

  • SpeechRecognitionErrorEvent
    음성 인식 서비스가 작동하다가 문제가 생겼을 때 던져주는 에러 메시지들을 나타냅니다.

  • SpeechRecognitionEvent
    result(인식 성공) 이벤트와 nomatch(인식 실패) 이벤트를 위한 이벤트 객체입니다. 중간 인식 결과(말하고 있는 도중)나 최종 음성 인식 결과와 관련된 모든 데이터가 이 안에 들어있습니다.

  • SpeechRecognitionPhrase
    문맥적 편향(contextual biasing)을 위해 음성 인식 엔진에 넘겨줄 수 있는 특정 구(phrase)를 나타냅니다. (특정 전문 용어나 이름을 더 잘 알아듣게 하고 싶을 때 힌트를 주는 기능이에요.)

  • SpeechRecognitionResult
    단일 인식 매치 결과를 나타냅니다. 이 안에는 여러 개의 SpeechRecognitionAlternative 객체들이 우선순위대로 포함될 수 있습니다.

  • SpeechRecognitionResultList
    SpeechRecognitionResult 객체들의 리스트를 나타냅니다. 만약 continuous(연속 인식) 모드로 캡처를 하고 있다면 하나의 결과만 들어있을 수도 있습니다.

음성 합성 (Speech synthesis)

  • SpeechSynthesis
    음성 합성 서비스의 컨트롤러 인터페이스입니다. 기기에서 사용 가능한 목소리 목록을 불러오거나, 말을 시작하고 일시정지하는 등 다양한 명령을 내릴 때 사용합니다.

  • SpeechSynthesisErrorEvent
    음성 서비스가 SpeechSynthesisUtterance 객체를 처리하는 동안 발생한 모든 에러 정보를 담고 있습니다.

  • SpeechSynthesisEvent
    음성 서비스에서 처리된 SpeechSynthesisUtterance 객체의 현재 상태(시작됨, 일시정지됨, 끝남 등)에 대한 정보를 담고 있습니다.

  • SpeechSynthesisUtterance
    스피커로 내보낼 '음성 요청' 자체를 나타냅니다. 음성 서비스가 읽어야 할 텍스트 내용은 물론이고, 어떤 언어, 어떤 높낮이(pitch), 어떤 크기(volume)로 읽어야 할지에 대한 모든 설정값을 가지고 있습니다.

  • SpeechSynthesisVoice
    시스템이 지원하는 특정한 목소리(예: 구글 한국어 여성 목소리 등)를 나타냅니다. 각각의 SpeechSynthesisVoice는 언어, 이름, URI 등에 대한 정보를 포함하는 고유한 음성 서비스를 가집니다.

  • Window.speechSynthesis
    이 속성은 SpeechSynthesisGetter라는 [NoInterfaceObject] 인터페이스의 일부로 명세되어 있으며, Window 객체에 구현되어 있습니다. 우리는 언제든 window.speechSynthesis를 통해 컨트롤러에 접근할 수 있고, 여기가 바로 음성 합성 기능의 출발점이 됩니다.

지원 중단된 인터페이스 (Deprecated interfaces)

웹 스피치 API에서 '문법(grammar)'이라는 개념은 이제 삭제되었습니다. 관련된 기능들은 구형 브라우저와의 하위 호환성 유지를 위해 명세서에 흔적만 남아있고 지원 브라우저에서도 에러를 내지는 않지만, 실제 음성 인식 서비스에는 아무런 영향을 미치지 않습니다. (따라서 새로 개발하실 때는 무시하셔도 됩니다!)

  • SpeechGrammar (사용 중단됨)
    인식 서비스가 인식해야 할 단어나 단어 패턴을 나타냈던 인터페이스입니다.
  • SpeechGrammarList (사용 중단됨)
    SpeechGrammar 객체들의 목록을 나타냈던 인터페이스입니다.

에러 처리하기 (Errors)

Speech API를 사용하다 보면 language-not-supported(지원되지 않는 언어)나 language-unavailable(언어 사용 불가) 같은 에러들을 마주칠 수 있습니다. 이 에러들에 대한 정보는 다음 문서들을 확인해 보세요:


보안 고려사항 (Security considerations)

Web Speech API의 온디바이스(기기 로컬) 음성 인식 기능에 접근하려면 브라우저의 권한 정책인 Permissions-Policy 지시문 중 on-device-speech-recognition에 의해 제어를 받습니다.

만약 이 정책이 사용을 차단하도록 설정되어 있는 환경이라면, API의 SpeechRecognition.available() 메서드나 SpeechRecognition.install() 메서드를 호출하려는 시도는 모두 실패하게 됩니다. 마이크 접근 권한과 직결된 문제이므로 사용자에게 올바르게 권한을 요청하는 흐름을 짜는 것이 중요합니다.


예제 (Examples)

백문이 불여일견이죠! 저희가 준비한 Web Speech API 예제 모음에서 음성 인식과 합성이 실제로 어떻게 동작하는지 소스 코드를 통해 확인해 보세요.

💡 마지막 강사 팁: > 요즘 웹 접근성(Accessibility)이 굉장히 중요해지고 있죠? 시각 장애인을 위해 화면 내용을 TTS로 읽어주거나, 키보드 조작이 불편한 분들을 위해 음성 명령으로 검색하게 해주는 기능은 정말 훌륭한 사용자 경험(UX)을 제공합니다. 여러분의 프로젝트 포트폴리오에 Web Speech API를 활용한 기능을 하나쯤 넣어보시면 면접관의 눈길을 사로잡을 수 있을 거예요! 화이팅!

Web Speech API 사용하기 (Using the Web Speech API)

안녕하세요, 예비 프론트엔드 개발자 여러분! 오늘 저와 함께 살펴볼 내용은 브라우저에 마법 같은 기능을 더해주는 Web Speech API입니다. 공식 문서가 영어라 읽기 부담스러우셨을 텐데, 제가 아주 쉽고 친절하게, 원본 내용을 하나도 빠짐없이 전부 번역해 드릴게요. 실무에서 바로 써먹을 수 있는 저만의 꿀팁도 듬뿍 담았으니 기대해 주세요!


Web Speech API는 음성 인식(Speech recognition)음성 합성(Speech synthesis, 텍스트를 음성으로 변환하는 TTS라고도 부르죠)이라는 두 가지 뚜렷한 기능 영역을 제공합니다. 이 기능들은 웹의 접근성을 높이고 완전히 새로운 형태의 제어 방식을 만들어내는 아주 흥미로운 가능성을 열어줍니다. 이 문서에서는 두 가지 영역 모두에 대한 소개와 함께 재미있는 데모를 제공합니다.


음성 인식 (Speech recognition)

음성 인식은 기기의 마이크(또는 오디오 트랙)로부터 오디오를 수신하고, 이를 음성 인식 서비스가 검사하는 과정을 거칩니다. 서비스가 단어나 구를 성공적으로 인식하게 되면, 여러분의 앱에서 다음 동작을 실행하는 데 사용할 수 있는 텍스트 문자열(또는 문자열의 목록)을 반환해 줍니다.

Web Speech API는 이를 위해 메인 컨트롤러 인터페이스인 SpeechRecognition과 인식된 결과를 표현하기 위한 여러 관련 인터페이스들을 가지고 있습니다.

일반적으로 음성 인식에는 사용자의 기기에 탑재된 음성 인식 시스템이 사용됩니다. 대부분의 최신 운영체제(OS)는 macOS의 받아쓰기(Dictation)나 Windows의 Copilot처럼 음성 명령을 내리기 위한 자체 음성 인식 시스템을 갖추고 있죠.

기본적으로 웹 페이지에서 음성 인식을 사용할 때는 서버 기반의 인식 엔진을 거치게 됩니다. 즉, 여러분이 말한 오디오 데이터가 처리를 위해 외부 웹 서비스로 전송된다는 뜻이며, 이 때문에 오프라인 상태에서는 작동하지 않습니다.

하지만 개인정보 보호(privacy)와 성능(performance)을 향상시키기 위해, 음성 인식이 기기 내부(on-device)에서 직접 수행되도록 지정할 수도 있습니다. 이렇게 하면 오디오 데이터나 변환된 텍스트가 처리를 위해 제3자 서비스(서버)로 전송되지 않는 것을 보장할 수 있죠. 온디바이스 기능에 대해서는 온디바이스 음성 인식 섹션에서 더 자세히 다루겠습니다.

데모 (Demo)

음성 인식을 어떻게 사용하는지 보여드리기 위해 Speech color changer(음성 색상 변경기)라는 간단한 샘플 앱을 만들었습니다. Start recognition(인식 시작) 버튼을 누른 다음, HTML 색상 키워드(예: red, blue, green 등)를 말해보세요. 그러면 앱의 배경색이 여러분이 말한 바로 그 색상으로 바뀔 것입니다!

Speech color changer 데모 앱 스크린샷

이 데모를 직접 실행해 보시려면, Web Speech API를 지원하는 브라우저에서 라이브 데모 URL로 접속해 보세요.

💡 강사의 팁: 현업에서 음성 인식을 도입할 때 가장 먼저 고려해야 할 점이 바로 '브라우저 호환성'입니다. 안타깝게도 아직 모든 브라우저가 완벽하게 지원하는 것은 아니거든요. 크롬 기반 브라우저에서는 아주 잘 작동하지만, 파이어폭스 등에서는 아직 실험적 기능일 수 있습니다. 그래서 항상 브라우저 지원 현황(Can I use)을 꼼꼼히 체크하는 습관을 들이는 것이 좋습니다!

HTML과 CSS (HTML and CSS)

이 앱의 HTML과 CSS는 아주 기본적인 수준입니다. 제목이 있고, 사용법을 알려주는 단락(<p>), 제어를 위한 버튼(<button>), 그리고 앱이 알아들은 단어를 포함해 여러 진단 메시지를 띄워줄 출력용 단락이 있습니다.

<h1>Speech color changer</h1>

<p class="hints"></p>

<button>Start recognition</button>

<p class="output"><em>...diagnostic messages</em></p>

CSS는 이 앱이 다양한 기기 환경에서도 그럭저럭 괜찮게 보이도록 아주 기본적인 반응형 스타일링을 제공합니다.

자바스크립트 (JavaScript)

이제 이 앱을 움직이는 자바스크립트를 조금 더 깊이 파헤쳐 보겠습니다.

접두사가 붙은 속성들 (Prefixed properties)

현재 일부 브라우저들은 벤더 접두사(vendor prefix)가 붙은 속성으로만 음성 인식을 지원합니다.
그래서 우리 코드의 맨 윗부분에는, 접두사가 붙은 속성과 붙지 않은 표준 버전을 모두 지원하기 위해 아래와 같은 코드를 작성해 둡니다.

const SpeechRecognition =
  window.SpeechRecognition || window.webkitSpeechRecognition;
const SpeechRecognitionEvent =
  window.SpeechRecognitionEvent || window.webkitSpeechRecognitionEvent;

💡 강사의 팁: 이렇게 ||(OR 연산자)를 활용하는 패턴은 구형 브라우저나 아직 표준화가 덜 된 API를 다룰 때 프론트엔드 개발자들이 숨 쉬듯 사용하는 아주 전형적이고 우아한 폴백(fallback) 방식입니다. 꼭 기억해 두세요!

색상 목록 (Color list)

코드의 다음 부분에서는 사용자에게 어떤 색상을 말해야 할지 힌트를 주기 위해 UI에 출력할 몇 가지 샘플 색상들을 배열로 정의합니다.

const colors = [
  "aqua",
  "azure",
  "beige",
  "bisque",
  "black",
  "blue",
  "brown",
  "chocolate",
  "coral",
  // …
];

음성 인식 인스턴스 생성하기 (Creating a speech recognition instance)

그다음으로, 우리 앱에서 음성 인식을 실질적으로 제어할 음성 인식 인스턴스를 하나 정의합니다. 이 작업은 SpeechRecognition() 생성자 함수를 호출해서 처리합니다.

const recognition = new SpeechRecognition();

이제 생성된 인식 인스턴스의 몇 가지 설정(properties)을 맞춰줍니다.

  • SpeechRecognition.continuous: 결과를 계속해서 연속적으로 캡처할지(true), 아니면 한 번 인식이 시작되고 끝날 때 딱 한 번만 캡처할지(false)를 제어합니다.
  • SpeechRecognition.lang: 인식할 언어를 설정합니다. 이 값을 명시적으로 설정해 주는 것이 권장되는 모범 사례(best practice)입니다.
  • SpeechRecognition.interimResults: 음성 인식 시스템이 최종 결과만 반환할지, 아니면 말하고 있는 도중의 임시(interim) 결과도 함께 반환할지 정의합니다. 이번 데모에서는 최종 결과만으로도 충분합니다.
  • SpeechRecognition.maxAlternatives: 결과당 반환될 '잠재적인 대안 일치 항목(alternative potential matches)'의 최대 개수를 설정합니다. 사용자의 발음이 불분명할 때 여러 개의 대안 목록을 보여주고 선택하게 할 때 유용할 수 있습니다. 하지만 이번 데모에서는 필요 없으므로 그냥 1개(어차피 기본값입니다)로 지정하겠습니다.
recognition.continuous = false;
recognition.lang = "en-US";
recognition.interimResults = false;
recognition.maxAlternatives = 1;

💡 강사의 팁: 만약 여러분이 실시간 음성 자막 기능(Live Caption)을 만든다면 interimResults = true로 설정해야 합니다. 그러면 사용자가 말을 마칠 때까지 기다리지 않고, 말하는 도중에 텍스트가 타닥타닥 화면에 찍히는 멋진 연출을 할 수 있답니다!

음성 인식 시작하기 (Starting the speech recognition)

출력할 단락(<p class="output">), <html> 요소, 설명 단락, 그리고 <button>에 대한 참조(reference)를 가져온 뒤, 버튼에 onclick 핸들러를 구현합니다. 사용자가 버튼을 누르면, SpeechRecognition.start() 메서드를 호출하여 음성 인식 서비스를 시작시킵니다. 또한 배열의 forEach()(여기서는 map()join()을 썼네요!) 메서드를 활용해 사용자가 읽어볼 수 있도록 힌트가 될 색상들을 시각적인 인디케이터로 화면에 출력해 줍니다.

const diagnostic = document.querySelector(".output");
const bg = document.querySelector("html");
const hints = document.querySelector(".hints");
const startBtn = document.querySelector("button");

const colorHTML = colors
  .map((v) => `<span style="background-color:${v};">${v}</span>`)
  .join("");
hints.innerHTML = `Press the button then say a color to change the background color of the app. Try ${colorHTML}.`;

startBtn.onclick = () => {
  recognition.start();
  console.log("Ready to receive a color command.");
};

결과 수신 및 처리하기 (Receiving and handling results)

음성 인식이 시작되고 나면, 여러 가지 이벤트 핸들러들을 사용할 수 있게 됩니다. 이 핸들러들을 이용해 인식 결과나 다른 관련 정보들을 가져올 수 있죠 (자세한 건 SpeechRecognitionEvents 부분을 참고하세요). 이 중에서 가장 흔하게 쓰이는 것이 바로 result 이벤트인데요, 성공적으로 인식 결과를 받았을 때 발생합니다.

recognition.onresult = (event) => {
  const color = event.results[0][0].transcript;
  diagnostic.textContent = `Result received: ${color}.`;
  bg.style.backgroundColor = color;
  console.log(`Confidence: ${event.results[0][0].confidence}`);
};

두 번째 줄(event.results[0][0].transcript)이 조금 복잡해 보일 수 있는데, 하나씩 쪼개서 설명해 드릴게요.

  • SpeechRecognitionEvent.results 속성은 여러 개의 SpeechRecognitionResult 객체들을 담고 있는 SpeechRecognitionResultList 객체를 반환합니다. 이 리스트는 배열처럼 접근할 수 있는 getter를 가지고 있어서, 첫 번째 [0]0번째 위치에 있는 SpeechRecognitionResult를 가져옵니다.
  • 그리고 각각의 SpeechRecognitionResult 객체는 또다시 그 안에 인식된 개별 단어를 나타내는 SpeechRecognitionAlternative 객체들을 담고 있습니다. 이것들도 배열처럼 접근할 수 있어서, 두 번째 [0]0번째 위치에 있는 SpeechRecognitionAlternative(즉, 가장 정확도가 높다고 판단된 1순위 결과)를 가져옵니다.
  • 마지막으로 SpeechRecognitionAlternativetranscript 속성은 인식된 텍스트를 문자열(string) 형태로 반환합니다. 우리는 이 값을 가져와서 배경색을 바꾸고, UI에 진단 메시지로 출력하는 데 사용합니다.

또한, 우리는 딱 한 단어가 인식되고 나면 더 이상 듣지 않고 음성 인식 서비스를 종료시키기 위해( SpeechRecognition.stop() 사용) speechend 이벤트도 사용합니다.

recognition.onspeechend = () => {
  recognition.stop();
};

에러와 인식 실패 처리하기 (Handling errors and unrecognized speech)

마지막 두 개의 핸들러는 사용자가 한 말을 아예 인식하지 못했거나 인식 과정에서 에러가 발생한 경우를 대비합니다. nomatch 이벤트는 아무것도 인식하지 못한 첫 번째 경우를 다룹니다. (다만 대부분의 경우, 인식 엔진은 그 말이 아무리 이해하기 힘들더라도 무언가 비슷한 결과를 억지로라도 반환하려는 경향이 있습니다.)

recognition.onnomatch = (event) => {
  diagnostic.textContent = "I didn't recognize that color.";
};

error 이벤트는 인식 과정에서 실제로 진짜 에러(마이크 권한 거부, 네트워크 끊김 등)가 발생했을 때 이를 처리합니다. 반환된 에러의 상세 내용은 SpeechRecognitionErrorEvent.error 속성에 담겨 있습니다.

recognition.onerror = (event) => {
  diagnostic.textContent = `Error occurred in recognition: ${event.error}`;
};

온디바이스(기기 내장) 음성 인식 (On-device speech recognition)

보통 음성 인식은 온라인 서비스를 사용해서 수행됩니다. 이 말은 즉, 녹음된 오디오 파일이 처리를 위해 서버로 전송되고, 그 결과가 다시 브라우저로 돌아온다는 뜻입니다. 하지만 이 방식에는 두 가지 큰 문제가 있습니다.

  • 개인정보 보호(Privacy): 많은 사용자가 자신의 목소리 데이터가 어딘지도 모를 서버로 전송되는 것을 매우 꺼립니다.
  • 성능(Performance): 아주 짧은 인식을 할 때마다 매번 데이터를 서버로 보내야 한다면, 처리가 많이 필요한 무거운 앱에서는 성능이 심각하게 느려질 수 있습니다. 게다가 오프라인 상태에서는 앱이 아예 작동하지 않게 되죠.

이러한 문제들을 완화하기 위해, Web Speech API는 브라우저가 기기 내부(on-device)에서 직접 음성 인식을 처리하도록 지정할 수 있게 해줍니다. 이 기능을 사용하려면 여러분이 인식하고 싶은 언어에 대해 언어 팩을 최초 한 번 다운로드해야 합니다. 일단 설치가 완료되면, 그 기능은 인터넷이 없는 오프라인 상태에서도 쌩쌩하게 작동하게 됩니다!

이 섹션에서는 이 온디바이스 음성 인식을 사용하는 방법을 설명하겠습니다.

데모 (Demo)

온디바이스 음성 인식을 보여주기 위해, 우리는 On-device speech color changer라는 새로운 샘플 앱을 만들었습니다. (여기서 라이브 데모를 실행해 보세요!)

이 데모는 앞에서 다뤘던 온라인 버전의 음성 색상 변경기 데모와 거의 똑같이 작동하지만, 아래에 설명할 몇 가지 차이점이 있습니다.

온디바이스 인식 지정하기 (Specifying on-device recognition)

브라우저의 온디바이스 처리 방식을 사용하고 싶다면, 음성 인식을 시작하기 전에 SpeechRecognition.processLocally 속성을 true로 설정해 주면 됩니다. (이 속성의 기본값은 false입니다.)

recognition.processLocally = true;

사용 가능 여부 확인 및 언어 팩 설치하기 (Checking availability and installing language packs)

온디바이스 음성 인식이 제대로 작동하려면, 브라우저에 여러분이 인식시키고자 하는 타겟 언어의 '언어 팩'이 미리 설치되어 있어야 합니다. 만약 processLocally = true로 지정해놓고 정작 필요한 언어 팩이 깔려있지 않은 상태에서 start() 메서드를 실행하면, 호출은 실패하고 language-not-supported 에러를 뱉어낼 것입니다.

올바른 언어 팩이 설치되도록 보장하려면 다음 두 단계를 거쳐야 합니다.

  1. 사용자의 기기에 해당 언어 팩이 사용 가능한지 확인합니다. 이는 SpeechRecognition.available() 정적(static) 메서드를 사용해 처리합니다.
  2. 만약 사용 불가능하다면, 언어 팩을 설치합니다. 이는 SpeechRecognition.install() 정적 메서드를 사용해 처리합니다.

이 단계들은 앱을 제어하는 <button>click 이벤트 핸들러 내에서 다음과 같이 처리됩니다.

startBtn.addEventListener("click", () => {
  // 타겟 언어의 사용 가능 여부를 체크합니다.
  SpeechRecognition.available({ langs: ["en-US"], processLocally: true }).then(
    (result) => {
      if (result === "unavailable") {
        diagnostic.textContent = `en-US is not available to download at this time. Sorry!`;
      } else if (result === "available") {
        recognition.start();
        console.log("Ready to receive a color command.");
      } else {
        diagnostic.textContent = `en-US language pack is downloading...`;
        SpeechRecognition.install({
          langs: ["en-US"],
          processLocally: true,
        }).then((result) => {
          if (result) {
            diagnostic.textContent = `en-US language pack downloaded. Start recognition again.`;
          } else {
            diagnostic.textContent = `en-US language pack failed to download. Try again later.`;
          }
        });
      }
    },
  );
});

available() 메서드는 두 개의 속성을 포함하는 옵션 객체를 인자로 받습니다.

  • langs: 사용 가능한지 확인하고 싶은 언어 코드들이 담긴 배열입니다.
  • processLocally: 언어 팩의 사용 가능 여부를 오직 기기 로컬에서만 확인할지(true), 아니면 로컬과 서버 기반 서비스 양쪽 모두에서 확인할지(false, 기본값) 지정하는 불리언(boolean) 값입니다.

이 메서드를 실행하면, 지정된 언어들의 사용 가능 여부를 나타내는 열거형(enumerated) 값으로 이행(resolve)되는 Promise를 반환합니다. 우리 데모에서는 세 가지 조건을 테스트합니다.

  • 결과값이 unavailable인 경우: 현재 다운로드할 수 있는 적합한 언어 팩이 없다는 뜻입니다. 화면에 미안하다는 메시지를 출력합니다.
  • 결과값이 available인 경우: 언어 팩이 이미 로컬에 설치되어 있어서 즉시 인식을 시작할 수 있다는 뜻입니다. 이 경우 start()를 실행하고, 앱이 음성을 받을 준비가 되었다고 콘솔에 로그를 남깁니다.
  • 결과값이 그 외의 값(downloadable 또는 downloading)인 경우: 사용자에게 언어 팩 다운로드가 시작된다는 진단 메시지를 띄우고, 다운로드를 처리하기 위해 install() 메서드를 실행합니다.

install() 메서드는 available() 메서드와 아주 비슷하게 작동하지만, 옵션 객체에 langs 배열 하나만 받습니다. 실행되면 langs에 지정된 모든 언어 팩을 다운로드하기 시작하고, 지정된 언어 팩들이 성공적으로 다운로드 및 설치되었는지(true) 아닌지(false)를 나타내는 불리언 값으로 이행되는 Promise를 반환합니다.

이번 데모에서는 설치 성공 및 실패 케이스를 나타내기 위해 단순히 텍스트 메시지만 출력했습니다. 하지만 실제 상용 앱을 만드실 때는 다운로드가 진행되는 동안 컨트롤(버튼 등)을 비활성화해두고, 프로미스가 완료되면 다시 활성화하는 방식으로 짜는 것이 훨씬 좋은 UX(사용자 경험)를 제공할 것입니다.

Permissions-policy(권한 정책) 연동 (Permissions-policy integration)

앞서 설명한 available()install() 메서드의 사용은 브라우저의 권한 정책인 Permissions-Policyon-device-speech-recognition 지시문에 의해 통제를 받습니다. 다시 말해, 정의된 보안 정책이 이 기능의 사용을 막고 있다면 이 메서드들을 호출하려는 모든 시도는 거부되고 실패하게 됩니다.

다행히 on-device-speech-recognition의 기본 허용 목록(allowlist) 값은 self입니다. 즉, 여러분이 iframe 등을 통해 다른 도메인(cross-origin)에 임베드된 문서에서 이 메서드를 사용하려고 하거나, 아예 명시적으로 이 기능의 사용을 비활성화하려는 특이한 상황이 아니라면 이 정책에 대해 크게 걱정할 필요는 없습니다.

접두사가 없는 Web Speech API (Unprefixed Web Speech API)

우리가 맨 처음 살펴봤던 오리지널 음성 색상 변경기 데모에서는, Web Speech API를 지원할 때 벤더 접두사(vendor-prefixed)가 붙은 속성으로만 지원하는 구형 브라우저들을 처리하기 위해 코드를 몇 줄 더 추가했었습니다 (자세한 건 접두사가 붙은 속성들 섹션을 참고하세요).

하지만 방금 본 온디바이스 버전의 데모에서는 그 접두사 처리 코드가 싹 빠졌습니다. 왜냐하면 온디바이스 음성 인식 기능을 지원하는 최신 브라우저들은 이미 접두사 없이(unprefixed) 표준 스펙 그대로 이 기능을 지원하고 있기 때문입니다!


음성 인식의 문맥적 편향 (Contextual biasing in speech recognition)

음성 인식 서비스가 특정한 단어나 구를 제대로 알아듣지 못하고 자꾸 엉뚱하게 인식하는 짜증 나는 순간들이 분명 있을 겁니다. 이런 현상은 주로 특정 분야에서만 쓰는 전문 용어(의학이나 과학 어휘 등), 고유 명사, 흔히 쓰지 않는 문구, 혹은 다른 단어와 발음이 너무 비슷해서 기계가 착각하기 쉬운 단어들에서 자주 발생하죠.

예를 들어, 저희가 온디바이스 색상 변경기 데모를 테스트할 때, 인식기가 azure(애저, 연한 청색)라는 색상을 인식하는 데 애를 먹는다는 걸 발견했어요. 자꾸 "as you" 같은 전혀 엉뚱한 결과만 반환했거든요. 자주 오인되는 다른 색상들로는 khaki(카키, "car key"로 인식), tan(탠), 그리고 thistle(시슬, "this all"로 인식) 등이 있었습니다.

이러한 문제를 우아하게 해결하기 위해, Web Speech API는 여러분이 인식 엔진에게 "지금 이런 상황이니까, 이 단어들이 나올 확률이 아주 높아. 그러니까 이 단어들에 가중치를 둬서 들어봐!"라고 '힌트'를 줄 수 있는 기능을 제공합니다. 이렇게 하면 엔진이 해당 단어나 구에 편향(biasing)되게 하여, 정확하게 인식될 확률을 비약적으로 높일 수 있죠.

이 기능은 SpeechRecognitionPhrase 객체들의 배열을 생성해서 SpeechRecognition.phrases 속성의 값으로 세팅해 주면 됩니다. 각각의 SpeechRecognitionPhrase 객체는 다음과 같은 내용을 담고 있습니다.

  • phrase: 가중치를 주고 싶은(boost) 단어나 구를 담은 문자열 속성.
  • boost: 0.0부터 10.0 사이의 부동 소수점(실수) 숫자로, 해당 단어에 얼만큼의 가중치(boost)를 부여할지 설정하는 속성. 값이 높을수록 엔진이 그 단어로 인식할 확률이 더 높아집니다.

우리 "온디바이스 음성 색상 변경기" 데모에서는, 이 문제를 해결하기 위해 가중치를 줄 문구들과 그 가중치 값을 담은 배열을 먼저 만들었습니다.

const phraseData = [
  { phrase: "azure", boost: 5.0 },
  { phrase: "khaki", boost: 3.0 },
  { phrase: "tan", boost: 2.0 },
];

하지만 이걸 그대로 던져줄 순 없어요. 이것들은 SpeechRecognitionPhrase 객체들로 이루어진 ObservableArray 형태여야만 합니다. 그래서 우리는 자바스크립트의 map() 메서드를 사용해서 원본 배열의 요소들을 순회하며, SpeechRecognitionPhrase() 생성자를 통해 완벽한 객체 형태로 하나하나 변환해 주었습니다.

const phraseObjects = phraseData.map(
  (p) => new SpeechRecognitionPhrase(p.phrase, p.boost),
);

이제 SpeechRecognition 인스턴스를 만들고 나서, 방금 뚝딱 만들어낸 phraseObjects 배열을 SpeechRecognition.phrases 속성의 값으로 할당해 주면 끝입니다! 이로써 문맥적 편향(힌트 주기) 세팅이 완료되었습니다.

recognition.phrases = phraseObjects;

이렇게 세팅된 phrases 배열은 일반적인 자바스크립트 배열 다루듯이 수정할 수도 있습니다. 예를 들어 런타임에 동적으로 새로운 문구를 쏙 밀어 넣고(push) 싶다면 이렇게 하시면 됩니다.

recognition.phrases.push(new SpeechRecognitionPhrase("thistle", 5.0));

이 코드를 적용하고 나니, 속을 썩이던 색상 키워드들이 이전보다 훨씬 더 정확하게 쏙쏙 인식되는 것을 확인할 수 있었습니다.


음성 합성 (Speech synthesis)

음성 합성(text-to-speech, 또는 간단히 TTS라고 하죠)은 앱 내에 존재하는 텍스트 데이터를 받아서 음성으로 변환하고, 그것을 기기의 스피커나 오디오 출력 장치를 통해 실제로 재생해 주는 기능을 말합니다.

Web Speech API는 이를 위해 메인 컨트롤러 인터페이스인 SpeechSynthesis를 가지고 있고, 여기에 덧붙여서 합성할 텍스트 내용을 나타내는 인터페이스(이를 '발화(utterance)'라고 부릅니다), 발화에 사용할 목소리(voice)를 나타내는 인터페이스 등 여러 관련 인터페이스들을 밀접하게 묶어서 제공하고 있습니다. 이번에도 마찬가지로, 운영체제(OS)가 내장하고 있는 음성 합성 시스템이 이 작업을 수행하는 데 주로 사용됩니다.

데모 (Demo)

웹 음성 합성을 어떻게 사용하는지 보여드리기 위해 Speech synthesizer (음성 합성기)라는 샘플 앱을 만들었습니다. 이 앱은 합성할 텍스트를 입력할 수 있는 텍스트 입력창 하나를 가지고 있어요. 슬라이더를 움직여서 목소리의 말하는 속도(rate)와 음의 높낮이(pitch)를 조절할 수 있고, 드롭다운 메뉴를 클릭해서 텍스트를 읽어줄 목소리 종류도 선택할 수 있답니다. 텍스트를 다 적으셨다면 키보드의 Enter/Return 키를 치거나 Play 버튼을 눌러보세요. 브라우저가 여러분이 쓴 글을 소리 내어 읽어줄 거예요!

Speak easy synthesis 앱의 UI. 텍스트를 입력하는 필드, 속도와 피치를 조절하는 슬라이더, 그리고 목소리를 선택하는 드롭다운 메뉴가 있습니다.

이 데모를 직접 실행해 보시려면, Web Speech API를 지원하는 브라우저에서 라이브 데모 URL로 접속해 보세요.

HTML과 CSS (HTML and CSS)

이 앱의 HTML과 CSS 역시 아주 기초적입니다. 제목이 하나 있고, 간단한 사용 설명이 적힌 문단, 그리고 몇 가지 기본 컨트롤 요소들이 들어있는 폼(form) 하나가 전부죠. 가장 핵심이 되는 목소리 선택용 드롭다운 <select> 요소는 처음에는 텅 비어 있습니다. 이곳에는 나중에 자바스크립트를 통해 사용할 수 있는 목소리 목록들(<option>)이 동적으로 채워지게 될 것입니다.

<h1>Speech synthesizer</h1>

<p>
  Enter some text in the input below and press return to hear it. Change voices
  using the dropdown menu.
</p>

<form>
  <input type="text" class="txt" />
  <div>
    <label for="rate">Rate</label
    ><input type="range" min="0.5" max="2" value="1" step="0.1" id="rate" />
    <div class="rate-value">1</div>
    <div class="clearfix"></div>
  </div>
  <div>
    <label for="pitch">Pitch</label
    ><input type="range" min="0" max="2" value="1" step="0.1" id="pitch" />
    <div class="pitch-value">1</div>
    <div class="clearfix"></div>
  </div>
  <select></select>
</form>

자바스크립트 (JavaScript)

자, 이제 이 앱에 생명을 불어넣는 자바스크립트를 찬찬히 분석해 봅시다.

변수 설정하기 (Setting variables)

무엇보다 먼저, UI에 엮여있는 모든 DOM 요소들의 참조(reference)를 가져와서 변수에 담아줍니다. 하지만 여기서 가장 주목해야 할 흥미로운 부분은 바로 Window.speechSynthesis에 대한 참조를 가져오는 첫 번째 줄입니다. 이것이 바로 이 API로 들어가는 관문(entry point)입니다. 이 녀석을 호출하면 웹 음성 합성을 총괄하는 컨트롤러 인터페이스인 SpeechSynthesis의 인스턴스가 반환됩니다!

const synth = window.speechSynthesis;

const inputForm = document.querySelector("form");
const inputTxt = document.querySelector(".txt");
const voiceSelect = document.querySelector("select");

const pitch = document.querySelector("#pitch");
const pitchValue = document.querySelector(".pitch-value");
const rate = document.querySelector("#rate");
const rateValue = document.querySelector(".rate-value");

const voices = [];

select 요소 채워 넣기 (Populating the select element)

기기에서 지원하는 다양한 목소리 옵션들로 아까 비워뒀던 <select> 요소를 채우기 위해, 우리는 populateVoiceList()라는 함수를 만들었습니다.

이 함수는 먼저 SpeechSynthesis.getVoices() 메서드를 호출하는데요, 이 메서드는 사용 가능한 모든 목소리들의 리스트를 (각각 SpeechSynthesisVoice 객체 형태로) 반환해 줍니다.
그다음 우리는 이 반환된 리스트를 빙글빙글 도는 루프(loop)를 돌면서, 각각의 목소리마다 <option> 요소를 새롭게 찍어냅니다. 이 옵션의 글자 내용(text content)에는 목소리의 이름(SpeechSynthesisVoice.name)과 언어(SpeechSynthesisVoice.lang)가 들어가게 세팅합니다. 그리고 만약 이 목소리가 해당 합성 엔진의 기본(default) 목소리라면 (SpeechSynthesisVoice.defaulttrue를 반환하는지 체크해서), 이름 뒤에 -- DEFAULT라는 멋진 꼬리표도 붙여줍니다.

마지막으로, 나중에 사용자가 어떤 옵션을 선택했는지 쉽게 찾아서 꺼내 쓸 수 있도록 각각의 옵션 태그에 data-namedata-lang 속성을 만들어 값을 심어준 뒤, 이 완성된 옵션 요소들을 <select> 태그의 자식으로 찰싹 붙여(append) 줍니다.

function populateVoiceList() {
  voices = synth.getVoices();

  for (const voice of voices) {
    const option = document.createElement("option");
    option.textContent = `${voice.name} (${voice.lang})`;

    if (voice.default) {
      option.textContent += " — DEFAULT";
    }

    option.setAttribute("data-lang", voice.lang);
    option.setAttribute("data-name", voice.name);
    voiceSelect.appendChild(option);
  }
}

💡 강사의 팁: 브라우저에 따라 음성 목록(voices)이 비동기적으로 로딩되는 경우가 있습니다. 페이지가 열리자마자 바로 getVoices()를 호출하면 빈 배열만 돌아오는 브라우저가 있어요! 그래서 이어지는 내용처럼 이벤트를 활용해 처리하는 것이 매우 중요합니다.

구형 브라우저들은 voiceschanged 이벤트를 지원하지 않고, 그냥 SpeechSynthesis.getVoices()가 실행될 때 곧바로 목소리 목록을 반환해버립니다. 반면 크롬(Chrome) 같은 최신 브라우저들에서는 이 voiceschanged 이벤트가 발동될 때까지 기다렸다가 목록을 채워 넣어야만 안전하게 목소리들을 가져올 수 있습니다.
이 두 가지 케이스를 모두 아우르기 위해, 우리는 함수를 다음과 같은 패턴으로 실행합니다.

populateVoiceList();
if (speechSynthesis.onvoiceschanged !== undefined) {
  speechSynthesis.onvoiceschanged = populateVoiceList;
}

입력된 텍스트 말하기 (Speaking the entered text)

이제 텍스트 입력창에 적힌 글씨를 말하기 시작하는 이벤트 핸들러를 만들 차례입니다. 우리는 <form> 요소에 onsubmit 핸들러를 달아서, 사용자가 Enter/Return 키를 쳤을 때 폼이 제출되면서 이 액션이 발생하도록 만들겠습니다.

가장 먼저, SpeechSynthesisUtterance() 생성자를 사용해 새로운 '발화(Utterance)' 인스턴스를 하나 뚝딱 만듭니다. 이때 생성자의 파라미터로 텍스트 입력창에 적힌 값(inputTxt.value)을 스윽 넘겨줍니다.

그다음엔 어떤 목소리로 읽게 할지 골라야겠죠? <select> 태그(HTMLSelectElement)의 selectedOptions 속성을 이용해 현재 선택된 <option> 요소를 낚아챕니다. 그리고 아까 우리가 정성껏 심어둔 이 요소의 data-name 속성값을 가져옵니다. 이제 우리가 가진 전체 voices 배열을 순회하면서, 이름이 이 속성값과 정확히 일치하는 SpeechSynthesisVoice 객체를 찾아냅니다. 일치하는 녀석을 찾았다면, 그걸 아까 만든 발화 객체의 목소리 속성(SpeechSynthesisUtterance.voice)에 장착해 줍니다!

마지막으로, 높낮이(SpeechSynthesisUtterance.pitch)와 속도(SpeechSynthesisUtterance.rate) 값도 화면에 있는 range 슬라이더의 현재 값으로 세팅해 줍니다.
자, 이렇게 모든 준비가 끝났습니다! 이제 SpeechSynthesis.speak() 메서드를 호출하면서 이 알차게 셋팅된 발화 객체를 넘겨주면, 브라우저가 신나게 떠들기 시작할 겁니다.

inputForm.onsubmit = (event) => {
  event.preventDefault(); // 폼 제출로 인한 페이지 새로고침 방지

  const utterThis = new SpeechSynthesisUtterance(inputTxt.value);
  const selectedOption =
    voiceSelect.selectedOptions[0].getAttribute("data-name");
  for (const voice of voices) {
    if (voice.name === selectedOption) {
      utterThis.voice = voice;
    }
  }
  utterThis.pitch = pitch.value;
  utterThis.rate = rate.value;
  synth.speak(utterThis);

  utterThis.onpause = (event) => {
    const char = event.utterance.text.charAt(event.charIndex);
    console.log(
      `Speech paused at character ${event.charIndex} of "${event.utterance.text}", which is "${char}".`,
    );
  };
  inputTxt.blur();
};

핸들러의 마지막 부분을 보시면 pause 이벤트를 포함시킨 것을 볼 수 있는데, 이는 SpeechSynthesisEvent 객체가 얼마나 유용하게 쓰일 수 있는지 보여주기 위함입니다. 만약 누군가 SpeechSynthesis.pause()를 호출해서 말을 중간에 끊어버린다면, 이 이벤트가 발동하면서 멈춘 위치의 글자 인덱스(character number)와 정확히 어떤 글자에서 멈췄는지를 콘솔에 보고해 줍니다.

마지막의 inputTxt.blur()는 텍스트 입력창에서 포커스를 없애버리는 코드인데요, 이건 주로 Firefox OS 같은 모바일 환경에서 폼 제출 후에 튀어나와 있는 가상 키보드를 쏙 숨기기 위한 센스 있는 처리랍니다.

화면에 표시되는 피치와 속도 값 업데이트하기 (Updating the displayed pitch and rate values)

코드의 맨 마지막 조각은 사용자가 피치(pitch)와 속도(rate) 슬라이더를 움직일 때마다, 그 숫자값을 UI 상에 실시간으로 업데이트해서 보여주는 역할을 합니다. 간단하지만 사용자 경험(UX)을 크게 올려주는 요소죠!

pitch.onchange = () => {
  pitchValue.textContent = pitch.value;
};

rate.onchange = () => {
  rateValue.textContent = rate.value;
};

자, 여기까지 Web Speech API를 활용해 음성을 듣고, 말하게 하는 멋진 기능들을 샅샅이 파헤쳐보았습니다! 브라우저가 이렇게 사람과 대화하듯 소통할 수 있다는 게 정말 신기하지 않나요? 이 지식들을 바탕으로 여러분만의 톡톡 튀는 아이디어를 더해 재미있는 프로젝트를 만들어보시길 응원합니다! 코딩하시다가 막히는 부분이 생기면 언제든 다시 찾아와 주세요. 화이팅! 🚀

profile
프론트에_가까운_풀스택_개발자

0개의 댓글