From 7fc3882607f48bb9371649ceacddf2fe278282d2 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 10 Apr 2020 13:44:31 -0400 Subject: [PATCH 01/10] Update the parameters passed to various audio-related functions --- ext/bg/js/anki-note-builder.js | 4 ++-- ext/bg/js/audio-uri-builder.js | 26 ++++++++++++-------------- ext/bg/js/backend.js | 22 +++++++--------------- ext/bg/js/settings/audio.js | 7 +------ ext/mixed/js/api.js | 4 ++-- ext/mixed/js/audio-system.js | 21 ++++++++++++--------- ext/mixed/js/display.js | 17 +++++++++-------- 7 files changed, 45 insertions(+), 56 deletions(-) diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js index 700d8237..9bab095d 100644 --- a/ext/bg/js/anki-note-builder.js +++ b/ext/bg/js/anki-note-builder.js @@ -85,14 +85,14 @@ class AnkiNoteBuilder { }); } - async injectAudio(definition, fields, sources, optionsContext) { + async injectAudio(definition, fields, sources, details) { if (!this._containsMarker(fields, 'audio')) { return; } try { const expressions = definition.expressions; const audioSourceDefinition = Array.isArray(expressions) ? expressions[0] : definition; - const {uri} = await this._audioSystem.getDefinitionAudio(audioSourceDefinition, sources, {tts: false, optionsContext}); + const {uri} = await this._audioSystem.getDefinitionAudio(audioSourceDefinition, sources, details); const filename = this._createInjectedAudioFileName(audioSourceDefinition); if (filename !== null) { definition.audio = {url: uri, filename}; diff --git a/ext/bg/js/audio-uri-builder.js b/ext/bg/js/audio-uri-builder.js index dfd195d8..27e97680 100644 --- a/ext/bg/js/audio-uri-builder.js +++ b/ext/bg/js/audio-uri-builder.js @@ -49,11 +49,11 @@ class AudioUriBuilder { return url; } - async getUri(definition, source, options) { + async getUri(definition, source, details) { const handler = this._getUrlHandlers.get(source); if (typeof handler === 'function') { try { - return await handler(definition, options); + return await handler(definition, details); } catch (e) { // NOP } @@ -132,26 +132,24 @@ class AudioUriBuilder { throw new Error('Failed to find audio URL'); } - async _getUriTextToSpeech(definition, options) { - const voiceURI = options.audio.textToSpeechVoice; - if (!voiceURI) { + async _getUriTextToSpeech(definition, {textToSpeechVoice}) { + if (!textToSpeechVoice) { throw new Error('No voice'); } - - return `tts:?text=${encodeURIComponent(definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; + return `tts:?text=${encodeURIComponent(definition.expression)}&voice=${encodeURIComponent(textToSpeechVoice)}`; } - async _getUriTextToSpeechReading(definition, options) { - const voiceURI = options.audio.textToSpeechVoice; - if (!voiceURI) { + async _getUriTextToSpeechReading(definition, {textToSpeechVoice}) { + if (!textToSpeechVoice) { throw new Error('No voice'); } - - return `tts:?text=${encodeURIComponent(definition.reading || definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; + return `tts:?text=${encodeURIComponent(definition.reading || definition.expression)}&voice=${encodeURIComponent(textToSpeechVoice)}`; } - async _getUriCustom(definition, options) { - const customSourceUrl = options.audio.customSourceUrl; + async _getUriCustom(definition, {customSourceUrl}) { + if (typeof customSourceUrl !== 'string') { + throw new Error('No custom URL defined'); + } return customSourceUrl.replace(/\{([^}]*)\}/g, (m0, m1) => (hasOwn(definition, m1) ? `${definition[m1]}` : m0)); } } diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index a1b788df..79402e67 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -51,8 +51,10 @@ class Backend { this.options = null; this.optionsSchema = null; this.defaultAnkiFieldTemplates = null; - this.audioSystem = new AudioSystem({getAudioUri: this._getAudioUri.bind(this)}); this.audioUriBuilder = new AudioUriBuilder(); + this.audioSystem = new AudioSystem({ + audioUriBuilder: this.audioUriBuilder + }); this.ankiNoteBuilder = new AnkiNoteBuilder({ anki: this.anki, audioSystem: this.audioSystem, @@ -494,11 +496,12 @@ class Backend { const templates = this.defaultAnkiFieldTemplates; if (mode !== 'kanji') { + const {customSourceUrl} = options.audio; await this.ankiNoteBuilder.injectAudio( definition, options.anki.terms.fields, options.audio.sources, - optionsContext + {textToSpeechVoice: null, customSourceUrl} ); } @@ -573,9 +576,8 @@ class Backend { return this._runCommand(command, params); } - async _onApiAudioGetUri({definition, source, optionsContext}) { - const options = this.getOptions(optionsContext); - return await this.audioUriBuilder.getUri(definition, source, options); + async _onApiAudioGetUri({definition, source, details}) { + return await this.audioUriBuilder.getUri(definition, source, details); } _onApiScreenshotGet({options}, sender) { @@ -861,16 +863,6 @@ class Backend { } } - async _getAudioUri(definition, source, details) { - let optionsContext = (typeof details === 'object' && details !== null ? details.optionsContext : null); - if (!(typeof optionsContext === 'object' && optionsContext !== null)) { - optionsContext = this.optionsContext; - } - - const options = this.getOptions(optionsContext); - return await this.audioUriBuilder.getUri(definition, source, options); - } - async _renderTemplate(template, data) { return handlebarsRenderDynamic(template, data); } diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index 3c6e126c..e9aa72e1 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -28,12 +28,7 @@ let audioSourceUI = null; let audioSystem = null; async function audioSettingsInitialize() { - audioSystem = new AudioSystem({ - getAudioUri: async (definition, source) => { - const optionsContext = getOptionsContext(); - return await apiAudioGetUri(definition, source, optionsContext); - } - }); + audioSystem = new AudioSystem({audioUriBuilder: null}); const optionsContext = getOptionsContext(); const options = await getOptionsMutable(optionsContext); diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 7080d93a..c97dc687 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -64,8 +64,8 @@ function apiTemplateRender(template, data) { return _apiInvoke('templateRender', {data, template}); } -function apiAudioGetUri(definition, source, optionsContext) { - return _apiInvoke('audioGetUri', {definition, source, optionsContext}); +function apiAudioGetUri(definition, source, details) { + return _apiInvoke('audioGetUri', {definition, source, details}); } function apiCommandExec(command, params) { diff --git a/ext/mixed/js/audio-system.js b/ext/mixed/js/audio-system.js index 45b733fc..574ad3dc 100644 --- a/ext/mixed/js/audio-system.js +++ b/ext/mixed/js/audio-system.js @@ -66,10 +66,10 @@ class TextToSpeechAudio { } class AudioSystem { - constructor({getAudioUri}) { + constructor({audioUriBuilder}) { this._cache = new Map(); this._cacheSizeMaximum = 32; - this._getAudioUri = getAudioUri; + this._audioUriBuilder = audioUriBuilder; if (typeof speechSynthesis !== 'undefined') { // speechSynthesis.getVoices() will not be populated unless some API call is made. @@ -90,7 +90,7 @@ class AudioSystem { if (uri === null) { continue; } try { - const audio = await this._createAudio(uri, details); + const audio = await this._createAudio(uri); this._cacheCheck(); this._cache.set(key, {audio, uri, source}); return {audio, uri, source}; @@ -114,20 +114,23 @@ class AudioSystem { // NOP } - async _createAudio(uri, details) { + async _createAudio(uri) { const ttsParameters = this._getTextToSpeechParameters(uri); if (ttsParameters !== null) { - if (typeof details === 'object' && details !== null) { - if (details.tts === false) { - throw new Error('Text-to-speech not permitted'); - } - } return this.createTextToSpeechAudio(ttsParameters); } return await this._createAudioFromUrl(uri); } + _getAudioUri(definition, source, details) { + return ( + this._audioUriBuilder !== null ? + this._audioUriBuilder.getUri(definition, source, details) : + null + ); + } + _createAudioFromUrl(url) { return new Promise((resolve, reject) => { const audio = new Audio(url); diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 63687dc2..7f3ba859 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -45,7 +45,13 @@ class Display { this.index = 0; this.audioPlaying = null; this.audioFallback = null; - this.audioSystem = new AudioSystem({getAudioUri: this._getAudioUri.bind(this)}); + this.audioSystem = new AudioSystem({ + audioUriBuilder: { + async getUri(definition, source, details) { + return await apiAudioGetUri(definition, source, details); + } + } + }); this.styleNode = null; this.eventListeners = new EventListenerCollection(); @@ -789,10 +795,10 @@ class Display { this.audioPlaying = null; } - const sources = this.options.audio.sources; let audio, source, info; try { - ({audio, source} = await this.audioSystem.getDefinitionAudio(expression, sources)); + const {sources, textToSpeechVoice, customSourceUrl} = this.options.audio; + ({audio, source} = await this.audioSystem.getDefinitionAudio(expression, sources, {textToSpeechVoice, customSourceUrl})); info = `From source ${1 + sources.indexOf(source)}: ${source}`; } catch (e) { if (this.audioFallback === null) { @@ -947,9 +953,4 @@ class Display { } }; } - - async _getAudioUri(definition, source) { - const optionsContext = this.getOptionsContext(); - return await apiAudioGetUri(definition, source, optionsContext); - } } From 823c026533dcd758c2a93038fa526978a5fa9cc3 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 10 Apr 2020 13:51:47 -0400 Subject: [PATCH 02/10] Remove de/structuring from public API --- ext/bg/js/settings/audio.js | 2 +- ext/mixed/js/audio-system.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index e9aa72e1..68dfe71e 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -110,7 +110,7 @@ function textToSpeechTest() { const text = document.querySelector('#text-to-speech-voice-test').dataset.speechText || ''; const voiceUri = document.querySelector('#text-to-speech-voice').value; - const audio = audioSystem.createTextToSpeechAudio({text, voiceUri}); + const audio = audioSystem.createTextToSpeechAudio(text, voiceUri); audio.volume = 1.0; audio.play(); } catch (e) { diff --git a/ext/mixed/js/audio-system.js b/ext/mixed/js/audio-system.js index 574ad3dc..5366e3e0 100644 --- a/ext/mixed/js/audio-system.js +++ b/ext/mixed/js/audio-system.js @@ -102,7 +102,7 @@ class AudioSystem { throw new Error('Could not create audio'); } - createTextToSpeechAudio({text, voiceUri}) { + createTextToSpeechAudio(text, voiceUri) { const voice = this._getTextToSpeechVoiceFromVoiceUri(voiceUri); if (voice === null) { throw new Error('Invalid text-to-speech voice'); @@ -117,7 +117,8 @@ class AudioSystem { async _createAudio(uri) { const ttsParameters = this._getTextToSpeechParameters(uri); if (ttsParameters !== null) { - return this.createTextToSpeechAudio(ttsParameters); + const {text, voiceUri} = ttsParameters; + return this.createTextToSpeechAudio(text, voiceUri); } return await this._createAudioFromUrl(uri); From e1ebfb02f724518432b2e1c5ec2a80ff03b38fd8 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 10 Apr 2020 16:12:55 -0400 Subject: [PATCH 03/10] Disable cache on the backend and fix a bug with the cache key --- ext/bg/js/backend.js | 3 ++- ext/bg/js/settings/audio.js | 5 ++++- ext/mixed/js/audio-system.js | 22 ++++++++++++++-------- ext/mixed/js/display.js | 3 ++- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 79402e67..9d1fa6c1 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -53,7 +53,8 @@ class Backend { this.defaultAnkiFieldTemplates = null; this.audioUriBuilder = new AudioUriBuilder(); this.audioSystem = new AudioSystem({ - audioUriBuilder: this.audioUriBuilder + audioUriBuilder: this.audioUriBuilder, + useCache: false }); this.ankiNoteBuilder = new AnkiNoteBuilder({ anki: this.anki, diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index 68dfe71e..98ed9b8b 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -28,7 +28,10 @@ let audioSourceUI = null; let audioSystem = null; async function audioSettingsInitialize() { - audioSystem = new AudioSystem({audioUriBuilder: null}); + audioSystem = new AudioSystem({ + audioUriBuilder: null, + useCache: true + }); const optionsContext = getOptionsContext(); const options = await getOptionsMutable(optionsContext); diff --git a/ext/mixed/js/audio-system.js b/ext/mixed/js/audio-system.js index 5366e3e0..255a96de 100644 --- a/ext/mixed/js/audio-system.js +++ b/ext/mixed/js/audio-system.js @@ -66,8 +66,8 @@ class TextToSpeechAudio { } class AudioSystem { - constructor({audioUriBuilder}) { - this._cache = new Map(); + constructor({audioUriBuilder, useCache}) { + this._cache = useCache ? new Map() : null; this._cacheSizeMaximum = 32; this._audioUriBuilder = audioUriBuilder; @@ -79,10 +79,14 @@ class AudioSystem { async getDefinitionAudio(definition, sources, details) { const key = `${definition.expression}:${definition.reading}`; - const cacheValue = this._cache.get(definition); - if (typeof cacheValue !== 'undefined') { - const {audio, uri, source} = cacheValue; - return {audio, uri, source}; + const hasCache = (this._cache !== null); + + if (hasCache) { + const cacheValue = this._cache.get(key); + if (typeof cacheValue !== 'undefined') { + const {audio, uri, source} = cacheValue; + return {audio, uri, source}; + } } for (const source of sources) { @@ -91,8 +95,10 @@ class AudioSystem { try { const audio = await this._createAudio(uri); - this._cacheCheck(); - this._cache.set(key, {audio, uri, source}); + if (hasCache) { + this._cacheCheck(); + this._cache.set(key, {audio, uri, source}); + } return {audio, uri, source}; } catch (e) { // NOP diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 7f3ba859..8edae7c9 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -50,7 +50,8 @@ class Display { async getUri(definition, source, details) { return await apiAudioGetUri(definition, source, details); } - } + }, + useCache: true }); this.styleNode = null; From f50aee1021179411322f67c5951eb35de81c5174 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 10 Apr 2020 16:35:43 -0400 Subject: [PATCH 04/10] Only return the cached value if it uses a valid source --- ext/mixed/js/audio-system.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/mixed/js/audio-system.js b/ext/mixed/js/audio-system.js index 255a96de..0ded3490 100644 --- a/ext/mixed/js/audio-system.js +++ b/ext/mixed/js/audio-system.js @@ -85,7 +85,9 @@ class AudioSystem { const cacheValue = this._cache.get(key); if (typeof cacheValue !== 'undefined') { const {audio, uri, source} = cacheValue; - return {audio, uri, source}; + if (sources.includes(source)) { + return {audio, uri, source}; + } } } From 7eb7c88394ebb56936861b91e6b04525abb57490 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 10 Apr 2020 16:38:53 -0400 Subject: [PATCH 05/10] Return index of the source instead of the source value --- ext/mixed/js/audio-system.js | 10 ++++++---- ext/mixed/js/display.js | 7 ++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ext/mixed/js/audio-system.js b/ext/mixed/js/audio-system.js index 0ded3490..94885d34 100644 --- a/ext/mixed/js/audio-system.js +++ b/ext/mixed/js/audio-system.js @@ -85,13 +85,15 @@ class AudioSystem { const cacheValue = this._cache.get(key); if (typeof cacheValue !== 'undefined') { const {audio, uri, source} = cacheValue; - if (sources.includes(source)) { - return {audio, uri, source}; + const index = sources.indexOf(source); + if (index >= 0) { + return {audio, uri, index}; } } } - for (const source of sources) { + for (let i = 0, ii = sources.length; i < ii; ++i) { + const source = sources[i]; const uri = await this._getAudioUri(definition, source, details); if (uri === null) { continue; } @@ -101,7 +103,7 @@ class AudioSystem { this._cacheCheck(); this._cache.set(key, {audio, uri, source}); } - return {audio, uri, source}; + return {audio, uri, index: i}; } catch (e) { // NOP } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 8edae7c9..5b8d3610 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -796,11 +796,12 @@ class Display { this.audioPlaying = null; } - let audio, source, info; + let audio, info; try { const {sources, textToSpeechVoice, customSourceUrl} = this.options.audio; - ({audio, source} = await this.audioSystem.getDefinitionAudio(expression, sources, {textToSpeechVoice, customSourceUrl})); - info = `From source ${1 + sources.indexOf(source)}: ${source}`; + let index; + ({audio, index} = await this.audioSystem.getDefinitionAudio(expression, sources, {textToSpeechVoice, customSourceUrl})); + info = `From source ${1 + index}: ${sources[index]}`; } catch (e) { if (this.audioFallback === null) { this.audioFallback = new Audio('/mixed/mp3/button.mp3'); From 5c2dff345eb9d4a25cf1022d14e28ba5925b0b10 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 10 Apr 2020 16:43:57 -0400 Subject: [PATCH 06/10] Fix button title text not updating correctly in merge mode --- ext/mixed/js/display.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 5b8d3610..d4481349 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -810,7 +810,7 @@ class Display { info = 'Could not find audio'; } - const button = this.audioButtonFindImage(entryIndex); + const button = this.audioButtonFindImage(entryIndex, expressionIndex); if (button !== null) { let titleDefault = button.dataset.titleDefault; if (!titleDefault) { @@ -909,9 +909,16 @@ class Display { viewerButton.dataset.noteId = noteId; } - audioButtonFindImage(index) { + audioButtonFindImage(index, expressionIndex) { const entry = this.getEntry(index); - return entry !== null ? entry.querySelector('.action-play-audio>img') : null; + if (entry === null) { return null; } + + const container = ( + expressionIndex >= 0 ? + entry.querySelector(`.term-expression:nth-of-type(${expressionIndex + 1})`) : + entry + ); + return container !== null ? container.querySelector('.action-play-audio>img') : null; } async getDefinitionsAddable(definitions, modes) { From 92790763d19a5259e4b091b72a51e67f45548685 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 14 Apr 2020 18:22:51 -0400 Subject: [PATCH 07/10] Update style --- ext/mixed/js/display.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index d4481349..f30a65e6 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -47,7 +47,7 @@ class Display { this.audioFallback = null; this.audioSystem = new AudioSystem({ audioUriBuilder: { - async getUri(definition, source, details) { + getUri: async (definition, source, details) => { return await apiAudioGetUri(definition, source, details); } }, From 9fe7b9ad29958b148162bfc2d065a7e32a986291 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 14 Apr 2020 18:26:24 -0400 Subject: [PATCH 08/10] Remove unused global --- ext/bg/js/settings/audio.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index 98ed9b8b..ac2d82f3 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -18,7 +18,6 @@ /* global * AudioSourceUI * AudioSystem - * apiAudioGetUri * getOptionsContext * getOptionsMutable * settingsSaveOptions From fcbfde506abf6ca3474d2dfdf4f337b86b0bb579 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 17 Apr 2020 17:48:55 -0400 Subject: [PATCH 09/10] Await and handle errors from audio.play() --- ext/mixed/js/audio-system.js | 2 +- ext/mixed/js/display.js | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ext/mixed/js/audio-system.js b/ext/mixed/js/audio-system.js index 94885d34..3273f982 100644 --- a/ext/mixed/js/audio-system.js +++ b/ext/mixed/js/audio-system.js @@ -40,7 +40,7 @@ class TextToSpeechAudio { } } - play() { + async play() { try { if (this._utterance === null) { this._utterance = new SpeechSynthesisUtterance(this.text || ''); diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index f30a65e6..b4a93d99 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -823,7 +823,14 @@ class Display { this.audioPlaying = audio; audio.currentTime = 0; audio.volume = this.options.audio.volume / 100.0; - audio.play(); + const playPromise = audio.play(); + if (typeof playPromise !== 'undefined') { + try { + await playPromise; + } catch (e2) { + // NOP + } + } } catch (e) { this.onError(e); } finally { From 320852f2d01d72c1039d098033081e8266d02be7 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 17 Apr 2020 18:00:28 -0400 Subject: [PATCH 10/10] Fix overlapping audio.play calls due to await --- ext/mixed/js/display.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index b4a93d99..c2284ffe 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -791,10 +791,7 @@ class Display { const expression = expressionIndex === -1 ? definition : definition.expressions[expressionIndex]; - if (this.audioPlaying !== null) { - this.audioPlaying.pause(); - this.audioPlaying = null; - } + this._stopPlayingAudio(); let audio, info; try { @@ -820,6 +817,8 @@ class Display { button.title = `${titleDefault}\n${info}`; } + this._stopPlayingAudio(); + this.audioPlaying = audio; audio.currentTime = 0; audio.volume = this.options.audio.volume / 100.0; @@ -838,6 +837,13 @@ class Display { } } + _stopPlayingAudio() { + if (this.audioPlaying !== null) { + this.audioPlaying.pause(); + this.audioPlaying = null; + } + } + noteUsesScreenshot(mode) { const optionsAnki = this.options.anki; const fields = (mode === 'kanji' ? optionsAnki.kanji : optionsAnki.terms).fields;