2 Commits

Author SHA1 Message Date
739eabfc6c Merge pull request '优化:公屏预加载叫号音频素材' (#3) from codex/bundled-audio-announcements into main
Reviewed-on: #3
2026-08-01 12:07:31 +08:00
6d07612bc9 优化:公屏预加载叫号音频素材
需求:页面打开后缓存音频片段,叫号时避免临时下载造成延迟。

实现:页面挂载时预加载全部数字与提示语 WAV,后端继续返回叫号数据/文本,前端按文本映射片段播放。

验证:前端 66 项测试通过,生产构建通过。
2026-08-01 12:06:01 +08:00
2 changed files with 41 additions and 2 deletions

View File

@@ -1,5 +1,10 @@
import { describe, expect, it } from "vitest"; import { renderHook } from "@testing-library/react";
import { announcementAudioQueue } from "./useAudioAnnouncements"; 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", () => { describe("announcementAudioQueue", () => {
it("忽略项目名并把单号码播报素材重复三遍", () => { it("忽略项目名并把单号码播报素材重复三遍", () => {
@@ -34,4 +39,15 @@ describe("announcementAudioQueue", () => {
"/audio/queue-voice/entrance.wav", "/audio/queue-voice/entrance.wav",
]); ]);
}); });
it("打开页面时预加载全部声音素材", () => {
const { unmount } = renderHook(() => useAudioAnnouncements());
const links = Array.from(document.head.querySelectorAll<HTMLLinkElement>("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();
});
}); });

View File

@@ -24,6 +24,12 @@ const PHRASE_AUDIO = [
["至", `${AUDIO_BASE_PATH}/through.wav`], ["至", `${AUDIO_BASE_PATH}/through.wav`],
] as const; ] as const;
export const audioPreloadSources = [
...Object.values(DIGIT_AUDIO),
...PHRASE_AUDIO.map(([, source]) => source),
GAP_AUDIO,
];
function clipsForAnnouncement(announcement: string): string[] { function clipsForAnnouncement(announcement: string): string[] {
const callStart = announcement.indexOf("请"); const callStart = announcement.indexOf("请");
if (callStart < 0) return []; if (callStart < 0) return [];
@@ -61,6 +67,23 @@ export function useAudioAnnouncements() {
const audioRef = useRef<HTMLAudioElement | null>(null); const audioRef = useRef<HTMLAudioElement | null>(null);
const playbackIdRef = useRef(0); 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 playQueue = useCallback((sources: string[]) => {
const audio = audioRef.current; const audio = audioRef.current;
if (!audio || !sources.length) return; if (!audio || !sources.length) return;