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
56 모바일과 데스크톱을 포괄하는 사용자의 터치 이벤트(onPointerTap) 황제낙엽 2025.04.09 192
55 Web RTC 에 관하여 황제낙엽 2024.11.20 256
54 HTML FRAMESET 태그 예제 황제낙엽 2020.11.06 278
53 네이버의 무료 나눔 글꼴 황제낙엽 2020.05.06 1373
52 HTTP/2 소개 황제낙엽 2018.10.12 284
51 Pragma와 Cache-Control 황제낙엽 2018.03.28 380
50 로드밸런싱(L4)+아파치를 운영시 etag제거로 캐시 성능 최적화 file 황제낙엽 2018.03.28 511
49 재미난 로그인 페이지 만들기 file 황제낙엽 2018.03.26 443
48 내 웹사이트의 속도를 빠르게! file 황제낙엽 2018.03.07 426
47 성능을 위한 초간단 HTTP 304 Not Modified 구현 방법 file 황제낙엽 2018.03.07 342
46 [MIME type/content type/media type] text/javascript와 application/javascript의 차이점 황제낙엽 2017.11.23 448
45 User Agent 에 관련된 링크 황제낙엽 2017.11.20 1050
44 [보안] 혼합 콘텐츠(Mixed Content) 방지 황제낙엽 2017.04.13 904
43 <img> image 엘리먼트에서 이미지를 base64로 인코딩해서 사용하기 file 황제낙엽 2017.04.01 1266
42 [ActiveX] CAB파일 수동 설치(레지스트리 등록) 방법 황제낙엽 2017.03.16 3679
41 ActiveX 에서 CLASSID 가 맞지 않을때의 현상 황제낙엽 2017.02.17 483
40 HTML5 강좌 23강 - 위치 정보(Geolocation API), 지도 서비스 file 황제낙엽 2016.12.03 494
39 HTML5 강좌 22강 - 파일접근, 파일정보 file 황제낙엽 2016.12.03 355
38 HTML5 강좌 21강 - 웹 스토리지 file 황제낙엽 2016.12.03 275