fix: add baidu audio for youdao dict
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { apiBaiduTTS } from "../apis";
|
||||
import { kissLog } from "../libs/log";
|
||||
import { logger } from "../libs/log";
|
||||
import { fetchData } from "../libs/fetch";
|
||||
|
||||
/**
|
||||
* 声音播放hook
|
||||
@@ -12,50 +12,97 @@ export function useAudio(src) {
|
||||
const [error, setError] = useState(null);
|
||||
const [ready, setReady] = useState(false);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const onPlay = useCallback(() => {
|
||||
audioRef.current?.play();
|
||||
const onPlay = useCallback(async () => {
|
||||
if (!audioRef.current) return;
|
||||
try {
|
||||
await audioRef.current.play();
|
||||
} catch (err) {
|
||||
logger.info("Playback failed:", err);
|
||||
setPlaying(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onPause = useCallback(() => {
|
||||
audioRef.current?.pause();
|
||||
}, []);
|
||||
|
||||
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));
|
||||
if (!src) return;
|
||||
|
||||
let ignore = false;
|
||||
let objectUrl = null;
|
||||
|
||||
setReady(false);
|
||||
setError(null);
|
||||
setPlaying(false);
|
||||
setLoading(true);
|
||||
|
||||
const audio = new Audio();
|
||||
audioRef.current = audio;
|
||||
|
||||
const handleCanPlay = () => setReady(true);
|
||||
const handlePlay = () => setPlaying(true);
|
||||
const handlePause = () => setPlaying(false);
|
||||
const handleEnded = () => setPlaying(false);
|
||||
const handleError = (e) => {
|
||||
if (!ignore) {
|
||||
setError(audio.error || e);
|
||||
setReady(false);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
audio.addEventListener("canplaythrough", handleCanPlay);
|
||||
audio.addEventListener("play", handlePlay);
|
||||
audio.addEventListener("pause", handlePause);
|
||||
audio.addEventListener("ended", handleEnded);
|
||||
audio.addEventListener("error", handleError);
|
||||
|
||||
const loadAudio = async () => {
|
||||
try {
|
||||
const data = await fetchData(src, {}, { expect: "audio" });
|
||||
if (ignore) return;
|
||||
|
||||
audio.src = data;
|
||||
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
if (!ignore) {
|
||||
logger.info("Audio fetch failed:", err);
|
||||
setError(err);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadAudio();
|
||||
|
||||
return () => {
|
||||
ignore = true;
|
||||
|
||||
audio.pause();
|
||||
audio.removeAttribute("src");
|
||||
|
||||
if (objectUrl) {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
}
|
||||
|
||||
audio.removeEventListener("canplaythrough", handleCanPlay);
|
||||
audio.removeEventListener("play", handlePlay);
|
||||
audio.removeEventListener("pause", handlePause);
|
||||
audio.removeEventListener("ended", handleEnded);
|
||||
audio.removeEventListener("error", handleError);
|
||||
};
|
||||
}, [src]);
|
||||
|
||||
return {
|
||||
loading,
|
||||
error,
|
||||
ready,
|
||||
playing,
|
||||
onPlay,
|
||||
onPause,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取语音hook
|
||||
* @param {*} text
|
||||
* @param {*} lan
|
||||
* @param {*} spd
|
||||
* @returns
|
||||
*/
|
||||
export function useTextAudio(text, lan = "uk", spd = 3) {
|
||||
const [src, setSrc] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
setSrc(await apiBaiduTTS(text, lan, spd));
|
||||
} catch (err) {
|
||||
kissLog("baidu tts", err);
|
||||
}
|
||||
})();
|
||||
}, [text, lan, spd]);
|
||||
|
||||
return useAudio(src);
|
||||
}
|
||||
|
||||
@@ -50,13 +50,13 @@ const newCacheReq = async (input, init) => {
|
||||
* @param {*} init
|
||||
* @returns
|
||||
*/
|
||||
export const getHttpCache = async ({ input, init }) => {
|
||||
export const getHttpCache = async ({ input, init, expect }) => {
|
||||
try {
|
||||
const request = await newCacheReq(input, init);
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
const response = await cache.match(request);
|
||||
if (response) {
|
||||
const res = await parseResponse(response);
|
||||
const res = await parseResponse(response, expect);
|
||||
return res;
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -99,7 +99,7 @@ export const putHttpCache = async ({
|
||||
* @param {*} res
|
||||
* @returns
|
||||
*/
|
||||
export const parseResponse = async (res) => {
|
||||
export const parseResponse = async (res, expect = null) => {
|
||||
if (!res) {
|
||||
throw new Error("Response object does not exist");
|
||||
}
|
||||
@@ -108,21 +108,45 @@ export const parseResponse = async (res) => {
|
||||
const msg = {
|
||||
url: res.url,
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
};
|
||||
if (res.headers.get("Content-Type")?.includes("json")) {
|
||||
msg.response = await res.json();
|
||||
|
||||
try {
|
||||
const errorText = await res.clone().text();
|
||||
try {
|
||||
msg.response = JSON.parse(errorText);
|
||||
} catch {
|
||||
msg.response = errorText;
|
||||
}
|
||||
} catch (e) {
|
||||
msg.response = "Unable to read error body";
|
||||
}
|
||||
|
||||
throw new Error(JSON.stringify(msg));
|
||||
}
|
||||
|
||||
const contentType = res.headers.get("Content-Type");
|
||||
if (contentType?.includes("json")) {
|
||||
return res.json();
|
||||
} else if (contentType?.includes("audio")) {
|
||||
const contentType = res.headers.get("Content-Type") || "";
|
||||
if (expect === "blob") return res.blob();
|
||||
if (expect === "text") return res.text();
|
||||
if (expect === "json") return res.json();
|
||||
if (
|
||||
expect === "audio" ||
|
||||
contentType.includes("audio") ||
|
||||
contentType.includes("image") ||
|
||||
contentType.includes("video")
|
||||
) {
|
||||
const blob = await res.blob();
|
||||
return blobToBase64(blob);
|
||||
}
|
||||
return res.text();
|
||||
|
||||
const text = await res.text();
|
||||
if (!text) return null;
|
||||
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (err) {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -99,7 +99,7 @@ export const fetchPatcher = async (input, init = {}, opts) => {
|
||||
*/
|
||||
export const fetchHandle = async ({ input, init, opts }) => {
|
||||
const res = await fetchPatcher(input, init, opts);
|
||||
return parseResponse(res);
|
||||
return parseResponse(res, opts.expect);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import VolumeUpIcon from "@mui/icons-material/VolumeUp";
|
||||
import { useAudio } from "../../hooks/Audio";
|
||||
import queryString from "query-string";
|
||||
|
||||
export default function AudioBtn({ src }) {
|
||||
export function AudioBtn({ src }) {
|
||||
const { error, ready, playing, onPlay } = useAudio(src);
|
||||
|
||||
if (error || !ready) {
|
||||
@@ -27,3 +28,10 @@ export default function AudioBtn({ src }) {
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
export function BaiduAudioBtn({ text, lan = "uk", spd = 3 }) {
|
||||
if (!text) return null;
|
||||
|
||||
const src = `https://fanyi.baidu.com/gettts?${queryString.stringify({ lan, text, spd })}`;
|
||||
return <AudioBtn src={src} />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Typography from "@mui/material/Typography";
|
||||
import AudioBtn from "./AudioBtn";
|
||||
import { AudioBtn, BaiduAudioBtn } from "./AudioBtn";
|
||||
import { OPT_DICT_BING, OPT_DICT_YOUDAO } from "../../config";
|
||||
import { apiMicrosoftDict, apiYoudaoDict } from "../../apis";
|
||||
|
||||
@@ -48,12 +48,14 @@ export const dictHandlers = {
|
||||
style={{ display: "inline-block", paddingRight: "1em" }}
|
||||
>
|
||||
<Typography component="span">{`UK [${data?.ec?.word?.ukphone}]`}</Typography>
|
||||
<BaiduAudioBtn text={data?.ec?.word?.["return-phrase"]} lan="uk" />
|
||||
</Typography>
|
||||
<Typography
|
||||
component="div"
|
||||
style={{ display: "inline-block", paddingRight: "1em" }}
|
||||
>
|
||||
<Typography component="span">{`US [${data?.ec?.word?.usphone}]`}</Typography>
|
||||
<BaiduAudioBtn text={data?.ec?.word?.["return-phrase"]} lan="en" />
|
||||
</Typography>
|
||||
</Typography>
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user