From 21fce9f3d98e4381f8813cf9c63410ca1dbd7f91 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 18 Jan 2021 22:01:08 -0500 Subject: [PATCH] Audio system refactoring (#1275) * Simplify details * Simplify audio creation * Return an array of sources instead of a single item * Use sourceIndex instead of index * Rename APIs * Return more info about the source * Return source instead of sourceIndex --- ext/bg/js/audio-downloader.js | 41 ++++++++++++------------ ext/bg/js/backend.js | 8 ++--- ext/mixed/js/api.js | 4 +-- ext/mixed/js/audio-system.js | 60 +++++++++++++++-------------------- ext/mixed/js/display-audio.js | 7 ++-- 5 files changed, 55 insertions(+), 65 deletions(-) diff --git a/ext/bg/js/audio-downloader.js b/ext/bg/js/audio-downloader.js index 19e9c368..171f5944 100644 --- a/ext/bg/js/audio-downloader.js +++ b/ext/bg/js/audio-downloader.js @@ -34,7 +34,7 @@ class AudioDownloader { ]); } - async getInfo(source, expression, reading, details) { + async getExpressionAudioInfoList(source, expression, reading, details) { const handler = this._getInfoHandlers.get(source); if (typeof handler === 'function') { try { @@ -43,23 +43,22 @@ class AudioDownloader { // NOP } } - return null; + return []; } - async downloadAudio(sources, expression, reading, details) { + async downloadExpressionAudio(sources, expression, reading, details) { for (const source of sources) { - const info = await this.getInfo(source, expression, reading, details); - if (info === null) { continue; } - - switch (info.type) { - case 'url': - try { - const {details: {url}} = info; - return await this._downloadAudioFromUrl(url); - } catch (e) { - // NOP - } - break; + const infoList = await this.getExpressionAudioInfoList(source, expression, reading, details); + for (const info of infoList) { + switch (info.type) { + case 'url': + try { + return await this._downloadAudioFromUrl(info.url); + } catch (e) { + // NOP + } + break; + } } } @@ -90,7 +89,7 @@ class AudioDownloader { } const url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`; - return {type: 'url', details: {url}}; + return [{type: 'url', url}]; } async _getInfoJpod101Alternate(expression, reading) { @@ -128,7 +127,7 @@ class AudioDownloader { const htmlReading = dom.getTextContent(htmlReadings[0]); if (htmlReading && (!reading || reading === htmlReading)) { url = this._normalizeUrl(url, response.url); - return {type: 'url', details: {url}}; + return [{type: 'url', url}]; } } catch (e) { // NOP @@ -159,7 +158,7 @@ class AudioDownloader { let url = dom.getAttribute(source, 'src'); if (url !== null) { url = this._normalizeUrl(url, response.url); - return {type: 'url', details: {url}}; + return [{type: 'url', url}]; } } } @@ -174,14 +173,14 @@ class AudioDownloader { if (!textToSpeechVoice) { throw new Error('No voice'); } - return {type: 'tts', details: {text: expression, voice: textToSpeechVoice}}; + return [{type: 'tts', text: expression, voice: textToSpeechVoice}]; } async _getInfoTextToSpeechReading(expression, reading, {textToSpeechVoice}) { if (!textToSpeechVoice) { throw new Error('No voice'); } - return {type: 'tts', details: {text: reading || expression, voice: textToSpeechVoice}}; + return [{type: 'tts', text: reading || expression, voice: textToSpeechVoice}]; } async _getInfoCustom(expression, reading, {customSourceUrl}) { @@ -190,7 +189,7 @@ class AudioDownloader { } const data = {expression, reading}; const url = customSourceUrl.replace(/\{([^}]*)\}/g, (m0, m1) => (Object.prototype.hasOwnProperty.call(data, m1) ? `${data[m1]}` : m0)); - return {type: 'url', details: {url}}; + return [{type: 'url', url}]; } async _downloadAudioFromUrl(url) { diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 433f5a93..2949cbed 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -99,7 +99,7 @@ class Backend { ['noteView', {async: true, contentScript: true, handler: this._onApiNoteView.bind(this)}], ['suspendAnkiCardsForNote', {async: true, contentScript: true, handler: this._onApiSuspendAnkiCardsForNote.bind(this)}], ['commandExec', {async: false, contentScript: true, handler: this._onApiCommandExec.bind(this)}], - ['getDefinitionAudioInfo', {async: true, contentScript: true, handler: this._onApiGetDefinitionAudioInfo.bind(this)}], + ['getExpressionAudioInfoList', {async: true, contentScript: true, handler: this._onApiGetExpressionAudioInfoList.bind(this)}], ['downloadDefinitionAudio', {async: true, contentScript: true, handler: this._onApiDownloadDefinitionAudio.bind(this)}], ['screenshotGet', {async: true, contentScript: true, handler: this._onApiScreenshotGet.bind(this)}], ['sendMessageToFrame', {async: false, contentScript: true, handler: this._onApiSendMessageToFrame.bind(this)}], @@ -500,8 +500,8 @@ class Backend { return this._runCommand(command, params); } - async _onApiGetDefinitionAudioInfo({source, expression, reading, details}) { - return await this._audioDownloader.getInfo(source, expression, reading, details); + async _onApiGetExpressionAudioInfoList({source, expression, reading, details}) { + return await this._audioDownloader.getExpressionAudioInfoList(source, expression, reading, details); } async _onApiDownloadDefinitionAudio({sources, expression, reading, details}) { @@ -1528,7 +1528,7 @@ class Backend { } async _downloadDefinitionAudio(sources, expression, reading, details) { - return await this._audioDownloader.downloadAudio(sources, expression, reading, details); + return await this._audioDownloader.downloadExpressionAudio(sources, expression, reading, details); } async _injectAnkNoteMedia(ankiConnect, timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails) { diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 433a52e2..03e58f5e 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -97,8 +97,8 @@ const api = (() => { return this._invoke('suspendAnkiCardsForNote', {noteId}); } - getDefinitionAudioInfo(source, expression, reading, details) { - return this._invoke('getDefinitionAudioInfo', {source, expression, reading, details}); + getExpressionAudioInfoList(source, expression, reading, details) { + return this._invoke('getExpressionAudioInfoList', {source, expression, reading, details}); } downloadDefinitionAudio(sources, expression, reading, details) { diff --git a/ext/mixed/js/audio-system.js b/ext/mixed/js/audio-system.js index 19c85690..f42fd657 100644 --- a/ext/mixed/js/audio-system.js +++ b/ext/mixed/js/audio-system.js @@ -36,47 +36,30 @@ class AudioSystem { eventListeners.addEventListener(speechSynthesis, 'voiceschanged', onVoicesChanged, false); } - async createDefinitionAudio(sources, expression, reading, details) { + async createExpressionAudio(sources, expression, reading, details) { const key = [expression, reading]; const cacheValue = this._cache.get(key); if (typeof cacheValue !== 'undefined') { - const {audio, source} = cacheValue; - const index = sources.indexOf(source); - if (index >= 0) { - return {audio, index}; - } + return cacheValue; } for (let i = 0, ii = sources.length; i < ii; ++i) { const source = sources[i]; - const info = await this._getAudioInfo(source, expression, reading, details); - if (info === null) { continue; } - - let audio; - try { - switch (info.type) { - case 'url': - { - const {details: {url}} = info; - audio = await this.createAudio(url); - } - break; - case 'tts': - { - const {details: {text, voice}} = info; - audio = this.createTextToSpeechAudio(text, voice); - } - break; - default: - throw new Error(`Unsupported type: ${info.type}`); + const infoList = await await api.getExpressionAudioInfoList(source, expression, reading, details); + for (let j = 0, jj = infoList.length; j < jj; ++j) { + const info = infoList[j]; + let audio; + try { + audio = await this.createAudioFromInfo(info); + } catch (e) { + continue; } - } catch (e) { - continue; - } - this._cache.set(key, {audio, source}); - return {audio, index: i}; + const result = {audio, source, infoList, infoListIndex: j}; + this._cache.set(key, result); + return result; + } } throw new Error('Could not create audio'); @@ -111,12 +94,19 @@ class AudioSystem { return new TextToSpeechAudio(text, voice); } - // Private - - async _getAudioInfo(source, expression, reading, details) { - return await api.getDefinitionAudioInfo(source, expression, reading, details); + async createAudioFromInfo(info) { + switch (info.type) { + case 'url': + return await this.createAudio(info.url); + case 'tts': + return this.createTextToSpeechAudio(info.text, info.voice); + default: + throw new Error(`Unsupported type: ${info.type}`); + } } + // Private + _isAudioValid(audio) { const duration = audio.duration; return ( diff --git a/ext/mixed/js/display-audio.js b/ext/mixed/js/display-audio.js index c423446e..0cd8a625 100644 --- a/ext/mixed/js/display-audio.js +++ b/ext/mixed/js/display-audio.js @@ -117,9 +117,10 @@ class DisplayAudio { let audio; let info; try { - let index; - ({audio, index} = await this._audioSystem.createDefinitionAudio(sources, expression, reading, {textToSpeechVoice, customSourceUrl})); - info = `From source ${1 + index}: ${sources[index]}`; + let source; + ({audio, source} = await this._audioSystem.createExpressionAudio(sources, expression, reading, {textToSpeechVoice, customSourceUrl})); + const sourceIndex = sources.indexOf(source); + info = `From source ${1 + sourceIndex}: ${source}`; } catch (e) { audio = this._audioSystem.getFallbackAudio(); info = 'Could not find audio';