From 6a6e200ef947b92576351e39fc30b6653a576d70 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 7 Oct 2019 21:04:58 -0400 Subject: Update rejections to use Error --- ext/bg/js/audio.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ext/bg/js/audio.js') diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 2e5db7cc..44da51d1 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -57,7 +57,7 @@ async function audioBuildUrl(definition, mode, cache={}) { const xhr = new XMLHttpRequest(); xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post'); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.addEventListener('error', () => reject('Failed to scrape audio data')); + xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); xhr.addEventListener('load', () => { cache[definition.expression] = xhr.responseText; resolve(xhr.responseText); @@ -87,7 +87,7 @@ async function audioBuildUrl(definition, mode, cache={}) { } else { const xhr = new XMLHttpRequest(); xhr.open('GET', `https://jisho.org/search/${definition.expression}`); - xhr.addEventListener('error', () => reject('Failed to scrape audio data')); + xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); xhr.addEventListener('load', () => { cache[definition.expression] = xhr.responseText; resolve(xhr.responseText); -- cgit v1.2.3 From 60a80418d7e2a695ddff538a03c263ab89935d5e Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 9 Oct 2019 21:54:58 -0400 Subject: Update how audio URLs are constructed --- ext/bg/js/audio.js | 133 +++++++++++++++++++++++++---------------------------- 1 file changed, 62 insertions(+), 71 deletions(-) (limited to 'ext/bg/js/audio.js') diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 44da51d1..de4e80f3 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -17,8 +17,8 @@ */ -async function audioBuildUrl(definition, mode, cache={}) { - if (mode === 'jpod101') { +const audioUrlBuilders = { + 'jpod101': async (definition) => { let kana = definition.reading; let kanji = definition.expression; @@ -35,84 +35,75 @@ async function audioBuildUrl(definition, mode, cache={}) { params.push(`kana=${encodeURIComponent(kana)}`); } - const url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`; - return Promise.resolve(url); - } else if (mode === 'jpod101-alternate') { - return new Promise((resolve, reject) => { - const response = cache[definition.expression]; - if (response) { - resolve(response); - } else { - const data = { - post: 'dictionary_reference', - match_type: 'exact', - search_query: definition.expression - }; - - const params = []; - for (const key in data) { - params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`); - } - - const xhr = new XMLHttpRequest(); - xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post'); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); - xhr.addEventListener('load', () => { - cache[definition.expression] = xhr.responseText; - resolve(xhr.responseText); - }); - - xhr.send(params.join('&')); - } - }).then(response => { - const dom = new DOMParser().parseFromString(response, 'text/html'); - for (const row of dom.getElementsByClassName('dc-result-row')) { - try { - const url = row.querySelector('audio>source[src]').getAttribute('src'); - const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText; - if (url && reading && (!definition.reading || definition.reading === reading)) { - return audioUrlNormalize(url, 'https://www.japanesepod101.com', '/learningcenter/reference/'); - } - } catch (e) { - // NOP - } - } + return `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`; + }, + 'jpod101-alternate': async (definition) => { + const response = await new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post'); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); + xhr.addEventListener('load', () => resolve(xhr.responseText)); + xhr.send(`post=dictionary_reference&match_type=exact&search_query=${encodeURIComponent(definition.expression)}`); }); - } else if (mode === 'jisho') { - return new Promise((resolve, reject) => { - const response = cache[definition.expression]; - if (response) { - resolve(response); - } else { - const xhr = new XMLHttpRequest(); - xhr.open('GET', `https://jisho.org/search/${definition.expression}`); - xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); - xhr.addEventListener('load', () => { - cache[definition.expression] = xhr.responseText; - resolve(xhr.responseText); - }); - - xhr.send(); - } - }).then(response => { + + const dom = new DOMParser().parseFromString(response, 'text/html'); + for (const row of dom.getElementsByClassName('dc-result-row')) { try { - const dom = new DOMParser().parseFromString(response, 'text/html'); - const audio = dom.getElementById(`audio_${definition.expression}:${definition.reading}`); - if (audio) { - const url = audio.getElementsByTagName('source').item(0).getAttribute('src'); - if (url) { - return audioUrlNormalize(url, 'https://jisho.org', '/search/'); - } + const url = row.querySelector('audio>source[src]').getAttribute('src'); + const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText; + if (url && reading && (!definition.reading || definition.reading === reading)) { + return audioUrlNormalize(url, 'https://www.japanesepod101.com', '/learningcenter/reference/'); } } catch (e) { // NOP } + } + + throw new Error('Failed to find audio URL'); + }, + 'jisho': async (definition) => { + const response = await new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', `https://jisho.org/search/${definition.expression}`); + xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); + xhr.addEventListener('load', () => resolve(xhr.responseText)); + xhr.send(); }); + + const dom = new DOMParser().parseFromString(response, 'text/html'); + try { + const audio = dom.getElementById(`audio_${definition.expression}:${definition.reading}`); + if (audio !== null) { + const url = audio.getElementsByTagName('source').item(0).getAttribute('src'); + if (url) { + return audioUrlNormalize(url, 'https://jisho.org', '/search/'); + } + } + } catch (e) { + // NOP + } + + throw new Error('Failed to find audio URL'); } - else { - return Promise.resolve(); +}; + +async function audioBuildUrl(definition, mode, cache={}) { + const cacheKey = `${mode}:${definition.expression}`; + if (cache.hasOwnProperty(cacheKey)) { + return Promise.resolve(cache[cacheKey]); + } + + if (audioUrlBuilders.hasOwnProperty(mode)) { + const handler = audioUrlBuilders[mode]; + return handler(definition).then( + (url) => { + cache[cacheKey] = url; + return url; + }, + () => null); } + return null; } function audioUrlNormalize(url, baseUrl, basePath) { -- cgit v1.2.3 From 22b218d17238cc003938ef46403596c2183a9c1b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 9 Oct 2019 22:03:56 -0400 Subject: Pass optionsContext to audioBuildUrl handlers --- ext/bg/js/api.js | 7 ++++--- ext/bg/js/audio.js | 8 ++++---- ext/bg/js/backend.js | 2 +- ext/fg/js/api.js | 4 ++-- ext/mixed/js/display.js | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) (limited to 'ext/bg/js/audio.js') diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index ed7171b5..d12104bf 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -68,7 +68,8 @@ async function apiDefinitionAdd(definition, mode, context, optionsContext) { await audioInject( definition, options.anki.terms.fields, - options.general.audioSource + options.general.audioSource, + optionsContext ); } @@ -174,8 +175,8 @@ apiCommandExec.handlers = { } }; -async function apiAudioGetUrl(definition, source) { - return audioBuildUrl(definition, source); +async function apiAudioGetUrl(definition, source, optionsContext) { + return audioBuildUrl(definition, source, optionsContext); } async function apiInjectScreenshot(definition, fields, screenshot) { diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index de4e80f3..36ce03cf 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -88,7 +88,7 @@ const audioUrlBuilders = { } }; -async function audioBuildUrl(definition, mode, cache={}) { +async function audioBuildUrl(definition, mode, optionsContext, cache={}) { const cacheKey = `${mode}:${definition.expression}`; if (cache.hasOwnProperty(cacheKey)) { return Promise.resolve(cache[cacheKey]); @@ -96,7 +96,7 @@ async function audioBuildUrl(definition, mode, cache={}) { if (audioUrlBuilders.hasOwnProperty(mode)) { const handler = audioUrlBuilders[mode]; - return handler(definition).then( + return handler(definition, optionsContext).then( (url) => { cache[cacheKey] = url; return url; @@ -138,7 +138,7 @@ function audioBuildFilename(definition) { } } -async function audioInject(definition, fields, mode) { +async function audioInject(definition, fields, mode, optionsContext) { let usesAudio = false; for (const name in fields) { if (fields[name].includes('{audio}')) { @@ -157,7 +157,7 @@ async function audioInject(definition, fields, mode) { audioSourceDefinition = definition.expressions[0]; } - const url = await audioBuildUrl(audioSourceDefinition, mode); + const url = await audioBuildUrl(audioSourceDefinition, mode, optionsContext); const filename = audioBuildFilename(audioSourceDefinition); if (url && filename) { diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index fb77b71e..453f4282 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -181,7 +181,7 @@ Backend.messageHandlers = { noteView: ({noteId}) => apiNoteView(noteId), templateRender: ({template, data, dynamic}) => apiTemplateRender(template, data, dynamic), commandExec: ({command}) => apiCommandExec(command), - audioGetUrl: ({definition, source}) => apiAudioGetUrl(definition, source), + audioGetUrl: ({definition, source, optionsContext}) => apiAudioGetUrl(definition, source, optionsContext), screenshotGet: ({options}, sender) => apiScreenshotGet(options, sender), forward: ({action, params}, sender) => apiForward(action, params, sender), frameInformationGet: (params, sender) => apiFrameInformationGet(sender), diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js index d0ac649a..a553e514 100644 --- a/ext/fg/js/api.js +++ b/ext/fg/js/api.js @@ -45,8 +45,8 @@ function apiTemplateRender(template, data, dynamic) { return utilInvoke('templateRender', {data, template, dynamic}); } -function apiAudioGetUrl(definition, source) { - return utilInvoke('audioGetUrl', {definition, source}); +function apiAudioGetUrl(definition, source, optionsContext) { + return utilInvoke('audioGetUrl', {definition, source, optionsContext}); } function apiCommandExec(command) { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 17370654..78e6d8e3 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -404,7 +404,7 @@ class Display { this.setSpinnerVisible(true); const expression = expressionIndex === -1 ? definition : definition.expressions[expressionIndex]; - let url = await apiAudioGetUrl(expression, this.options.general.audioSource); + let url = await apiAudioGetUrl(expression, this.options.general.audioSource, this.optionsContext); if (!url) { url = '/mixed/mp3/button.mp3'; } -- cgit v1.2.3 From 8ae1da427756a9a1e057b3518c4069ac7d5b4b3a Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 9 Oct 2019 22:33:35 -0400 Subject: Update audio options format --- ext/bg/js/api.js | 2 +- ext/bg/js/audio.js | 4 ++-- ext/bg/js/options.js | 23 ++++++++++++++++++++--- ext/bg/js/settings.js | 14 ++++++++------ ext/bg/settings.html | 4 ++++ ext/mixed/js/display.js | 11 ++++++----- 6 files changed, 41 insertions(+), 17 deletions(-) (limited to 'ext/bg/js/audio.js') diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index d12104bf..f768e6f9 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -68,7 +68,7 @@ async function apiDefinitionAdd(definition, mode, context, optionsContext) { await audioInject( definition, options.anki.terms.fields, - options.general.audioSource, + options.audio.sources, optionsContext ); } diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 36ce03cf..26896027 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -138,7 +138,7 @@ function audioBuildFilename(definition) { } } -async function audioInject(definition, fields, mode, optionsContext) { +async function audioInject(definition, fields, sources, optionsContext) { let usesAudio = false; for (const name in fields) { if (fields[name].includes('{audio}')) { @@ -157,7 +157,7 @@ async function audioInject(definition, fields, mode, optionsContext) { audioSourceDefinition = definition.expressions[0]; } - const url = await audioBuildUrl(audioSourceDefinition, mode, optionsContext); + const url = await audioBuildUrl(audioSourceDefinition, sources[0], optionsContext); const filename = audioBuildFilename(audioSourceDefinition); if (url && filename) { diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index a2ab8877..d0aa6fd3 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -74,6 +74,18 @@ const profileOptionsVersionUpdates = [ if (utilStringHashCode(options.anki.fieldTemplates) === -250091611) { options.anki.fieldTemplates = profileOptionsGetDefaultFieldTemplates(); } + }, + (options) => { + const oldAudioSource = options.general.audioSource; + const disabled = oldAudioSource === 'disabled'; + options.audio.enabled = !disabled; + options.audio.volume = options.general.audioVolume; + options.audio.autoPlay = options.general.autoPlayAudio; + options.audio.sources = [disabled ? 'jpod101' : oldAudioSource]; + + delete options.general.audioSource; + delete options.general.audioVolume; + delete options.general.autoPlayAudio; } ]; @@ -247,9 +259,6 @@ function profileOptionsCreateDefaults() { return { general: { enable: true, - audioSource: 'jpod101', - audioVolume: 100, - autoPlayAudio: false, resultOutputMode: 'group', debugInfo: false, maxResults: 32, @@ -270,6 +279,14 @@ function profileOptionsCreateDefaults() { customPopupCss: '' }, + audio: { + enabled: true, + sources: ['jpod101', 'jpod101-alternate', 'jisho', 'custom'], + volume: 100, + autoPlay: false, + customSourceUrl: '' + }, + scanning: { middleMouse: true, touchInputEnabled: true, diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index 9838ea02..46521f1f 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -26,10 +26,7 @@ async function formRead(options) { options.general.showGuide = $('#show-usage-guide').prop('checked'); options.general.compactTags = $('#compact-tags').prop('checked'); options.general.compactGlossaries = $('#compact-glossaries').prop('checked'); - options.general.autoPlayAudio = $('#auto-play-audio').prop('checked'); options.general.resultOutputMode = $('#result-output-mode').val(); - options.general.audioSource = $('#audio-playback-source').val(); - options.general.audioVolume = parseFloat($('#audio-playback-volume').val()); options.general.debugInfo = $('#show-debug-info').prop('checked'); options.general.showAdvanced = $('#show-advanced-options').prop('checked'); options.general.maxResults = parseInt($('#max-displayed-results').val(), 10); @@ -44,6 +41,10 @@ async function formRead(options) { options.general.popupVerticalOffset2 = parseInt($('#popup-vertical-offset2').val(), 10); options.general.customPopupCss = $('#custom-popup-css').val(); + options.audio.enabled = $('#audio-playback-enabled').prop('checked'); + options.audio.autoPlay = $('#auto-play-audio').prop('checked'); + options.audio.volume = parseFloat($('#audio-playback-volume').val()); + options.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked'); options.scanning.touchInputEnabled = $('#touch-input-enabled').prop('checked'); options.scanning.selectText = $('#select-matched-text').prop('checked'); @@ -92,10 +93,7 @@ async function formWrite(options) { $('#show-usage-guide').prop('checked', options.general.showGuide); $('#compact-tags').prop('checked', options.general.compactTags); $('#compact-glossaries').prop('checked', options.general.compactGlossaries); - $('#auto-play-audio').prop('checked', options.general.autoPlayAudio); $('#result-output-mode').val(options.general.resultOutputMode); - $('#audio-playback-source').val(options.general.audioSource); - $('#audio-playback-volume').val(options.general.audioVolume); $('#show-debug-info').prop('checked', options.general.debugInfo); $('#show-advanced-options').prop('checked', options.general.showAdvanced); $('#max-displayed-results').val(options.general.maxResults); @@ -110,6 +108,10 @@ async function formWrite(options) { $('#popup-vertical-offset2').val(options.general.popupVerticalOffset2); $('#custom-popup-css').val(options.general.customPopupCss); + $('#audio-playback-enabled').prop('checked', options.audio.enabled); + $('#auto-play-audio').prop('checked', options.audio.autoPlay); + $('#audio-playback-volume').val(options.audio.volume); + $('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse); $('#touch-input-enabled').prop('checked', options.scanning.touchInputEnabled); $('#select-matched-text').prop('checked', options.scanning.selectText); diff --git a/ext/bg/settings.html b/ext/bg/settings.html index bfbb7c7d..f10e55b7 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -240,6 +240,10 @@

Audio Options

+
+ +
+
diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 78e6d8e3..c1224084 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -189,7 +189,7 @@ class Display { addable: options.anki.enable, grouped: options.general.resultOutputMode === 'group', merged: options.general.resultOutputMode === 'merge', - playback: options.general.audioSource !== 'disabled', + playback: options.audio.enabled, compactGlossaries: options.general.compactGlossaries, debug: options.general.debugInfo }; @@ -209,7 +209,7 @@ class Display { const {index, scroll} = context || {}; this.entryScrollIntoView(index || 0, scroll); - if (this.options.general.autoPlayAudio && this.options.general.audioSource !== 'disabled') { + if (this.options.audio.enabled && this.options.audio.autoPlay) { this.autoPlayAudio(); } @@ -404,7 +404,7 @@ class Display { this.setSpinnerVisible(true); const expression = expressionIndex === -1 ? definition : definition.expressions[expressionIndex]; - let url = await apiAudioGetUrl(expression, this.options.general.audioSource, this.optionsContext); + let url = await apiAudioGetUrl(expression, this.options.audio.sources[0], this.optionsContext); if (!url) { url = '/mixed/mp3/button.mp3'; } @@ -413,10 +413,11 @@ class Display { this.audioCache[key].pause(); } + const volume = this.options.audio.volume / 100.0; let audio = this.audioCache[url]; if (audio) { audio.currentTime = 0; - audio.volume = this.options.general.audioVolume / 100.0; + audio.volume = volume; audio.play(); } else { audio = new Audio(url); @@ -426,7 +427,7 @@ class Display { } this.audioCache[url] = audio; - audio.volume = this.options.general.audioVolume / 100.0; + audio.volume = volume; audio.play(); }; } -- cgit v1.2.3 From 1d516b3b24eb6b89aa3a345341b6c1c35e24dfed Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 10 Oct 2019 19:58:06 -0400 Subject: Implement audio fallbacks --- ext/bg/background.html | 1 + ext/bg/js/audio.js | 12 +++++----- ext/bg/search.html | 2 +- ext/fg/float.html | 1 + ext/mixed/js/audio.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ ext/mixed/js/display.js | 39 +++++++++++++------------------- 6 files changed, 86 insertions(+), 29 deletions(-) create mode 100644 ext/mixed/js/audio.js (limited to 'ext/bg/js/audio.js') diff --git a/ext/bg/background.html b/ext/bg/background.html index 3b37db87..194d4a45 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -27,6 +27,7 @@ + diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 26896027..0bf836df 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -136,6 +136,7 @@ function audioBuildFilename(definition) { return filename += '.mp3'; } + return null; } async function audioInject(definition, fields, sources, optionsContext) { @@ -157,11 +158,12 @@ async function audioInject(definition, fields, sources, optionsContext) { audioSourceDefinition = definition.expressions[0]; } - const url = await audioBuildUrl(audioSourceDefinition, sources[0], optionsContext); - const filename = audioBuildFilename(audioSourceDefinition); - - if (url && filename) { - definition.audio = {url, filename}; + const {url} = await audioGetFromSources(audioSourceDefinition, sources, optionsContext, false); + if (url !== null) { + const filename = audioBuildFilename(audioSourceDefinition); + if (filename !== null) { + definition.audio = {url, filename}; + } } return true; diff --git a/ext/bg/search.html b/ext/bg/search.html index e71824d3..3284ed43 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -36,7 +36,6 @@ - @@ -44,6 +43,7 @@ + diff --git a/ext/fg/float.html b/ext/fg/float.html index 52c7faa3..fe1aee8f 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -39,6 +39,7 @@ + diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js new file mode 100644 index 00000000..b905140c --- /dev/null +++ b/ext/mixed/js/audio.js @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 Alex Yatskov + * Author: Alex Yatskov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +function audioGetFromUrl(url) { + return new Promise((resolve, reject) => { + const audio = new Audio(url); + audio.addEventListener('loadeddata', () => { + if (audio.duration === 5.694694 || audio.duration === 5.720718) { + // Hardcoded values for invalid audio + reject(new Error('Could not retrieve audio')); + } else { + resolve(audio); + } + }); + audio.addEventListener('error', () => reject(audio.error)); + }); +} + +async function audioGetFromSources(expression, sources, optionsContext, createAudioObject, cache=null) { + const key = `${expression.expression}:${expression.reading}`; + if (cache !== null && cache.hasOwnProperty(expression)) { + return cache[key]; + } + + for (let i = 0, ii = sources.length; i < ii; ++i) { + const source = sources[i]; + const url = await apiAudioGetUrl(expression, source, optionsContext); + if (url === null) { + continue; + } + + try { + const audio = createAudioObject ? await audioGetFromUrl(url) : null; + const result = {audio, url, source}; + if (cache !== null) { + cache[key] = result; + } + return result; + } catch (e) { + // NOP + } + } + return {audio: null, source: null}; +} diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index c1224084..8d4e1e68 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -26,6 +26,8 @@ class Display { this.context = null; this.sequence = 0; this.index = 0; + this.audioPlaying = null; + this.audioFallback = null; this.audioCache = {}; this.optionsContext = {}; this.eventListeners = []; @@ -404,33 +406,24 @@ class Display { this.setSpinnerVisible(true); const expression = expressionIndex === -1 ? definition : definition.expressions[expressionIndex]; - let url = await apiAudioGetUrl(expression, this.options.audio.sources[0], this.optionsContext); - if (!url) { - url = '/mixed/mp3/button.mp3'; - } - for (const key in this.audioCache) { - this.audioCache[key].pause(); + if (this.audioPlaying !== null) { + this.audioPlaying.pause(); + this.audioPlaying = null; } - const volume = this.options.audio.volume / 100.0; - let audio = this.audioCache[url]; - if (audio) { - audio.currentTime = 0; - audio.volume = volume; - audio.play(); - } else { - audio = new Audio(url); - audio.onloadeddata = () => { - if (audio.duration === 5.694694 || audio.duration === 5.720718) { - audio = new Audio('/mixed/mp3/button.mp3'); - } - - this.audioCache[url] = audio; - audio.volume = volume; - audio.play(); - }; + let {audio} = await audioGetFromSources(expression, this.options.audio.sources, this.optionsContext, true, this.audioCache); + if (audio === null) { + if (this.audioFallback === null) { + this.audioFallback = new Audio('/mixed/mp3/button.mp3'); + } + audio = this.audioFallback; } + + this.audioPlaying = audio; + audio.currentTime = 0; + audio.volume = this.options.audio.volume / 100.0; + audio.play(); } catch (e) { this.onError(e); } finally { -- cgit v1.2.3 From 27c8430915af0a11a5f0c3216053cf3c5f090c50 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 10 Oct 2019 20:17:01 -0400 Subject: Implement custom audio source --- ext/bg/js/audio.js | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'ext/bg/js/audio.js') diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 0bf836df..9e0ae67c 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -85,6 +85,11 @@ const audioUrlBuilders = { } throw new Error('Failed to find audio URL'); + }, + 'custom': async (definition, optionsContext) => { + const options = await apiOptionsGet(optionsContext); + const customSourceUrl = options.audio.customSourceUrl; + return customSourceUrl.replace(/\{([^\}]*)\}/g, (m0, m1) => (definition.hasOwnProperty(m1) ? `${definition[m1]}` : m0)); } }; -- cgit v1.2.3