diff --git a/src/components/TsBtn/index.tsx b/src/components/TsBtn/index.tsx index 9c03234d..570c2364 100644 --- a/src/components/TsBtn/index.tsx +++ b/src/components/TsBtn/index.tsx @@ -10,7 +10,7 @@ import { } from '../../constants/chromeSendMessageTypes'; import IconFont from '../IconFont'; import './style.css'; -import { sendAudio, sendSeparate } from '../../public/send'; +import { sendSeparate } from '../../public/send'; import { getOptions } from '../../public/options'; import { debounce, isTextBox } from '../../public/utils'; import { DefaultOptions, Position } from '../../types'; @@ -26,6 +26,7 @@ import { TRANSLATE_BUTTON_TL_THIRD, TRANSLATE_BUTTON_TRANSLATE } from '../../constants/translateButtonTypes'; +import { playAudio } from '../../public/play-audio'; const initText = ''; const initPos = { x: 5, y: 5 }; @@ -132,7 +133,7 @@ const TsBtn: React.FC = () => { handleForwardTranslate(text, pos); break; case TRANSLATE_BUTTON_LISTEN: - sendAudio(text, {}); + playAudio({ text }); break; case TRANSLATE_BUTTON_COPY: navigator.clipboard.writeText(text); @@ -187,7 +188,7 @@ const TsBtn: React.FC = () => { break; case SCTS_AUDIO_COMMAND_KEY_PRESSED: text = getSelectedText(); - text && sendAudio(text, {}); + text && playAudio({ text }); break; case SCTS_CALL_OUT_COMMAND_KEY_PRESSED: dispatch(callOutPanel()); diff --git a/src/components/TsHistory/HistoryResultPanel/index.tsx b/src/components/TsHistory/HistoryResultPanel/index.tsx index f462e02b..a738ea4a 100644 --- a/src/components/TsHistory/HistoryResultPanel/index.tsx +++ b/src/components/TsHistory/HistoryResultPanel/index.tsx @@ -1,5 +1,5 @@ import React, { useLayoutEffect, useRef, useState } from 'react'; -import { sendAudio } from '../../../public/send'; +import { playAudio } from '../../../public/play-audio'; import { calculatePosition } from '../../../public/utils'; import { Translation } from '../../../redux/slice/multipleTranslateSlice'; import IconFont from '../../IconFont'; @@ -47,7 +47,7 @@ const HistoryResultPanel: React.FC = ({ translations, t { e.stopPropagation(); sendAudio(translateRequest.result.text, { source, from: translateRequest.result.from }); }} + onClick={(e) => { e.stopPropagation(); playAudio({ text: translateRequest.result.text, source, from: translateRequest.result.from }); }} /> } @@ -55,7 +55,7 @@ const HistoryResultPanel: React.FC = ({ translations, t
sendAudio(text, { source, from })} + readText={(text, from) => playAudio({ text, source, from })} /> ))} diff --git a/src/constants/chromeSendMessageTypes.ts b/src/constants/chromeSendMessageTypes.ts index 5c1571a9..d60f3d36 100644 --- a/src/constants/chromeSendMessageTypes.ts +++ b/src/constants/chromeSendMessageTypes.ts @@ -1,5 +1,6 @@ export const SCTS_TRANSLATE = 'SCTS_TRANSLATE'; export const SCTS_AUDIO = 'SCTS_AUDIO'; +export const SCTS_DETECT = 'SCTS_DETECT'; export const SCTS_CONTEXT_MENUS_CLICKED = 'SCTS_CONTEXT_MENUS_CLICKED'; export const SCTS_TRANSLATE_COMMAND_KEY_PRESSED = 'SCTS_TRANSLATE_COMMAND_KEY_PRESSED'; export const SCTS_AUDIO_COMMAND_KEY_PRESSED = 'SCTS_AUDIO_COMMAND_KEY_PRESSED'; diff --git a/src/entry/background/audio.ts b/src/entry/background/audio.ts deleted file mode 100644 index eaad8308..00000000 --- a/src/entry/background/audio.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { getLocalStorage } from "../../public/chrome-call"; -import { listenOptionsChange } from "../../public/options"; -import { DefaultOptions } from "../../types"; - -const audioPlayer = new Audio(); -audioPlayer.crossOrigin = 'anonymous'; -let index = 0; -let audioSrcList: string[] = []; - -audioPlayer.addEventListener('ended', () => { - if (index < audioSrcList.length) { - audioPlayer.src = audioSrcList[index]; - audioPlayer.play(); - ++index; - } -}); - -export const playAudio = (srcList: string | string[]) => { - if (!Array.isArray(srcList)) { - audioPlayer.src = srcList; - audioPlayer.play(); - return; - } - - if (1 > srcList.length) return; - - audioSrcList = srcList; - index = 1; - - audioPlayer.src = srcList[0]; - audioPlayer.play(); -}; - -type PickedOptions = Pick; -const keys: (keyof PickedOptions)[] = ['audioVolume', 'audioPlaybackRate']; -getLocalStorage(keys, (storage) => { - storage.audioVolume !== undefined && (audioPlayer.volume = storage.audioVolume / 100); - storage.audioPlaybackRate !== undefined && (audioPlayer.defaultPlaybackRate = storage.audioPlaybackRate); -}); -listenOptionsChange(keys, (changes) => { - changes.audioVolume !== undefined && (audioPlayer.volume = changes.audioVolume / 100); - changes.audioPlaybackRate !== undefined && (audioPlayer.defaultPlaybackRate = changes.audioPlaybackRate); -}); \ No newline at end of file diff --git a/src/entry/background/context-menus.ts b/src/entry/background/context-menus.ts index 2ba9ed4d..0a4bcf8c 100644 --- a/src/entry/background/context-menus.ts +++ b/src/entry/background/context-menus.ts @@ -1,5 +1,5 @@ import { listenOptionsChange } from '../../public/options'; -import { SCTS_CONTEXT_MENUS_CLICKED, SCTS_TRANSLATE_CURRENT_PAGE } from '../../constants/chromeSendMessageTypes'; +import { SCTS_AUDIO_COMMAND_KEY_PRESSED, SCTS_CONTEXT_MENUS_CLICKED, SCTS_TRANSLATE_CURRENT_PAGE } from '../../constants/chromeSendMessageTypes'; import { createNewTab, getI18nMessage, getLocalStorage } from '../../public/chrome-call'; import { createSeparateWindow } from './separate-window'; import { getIsContentScriptEnabled } from '../../public/utils'; @@ -11,8 +11,6 @@ import { TRANSLATE_CURRENT_PAGE, TRANSLATE_SELECTION_TEXT } from '../../constants/contextMenusIds'; -import { playAudio } from './audio'; -import { audio } from '../../public/request'; import { DefaultOptions, OptionsContextMenu } from '../../types'; type OnContextMenuClick = (info: chrome.contextMenus.OnClickData, tab?: chrome.tabs.Tab | undefined) => void; @@ -31,16 +29,13 @@ const translateSelectionText: OnContextMenuClick = async ({ selectionText }, tab } }; -const listenSelectionText: OnContextMenuClick = ({ selectionText }) => { +const listenSelectionText: OnContextMenuClick = async ({ selectionText }, tab) => { if (!selectionText) { return; } - type PickedOptions = Pick; - const tmpKeys: (keyof PickedOptions)[] = ['defaultAudioSource', 'useDotCn']; - getLocalStorage(tmpKeys, (storage) => { - const defaultAudioSource = storage.defaultAudioSource; - const useDotCn = storage.useDotCn; - audio({ source: defaultAudioSource, defaultSource: defaultAudioSource, requestObj: { text: selectionText, com: !useDotCn } }, uri => playAudio(uri)); - }); + if (tab?.id && tab.id >= 0) { + const enabled = await getIsContentScriptEnabled(tab.id); + enabled && chrome.tabs.sendMessage(tab.id, { type: SCTS_AUDIO_COMMAND_KEY_PRESSED }); + } }; const openThisPageWithPdfViewer: OnContextMenuClick = (info, tab) => { @@ -102,7 +97,7 @@ chrome.contextMenus.onClicked.addListener((info, tab) => { translateSelectionText(info, tab); return; case LISTEN_SELECTION_TEXT: - listenSelectionText(info); + listenSelectionText(info, tab); return; case OPEN_SEPARATE_WINDOW: openSeparateTranslateWindow(); diff --git a/src/entry/background/index.ts b/src/entry/background/index.ts index bbd9ed9d..8ad2c8ce 100644 --- a/src/entry/background/index.ts +++ b/src/entry/background/index.ts @@ -1,7 +1,6 @@ import './context-menus'; import './install'; import './on-message'; -import './audio'; import './tabs'; import './commands'; import './web-request'; \ No newline at end of file diff --git a/src/entry/background/on-message.ts b/src/entry/background/on-message.ts index 87a20312..9d8257d7 100644 --- a/src/entry/background/on-message.ts +++ b/src/entry/background/on-message.ts @@ -1,14 +1,11 @@ import * as types from '../../constants/chromeSendMessageTypes'; -import { translate, audio } from '../../public/request'; -import { playAudio } from './audio'; +import { translate, audio, detect } from '../../public/request'; import { listenOptionsChange } from '../../public/options'; import { getLocalStorage } from '../../public/chrome-call'; -import { GOOGLE_COM } from '../../constants/translateSource'; import { LANG_EN } from '../../constants/langCode'; import { createSeparateWindow } from './separate-window'; import { DefaultOptions } from '../../types'; -let defaultAudioSource = GOOGLE_COM; let useDotCn = false; let preferredLanguage = LANG_EN; let secondPreferredLanguage = LANG_EN; @@ -16,13 +13,11 @@ let secondPreferredLanguage = LANG_EN; type PickedOptions = Pick; const keys: (keyof PickedOptions)[] = ['defaultAudioSource', 'useDotCn', 'preferredLanguage', 'secondPreferredLanguage']; getLocalStorage(keys, (storage) => { - defaultAudioSource = storage.defaultAudioSource; useDotCn = storage.useDotCn; preferredLanguage = storage.preferredLanguage; secondPreferredLanguage = storage.secondPreferredLanguage; }); listenOptionsChange(keys, (changes) => { - changes.defaultAudioSource !== undefined && (defaultAudioSource = changes.defaultAudioSource); changes.useDotCn !== undefined && (useDotCn = changes.useDotCn); changes.preferredLanguage !== undefined && (preferredLanguage = changes.preferredLanguage); changes.secondPreferredLanguage !== undefined && (secondPreferredLanguage = changes.secondPreferredLanguage); @@ -44,12 +39,20 @@ chrome.runtime.onMessage.addListener( return true; case types.SCTS_AUDIO: if (payload) { - payload.requestObj.com = !useDotCn; - !payload.source && (payload.source = defaultAudioSource); - payload.defaultSource = defaultAudioSource; - audio(payload, uri => playAudio(uri)); + payload.com = !useDotCn; + audio(payload, (result) => { + sendResponse(result); + }); } - return false; + return true; + case types.SCTS_DETECT: + if (payload) { + payload.com = !useDotCn; + detect(payload, (result) => { + sendResponse(result); + }); + } + return true; case types.SCTS_SEND_TEXT_TO_SEPARATE_WINDOW: payload?.text && createSeparateWindow(payload.text); return false; diff --git a/src/entry/content/MultipleTranslateResult/index.tsx b/src/entry/content/MultipleTranslateResult/index.tsx index d0ec6971..3951d926 100644 --- a/src/entry/content/MultipleTranslateResult/index.tsx +++ b/src/entry/content/MultipleTranslateResult/index.tsx @@ -3,7 +3,7 @@ import MtResult from '../../../components/MtResult'; import MtAddSource from '../../../components/MtAddSource'; import LanguageSelection from '../../../components/LanguageSelection'; import RawText from '../../../components/RawText'; -import { sendTranslate, sendAudio } from '../../../public/send'; +import { sendTranslate } from '../../../public/send'; import { mtLangCode } from '../../../constants/langCode'; import './style.css'; import { getMessage } from '../../../public/i18n'; @@ -11,6 +11,7 @@ import { useAppDispatch, useAppSelector, useInsertResult, useIsEnable } from '.. import { textPreprocessing } from '../../../public/text-preprocessing'; import { mtAddSource, mtRemoveSource, mtRequestError, mtRequestFinish, mtRequestStart, mtSetFromAndTo, mtSetText } from '../../../redux/slice/multipleTranslateSlice'; import { addHistory, updateHistoryError, updateHistoryFinish } from '../../../redux/slice/translateHistorySlice'; +import { playAudio } from '../../../public/play-audio'; type MultipleTranslateResultProps = { showRtAndLs: boolean; @@ -125,7 +126,7 @@ const MultipleTranslateResult: React.FC = ({ showR translateRequest={translateRequest} key={source} remove={() => handleRemoveSource(source)} - readText={(text, from) => sendAudio(text, { source, from })} + readText={(text, from) => playAudio({ text, source, from })} retry={() => handleRetry(source)} setText={handleSetText} insertResult={canInsertResult ? result => insertResultToggle(translateId, source, result) : undefined} diff --git a/src/entry/content/SingleTranslateResult/index.tsx b/src/entry/content/SingleTranslateResult/index.tsx index c992cedd..a0cd532d 100644 --- a/src/entry/content/SingleTranslateResult/index.tsx +++ b/src/entry/content/SingleTranslateResult/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useCallback, useRef, useLayoutEffect, useState } from 'react'; -import { sendTranslate, sendAudio } from '../../../public/send'; +import { sendTranslate } from '../../../public/send'; import TsResult from '../../../components/TsResult'; import LanguageSelection from '../../../components/LanguageSelection'; import RawText from '../../../components/RawText'; @@ -11,6 +11,7 @@ import './style.css'; import { textPreprocessing } from '../../../public/text-preprocessing'; import { stRequestError, stRequestFinish, stRequestStart, stSetFromAndTo, stSetSourceFromTo, stSetText } from '../../../redux/slice/singleTranslateSlice'; import { addHistory, updateHistoryError, updateHistoryFinish } from '../../../redux/slice/translateHistorySlice'; +import { playAudio } from '../../../public/play-audio'; type SingleTranslateResultProps = { showRtAndLs: boolean; @@ -80,7 +81,7 @@ const SingleTranslateResult: React.FC = ({ showRtAnd }, [dispatch]); const handleReadText = useCallback((text: string, from: string) => { - text && sendAudio(text, { source, from }); + text && playAudio({ text, source, from }); }, [source]); const handleRetry = useCallback(() => { diff --git a/src/entry/options/Options/sections/Audio.tsx b/src/entry/options/Options/sections/Audio.tsx index 8220319e..9088c8e3 100644 --- a/src/entry/options/Options/sections/Audio.tsx +++ b/src/entry/options/Options/sections/Audio.tsx @@ -4,7 +4,7 @@ import Slider, { SliderFormat, SliderMarks } from '../../../../components/Slider import SourceSelect from '../../../../components/SourceSelect'; import { audioSource } from '../../../../constants/translateSource'; import { getMessage } from '../../../../public/i18n'; -import { sendAudio } from '../../../../public/send'; +import { playAudio } from '../../../../public/play-audio'; import { DefaultOptions } from '../../../../types'; const marksVolume: SliderMarks = [ @@ -75,7 +75,7 @@ const Audio: React.FC = ({ updateStorage, defaultAudioSource, audioV />
- +
); diff --git a/src/entry/popup/MultipleTranslateResult/index.tsx b/src/entry/popup/MultipleTranslateResult/index.tsx index a2a84cd1..c7a2e20e 100644 --- a/src/entry/popup/MultipleTranslateResult/index.tsx +++ b/src/entry/popup/MultipleTranslateResult/index.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useRef } from 'react'; import './style.css'; import RawText from '../../../components/RawText'; import LanguageSelection from '../../../components/LanguageSelection'; -import { sendTranslate, sendAudio } from '../../../public/send'; +import { sendTranslate } from '../../../public/send'; import MtAddSource from '../../../components/MtAddSource'; import MtResult from '../../../components/MtResult'; import { mtLangCode } from '../../../constants/langCode'; @@ -12,6 +12,7 @@ import { textPreprocessing } from '../../../public/text-preprocessing'; import { useAppDispatch, useAppSelector } from '../../../public/react-use'; import { mtAddSource, mtRemoveSource, mtRequestError, mtRequestFinish, mtRequestStart, mtSetFromAndTo, mtSetText } from '../../../redux/slice/multipleTranslateSlice'; import { callOutPanel } from '../../../redux/slice/panelStatusSlice'; +import { playAudio } from '../../../public/play-audio'; type MultipleTranslateResultProps = { autoTranslateAfterInput: boolean; @@ -105,7 +106,7 @@ const MultipleTranslateResult: React.FC = ({ autoT translateRequest={translateRequest} key={source} remove={() => handleRemoveSource(source)} - readText={(text, from) => sendAudio(text, { source, from })} + readText={(text, from) => playAudio({ text, source, from })} retry={() => handleRetry(source)} setText={handleSetText} /> diff --git a/src/entry/popup/SingleTranslateResult/index.tsx b/src/entry/popup/SingleTranslateResult/index.tsx index 5c3e1b61..37dd3051 100644 --- a/src/entry/popup/SingleTranslateResult/index.tsx +++ b/src/entry/popup/SingleTranslateResult/index.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useRef } from 'react'; import LanguageSelection from '../../../components/LanguageSelection'; -import { sendAudio, sendTranslate } from '../../../public/send'; +import { sendTranslate } from '../../../public/send'; import RawText from '../../../components/RawText'; import TsResult from '../../../components/TsResult'; import { langCode } from '../../../constants/langCode'; @@ -11,6 +11,7 @@ import { textPreprocessing } from '../../../public/text-preprocessing'; import { useAppDispatch, useAppSelector } from '../../../public/react-use'; import { stRequestError, stRequestFinish, stRequestStart, stSetFromAndTo, stSetSourceFromTo, stSetText } from '../../../redux/slice/singleTranslateSlice'; import { callOutPanel } from '../../../redux/slice/panelStatusSlice'; +import { playAudio } from '../../../public/play-audio'; type SingleTranslateResultProps = { autoTranslateAfterInput: boolean; @@ -47,7 +48,7 @@ const SingleTranslateResult: React.FC = ({ autoTrans }, [dispatch]); const handleReadText = useCallback((text: string, from: string) => { - sendAudio(text, { source, from }); + playAudio({ text, source, from }); }, [source]); const handleSourceChange = useCallback((targetSource: string) => { diff --git a/src/entry/separate/HandleCommands/index.tsx b/src/entry/separate/HandleCommands/index.tsx index 078c2fe3..8c0e5ae2 100644 --- a/src/entry/separate/HandleCommands/index.tsx +++ b/src/entry/separate/HandleCommands/index.tsx @@ -7,8 +7,8 @@ import { SCTS_SEPARATE_WINDOW_SET_TEXT, SCTS_TRANSLATE_COMMAND_KEY_PRESSED } from '../../../constants/chromeSendMessageTypes'; +import { playAudio } from '../../../public/play-audio'; import { useAppDispatch, useOnExtensionMessage } from '../../../public/react-use'; -import { sendAudio } from '../../../public/send'; import { getSelectedText } from '../../../public/utils/get-selection'; import { mtSetText } from '../../../redux/slice/multipleTranslateSlice'; import { callOutPanel } from '../../../redux/slice/panelStatusSlice'; @@ -33,7 +33,7 @@ const HandleCommands: React.FC = () => { break; case SCTS_AUDIO_COMMAND_KEY_PRESSED: text = getSelectedText(); - text && sendAudio(text, {}); + text && playAudio({ text }); break; case SCTS_CALL_OUT_COMMAND_KEY_PRESSED: dispatch(callOutPanel()); diff --git a/src/entry/separate/Separate/index.tsx b/src/entry/separate/Separate/index.tsx index ee74acb3..9739bbdc 100644 --- a/src/entry/separate/Separate/index.tsx +++ b/src/entry/separate/Separate/index.tsx @@ -6,7 +6,7 @@ import MtResult from '../../../components/MtResult'; import RawText from '../../../components/RawText'; import { mtLangCode } from '../../../constants/langCode'; import { openOptionsPage, setLocalStorage } from '../../../public/chrome-call'; -import { sendAudio, sendTranslate } from '../../../public/send'; +import { sendTranslate } from '../../../public/send'; import './style.css'; import '../../../components/PopupHeader/style.css'; import { useAppDispatch, useAppSelector, useOptions } from '../../../public/react-use'; @@ -16,6 +16,7 @@ import { textPreprocessing } from '../../../public/text-preprocessing'; import { mtAddSource, mtRemoveSource, mtRequestError, mtRequestFinish, mtRequestStart, mtSetFromAndTo, mtSetText } from '../../../redux/slice/multipleTranslateSlice'; import { DefaultOptions } from '../../../types'; import { callOutPanel } from '../../../redux/slice/panelStatusSlice'; +import { playAudio } from '../../../public/play-audio'; type PickedOptions = Pick; const useOptionsDependency: (keyof PickedOptions)[] = ['styleVarsList', 'styleVarsIndex', 'rememberStwSizeAndPosition', 'autoTranslateAfterInput']; @@ -159,7 +160,7 @@ const Separate: React.FC = () => { translateRequest={translateRequest} key={source} remove={() => handleRemoveSource(source)} - readText={(text, from) => sendAudio(text, { source, from })} + readText={(text, from) => playAudio({ text, source, from })} retry={() => handleRetry(source)} setText={handleSetText} /> diff --git a/src/public/play-audio.ts b/src/public/play-audio.ts new file mode 100644 index 00000000..a2b0c47d --- /dev/null +++ b/src/public/play-audio.ts @@ -0,0 +1,185 @@ +import { GOOGLE_COM } from '../constants/translateSource'; +import { DefaultOptions } from '../types'; +import { getLocalStorage } from './chrome-call'; +import { listenOptionsChange } from './options'; +import { sendDetect, sendAudio } from './send'; + +const audio = new Audio(); + +let defaultAudioSource = GOOGLE_COM; + +type AudioCache = { + textList: string[]; + source: string; + text: string; + from: string; + detectedFrom: string; + dataUriList: string[]; + index: number; + requesting: boolean; + onPause: undefined | (() => void); + id: number; +}; + +let audioCache: AudioCache = { + textList: [], + source: '', + text: '', + from: '', + detectedFrom: '', + dataUriList: [], + index: 0, + requesting: false, + onPause: undefined, + id: 0 +}; + +export const playAudio = ({ text, source, from = '' }: { text: string, source?: string, from?: string }, onPause?: () => void) => { + if (!audio.paused) { + pauseAudio(); + } + + if (!source) { + source = defaultAudioSource; + } + + if (audioCache.text === text && audioCache.source === source && audioCache.from === from && audioCache.detectedFrom) { + audioCache.index = 0; + audioCache.onPause = onPause; + startPlaying(); + return; + } + + let textList: string[] = []; + + switch (source) { + case GOOGLE_COM: + textList = getTextList(text, 200); + break; + default: + textList = [text]; + break; + } + + audioCache.textList = textList; + audioCache.source = source; + audioCache.text = text; + audioCache.from = from; + audioCache.detectedFrom = from; + audioCache.dataUriList = []; + audioCache.index = 0; + audioCache.requesting = false; + audioCache.onPause = onPause; + audioCache.id = audioCache.id + 1; + + if (audioCache.detectedFrom) { + startPlaying(); + } + else { + sendDetect({ text: audioCache.textList[0], source }, (result) => { + if (result.suc && result.text === audioCache.textList[0] && source === audioCache.source) { + audioCache.detectedFrom = result.data; + startPlaying(); + } + }); + } +}; + +export const pauseAudio = () => { + audio.pause(); + audioCache.onPause?.(); +}; + +const startPlaying = () => { + const { textList, source, detectedFrom, index, dataUriList, id } = audioCache; + + if (index >= textList.length) { + audioCache.onPause?.(); + return; + } + + if (dataUriList[index]) { + play(dataUriList[index]); + return; + } + + if (!audioCache.requesting) { + sendAudio({ text: textList[index], source, from: detectedFrom, index }, (result) => { + if (id === audioCache.id) { + if (result.suc) { + audioCache.dataUriList[index] = result.data; + play(result.data); + } + audioCache.requesting = false; + } + }); + + audioCache.requesting = true; + } +}; + +audio.addEventListener('ended', () => { + startPlaying(); +}); + +const play = (dataURL: string) => { + audio.src = dataURL; + audio.play().catch(); + + ++audioCache.index; +}; + +const getTextList = (text: string, textLength: number) => { + let arr: string[] = []; + let textArr = []; + + while (text) { + const index = text.search(/\.|。|\?|?|,|,|:|:|;|;|\s|\n/g); + if (index >= 0) { + textArr.push(text.substr(0, index + 1)); + text = text.substr(index + 1, text.length); + } else { + textArr.push(text); + break; + } + } + + textArr.reduce((total, value, index) => { + let { length, str } = total; + if (length + value.length <= textLength) { + length += value.length; + str += value; + } + else { + str && arr.push(str); + + if (value.length > textLength) { + while (value.length > textLength) { + arr.push(value.substr(0, textLength)); + value = value.substr(textLength, value.length); + } + } + + length = value.length; + str = value; + } + (index === textArr.length - 1 && str) && arr.push(str); + + return { length, str }; + }, { length: 0, str: '' }); + + return arr; +}; + +type PickedOptions = Pick; +const keys: (keyof PickedOptions)[] = ['audioVolume', 'audioPlaybackRate', 'defaultAudioSource']; +getLocalStorage(keys, (storage) => { + audio.volume = storage.audioVolume / 100; + audio.defaultPlaybackRate = storage.audioPlaybackRate; + defaultAudioSource = storage.defaultAudioSource; +}); +listenOptionsChange(keys, (changes) => { + changes.audioVolume !== undefined && (audio.volume = changes.audioVolume / 100); + changes.audioPlaybackRate !== undefined && (audio.defaultPlaybackRate = changes.audioPlaybackRate); + changes.defaultAudioSource !== undefined && (defaultAudioSource = changes.defaultAudioSource); +}); \ No newline at end of file diff --git a/src/public/request.ts b/src/public/request.ts index ebbafa64..02cb7f1f 100644 --- a/src/public/request.ts +++ b/src/public/request.ts @@ -2,8 +2,7 @@ import { GOOGLE_COM, BING_COM, MOJIDICT_COM, - BAIDU_COM, - audioSource + BAIDU_COM } from '../constants/translateSource'; import google from '../public/translate/google'; import bing from '../public/translate/bing'; @@ -11,7 +10,7 @@ import mojidict from '../public/translate/mojidict'; import baidu from '../public/translate/baidu'; import { bingSwitchLangCode, baiduSwitchLangCode } from '../public/switch-lang-code'; import { SOURCE_ERROR } from '../constants/errorCodes'; -import { TranslateCallback } from './send'; +import { AudioCallback, DetectCallback, TranslateCallback } from './send'; import { getError } from './translate/utils'; type TranslateRequestObject = { @@ -66,36 +65,60 @@ export const translate = ({ source, translateId, requestObj }: TranslateRequestO type AudioRequestObject = { source: string; - defaultSource: string; - requestObj: { - text: string; - from?: string; - com?: boolean; - } -} - -export const audio = ({ source, requestObj, defaultSource }: AudioRequestObject, cb: (uri: string | string[]) => void) => { - audioSource.findIndex(v => v.source === source) < 0 && (source = defaultSource); + text: string; + from: string; + com: boolean; + index: number; +}; +export const audio = (requestObject: AudioRequestObject, cb: AudioCallback) => { let audio; - switch (source) { + switch (requestObject.source) { case GOOGLE_COM: audio = google.audio; break; case BING_COM: audio = bing.audio; - requestObj.from = bingSwitchLangCode(requestObj.from ?? ''); + requestObject.from = bingSwitchLangCode(requestObject.from ?? ''); break; case BAIDU_COM: audio = baidu.audio; - requestObj.from = baiduSwitchLangCode(requestObj.from ?? ''); + requestObject.from = baiduSwitchLangCode(requestObject.from ?? ''); break; default: audio = google.audio; break; } - audio(requestObj) - .then(uri => cb && cb(uri)) - .catch(err => console.error(err.code)); + audio(requestObject) + .then(dataUri => cb({ suc: true, data: dataUri, text: requestObject.text, index: requestObject.index })) + .catch(err => cb({ suc: false, code: err.code, text: requestObject.text, index: requestObject.index })); +}; + +type DetectRequestObject = { + source: string; + text: string; + com: boolean; +}; + +export const detect = (requestObject: DetectRequestObject, cb: DetectCallback) => { + let detect; + switch (requestObject.source) { + case GOOGLE_COM: + detect = google.detect; + break; + case BING_COM: + detect = bing.detect; + break; + case BAIDU_COM: + detect = baidu.detect; + break; + default: + detect = google.detect; + break; + } + + detect(requestObject) + .then(langCode => cb({ suc: true, text: requestObject.text, data: langCode })) + .catch(err => cb({ suc: false, code: err.code, text: requestObject.text })); }; \ No newline at end of file diff --git a/src/public/send.ts b/src/public/send.ts index e493e00a..2b5e7d19 100644 --- a/src/public/send.ts +++ b/src/public/send.ts @@ -11,8 +11,31 @@ type TranslateResponse = { data: TranslateResult; translateId: number; }; +type AudioResponse = { + suc: false; + text: string; + index: number; + code: string; +} | { + suc: true; + text: string; + index: number; + data: string; +}; +type DetectResponse = { + suc: false; + text: string; + code: string; +} | { + suc: true; + text: string; + data: string; +}; export type TranslateCallback = (response: TranslateResponse) => void; +export type AudioCallback = (response: AudioResponse) => void; +export type DetectCallback = (response: DetectResponse) => void; + type SendTranslatePayload = { source: string; translateId: number; @@ -46,13 +69,32 @@ export const sendTranslate = (text: string, { source, from, to, translateId }: { chromeSendMessage(action, cb); }; -export const sendAudio = (text: string, { source = '', from = '' }) => { +export const sendAudio = ({ text, source, from, index }: { text: string; source: string; from: string; index: number }, cb: AudioCallback) => { const action = { type: types.SCTS_AUDIO, - payload: packData(text, { source, from }) + payload: { text, source, from, index } + }; + + try { + chrome.runtime.sendMessage(action, cb); + } + catch { + cb({ suc: false, text, code: EXTENSION_UPDATED, index }); + } +}; + +export const sendDetect = ({ text, source }: { text: string; source: string; }, cb: DetectCallback) => { + const action = { + type: types.SCTS_DETECT, + payload: { text, source } }; - chromeSendMessage(action); + try { + chrome.runtime.sendMessage(action, cb); + } + catch { + cb({ suc: false, text, code: EXTENSION_UPDATED }); + } }; export const sendSeparate = (text: string) => { diff --git a/src/public/translate/baidu/audio.ts b/src/public/translate/baidu/audio.ts index 8996f973..5649eb21 100644 --- a/src/public/translate/baidu/audio.ts +++ b/src/public/translate/baidu/audio.ts @@ -1,7 +1,7 @@ -import { getQueryString, getError } from '../utils'; +import { getQueryString, getError, blobToDataURL, fetchData } from '../utils'; import { detect } from './detect'; import { langCode } from './lang-code'; -import { LANGUAGE_NOT_SOPPORTED } from '../error-codes'; +import { LANGUAGE_NOT_SOPPORTED, RESULT_ERROR } from '../error-codes'; import { AudioParams } from '../translate-types'; export const audio = async ({ text, from = '' }: AudioParams) => { @@ -17,7 +17,16 @@ export const audio = async ({ text, from = '' }: AudioParams) => { spd: 3, source: 'web' }; - url += getQueryString(params); - return url; + const res = await fetchData(url + getQueryString(params)); + + try { + const blob = await res.blob(); + + const dataURL: string = await blobToDataURL(blob); + + return dataURL; + } catch (err) { + throw getError(RESULT_ERROR); + } }; \ No newline at end of file diff --git a/src/public/translate/google/audio.ts b/src/public/translate/google/audio.ts index d902a4a5..1d8df16b 100644 --- a/src/public/translate/google/audio.ts +++ b/src/public/translate/google/audio.ts @@ -1,68 +1,36 @@ -import { getQueryString, getError } from '../utils'; +import { getQueryString, getError, fetchData, blobToDataURL } from '../utils'; import { langCode } from './lang-code'; -import { LANGUAGE_NOT_SOPPORTED } from '../error-codes'; +import { LANGUAGE_NOT_SOPPORTED, RESULT_ERROR } from '../error-codes'; import { detect } from './detect'; import { AudioParams } from '../translate-types'; +const MAX_TEXT_LENGTH = 200; + export const audio = async ({ text, from = '' }: AudioParams) => { + if (text.length > MAX_TEXT_LENGTH) { throw getError('MAX_LENGTH_EXCEED'); } + from = from || await detect({ text }); if (!(from in langCode)) { throw getError(LANGUAGE_NOT_SOPPORTED); } let url = `https://translate.googleapis.com/translate_tts`; - let arr = getTextArray(text); - - for (let i = 0; i < arr.length; ++i) { - const params = { - client: 'gtx', - ie: 'UTF-8', - tl: from, - q: encodeURIComponent(arr[i]) - }; - - arr[i] = url + getQueryString(params); - } - - return arr; -}; + const params = { + client: 'gtx', + ie: 'UTF-8', + tl: from, + q: encodeURIComponent(text) + }; -const MAX_TEXT_LENGTH = 200; + const res = await fetchData(url + getQueryString(params)); -const getTextArray = (text: string) => { - let arr: string[] = []; - let textArr = []; + try { + const blob = await res.blob(); - while (text) { - const index = text.search(/\.|。|\?|?|,|,|:|:|;|;|\s|\n/g); - if (index >= 0) { - textArr.push(text.substr(0, index + 1)); - text = text.substr(index + 1, text.length); - } else { - textArr.push(text); - break; - } + const dataURL: string = await blobToDataURL(blob); + + return dataURL; + } catch (err) { + throw getError(RESULT_ERROR); } - - textArr.reduce((total, value, index) => { - let {length, str} = total; - if (length + value.length <= MAX_TEXT_LENGTH) { - length += value.length; - str += value; - } else { - str && arr.push(str); - if (value.length > MAX_TEXT_LENGTH) { - while (value.length > MAX_TEXT_LENGTH) { - arr.push(value.substr(0, MAX_TEXT_LENGTH)); - value = value.substr(MAX_TEXT_LENGTH, value.length); - } - } - length = value.length; - str = value; - } - (index === textArr.length - 1 && str) && arr.push(str); - return {length, str}; - }, {length: 0, str: ''}); - - return arr; }; \ No newline at end of file diff --git a/src/public/translate/utils.ts b/src/public/translate/utils.ts index 12806427..fa0ed988 100644 --- a/src/public/translate/utils.ts +++ b/src/public/translate/utils.ts @@ -18,6 +18,15 @@ export const getQueryString = (params: { [key: string]: string | number | (strin return search; }; +export const blobToDataURL = (blob: Blob): Promise => { + return new Promise((resolve) => { + var reader = new FileReader(); + reader.onload = () => { + resolve(reader.result as string); + }; + reader.readAsDataURL(blob); + }); +}; export const getError = (code: string): Error & { code: string } => { let error: Error = new Error();