docs: queue dialogue
This commit is contained in:
@@ -4,6 +4,32 @@ import { useSubtitleStore } from "@/managers/stores/useSubtitleStore";
|
|||||||
import type { DialogueManifest } from "@/types/dialogues/dialogues";
|
import type { DialogueManifest } from "@/types/dialogues/dialogues";
|
||||||
import { loadDialogueSubtitleCue } from "@/utils/dialogues/loadDialogueManifest";
|
import { loadDialogueSubtitleCue } from "@/utils/dialogues/loadDialogueManifest";
|
||||||
|
|
||||||
|
interface QueuedDialogueRequest {
|
||||||
|
manifest: DialogueManifest;
|
||||||
|
dialogueId: string;
|
||||||
|
resolve: (audio: HTMLAudioElement | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DIALOGUE_PLAY_START_TIMEOUT_MS = 800;
|
||||||
|
const dialogueQueue: QueuedDialogueRequest[] = [];
|
||||||
|
let isDialogueQueuePlaying = false;
|
||||||
|
|
||||||
|
export function queueDialogueById(
|
||||||
|
manifest: DialogueManifest,
|
||||||
|
dialogueId: string,
|
||||||
|
): Promise<HTMLAudioElement | null> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
dialogueQueue.push({ manifest, dialogueId, resolve });
|
||||||
|
void playNextQueuedDialogue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearQueuedDialogues(): void {
|
||||||
|
while (dialogueQueue.length > 0) {
|
||||||
|
dialogueQueue.shift()?.resolve(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function playDialogueById(
|
export async function playDialogueById(
|
||||||
manifest: DialogueManifest,
|
manifest: DialogueManifest,
|
||||||
dialogueId: string,
|
dialogueId: string,
|
||||||
@@ -28,6 +54,7 @@ export async function playDialogueById(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const cleanup = (): void => {
|
const cleanup = (): void => {
|
||||||
|
audio.removeEventListener("play", syncSubtitle);
|
||||||
audio.removeEventListener("timeupdate", syncSubtitle);
|
audio.removeEventListener("timeupdate", syncSubtitle);
|
||||||
audio.removeEventListener("ended", cleanup);
|
audio.removeEventListener("ended", cleanup);
|
||||||
audio.removeEventListener("pause", cleanup);
|
audio.removeEventListener("pause", cleanup);
|
||||||
@@ -51,10 +78,70 @@ export async function playDialogueById(
|
|||||||
clearSubtitle();
|
clearSubtitle();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
audio.addEventListener("play", syncSubtitle);
|
||||||
audio.addEventListener("timeupdate", syncSubtitle);
|
audio.addEventListener("timeupdate", syncSubtitle);
|
||||||
audio.addEventListener("ended", cleanup);
|
audio.addEventListener("ended", cleanup);
|
||||||
audio.addEventListener("pause", cleanup);
|
audio.addEventListener("pause", cleanup);
|
||||||
syncSubtitle();
|
|
||||||
|
|
||||||
return audio;
|
return audio;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function playNextQueuedDialogue(): Promise<void> {
|
||||||
|
if (isDialogueQueuePlaying) return;
|
||||||
|
|
||||||
|
isDialogueQueuePlaying = true;
|
||||||
|
|
||||||
|
while (dialogueQueue.length > 0) {
|
||||||
|
const request = dialogueQueue.shift();
|
||||||
|
if (!request) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const audio = await playDialogueById(
|
||||||
|
request.manifest,
|
||||||
|
request.dialogueId,
|
||||||
|
);
|
||||||
|
request.resolve(audio);
|
||||||
|
if (audio) await waitForDialogueToFinish(audio);
|
||||||
|
} catch {
|
||||||
|
request.resolve(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isDialogueQueuePlaying = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForDialogueToFinish(audio: HTMLAudioElement): Promise<void> {
|
||||||
|
if (audio.ended) return Promise.resolve();
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let hasStarted = !audio.paused;
|
||||||
|
let startTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
|
function cleanup(): void {
|
||||||
|
if (startTimeout) clearTimeout(startTimeout);
|
||||||
|
audio.removeEventListener("play", handlePlay);
|
||||||
|
audio.removeEventListener("ended", finish);
|
||||||
|
audio.removeEventListener("pause", finish);
|
||||||
|
audio.removeEventListener("error", finish);
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish(): void {
|
||||||
|
cleanup();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePlay(): void {
|
||||||
|
hasStarted = true;
|
||||||
|
if (startTimeout) clearTimeout(startTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
audio.addEventListener("play", handlePlay);
|
||||||
|
audio.addEventListener("ended", finish);
|
||||||
|
audio.addEventListener("pause", finish);
|
||||||
|
audio.addEventListener("error", finish);
|
||||||
|
|
||||||
|
startTimeout = setTimeout(() => {
|
||||||
|
if (!hasStarted && audio.paused) finish();
|
||||||
|
}, DIALOGUE_PLAY_START_TIMEOUT_MS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import { useFrame } from "@react-three/fiber";
|
import { useFrame } from "@react-three/fiber";
|
||||||
import type { DialogueManifest } from "@/types/dialogues/dialogues";
|
import type { DialogueManifest } from "@/types/dialogues/dialogues";
|
||||||
import { loadDialogueManifest } from "@/utils/dialogues/loadDialogueManifest";
|
import { loadDialogueManifest } from "@/utils/dialogues/loadDialogueManifest";
|
||||||
import { playDialogueById } from "@/utils/dialogues/playDialogue";
|
import {
|
||||||
|
clearQueuedDialogues,
|
||||||
|
queueDialogueById,
|
||||||
|
} from "@/utils/dialogues/playDialogue";
|
||||||
import { logger } from "@/utils/core/logger";
|
import { logger } from "@/utils/core/logger";
|
||||||
|
|
||||||
export function GameDialogues(): null {
|
export function GameDialogues(): null {
|
||||||
@@ -26,6 +29,7 @@ export function GameDialogues(): null {
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
mounted = false;
|
mounted = false;
|
||||||
|
clearQueuedDialogues();
|
||||||
activeAudios.forEach((audio) => audio.pause());
|
activeAudios.forEach((audio) => audio.pause());
|
||||||
activeAudios.clear();
|
activeAudios.clear();
|
||||||
};
|
};
|
||||||
@@ -43,7 +47,7 @@ export function GameDialogues(): null {
|
|||||||
|
|
||||||
playedDialoguesRef.current.add(dialogue.id);
|
playedDialoguesRef.current.add(dialogue.id);
|
||||||
|
|
||||||
void playDialogueById(manifest, dialogue.id).then((audio) => {
|
void queueDialogueById(manifest, dialogue.id).then((audio) => {
|
||||||
if (!audio) return;
|
if (!audio) return;
|
||||||
activeAudiosRef.current.add(audio);
|
activeAudiosRef.current.add(audio);
|
||||||
audio.addEventListener(
|
audio.addEventListener(
|
||||||
|
|||||||
Reference in New Issue
Block a user