@internetarchive/bookreader 5.0.0-70-a3 → 5.0.0-71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BookReader/BookReader.js +1 -1
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/plugins/plugin.tts.js +1 -1
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/BookReader/plugins/plugin.url.js +1 -1
- package/BookReader/plugins/plugin.url.js.map +1 -1
- package/CHANGELOG.md +8 -0
- package/package.json +4 -4
- package/src/BookReader.js +4 -5
- package/src/plugins/tts/AbstractTTSEngine.js +22 -3
- package/src/plugins/url/UrlPlugin.js +5 -7
- package/tests/jest/plugins/tts/AbstractTTSEngine.test.js +20 -0
- package/tests/jest/plugins/url/UrlPlugin.test.js +8 -0
- /package/tests/jest/BookNavigator/{volumes/volumes-provider.test.js → viewable-files/viewable-files-provider.test.js} +0 -0
|
@@ -142,6 +142,10 @@ export default class AbstractTTSEngine {
|
|
|
142
142
|
// MS Edge fires voices changed randomly very often
|
|
143
143
|
this.events.off('voiceschanged', this.updateBestVoice);
|
|
144
144
|
this.voice = this.getVoices().find(voice => voice.voiceURI === voiceURI);
|
|
145
|
+
// if the current book has a language set, store the selected voice with the book language as a suffix
|
|
146
|
+
if (this.opts.bookLanguage) {
|
|
147
|
+
localStorage.setItem(`BRtts-voice-${this.opts.bookLanguage}`, this.voice.voiceURI);
|
|
148
|
+
}
|
|
145
149
|
if (this.activeSound) this.activeSound.setVoice(this.voice);
|
|
146
150
|
}
|
|
147
151
|
|
|
@@ -221,10 +225,12 @@ export default class AbstractTTSEngine {
|
|
|
221
225
|
// user languages that match the book language
|
|
222
226
|
const matchingUserLangs = userLanguages.filter(lang => lang.startsWith(bookLanguage));
|
|
223
227
|
|
|
224
|
-
//
|
|
225
|
-
return AbstractTTSEngine.
|
|
228
|
+
// First try to find the last chosen voice from localStorage for the current book language
|
|
229
|
+
return AbstractTTSEngine.getMatchingStoredVoice(bookLangVoices, bookLanguage)
|
|
230
|
+
// Try to find voices that intersect these two sets
|
|
231
|
+
|| AbstractTTSEngine.getMatchingVoice(matchingUserLangs, bookLangVoices)
|
|
226
232
|
// no user languages match the books; let's return the best voice for the book language
|
|
227
|
-
(bookLangVoices.find(v => v.default) || bookLangVoices[0])
|
|
233
|
+
|| (bookLangVoices.find(v => v.default) || bookLangVoices[0])
|
|
228
234
|
// No voices match the book language? let's find a voice in the user's language
|
|
229
235
|
// and ignore book lang
|
|
230
236
|
|| AbstractTTSEngine.getMatchingVoice(userLanguages, voices)
|
|
@@ -232,6 +238,19 @@ export default class AbstractTTSEngine {
|
|
|
232
238
|
|| (voices.find(v => v.default) || voices[0]);
|
|
233
239
|
}
|
|
234
240
|
|
|
241
|
+
/**
|
|
242
|
+
* @private
|
|
243
|
+
* Get the voice last selected by the user for the book language from localStorage.
|
|
244
|
+
* Returns undefined if no voice is stored or found.
|
|
245
|
+
* @param {SpeechSynthesisVoice[]} voices browser voices to choose from
|
|
246
|
+
* @param {ISO6391} bookLanguage book language to look for
|
|
247
|
+
* @return {SpeechSynthesisVoice | undefined}
|
|
248
|
+
*/
|
|
249
|
+
static getMatchingStoredVoice(voices, bookLanguage) {
|
|
250
|
+
const storedVoice = localStorage.getItem(`BRtts-voice-${bookLanguage}`);
|
|
251
|
+
return (storedVoice ? voices.find(v => v.voiceURI === storedVoice) : undefined);
|
|
252
|
+
}
|
|
253
|
+
|
|
235
254
|
/**
|
|
236
255
|
* @private
|
|
237
256
|
* Get the best voice that matches one of the BCP47 languages (order by preference)
|
|
@@ -45,7 +45,7 @@ export class UrlPlugin {
|
|
|
45
45
|
|
|
46
46
|
const strPathParams = this.urlSchema
|
|
47
47
|
.filter(s => s.position == 'path')
|
|
48
|
-
.map(schema => pathParams[schema.name] ? `${schema.name}/${pathParams[schema.name]}` : '')
|
|
48
|
+
.map(schema => pathParams[schema.name] ? `${schema.name}/${encodeURIComponent(pathParams[schema.name])}` : '')
|
|
49
49
|
.join('/');
|
|
50
50
|
|
|
51
51
|
// replace consecutive slashes with a single slash + remove trailing slashes
|
|
@@ -83,15 +83,13 @@ export class UrlPlugin {
|
|
|
83
83
|
const hasPropertyKey = doesKeyExists(urlStrSplitSlashObj, schema.name);
|
|
84
84
|
const hasDeprecatedKey = doesKeyExists(schema, 'deprecated_for') && hasPropertyKey;
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
// Not in the URL
|
|
87
|
+
if (!hasPropertyKey && !hasDeprecatedKey) {
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
91
|
+
const urlStateParam = hasDeprecatedKey ? schema.deprecated_for : schema.name;
|
|
92
|
+
urlState[urlStateParam] = decodeURIComponent(urlStrSplitSlashObj[schema.name]);
|
|
95
93
|
});
|
|
96
94
|
|
|
97
95
|
// Add searchParams to urlState
|
|
@@ -111,6 +111,26 @@ for (const dummyVoice of [dummyVoiceHyphens, dummyVoiceUnderscores]) {
|
|
|
111
111
|
|
|
112
112
|
expect(getBestBookVoice(voices, 'en', ['en-CA', 'en'])).toBe(voices[0]);
|
|
113
113
|
});
|
|
114
|
+
|
|
115
|
+
test('choose stored language from localStorage', () => {
|
|
116
|
+
const voices = [
|
|
117
|
+
dummyVoice({lang: "en-US", voiceURI: "English US", default: true}),
|
|
118
|
+
dummyVoice({lang: "en-GB", voiceURI: "English GB"}),
|
|
119
|
+
dummyVoice({lang: "en-CA", voiceURI: "English CA"}),
|
|
120
|
+
];
|
|
121
|
+
class DummyEngine extends AbstractTTSEngine {
|
|
122
|
+
getVoices() { return voices; }
|
|
123
|
+
}
|
|
124
|
+
const ttsEngine = new DummyEngine({...DUMMY_TTS_ENGINE_OPTS, bookLanguage: 'en'});
|
|
125
|
+
// simulates setting default voice on tts startup
|
|
126
|
+
ttsEngine.updateBestVoice();
|
|
127
|
+
// simulates user choosing a voice that matches the bookLanguage
|
|
128
|
+
// voice will be stored in localStorage
|
|
129
|
+
ttsEngine.setVoice(voices[2].voiceURI);
|
|
130
|
+
|
|
131
|
+
// expecting the voice to be selected by getMatchingStoredVoice and returned as best voice
|
|
132
|
+
expect(getBestBookVoice(voices, 'en', [])).toBe(voices[2]);
|
|
133
|
+
});
|
|
114
134
|
});
|
|
115
135
|
}
|
|
116
136
|
|
|
@@ -19,6 +19,10 @@ describe('UrlPlugin tests', () => {
|
|
|
19
19
|
expect(urlPlugin.urlStateToUrlString(urlStateWithQueries)).toBe(expectedUrlFromStateWithQueries);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
test('encodes page number', () => {
|
|
23
|
+
expect(urlPlugin.urlStateToUrlString({ page: '12/46' })).toBe(`page/12%2F46`);
|
|
24
|
+
});
|
|
25
|
+
|
|
22
26
|
test('urlStateToUrlString with unknown states in schema', () => {
|
|
23
27
|
const urlState = { page: 'n7', mode: '1up' };
|
|
24
28
|
const urlStateWithQueries = { page: 'n7', mode: '1up', q: 'hello', viewer: 'theater', sortBy: 'title_asc' };
|
|
@@ -47,6 +51,10 @@ describe('UrlPlugin tests', () => {
|
|
|
47
51
|
expect(urlPlugin.urlStringToUrlState(url1)).toEqual({page: 'n7', mode: '1up'});
|
|
48
52
|
});
|
|
49
53
|
|
|
54
|
+
test('decodes page number', () => {
|
|
55
|
+
expect(urlPlugin.urlStringToUrlState('/page/12%2F46')).toStrictEqual({ page: '12/46' });
|
|
56
|
+
});
|
|
57
|
+
|
|
50
58
|
test('urlStringToUrlState with deprecated_for', () => {
|
|
51
59
|
const url = '/page/n7/mode/2up/search/hello';
|
|
52
60
|
|