@internetarchive/bookreader 5.0.0-5 → 5.0.0-50-a1

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.
Files changed (260) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +77 -6
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +131 -339
  6. package/BookReader/BookReader.js +1 -1
  7. package/BookReader/BookReader.js.LICENSE.txt +24 -0
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1493 -0
  10. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +17 -0
  11. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  12. package/BookReader/icons/close-circle-dark.svg +1 -0
  13. package/BookReader/icons/magnify-minus.svg +1 -1
  14. package/BookReader/icons/magnify-plus.svg +1 -1
  15. package/BookReader/icons/pause.svg +1 -1
  16. package/BookReader/icons/playback-speed.svg +1 -1
  17. package/BookReader/icons/read-aloud.svg +1 -1
  18. package/BookReader/icons/voice.svg +1 -0
  19. package/BookReader/images/BRicons.svg +2 -2
  20. package/BookReader/images/books_graphic.svg +1 -1
  21. package/BookReader/images/icon_book.svg +1 -1
  22. package/BookReader/images/icon_gear.svg +1 -1
  23. package/BookReader/images/icon_info.svg +1 -1
  24. package/BookReader/images/icon_playback-rate.svg +1 -1
  25. package/BookReader/images/icon_search_button.svg +1 -1
  26. package/BookReader/images/icon_share.svg +1 -1
  27. package/BookReader/images/icon_speaker.svg +1 -1
  28. package/BookReader/images/icon_speaker_open.svg +1 -1
  29. package/BookReader/images/marker_chap-off.svg +1 -1
  30. package/BookReader/images/marker_chap-on.svg +1 -1
  31. package/BookReader/images/marker_srch-on.svg +1 -1
  32. package/BookReader/jquery-3.js +2 -0
  33. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  34. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  35. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  36. package/BookReader/plugins/plugin.autoplay.js +1 -1
  37. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  38. package/BookReader/plugins/plugin.chapters.js +1 -1
  39. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  40. package/BookReader/plugins/plugin.iframe.js +1 -1
  41. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  42. package/BookReader/plugins/plugin.mobile_nav.js +1 -1
  43. package/BookReader/plugins/plugin.mobile_nav.js.map +1 -1
  44. package/BookReader/plugins/plugin.resume.js +1 -1
  45. package/BookReader/plugins/plugin.resume.js.map +1 -1
  46. package/BookReader/plugins/plugin.search.js +1 -1
  47. package/BookReader/plugins/plugin.search.js.map +1 -1
  48. package/BookReader/plugins/plugin.text_selection.js +1 -1
  49. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  50. package/BookReader/plugins/plugin.tts.js +1 -1
  51. package/BookReader/plugins/plugin.tts.js.map +1 -1
  52. package/BookReader/plugins/plugin.url.js +1 -1
  53. package/BookReader/plugins/plugin.url.js.map +1 -1
  54. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  55. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  56. package/BookReader/webcomponents-bundle.js +3 -0
  57. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  58. package/BookReader/webcomponents-bundle.js.map +1 -0
  59. package/BookReaderDemo/BookReaderDemo.css +14 -1
  60. package/BookReaderDemo/IADemoBr.js +148 -0
  61. package/BookReaderDemo/demo-advanced.html +2 -2
  62. package/BookReaderDemo/demo-autoplay.html +2 -1
  63. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  64. package/BookReaderDemo/demo-fullscreen-mobile.html +2 -1
  65. package/BookReaderDemo/demo-fullscreen.html +2 -1
  66. package/BookReaderDemo/demo-iiif.html +2 -1
  67. package/BookReaderDemo/demo-internetarchive.html +84 -17
  68. package/BookReaderDemo/demo-multiple.html +2 -1
  69. package/BookReaderDemo/demo-preview-pages.html +2 -1
  70. package/BookReaderDemo/demo-simple.html +2 -1
  71. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -1
  72. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  73. package/BookReaderDemo/immersion-1up.html +2 -1
  74. package/BookReaderDemo/immersion-mode.html +2 -1
  75. package/BookReaderDemo/toggle_controls.html +2 -1
  76. package/BookReaderDemo/view_mode.html +2 -1
  77. package/BookReaderDemo/viewmode-cycle.html +2 -3
  78. package/CHANGELOG.md +202 -0
  79. package/README.md +14 -1
  80. package/babel.config.js +18 -0
  81. package/codecov.yml +6 -0
  82. package/index.html +3 -0
  83. package/jsconfig.json +19 -0
  84. package/package.json +66 -56
  85. package/renovate.json +52 -0
  86. package/scripts/preversion.js +4 -1
  87. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  88. package/src/BookNavigator/assets/button-base.js +9 -2
  89. package/src/BookNavigator/assets/ia-logo.js +17 -0
  90. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  91. package/src/BookNavigator/assets/icon_close.js +1 -1
  92. package/src/BookNavigator/assets/icon_sort_asc.js +5 -0
  93. package/src/BookNavigator/assets/icon_sort_desc.js +5 -0
  94. package/src/BookNavigator/assets/icon_sort_neutral.js +5 -0
  95. package/src/BookNavigator/assets/icon_volumes.js +11 -0
  96. package/src/BookNavigator/book-navigator.js +583 -0
  97. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  98. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  99. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  100. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  101. package/src/BookNavigator/bookmarks/bookmarks-provider.js +21 -8
  102. package/src/BookNavigator/bookmarks/ia-bookmarks.js +102 -66
  103. package/src/BookNavigator/delete-modal-actions.js +1 -1
  104. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  105. package/src/BookNavigator/downloads/downloads.js +41 -25
  106. package/src/BookNavigator/search/a-search-result.js +18 -13
  107. package/src/BookNavigator/search/search-provider.js +80 -28
  108. package/src/BookNavigator/search/search-results.js +10 -18
  109. package/src/BookNavigator/sharing.js +27 -0
  110. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
  111. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  112. package/src/BookNavigator/volumes/volumes-provider.js +114 -0
  113. package/src/BookNavigator/volumes/volumes.js +188 -0
  114. package/src/BookReader/BookModel.js +0 -29
  115. package/src/BookReader/DebugConsole.js +3 -3
  116. package/src/BookReader/DragScrollable.js +233 -0
  117. package/src/BookReader/Mode1Up.js +51 -351
  118. package/src/BookReader/Mode1UpLit.js +441 -0
  119. package/src/BookReader/Mode2Up.js +120 -105
  120. package/src/BookReader/ModeSmoothZoom.js +179 -0
  121. package/src/BookReader/ModeThumb.js +17 -11
  122. package/src/BookReader/Navbar/Navbar.js +10 -36
  123. package/src/BookReader/PageContainer.js +69 -6
  124. package/src/BookReader/ReduceSet.js +1 -1
  125. package/src/BookReader/Toolbar/Toolbar.js +10 -37
  126. package/src/BookReader/options.js +10 -0
  127. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  128. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  129. package/src/BookReader/utils.js +92 -13
  130. package/src/BookReader.js +431 -620
  131. package/src/assets/icons/close-circle-dark.svg +1 -0
  132. package/src/assets/icons/magnify-minus.svg +3 -7
  133. package/src/assets/icons/magnify-plus.svg +3 -7
  134. package/src/assets/icons/voice.svg +1 -0
  135. package/src/css/BookReader.scss +0 -12
  136. package/src/css/_BRComponent.scss +1 -1
  137. package/src/css/_BRmain.scss +19 -24
  138. package/src/css/_BRnav.scss +4 -26
  139. package/src/css/_BRpages.scss +35 -0
  140. package/src/css/_BRsearch.scss +25 -216
  141. package/src/css/_TextSelection.scss +14 -17
  142. package/src/css/_colorbox.scss +2 -2
  143. package/src/css/_controls.scss +17 -5
  144. package/src/css/_icons.scss +6 -0
  145. package/src/ia-bookreader/ia-bookreader.js +224 -0
  146. package/src/plugins/plugin.autoplay.js +4 -4
  147. package/src/plugins/plugin.chapters.js +28 -35
  148. package/src/plugins/plugin.mobile_nav.js +11 -10
  149. package/src/plugins/plugin.resume.js +3 -3
  150. package/src/plugins/plugin.text_selection.js +26 -39
  151. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  152. package/src/plugins/search/plugin.search.js +174 -116
  153. package/src/plugins/search/view.js +63 -179
  154. package/src/plugins/tts/AbstractTTSEngine.js +46 -37
  155. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  156. package/src/plugins/tts/PageChunk.js +15 -21
  157. package/src/plugins/tts/PageChunkIterator.js +8 -12
  158. package/src/plugins/tts/WebTTSEngine.js +66 -69
  159. package/src/plugins/tts/plugin.tts.js +92 -109
  160. package/src/plugins/tts/utils.js +0 -9
  161. package/src/plugins/url/UrlPlugin.js +184 -0
  162. package/src/plugins/{plugin.url.js → url/plugin.url.js} +28 -6
  163. package/src/util/manifestGenerator.js +0 -0
  164. package/tests/e2e/README.md +37 -0
  165. package/tests/e2e/autoplay.test.js +2 -2
  166. package/tests/e2e/base.test.js +7 -7
  167. package/tests/e2e/helpers/base.js +9 -3
  168. package/tests/e2e/helpers/debug.js +1 -1
  169. package/tests/e2e/helpers/desktopSearch.js +14 -13
  170. package/tests/e2e/helpers/mobileSearch.js +3 -3
  171. package/tests/e2e/helpers/params.js +17 -0
  172. package/tests/e2e/models/Navigation.js +13 -4
  173. package/tests/e2e/rightToLeft.test.js +4 -5
  174. package/tests/e2e/viewmode.test.js +38 -33
  175. package/tests/jest/BookNavigator/book-navigator.test.js +634 -0
  176. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  177. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  178. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  179. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  180. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  181. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  182. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  183. package/tests/{karma/BookNavigator → jest/BookNavigator/search}/search-results.test.js +102 -58
  184. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  185. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  186. package/tests/jest/BookNavigator/volumes/volumes-provider.test.js +184 -0
  187. package/tests/jest/BookNavigator/volumes/volumes.test.js +97 -0
  188. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +34 -14
  189. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +176 -0
  190. package/tests/{BookReader → jest/BookReader}/DebugConsole.test.js +1 -1
  191. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  192. package/tests/jest/BookReader/Mode1UpLit.test.js +92 -0
  193. package/tests/{BookReader → jest/BookReader}/Mode2Up.test.js +36 -15
  194. package/tests/jest/BookReader/ModeSmoothZoom.test.js +149 -0
  195. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  196. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +7 -7
  197. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  198. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  199. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  200. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  201. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  202. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  203. package/tests/jest/BookReader/utils.test.js +186 -0
  204. package/tests/jest/BookReader.keyboard.test.js +190 -0
  205. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  206. package/tests/{BookReader.test.js → jest/BookReader.test.js} +18 -37
  207. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  208. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  209. package/tests/{plugins → jest/plugins}/plugin.chapters.test.js +10 -11
  210. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  211. package/tests/{plugins → jest/plugins}/plugin.mobile_nav.test.js +5 -5
  212. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  213. package/tests/{plugins → jest/plugins}/plugin.text_selection.test.js +39 -47
  214. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  215. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +63 -47
  216. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +35 -6
  217. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +9 -9
  218. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  219. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  220. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  221. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +1 -1
  222. package/tests/{plugins → jest/plugins}/tts/utils.test.js +3 -28
  223. package/tests/jest/plugins/url/UrlPlugin.test.js +190 -0
  224. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +33 -14
  225. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  226. package/tests/{util → jest/util}/docCookies.test.js +1 -1
  227. package/tests/{util → jest/util}/strings.test.js +1 -1
  228. package/tests/{utils.js → jest/utils.js} +38 -0
  229. package/webpack.config.js +11 -5
  230. package/.babelrc +0 -12
  231. package/.dependabot/config.yml +0 -6
  232. package/.testcaferc.json +0 -5
  233. package/BookReader/bookreader-component-bundle.js +0 -1450
  234. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  235. package/BookReader/bookreader-component-bundle.js.map +0 -1
  236. package/BookReader/jquery-1.10.1.js +0 -2
  237. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  238. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  239. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  240. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  241. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  242. package/karma.conf.js +0 -23
  243. package/src/BookNavigator/BookModel.js +0 -14
  244. package/src/BookNavigator/BookNavigator.js +0 -438
  245. package/src/BookNavigator/assets/book-loader.js +0 -27
  246. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  247. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  248. package/src/ItemNavigator/ItemNavigator.js +0 -372
  249. package/src/ItemNavigator/providers/sharing.js +0 -29
  250. package/src/Layers/sharing/sharing-provider.js +0 -22
  251. package/src/dragscrollable-br.js +0 -261
  252. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  253. package/src/plugins/plugin.bookmarks.js +0 -50
  254. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  255. package/tests/BookReader/Mode1Up.test.js +0 -164
  256. package/tests/BookReader/utils.test.js +0 -109
  257. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  258. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  259. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  260. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
