첫 번째 음성 구동 웹 응용 프로그램 구축

얼마 전, 나의 목표는 웹 응용 프로그램을 실현하여 내가 말한 내용을 자동으로 작성하고 음성 명령을 통해 조작할 수 있도록 하는 것이다.나는 응용 프로그램의 피드백을 들을 수 있는 방법을 제공하는 것도 좋은 생각이라고 생각한다.빠른 연구 끝에 나는 이 문제를 해결하기 위해 몇 개의 웹 API를 발견했다.
본고에서 현대 웹 API를 사용하여 웹 응용 프로그램과 대화하는 좋은 기능을 추가하는 방법을 설명하고 응답할 수 있도록 하겠습니다.우리는 처음부터 이 응용 프로그램을 실시할 것이다.

API란 무엇입니까?


API는 응용 프로그램 프로그래밍 인터페이스의 이니셜입니다.MDN 웹 사이트:

APIs are constructs made available in programming languages to allow developers to create complex functionality more easily.


간단히 말하면 API는 복잡한 응용 프로그램을 만들 수 있는 방법을 제공하며 세부적인 것을 배우거나 실현할 필요가 없다.

API 그림 - 카메라 예

Web API


당신은 일찍이fetch나 서비스 직원을 사용한 적이 있습니까?JavaScript의 DOM 를 사용하거나 액세스하셨습니까?
음, 이러한 기능을 바탕으로 복잡한 임무를 완성할 수 있습니다. 왜냐하면 이것은 Web APIs의 광범위한 목록의 일부이기 때문입니다.이러한 API는 JavaScript의 일부는 아니지만 이러한 프로그래밍 언어 (또는 다른 JavaScript 기반 라이브러리/프레임워크) 를 통해 사용할 수 있습니다.
다른 한편, 웹 API 기반 응용 프로그램을 구축하기 전에 웹 브라우저가 웹 API를 완전히 지원하는지 확인해야 할 수도 있습니다.예를 들어 fetch를 사용할 계획이라면 어떤 브라우저나 자바스크립트 엔진support it을 볼 수 있습니다.

웹 음성 API



