feat: word pronunciation supported
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
|||||||
KV_SALT_SYNC,
|
KV_SALT_SYNC,
|
||||||
URL_BAIDU_LANGDETECT,
|
URL_BAIDU_LANGDETECT,
|
||||||
URL_BAIDU_SUGGEST,
|
URL_BAIDU_SUGGEST,
|
||||||
|
URL_BAIDU_TTS,
|
||||||
OPT_LANGS_BAIDU,
|
OPT_LANGS_BAIDU,
|
||||||
URL_TENCENT_TRANSMART,
|
URL_TENCENT_TRANSMART,
|
||||||
OPT_LANGS_TENCENT,
|
OPT_LANGS_TENCENT,
|
||||||
@@ -95,6 +96,18 @@ export const apiBaiduSuggest = async (text) => {
|
|||||||
return [];
|
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
|
* @param {*} text
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ export const URL_MICROSOFT_TRAN =
|
|||||||
export const URL_MICROSOFT_AUTH = "https://edge.microsoft.com/translate/auth";
|
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_LANGDETECT = "https://fanyi.baidu.com/langdetect";
|
||||||
export const URL_BAIDU_SUGGEST = "https://fanyi.baidu.com/sug";
|
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_WEB = "https://fanyi.baidu.com/";
|
||||||
export const URL_BAIDU_TRANSAPI = "https://fanyi.baidu.com/transapi";
|
export const URL_BAIDU_TRANSAPI = "https://fanyi.baidu.com/transapi";
|
||||||
export const URL_BAIDU_TRANSAPI_V2 = "https://fanyi.baidu.com/v2transapi";
|
export const URL_BAIDU_TRANSAPI_V2 = "https://fanyi.baidu.com/v2transapi";
|
||||||
|
|||||||
51
src/hooks/Audio.js
Normal file
51
src/hooks/Audio.js
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import { isBg } from "./browser";
|
import { isBg } from "./browser";
|
||||||
import { newCacheReq, newTransReq } from "./req";
|
import { newCacheReq, newTransReq } from "./req";
|
||||||
import { kissLog } from "./log";
|
import { kissLog } from "./log";
|
||||||
|
import { blobToBase64 } from "./utils";
|
||||||
|
|
||||||
const TIMEOUT = 5000;
|
const TIMEOUT = 5000;
|
||||||
|
|
||||||
@@ -163,6 +164,9 @@ export const fetchData = async (
|
|||||||
const contentType = res.headers.get("Content-Type");
|
const contentType = res.headers.get("Content-Type");
|
||||||
if (contentType?.includes("json")) {
|
if (contentType?.includes("json")) {
|
||||||
return await res.json();
|
return await res.json();
|
||||||
|
} else if (contentType?.includes("audio")) {
|
||||||
|
const blob = await res.blob();
|
||||||
|
return await blobToBase64(blob);
|
||||||
}
|
}
|
||||||
return await res.text();
|
return await res.text();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -233,3 +233,16 @@ export const isValidWord = (str) => {
|
|||||||
const regex = /^[a-zA-Z-]+$/;
|
const regex = /^[a-zA-Z-]+$/;
|
||||||
return regex.test(str);
|
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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
37
src/views/Selection/AudioBtn.js
Normal file
37
src/views/Selection/AudioBtn.js
Normal file
@@ -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 (
|
||||||
|
<IconButton disabled>
|
||||||
|
<VolumeUpIcon />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playing) {
|
||||||
|
return (
|
||||||
|
<IconButton color="primary">
|
||||||
|
<VolumeUpIcon />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
play();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VolumeUpIcon />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,10 +2,11 @@ import Box from "@mui/material/Box";
|
|||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
import FavBtn from "./FavBtn";
|
import FavBtn from "./FavBtn";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
|
import AudioBtn from "./AudioBtn";
|
||||||
|
|
||||||
const phonicMap = {
|
const phonicMap = {
|
||||||
en_phonic: "英",
|
en_phonic: ["英", "uk"],
|
||||||
us_phonic: "美",
|
us_phonic: ["美", "en"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function DictCont({ dictResult }) {
|
export default function DictCont({ dictResult }) {
|
||||||
@@ -27,13 +28,17 @@ export default function DictCont({ dictResult }) {
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Typography component="div">
|
<Typography component="div">
|
||||||
<Typography>
|
<Stack direction="row">
|
||||||
{dictResult.voice
|
{dictResult.voice
|
||||||
?.map(Object.entries)
|
?.map(Object.entries)
|
||||||
.map((item) => item[0])
|
.map((item) => item[0])
|
||||||
.map(([key, val]) => `${phonicMap[key] || key} ${val}`)
|
.map(([key, val]) => (
|
||||||
.join(" ")}
|
<span>
|
||||||
</Typography>
|
<span>{`${phonicMap[key]?.[0] || key} ${val}`}</span>
|
||||||
|
<AudioBtn text={dictResult.src} lan={phonicMap[key]?.[1]} />
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
<ul style={{ margin: "0.5em 0" }}>
|
<ul style={{ margin: "0.5em 0" }}>
|
||||||
{dictResult.content[0].mean.map(({ pre, cont }, idx) => (
|
{dictResult.content[0].mean.map(({ pre, cont }, idx) => (
|
||||||
<li key={idx}>
|
<li key={idx}>
|
||||||
|
|||||||
Reference in New Issue
Block a user