diff --git a/src/apis/index.js b/src/apis/index.js index ab07fcc..cf3795f 100644 --- a/src/apis/index.js +++ b/src/apis/index.js @@ -16,6 +16,7 @@ import { KV_SALT_SYNC, URL_BAIDU_LANGDETECT, URL_BAIDU_SUGGEST, + URL_BAIDU_TTS, OPT_LANGS_BAIDU, URL_TENCENT_TRANSMART, OPT_LANGS_TENCENT, @@ -95,6 +96,18 @@ export const apiBaiduSuggest = async (text) => { return []; }; +/** + * 百度语音 + * @param {*} text + * @param {*} lan + * @param {*} spd + * @returns + */ +export const apiBaiduTTS = (text, lan = "uk", spd = 3) => { + const url = `${URL_BAIDU_TTS}?${queryString.stringify({ lan, text, spd })}`; + return fetchPolyfill(url); +}; + /** * 腾讯语言识别 * @param {*} text diff --git a/src/config/index.js b/src/config/index.js index 2d22dee..3f00333 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -83,6 +83,7 @@ export const URL_MICROSOFT_TRAN = export const URL_MICROSOFT_AUTH = "https://edge.microsoft.com/translate/auth"; export const URL_BAIDU_LANGDETECT = "https://fanyi.baidu.com/langdetect"; export const URL_BAIDU_SUGGEST = "https://fanyi.baidu.com/sug"; +export const URL_BAIDU_TTS = "https://fanyi.baidu.com/gettts"; export const URL_BAIDU_WEB = "https://fanyi.baidu.com/"; export const URL_BAIDU_TRANSAPI = "https://fanyi.baidu.com/transapi"; export const URL_BAIDU_TRANSAPI_V2 = "https://fanyi.baidu.com/v2transapi"; diff --git a/src/hooks/Audio.js b/src/hooks/Audio.js new file mode 100644 index 0000000..f9f2587 --- /dev/null +++ b/src/hooks/Audio.js @@ -0,0 +1,51 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import { apiBaiduTTS } from "../apis"; + +/** + * 声音播放hook + * @param {*} src + * @returns + */ +export function useAudio(src) { + // const audioRef = useRef(new Audio(src)); + const audioRef = useRef(null); + const [error, setError] = useState(null); + const [ready, setReady] = useState(false); + const [playing, setPlaying] = useState(false); + + const play = useCallback(() => { + audioRef.current?.play(); + }, []); + + useEffect(() => { + if (!src) { + return; + } + const audio = new Audio(src); + audio.addEventListener("error", (err) => setError(err)); + audio.addEventListener("canplaythrough", () => setReady(true)); + audio.addEventListener("play", () => setPlaying(true)); + audio.addEventListener("ended", () => setPlaying(false)); + audioRef.current = audio; + }, [src]); + + return { + error, + ready, + playing, + play, + }; +} + +export function useTextAudio(text, lan = "uk", spd = 3) { + const [src, setSrc] = useState(""); + + useEffect(() => { + (async () => { + const res = await apiBaiduTTS(text, lan, spd); + setSrc(res); + })(); + }, [text, lan, spd]); + + return useAudio(src); +} diff --git a/src/libs/fetch.js b/src/libs/fetch.js index 15215f8..c5598e0 100644 --- a/src/libs/fetch.js +++ b/src/libs/fetch.js @@ -12,6 +12,7 @@ import { import { isBg } from "./browser"; import { newCacheReq, newTransReq } from "./req"; import { kissLog } from "./log"; +import { blobToBase64 } from "./utils"; const TIMEOUT = 5000; @@ -163,6 +164,9 @@ export const fetchData = async ( const contentType = res.headers.get("Content-Type"); if (contentType?.includes("json")) { return await res.json(); + } else if (contentType?.includes("audio")) { + const blob = await res.blob(); + return await blobToBase64(blob); } return await res.text(); }; diff --git a/src/libs/utils.js b/src/libs/utils.js index 4a0f9e5..77700db 100644 --- a/src/libs/utils.js +++ b/src/libs/utils.js @@ -233,3 +233,16 @@ export const isValidWord = (str) => { const regex = /^[a-zA-Z-]+$/; return regex.test(str); }; + +/** + * blob转为base64 + * @param {*} blob + * @returns + */ +export const blobToBase64 = (blob) => { + return new Promise((resolve) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result); + reader.readAsDataURL(blob); + }); +}; diff --git a/src/views/Selection/AudioBtn.js b/src/views/Selection/AudioBtn.js new file mode 100644 index 0000000..510aaeb --- /dev/null +++ b/src/views/Selection/AudioBtn.js @@ -0,0 +1,37 @@ +import IconButton from "@mui/material/IconButton"; +import VolumeUpIcon from "@mui/icons-material/VolumeUp"; +import { useTextAudio } from "../../hooks/Audio"; + +export default function AudioBtn({ text, lan = "uk" }) { + const { error, ready, playing, play } = useTextAudio(text, lan); + + if (error) { + return; + } + + if (!ready) { + return ( + + + + ); + } + + if (playing) { + return ( + + + + ); + } + + return ( + { + play(); + }} + > + + + ); +} diff --git a/src/views/Selection/DictCont.js b/src/views/Selection/DictCont.js index f8b37d5..dbaa545 100644 --- a/src/views/Selection/DictCont.js +++ b/src/views/Selection/DictCont.js @@ -2,10 +2,11 @@ import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import FavBtn from "./FavBtn"; import Typography from "@mui/material/Typography"; +import AudioBtn from "./AudioBtn"; const phonicMap = { - en_phonic: "英", - us_phonic: "美", + en_phonic: ["英", "uk"], + us_phonic: ["美", "en"], }; export default function DictCont({ dictResult }) { @@ -27,13 +28,17 @@ export default function DictCont({ dictResult }) { - + {dictResult.voice ?.map(Object.entries) .map((item) => item[0]) - .map(([key, val]) => `${phonicMap[key] || key} ${val}`) - .join(" ")} - + .map(([key, val]) => ( + + {`${phonicMap[key]?.[0] || key} ${val}`} + + + ))} +