@internetarchive/bookreader 5.0.0-9 → 5.0.0-91

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 (325) hide show
  1. package/.eslintrc.js +21 -19
  2. package/.github/workflows/node.js.yml +76 -11
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +404 -1125
  6. package/BookReader/BookReader.js +1 -1
  7. package/BookReader/BookReader.js.LICENSE.txt +20 -20
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1782 -0
  10. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +7 -0
  11. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  12. package/BookReader/icons/1up.svg +1 -1
  13. package/BookReader/icons/2up.svg +1 -1
  14. package/BookReader/icons/advance.svg +1 -1
  15. package/BookReader/icons/chevron-right.svg +1 -1
  16. package/BookReader/icons/close-circle-dark.svg +1 -1
  17. package/BookReader/icons/close-circle.svg +1 -1
  18. package/BookReader/icons/fullscreen.svg +1 -1
  19. package/BookReader/icons/fullscreen_exit.svg +1 -1
  20. package/BookReader/icons/hamburger.svg +1 -1
  21. package/BookReader/icons/left-arrow.svg +1 -1
  22. package/BookReader/icons/magnify-minus.svg +1 -1
  23. package/BookReader/icons/magnify-plus.svg +1 -1
  24. package/BookReader/icons/magnify.svg +1 -1
  25. package/BookReader/icons/pause.svg +1 -1
  26. package/BookReader/icons/play.svg +1 -1
  27. package/BookReader/icons/playback-speed.svg +1 -1
  28. package/BookReader/icons/read-aloud.svg +1 -1
  29. package/BookReader/icons/review.svg +1 -1
  30. package/BookReader/icons/thumbnails.svg +1 -1
  31. package/BookReader/icons/voice.svg +1 -0
  32. package/BookReader/icons/volume-full.svg +1 -1
  33. package/BookReader/images/BRicons.svg +3 -3
  34. package/BookReader/images/books_graphic.svg +1 -1
  35. package/BookReader/images/icon_book.svg +1 -1
  36. package/BookReader/images/icon_bookmark.svg +1 -1
  37. package/BookReader/images/icon_gear.svg +1 -1
  38. package/BookReader/images/icon_hamburger.svg +1 -1
  39. package/BookReader/images/icon_home.svg +1 -1
  40. package/BookReader/images/icon_info.svg +1 -1
  41. package/BookReader/images/icon_one_page.svg +1 -1
  42. package/BookReader/images/icon_pause.svg +1 -1
  43. package/BookReader/images/icon_play.svg +1 -1
  44. package/BookReader/images/icon_playback-rate.svg +1 -1
  45. package/BookReader/images/icon_search_button.svg +1 -1
  46. package/BookReader/images/icon_share.svg +1 -1
  47. package/BookReader/images/icon_skip-ahead.svg +1 -1
  48. package/BookReader/images/icon_skip-back.svg +1 -1
  49. package/BookReader/images/icon_speaker.svg +1 -1
  50. package/BookReader/images/icon_speaker_open.svg +1 -1
  51. package/BookReader/images/icon_thumbnails.svg +1 -1
  52. package/BookReader/images/icon_toc.svg +1 -1
  53. package/BookReader/images/icon_two_pages.svg +1 -1
  54. package/BookReader/images/marker_chap-off.svg +1 -1
  55. package/BookReader/images/marker_chap-on.svg +1 -1
  56. package/BookReader/images/marker_srch-on.svg +1 -1
  57. package/BookReader/images/unviewable_page.png +0 -0
  58. package/BookReader/jquery-3.js +2 -0
  59. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  60. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  61. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  62. package/BookReader/plugins/plugin.autoplay.js +1 -1
  63. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  64. package/BookReader/plugins/plugin.chapters.js +25 -1
  65. package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
  66. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  67. package/BookReader/plugins/plugin.iframe.js +1 -1
  68. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  69. package/BookReader/plugins/plugin.iiif.js +2 -0
  70. package/BookReader/plugins/plugin.iiif.js.map +1 -0
  71. package/BookReader/plugins/plugin.resume.js +1 -1
  72. package/BookReader/plugins/plugin.resume.js.map +1 -1
  73. package/BookReader/plugins/plugin.search.js +2 -1
  74. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  75. package/BookReader/plugins/plugin.search.js.map +1 -1
  76. package/BookReader/plugins/plugin.text_selection.js +2 -1
  77. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  78. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  79. package/BookReader/plugins/plugin.tts.js +1 -1
  80. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  81. package/BookReader/plugins/plugin.tts.js.map +1 -1
  82. package/BookReader/plugins/plugin.url.js +1 -1
  83. package/BookReader/plugins/plugin.url.js.map +1 -1
  84. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  85. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  86. package/BookReader/webcomponents-bundle.js +3 -0
  87. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  88. package/BookReader/webcomponents-bundle.js.map +1 -0
  89. package/BookReaderDemo/BookReaderDemo.css +18 -19
  90. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -3
  91. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  92. package/BookReaderDemo/IADemoBr.js +144 -0
  93. package/BookReaderDemo/demo-advanced.html +2 -2
  94. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  95. package/BookReaderDemo/demo-fullscreen-mobile.html +3 -5
  96. package/BookReaderDemo/demo-fullscreen.html +2 -4
  97. package/BookReaderDemo/demo-iiif.html +99 -12
  98. package/BookReaderDemo/demo-internetarchive.html +214 -18
  99. package/BookReaderDemo/demo-multiple.html +2 -1
  100. package/BookReaderDemo/demo-preview-pages.html +526 -525
  101. package/BookReaderDemo/demo-simple.html +2 -1
  102. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -4
  103. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  104. package/BookReaderDemo/immersion-1up.html +2 -2
  105. package/BookReaderDemo/immersion-mode.html +2 -4
  106. package/BookReaderDemo/toggle_controls.html +3 -2
  107. package/BookReaderDemo/view_mode.html +2 -1
  108. package/BookReaderDemo/viewmode-cycle.html +2 -3
  109. package/CHANGELOG.md +600 -33
  110. package/README.md +14 -1
  111. package/babel.config.js +20 -0
  112. package/codecov.yml +6 -0
  113. package/index.html +5 -2
  114. package/jsconfig.json +19 -0
  115. package/netlify.toml +9 -0
  116. package/package.json +70 -62
  117. package/renovate.json +52 -0
  118. package/scripts/preversion.js +0 -1
  119. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  120. package/src/BookNavigator/assets/button-base.js +5 -2
  121. package/src/BookNavigator/assets/ia-logo.js +17 -0
  122. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  123. package/src/BookNavigator/assets/icon_close.js +1 -1
  124. package/src/BookNavigator/book-navigator.js +590 -0
  125. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  126. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  127. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  128. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +4 -9
  129. package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
  130. package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
  131. package/src/BookNavigator/delete-modal-actions.js +1 -1
  132. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  133. package/src/BookNavigator/downloads/downloads.js +29 -25
  134. package/src/BookNavigator/search/search-provider.js +50 -28
  135. package/src/BookNavigator/search/search-results.js +24 -10
  136. package/src/BookNavigator/sharing.js +27 -0
  137. package/src/BookNavigator/viewable-files.js +95 -0
  138. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +13 -12
  139. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +7 -7
  140. package/src/BookReader/BookModel.js +79 -43
  141. package/src/BookReader/DragScrollable.js +233 -0
  142. package/src/BookReader/ImageCache.js +48 -15
  143. package/src/BookReader/Mode1Up.js +56 -351
  144. package/src/BookReader/Mode1UpLit.js +388 -0
  145. package/src/BookReader/Mode2Up.js +73 -1318
  146. package/src/BookReader/Mode2UpLit.js +777 -0
  147. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  148. package/src/BookReader/ModeSmoothZoom.js +312 -0
  149. package/src/BookReader/ModeThumb.js +19 -13
  150. package/src/BookReader/Navbar/Navbar.js +70 -54
  151. package/src/BookReader/PageContainer.js +116 -22
  152. package/src/BookReader/ReduceSet.js +3 -3
  153. package/src/BookReader/Toolbar/Toolbar.js +14 -41
  154. package/src/BookReader/events.js +2 -3
  155. package/src/BookReader/options.js +75 -15
  156. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  157. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  158. package/src/BookReader/utils/SelectionObserver.js +45 -0
  159. package/src/BookReader/utils/classes.js +1 -1
  160. package/src/BookReader/utils.js +128 -13
  161. package/src/BookReader.js +559 -1083
  162. package/src/BookReaderPlugin.js +44 -0
  163. package/src/assets/icons/magnify-minus.svg +3 -7
  164. package/src/assets/icons/magnify-plus.svg +3 -7
  165. package/src/assets/icons/voice.svg +1 -0
  166. package/src/assets/images/unviewable_page.png +0 -0
  167. package/src/css/BookReader.scss +1 -5
  168. package/src/css/_BRBookmarks.scss +1 -1
  169. package/src/css/_BRComponent.scss +1 -1
  170. package/src/css/_BRicon.scss +8 -2
  171. package/src/css/_BRmain.scss +16 -3
  172. package/src/css/_BRnav.scss +12 -42
  173. package/src/css/_BRpages.scss +170 -42
  174. package/src/css/_BRsearch.scss +68 -25
  175. package/src/css/_BRtoolbar.scss +5 -5
  176. package/src/css/_TextSelection.scss +87 -27
  177. package/src/css/_colorbox.scss +2 -2
  178. package/src/css/_controls.scss +24 -7
  179. package/src/css/_icons.scss +1 -1
  180. package/src/ia-bookreader/ia-bookreader.js +224 -0
  181. package/src/plugins/plugin.archive_analytics.js +84 -78
  182. package/src/plugins/plugin.autoplay.js +99 -104
  183. package/src/plugins/plugin.chapters.js +310 -201
  184. package/src/plugins/plugin.iframe.js +1 -1
  185. package/src/plugins/plugin.iiif.js +141 -0
  186. package/src/plugins/plugin.resume.js +53 -50
  187. package/src/plugins/plugin.text_selection.js +519 -175
  188. package/src/plugins/plugin.vendor-fullscreen.js +7 -7
  189. package/src/plugins/search/plugin.search.js +151 -127
  190. package/src/plugins/search/utils.js +43 -0
  191. package/src/plugins/search/view.js +37 -59
  192. package/src/plugins/tts/AbstractTTSEngine.js +75 -45
  193. package/src/plugins/tts/FestivalTTSEngine.js +21 -31
  194. package/src/plugins/tts/PageChunk.js +16 -23
  195. package/src/plugins/tts/PageChunkIterator.js +11 -17
  196. package/src/plugins/tts/WebTTSEngine.js +126 -84
  197. package/src/plugins/tts/plugin.tts.js +308 -350
  198. package/src/plugins/tts/utils.js +29 -26
  199. package/src/plugins/url/UrlPlugin.js +191 -0
  200. package/src/plugins/{plugin.url.js → url/plugin.url.js} +47 -18
  201. package/src/util/browserSniffing.js +33 -1
  202. package/src/util/docCookies.js +21 -2
  203. package/src/util/strings.js +1 -0
  204. package/tests/e2e/README.md +37 -0
  205. package/tests/e2e/autoplay.test.js +9 -6
  206. package/tests/e2e/base.test.js +8 -16
  207. package/tests/e2e/helpers/base.js +55 -50
  208. package/tests/e2e/helpers/debug.js +1 -1
  209. package/tests/e2e/helpers/mockSearch.js +19 -22
  210. package/tests/e2e/helpers/params.js +17 -0
  211. package/tests/e2e/helpers/rightToLeft.js +8 -14
  212. package/tests/e2e/helpers/search.js +73 -0
  213. package/tests/e2e/models/Navigation.js +20 -37
  214. package/tests/e2e/rightToLeft.test.js +4 -5
  215. package/tests/e2e/viewmode.test.js +40 -33
  216. package/tests/jest/BookNavigator/book-navigator.test.js +661 -0
  217. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  218. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  219. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  220. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  221. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  222. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  223. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  224. package/tests/{karma → jest}/BookNavigator/search/search-results.test.js +109 -60
  225. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  226. package/tests/jest/BookNavigator/viewable-files/viewable-files-provider.test.js +80 -0
  227. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  228. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
  229. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +263 -0
  230. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  231. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  232. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  233. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  234. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  235. package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
  236. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  237. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +42 -29
  238. package/tests/jest/BookReader/PageContainer.test.js +238 -0
  239. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  240. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +3 -3
  241. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  242. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  243. package/tests/jest/BookReader/utils/SelectionObserver.test.js +57 -0
  244. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  245. package/tests/jest/BookReader/utils.test.js +250 -0
  246. package/tests/jest/BookReader.keyboard.test.js +190 -0
  247. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +10 -2
  248. package/tests/{BookReader.test.js → jest/BookReader.test.js} +43 -53
  249. package/tests/jest/plugins/plugin.archive_analytics.test.js +20 -0
  250. package/tests/jest/plugins/plugin.autoplay.test.js +35 -0
  251. package/tests/jest/plugins/plugin.chapters.test.js +193 -0
  252. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +4 -4
  253. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +22 -35
  254. package/tests/jest/plugins/plugin.text_selection.test.js +316 -0
  255. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  256. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +19 -47
  257. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +42 -9
  258. package/tests/jest/plugins/search/utils.js +25 -0
  259. package/tests/jest/plugins/search/utils.test.js +29 -0
  260. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +30 -10
  261. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  262. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  263. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  264. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +65 -13
  265. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  266. package/tests/jest/plugins/url/UrlPlugin.test.js +198 -0
  267. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +57 -18
  268. package/tests/jest/setup.js +3 -0
  269. package/tests/{util → jest/util}/browserSniffing.test.js +10 -4
  270. package/tests/jest/util/docCookies.test.js +24 -0
  271. package/tests/{util → jest/util}/strings.test.js +1 -1
  272. package/tests/jest/utils.js +83 -0
  273. package/webpack.config.js +16 -10
  274. package/.babelrc +0 -12
  275. package/.dependabot/config.yml +0 -6
  276. package/.testcaferc.json +0 -5
  277. package/BookReader/bookreader-component-bundle.js +0 -1450
  278. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  279. package/BookReader/bookreader-component-bundle.js.map +0 -1
  280. package/BookReader/jquery-1.10.1.js +0 -2
  281. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  282. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  283. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  284. package/BookReader/plugins/plugin.mobile_nav.js +0 -2
  285. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  286. package/BookReaderDemo/BookReaderJSAutoplay.js +0 -56
  287. package/BookReaderDemo/IIIFBookReader.js +0 -207
  288. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  289. package/BookReaderDemo/demo-autoplay.html +0 -38
  290. package/BookReaderDemo/demo-iiif.js +0 -26
  291. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  292. package/karma.conf.js +0 -23
  293. package/src/BookNavigator/BookModel.js +0 -14
  294. package/src/BookNavigator/BookNavigator.js +0 -446
  295. package/src/BookNavigator/assets/book-loader.js +0 -27
  296. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  297. package/src/BookNavigator/search/a-search-result.js +0 -55
  298. package/src/BookReader/DebugConsole.js +0 -54
  299. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  300. package/src/ItemNavigator/ItemNavigator.js +0 -376
  301. package/src/ItemNavigator/providers/sharing.js +0 -29
  302. package/src/css/_MobileNav.scss +0 -194
  303. package/src/dragscrollable-br.js +0 -261
  304. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  305. package/src/plugins/plugin.mobile_nav.js +0 -287
  306. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  307. package/tests/BookReader/DebugConsole.test.js +0 -25
  308. package/tests/BookReader/Mode1Up.test.js +0 -164
  309. package/tests/BookReader/Mode2Up.test.js +0 -247
  310. package/tests/BookReader/PageContainer.test.js +0 -115
  311. package/tests/BookReader/utils.test.js +0 -109
  312. package/tests/e2e/helpers/desktopSearch.js +0 -72
  313. package/tests/e2e/helpers/mobileSearch.js +0 -85
  314. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  315. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  316. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  317. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  318. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  319. package/tests/plugins/plugin.archive_analytics.test.js +0 -23
  320. package/tests/plugins/plugin.autoplay.test.js +0 -52
  321. package/tests/plugins/plugin.chapters.test.js +0 -130
  322. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  323. package/tests/plugins/plugin.text_selection.test.js +0 -203
  324. package/tests/util/docCookies.test.js +0 -15
  325. package/tests/utils.js +0 -42
