diff options
Diffstat (limited to 'ext/js')
| -rw-r--r-- | ext/js/background/backend.js | 14 | ||||
| -rw-r--r-- | ext/js/comm/api.js | 4 | ||||
| -rw-r--r-- | ext/js/display/display-audio.js | 443 | ||||
| -rw-r--r-- | ext/js/display/display.js | 26 | ||||
| -rw-r--r-- | ext/js/media/audio-downloader.js | 72 | ||||
| -rw-r--r-- | ext/js/media/audio-system.js | 8 | 
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; |