@@ -1,6 +1,7 @@
1
1
  /* global br */
2
2
  import { isChrome, isFirefox } from '../../util/browserSniffing.js';
3
- import { sleep, promisifyEvent, isAndroid } from './utils.js';
3
+ import { promisifyEvent, isAndroid } from './utils.js';
4
+ import { sleep } from '../../BookReader/utils.js';
4
5
  import AbstractTTSEngine from './AbstractTTSEngine.js';
5
6
  /** @typedef {import("./AbstractTTSEngine.js").PageChunk} PageChunk */
6
7
  /** @typedef {import("./AbstractTTSEngine.js").AbstractTTSSound} AbstractTTSSound */
@@ -167,7 +168,7 @@ export class WebTTSSound {
167
168
  * left off.
168
169
  * @return {Promise<void>}
169
170
  */
170
- reload() {
171
+ async reload() {
171
172
  // We'll restore the pause state, so copy it here
172
173
  const wasPaused = this.paused;
173
174
  // Use recent event to determine where we'll restart from
@@ -179,14 +180,12 @@ export class WebTTSSound {
179
180
  }
180
181
 
181
182
  // We can't modify the utterance object, so we have to make a new one
182
- return this.stop()
183
- .then(() => {
184
- this.load();
185
- // Instead of playing and immediately pausing, we don't start playing. Note
186
- // this is a requirement because pause doesn't work consistently across
187
- // browsers.
188
- if (!wasPaused) this.play();
189
- });
183
+ await this.stop();
184
+ this.load();
185
+ // Instead of playing and immediately pausing, we don't start playing. Note
186
+ // this is a requirement because pause doesn't work consistently across
187
+ // browsers.
188
+ if (!wasPaused) this.play();
190
189
  }
191
190
 
192
191
  play() {
@@ -222,15 +221,16 @@ export class WebTTSSound {
222
221
  return endPromise;
223
222
  }
224
223
 
225
- finish() {
226
- this.stop().then(() => this.utterance.dispatchEvent(new Event('finish')));
224
+ async finish() {
225
+ await this.stop();
226
+ this.utterance.dispatchEvent(new Event('finish'));
227
227
  }
228
228
 
229
229
  /**
230
230
  * @override
231
231
  * Will fire a pause event unless already paused
232
232
  **/
233
- pause() {
233
+ async pause() {
234
234
  if (this.paused) return;
235
235
 
236
236
  const pausePromise = promisifyEvent(this.utterance, 'pause');
@@ -246,19 +246,18 @@ export class WebTTSSound {
246
246
  if (pauseMightNotFire) {
247
247
  // wait for it just in case
248
248
  const timeoutPromise = sleep(100).then(() => 'timeout');
249
- Promise.race([pausePromise, timeoutPromise])
250
- .then(result => {
251
- // We got our pause event; nothing to do!
252
- if (result != 'timeout') return;
253
-
254
- this.utterance.dispatchEvent(new CustomEvent('pause', this._lastEvents.start));
255
- // if pause might not work, then we'll stop entirely and restart later
256
- if (pauseMightNotWork) this.stop();
257
- });
249
+ const result = await Promise.race([pausePromise, timeoutPromise]);
250
+ // We got our pause event; nothing to do!
251
+ if (result != 'timeout') return;
252
+
253
+ this.utterance.dispatchEvent(new CustomEvent('pause', this._lastEvents.start));
254
+
255
+ // if pause might not work, then we'll stop entirely and restart later
256
+ if (pauseMightNotWork) this.stop();
258
257
  }
259
258
  }
260
259
 
261
- resume() {
260
+ async resume() {
262
261
  if (!this.started) {
263
262
  this.play();
264
263
  return;
@@ -278,16 +277,15 @@ export class WebTTSSound {
278
277
  speechSynthesis.resume();
279
278
 
280
279
  if (resumeMightNotFire) {
281
- Promise.race([resumePromise, sleep(100).then(() => 'timeout')])
282
- .then(result => {
283
- if (result != 'timeout') return;
284
-
285
- this.utterance.dispatchEvent(new CustomEvent('resume', {}));
286
- if (resumeMightNotWork) {
287
- const reloadPromise = this.reload();
288
- reloadPromise.then(() => this.play());
289
- }
290
- });
280
+ const result = await Promise.race([resumePromise, sleep(100).then(() => 'timeout')]);
281
+
282
+ if (result != 'timeout') return;
283
+
284
+ this.utterance.dispatchEvent(new CustomEvent('resume', {}));
285
+ if (resumeMightNotWork) {
286
+ await this.reload();
287
+ this.play();
288
+ }
291
289
  }
292
290
  }
293
291
 
@@ -296,6 +294,11 @@ export class WebTTSSound {
296
294
  this.reload();
297
295
  }
298
296
 
297
+ /** @param {SpeechSynthesisVoice} voice */
298
+ setVoice(voice) {
299
+ this.voice = voice;
300
+ this.reload();
301
+ }
299
302
  /**
300
303
  * @private
301
304
  * Chrome has a bug where it only plays 15 seconds of TTS and then
@@ -303,45 +306,39 @@ export class WebTTSSound {
303
306
  * We avoid this (as described here: https://bugs.chromium.org/p/chromium/issues/detail?id=679437#c15 )
304
307
  * by pausing after 14 seconds and ~instantly resuming.
305
308
  */
306
- _chromePausingBugFix() {
309
+ async _chromePausingBugFix() {
307
310
  const timeoutPromise = sleep(14000).then(() => 'timeout');
308
311
  const pausePromise = promisifyEvent(this.utterance, 'pause').then(() => 'paused');
309
312
  const endPromise = promisifyEvent(this.utterance, 'end').then(() => 'ended');
310
- return Promise.race([timeoutPromise, pausePromise, endPromise])
311
- .then(result => {
312
- if (location.toString().indexOf('_debugReadAloud=true') != -1) {
313
- console.log(`CHROME-PAUSE-HACK: ${result}`);
314
- }
315
- switch (result) {
316
- case 'ended':
317
- // audio was stopped/finished; nothing to do
318
- break;
319
- case 'paused':
320
- // audio was paused; wait for resume
321
- // Chrome won't let you resume the audio if 14s have passed 🤷‍
322
- // We could do the same as before (but resume+pause instead of pause+resume),
323
- // but that means we'd _constantly_ be running in the background. So in that
324
- // case, let's just restart the chunk
325
- Promise.race([
326
- promisifyEvent(this.utterance, 'resume'),
327
- sleep(14000).then(() => 'timeout'),
328
- ])
329
- .then(result => {
330
- result == 'timeout' ? this.reload() : this._chromePausingBugFix();
331
- });
332
- break;
333
- case 'timeout':
334
- // We hit Chrome's secret cut off time. Pause/resume
335
- // to be able to keep TTS-ing
336
- speechSynthesis.pause();
337
- sleep(25)
338
- .then(() => {
339
- speechSynthesis.resume();
340
- this._chromePausingBugFix();
341
- });
342
- break;
343
- }
344
- });
313
+ const result = await Promise.race([timeoutPromise, pausePromise, endPromise]);
314
+ if (location.toString().indexOf('_debugReadAloud=true') != -1) {
315
+ console.log(`CHROME-PAUSE-HACK: ${result}`);
316
+ }
317
+ switch (result) {
318
+ case 'ended':
319
+ // audio was stopped/finished; nothing to do
320
+ break;
321
+ case 'paused':
322
+ // audio was paused; wait for resume
323
+ // Chrome won't let you resume the audio if 14s have passed 🤷‍
324
+ // We could do the same as before (but resume+pause instead of pause+resume),
325
+ // but that means we'd _constantly_ be running in the background. So in that
326
+ // case, let's just restart the chunk
327
+ await Promise.race([
328
+ promisifyEvent(this.utterance, 'resume'),
329
+ sleep(14000).then(() => 'timeout'),
330
+ ]);
331
+ result == 'timeout' ? this.reload() : this._chromePausingBugFix();
332
+ break;
333
+ case 'timeout':
334
+ // We hit Chrome's secret cut off time. Pause/resume
335
+ // to be able to keep TTS-ing
336
+ speechSynthesis.pause();
337
+ await sleep(25);
338
+ speechSynthesis.resume();
339
+ this._chromePausingBugFix();
340
+ break;
341
+ }
345
342
  }
346
343
  }
347
344
 
@@ -6,8 +6,9 @@ import FestivalTTSEngine from './FestivalTTSEngine.js';
6
6
  import WebTTSEngine from './WebTTSEngine.js';
7
7
  import { toISO6391, approximateWordCount } from './utils.js';
8
8
  import { en as tooltips } from './tooltip_dict.js';
9
- /** @typedef {import('./PageChunk.js')} PageChunk */
10
- /** @typedef {import("./AbstractTTSEngine.js")} AbstractTTSEngine */
9
+ import { renderBoxesInPageContainerLayer } from '../../BookReader/PageContainer.js';
10
+ /** @typedef {import('./PageChunk.js').default} PageChunk */
11
+ /** @typedef {import("./AbstractTTSEngine.js").default} AbstractTTSEngine */
11
12
 
12
13
  // Default options for TTS
13
14
  jQuery.extend(BookReader.defaultOptions, {
@@ -22,7 +23,9 @@ BookReader.prototype.setup = (function (super_) {
22
23
  super_.call(this, options);
23
24
 
24
25
  if (this.options.enableTtsPlugin) {
25
- this.ttsHilites = [];
26
+ /** @type { {[pageIndex: number]: Array<{ l: number, r: number, t: number, b: number }>} } */
27
+ this._ttsBoxesByIndex = {};
28
+
26
29
  let TTSEngine = WebTTSEngine.isSupported() ? WebTTSEngine :
27
30
  FestivalTTSEngine.isSupported() ? FestivalTTSEngine :
28
31
  null;
@@ -54,17 +57,6 @@ BookReader.prototype.init = (function(super_) {
54
57
  if (this.options.enableTtsPlugin) {
55
58
  // Bind to events
56
59
 
57
- // TODO move this to BookReader.js or something
58
- this.bind(BookReader.eventNames.fragmentChange, () => {
59
- if (this.mode == this.constMode2up) {
60
- // clear highlights if they're no longer valid for this page
61
- const visibleIndices = [this.twoPage.currentIndexL, this.twoPage.currentIndexR];
62
- const visibleSelector = visibleIndices.map(i => `.BRReadAloudHilite.Leaf-${i}`).join(', ');
63
- $(this.ttsHilites).filter(visibleSelector).show();
64
- $(this.ttsHilites).not(visibleSelector).hide();
65
- }
66
- });
67
-
68
60
  this.bind(BookReader.eventNames.PostInit, () => {
69
61
  this.$('.BRicon.read').click(() => {
70
62
  this.ttsToggle();
@@ -88,6 +80,17 @@ BookReader.prototype.init = (function(super_) {
88
80
  };
89
81
  })(BookReader.prototype.init);
90
82
 
83
+ /** @override */
84
+ BookReader.prototype._createPageContainer = (function (super_) {
85
+ return function (index) {
86
+ const pageContainer = super_.call(this, index);
87
+ if (this.options.enableTtsPlugin && pageContainer.page && index in this._ttsBoxesByIndex) {
88
+ const pageIndex = pageContainer.page.index;
89
+ renderBoxesInPageContainerLayer('ttsHiliteLayer', this._ttsBoxesByIndex[pageIndex], pageContainer.page, pageContainer.$container[0]);
90
+ }
91
+ return pageContainer;
92
+ };
93
+ })(BookReader.prototype._createPageContainer);
91
94
 
92
95
  // Extend buildMobileDrawerElement
93
96
  BookReader.prototype.buildMobileDrawerElement = (function (super_) {
@@ -146,15 +149,49 @@ BookReader.prototype.initNavbar = (function (super_) {
146
149
  <div class="icon icon-advance"></div>
147
150
  </button>
148
151
  </li>
152
+ <li>
153
+ <select class="playback-voices" name="playback-voice" style="display: none" title="Change read aloud voices">
154
+ </select>
155
+ </li>
149
156
  </ul>
150
157
  `);
158
+
151
159
  $el.find('.BRcontrols').prepend(this.refs.$BRReadAloudToolbar);
160
+
161
+ const renderVoiceOption = (voices) => {
162
+ return voices.map(voice =>
163
+ `<option value="${voice.voiceURI}">${voice.lang} - ${voice.name}</option>`).join('');
164
+ };
165
+
166
+ const voiceSortOrder = (a,b) => `${a.lang} - ${a.name}`.localeCompare(`${b.lang} - ${b.name}`);
167
+
168
+ const renderVoicesMenu = (voicesMenu) => {
169
+ voicesMenu.empty();
170
+ const bookLanguage = this.ttsEngine.opts.bookLanguage;
171
+ const bookLanguages = this.ttsEngine.getVoices().filter(v => v.lang.startsWith(bookLanguage)).sort(voiceSortOrder);
172
+ const otherLanguages = this.ttsEngine.getVoices().filter(v => !v.lang.startsWith(bookLanguage)).sort(voiceSortOrder);
173
+
174
+ if (this.ttsEngine.getVoices().length > 1) {
175
+ voicesMenu.append($(`<optgroup label="Book Language (${bookLanguage})"> ${renderVoiceOption(bookLanguages)} </optgroup>`));
176
+ voicesMenu.append($(`<optgroup label="Other Languages"> ${renderVoiceOption(otherLanguages)} </optgroup>`));
177
+
178
+ voicesMenu.val(this.ttsEngine.voice.voiceURI);
179
+ voicesMenu.show();
180
+ } else {
181
+ voicesMenu.hide();
182
+ }
183
+ };
184
+
185
+ const voicesMenu = this.refs.$BRReadAloudToolbar.find('[name=playback-voice]');
186
+ renderVoicesMenu(voicesMenu);
187
+ voicesMenu.on("change", ev => this.ttsEngine.setVoice(voicesMenu.val()));
152
188
  this.ttsEngine.events.on('pause resume start', () => this.ttsUpdateState());
153
- this.refs.$BRReadAloudToolbar.find('[name=play]').click(this.ttsPlayPause.bind(this));
154
- this.refs.$BRReadAloudToolbar.find('[name=advance]').click(this.ttsJumpForward.bind(this));
155
- this.refs.$BRReadAloudToolbar.find('[name=review]').click(this.ttsJumpBackward.bind(this));
189
+ this.ttsEngine.events.on('voiceschanged', () => renderVoicesMenu(voicesMenu));
190
+ this.refs.$BRReadAloudToolbar.find('[name=play]').on("click", this.ttsPlayPause.bind(this));
191
+ this.refs.$BRReadAloudToolbar.find('[name=advance]').on("click", this.ttsJumpForward.bind(this));
192
+ this.refs.$BRReadAloudToolbar.find('[name=review]').on("click", this.ttsJumpBackward.bind(this));
156
193
  const $rateSelector = this.refs.$BRReadAloudToolbar.find('select[name="playback-speed"]');
157
- $rateSelector.change(ev => this.ttsEngine.setPlaybackRate(parseFloat($rateSelector.val())));
194
+ $rateSelector.on("change", ev => this.ttsEngine.setPlaybackRate(parseFloat($rateSelector.val())));
158
195
  $(`<li>
159
196
  <button class="BRicon read js-tooltip" title="${tooltips.readAloud}">
160
197
  <div class="icon icon-read-aloud"></div>
@@ -187,7 +224,7 @@ BookReader.prototype.ttsStart = function (startTTSEngine = true) {
187
224
  this.$('.BRicon.read').addClass('unread active');
188
225
  this.ttsSendAnalyticsEvent('Start');
189
226
  if (startTTSEngine)
190
- this.ttsEngine.start(this.currentIndex(), this.getNumLeafs());
227
+ this.ttsEngine.start(this.currentIndex(), this.book.getNumLeafs());
191
228
  };
192
229
 
193
230
  BookReader.prototype.ttsJumpForward = function () {
@@ -233,12 +270,10 @@ BookReader.prototype.ttsStop = function () {
233
270
  * @param {PageChunk} chunk
234
271
  * @return {PromiseLike<void>} returns once the flip is done
235
272
  */
236
- BookReader.prototype.ttsBeforeChunkPlay = function(chunk) {
237
- return this.ttsMaybeFlipToIndex(chunk.leafIndex)
238
- .then(() => {
239
- this.ttsHighlightChunk(chunk);
240
- this.ttsScrollToChunk(chunk);
241
- });
273
+ BookReader.prototype.ttsBeforeChunkPlay = async function(chunk) {
274
+ await this.ttsMaybeFlipToIndex(chunk.leafIndex);
275
+ this.ttsHighlightChunk(chunk);
276
+ this.ttsScrollToChunk(chunk);
242
277
  };
243
278
 
244
279
  /**
@@ -267,113 +302,61 @@ BookReader.prototype.ttsMaybeFlipToIndex = function (leafIndex) {
267
302
  resolve();
268
303
  } else {
269
304
  this.animationFinishedCallback = resolve;
270
- const mustGoNext = leafIndex > Math.max(this.twoPage.currentIndexR, this.twoPage.currentIndexL);
271
- if (mustGoNext) this.next();
272
- else this.prev();
273
- promise.then(this.ttsMaybeFlipToIndex.bind(this, leafIndex));
305
+ this.jumpToIndex(leafIndex);
274
306
  }
275
307
  }
276
308
 
277
309
  return promise;
278
- }
279
-
280
- /**
281
- * @param {PageChunk} chunk
282
- */
283
- BookReader.prototype.ttsHighlightChunk = function(chunk) {
284
- this.ttsRemoveHilites();
285
-
286
- if (this.constMode2up == this.mode) {
287
- this.ttsHilite2UP(chunk);
288
- } else {
289
- this.ttsHilite1UP(chunk);
290
- }
291
310
  };
292
311
 
293
312
  /**
294
313
  * @param {PageChunk} chunk
295
314
  */
296
- BookReader.prototype.ttsScrollToChunk = function(chunk) {
297
- if (this.constMode1up != this.mode) return;
298
-
299
- let leafTop = 0;
300
- let h;
301
- let i;
302
- for (i = 0; i < chunk.leafIndex; i++) {
303
- h = parseInt(this._getPageHeight(i) / this.reduce);
304
- leafTop += h + this.padding;
305
- }
306
-
307
- const chunkTop = chunk.lineRects[0][3]; //coords are in l,b,r,t order
308
- const chunkBot = chunk.lineRects[chunk.lineRects.length - 1][1];
309
-
310
- const topOfFirstChunk = leafTop + chunkTop / this.reduce;
311
- const botOfLastChunk = leafTop + chunkBot / this.reduce;
312
-
313
- if (window?.soundManager?.debugMode) console.log('leafTop = ' + leafTop + ' topOfFirstChunk = ' + topOfFirstChunk + ' botOfLastChunk = ' + botOfLastChunk);
315
+ BookReader.prototype.ttsHighlightChunk = function(chunk) {
316
+ // The poorly-named variable leafIndex
317
+ const pageIndex = chunk.leafIndex;
314
318
 
315
- const containerTop = this.refs.$brContainer.prop('scrollTop');
316
- const containerBot = containerTop + this.refs.$brContainer.height();
317
- if (window?.soundManager?.debugMode) console.log('containerTop = ' + containerTop + ' containerBot = ' + containerBot);
319
+ this.ttsRemoveHilites();
318
320
 
319
- if ((topOfFirstChunk < containerTop) || (botOfLastChunk > containerBot)) {
320
- this.refs.$brContainer.stop(true).animate({scrollTop: topOfFirstChunk},'fast');
321
- }
322
- };
321
+ // group by index; currently only possible to have chunks on one page :/
322
+ this._ttsBoxesByIndex = {
323
+ [pageIndex]: chunk.lineRects.map(([l, b, r, t]) => ({l, r, b, t}))
324
+ };
323
325
 
324
- /**
325
- * @param {PageChunk} chunk
326
- */
327
- BookReader.prototype.ttsHilite1UP = function(chunk) {
328
- for (let i = 0; i < chunk.lineRects.length; i++) {
329
- //each rect is an array of l,b,r,t coords (djvu.xml ordering...)
330
- const l = chunk.lineRects[i][0];
331
- const b = chunk.lineRects[i][1];
332
- const r = chunk.lineRects[i][2];
333
- const t = chunk.lineRects[i][3];
334
-
335
- const div = document.createElement('div');
336
- this.ttsHilites.push(div);
337
- $(div).prop('className', 'BookReaderSearchHilite').appendTo(
338
- this.$('.pagediv' + chunk.leafIndex)
339
- );
340
-
341
- $(div).css({
342
- width: (r - l) / this.reduce + 'px',
343
- height: (b - t) / this.reduce + 'px',
344
- left: l / this.reduce + 'px',
345
- top: t / this.reduce + 'px'
346
- });
326
+ // update any already created pages
327
+ for (const [pageIndexString, boxes] of Object.entries(this._ttsBoxesByIndex)) {
328
+ const pageIndex = parseFloat(pageIndexString);
329
+ const page = this.book.getPage(pageIndex);
330
+ const pageContainers = this.getActivePageContainerElementsForIndex(pageIndex);
331
+ pageContainers.forEach(container => renderBoxesInPageContainerLayer('ttsHiliteLayer', boxes, page, container));
347
332
  }
348
-
349
333
  };
350
334
 
351
335
  /**
352
336
  * @param {PageChunk} chunk
353
337
  */
354
- BookReader.prototype.ttsHilite2UP = function (chunk) {
355
- for (let i = 0; i < chunk.lineRects.length; i++) {
356
- //each rect is an array of l,b,r,t coords (djvu.xml ordering...)
357
- const l = chunk.lineRects[i][0];
358
- const b = chunk.lineRects[i][1];
359
- const r = chunk.lineRects[i][2];
360
- const t = chunk.lineRects[i][3];
361
-
362
- const div = document.createElement('div');
363
- this.ttsHilites.push(div);
364
- $(div)
365
- .prop('className', 'BookReaderSearchHilite BRReadAloudHilite Leaf-' + chunk.leafIndex)
366
- .css('zIndex', 3)
367
- .appendTo(this.refs.$brTwoPageView);
368
- this.setHilightCss2UP(div, chunk.leafIndex, l, r, t, b);
369
- }
338
+ BookReader.prototype.ttsScrollToChunk = function(chunk) {
339
+ // It behaves weird if used in thumb mode
340
+ if (this.constModeThumb == this.mode) return;
341
+
342
+ $(`.pagediv${chunk.leafIndex} .ttsHiliteLayer rect`).last()?.[0]?.scrollIntoView({
343
+ // Only vertically center the highlight if we're in 1up or in full screen. In
344
+ // 2up, if we're not fullscreen, the whole body gets scrolled around to try to
345
+ // center the highlight 🙄 See:
346
+ // https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move/11041376
347
+ // Note: nearest doesn't quite work great, because the ReadAloud toolbar is now
348
+ // full-width, and covers up the last line of the highlight.
349
+ block: this.constMode1up == this.mode || this.isFullscreenActive ? 'center' : 'nearest',
350
+ inline: 'center',
351
+ behavior: 'smooth',
352
+ });
370
353
  };
371
354
 
372
355
  // ttsRemoveHilites()
373
356
  //______________________________________________________________________________
374
357
  BookReader.prototype.ttsRemoveHilites = function () {
375
- $(this.ttsHilites).remove();
376
- this.ttsHilites = [];
358
+ $(this.getActivePageContainerElements()).find('.ttsHiliteLayer').remove();
359
+ this._ttsBoxesByIndex = {};
377
360
  };
378
361
 
379
362
  /**
@@ -26,15 +26,6 @@ export function approximateWordCount(text) {
26
26
  return m ? m.length : 0;
27
27
  }
28
28
 
29
- /**
30
- * Waits the provided number of ms and then resolves with a promise
31
- * @param {number} ms
32
- * @return {Promise}
33
- */
34
- export function sleep(ms) {
35
- return new Promise(res => setTimeout(res, ms));
36
- }
37
-
38
29
  /**
39
30
  * Checks whether the current browser is on android
40
31
  * @param {string} [userAgent]