summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2021-05-30 12:15:07 -0400
committerGitHub <noreply@github.com>2021-05-30 12:15:07 -0400
commitefd35de67f6700ecf4f49a87d310d99cefbaa328 (patch)
treeb031d5c24b3e118efb868bf1b098bb10cf3b2c3e
parent0f0e80aadb97c95d307dcd97edabd2087d0a1743 (diff)
Refactor display audio options (#1717)
* Update how options are updated and stored in DisplayAudio * Add source list * Improve menus for custom json * Clear cache after options update * Move function * Update public API * Simplify playing audio from a specific source * Simplify audio list * Refactor audio source usage * Refactoring * Refactor argument names * Fix incorrect source usage * Remove unused * Remove return value * Simplify details * Simplify Anki card audio details * Update the data that is passed to AudioDownloader * Simplify schema handling * Remove unnecessary details
-rw-r--r--ext/js/background/backend.js14
-rw-r--r--ext/js/comm/api.js4
-rw-r--r--ext/js/display/display-audio.js443
-rw-r--r--ext/js/display/display.js26
-rw-r--r--ext/js/media/audio-downloader.js72
-rw-r--r--ext/js/media/audio-system.js8
6 files changed, 277 insertions, 290 deletions
diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js
index ba310d93..42b03c59 100644
--- a/ext/js/background/backend.js
+++ b/ext/js/background/backend.js
@@ -519,8 +519,8 @@ class Backend {
return this._runCommand(command, params);
}
- async _onApiGetTermAudioInfoList({source, term, reading, details}) {
- return await this._audioDownloader.getTermAudioInfoList(source, term, reading, details);
+ async _onApiGetTermAudioInfoList({source, term, reading}) {
+ return await this._audioDownloader.getTermAudioInfoList(source, term, reading);
}
_onApiSendMessageToFrame({frameId: targetFrameId, action, params}, sender) {
@@ -1742,7 +1742,7 @@ class Backend {
return null;
}
- const {sources, preferredAudioIndex, customSourceUrl} = details;
+ const {sources, preferredAudioIndex} = details;
let data;
let contentType;
try {
@@ -1750,13 +1750,7 @@ class Backend {
sources,
preferredAudioIndex,
term,
- reading,
- {
- textToSpeechVoice: null,
- customSourceUrl,
- binary: true,
- disableCache: true
- }
+ reading
));
} catch (e) {
// No audio
diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js
index 3795dcf4..7e77180e 100644
--- a/ext/js/comm/api.js
+++ b/ext/js/comm/api.js
@@ -68,8 +68,8 @@ class API {
return this._invoke('suspendAnkiCardsForNote', {noteId});
}
- getTermAudioInfoList(source, term, reading, details) {
- return this._invoke('getTermAudioInfoList', {source, term, reading, details});
+ getTermAudioInfoList(source, term, reading) {
+ return this._invoke('getTermAudioInfoList', {source, term, reading});
}
commandExec(command, params) {
diff --git a/ext/js/display/display-audio.js b/ext/js/display/display-audio.js
index 34e74004..b7ec6ba1 100644
--- a/ext/js/display/display-audio.js
+++ b/ext/js/display/display-audio.js
@@ -25,6 +25,8 @@ class DisplayAudio {
this._display = display;
this._audioPlaying = null;
this._audioSystem = new AudioSystem();
+ this._playbackVolume = 1.0;
+ this._autoPlay = false;
this._autoPlayAudioTimer = null;
this._autoPlayAudioDelay = 400;
this._eventListeners = new EventListenerCollection();
@@ -32,6 +34,16 @@ class DisplayAudio {
this._menuContainer = document.querySelector('#popup-menus');
this._entriesToken = {};
this._openMenus = new Set();
+ this._audioSources = [];
+ this._audioSourceTypeNames = new Map([
+ ['jpod101', 'JapanesePod101'],
+ ['jpod101-alternate', 'JapanesePod101 (Alternate)'],
+ ['jisho', 'Jisho.org'],
+ ['text-to-speech', 'Text-to-speech'],
+ ['text-to-speech-reading', 'Text-to-speech (Kana reading)'],
+ ['custom', 'Custom URL'],
+ ['custom-json', 'Custom URL (JSON)']
+ ]);
}
get autoPlayAudioDelay() {
@@ -44,11 +56,8 @@ class DisplayAudio {
prepare() {
this._audioSystem.prepare();
- }
-
- updateOptions(options) {
- const data = document.documentElement.dataset;
- data.audioEnabled = `${options.audio.enabled && options.audio.sources.length > 0}`;
+ this._display.on('optionsUpdated', this._onOptionsUpdated.bind(this));
+ this._onOptionsUpdated({options: this._display.getOptions()});
}
cleanupEntries() {
@@ -68,8 +77,7 @@ class DisplayAudio {
}
setupEntriesComplete() {
- const audioOptions = this._getAudioOptions();
- if (!audioOptions.enabled || !audioOptions.autoPlay) { return; }
+ if (!this._autoPlay) { return; }
this.clearAutoPlayTimer();
@@ -103,85 +111,81 @@ class DisplayAudio {
this._audioPlaying = null;
}
- async playAudio(dictionaryEntryIndex, headwordIndex, sources=null, sourceDetailsMap=null) {
- this.stopAudio();
- this.clearAutoPlayTimer();
-
- const headword = this._getHeadword(dictionaryEntryIndex, headwordIndex);
- if (headword === null) {
- return {audio: null, source: null, valid: false};
+ async playAudio(dictionaryEntryIndex, headwordIndex, sourceType=null) {
+ let sources = this._audioSources;
+ if (sourceType !== null) {
+ sources = [];
+ for (const source of this._audioSources) {
+ if (source.type === sourceType) {
+ sources.push(source);
+ }
+ }
}
+ await this._playAudio(dictionaryEntryIndex, headwordIndex, sources, null);
+ }
+
+ getAnkiNoteMediaAudioDetails(term, reading) {
+ const sources = [];
+ let preferredAudioIndex = null;
+ const primaryCardAudio = this._getPrimaryCardAudio(term, reading);
+ if (primaryCardAudio !== null) {
+ const {index, subIndex} = primaryCardAudio;
+ const source = this._audioSources[index];
+ sources.push(this._getSourceData(source));
+ preferredAudioIndex = subIndex;
+ } else {
+ for (const source of this._audioSources) {
+ if (!source.isInOptions) { continue; }
+ sources.push(this._getSourceData(source));
+ }
+ }
+ return {sources, preferredAudioIndex};
+ }
- const buttons = this._getAudioPlayButtons(dictionaryEntryIndex, headwordIndex);
+ // Private
- const {term, reading} = headword;
- const audioOptions = this._getAudioOptions();
- const {textToSpeechVoice, customSourceUrl, volume} = audioOptions;
- if (!Array.isArray(sources)) {
- ({sources} = audioOptions);
+ _onOptionsUpdated({options}) {
+ if (options === null) { return; }
+ const {enabled, autoPlay, textToSpeechVoice, customSourceUrl, volume, sources} = options.audio;
+ this._autoPlay = enabled && autoPlay;
+ this._playbackVolume = Number.isFinite(volume) ? Math.max(0.0, Math.min(1.0, volume / 100.0)) : 1.0;
+
+ const requiredAudioSources = new Set([
+ 'jpod101',
+ 'jpod101-alternate',
+ 'jisho'
+ ]);
+ this._audioSources.length = 0;
+ for (const type of sources) {
+ this._addAudioSourceInfo(type, customSourceUrl, textToSpeechVoice, true);
+ requiredAudioSources.delete(type);
}
- if (!(sourceDetailsMap instanceof Map)) {
- sourceDetailsMap = null;
+ for (const type of requiredAudioSources) {
+ this._addAudioSourceInfo(type, '', '', false);
}
- const progressIndicatorVisible = this._display.progressIndicatorVisible;
- const overrideToken = progressIndicatorVisible.setOverride(true);
- try {
- // Create audio
- let audio;
- let title;
- let source = null;
- const info = await this._createTermAudio(sources, sourceDetailsMap, term, reading, {textToSpeechVoice, customSourceUrl});
- const valid = (info !== null);
- if (valid) {
- ({audio, source} = info);
- const sourceIndex = sources.indexOf(source);
- title = `From source ${1 + sourceIndex}: ${source}`;
- } else {
- audio = this._audioSystem.getFallbackAudio();
- title = 'Could not find audio';
- }
-
- // Stop any currently playing audio
- this.stopAudio();
-
- // Update details
- const potentialAvailableAudioCount = this._getPotentialAvailableAudioCount(term, reading);
- for (const button of buttons) {
- const titleDefault = button.dataset.titleDefault || '';
- button.title = `${titleDefault}\n${title}`;
- this._updateAudioPlayButtonBadge(button, potentialAvailableAudioCount);
- }
-
- // Play
- audio.currentTime = 0;
- audio.volume = Number.isFinite(volume) ? Math.max(0.0, Math.min(1.0, volume / 100.0)) : 1.0;
-
- const playPromise = audio.play();
- this._audioPlaying = audio;
-
- if (typeof playPromise !== 'undefined') {
- try {
- await playPromise;
- } catch (e) {
- // NOP
- }
- }
+ const data = document.documentElement.dataset;
+ data.audioEnabled = `${enabled && sources.length > 0}`;
- return {audio, source, valid};
- } finally {
- progressIndicatorVisible.clearOverride(overrideToken);
- }
+ this._cache.clear();
}
- getPrimaryCardAudio(term, reading) {
- const cacheEntry = this._getCacheItem(term, reading, false);
- const primaryCardAudio = typeof cacheEntry !== 'undefined' ? cacheEntry.primaryCardAudio : null;
- return primaryCardAudio;
+ _addAudioSourceInfo(type, url, voice, isInOptions) {
+ const index = this._audioSources.length;
+ const downloadable = this._sourceIsDownloadable(type);
+ let displayName = this._audioSourceTypeNames.get(type);
+ if (typeof displayName === 'undefined') { displayName = 'Unknown'; }
+ this._audioSources.push({
+ index,
+ type,
+ url,
+ voice,
+ isInOptions,
+ downloadable,
+ displayName
+ });
}
- // Private
-
_onAudioPlayButtonClick(dictionaryEntryIndex, headwordIndex, e) {
e.preventDefault();
@@ -229,29 +233,93 @@ class DisplayAudio {
_getMenuItemSourceInfo(item) {
const group = item.closest('.popup-menu-item-group');
- if (group === null) { return null; }
-
- let {source, index} = group.dataset;
- if (typeof index !== 'undefined') {
+ if (group !== null) {
+ let {index, subIndex} = group.dataset;
index = Number.parseInt(index, 10);
+ if (index >= 0 && index < this._audioSources.length) {
+ const source = this._audioSources[index];
+ if (typeof subIndex === 'string') {
+ subIndex = Number.parseInt(subIndex, 10);
+ } else {
+ subIndex = null;
+ }
+ return {source, subIndex};
+ }
}
- const hasIndex = (Number.isFinite(index) && Math.floor(index) === index);
- if (!hasIndex) {
- index = 0;
+ return {source: null, subIndex: null};
+ }
+
+ async _playAudio(dictionaryEntryIndex, headwordIndex, sources, audioInfoListIndex) {
+ this.stopAudio();
+ this.clearAutoPlayTimer();
+
+ const headword = this._getHeadword(dictionaryEntryIndex, headwordIndex);
+ if (headword === null) {
+ return {audio: null, source: null, valid: false};
+ }
+
+ const buttons = this._getAudioPlayButtons(dictionaryEntryIndex, headwordIndex);
+
+ const {term, reading} = headword;
+
+ const progressIndicatorVisible = this._display.progressIndicatorVisible;
+ const overrideToken = progressIndicatorVisible.setOverride(true);
+ try {
+ // Create audio
+ let audio;
+ let title;
+ let source = null;
+ let subIndex = 0;
+ const info = await this._createTermAudio(term, reading, sources, audioInfoListIndex);
+ const valid = (info !== null);
+ if (valid) {
+ ({audio, source, subIndex} = info);
+ const sourceIndex = sources.indexOf(source);
+ title = `From source ${1 + sourceIndex}: ${source}`;
+ } else {
+ audio = this._audioSystem.getFallbackAudio();
+ title = 'Could not find audio';
+ }
+
+ // Stop any currently playing audio
+ this.stopAudio();
+
+ // Update details
+ const potentialAvailableAudioCount = this._getPotentialAvailableAudioCount(term, reading);
+ for (const button of buttons) {
+ const titleDefault = button.dataset.titleDefault || '';
+ button.title = `${titleDefault}\n${title}`;
+ this._updateAudioPlayButtonBadge(button, potentialAvailableAudioCount);
+ }
+
+ // Play
+ audio.currentTime = 0;
+ audio.volume = this._playbackVolume;
+
+ const playPromise = audio.play();
+ this._audioPlaying = audio;
+
+ if (typeof playPromise !== 'undefined') {
+ try {
+ await playPromise;
+ } catch (e) {
+ // NOP
+ }
+ }
+
+ return {audio, source, subIndex, valid};
+ } finally {
+ progressIndicatorVisible.clearOverride(overrideToken);
}
- return {source, index, hasIndex};
}
async _playAudioFromSource(dictionaryEntryIndex, headwordIndex, item) {
- const sourceInfo = this._getMenuItemSourceInfo(item);
- if (sourceInfo === null) { return; }
-
- const {source, index, hasIndex} = sourceInfo;
- const sourceDetailsMap = hasIndex ? new Map([[source, {start: index, end: index + 1}]]) : null;
+ const {source, subIndex} = this._getMenuItemSourceInfo(item);
+ if (source === null) { return; }
try {
const token = this._entriesToken;
- const {valid} = await this.playAudio(dictionaryEntryIndex, headwordIndex, [source], sourceDetailsMap);
+ const {valid} = await this._playAudio(dictionaryEntryIndex, headwordIndex, [source], subIndex);
if (valid && token === this._entriesToken) {
this._setPrimaryAudio(dictionaryEntryIndex, headwordIndex, item, null, false);
}
@@ -261,11 +329,8 @@ class DisplayAudio {
}
_setPrimaryAudio(dictionaryEntryIndex, headwordIndex, item, menu, canToggleOff) {
- const sourceInfo = this._getMenuItemSourceInfo(item);
- if (sourceInfo === null) { return; }
-
- const {source, index} = sourceInfo;
- if (!this._sourceIsDownloadable(source)) { return; }
+ const {source, subIndex} = this._getMenuItemSourceInfo(item);
+ if (source === null || !source.downloadable) { return; }
const headword = this._getHeadword(dictionaryEntryIndex, headwordIndex);
if (headword === null) { return; }
@@ -274,7 +339,12 @@ class DisplayAudio {
const cacheEntry = this._getCacheItem(term, reading, true);
let {primaryCardAudio} = cacheEntry;
- primaryCardAudio = (!canToggleOff || primaryCardAudio === null || primaryCardAudio.source !== source || primaryCardAudio.index !== index) ? {source, index} : null;
+ primaryCardAudio = (
+ !canToggleOff ||
+ primaryCardAudio === null ||
+ primaryCardAudio.source !== source ||
+ primaryCardAudio.index !== subIndex
+ ) ? {index: source.index, subIndex} : null;
cacheEntry.primaryCardAudio = primaryCardAudio;
if (menu !== null) {
@@ -304,19 +374,19 @@ class DisplayAudio {
return results;
}
- async _createTermAudio(sources, sourceDetailsMap, term, reading, details) {
+ async _createTermAudio(term, reading, sources, audioInfoListIndex) {
const {sourceMap} = this._getCacheItem(term, reading, true);
- for (let i = 0, ii = sources.length; i < ii; ++i) {
- const source = sources[i];
+ for (const source of sources) {
+ const {index} = source;
let cacheUpdated = false;
let infoListPromise;
- let sourceInfo = sourceMap.get(source);
+ let sourceInfo = sourceMap.get(index);
if (typeof sourceInfo === 'undefined') {
- infoListPromise = this._getTermAudioInfoList(source, term, reading, details);
+ infoListPromise = this._getTermAudioInfoList(source, term, reading);
sourceInfo = {infoListPromise, infoList: null};
- sourceMap.set(source, sourceInfo);
+ sourceMap.set(index, sourceInfo);
cacheUpdated = true;
}
@@ -326,29 +396,29 @@ class DisplayAudio {
sourceInfo.infoList = infoList;
}
- let start = 0;
- let end = infoList.length;
-
- if (sourceDetailsMap !== null) {
- const sourceDetails = sourceDetailsMap.get(source);
- if (typeof sourceDetails !== 'undefined') {
- const {start: start2, end: end2} = sourceDetails;
- if (this._isInteger(start2)) { start = this._clamp(start2, start, end); }
- if (this._isInteger(end2)) { end = this._clamp(end2, start, end); }
- }
- }
-
- const {result, cacheUpdated: cacheUpdated2} = await this._createAudioFromInfoList(source, infoList, start, end);
+ const {audio, index: subIndex, cacheUpdated: cacheUpdated2} = await this._createAudioFromInfoList(source, infoList, audioInfoListIndex);
if (cacheUpdated || cacheUpdated2) { this._updateOpenMenu(); }
- if (result !== null) { return result; }
+ if (audio !== null) {
+ return {audio, source, subIndex};
+ }
}
return null;
}
- async _createAudioFromInfoList(source, infoList, start, end) {
- let result = null;
- let cacheUpdated = false;
+ async _createAudioFromInfoList(source, infoList, audioInfoListIndex) {
+ let start = 0;
+ let end = infoList.length;
+ if (audioInfoListIndex !== null) {
+ start = Math.max(0, Math.min(end, audioInfoListIndex));
+ end = Math.max(0, Math.min(end, audioInfoListIndex + 1));
+ }
+
+ const result = {
+ audio: null,
+ index: -1,
+ cacheUpdated: false
+ };
for (let i = start; i < end; ++i) {
const item = infoList[i];
@@ -361,7 +431,7 @@ class DisplayAudio {
item.audioPromise = audioPromise;
}
- cacheUpdated = true;
+ result.cacheUpdated = true;
try {
audio = await audioPromise;
@@ -375,17 +445,18 @@ class DisplayAudio {
}
if (audio !== null) {
- result = {audio, source, infoListIndex: i};
+ result.audio = audio;
+ result.index = i;
break;
}
}
- return {result, cacheUpdated};
+ return result;
}
async _createAudioFromInfo(info, source) {
switch (info.type) {
case 'url':
- return await this._audioSystem.createAudio(info.url, source);
+ return await this._audioSystem.createAudio(info.url, source.type);
case 'tts':
return this._audioSystem.createTextToSpeechAudio(info.text, info.voice);
default:
@@ -393,8 +464,9 @@ class DisplayAudio {
}
}
- async _getTermAudioInfoList(source, term, reading, details) {
- const infoList = await yomichan.api.getTermAudioInfoList(source, term, reading, details);
+ async _getTermAudioInfoList(source, term, reading) {
+ const sourceData = this._getSourceData(source);
+ const infoList = await yomichan.api.getTermAudioInfoList(sourceData, term, reading);
return infoList.map((info) => ({info, audioPromise: null, audioResolved: false, audio: null}));
}
@@ -415,22 +487,6 @@ class DisplayAudio {
return JSON.stringify([term, reading]);
}
- _getAudioOptions() {
- return this._display.getOptions().audio;
- }
-
- _isInteger(value) {
- return (
- typeof value === 'number' &&
- Number.isFinite(value) &&
- Math.floor(value) === value
- );
- }
-
- _clamp(value, min, max) {
- return Math.max(min, Math.min(max, value));
- }
-
_updateAudioPlayButtonBadge(button, potentialAvailableAudioCount) {
if (potentialAvailableAudioCount === null) {
delete button.dataset.potentialAvailableAudioCount;
@@ -501,55 +557,6 @@ class DisplayAudio {
}
}
- _getAudioSources(audioOptions) {
- const {sources, textToSpeechVoice, customSourceUrl} = audioOptions;
- const ttsSupported = (textToSpeechVoice.length > 0);
- const customSupported = (customSourceUrl.length > 0);
-
- const sourceIndexMap = new Map();
- const optionsSourcesCount = sources.length;
- for (let i = 0; i < optionsSourcesCount; ++i) {
- sourceIndexMap.set(sources[i], i);
- }
-
- const rawSources = [
- ['jpod101', 'JapanesePod101', true],
- ['jpod101-alternate', 'JapanesePod101 (Alternate)', true],
- ['jisho', 'Jisho.org', true],
- ['text-to-speech', 'Text-to-speech', ttsSupported],
- ['text-to-speech-reading', 'Text-to-speech (Kana reading)', ttsSupported],
- ['custom', 'Custom URL', customSupported],
- ['custom-json', 'Custom URL (JSON)', customSupported]
- ];
-
- const results = [];
- for (const [source, displayName, supported] of rawSources) {
- if (!supported) { continue; }
- const downloadable = this._sourceIsDownloadable(source);
- let optionsIndex = sourceIndexMap.get(source);
- const isInOptions = typeof optionsIndex !== 'undefined';
- if (!isInOptions) {
- optionsIndex = optionsSourcesCount;
- }
- results.push({
- source,
- displayName,
- index: results.length,
- optionsIndex,
- isInOptions,
- downloadable
- });
- }
-
- // Sort according to source order in options
- results.sort((a, b) => {
- const i = a.optionsIndex - b.optionsIndex;
- return i !== 0 ? i : a.index - b.index;
- });
-
- return results;
- }
-
_createMenu(sourceButton, term, reading) {
// Create menu
const menuContainerNode = this._display.displayGenerator.instantiateTemplate('audio-button-popup-menu');
@@ -569,15 +576,15 @@ class DisplayAudio {
}
_createMenuItems(menuContainerNode, menuItemContainer, term, reading) {
- const sources = this._getAudioSources(this._getAudioOptions());
const {displayGenerator} = this._display;
let showIcons = false;
const currentItems = [...menuItemContainer.children];
- for (const {source, displayName, isInOptions, downloadable} of sources) {
+ for (const source of this._audioSources) {
+ const {index, displayName, isInOptions, downloadable} = source;
const entries = this._getMenuItemEntries(source, term, reading);
for (let i = 0, ii = entries.length; i < ii; ++i) {
- const {valid, index, name} = entries[i];
- let node = this._getOrCreateMenuItem(currentItems, source, index);
+ const {valid, index: subIndex, name} = entries[i];
+ let node = this._getOrCreateMenuItem(currentItems, index, subIndex);
if (node === null) {
node = displayGenerator.instantiateTemplate('audio-button-popup-menu-item');
}
@@ -596,9 +603,9 @@ class DisplayAudio {
icon.dataset.icon = valid ? 'checkmark' : 'cross';
showIcons = true;
}
- node.dataset.source = source;
- if (index !== null) {
- node.dataset.index = `${index}`;
+ node.dataset.index = `${index}`;
+ if (subIndex !== null) {
+ node.dataset.subIndex = `${subIndex}`;
}
node.dataset.valid = `${valid}`;
node.dataset.sourceInOptions = `${isInOptions}`;
@@ -615,16 +622,16 @@ class DisplayAudio {
menuContainerNode.dataset.showIcons = `${showIcons}`;
}
- _getOrCreateMenuItem(currentItems, source, index) {
- if (index === null) { index = 0; }
+ _getOrCreateMenuItem(currentItems, index, subIndex) {
index = `${index}`;
+ subIndex = `${subIndex !== null ? subIndex : 0}`;
for (let i = 0, ii = currentItems.length; i < ii; ++i) {
const node = currentItems[i];
- if (source !== node.dataset.source) { continue; }
+ if (index !== node.dataset.index) { continue; }
- let index2 = node.dataset.index;
- if (typeof index2 === 'undefined') { index2 = '0'; }
- if (index !== index2) { continue; }
+ let subIndex2 = node.dataset.subIndex;
+ if (typeof subIndex2 === 'undefined') { subIndex2 = '0'; }
+ if (subIndex !== subIndex2) { continue; }
currentItems.splice(i, 1);
return node;
@@ -636,7 +643,7 @@ class DisplayAudio {
const cacheEntry = this._getCacheItem(term, reading, false);
if (typeof cacheEntry !== 'undefined') {
const {sourceMap} = cacheEntry;
- const sourceInfo = sourceMap.get(source);
+ const sourceInfo = sourceMap.get(source.index);
if (typeof sourceInfo !== 'undefined') {
const {infoList} = sourceInfo;
if (infoList !== null) {
@@ -659,23 +666,20 @@ class DisplayAudio {
return [{valid: null, index: null, name: null}];
}
- _updateMenuPrimaryCardAudio(menuBodyNode, term, reading) {
- const primaryCardAudio = this.getPrimaryCardAudio(term, reading);
- const {source: primaryCardAudioSource, index: primaryCardAudioIndex} = (primaryCardAudio !== null ? primaryCardAudio : {source: null, index: -1});
+ _getPrimaryCardAudio(term, reading) {
+ const cacheEntry = this._getCacheItem(term, reading, false);
+ return typeof cacheEntry !== 'undefined' ? cacheEntry.primaryCardAudio : null;
+ }
+ _updateMenuPrimaryCardAudio(menuBodyNode, term, reading) {
+ const primaryCardAudio = this._getPrimaryCardAudio(term, reading);
+ const primaryCardAudioIndex = (primaryCardAudio !== null ? primaryCardAudio.index : null);
+ const primaryCardAudioSubIndex = (primaryCardAudio !== null ? primaryCardAudio.subIndex : null);
const itemGroups = menuBodyNode.querySelectorAll('.popup-menu-item-group');
- let sourceIndex = 0;
- let sourcePre = null;
for (const node of itemGroups) {
- const {source} = node.dataset;
- if (source !== sourcePre) {
- sourcePre = source;
- sourceIndex = 0;
- } else {
- ++sourceIndex;
- }
-
- const isPrimaryCardAudio = (source === primaryCardAudioSource && sourceIndex === primaryCardAudioIndex);
+ const index = Number.parseInt(node.dataset.index, 10);
+ const subIndex = Number.parseInt(node.dataset.subIndex, 10);
+ const isPrimaryCardAudio = (index === primaryCardAudioIndex && subIndex === primaryCardAudioSubIndex);
node.dataset.isPrimaryCardAudio = `${isPrimaryCardAudio}`;
}
}
@@ -688,4 +692,9 @@ class DisplayAudio {
menu.updatePosition();
}
}
+
+ _getSourceData(source) {
+ const {type, url, voice} = source;
+ return {type, url, voice};
+ }
}
diff --git a/ext/js/display/display.js b/ext/js/display/display.js
index 1c4602c5..ccca8229 100644
--- a/ext/js/display/display.js
+++ b/ext/js/display/display.js
@@ -296,7 +296,6 @@ class Display extends EventDispatcher {
this._updateDocumentOptions(options);
this._updateTheme(options.general.popupTheme);
this.setCustomCss(options.general.customPopupCss);
- this._displayAudio.updateOptions(options);
this._hotkeyHelpController.setOptions(options);
this._displayGenerator.updateHotkeys();
this._hotkeyHelpController.setupNode(document.documentElement);
@@ -1330,7 +1329,7 @@ class Display extends EventDispatcher {
}
async _playAudioCurrent() {
- return await this._displayAudio.playAudio(this._index, 0);
+ await this._displayAudio.playAudio(this._index, 0);
}
_getEntry(index) {
@@ -1552,26 +1551,17 @@ class Display extends EventDispatcher {
}
async _injectAnkiNoteMedia(dictionaryEntry, options, fields) {
- const {
- anki: {screenshot: {format, quality}},
- audio: {sources, customSourceUrl}
- } = options;
+ const {anki: {screenshot: {format, quality}}} = options;
const timestamp = Date.now();
const dictionaryEntryDetails = this._getDictionaryEntryDetailsForNote(dictionaryEntry);
- let audioDetails = null;
- if (dictionaryEntryDetails.type !== 'kanji' && AnkiUtil.fieldsObjectContainsMarker(fields, 'audio')) {
- const primaryCardAudio = this._displayAudio.getPrimaryCardAudio(dictionaryEntryDetails.term, dictionaryEntryDetails.reading);
- let preferredAudioIndex = null;
- let sources2 = sources;
- if (primaryCardAudio !== null) {
- sources2 = [primaryCardAudio.source];
- preferredAudioIndex = primaryCardAudio.index;
- }
- audioDetails = {sources: sources2, preferredAudioIndex, customSourceUrl};
- }
+ const audioDetails = (
+ dictionaryEntryDetails.type !== 'kanji' && AnkiUtil.fieldsObjectContainsMarker(fields, 'audio') ?
+ this._displayAudio.getAnkiNoteMediaAudioDetails(dictionaryEntryDetails.term, dictionaryEntryDetails.reading) :
+ null
+ );
const screenshotDetails = (
AnkiUtil.fieldsObjectContainsMarker(fields, 'screenshot') && typeof this._contentOriginTabId === 'number' ?
@@ -1906,7 +1896,7 @@ class Display extends EventDispatcher {
}
_onHotkeyActionPlayAudioFromSource(source) {
- this._displayAudio.playAudio(this._index, 0, [source]);
+ this._displayAudio.playAudio(this._index, 0, source);
}
_closeAllPopupMenus() {
diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js
index 70e99f11..e3b507b4 100644
--- a/ext/js/media/audio-downloader.js
+++ b/ext/js/media/audio-downloader.js
@@ -26,7 +26,6 @@ class AudioDownloader {
this._japaneseUtil = japaneseUtil;
this._requestBuilder = requestBuilder;
this._customAudioListSchema = null;
- this._customAudioListSchema = null;
this._getInfoHandlers = new Map([
['jpod101', this._getInfoJpod101.bind(this)],
['jpod101-alternate', this._getInfoJpod101Alternate.bind(this)],
@@ -38,11 +37,11 @@ class AudioDownloader {
]);
}
- async getTermAudioInfoList(source, term, reading, details) {
- const handler = this._getInfoHandlers.get(source);
+ async getTermAudioInfoList(source, term, reading) {
+ const handler = this._getInfoHandlers.get(source.type);
if (typeof handler === 'function') {
try {
- return await handler(term, reading, details);
+ return await handler(term, reading, source);
} catch (e) {
// NOP
}
@@ -50,9 +49,9 @@ class AudioDownloader {
return [];
}
- async downloadTermAudio(sources, preferredAudioIndex, term, reading, details) {
+ async downloadTermAudio(sources, preferredAudioIndex, term, reading) {
for (const source of sources) {
- let infoList = await this.getTermAudioInfoList(source, term, reading, details);
+ let infoList = await this.getTermAudioInfoList(source, term, reading);
if (typeof preferredAudioIndex === 'number') {
infoList = (preferredAudioIndex >= 0 && preferredAudioIndex < infoList.length ? [infoList[preferredAudioIndex]] : []);
}
@@ -60,7 +59,7 @@ class AudioDownloader {
switch (info.type) {
case 'url':
try {
- return await this._downloadAudioFromUrl(info.url, source);
+ return await this._downloadAudioFromUrl(info.url, source.type);
} catch (e) {
// NOP
}
@@ -178,27 +177,27 @@ class AudioDownloader {
throw new Error('Failed to find audio URL');
}
- async _getInfoTextToSpeech(term, reading, {textToSpeechVoice}) {
- if (!textToSpeechVoice) {
+ async _getInfoTextToSpeech(term, reading, {voice}) {
+ if (!voice) {
throw new Error('No voice');
}
- return [{type: 'tts', text: term, voice: textToSpeechVoice}];
+ return [{type: 'tts', text: term, voice: voice}];
}
- async _getInfoTextToSpeechReading(term, reading, {textToSpeechVoice}) {
- if (!textToSpeechVoice) {
+ async _getInfoTextToSpeechReading(term, reading, {voice}) {
+ if (!voice) {
throw new Error('No voice');
}
- return [{type: 'tts', text: reading, voice: textToSpeechVoice}];
+ return [{type: 'tts', text: reading, voice: voice}];
}
- async _getInfoCustom(term, reading, {customSourceUrl}) {
- const url = this._getCustomUrl(term, reading, customSourceUrl);
+ async _getInfoCustom(term, reading, {url}) {
+ url = this._getCustomUrl(term, reading, url);
return [{type: 'url', url}];
}
- async _getInfoCustomJson(term, reading, {customSourceUrl}) {
- const url = this._getCustomUrl(term, reading, customSourceUrl);
+ async _getInfoCustomJson(term, reading, {url}) {
+ url = this._getCustomUrl(term, reading, url);
const response = await this._requestBuilder.fetchAnonymous(url, {
method: 'GET',
@@ -230,15 +229,15 @@ class AudioDownloader {
return results;
}
- _getCustomUrl(term, reading, customSourceUrl) {
- if (typeof customSourceUrl !== 'string') {
+ _getCustomUrl(term, reading, url) {
+ if (typeof url !== 'string') {
throw new Error('No custom URL defined');
}
const data = {term, reading};
- return customSourceUrl.replace(/\{([^}]*)\}/g, (m0, m1) => (Object.prototype.hasOwnProperty.call(data, m1) ? `${data[m1]}` : m0));
+ return url.replace(/\{([^}]*)\}/g, (m0, m1) => (Object.prototype.hasOwnProperty.call(data, m1) ? `${data[m1]}` : m0));
}
- async _downloadAudioFromUrl(url, source) {
+ async _downloadAudioFromUrl(url, sourceType) {
const response = await this._requestBuilder.fetchAnonymous(url, {
method: 'GET',
mode: 'cors',
@@ -254,7 +253,7 @@ class AudioDownloader {
const arrayBuffer = await response.arrayBuffer();
- if (!await this._isAudioBinaryValid(arrayBuffer, source)) {
+ if (!await this._isAudioBinaryValid(arrayBuffer, sourceType)) {
throw new Error('Could not retrieve audio');
}
@@ -263,8 +262,8 @@ class AudioDownloader {
return {data, contentType};
}
- async _isAudioBinaryValid(arrayBuffer, source) {
- switch (source) {
+ async _isAudioBinaryValid(arrayBuffer, sourceType) {
+ switch (sourceType) {
case 'jpod101':
{
const digest = await this._arrayBufferDigest(arrayBuffer);
@@ -304,20 +303,15 @@ class AudioDownloader {
}
async _getCustomAudioListSchema() {
- let schema = this._customAudioListSchema;
- if (schema === null) {
- const url = chrome.runtime.getURL('/data/schemas/custom-audio-list-schema.json');
- const response = await fetch(url, {
- method: 'GET',
- mode: 'no-cors',
- cache: 'default',
- credentials: 'omit',
- redirect: 'follow',
- referrerPolicy: 'no-referrer'
- });
- schema = await response.json();
- this._customAudioListSchema = schema;
- }
- return schema;
+ const url = chrome.runtime.getURL('/data/schemas/custom-audio-list-schema.json');
+ const response = await fetch(url, {
+ method: 'GET',
+ mode: 'no-cors',
+ cache: 'default',
+ credentials: 'omit',
+ redirect: 'follow',
+ referrerPolicy: 'no-referrer'
+ });
+ return await response.json();
}
}
diff --git a/ext/js/media/audio-system.js b/ext/js/media/audio-system.js
index cc2bcfc0..03e68767 100644
--- a/ext/js/media/audio-system.js
+++ b/ext/js/media/audio-system.js
@@ -42,11 +42,11 @@ class AudioSystem extends EventDispatcher {
return this._fallbackAudio;
}
- createAudio(url, source) {
+ createAudio(url, sourceType) {
return new Promise((resolve, reject) => {
const audio = new Audio(url);
audio.addEventListener('loadeddata', () => {
- if (!this._isAudioValid(audio, source)) {
+ if (!this._isAudioValid(audio, sourceType)) {
reject(new Error('Could not retrieve audio'));
} else {
resolve(audio);
@@ -70,8 +70,8 @@ class AudioSystem extends EventDispatcher {
this.trigger('voiceschanged', e);
}
- _isAudioValid(audio, source) {
- switch (source) {
+ _isAudioValid(audio, sourceType) {
+ switch (sourceType) {
case 'jpod101':
{
const duration = audio.duration;