@@ -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 { isAndroid, DEBUG_READ_ALOUD } from './utils.js';
4
+ import { promisifyEvent, 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 */
@@ -12,7 +13,7 @@ import AbstractTTSEngine from './AbstractTTSEngine.js';
12
13
  **/
13
14
  export default class WebTTSEngine extends AbstractTTSEngine {
14
15
  static isSupported() {
15
- return typeof(window.speechSynthesis) !== 'undefined' && !/samsungbrowser/i.test(navigator.userAgent);
16
+ return typeof(window.speechSynthesis) !== 'undefined';
16
17
  }
17
18
 
18
19
  /** @param {TTSEngineOptions} options */
@@ -44,7 +45,11 @@ export default class WebTTSEngine extends AbstractTTSEngine {
44
45
  // album: 'The Ultimate Collection (Remastered)',
45
46
  artwork: [
46
47
  { src: br.options.thumbnail, type: 'image/jpg' },
47
- ]
48
+ ],
49
+ });
50
+
51
+ navigator.mediaSession.setPositionState({
52
+ duration: Infinity,
48
53
  });
49
54
 
50
55
  navigator.mediaSession.setActionHandler('play', () => {
@@ -70,7 +75,22 @@ export default class WebTTSEngine extends AbstractTTSEngine {
70
75
  }
71
76
 
72
77
  /** @override */
73
- getVoices() { return speechSynthesis.getVoices(); }
78
+ getVoices() {
79
+ const voices = speechSynthesis.getVoices();
80
+ if (voices.filter(v => v.default).length != 1) {
81
+ // iOS bug where the default system voice is sometimes
82
+ // missing from the list
83
+ voices.unshift({
84
+ voiceURI: 'bookreader.SystemDefault',
85
+ name: 'System Default',
86
+ // Not necessarily true, but very likely
87
+ lang: navigator.language,
88
+ default: true,
89
+ localService: true,
90
+ });
91
+ }
92
+ return voices;
93
+ }
74
94
 
75
95
  /** @override */
76
96
  createSound(chunk) {
@@ -121,18 +141,22 @@ export class WebTTSSound {
121
141
  this.started = false;
122
142
 
123
143
  this.utterance = new SpeechSynthesisUtterance(this.text.slice(this._charIndex));
124
- this.utterance.voice = this.voice;
144
+ // iOS bug where the default system voice is sometimes
145
+ // missing from the list
146
+ if (this.voice?.voiceURI !== 'bookreader.SystemDefault') {
147
+ this.utterance.voice = this.voice;
148
+ }
125
149
  // Need to also set lang (for some reason); won't set voice on Chrome@Android otherwise
126
150
  if (this.voice) this.utterance.lang = this.voice.lang;
127
151
  this.utterance.rate = this.rate;
128
152
 
129
153
  // Useful for debugging things
130
- if (location.toString().indexOf('_debugReadAloud=true') != -1) {
154
+ if (DEBUG_READ_ALOUD) {
131
155
  this.utterance.addEventListener('pause', () => console.log('pause'));
132
156
  this.utterance.addEventListener('resume', () => console.log('resume'));
133
157
  this.utterance.addEventListener('start', () => console.log('start'));
134
158
  this.utterance.addEventListener('end', () => console.log('end'));
135
- this.utterance.addEventListener('error', () => console.log('error'));
159
+ this.utterance.addEventListener('error', ev => console.log('error', ev));
136
160
  this.utterance.addEventListener('boundary', () => console.log('boundary'));
137
161
  this.utterance.addEventListener('mark', () => console.log('mark'));
138
162
  this.utterance.addEventListener('finish', () => console.log('finish'));
@@ -167,7 +191,7 @@ export class WebTTSSound {
167
191
  * left off.
168
192
  * @return {Promise<void>}
169
193
  */
170
- reload() {
194
+ async reload() {
171
195
  // We'll restore the pause state, so copy it here
172
196
  const wasPaused = this.paused;
173
197
  // Use recent event to determine where we'll restart from
@@ -179,14 +203,12 @@ export class WebTTSSound {
179
203
  }
180
204
 
181
205
  // 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
- });
206
+ await this.stop();
207
+ this.load();
208
+ // Instead of playing and immediately pausing, we don't start playing. Note
209
+ // this is a requirement because pause doesn't work consistently across
210
+ // browsers.
211
+ if (!wasPaused) this.play();
190
212
  }
191
213
 
192
214
  play() {
@@ -222,15 +244,16 @@ export class WebTTSSound {
222
244
  return endPromise;
223
245
  }
224
246
 
225
- finish() {
226
- this.stop().then(() => this.utterance.dispatchEvent(new Event('finish')));
247
+ async finish() {
248
+ await this.stop();
249
+ this.utterance.dispatchEvent(new Event('finish'));
227
250
  }
228
251
 
229
252
  /**
230
253
  * @override
231
254
  * Will fire a pause event unless already paused
232
255
  **/
233
- pause() {
256
+ async pause() {
234
257
  if (this.paused) return;
235
258
 
236
259
  const pausePromise = promisifyEvent(this.utterance, 'pause');
@@ -241,25 +264,25 @@ export class WebTTSSound {
241
264
  // 2. Pause doesn't work and doesn't fire
242
265
  // 3. Pause works but doesn't fire
243
266
  const pauseMightNotWork = (isFirefox() && isAndroid());
244
- const pauseMightNotFire = isChrome() || pauseMightNotWork;
245
-
246
- if (pauseMightNotFire) {
247
- // wait for it just in case
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
- });
267
+
268
+ // Pause sometimes works, but doesn't fire the event, so wait to see if it fires
269
+ const winner = await Promise.race([pausePromise, sleep(100).then(() => 'timeout')]);
270
+
271
+ // We got our pause event; nothing to do!
272
+ if (winner != 'timeout') return;
273
+
274
+ if (DEBUG_READ_ALOUD) {
275
+ console.log('TTS: Firing pause event manually');
258
276
  }
277
+
278
+ this.utterance.dispatchEvent(new CustomEvent('pause', this._lastEvents.start));
279
+
280
+ // if pause might not work, then we'll stop entirely and restart later
281
+ if (pauseMightNotWork) this.stop();
259
282
  }
260
283
 
261
- resume() {
262
- if (!this.started) {
284
+ async resume() {
285
+ if (!this.started || this.stopped) {
263
286
  this.play();
264
287
  return;
265
288
  }
@@ -271,23 +294,24 @@ export class WebTTSSound {
271
294
  // 2. Resume works + doesn't fire (Chrome Desktop)
272
295
  // 3. Resume doesn't work + doesn't fire (Chrome/FF Android)
273
296
  const resumeMightNotWork = (isChrome() && isAndroid()) || (isFirefox() && isAndroid());
274
- const resumeMightNotFire = isChrome() || resumeMightNotWork;
275
297
 
276
298
  // Try resume
277
299
  const resumePromise = promisifyEvent(this.utterance, 'resume');
278
300
  speechSynthesis.resume();
279
301
 
280
- if (resumeMightNotFire) {
281
- Promise.race([resumePromise, sleep(100).then(() => 'timeout')])
282
- .then(result => {
283
- if (result != 'timeout') return;
302
+ const winner = await Promise.race([resumePromise, sleep(100).then(() => 'timeout')]);
303
+ // We got resume! All is good
304
+ if (winner != 'timeout') return;
284
305
 
285
- this.utterance.dispatchEvent(new CustomEvent('resume', {}));
286
- if (resumeMightNotWork) {
287
- const reloadPromise = this.reload();
288
- reloadPromise.then(() => this.play());
289
- }
290
- });
306
+ if (DEBUG_READ_ALOUD) {
307
+ console.log('TTS: Firing resume event manually');
308
+ }
309
+
310
+ // Fake it
311
+ this.utterance.dispatchEvent(new CustomEvent('resume', {}));
312
+ if (resumeMightNotWork) {
313
+ await this.reload();
314
+ this.play();
291
315
  }
292
316
  }
293
317
 
@@ -296,6 +320,11 @@ export class WebTTSSound {
296
320
  this.reload();
297
321
  }
298
322
 
323
+ /** @param {SpeechSynthesisVoice} voice */
324
+ setVoice(voice) {
325
+ this.voice = voice;
326
+ this.reload();
327
+ }
299
328
  /**
300
329
  * @private
301
330
  * Chrome has a bug where it only plays 15 seconds of TTS and then
@@ -303,45 +332,58 @@ export class WebTTSSound {
303
332
  * We avoid this (as described here: https://bugs.chromium.org/p/chromium/issues/detail?id=679437#c15 )
304
333
  * by pausing after 14 seconds and ~instantly resuming.
305
334
  */
306
- _chromePausingBugFix() {
307
- const timeoutPromise = sleep(14000).then(() => 'timeout');
308
- const pausePromise = promisifyEvent(this.utterance, 'pause').then(() => 'paused');
309
- 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;
335
+ async _chromePausingBugFix() {
336
+ if (DEBUG_READ_ALOUD) {
337
+ console.log('CHROME-PAUSE-HACK: starting');
338
+ }
339
+
340
+ const result = await Promise.race([
341
+ sleep(14000).then(() => 'timeout'),
342
+ promisifyEvent(this.utterance, 'pause').then(() => 'pause'),
343
+ promisifyEvent(this.utterance, 'end').then(() => 'end'),
344
+ // Some browsers (Edge) trigger error when the utterance is interrupted/stopped
345
+ promisifyEvent(this.utterance, 'error').then(() => 'error'),
346
+ ]);
347
+
348
+ if (DEBUG_READ_ALOUD) {
349
+ console.log(`CHROME-PAUSE-HACK: ${result}`);
350
+ }
351
+ if (result == 'end' || result == 'error') {
352
+ // audio was stopped/finished; nothing to do
353
+ if (DEBUG_READ_ALOUD) {
354
+ console.log('CHROME-PAUSE-HACK: stopped (end/error)');
355
+ }
356
+ } else if (result == 'pause') {
357
+ // audio was paused; wait for resume
358
+ // Chrome won't let you resume the audio if 14s have passed 🤷‍
359
+ // We could do the same as before (but resume+pause instead of pause+resume),
360
+ // but that means we'd _constantly_ be running in the background. So in that
361
+ // case, let's just restart the chunk
362
+ const result2 = await Promise.race([
363
+ promisifyEvent(this.utterance, 'resume').then(() => 'resume'),
364
+ sleep(14000).then(() => 'timeout'),
365
+ ]);
366
+ if (result2 == 'timeout') {
367
+ if (DEBUG_READ_ALOUD) {
368
+ console.log('CHROME-PAUSE-HACK: stopped (timed out while paused)');
343
369
  }
344
- });
370
+ // We hit Chrome's secret cut off time while paused, and
371
+ // won't be able to resume normally, so trigger a stop.
372
+ this.stop();
373
+ } else {
374
+ // The user resumed before the cut off! Continue as normal
375
+ this._chromePausingBugFix();
376
+ }
377
+ } else if (result == 'timeout') {
378
+ // We hit Chrome's secret cut off time while playing.
379
+ // To be able to keep TTS-ing, quickly pause/resume.
380
+ speechSynthesis.pause();
381
+ await sleep(25);
382
+ speechSynthesis.resume();
383
+
384
+ // Listen for more
385
+ this._chromePausingBugFix();
386
+ }
345
387
  }
346
388
  }
347
389