From 8ad0aa10013bbd0dde0e9b9afd5e7188f6657c60 Mon Sep 17 00:00:00 2001 From: sweetcola <838689894@qq.com> Date: Sat, 9 Oct 2021 19:32:59 +0800 Subject: [PATCH] Remake the mechanism of playing audio; (The place play audio was background. Now is page.) After remaking, audio can be paused, the whole project could be upgraded to MV3 more smoothly. But there might be some users playing audio and then close page. They can't do it after the remaking, it is a compromise. Also, the last audio will be cached now. The experience of playing audio with the same text multiple times will be better. --- src/components/TsBtn/index.tsx | 7 +- .../TsHistory/HistoryResultPanel/index.tsx | 6 +- src/constants/chromeSendMessageTypes.ts | 1 + src/entry/background/audio.ts | 43 ---- src/entry/background/context-menus.ts | 19 +- src/entry/background/index.ts | 1 - src/entry/background/on-message.ts | 25 +-- .../content/MultipleTranslateResult/index.tsx | 5 +- .../content/SingleTranslateResult/index.tsx | 5 +- src/entry/options/Options/sections/Audio.tsx | 4 +- .../popup/MultipleTranslateResult/index.tsx | 5 +- .../popup/SingleTranslateResult/index.tsx | 5 +- src/entry/separate/HandleCommands/index.tsx | 4 +- src/entry/separate/Separate/index.tsx | 5 +- src/public/play-audio.ts | 185 ++++++++++++++++++ src/public/request.ts | 61 ++++-- src/public/send.ts | 48 ++++- src/public/translate/baidu/audio.ts | 17 +- src/public/translate/google/audio.ts | 72 ++----- src/public/translate/utils.ts | 9 + 20 files changed, 362 insertions(+), 165 deletions(-) delete mode 100644 src/entry/background/audio.ts create mode 100644 src/public/play-audio.ts 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();