첫 번째 음성 구동 웹 응용 프로그램 구축
78181 단어 typescriptwebdevangularjavascript
본고에서 현대 웹 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는 다음과 같은 기능을 지원합니다.
음성 합성 인터페이스
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 각도에서 shared
및 material
모듈을 생성할 수 있습니다.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);
}
}
기본적으로 앞의 코드는 구현할 주요 속성과 함수를 정의하는 방법을 보여 줍니다.정답은 사용
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);
}
}
이제 응용 프로그램이 당신과 대화할 수 있습니다.응용 프로그램이 음성 구동 조작을 실행할 준비가 되었을 때, 우리는 이 서비스를 호출할 수 있다.그 밖에 우리는 언제 조작을 실행했는지 확인할 수 있고, 심지어는 파라미터를 물어볼 수도 있다.다음 목표는 응용 프로그램에서 실행할 음성 명령을 정의하는 것이다.
정책을 통한 행동 정의
응용 프로그램에서 음성 명령이 실행해야 할 주요 동작을 생각해 보겠습니다.
현재 전략은 우리가 가장 좋아하는 키워드다.Design Patterns을 본 후에 우리는 Strategy Pattern를 사용하여 이 문제를 해결할 수 있다는 것이 분명하다.
정책 모드 적용
ActionContext 서비스 및 정책 추가
ActionContext
, ActionStrategy
, ChangeThemeStrategy
및 ChangeTitleStrategy
클래스를 만듭니다.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
Reference
이 문제에 관하여(첫 번째 음성 구동 웹 응용 프로그램 구축), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/luixaviles/build-your-first-voice-driven-web-application-1h99텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)