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
This commit is contained in:
parent
0f0e80aadb
commit
efd35de67f
@ -519,8 +519,8 @@ class Backend {
|
|||||||
return this._runCommand(command, params);
|
return this._runCommand(command, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onApiGetTermAudioInfoList({source, term, reading, details}) {
|
async _onApiGetTermAudioInfoList({source, term, reading}) {
|
||||||
return await this._audioDownloader.getTermAudioInfoList(source, term, reading, details);
|
return await this._audioDownloader.getTermAudioInfoList(source, term, reading);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onApiSendMessageToFrame({frameId: targetFrameId, action, params}, sender) {
|
_onApiSendMessageToFrame({frameId: targetFrameId, action, params}, sender) {
|
||||||
@ -1742,7 +1742,7 @@ class Backend {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {sources, preferredAudioIndex, customSourceUrl} = details;
|
const {sources, preferredAudioIndex} = details;
|
||||||
let data;
|
let data;
|
||||||
let contentType;
|
let contentType;
|
||||||
try {
|
try {
|
||||||
@ -1750,13 +1750,7 @@ class Backend {
|
|||||||
sources,
|
sources,
|
||||||
preferredAudioIndex,
|
preferredAudioIndex,
|
||||||
term,
|
term,
|
||||||
reading,
|
reading
|
||||||
{
|
|
||||||
textToSpeechVoice: null,
|
|
||||||
customSourceUrl,
|
|
||||||
binary: true,
|
|
||||||
disableCache: true
|
|
||||||
}
|
|
||||||
));
|
));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// No audio
|
// No audio
|
||||||
|
@ -68,8 +68,8 @@ class API {
|
|||||||
return this._invoke('suspendAnkiCardsForNote', {noteId});
|
return this._invoke('suspendAnkiCardsForNote', {noteId});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTermAudioInfoList(source, term, reading, details) {
|
getTermAudioInfoList(source, term, reading) {
|
||||||
return this._invoke('getTermAudioInfoList', {source, term, reading, details});
|
return this._invoke('getTermAudioInfoList', {source, term, reading});
|
||||||
}
|
}
|
||||||
|
|
||||||
commandExec(command, params) {
|
commandExec(command, params) {
|
||||||
|
@ -25,6 +25,8 @@ class DisplayAudio {
|
|||||||
this._display = display;
|
this._display = display;
|
||||||
this._audioPlaying = null;
|
this._audioPlaying = null;
|
||||||
this._audioSystem = new AudioSystem();
|
this._audioSystem = new AudioSystem();
|
||||||
|
this._playbackVolume = 1.0;
|
||||||
|
this._autoPlay = false;
|
||||||
this._autoPlayAudioTimer = null;
|
this._autoPlayAudioTimer = null;
|
||||||
this._autoPlayAudioDelay = 400;
|
this._autoPlayAudioDelay = 400;
|
||||||
this._eventListeners = new EventListenerCollection();
|
this._eventListeners = new EventListenerCollection();
|
||||||
@ -32,6 +34,16 @@ class DisplayAudio {
|
|||||||
this._menuContainer = document.querySelector('#popup-menus');
|
this._menuContainer = document.querySelector('#popup-menus');
|
||||||
this._entriesToken = {};
|
this._entriesToken = {};
|
||||||
this._openMenus = new Set();
|
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() {
|
get autoPlayAudioDelay() {
|
||||||
@ -44,11 +56,8 @@ class DisplayAudio {
|
|||||||
|
|
||||||
prepare() {
|
prepare() {
|
||||||
this._audioSystem.prepare();
|
this._audioSystem.prepare();
|
||||||
}
|
this._display.on('optionsUpdated', this._onOptionsUpdated.bind(this));
|
||||||
|
this._onOptionsUpdated({options: this._display.getOptions()});
|
||||||
updateOptions(options) {
|
|
||||||
const data = document.documentElement.dataset;
|
|
||||||
data.audioEnabled = `${options.audio.enabled && options.audio.sources.length > 0}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanupEntries() {
|
cleanupEntries() {
|
||||||
@ -68,8 +77,7 @@ class DisplayAudio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupEntriesComplete() {
|
setupEntriesComplete() {
|
||||||
const audioOptions = this._getAudioOptions();
|
if (!this._autoPlay) { return; }
|
||||||
if (!audioOptions.enabled || !audioOptions.autoPlay) { return; }
|
|
||||||
|
|
||||||
this.clearAutoPlayTimer();
|
this.clearAutoPlayTimer();
|
||||||
|
|
||||||
@ -103,85 +111,81 @@ class DisplayAudio {
|
|||||||
this._audioPlaying = null;
|
this._audioPlaying = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async playAudio(dictionaryEntryIndex, headwordIndex, sources=null, sourceDetailsMap=null) {
|
async playAudio(dictionaryEntryIndex, headwordIndex, sourceType=null) {
|
||||||
this.stopAudio();
|
let sources = this._audioSources;
|
||||||
this.clearAutoPlayTimer();
|
if (sourceType !== null) {
|
||||||
|
sources = [];
|
||||||
const headword = this._getHeadword(dictionaryEntryIndex, headwordIndex);
|
for (const source of this._audioSources) {
|
||||||
if (headword === null) {
|
if (source.type === sourceType) {
|
||||||
return {audio: null, source: null, valid: false};
|
sources.push(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this._playAudio(dictionaryEntryIndex, headwordIndex, sources, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttons = this._getAudioPlayButtons(dictionaryEntryIndex, headwordIndex);
|
getAnkiNoteMediaAudioDetails(term, reading) {
|
||||||
|
const sources = [];
|
||||||
const {term, reading} = headword;
|
let preferredAudioIndex = null;
|
||||||
const audioOptions = this._getAudioOptions();
|
const primaryCardAudio = this._getPrimaryCardAudio(term, reading);
|
||||||
const {textToSpeechVoice, customSourceUrl, volume} = audioOptions;
|
if (primaryCardAudio !== null) {
|
||||||
if (!Array.isArray(sources)) {
|
const {index, subIndex} = primaryCardAudio;
|
||||||
({sources} = audioOptions);
|
const source = this._audioSources[index];
|
||||||
}
|
sources.push(this._getSourceData(source));
|
||||||
if (!(sourceDetailsMap instanceof Map)) {
|
preferredAudioIndex = subIndex;
|
||||||
sourceDetailsMap = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
audio = this._audioSystem.getFallbackAudio();
|
for (const source of this._audioSources) {
|
||||||
title = 'Could not find audio';
|
if (!source.isInOptions) { continue; }
|
||||||
}
|
sources.push(this._getSourceData(source));
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return {sources, preferredAudioIndex};
|
||||||
return {audio, source, valid};
|
|
||||||
} finally {
|
|
||||||
progressIndicatorVisible.clearOverride(overrideToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPrimaryCardAudio(term, reading) {
|
|
||||||
const cacheEntry = this._getCacheItem(term, reading, false);
|
|
||||||
const primaryCardAudio = typeof cacheEntry !== 'undefined' ? cacheEntry.primaryCardAudio : null;
|
|
||||||
return primaryCardAudio;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private
|
// Private
|
||||||
|
|
||||||
|
_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);
|
||||||
|
}
|
||||||
|
for (const type of requiredAudioSources) {
|
||||||
|
this._addAudioSourceInfo(type, '', '', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = document.documentElement.dataset;
|
||||||
|
data.audioEnabled = `${enabled && sources.length > 0}`;
|
||||||
|
|
||||||
|
this._cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
_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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_onAudioPlayButtonClick(dictionaryEntryIndex, headwordIndex, e) {
|
_onAudioPlayButtonClick(dictionaryEntryIndex, headwordIndex, e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@ -229,29 +233,93 @@ class DisplayAudio {
|
|||||||
|
|
||||||
_getMenuItemSourceInfo(item) {
|
_getMenuItemSourceInfo(item) {
|
||||||
const group = item.closest('.popup-menu-item-group');
|
const group = item.closest('.popup-menu-item-group');
|
||||||
if (group === null) { return null; }
|
if (group !== null) {
|
||||||
|
let {index, subIndex} = group.dataset;
|
||||||
let {source, index} = group.dataset;
|
|
||||||
if (typeof index !== 'undefined') {
|
|
||||||
index = Number.parseInt(index, 10);
|
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;
|
||||||
}
|
}
|
||||||
const hasIndex = (Number.isFinite(index) && Math.floor(index) === index);
|
return {source, subIndex};
|
||||||
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) {
|
async _playAudioFromSource(dictionaryEntryIndex, headwordIndex, item) {
|
||||||
const sourceInfo = this._getMenuItemSourceInfo(item);
|
const {source, subIndex} = this._getMenuItemSourceInfo(item);
|
||||||
if (sourceInfo === null) { return; }
|
if (source === null) { return; }
|
||||||
|
|
||||||
const {source, index, hasIndex} = sourceInfo;
|
|
||||||
const sourceDetailsMap = hasIndex ? new Map([[source, {start: index, end: index + 1}]]) : null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = this._entriesToken;
|
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) {
|
if (valid && token === this._entriesToken) {
|
||||||
this._setPrimaryAudio(dictionaryEntryIndex, headwordIndex, item, null, false);
|
this._setPrimaryAudio(dictionaryEntryIndex, headwordIndex, item, null, false);
|
||||||
}
|
}
|
||||||
@ -261,11 +329,8 @@ class DisplayAudio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_setPrimaryAudio(dictionaryEntryIndex, headwordIndex, item, menu, canToggleOff) {
|
_setPrimaryAudio(dictionaryEntryIndex, headwordIndex, item, menu, canToggleOff) {
|
||||||
const sourceInfo = this._getMenuItemSourceInfo(item);
|
const {source, subIndex} = this._getMenuItemSourceInfo(item);
|
||||||
if (sourceInfo === null) { return; }
|
if (source === null || !source.downloadable) { return; }
|
||||||
|
|
||||||
const {source, index} = sourceInfo;
|
|
||||||
if (!this._sourceIsDownloadable(source)) { return; }
|
|
||||||
|
|
||||||
const headword = this._getHeadword(dictionaryEntryIndex, headwordIndex);
|
const headword = this._getHeadword(dictionaryEntryIndex, headwordIndex);
|
||||||
if (headword === null) { return; }
|
if (headword === null) { return; }
|
||||||
@ -274,7 +339,12 @@ class DisplayAudio {
|
|||||||
const cacheEntry = this._getCacheItem(term, reading, true);
|
const cacheEntry = this._getCacheItem(term, reading, true);
|
||||||
|
|
||||||
let {primaryCardAudio} = cacheEntry;
|
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;
|
cacheEntry.primaryCardAudio = primaryCardAudio;
|
||||||
|
|
||||||
if (menu !== null) {
|
if (menu !== null) {
|
||||||
@ -304,19 +374,19 @@ class DisplayAudio {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _createTermAudio(sources, sourceDetailsMap, term, reading, details) {
|
async _createTermAudio(term, reading, sources, audioInfoListIndex) {
|
||||||
const {sourceMap} = this._getCacheItem(term, reading, true);
|
const {sourceMap} = this._getCacheItem(term, reading, true);
|
||||||
|
|
||||||
for (let i = 0, ii = sources.length; i < ii; ++i) {
|
for (const source of sources) {
|
||||||
const source = sources[i];
|
const {index} = source;
|
||||||
|
|
||||||
let cacheUpdated = false;
|
let cacheUpdated = false;
|
||||||
let infoListPromise;
|
let infoListPromise;
|
||||||
let sourceInfo = sourceMap.get(source);
|
let sourceInfo = sourceMap.get(index);
|
||||||
if (typeof sourceInfo === 'undefined') {
|
if (typeof sourceInfo === 'undefined') {
|
||||||
infoListPromise = this._getTermAudioInfoList(source, term, reading, details);
|
infoListPromise = this._getTermAudioInfoList(source, term, reading);
|
||||||
sourceInfo = {infoListPromise, infoList: null};
|
sourceInfo = {infoListPromise, infoList: null};
|
||||||
sourceMap.set(source, sourceInfo);
|
sourceMap.set(index, sourceInfo);
|
||||||
cacheUpdated = true;
|
cacheUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,29 +396,29 @@ class DisplayAudio {
|
|||||||
sourceInfo.infoList = infoList;
|
sourceInfo.infoList = infoList;
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = 0;
|
const {audio, index: subIndex, cacheUpdated: cacheUpdated2} = await this._createAudioFromInfoList(source, infoList, audioInfoListIndex);
|
||||||
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);
|
|
||||||
if (cacheUpdated || cacheUpdated2) { this._updateOpenMenu(); }
|
if (cacheUpdated || cacheUpdated2) { this._updateOpenMenu(); }
|
||||||
if (result !== null) { return result; }
|
if (audio !== null) {
|
||||||
|
return {audio, source, subIndex};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _createAudioFromInfoList(source, infoList, start, end) {
|
async _createAudioFromInfoList(source, infoList, audioInfoListIndex) {
|
||||||
let result = null;
|
let start = 0;
|
||||||
let cacheUpdated = false;
|
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) {
|
for (let i = start; i < end; ++i) {
|
||||||
const item = infoList[i];
|
const item = infoList[i];
|
||||||
|
|
||||||
@ -361,7 +431,7 @@ class DisplayAudio {
|
|||||||
item.audioPromise = audioPromise;
|
item.audioPromise = audioPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheUpdated = true;
|
result.cacheUpdated = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
audio = await audioPromise;
|
audio = await audioPromise;
|
||||||
@ -375,17 +445,18 @@ class DisplayAudio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (audio !== null) {
|
if (audio !== null) {
|
||||||
result = {audio, source, infoListIndex: i};
|
result.audio = audio;
|
||||||
|
result.index = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {result, cacheUpdated};
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _createAudioFromInfo(info, source) {
|
async _createAudioFromInfo(info, source) {
|
||||||
switch (info.type) {
|
switch (info.type) {
|
||||||
case 'url':
|
case 'url':
|
||||||
return await this._audioSystem.createAudio(info.url, source);
|
return await this._audioSystem.createAudio(info.url, source.type);
|
||||||
case 'tts':
|
case 'tts':
|
||||||
return this._audioSystem.createTextToSpeechAudio(info.text, info.voice);
|
return this._audioSystem.createTextToSpeechAudio(info.text, info.voice);
|
||||||
default:
|
default:
|
||||||
@ -393,8 +464,9 @@ class DisplayAudio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getTermAudioInfoList(source, term, reading, details) {
|
async _getTermAudioInfoList(source, term, reading) {
|
||||||
const infoList = await yomichan.api.getTermAudioInfoList(source, term, reading, details);
|
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}));
|
return infoList.map((info) => ({info, audioPromise: null, audioResolved: false, audio: null}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,22 +487,6 @@ class DisplayAudio {
|
|||||||
return JSON.stringify([term, reading]);
|
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) {
|
_updateAudioPlayButtonBadge(button, potentialAvailableAudioCount) {
|
||||||
if (potentialAvailableAudioCount === null) {
|
if (potentialAvailableAudioCount === null) {
|
||||||
delete button.dataset.potentialAvailableAudioCount;
|
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) {
|
_createMenu(sourceButton, term, reading) {
|
||||||
// Create menu
|
// Create menu
|
||||||
const menuContainerNode = this._display.displayGenerator.instantiateTemplate('audio-button-popup-menu');
|
const menuContainerNode = this._display.displayGenerator.instantiateTemplate('audio-button-popup-menu');
|
||||||
@ -569,15 +576,15 @@ class DisplayAudio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_createMenuItems(menuContainerNode, menuItemContainer, term, reading) {
|
_createMenuItems(menuContainerNode, menuItemContainer, term, reading) {
|
||||||
const sources = this._getAudioSources(this._getAudioOptions());
|
|
||||||
const {displayGenerator} = this._display;
|
const {displayGenerator} = this._display;
|
||||||
let showIcons = false;
|
let showIcons = false;
|
||||||
const currentItems = [...menuItemContainer.children];
|
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);
|
const entries = this._getMenuItemEntries(source, term, reading);
|
||||||
for (let i = 0, ii = entries.length; i < ii; ++i) {
|
for (let i = 0, ii = entries.length; i < ii; ++i) {
|
||||||
const {valid, index, name} = entries[i];
|
const {valid, index: subIndex, name} = entries[i];
|
||||||
let node = this._getOrCreateMenuItem(currentItems, source, index);
|
let node = this._getOrCreateMenuItem(currentItems, index, subIndex);
|
||||||
if (node === null) {
|
if (node === null) {
|
||||||
node = displayGenerator.instantiateTemplate('audio-button-popup-menu-item');
|
node = displayGenerator.instantiateTemplate('audio-button-popup-menu-item');
|
||||||
}
|
}
|
||||||
@ -596,9 +603,9 @@ class DisplayAudio {
|
|||||||
icon.dataset.icon = valid ? 'checkmark' : 'cross';
|
icon.dataset.icon = valid ? 'checkmark' : 'cross';
|
||||||
showIcons = true;
|
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.valid = `${valid}`;
|
||||||
node.dataset.sourceInOptions = `${isInOptions}`;
|
node.dataset.sourceInOptions = `${isInOptions}`;
|
||||||
@ -615,16 +622,16 @@ class DisplayAudio {
|
|||||||
menuContainerNode.dataset.showIcons = `${showIcons}`;
|
menuContainerNode.dataset.showIcons = `${showIcons}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getOrCreateMenuItem(currentItems, source, index) {
|
_getOrCreateMenuItem(currentItems, index, subIndex) {
|
||||||
if (index === null) { index = 0; }
|
|
||||||
index = `${index}`;
|
index = `${index}`;
|
||||||
|
subIndex = `${subIndex !== null ? subIndex : 0}`;
|
||||||
for (let i = 0, ii = currentItems.length; i < ii; ++i) {
|
for (let i = 0, ii = currentItems.length; i < ii; ++i) {
|
||||||
const node = currentItems[i];
|
const node = currentItems[i];
|
||||||
if (source !== node.dataset.source) { continue; }
|
if (index !== node.dataset.index) { continue; }
|
||||||
|
|
||||||
let index2 = node.dataset.index;
|
let subIndex2 = node.dataset.subIndex;
|
||||||
if (typeof index2 === 'undefined') { index2 = '0'; }
|
if (typeof subIndex2 === 'undefined') { subIndex2 = '0'; }
|
||||||
if (index !== index2) { continue; }
|
if (subIndex !== subIndex2) { continue; }
|
||||||
|
|
||||||
currentItems.splice(i, 1);
|
currentItems.splice(i, 1);
|
||||||
return node;
|
return node;
|
||||||
@ -636,7 +643,7 @@ class DisplayAudio {
|
|||||||
const cacheEntry = this._getCacheItem(term, reading, false);
|
const cacheEntry = this._getCacheItem(term, reading, false);
|
||||||
if (typeof cacheEntry !== 'undefined') {
|
if (typeof cacheEntry !== 'undefined') {
|
||||||
const {sourceMap} = cacheEntry;
|
const {sourceMap} = cacheEntry;
|
||||||
const sourceInfo = sourceMap.get(source);
|
const sourceInfo = sourceMap.get(source.index);
|
||||||
if (typeof sourceInfo !== 'undefined') {
|
if (typeof sourceInfo !== 'undefined') {
|
||||||
const {infoList} = sourceInfo;
|
const {infoList} = sourceInfo;
|
||||||
if (infoList !== null) {
|
if (infoList !== null) {
|
||||||
@ -659,23 +666,20 @@ class DisplayAudio {
|
|||||||
return [{valid: null, index: null, name: null}];
|
return [{valid: null, index: null, name: null}];
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateMenuPrimaryCardAudio(menuBodyNode, term, reading) {
|
_getPrimaryCardAudio(term, reading) {
|
||||||
const primaryCardAudio = this.getPrimaryCardAudio(term, reading);
|
const cacheEntry = this._getCacheItem(term, reading, false);
|
||||||
const {source: primaryCardAudioSource, index: primaryCardAudioIndex} = (primaryCardAudio !== null ? primaryCardAudio : {source: null, index: -1});
|
return typeof cacheEntry !== 'undefined' ? cacheEntry.primaryCardAudio : 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);
|
_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');
|
||||||
|
for (const node of itemGroups) {
|
||||||
|
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}`;
|
node.dataset.isPrimaryCardAudio = `${isPrimaryCardAudio}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -688,4 +692,9 @@ class DisplayAudio {
|
|||||||
menu.updatePosition();
|
menu.updatePosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getSourceData(source) {
|
||||||
|
const {type, url, voice} = source;
|
||||||
|
return {type, url, voice};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -296,7 +296,6 @@ class Display extends EventDispatcher {
|
|||||||
this._updateDocumentOptions(options);
|
this._updateDocumentOptions(options);
|
||||||
this._updateTheme(options.general.popupTheme);
|
this._updateTheme(options.general.popupTheme);
|
||||||
this.setCustomCss(options.general.customPopupCss);
|
this.setCustomCss(options.general.customPopupCss);
|
||||||
this._displayAudio.updateOptions(options);
|
|
||||||
this._hotkeyHelpController.setOptions(options);
|
this._hotkeyHelpController.setOptions(options);
|
||||||
this._displayGenerator.updateHotkeys();
|
this._displayGenerator.updateHotkeys();
|
||||||
this._hotkeyHelpController.setupNode(document.documentElement);
|
this._hotkeyHelpController.setupNode(document.documentElement);
|
||||||
@ -1330,7 +1329,7 @@ class Display extends EventDispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _playAudioCurrent() {
|
async _playAudioCurrent() {
|
||||||
return await this._displayAudio.playAudio(this._index, 0);
|
await this._displayAudio.playAudio(this._index, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getEntry(index) {
|
_getEntry(index) {
|
||||||
@ -1552,26 +1551,17 @@ class Display extends EventDispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _injectAnkiNoteMedia(dictionaryEntry, options, fields) {
|
async _injectAnkiNoteMedia(dictionaryEntry, options, fields) {
|
||||||
const {
|
const {anki: {screenshot: {format, quality}}} = options;
|
||||||
anki: {screenshot: {format, quality}},
|
|
||||||
audio: {sources, customSourceUrl}
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
|
|
||||||
const dictionaryEntryDetails = this._getDictionaryEntryDetailsForNote(dictionaryEntry);
|
const dictionaryEntryDetails = this._getDictionaryEntryDetailsForNote(dictionaryEntry);
|
||||||
|
|
||||||
let audioDetails = null;
|
const audioDetails = (
|
||||||
if (dictionaryEntryDetails.type !== 'kanji' && AnkiUtil.fieldsObjectContainsMarker(fields, 'audio')) {
|
dictionaryEntryDetails.type !== 'kanji' && AnkiUtil.fieldsObjectContainsMarker(fields, 'audio') ?
|
||||||
const primaryCardAudio = this._displayAudio.getPrimaryCardAudio(dictionaryEntryDetails.term, dictionaryEntryDetails.reading);
|
this._displayAudio.getAnkiNoteMediaAudioDetails(dictionaryEntryDetails.term, dictionaryEntryDetails.reading) :
|
||||||
let preferredAudioIndex = null;
|
null
|
||||||
let sources2 = sources;
|
);
|
||||||
if (primaryCardAudio !== null) {
|
|
||||||
sources2 = [primaryCardAudio.source];
|
|
||||||
preferredAudioIndex = primaryCardAudio.index;
|
|
||||||
}
|
|
||||||
audioDetails = {sources: sources2, preferredAudioIndex, customSourceUrl};
|
|
||||||
}
|
|
||||||
|
|
||||||
const screenshotDetails = (
|
const screenshotDetails = (
|
||||||
AnkiUtil.fieldsObjectContainsMarker(fields, 'screenshot') && typeof this._contentOriginTabId === 'number' ?
|
AnkiUtil.fieldsObjectContainsMarker(fields, 'screenshot') && typeof this._contentOriginTabId === 'number' ?
|
||||||
@ -1906,7 +1896,7 @@ class Display extends EventDispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onHotkeyActionPlayAudioFromSource(source) {
|
_onHotkeyActionPlayAudioFromSource(source) {
|
||||||
this._displayAudio.playAudio(this._index, 0, [source]);
|
this._displayAudio.playAudio(this._index, 0, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
_closeAllPopupMenus() {
|
_closeAllPopupMenus() {
|
||||||
|
@ -26,7 +26,6 @@ class AudioDownloader {
|
|||||||
this._japaneseUtil = japaneseUtil;
|
this._japaneseUtil = japaneseUtil;
|
||||||
this._requestBuilder = requestBuilder;
|
this._requestBuilder = requestBuilder;
|
||||||
this._customAudioListSchema = null;
|
this._customAudioListSchema = null;
|
||||||
this._customAudioListSchema = null;
|
|
||||||
this._getInfoHandlers = new Map([
|
this._getInfoHandlers = new Map([
|
||||||
['jpod101', this._getInfoJpod101.bind(this)],
|
['jpod101', this._getInfoJpod101.bind(this)],
|
||||||
['jpod101-alternate', this._getInfoJpod101Alternate.bind(this)],
|
['jpod101-alternate', this._getInfoJpod101Alternate.bind(this)],
|
||||||
@ -38,11 +37,11 @@ class AudioDownloader {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTermAudioInfoList(source, term, reading, details) {
|
async getTermAudioInfoList(source, term, reading) {
|
||||||
const handler = this._getInfoHandlers.get(source);
|
const handler = this._getInfoHandlers.get(source.type);
|
||||||
if (typeof handler === 'function') {
|
if (typeof handler === 'function') {
|
||||||
try {
|
try {
|
||||||
return await handler(term, reading, details);
|
return await handler(term, reading, source);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
@ -50,9 +49,9 @@ class AudioDownloader {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadTermAudio(sources, preferredAudioIndex, term, reading, details) {
|
async downloadTermAudio(sources, preferredAudioIndex, term, reading) {
|
||||||
for (const source of sources) {
|
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') {
|
if (typeof preferredAudioIndex === 'number') {
|
||||||
infoList = (preferredAudioIndex >= 0 && preferredAudioIndex < infoList.length ? [infoList[preferredAudioIndex]] : []);
|
infoList = (preferredAudioIndex >= 0 && preferredAudioIndex < infoList.length ? [infoList[preferredAudioIndex]] : []);
|
||||||
}
|
}
|
||||||
@ -60,7 +59,7 @@ class AudioDownloader {
|
|||||||
switch (info.type) {
|
switch (info.type) {
|
||||||
case 'url':
|
case 'url':
|
||||||
try {
|
try {
|
||||||
return await this._downloadAudioFromUrl(info.url, source);
|
return await this._downloadAudioFromUrl(info.url, source.type);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
@ -178,27 +177,27 @@ class AudioDownloader {
|
|||||||
throw new Error('Failed to find audio URL');
|
throw new Error('Failed to find audio URL');
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getInfoTextToSpeech(term, reading, {textToSpeechVoice}) {
|
async _getInfoTextToSpeech(term, reading, {voice}) {
|
||||||
if (!textToSpeechVoice) {
|
if (!voice) {
|
||||||
throw new Error('No 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}) {
|
async _getInfoTextToSpeechReading(term, reading, {voice}) {
|
||||||
if (!textToSpeechVoice) {
|
if (!voice) {
|
||||||
throw new Error('No 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}) {
|
async _getInfoCustom(term, reading, {url}) {
|
||||||
const url = this._getCustomUrl(term, reading, customSourceUrl);
|
url = this._getCustomUrl(term, reading, url);
|
||||||
return [{type: 'url', url}];
|
return [{type: 'url', url}];
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getInfoCustomJson(term, reading, {customSourceUrl}) {
|
async _getInfoCustomJson(term, reading, {url}) {
|
||||||
const url = this._getCustomUrl(term, reading, customSourceUrl);
|
url = this._getCustomUrl(term, reading, url);
|
||||||
|
|
||||||
const response = await this._requestBuilder.fetchAnonymous(url, {
|
const response = await this._requestBuilder.fetchAnonymous(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -230,15 +229,15 @@ class AudioDownloader {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getCustomUrl(term, reading, customSourceUrl) {
|
_getCustomUrl(term, reading, url) {
|
||||||
if (typeof customSourceUrl !== 'string') {
|
if (typeof url !== 'string') {
|
||||||
throw new Error('No custom URL defined');
|
throw new Error('No custom URL defined');
|
||||||
}
|
}
|
||||||
const data = {term, reading};
|
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, {
|
const response = await this._requestBuilder.fetchAnonymous(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
@ -254,7 +253,7 @@ class AudioDownloader {
|
|||||||
|
|
||||||
const arrayBuffer = await response.arrayBuffer();
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
|
||||||
if (!await this._isAudioBinaryValid(arrayBuffer, source)) {
|
if (!await this._isAudioBinaryValid(arrayBuffer, sourceType)) {
|
||||||
throw new Error('Could not retrieve audio');
|
throw new Error('Could not retrieve audio');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,8 +262,8 @@ class AudioDownloader {
|
|||||||
return {data, contentType};
|
return {data, contentType};
|
||||||
}
|
}
|
||||||
|
|
||||||
async _isAudioBinaryValid(arrayBuffer, source) {
|
async _isAudioBinaryValid(arrayBuffer, sourceType) {
|
||||||
switch (source) {
|
switch (sourceType) {
|
||||||
case 'jpod101':
|
case 'jpod101':
|
||||||
{
|
{
|
||||||
const digest = await this._arrayBufferDigest(arrayBuffer);
|
const digest = await this._arrayBufferDigest(arrayBuffer);
|
||||||
@ -304,8 +303,6 @@ class AudioDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _getCustomAudioListSchema() {
|
async _getCustomAudioListSchema() {
|
||||||
let schema = this._customAudioListSchema;
|
|
||||||
if (schema === null) {
|
|
||||||
const url = chrome.runtime.getURL('/data/schemas/custom-audio-list-schema.json');
|
const url = chrome.runtime.getURL('/data/schemas/custom-audio-list-schema.json');
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -315,9 +312,6 @@ class AudioDownloader {
|
|||||||
redirect: 'follow',
|
redirect: 'follow',
|
||||||
referrerPolicy: 'no-referrer'
|
referrerPolicy: 'no-referrer'
|
||||||
});
|
});
|
||||||
schema = await response.json();
|
return await response.json();
|
||||||
this._customAudioListSchema = schema;
|
|
||||||
}
|
|
||||||
return schema;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,11 +42,11 @@ class AudioSystem extends EventDispatcher {
|
|||||||
return this._fallbackAudio;
|
return this._fallbackAudio;
|
||||||
}
|
}
|
||||||
|
|
||||||
createAudio(url, source) {
|
createAudio(url, sourceType) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const audio = new Audio(url);
|
const audio = new Audio(url);
|
||||||
audio.addEventListener('loadeddata', () => {
|
audio.addEventListener('loadeddata', () => {
|
||||||
if (!this._isAudioValid(audio, source)) {
|
if (!this._isAudioValid(audio, sourceType)) {
|
||||||
reject(new Error('Could not retrieve audio'));
|
reject(new Error('Could not retrieve audio'));
|
||||||
} else {
|
} else {
|
||||||
resolve(audio);
|
resolve(audio);
|
||||||
@ -70,8 +70,8 @@ class AudioSystem extends EventDispatcher {
|
|||||||
this.trigger('voiceschanged', e);
|
this.trigger('voiceschanged', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
_isAudioValid(audio, source) {
|
_isAudioValid(audio, sourceType) {
|
||||||
switch (source) {
|
switch (sourceType) {
|
||||||
case 'jpod101':
|
case 'jpod101':
|
||||||
{
|
{
|
||||||
const duration = audio.duration;
|
const duration = audio.duration;
|
||||||
|
Loading…
Reference in New Issue
Block a user