Skip to content

Commit

Permalink
generate audio in background while continuously playing
Browse files Browse the repository at this point in the history
  • Loading branch information
cm-ayf committed Jan 28, 2024
1 parent 2a19032 commit b0e6ef1
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 16 deletions.
60 changes: 44 additions & 16 deletions src/store/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ import {
generateLabFromAudioQuery,
handlePossiblyNotMorphableError,
} from "./audioGenerate";
import {
ContinuousPlayer,
GenerateEndEvent,
PlayEndEvent,
} from "./audioContinuousPlayer";
import {
AudioKey,
CharacterInfo,
Expand Down Expand Up @@ -1679,23 +1684,46 @@ export const audioStore = createPartialStore<AudioStoreTypes>({
index = state.audioKeys.findIndex((v) => v === currentAudioKey);
}

commit("SET_NOW_PLAYING_CONTINUOUSLY", { nowPlaying: true });
try {
for (let i = index; i < state.audioKeys.length; ++i) {
const audioKey = state.audioKeys[i];
commit("SET_ACTIVE_AUDIO_KEY", { audioKey });
const isEnded = await dispatch("PLAY_AUDIO", { audioKey });
if (!isEnded) {
break;
}
}
} finally {
commit("SET_ACTIVE_AUDIO_KEY", { audioKey: currentAudioKey });
commit("SET_AUDIO_PLAY_START_POINT", {
startPoint: currentAudioPlayStartPoint,
const player = new ContinuousPlayer(state.audioKeys.slice(index));
player.addEventListener("generatestart", async (e) => {
const result = await dispatch("FETCH_AUDIO", {
audioKey: e.audioKey,
});
commit("SET_NOW_PLAYING_CONTINUOUSLY", { nowPlaying: false });
}
player.dispatchEvent(new GenerateEndEvent(e.audioKey, result));
});
player.addEventListener("playstart", async (e) => {
commit("SET_ACTIVE_AUDIO_KEY", { audioKey: e.audioKey });
const isEnded = await dispatch("PLAY_AUDIO_BLOB", {
audioBlob: e.result.blob,
audioKey: e.audioKey,
});
player.dispatchEvent(new PlayEndEvent(e.audioKey, !isEnded));
});
player.addEventListener("waitstart", async (e) => {
dispatch("START_PROGRESS");
commit("SET_ACTIVE_AUDIO_KEY", { audioKey: e.audioKey });
commit("SET_AUDIO_NOW_GENERATING", {
audioKey: e.audioKey,
nowGenerating: true,
});
});
player.addEventListener("waitend", async (e) => {
dispatch("RESET_PROGRESS");
commit("SET_AUDIO_NOW_GENERATING", {
audioKey: e.audioKey,
nowGenerating: false,
});
});

commit("SET_NOW_PLAYING_CONTINUOUSLY", { nowPlaying: true });

await player.play();

commit("SET_ACTIVE_AUDIO_KEY", { audioKey: currentAudioKey });
commit("SET_AUDIO_PLAY_START_POINT", {
startPoint: currentAudioPlayStartPoint,
});
commit("SET_NOW_PLAYING_CONTINUOUSLY", { nowPlaying: false });
}),
},
});
Expand Down
132 changes: 132 additions & 0 deletions src/store/audioContinuousPlayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { FetchAudioResult } from "./type";
import { AudioKey } from "@/type/preload";

export class ContinuousPlayer extends EventTarget {
private playQueue: ({ audioKey: AudioKey } & FetchAudioResult)[] = [];
private generating?: { audioKey: AudioKey };
private playing?: { audioKey: AudioKey } & FetchAudioResult;

private finished = false;
private resolve!: () => void;
private promise: Promise<void>;

constructor(private generateQueue: AudioKey[]) {
super();

this.addEventListener("generatestart", (e) => {
this.generating = { audioKey: e.audioKey };
});
this.addEventListener("generateend", (e) => {
delete this.generating;

const { audioKey, result } = e;
if (this.playing) {
this.playQueue.push({ audioKey, ...result });
} else {
this.dispatchEvent(new WaitEndEvent(e.audioKey));
if (this.finished) return;
this.dispatchEvent(new PlayStartEvent(audioKey, result));
}

const next = this.generateQueue.shift();
if (next) {
this.dispatchEvent(new GenerateStartEvent(next));
}
});
this.addEventListener("playstart", (e) => {
this.playing = { audioKey: e.audioKey, ...e.result };
});
this.addEventListener("playend", (e) => {
delete this.playing;
if (e.forceFinish) {
this.finish();
return;
}

const next = this.playQueue.shift();
if (next) {
this.dispatchEvent(new PlayStartEvent(next.audioKey, next));
} else if (this.generating) {
this.dispatchEvent(new WaitStartEvent(this.generating.audioKey));
} else {
this.finish();
}
});

this.promise = new Promise((resolve) => {
this.resolve = resolve;
});
}

private finish() {
this.finished = true;
this.resolve();
}

async play() {
const next = this.generateQueue.shift();
if (!next) return;
this.dispatchEvent(new WaitStartEvent(next));
this.dispatchEvent(new GenerateStartEvent(next));

await this.promise;
}
}

export interface ContinuousPlayer extends EventTarget {
addEventListener<K extends keyof ContinuousPlayerEvents>(
type: K,
listener: (this: ContinuousPlayer, ev: ContinuousPlayerEvents[K]) => void,
options?: boolean | AddEventListenerOptions
): void;
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions
): void;
}

interface ContinuousPlayerEvents {
generatestart: GenerateStartEvent;
generateend: GenerateEndEvent;
playstart: PlayStartEvent;
playend: PlayEndEvent;
waitstart: WaitStartEvent;
waitend: WaitEndEvent;
}

export class GenerateStartEvent extends Event {
constructor(public audioKey: AudioKey) {
super("generatestart");
}
}

export class GenerateEndEvent extends Event {
constructor(public audioKey: AudioKey, public result: FetchAudioResult) {
super("generateend");
}
}

export class PlayStartEvent extends Event {
constructor(public audioKey: AudioKey, public result: FetchAudioResult) {
super("playstart");
}
}

export class PlayEndEvent extends Event {
constructor(public audioKey: AudioKey, public forceFinish: boolean) {
super("playend");
}
}

export class WaitStartEvent extends Event {
constructor(public audioKey: AudioKey) {
super("waitstart");
}
}

export class WaitEndEvent extends Event {
constructor(public audioKey: AudioKey) {
super("waitend");
}
}

0 comments on commit b0e6ef1

Please sign in to comment.