update: play audio + srt sync
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
import { useSettingsStore } from "@/managers/stores/useSettingsStore";
|
import { useSettingsStore } from "@/managers/stores/useSettingsStore";
|
||||||
|
import { useSubtitleStore } from "@/managers/stores/useSubtitleStore";
|
||||||
|
import type { DialogueSpeaker } from "@/types/dialogues/dialogues";
|
||||||
|
|
||||||
export type SubtitleSpeaker = "Narrateur" | "Fermier" | "Leonie";
|
export type SubtitleSpeaker = DialogueSpeaker;
|
||||||
|
|
||||||
interface SubtitlesProps {
|
interface SubtitlesProps {
|
||||||
speaker?: SubtitleSpeaker | null;
|
speaker?: SubtitleSpeaker | null;
|
||||||
@@ -12,18 +14,20 @@ export function Subtitles({
|
|||||||
text = null,
|
text = null,
|
||||||
}: SubtitlesProps): React.JSX.Element | null {
|
}: SubtitlesProps): React.JSX.Element | null {
|
||||||
const subtitlesEnabled = useSettingsStore((state) => state.subtitlesEnabled);
|
const subtitlesEnabled = useSettingsStore((state) => state.subtitlesEnabled);
|
||||||
const content = text?.trim();
|
const activeSubtitle = useSubtitleStore((state) => state.activeSubtitle);
|
||||||
|
const subtitleSpeaker = speaker ?? activeSubtitle?.speaker ?? null;
|
||||||
|
const content = (text ?? activeSubtitle?.text)?.trim();
|
||||||
|
|
||||||
if (!subtitlesEnabled || !content) return null;
|
if (!subtitlesEnabled || !content) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="subtitles" aria-live="polite">
|
<div className="subtitles" aria-live="polite">
|
||||||
<p>
|
<p>
|
||||||
{speaker ? (
|
{subtitleSpeaker ? (
|
||||||
<span
|
<span
|
||||||
className={`subtitles__speaker subtitles__speaker--${speaker.toLowerCase()}`}
|
className={`subtitles__speaker subtitles__speaker--${subtitleSpeaker.toLowerCase()}`}
|
||||||
>
|
>
|
||||||
{speaker}:
|
{subtitleSpeaker}:
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
{content}
|
{content}
|
||||||
|
|||||||
@@ -54,7 +54,11 @@ export class AudioManager {
|
|||||||
return this._categoryVolumes[category];
|
return this._categoryVolumes[category];
|
||||||
}
|
}
|
||||||
|
|
||||||
playSound(path: string, volume = 1, options: PlaySoundOptions = {}): void {
|
playSound(
|
||||||
|
path: string,
|
||||||
|
volume = 1,
|
||||||
|
options: PlaySoundOptions = {},
|
||||||
|
): HTMLAudioElement {
|
||||||
const audio = this._acquireAudio(path);
|
const audio = this._acquireAudio(path);
|
||||||
const category = options.category ?? AudioManager.DEFAULT_SOUND_CATEGORY;
|
const category = options.category ?? AudioManager.DEFAULT_SOUND_CATEGORY;
|
||||||
audio.volume = this._getEffectiveVolume(category, volume);
|
audio.volume = this._getEffectiveVolume(category, volume);
|
||||||
@@ -75,6 +79,8 @@ export class AudioManager {
|
|||||||
error: AudioManager._toLogValue(error),
|
error: AudioManager._toLogValue(error),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return audio;
|
||||||
}
|
}
|
||||||
|
|
||||||
playMusic(path: string, volume = 1): void {
|
playMusic(path: string, volume = 1): void {
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import type { DialogueSpeaker } from "@/types/dialogues/dialogues";
|
||||||
|
|
||||||
|
interface ActiveSubtitle {
|
||||||
|
speaker: DialogueSpeaker;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubtitleState {
|
||||||
|
activeSubtitle: ActiveSubtitle | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubtitleActions {
|
||||||
|
setActiveSubtitle: (subtitle: ActiveSubtitle | null) => void;
|
||||||
|
clearActiveSubtitle: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubtitleStore = SubtitleState & SubtitleActions;
|
||||||
|
|
||||||
|
export const useSubtitleStore = create<SubtitleStore>()((set) => ({
|
||||||
|
activeSubtitle: null,
|
||||||
|
setActiveSubtitle: (activeSubtitle) => set({ activeSubtitle }),
|
||||||
|
clearActiveSubtitle: () => set({ activeSubtitle: null }),
|
||||||
|
}));
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { AudioManager } from "@/managers/AudioManager";
|
||||||
|
import { useSettingsStore } from "@/managers/stores/useSettingsStore";
|
||||||
|
import { useSubtitleStore } from "@/managers/stores/useSubtitleStore";
|
||||||
|
import type { DialogueManifest } from "@/types/dialogues/dialogues";
|
||||||
|
import { loadDialogueSubtitleCue } from "@/utils/dialogues/loadDialogueManifest";
|
||||||
|
|
||||||
|
export async function playDialogueById(
|
||||||
|
manifest: DialogueManifest,
|
||||||
|
dialogueId: string,
|
||||||
|
): Promise<HTMLAudioElement | null> {
|
||||||
|
const dialogue = manifest.dialogues.find((item) => item.id === dialogueId);
|
||||||
|
if (!dialogue) return null;
|
||||||
|
|
||||||
|
const subtitleLanguage = useSettingsStore.getState().subtitleLanguage;
|
||||||
|
const subtitle = await loadDialogueSubtitleCue(
|
||||||
|
manifest,
|
||||||
|
dialogue,
|
||||||
|
subtitleLanguage,
|
||||||
|
);
|
||||||
|
const audio = AudioManager.getInstance().playSound(dialogue.audio, 1, {
|
||||||
|
category: "dialogue",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!subtitle) return audio;
|
||||||
|
|
||||||
|
const clearSubtitle = (): void => {
|
||||||
|
useSubtitleStore.getState().clearActiveSubtitle();
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanup = (): void => {
|
||||||
|
audio.removeEventListener("timeupdate", syncSubtitle);
|
||||||
|
audio.removeEventListener("ended", cleanup);
|
||||||
|
audio.removeEventListener("pause", cleanup);
|
||||||
|
clearSubtitle();
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncSubtitle = (): void => {
|
||||||
|
const currentTime = audio.currentTime;
|
||||||
|
const shouldShowSubtitle =
|
||||||
|
currentTime >= subtitle.cue.startTime &&
|
||||||
|
currentTime <= subtitle.cue.endTime;
|
||||||
|
|
||||||
|
if (shouldShowSubtitle) {
|
||||||
|
useSubtitleStore.getState().setActiveSubtitle({
|
||||||
|
speaker: subtitle.voice.speaker,
|
||||||
|
text: subtitle.cue.text,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSubtitle();
|
||||||
|
};
|
||||||
|
|
||||||
|
audio.addEventListener("timeupdate", syncSubtitle);
|
||||||
|
audio.addEventListener("ended", cleanup);
|
||||||
|
audio.addEventListener("pause", cleanup);
|
||||||
|
syncSubtitle();
|
||||||
|
|
||||||
|
return audio;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user