웹 음성 API를 통한 음성 인식
위의 그림과 같이 이 웹 API는 다음과 같은 기능을 지원합니다.
  • 텍스트 출력에 음성 생성
  • 음성인식을 입력으로 사용
  • 연속 받아쓰기 지원 (전체 편지를 쓸 수 있음)
  • 웹 브라우저의 제어 인터페이스
  • 자세한 내용은 Web Speech API 사양을 참조하십시오.

    음성 합성 인터페이스



    SpeechSynthesis 인터페이스를 통해 텍스트를 음성으로 변환
    너는 위의 그림에서 이 생각을 얻었다.웹 음성 합성 인터페이스는 텍스트를 음성 출력으로 생성할 수 있다.
    이 인터페이스에 대한 자세한 내용은 specification 를 참조하십시오.

    비디오 보기


    데모: 음성 기반 웹 애플리케이션

    웹 응용 프로그램 구현


    이 프로그램은 HTML, CSS 및 TypeScript를 프로그래밍 언어로 사용합니다.우리는 각도 재료 어셈블리를 통해 최신 각도 버전을 사용할 것입니다.
    그 밖에 우리는 각도에서 관찰하고 비동기적인 파이프를 사용하는 반응식 프로그래밍 방법을 정의할 것이다.마지막으로, 우리는 전략 모델의 실현과 기타 기능을 제공할 것이다.

    항목 만들기


    최신 Angular CLI 웹 응용 프로그램을 처음부터 만듭니다.
    ng new web-speech-angular --routing --style css --prefix wsa --strict
    
  • --routing: 프로젝트의 루트 모듈을 생성합니다.
  • --style: 스타일 파일의 파일 확장자입니다.
  • --prefix: 구성 요소 선택기의 접두사 설정
  • --strict: Angular 10 에서 얻을 수 있습니다.더욱 엄격한 유형 검사와 최적화 옵션 생성을 사용합니다.
  • 각도 재료 추가


    각도 재료를 추가하는 것은 간단합니다.
    ng add @angular/material
    
    이제 Overall structural guidelines 각도에서 sharedmaterial 모듈을 생성할 수 있습니다.
    ng generate module shared --module app
    ng generate module shared/material --module shared
    
    이러한 명령은 프로젝트에서 다음 구조를 생성합니다.
    |- src/
        |- app/
            |- shared/
                |- material/
                    |- material.module.ts
                |- shared.module.ts
    

    웹 음성 모듈 추가


    응용 프로그램 컨트롤을 표시하는 데 필요한 구성 요소를 정의할 새 모듈을 추가할 때입니다.
    ng generate module web-speech --module app
    ng generate component web-speech
    
    이제 다음과 같은 구조가 제공됩니다.
    |- src/
        |- app/
            |- shared/
            |- web-speech/
                |- web-speech.module.ts
                |- web-speech.component.ts|html|css
    

    웹 API 디렉토리 추가


    우리가 사용할 웹 API와 관련된 서비스를 그룹화하는 새 폴더를 만듭니다.또한 새로운 서비스가 지원하는 언어, 알림, 오류, 이벤트에 대해 TypeScript 파일을 정의합니다.
    ng generate service shared/services/web-apis/speech-recognizer
    
    이전 명령을 실행하고 모델 파일을 생성한 후 구조는 다음과 같습니다.
    |- src/
        |- app/
            |- shared/
                |- shared.module.ts
                |- services/
                    |- web-apis/
                        |- speech-recognizer.service.ts
                |- model/
                    |- languages.ts
                    |- speech-error.ts
                    |- speech-event.ts
                    |- speech-notification.ts
            |- web-speech/
                |- web-speech.module.ts
                |- web-speech.component.ts|html|css
    

    알림, 이벤트 및 오류 모델링


    현재 규범은 JavaScript로 작성되었기 때문에 유형을 이용하기 위해 TypeScript 코드를 제공할 수 있습니다.이 항목은 TypeScript 활성화strict 모드로 구성되어 있기 때문에 더욱 중요합니다.
    // languages.ts
    export const languages = ['en-US', 'es-ES'];
    export const defaultLanguage = languages[0];
    
    // speech-error.ts
    export enum SpeechError {
      NoSpeech = 'no-speech',
      AudioCapture = 'audio-capture',
      NotAllowed = 'not-allowed',
      Unknown = 'unknown'
    }
    
    // speech-event.ts
    export enum SpeechEvent {
      Start,
      End,
      FinalContent,
      InterimContent
    }
    
    // speech-notification.ts
    export interface SpeechNotification<T> {
        event?: SpeechEvent;
        error?: SpeechError;
        content?: T;
    }
    
    주의 SpeechError 매거.문자열 키는 SpeechRecognitionErrorEvent 규범의 실제 값과 일치합니다.

    SpeechRecognizer Service 생성(비동기식 음성인식)


    주요 목표는 애플리케이션에 필요한 기능의 추상성을 정의하는 것입니다.
  • 정의SpeechRecognizerService의 기본 설정(구글 브라우저 지원webkitSpeechRecognition 실례).
  • 언어 구성을 정의합니다.

  • 중기와 최종 결과를 포착하다.
  • 인식기 서비스를 시작하고 중지할 수 있습니다.
  • 다음 코드는 이러한 요구 사항을 실현합니다.
    // speech-recognizer.service.ts
    @Injectable({
      providedIn: 'root',
    })
    export class SpeechRecognizerService {
      recognition: SpeechRecognition;
      language: string;
      isListening = false;
    
      constructor() {}
    
      initialize(language: string): void {
        this.recognition = new webkitSpeechRecognition();
        this.recognition.continuous = true;
        this.recognition.interimResults = true;
        this.setLanguage(language);
      }
    
      setLanguage(language: string): void {
        this.language = language;
        this.recognition.lang = language;
      }
    
      start(): void {
        this.recognition.start();
        this.isListening = true;
      }
    
      stop(): void {
        this.recognition.stop();
      }
    }
    
    이제 반응식 프로그래밍을 위한 API를 제공하여 관찰할 수 있는 연속 데이터 흐름을 사용할 때가 되었다.이것은 사용자가 끊임없이 말을 할 때 추정된 텍스트를 포착하는 데 도움이 될 것이다. (우리는 매번 값을 끌어와서 새로운 내용이 있는지 확인할 필요가 없다.)
    export class SpeechRecognizerService {
      // previous implementation here...
    
      onStart(): Observable<SpeechNotification<never>> {
        if (!this.recognition) {
          this.initialize(this.language);
        }
    
        return new Observable(observer => {
          this.recognition.onstart = () => observer.next({
            event: SpeechEvent.Start
          });
        });
      }
    
      onEnd(): Observable<SpeechNotification<never>> {
        return new Observable(observer => {
          this.recognition.onend = () => {
            observer.next({
              event: SpeechEvent.End
            });
            this.isListening = false;
          };
        });
      }
    
      onResult(): Observable<SpeechNotification<string>> {
        return new Observable(observer => {
          this.recognition.onresult = (event: SpeechRecognitionEvent) => {
            let interimContent = '';
            let finalContent = '';
    
            for (let i = event.resultIndex; i < event.results.length; ++i) {
              if (event.results[i].isFinal) {
                finalContent += event.results[i][0].transcript;
                observer.next({
                  event: SpeechEvent.FinalContent,
                  content: finalContent
                });
              } else {
                interimContent += event.results[i][0].transcript;
                observer.next({
                  event: SpeechEvent.InterimContent,
                  content: interimContent
                });
              }
            }
          };
        });
      }
    
      onError(): Observable<SpeechNotification<never>> {
        return new Observable(observer => {
          this.recognition.onerror = (event) => {
            const eventError: string = (event as any).error;
            let error: SpeechError;
            switch (eventError) {
              case 'no-speech':
                error = SpeechError.NoSpeech;
                break;
              case 'audio-capture':
                error = SpeechError.AudioCapture;
                break;
              case 'not-allowed':
                error = SpeechError.NotAllowed;
                break;
              default:
                error = SpeechError.Unknown;
                break;
            }
    
            observer.next({
              error
            });
          };
        });
      }  
    }
    
    앞에서 살펴볼 수 있는 객체로 되돌아오는 패키지 함수를 작성하여 다음 이벤트 처리 프로그램을 관리하고 있습니다.
    recognition.onstart = function() { ... }
    recognition.onend = function() { ... }
    recognition.onresult = function(event) { ... }
    recognition.onerror = function(event) { ... }
    
    이러한 함수가 어떻게 작동하는지 잘 이해하기 위해서는 SpeechRecognition Events, SpeechRecognitionResult, SpeechRecognitionErrorEvent의 API 규범을 참조하십시오.

    WebSpeechComponent 사용


    우리는 이미 SpeechRecognizerService 있기 때문에, 지금은 각도 분량을 정의할 때이다.
    // web-speech-component.ts
    import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
    import { merge, Observable, Subject } from 'rxjs';
    import { map, tap } from 'rxjs/operators';
    import { defaultLanguage, languages } from '../shared/model/languages';
    import { SpeechError } from '../shared/model/speech-error';
    import { SpeechEvent } from '../shared/model/speech-event';
    import { SpeechRecognizerService } from '../shared/web-apis/speech-recognizer.service';
    
    @Component({
      selector: 'wsa-web-speech',
      templateUrl: './web-speech.component.html',
      styleUrls: ['./web-speech.component.css'],
      changeDetection: ChangeDetectionStrategy.OnPush,
    })
    export class WebSpeechComponent implements OnInit {
      languages: string[] = languages;
      currentLanguage: string = defaultLanguage; // Set the default language
      totalTranscript: string; // The variable to accumulate all the recognized texts
    
      transcript$: Observable<string>; // Shows the transcript in "real-time"
      listening$: Observable<boolean>; // Changes to 'true'/'false' when the recognizer starts/stops
      errorMessage$: Observable<string>; // An error from the Speech Recognizer
      defaultError$ = new Subject<undefined>(); // Clean-up of the previous errors
    
      constructor(private speechRecognizer: SpeechRecognizerService) {}
    
      ngOnInit(): void {
        // Initialize the speech recognizer with the default language
        this.speechRecognizer.initialize(this.currentLanguage);
        // Prepare observables to "catch" events, results and errors.
        this.initRecognition();
      }
    
      start(): void {
        if (this.speechRecognizer.isListening) {
          this.stop();
          return;
        }
    
        this.defaultError$.next(undefined);
        this.speechRecognizer.start();
      }
    
      stop(): void {
        this.speechRecognizer.stop();
      }
    
      selectLanguage(language: string): void {
        if (this.speechRecognizer.isListening) {
          this.stop();
        }
        this.currentLanguage = language;
        this.speechRecognizer.setLanguage(this.currentLanguage);
      }
    }
    
    기본적으로 앞의 코드는 구현할 주요 속성과 함수를 정의하는 방법을 보여 줍니다.
  • 음성인식 언어를 전환할 수 있습니다.
  • Speech Recognitor가 언제 "듣고"있는지 알 수 있습니다.
  • 구성 요소 상하문에서 음성인식기를 시작하고 정지할 수 있습니다.
  • 현재의 문제는 우리가 어떻게 기록(사용자가 텍스트에서 말한 내용)을 얻고 음성 서비스가 언제 듣고 있는지 어떻게 알 수 있느냐는 것이다.또한 마이크나 API 자체에 오류가 있는지 어떻게 알 수 있습니까?
    정답은 사용SpeechRecognizerService의 관찰치입니다.우리는subscribe를 사용하지 않고 서비스에서 관찰할 수 있는 대상을 얻고 분배합니다. 이따가 템플릿의 비동기 파이프를 통해 관찰할 수 있는 대상을 사용합니다.
    // web-speech.component.ts
    export class WebSpeechComponent implements OnInit {
      // Previous code here...
      private initRecognition(): void {
    
        // "transcript$" now will receive every text(interim result) from the Speech API.
        // Also, for every "Final Result"(from the speech), the code will append that text to the existing Text Area component.
        this.transcript$ = this.speechRecognizer.onResult().pipe(
          tap((notification) => {
            if (notification.event === SpeechEvent.FinalContent) {
              this.totalTranscript = this.totalTranscript
                ? `${this.totalTranscript}\n${notification.content?.trim()}`
                : notification.content;
            }
          }),
          map((notification) => notification.content || '')
        );
    
      // "listening$" will receive 'true' when the Speech API starts and 'false' when it's finished.
        this.listening$ = merge(
          this.speechRecognizer.onStart(),
          this.speechRecognizer.onEnd()
        ).pipe(
          map((notification) => notification.event === SpeechEvent.Start)
        );
    
      // "errorMessage$" will receive any error from Speech API and it will map that value to a meaningful message for the user
        this.errorMessage$ = merge(
          this.speechRecognizer.onError(),
          this.defaultError$
        ).pipe(
          map((data) => {
            if (data === undefined) {
              return '';
            }
            let message;
            switch (data.error) {
              case SpeechError.NotAllowed:
                message = `Cannot run the demo.
                Your browser is not authorized to access your microphone.
                Verify that your browser has access to your microphone and try again.`;
                break;
              case SpeechError.NoSpeech:
                message = `No speech has been detected. Please try again.`;
                break;
              case SpeechError.AudioCapture:
                message = `Microphone is not available. Plese verify the connection of your microphone and try again.`;
                break;
              default:
                message = '';
                break;
            }
            return message;
          })
        );
      }
    }
    

    WebSpeechComponent 템플릿


    앞서 설명한 바와 같이 어셈블리의 템플릿은 비동기식 파이핑에서 지원됩니다.
    <section>
      <mat-card *ngIf="errorMessage$| async as errorMessage" class="notification">{{errorMessage}}</mat-card>
    </section>
    <section>
      <mat-form-field>
        <mat-label>Select your language</mat-label>
        <mat-select [(value)]="currentLanguage">
          <mat-option *ngFor="let language of languages" [value]="language" (click)="selectLanguage(language)">
            {{language}}
          </mat-option>
        </mat-select>
      </mat-form-field>
    </section>
    <section>
      <button mat-fab *ngIf="listening$ | async; else mic" (click)="stop()">
        <mat-icon class="soundwave">mic</mat-icon>
      </button>
      <ng-template #mic>
        <button mat-fab (click)="start()">
          <mat-icon>mic</mat-icon>
        </button>
      </ng-template>
    </section>
    <section *ngIf="transcript$ | async">
      <mat-card class="notification mat-elevation-z4">{{transcript$ | async}}</mat-card>
    </section>
    <section>
      <mat-form-field class="speech-result-width">
        <textarea matInput [value]="totalTranscript || ''" placeholder="Speech Input Result" rows="15" disabled="false"></textarea>
      </mat-form-field>
    </section>
    
    응용 프로그램이 마이크를 켜고 소리를 들을 준비가 되어 있습니다!

    SpeechSynthesizer Service 추가(텍스트에서 음성으로)


    먼저 서비스를 만듭니다.
    ng generate service shared/services/web-apis/speech-synthesizer
    
    이 파일에 다음 코드를 추가합니다.
    // speech-synthesizer.ts
    import { Injectable } from '@angular/core';
    
    @Injectable({
      providedIn: 'root',
    })
    export class SpeechSynthesizerService {
      speechSynthesizer!: SpeechSynthesisUtterance;
    
      constructor() {
        this.initSynthesis();
      }
    
      initSynthesis(): void {
        this.speechSynthesizer = new SpeechSynthesisUtterance();
        this.speechSynthesizer.volume = 1;
        this.speechSynthesizer.rate = 1;
        this.speechSynthesizer.pitch = 0.2;
      }
    
      speak(message: string, language: string): void {
        this.speechSynthesizer.lang = language;
        this.speechSynthesizer.text = message;
        speechSynthesis.speak(this.speechSynthesizer);
      }
    }
    
    이제 응용 프로그램이 당신과 대화할 수 있습니다.응용 프로그램이 음성 구동 조작을 실행할 준비가 되었을 때, 우리는 이 서비스를 호출할 수 있다.그 밖에 우리는 언제 조작을 실행했는지 확인할 수 있고, 심지어는 파라미터를 물어볼 수도 있다.
    다음 목표는 응용 프로그램에서 실행할 음성 명령을 정의하는 것이다.

    정책을 통한 행동 정의


    응용 프로그램에서 음성 명령이 실행해야 할 주요 동작을 생각해 보겠습니다.
  • 응용 프로그램은 Angular Material에서 제공하는 다른 테마를 통해 기본 테마를 변경할 수 있습니다.
  • 응용 프로그램은 응용 프로그램의 제목 속성을 변경할 수 있습니다.
  • 동시에 우리는 기존 텍스트 영역 구성 요소에 모든 최종 결과를 추가할 수 있어야 한다.
  • 이런 상황에 맞추어 해결 방안을 설계하는 방법은 다르다.이런 상황에서 응용 프로그램의 주제와 제목을 변경하기 위해 정책을 정의하는 것을 고려해 봅시다.
    현재 전략은 우리가 가장 좋아하는 키워드다.Design Patterns을 본 후에 우리는 Strategy Pattern를 사용하여 이 문제를 해결할 수 있다는 것이 분명하다.

    정책 모드 적용

    ActionContext 서비스 및 정책 추가

    ActionContext, ActionStrategy, ChangeThemeStrategyChangeTitleStrategy 클래스를 만듭니다.
    ng generate class shared/services/action/action-context
    ng generate class shared/services/action/action-strategy
    ng generate class shared/services/action/change-theme-strategy
    ng generate class shared/services/action/change-title-strategy
    
    // action-context.ts
    @Injectable({
      providedIn: 'root',
    })
    export class ActionContext {
      private currentStrategy?: ActionStrategy;
    
      constructor(
        private changeThemeStrategy: ChangeThemeStrategy,
        private changeTitleStrategy: ChangeTitleStrategy,
        private titleService: Title,
        private speechSynthesizer: SpeechSynthesizerService
      ) {
        this.changeTitleStrategy.titleService = titleService;
      }
    
      processMessage(message: string, language: string): void {
        const msg = message.toLowerCase();
        const hasChangedStrategy = this.hasChangedStrategy(msg, language);
    
        let isFinishSignal = false;
        if (!hasChangedStrategy) {
          isFinishSignal = this.isFinishSignal(msg, language);
        }
    
        if (!hasChangedStrategy && !isFinishSignal) {
          this.runAction(message, language);
        }
      }
    
      runAction(input: string, language: string): void {
        if (this.currentStrategy) {
          this.currentStrategy.runAction(input, language);
        }
      }
    
      setStrategy(strategy: ActionStrategy | undefined): void {
        this.currentStrategy = strategy;
      }
    
      // Private methods omitted. Please refer to the repository to see all the related source code.
    
    // action-strategy.ts
    export abstract class ActionStrategy {
      protected mapStartSignal: Map<string, string> = new Map<string, string>();
      protected mapEndSignal: Map<string, string> = new Map<string, string>();
    
      protected mapInitResponse: Map<string, string> = new Map<string, string>();
      protected mapFinishResponse: Map<string, string> = new Map<string, string>();
      protected mapActionDone: Map<string, string> = new Map<string, string>();
    
      constructor() {
        this.mapFinishResponse.set('en-US', 'Your action has been completed.');
        this.mapFinishResponse.set('es-ES', 'La accion ha sido finalizada.');
      }
    
      getStartSignal(language: string): string {
        return this.mapStartSignal.get(language) || '';
      }
    
      getEndSignal(language: string): string {
        return this.mapEndSignal.get(language) || '';
      }
    
      getInitialResponse(language: string): string {
        return this.mapInitResponse.get(language) || '';
      }
      getFinishResponse(language: string): string {
        return this.mapFinishResponse.get(language) || '';
      }
      abstract runAction(input: string, language: string): void;
    }
    
    // change-theme-strategy.ts
    @Injectable({
      providedIn: 'root',
    })
    export class ChangeThemeStrategy extends ActionStrategy {
      private mapThemes: Map<string, Theme[]> = new Map<string, Theme[]>();
      private styleManager: StyleManager = new StyleManager();
    
      constructor(private speechSynthesizer: SpeechSynthesizerService) {
        super();
        this.mapStartSignal.set('en-US', 'perform change theme');
        this.mapStartSignal.set('es-ES', 'iniciar cambio de tema');
    
        this.mapEndSignal.set('en-US', 'finish change theme');
        this.mapEndSignal.set('es-ES', 'finalizar cambio de tema');
    
        this.mapInitResponse.set('en-US', 'Please, tell me your theme name.');
        this.mapInitResponse.set('es-ES', 'Por favor, mencione el nombre de tema.');
    
        this.mapActionDone.set('en-US', 'Changing Theme of the Application to');
        this.mapActionDone.set('es-ES', 'Cambiando el tema de la Aplicación a');
    
        this.mapThemes.set('en-US', [
          {
            keyword: 'deep purple',
            href: 'deeppurple-amber.css',
          }
        ]);
        this.mapThemes.set('es-ES', [
          {
            keyword: 'púrpura',
            href: 'deeppurple-amber.css',
          }
        ]);
        }
    
      runAction(input: string, language: string): void {
        const themes = this.mapThemes.get(language) || [];
        const theme = themes.find((th) => {
          return input.toLocaleLowerCase() === th.keyword;
        });
    
        if (theme) {
          this.styleManager.removeStyle('theme');
          this.styleManager.setStyle('theme', `assets/theme/${theme.href}`);
          this.speechSynthesizer.speak(
            `${this.mapActionDone.get(language)}: ${theme.keyword}`,
            language
          );
        }
      }
    }
    
    // change-title-strategy.ts
    @Injectable({
      providedIn: 'root',
    })
    export class ChangeTitleStrategy extends ActionStrategy {
      private title?: Title;
    
      constructor(private speechSynthesizer: SpeechSynthesizerService) {
        super();
        this.mapStartSignal.set('en-US', 'perform change title');
        this.mapStartSignal.set('es-ES', 'iniciar cambio de título');
    
        this.mapEndSignal.set('en-US', 'finish change title');
        this.mapEndSignal.set('es-ES', 'finalizar cambio de título');
    
        this.mapInitResponse.set('en-US', 'Please, tell me the new title');
        this.mapInitResponse.set('es-ES', 'Por favor, mencione el nuevo título');
    
        this.mapActionDone.set('en-US', 'Changing title of the Application to');
        this.mapActionDone.set('es-ES', 'Cambiando el título de la Aplicación a');
      }
    
      set titleService(title: Title) {
        this.title = title;
      }
    
      runAction(input: string, language: string): void {
        this.title?.setTitle(input);
        this.speechSynthesizer.speak(
          `${this.mapActionDone.get(language)}: ${input}`,
          language
        );
      }
    }
    
    SpeechSynthesizerService의 사용법과 이 서비스를 호출하는 위치를 주의하십시오.speak 기능을 사용할 때, 프로그램은 당신의 스피커를 사용하여 당신의 질문에 대답할 것입니다.

    소스 코드 및 현장 프레젠테이션


    소스 코드


    GitHub 저장소에서 전체 항목을 찾습니다.https://github.com/luixaviles/web-speech-angular.별을 하나 주는 것을 잊지 마라⭐️ 더 많은 기능을 제공하기로 결정하면 요청을 보낼 수도 있습니다.

    현장 프레젠테이션


    Chrome 웹 브라우저를 열고 https://luixaviles.com/web-speech-angular/ 로 이동합니다.응용 프로그램의 주석을 보고 영어, 스페인어로 테스트를 합니다.

    마지막 한마디


    프레젠테이션이 Angular 및 TypeScript를 사용하여 작성된 경우에도 이러한 개념과 웹 API를 다른 JavaScript 프레임워크나 라이브러리에 적용할 수 있습니다.
    너는 나와 함께 갈 수 있다GitHub. 나의 일에 대한 더 많은 정보를 알 수 있다.
    읽어주셔서 감사합니다!
    Luis Aviles

    좋은 웹페이지 즐겨찾기