From 4405bf96f7ad8029fbf62741d92ddaab7b99c096 Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Sat, 1 Aug 2026 12:16:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=E5=B0=86=E5=8F=AB?= =?UTF-8?q?=E5=8F=B7=E9=9F=B3=E9=A2=91=E5=86=85=E8=81=94=E5=88=B0=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:音频使用 /audio 路径,浏览器预加载仍会向服务端发起独立请求。 实现:将 15 个 WAV 作为 Vite 内联资源编入前端 JS,后端继续仅返回叫号数据/文本。 验证:前端 66 项测试与生产构建通过;dist 中 WAV 文件为 0,含 15 个内联音频数据。 --- .../assets}/queue-voice/digit-0.wav | Bin .../assets}/queue-voice/digit-1.wav | Bin .../assets}/queue-voice/digit-2.wav | Bin .../assets}/queue-voice/digit-3.wav | Bin .../assets}/queue-voice/digit-4.wav | Bin .../assets}/queue-voice/digit-5.wav | Bin .../assets}/queue-voice/digit-6.wav | Bin .../assets}/queue-voice/digit-7.wav | Bin .../assets}/queue-voice/digit-8.wav | Bin .../assets}/queue-voice/digit-9.wav | Bin .../assets}/queue-voice/entrance.wav | Bin .../audio => src/assets}/queue-voice/gap.wav | Bin .../assets}/queue-voice/number.wav | Bin .../assets}/queue-voice/please.wav | Bin .../assets}/queue-voice/through.wav | Bin web/src/hooks/useAudioAnnouncements.test.ts | 55 +++++--------- web/src/hooks/useAudioAnnouncements.ts | 69 ++++++++---------- 17 files changed, 47 insertions(+), 77 deletions(-) rename web/{public/audio => src/assets}/queue-voice/digit-0.wav (100%) rename web/{public/audio => src/assets}/queue-voice/digit-1.wav (100%) rename web/{public/audio => src/assets}/queue-voice/digit-2.wav (100%) rename web/{public/audio => src/assets}/queue-voice/digit-3.wav (100%) rename web/{public/audio => src/assets}/queue-voice/digit-4.wav (100%) rename web/{public/audio => src/assets}/queue-voice/digit-5.wav (100%) rename web/{public/audio => src/assets}/queue-voice/digit-6.wav (100%) rename web/{public/audio => src/assets}/queue-voice/digit-7.wav (100%) rename web/{public/audio => src/assets}/queue-voice/digit-8.wav (100%) rename web/{public/audio => src/assets}/queue-voice/digit-9.wav (100%) rename web/{public/audio => src/assets}/queue-voice/entrance.wav (100%) rename web/{public/audio => src/assets}/queue-voice/gap.wav (100%) rename web/{public/audio => src/assets}/queue-voice/number.wav (100%) rename web/{public/audio => src/assets}/queue-voice/please.wav (100%) rename web/{public/audio => src/assets}/queue-voice/through.wav (100%) diff --git a/web/public/audio/queue-voice/digit-0.wav b/web/src/assets/queue-voice/digit-0.wav similarity index 100% rename from web/public/audio/queue-voice/digit-0.wav rename to web/src/assets/queue-voice/digit-0.wav diff --git a/web/public/audio/queue-voice/digit-1.wav b/web/src/assets/queue-voice/digit-1.wav similarity index 100% rename from web/public/audio/queue-voice/digit-1.wav rename to web/src/assets/queue-voice/digit-1.wav diff --git a/web/public/audio/queue-voice/digit-2.wav b/web/src/assets/queue-voice/digit-2.wav similarity index 100% rename from web/public/audio/queue-voice/digit-2.wav rename to web/src/assets/queue-voice/digit-2.wav diff --git a/web/public/audio/queue-voice/digit-3.wav b/web/src/assets/queue-voice/digit-3.wav similarity index 100% rename from web/public/audio/queue-voice/digit-3.wav rename to web/src/assets/queue-voice/digit-3.wav diff --git a/web/public/audio/queue-voice/digit-4.wav b/web/src/assets/queue-voice/digit-4.wav similarity index 100% rename from web/public/audio/queue-voice/digit-4.wav rename to web/src/assets/queue-voice/digit-4.wav diff --git a/web/public/audio/queue-voice/digit-5.wav b/web/src/assets/queue-voice/digit-5.wav similarity index 100% rename from web/public/audio/queue-voice/digit-5.wav rename to web/src/assets/queue-voice/digit-5.wav diff --git a/web/public/audio/queue-voice/digit-6.wav b/web/src/assets/queue-voice/digit-6.wav similarity index 100% rename from web/public/audio/queue-voice/digit-6.wav rename to web/src/assets/queue-voice/digit-6.wav diff --git a/web/public/audio/queue-voice/digit-7.wav b/web/src/assets/queue-voice/digit-7.wav similarity index 100% rename from web/public/audio/queue-voice/digit-7.wav rename to web/src/assets/queue-voice/digit-7.wav diff --git a/web/public/audio/queue-voice/digit-8.wav b/web/src/assets/queue-voice/digit-8.wav similarity index 100% rename from web/public/audio/queue-voice/digit-8.wav rename to web/src/assets/queue-voice/digit-8.wav diff --git a/web/public/audio/queue-voice/digit-9.wav b/web/src/assets/queue-voice/digit-9.wav similarity index 100% rename from web/public/audio/queue-voice/digit-9.wav rename to web/src/assets/queue-voice/digit-9.wav diff --git a/web/public/audio/queue-voice/entrance.wav b/web/src/assets/queue-voice/entrance.wav similarity index 100% rename from web/public/audio/queue-voice/entrance.wav rename to web/src/assets/queue-voice/entrance.wav diff --git a/web/public/audio/queue-voice/gap.wav b/web/src/assets/queue-voice/gap.wav similarity index 100% rename from web/public/audio/queue-voice/gap.wav rename to web/src/assets/queue-voice/gap.wav diff --git a/web/public/audio/queue-voice/number.wav b/web/src/assets/queue-voice/number.wav similarity index 100% rename from web/public/audio/queue-voice/number.wav rename to web/src/assets/queue-voice/number.wav diff --git a/web/public/audio/queue-voice/please.wav b/web/src/assets/queue-voice/please.wav similarity index 100% rename from web/public/audio/queue-voice/please.wav rename to web/src/assets/queue-voice/please.wav diff --git a/web/public/audio/queue-voice/through.wav b/web/src/assets/queue-voice/through.wav similarity index 100% rename from web/public/audio/queue-voice/through.wav rename to web/src/assets/queue-voice/through.wav diff --git a/web/src/hooks/useAudioAnnouncements.test.ts b/web/src/hooks/useAudioAnnouncements.test.ts index 930854e..2b3f89c 100644 --- a/web/src/hooks/useAudioAnnouncements.test.ts +++ b/web/src/hooks/useAudioAnnouncements.test.ts @@ -1,53 +1,32 @@ -import { renderHook } from "@testing-library/react"; -import { afterEach, describe, expect, it } from "vitest"; -import { announcementAudioQueue, audioPreloadSources, useAudioAnnouncements } from "./useAudioAnnouncements"; +import { describe, expect, it } from "vitest"; +import { announcementAudioQueue } from "./useAudioAnnouncements"; -afterEach(() => { - document.head.querySelectorAll("link[data-queue-voice-preload]").forEach((link) => link.remove()); -}); +const inlineAudio = (source: string) => source.startsWith("data:audio/wav;base64,"); describe("announcementAudioQueue", () => { it("忽略项目名并把单号码播报素材重复三遍", () => { - const once = [ - "/audio/queue-voice/please.wav", - "/audio/queue-voice/digit-0.wav", - "/audio/queue-voice/digit-0.wav", - "/audio/queue-voice/digit-0.wav", - "/audio/queue-voice/digit-1.wav", - "/audio/queue-voice/digit-6.wav", - "/audio/queue-voice/number.wav", - "/audio/queue-voice/entrance.wav", - "/audio/queue-voice/gap.wav", - ]; + const queue = announcementAudioQueue(["东门观光车,请零零零一六号,前往入口。"]); + const once = queue.slice(0, 9); - expect(announcementAudioQueue(["东门观光车,请零零零一六号,前往入口。"])) - .toEqual([...once, ...once, ...once]); + expect(once).toHaveLength(9); + expect(once.every(inlineAudio)).toBe(true); + expect(once[1]).toBe(once[2]); + expect(once[2]).toBe(once[3]); + expect(queue).toEqual([...once, ...once, ...once]); }); it("支持连续号码范围", () => { const queue = announcementAudioQueue(["请一七号至一九号,前往入口。"]); - expect(queue.slice(0, 9)).toEqual([ - "/audio/queue-voice/please.wav", - "/audio/queue-voice/digit-1.wav", - "/audio/queue-voice/digit-7.wav", - "/audio/queue-voice/number.wav", - "/audio/queue-voice/through.wav", - "/audio/queue-voice/digit-1.wav", - "/audio/queue-voice/digit-9.wav", - "/audio/queue-voice/number.wav", - "/audio/queue-voice/entrance.wav", - ]); + expect(queue.slice(0, 9).every(inlineAudio)).toBe(true); + expect(queue[1]).not.toBe(queue[2]); + expect(queue[3]).not.toBe(queue[4]); }); - it("打开页面时预加载全部声音素材", () => { - const { unmount } = renderHook(() => useAudioAnnouncements()); - const links = Array.from(document.head.querySelectorAll("link[data-queue-voice-preload]")); + it("音频素材不使用独立 HTTP 路径", () => { + const queue = announcementAudioQueue(["请一号,前往入口。"]); - 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(); + expect(queue).not.toContain("/audio/queue-voice/please.wav"); + expect(queue.every(inlineAudio)).toBe(true); }); }); diff --git a/web/src/hooks/useAudioAnnouncements.ts b/web/src/hooks/useAudioAnnouncements.ts index 4462521..5a90445 100644 --- a/web/src/hooks/useAudioAnnouncements.ts +++ b/web/src/hooks/useAudioAnnouncements.ts @@ -1,35 +1,43 @@ import { useCallback, useEffect, useRef } from "react"; +import digit0Audio from "../assets/queue-voice/digit-0.wav?inline"; +import digit1Audio from "../assets/queue-voice/digit-1.wav?inline"; +import digit2Audio from "../assets/queue-voice/digit-2.wav?inline"; +import digit3Audio from "../assets/queue-voice/digit-3.wav?inline"; +import digit4Audio from "../assets/queue-voice/digit-4.wav?inline"; +import digit5Audio from "../assets/queue-voice/digit-5.wav?inline"; +import digit6Audio from "../assets/queue-voice/digit-6.wav?inline"; +import digit7Audio from "../assets/queue-voice/digit-7.wav?inline"; +import digit8Audio from "../assets/queue-voice/digit-8.wav?inline"; +import digit9Audio from "../assets/queue-voice/digit-9.wav?inline"; +import entranceAudio from "../assets/queue-voice/entrance.wav?inline"; +import gapAudio from "../assets/queue-voice/gap.wav?inline"; +import numberAudio from "../assets/queue-voice/number.wav?inline"; +import pleaseAudio from "../assets/queue-voice/please.wav?inline"; +import throughAudio from "../assets/queue-voice/through.wav?inline"; -const AUDIO_BASE_PATH = "/audio/queue-voice"; const ANNOUNCEMENT_REPEAT_COUNT = 3; -const GAP_AUDIO = `${AUDIO_BASE_PATH}/gap.wav`; +const GAP_AUDIO = gapAudio; const DIGIT_AUDIO: Record = { - 零: `${AUDIO_BASE_PATH}/digit-0.wav`, - 一: `${AUDIO_BASE_PATH}/digit-1.wav`, - 二: `${AUDIO_BASE_PATH}/digit-2.wav`, - 三: `${AUDIO_BASE_PATH}/digit-3.wav`, - 四: `${AUDIO_BASE_PATH}/digit-4.wav`, - 五: `${AUDIO_BASE_PATH}/digit-5.wav`, - 六: `${AUDIO_BASE_PATH}/digit-6.wav`, - 七: `${AUDIO_BASE_PATH}/digit-7.wav`, - 八: `${AUDIO_BASE_PATH}/digit-8.wav`, - 九: `${AUDIO_BASE_PATH}/digit-9.wav`, + 零: digit0Audio, + 一: digit1Audio, + 二: digit2Audio, + 三: digit3Audio, + 四: digit4Audio, + 五: digit5Audio, + 六: digit6Audio, + 七: digit7Audio, + 八: digit8Audio, + 九: digit9Audio, }; const PHRASE_AUDIO = [ - ["前往入口", `${AUDIO_BASE_PATH}/entrance.wav`], - ["请", `${AUDIO_BASE_PATH}/please.wav`], - ["号", `${AUDIO_BASE_PATH}/number.wav`], - ["至", `${AUDIO_BASE_PATH}/through.wav`], + ["前往入口", entranceAudio], + ["请", pleaseAudio], + ["号", numberAudio], + ["至", throughAudio], ] 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 []; @@ -67,23 +75,6 @@ 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;