sitelink1 https://developer.mozilla.org/en-US/docs...hSynthesis 
sitelink2 https://developer.mozilla.org/en-US/docs...Speech_API 
sitelink3  
sitelink4  
extra_vars5  
extra_vars6  
  • 기술 키워드
    • SpeechSynthesis(Text-to-Speech)
    • SpeechRecognition(Asynchronous Speech Recognition.)
  • 샘플 다운로드 : stt_tts.7z

* 2025.09.08 STT_TTS 샘플 추가 (from ChatGPT)

<!doctype html>

<html lang="ko">

<head>

<meta charset="utf-8" />

<meta name="viewport" content="width=device-width,initial-scale=1" />

<title>STT + TTS Sample (webkitSpeechRecognition + speechSynthesis)</title>

<style>

  body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans KR", "Apple SD Gothic Neo", "Malgun Gothic", sans-serif; max-width: 900px; margin: 28px auto; padding: 0 18px; color:#111; }

  h1 { font-size: 1.4rem; margin-bottom: 8px; }

  .card { border-radius: 12px; padding: 16px; box-shadow: 0 6px 18px rgba(0,0,0,0.06); margin-bottom: 16px; background: #fff; }

  label { display:block; margin:8px 0 4px; font-weight:600; }

  button { padding: 8px 12px; margin-right:8px; border-radius:8px; border:1px solid #ddd; background:#f7f7f7; cursor:pointer; }

  button.primary { background:#2563eb; color:white; border-color:#2563eb; }

  textarea { width:100%; min-height:90px; padding:8px; border-radius:8px; border:1px solid #ddd; resize:vertical; }

  pre { white-space:pre-wrap; word-break:break-word; background:#f8f9fb; padding:12px; border-radius:8px; border:1px solid #eee; }

  .controls { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }

  .small { font-size:0.9rem; color:#555; }

</style>

</head>

<body>

  <h1>브라우저 STT + TTS 샘플</h1>

 

  <div class="card">

    <strong>설명</strong>

    <p class="small">왼쪽은 마이크로부터 음성을 받아 텍스트로 변환(STT), 오른쪽은 텍스트를 음성으로 재생(TTS)합니다. Chrome 기반 브라우저에서 웹 API를 사용합니다.</p>

  </div>

 

  <div class="card" id="sttCard">

    <h2>▶ STT (Speech-to-Text)</h2>

    <div class="controls">

      <button id="startRec" class="primary">녹음 시작</button>

      <button id="stopRec">중지</button>

      <label style="margin-left:8px;">

        <input type="checkbox" id="continuous" checked /> 연속 인식

      </label>

      <label>

        <input type="checkbox" id="interim" checked /> 중간 결과 보여주기(interim)

      </label>

    </div>

 

    <label>인식 결과 (최종 + 중간):</label>

    <pre id="transcript" aria-live="polite"></pre>

 

    <label>상태:</label>

    <div id="sttStatus" class="small">초기화 대기</div>

 

    <div style="margin-top:10px" class="small">

      <strong>참고:</strong> 브라우저 권한(마이크 허용)이 필요합니다. Safari/Firefox/Edge는 구현 차이가 있을 수 있습니다.

    </div>

  </div>

 

  <div class="card" id="ttsCard">

    <h2>▶ TTS (Text-to-Speech)</h2>

 

    <label for="ttsText">읽을 텍스트:</label>

    <textarea id="ttsText">안녕하세요. 이것은 브라우저 기반 TTS 테스트입니다.</textarea>

 

    <label>음성 선택:</label>

    <select id="voiceSelect"></select>

 

    <div class="controls" style="margin-top:8px">

      <label>속도: <input id="rate" type="range" min="0.5" max="2" value="1" step="0.1"></label>

      <label>높낮이: <input id="pitch" type="range" min="0" max="2" value="1" step="0.1"></label>

      <label>볼륨: <input id="volume" type="range" min="0" max="1" value="1" step="0.1"></label>

    </div>

 

    <div style="margin-top:10px">

      <button id="speakBtn" class="primary">읽기</button>

      <button id="cancelBtn">중지</button>

    </div>

 

    <div style="margin-top:12px" class="small">

      <strong>브라우저 음성 목록:</strong> 아래에서 음성을 선택하세요. (한국어 음성이 없으면 시스템/브라우저에 따라 보이지 않을 수 있음)

    </div>

  </div>

 

  <script>

  // --------------------

  // STT (webkitSpeechRecognition) 설정

  // --------------------

  const transcriptEl = document.getElementById('transcript');

  const sttStatus = document.getElementById('sttStatus');

  const startRecBtn = document.getElementById('startRec');

  const stopRecBtn = document.getElementById('stopRec');

  const continuousCheckbox = document.getElementById('continuous');

  const interimCheckbox = document.getElementById('interim');

 

  // 브라우저 호환 처리: 표준 SpeechRecognition 이 있으면 사용, 없으면 webkitSpeechRecognition

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

  let recognizer = null;

  let finalTranscript = '';

 

  if (!SpeechRecognition) {

    sttStatus.textContent = '이 브라우저는 SpeechRecognition API를 지원하지 않습니다.';

    startRecBtn.disabled = true;

    stopRecBtn.disabled = true;

  } else {

    recognizer = new SpeechRecognition();

    recognizer.lang = 'ko-KR'; // 기본 한국어

    recognizer.interimResults = true;

    recognizer.maxAlternatives = 1;

    recognizer.continuous = true;

 

    recognizer.onstart = () => {

      sttStatus.textContent = '음성 인식 시작 (마이크 사용 중) — 말하세요.';

    };

 

    recognizer.onerror = (e) => {

      sttStatus.textContent = '오류: ' + (e.error || JSON.stringify(e));

    };

 

    recognizer.onend = () => {

      sttStatus.textContent = '음성 인식이 중지되었습니다.';

    };

 

    recognizer.onresult = (event) => {

      let interim = '';

      for (let i = event.resultIndex; i < event.results.length; ++i) {

        const res = event.results[i];

        if (res.isFinal) {

          finalTranscript += res[0].transcript;

        } else {

          interim += res[0].transcript;

        }

      }

      transcriptEl.textContent = (finalTranscript + (interimCheckbox.checked ? ('\n(중간) ' + interim) : ''));

    };

  }

 

  startRecBtn.addEventListener('click', async () => {

    if (!recognizer) return;

    // 사용자 미디어 권한을 명시적으로 요청해 상태를 빨리 알 수 있게 함

    try {

      await navigator.mediaDevices.getUserMedia({ audio: true });

    } catch (err) {

      sttStatus.textContent = '마이크 접근 거부됨: ' + err.message;

      return;

    }

 

    finalTranscript = '';

    transcriptEl.textContent = '... listening ...';

    recognizer.interimResults = interimCheckbox.checked;

    recognizer.continuous = continuousCheckbox.checked;

    recognizer.lang = 'ko-KR';

    recognizer.start();

  });

 

  stopRecBtn.addEventListener('click', () => {

    if (!recognizer) return;

    recognizer.stop();

  });

 

  // --------------------

  // TTS (speechSynthesis) 설정

  // --------------------

  const voiceSelect = document.getElementById('voiceSelect');

  const ttsText = document.getElementById('ttsText');

  const speakBtn = document.getElementById('speakBtn');

  const cancelBtn = document.getElementById('cancelBtn');

  const rateInput = document.getElementById('rate');

  const pitchInput = document.getElementById('pitch');

  const volumeInput = document.getElementById('volume');

 

  function populateVoices() {

    const voices = speechSynthesis.getVoices();

    voiceSelect.innerHTML = '';

    voices.forEach((v, i) => {

      const opt = document.createElement('option');

      opt.value = i;

      opt.textContent = `${v.name}${v.lang}${v.default ? ' — default' : ''}`;

      voiceSelect.appendChild(opt);

    });

    if (voices.length === 0) {

      voiceSelect.innerHTML = '<option>사용 가능한 음성이 없습니다.</option>';

    }

  }

 

  // voiceschanged 이벤트는 음성 로딩이 완료되면 발생함

  window.speechSynthesis.onvoiceschanged = populateVoices;

  populateVoices();

 

  speakBtn.addEventListener('click', () => {

    if (!('speechSynthesis' in window)) {

      alert('이 브라우저는 speechSynthesis를 지원하지 않습니다.');

      return;

    }

    const text = ttsText.value.trim();

    if (!text) {

      alert('읽을 텍스트를 입력하세요.');

      return;

    }

    const utter = new SpeechSynthesisUtterance(text);

    const voices = speechSynthesis.getVoices();

    const idx = parseInt(voiceSelect.value, 10);

    if (!isNaN(idx) && voices[idx]) utter.voice = voices[idx];

 

    utter.rate = parseFloat(rateInput.value);

    utter.pitch = parseFloat(pitchInput.value);

    utter.volume = parseFloat(volumeInput.value);

 

    utter.onstart = () => { speakBtn.disabled = true; sttStatus.textContent = 'TTS 재생 중...'; };

    utter.onend = () => { speakBtn.disabled = false; sttStatus.textContent = 'TTS 재생 완료.'; };

    utter.onerror = (e) => { speakBtn.disabled = false; sttStatus.textContent = 'TTS 오류: ' + e.error; };

 

    // 재생 전에 큐 비우기(원하면 주석 처리 가능)

    window.speechSynthesis.cancel();

    window.speechSynthesis.speak(utter);

  });

 

  cancelBtn.addEventListener('click', () => {

    window.speechSynthesis.cancel();

    sttStatus.textContent = 'TTS 재생 취소됨.';

    speakBtn.disabled = false;

  });

 

  // 키보드 단축(예: Ctrl+Enter -> 읽기)

  ttsText.addEventListener('keydown', (e) => {

    if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {

      speakBtn.click();

    }

  });

  </script>

</body>

</html>

 

* 아래의 예시는 2025.04.14 에 Copilot 이 만들어준 STT 의 샘플인데 너무 잘 작동한다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Speech API Example</title>
</head>
<body>
    <h1>Web Speech API Example</h1>
    <button id="start-recognition">Start Recognition</button>
    <p id="output">Speech will appear here...</p>
      <script>
        const startButton = document.getElementById('start-recognition');
        const output = document.getElementById('output');
          // Check if the browser supports Web Speech API
        if (!('webkitSpeechRecognition' in window)) {
            output.textContent = 'Web Speech API is not supported in this browser.';
        } else {
            const recognition = new webkitSpeechRecognition();
            recognition.lang = 'ko-KR'; // Set language
            recognition.interimResults = false; // Show only final results
            recognition.maxAlternatives = 1;
              startButton.addEventListener('click', () => {
                recognition.start();
                output.textContent = 'Listening...';
            });
              recognition.onresult = (event) => {
                const transcript = event.results[0][0].transcript;
                output.textContent = `You said: ${transcript}`;
            };
              recognition.onerror = (event) => {
                output.textContent = `Error occurred: ${event.error}`;
            };
              recognition.onend = () => {
                output.textContent += ' (Recognition ended)';
            };
        }
    </script>

 

번호 제목 글쓴이 날짜 조회 수
» Web Speech API Example (HTML5 STT & TTS) file 황제낙엽 2025.04.14 245
127 모바일과 데스크톱을 포괄하는 사용자의 터치 이벤트(onPointerTap) 황제낙엽 2025.04.09 191
126 Web RTC 에 관하여 황제낙엽 2024.11.20 256
125 div와 span의 차이점 (from Bing) 황제낙엽 2023.06.16 251
124 flexbox (flexible box layout) 황제낙엽 2023.02.27 228
123 characters from ISO 8859-1 황제낙엽 2020.11.10 33247
122 HTML FRAMESET 태그 예제 황제낙엽 2020.11.06 278
121 스마트 기기에서 여러 단말기의 해상도에 대응하기 위해 사용하는 HTML viewport meta tag 황제낙엽 2020.11.06 297
120 네이버의 무료 나눔 글꼴 황제낙엽 2020.05.06 1372
119 서버의 이미지나 txt 파일을 웹브라우저에서 다운로드하기 황제낙엽 2019.05.13 419
118 When to encode space to plus (+) or %20? (application/x-www-form-urlencoded) 황제낙엽 2019.04.15 462
117 HTTP/2 소개 황제낙엽 2018.10.12 284
116 글자, 문자, 텍스트, 글꼴, 서체, 폰트와 관련한 속성들 / font-size , 크기 단위(em, px, pt, rem), 절대 단위, 상대 단위 황제낙엽 2018.04.18 411
115 Pragma와 Cache-Control 황제낙엽 2018.03.28 380
114 로드밸런싱(L4)+아파치를 운영시 etag제거로 캐시 성능 최적화 file 황제낙엽 2018.03.28 509
113 HTTP 요청 헤더 III - If-Match, If-Modified-Since, If-None-Match file 황제낙엽 2018.03.27 302
112 재미난 로그인 페이지 만들기 file 황제낙엽 2018.03.26 443
111 HTTP Cache-Control 요청 지시어 황제낙엽 2018.03.09 305
110 내 웹사이트의 속도를 빠르게! file 황제낙엽 2018.03.07 426
109 성능을 위한 초간단 HTTP 304 Not Modified 구현 방법 file 황제낙엽 2018.03.07 342