diff --git a/web/src/hooks/useAudioAnnouncements.test.ts b/web/src/hooks/useAudioAnnouncements.test.ts index ceabd46..930854e 100644 --- a/web/src/hooks/useAudioAnnouncements.test.ts +++ b/web/src/hooks/useAudioAnnouncements.test.ts @@ -1,5 +1,10 @@ -import { describe, expect, it } from "vitest"; -import { announcementAudioQueue } from "./useAudioAnnouncements"; +import { renderHook } from "@testing-library/react"; +import { afterEach, describe, expect, it } from "vitest"; +import { announcementAudioQueue, audioPreloadSources, useAudioAnnouncements } from "./useAudioAnnouncements"; + +afterEach(() => { + document.head.querySelectorAll("link[data-queue-voice-preload]").forEach((link) => link.remove()); +}); describe("announcementAudioQueue", () => { it("忽略项目名并把单号码播报素材重复三遍", () => { @@ -34,4 +39,15 @@ describe("announcementAudioQueue", () => { "/audio/queue-voice/entrance.wav", ]); }); + + it("打开页面时预加载全部声音素材", () => { + const { unmount } = renderHook(() => useAudioAnnouncements()); + const links = Array.from(document.head.querySelectorAll("link[data-queue-voice-preload]")); + + expect(links.map((link) => link.getAttribute("href"))).toEqual(audioPreloadSources); + expect(links.every((link) => link.rel === "preload" && link.getAttribute("as") === "audio")).toBe(true); + + unmount(); + expect(document.head.querySelector("link[data-queue-voice-preload]")).toBeNull(); + }); }); diff --git a/web/src/hooks/useAudioAnnouncements.ts b/web/src/hooks/useAudioAnnouncements.ts index 4c3dcb4..4462521 100644 --- a/web/src/hooks/useAudioAnnouncements.ts +++ b/web/src/hooks/useAudioAnnouncements.ts @@ -24,6 +24,12 @@ const PHRASE_AUDIO = [ ["至", `${AUDIO_BASE_PATH}/through.wav`], ] as const; +export const audioPreloadSources = [ + ...Object.values(DIGIT_AUDIO), + ...PHRASE_AUDIO.map(([, source]) => source), + GAP_AUDIO, +]; + function clipsForAnnouncement(announcement: string): string[] { const callStart = announcement.indexOf("请"); if (callStart < 0) return []; @@ -61,6 +67,23 @@ export function useAudioAnnouncements() { const audioRef = useRef(null); const playbackIdRef = useRef(0); + useEffect(() => { + if (typeof document === "undefined") return; + + const links = audioPreloadSources.map((source) => { + const link = document.createElement("link"); + link.rel = "preload"; + link.setAttribute("as", "audio"); + link.type = "audio/wav"; + link.href = source; + link.dataset.queueVoicePreload = "true"; + document.head.appendChild(link); + return link; + }); + + return () => links.forEach((link) => link.remove()); + }, []); + const playQueue = useCallback((sources: string[]) => { const audio = audioRef.current; if (!audio || !sources.length) return;