Skip to content

Commit

Permalink
Allow stop playing audio
Browse files Browse the repository at this point in the history
  • Loading branch information
gsans committed Oct 12, 2023
1 parent 8e8dc81 commit c77c077
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 16 deletions.
70 changes: 57 additions & 13 deletions src/app/read/audio.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@ export class AudioService {
ELEVEN_LABS_VOICE_ID = environment.ELEVEN_LABS_VOICE_ID;
ELEVEN_LABS_API_KEY = environment.ELEVEN_LABS_API_KEY;

public audio: HTMLAudioElement;
public audio!: HTMLAudioElement;
public source!: AudioBufferSourceNode;
private audioContext!: AudioContext;
private streamPlaying: boolean = false;

constructor(private http: HttpClient) {
this.audio = new Audio();
}

public setAudioSourceAndPlay(source: string): void {
if (this.isAudioPlaying()) return;
this.audio.src = source;
this.audio.play();
}

public setAudioAndPlay(data: ArrayBuffer): void {
if (this.isAudioPlaying()) return;
const blob = new Blob([data], { type: 'audio/mpeg' });
const url = URL.createObjectURL(blob);
this.audio.src = url;
Expand All @@ -27,11 +32,12 @@ export class AudioService {
console.log('Ended playing: ' + Date.now());
}

public playTextToSpeech(text: string) {
this.getAudio(text);
public async playTextToSpeech(text: string) {
if (this.isAudioPlaying()) return;
await this.getAudio(text);
}

private getAudio(text: string) {
private async getAudio(text: string) {
const ttsURL = `https://api.elevenlabs.io/v1/text-to-speech/${this.ELEVEN_LABS_VOICE_ID}`;

const headers = {
Expand Down Expand Up @@ -73,6 +79,8 @@ export class AudioService {
}

private async getStreamAudio(text: string) {
if (this.isAudioStreamingPlaying()) return;

const streamingURL = `https://api.elevenlabs.io/v1/text-to-speech/${this.ELEVEN_LABS_VOICE_ID}/stream?optimize_streaming_latency=3`;

const headers = {
Expand All @@ -96,6 +104,7 @@ export class AudioService {
.subscribe({
next: (response: ArrayBuffer) => {
this.playAudioStream(response);
console.log('stream chunk');
},
error: (error) => {
console.error('Error:', error);
Expand All @@ -104,19 +113,54 @@ export class AudioService {
}

private async playAudioStream(audioData: ArrayBuffer) {
const audioContext = new AudioContext();
const audioBuffer = await audioContext.decodeAudioData(audioData);
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);

source.onended = () => {
//todo: add error handling
if (this.isAudioStreamingPlaying()) {
return;
}
else {
this.audioContext = new AudioContext();
this.source = this.audioContext.createBufferSource();
}
const audioBuffer = await this.audioContext.decodeAudioData(audioData);
this.source.buffer = audioBuffer;
this.source.connect(this.audioContext.destination);

this.source.onended = () => {
console.log('Ended playing: ' + Date.now());
};

let startTime = audioContext.currentTime + 0.1;
let startTime = this.audioContext.currentTime + 0.1;
console.log('Started playing: ' + startTime);
source.start(startTime);
this.streamPlaying = true;
this.source.start(startTime);
}

pause() {
if (this.isAudioPlaying()) {
this.audio.pause();
this.audio.currentTime = 0;
}
if (this.isAudioStreamingPlaying()) {
this.source.stop();
this.source.disconnect();
this.audioContext.close();
this.streamPlaying = false;
}
}

public isAudioPlaying(){
return (this.audio
&& this.audio.currentTime > 0
&& !this.audio.paused
&& !this.audio.ended
&& this.audio.readyState > 2);
}

public isAudioStreamingPlaying() {
return (this.source
&& this.audioContext
&& this.audioContext.state == 'running'
&& this.streamPlaying
);
}
}
4 changes: 2 additions & 2 deletions src/app/rich-text-editor/rich-text-editor.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<div class="header">
<div class="toolbar">
<div>Write your prompt</div>
<button mat-icon-button (click)="onSpeakerClick()" [disabled]="isEditorEmpty">
<mat-icon>volume_up</mat-icon>
<button mat-icon-button (click)="onSpeakerClick(); playing = !playing" [disabled]="isEditorEmpty">
<mat-icon>{{playing ? 'stop' : 'volume_up'}}</mat-icon>
</button>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/app/rich-text-editor/rich-text-editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class RichTextEditorComponent {
ideasArray = PROMPTS;
usedIndices: number[] = [];
timeoutId: any | undefined;
playing: boolean = false;

quillConfiguration = {
toolbar: false,
Expand Down
7 changes: 6 additions & 1 deletion src/app/text/text.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class TextComponent {
@ViewChild(RichTextEditorComponent)
editor!: RichTextEditorComponent;
editorEmpty: boolean = true;
playing: boolean = false;

constructor(
@Inject(TEXT_SERVICE_CLIENT_TOKEN) public client: TextServiceClient,
Expand All @@ -40,12 +41,16 @@ export class TextComponent {
}

speakoutPrompt() {
if (this.audio.isAudioStreamingPlaying()) {
this.audio.pause();
return;
}
const prompt = this.editor.extractPrompt();
if (prompt.length == 0) return;
const phrases = prompt.split('.');
const limitedPhrases = phrases.slice(0, MAX_PHRASES).join('.');
if (limitedPhrases.length > 0) {
this.audio.playTextToSpeech(limitedPhrases);
this.audio.playStreamAudio(limitedPhrases);
}
}

Expand Down

0 comments on commit c77c077

Please sign in to comment.