@internetarchive/bookreader 5.0.0-88-alpha.10 → 5.0.0-88

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 (215) hide show
  1. package/BookReader/BookReader.css +3 -3
  2. package/BookReader/BookReader.js +1 -1
  3. package/BookReader/BookReader.js.map +1 -1
  4. package/BookReader/ia-bookreader-bundle.js +87 -108
  5. package/BookReader/ia-bookreader-bundle.js.map +1 -1
  6. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  7. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  8. package/BookReader/plugins/plugin.search.js +1 -1
  9. package/BookReader/plugins/plugin.search.js.map +1 -1
  10. package/BookReader/plugins/plugin.text_selection.js +1 -1
  11. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  12. package/BookReader/plugins/plugin.tts.js +1 -1
  13. package/BookReader/plugins/plugin.tts.js.map +1 -1
  14. package/BookReader/plugins/plugin.url.js +1 -1
  15. package/BookReader/plugins/plugin.url.js.map +1 -1
  16. package/CHANGELOG.md +4 -0
  17. package/babel.config.js +12 -30
  18. package/jsconfig.json +1 -3
  19. package/package.json +14 -16
  20. package/src/BookNavigator/search/search-results.js +1 -1
  21. package/src/BookReader/Mode1UpLit.js +56 -86
  22. package/src/BookReader/Mode2UpLit.js +2 -3
  23. package/src/BookReader/Navbar/Navbar.js +53 -11
  24. package/src/BookReader/options.js +3 -0
  25. package/src/BookReader.js +49 -1
  26. package/src/BookReaderPlugin.js +28 -0
  27. package/src/css/_BRnav.scss +0 -3
  28. package/src/css/_controls.scss +4 -0
  29. package/src/plugins/plugin.archive_analytics.js +84 -78
  30. package/src/plugins/plugin.chapters.js +17 -22
  31. package/src/plugins/plugin.text_selection.js +1 -1
  32. package/src/plugins/tts/plugin.tts.js +2 -2
  33. package/tests/jest/BookReader/Navbar/Navbar.test.js +16 -3
  34. package/tests/jest/plugins/plugin.archive_analytics.test.js +8 -11
  35. package/dist/esm/BookNavigator/assets/bookmark-colors.js +0 -4
  36. package/dist/esm/BookNavigator/assets/button-base.js +0 -4
  37. package/dist/esm/BookNavigator/assets/ia-logo.js +0 -4
  38. package/dist/esm/BookNavigator/assets/icon_checkmark.js +0 -8
  39. package/dist/esm/BookNavigator/assets/icon_close.js +0 -4
  40. package/dist/esm/BookNavigator/book-navigator.js +0 -612
  41. package/dist/esm/BookNavigator/bookmarks/bookmark-button.js +0 -35
  42. package/dist/esm/BookNavigator/bookmarks/bookmark-edit.js +0 -78
  43. package/dist/esm/BookNavigator/bookmarks/bookmarks-list.js +0 -160
  44. package/dist/esm/BookNavigator/bookmarks/bookmarks-loginCTA.js +0 -24
  45. package/dist/esm/BookNavigator/bookmarks/bookmarks-provider.js +0 -55
  46. package/dist/esm/BookNavigator/bookmarks/ia-bookmarks.js +0 -521
  47. package/dist/esm/BookNavigator/delete-modal-actions.js +0 -29
  48. package/dist/esm/BookNavigator/downloads/downloads-provider.js +0 -84
  49. package/dist/esm/BookNavigator/downloads/downloads.js +0 -69
  50. package/dist/esm/BookNavigator/search/search-provider.js +0 -238
  51. package/dist/esm/BookNavigator/search/search-results.js +0 -161
  52. package/dist/esm/BookNavigator/sharing.js +0 -26
  53. package/dist/esm/BookNavigator/viewable-files.js +0 -94
  54. package/dist/esm/BookNavigator/visual-adjustments/visual-adjustments-provider.js +0 -83
  55. package/dist/esm/BookNavigator/visual-adjustments/visual-adjustments.js +0 -131
  56. package/dist/esm/BookReader/BookModel.js +0 -575
  57. package/dist/esm/BookReader/DragScrollable.js +0 -224
  58. package/dist/esm/BookReader/ImageCache.js +0 -122
  59. package/dist/esm/BookReader/Mode1Up.js +0 -114
  60. package/dist/esm/BookReader/Mode1UpLit.js +0 -579
  61. package/dist/esm/BookReader/Mode2Up.js +0 -106
  62. package/dist/esm/BookReader/Mode2UpLit.js +0 -1020
  63. package/dist/esm/BookReader/ModeCoordinateSpace.js +0 -28
  64. package/dist/esm/BookReader/ModeSmoothZoom.js +0 -318
  65. package/dist/esm/BookReader/ModeThumb.js +0 -366
  66. package/dist/esm/BookReader/Navbar/Navbar.js +0 -253
  67. package/dist/esm/BookReader/PageContainer.js +0 -165
  68. package/dist/esm/BookReader/ReduceSet.js +0 -27
  69. package/dist/esm/BookReader/Toolbar/Toolbar.js +0 -242
  70. package/dist/esm/BookReader/events.js +0 -20
  71. package/dist/esm/BookReader/options.js +0 -331
  72. package/dist/esm/BookReader/utils/HTMLDimensionsCacher.js +0 -48
  73. package/dist/esm/BookReader/utils/ScrollClassAdder.js +0 -31
  74. package/dist/esm/BookReader/utils/SelectionObserver.js +0 -42
  75. package/dist/esm/BookReader/utils/classes.js +0 -37
  76. package/dist/esm/BookReader/utils.js +0 -315
  77. package/dist/esm/BookReader.js +0 -1827
  78. package/dist/esm/assets/icons/1up.svg +0 -12
  79. package/dist/esm/assets/icons/2up.svg +0 -15
  80. package/dist/esm/assets/icons/advance.svg +0 -26
  81. package/dist/esm/assets/icons/chevron-right.svg +0 -1
  82. package/dist/esm/assets/icons/close-circle-dark.svg +0 -1
  83. package/dist/esm/assets/icons/close-circle.svg +0 -1
  84. package/dist/esm/assets/icons/fullscreen.svg +0 -17
  85. package/dist/esm/assets/icons/fullscreen_exit.svg +0 -17
  86. package/dist/esm/assets/icons/hamburger.svg +0 -15
  87. package/dist/esm/assets/icons/left-arrow.svg +0 -12
  88. package/dist/esm/assets/icons/magnify-minus.svg +0 -12
  89. package/dist/esm/assets/icons/magnify-plus.svg +0 -13
  90. package/dist/esm/assets/icons/magnify.svg +0 -15
  91. package/dist/esm/assets/icons/pause.svg +0 -23
  92. package/dist/esm/assets/icons/play.svg +0 -22
  93. package/dist/esm/assets/icons/playback-speed.svg +0 -34
  94. package/dist/esm/assets/icons/read-aloud.svg +0 -22
  95. package/dist/esm/assets/icons/review.svg +0 -22
  96. package/dist/esm/assets/icons/thumbnails.svg +0 -17
  97. package/dist/esm/assets/icons/voice.svg +0 -1
  98. package/dist/esm/assets/icons/volume-full.svg +0 -22
  99. package/dist/esm/assets/images/BRicons.png +0 -0
  100. package/dist/esm/assets/images/BRicons.svg +0 -94
  101. package/dist/esm/assets/images/BRicons_ia.png +0 -0
  102. package/dist/esm/assets/images/back_pages.png +0 -0
  103. package/dist/esm/assets/images/book_bottom_icon.png +0 -0
  104. package/dist/esm/assets/images/book_down_icon.png +0 -0
  105. package/dist/esm/assets/images/book_left_icon.png +0 -0
  106. package/dist/esm/assets/images/book_leftmost_icon.png +0 -0
  107. package/dist/esm/assets/images/book_right_icon.png +0 -0
  108. package/dist/esm/assets/images/book_rightmost_icon.png +0 -0
  109. package/dist/esm/assets/images/book_top_icon.png +0 -0
  110. package/dist/esm/assets/images/book_up_icon.png +0 -0
  111. package/dist/esm/assets/images/books_graphic.svg +0 -177
  112. package/dist/esm/assets/images/booksplit.png +0 -0
  113. package/dist/esm/assets/images/control_pause_icon.png +0 -0
  114. package/dist/esm/assets/images/control_play_icon.png +0 -0
  115. package/dist/esm/assets/images/embed_icon.png +0 -0
  116. package/dist/esm/assets/images/icon-home-ia.png +0 -0
  117. package/dist/esm/assets/images/icon_OL-logo-xs.png +0 -0
  118. package/dist/esm/assets/images/icon_alert-xs.png +0 -0
  119. package/dist/esm/assets/images/icon_book.svg +0 -12
  120. package/dist/esm/assets/images/icon_bookmark.svg +0 -12
  121. package/dist/esm/assets/images/icon_close-pop.png +0 -0
  122. package/dist/esm/assets/images/icon_download.png +0 -0
  123. package/dist/esm/assets/images/icon_gear.svg +0 -14
  124. package/dist/esm/assets/images/icon_hamburger.svg +0 -20
  125. package/dist/esm/assets/images/icon_home.png +0 -0
  126. package/dist/esm/assets/images/icon_home.svg +0 -21
  127. package/dist/esm/assets/images/icon_home_ia.png +0 -0
  128. package/dist/esm/assets/images/icon_indicator.png +0 -0
  129. package/dist/esm/assets/images/icon_info.svg +0 -11
  130. package/dist/esm/assets/images/icon_one_page.svg +0 -8
  131. package/dist/esm/assets/images/icon_pause.svg +0 -1
  132. package/dist/esm/assets/images/icon_play.svg +0 -1
  133. package/dist/esm/assets/images/icon_playback-rate.svg +0 -15
  134. package/dist/esm/assets/images/icon_return.png +0 -0
  135. package/dist/esm/assets/images/icon_search_button.svg +0 -8
  136. package/dist/esm/assets/images/icon_share.svg +0 -9
  137. package/dist/esm/assets/images/icon_skip-ahead.svg +0 -6
  138. package/dist/esm/assets/images/icon_skip-back.svg +0 -13
  139. package/dist/esm/assets/images/icon_speaker.svg +0 -18
  140. package/dist/esm/assets/images/icon_speaker_open.svg +0 -10
  141. package/dist/esm/assets/images/icon_thumbnails.svg +0 -12
  142. package/dist/esm/assets/images/icon_toc.svg +0 -5
  143. package/dist/esm/assets/images/icon_two_pages.svg +0 -9
  144. package/dist/esm/assets/images/icon_zoomer.png +0 -0
  145. package/dist/esm/assets/images/loading.gif +0 -0
  146. package/dist/esm/assets/images/logo_icon.png +0 -0
  147. package/dist/esm/assets/images/marker_chap-off.png +0 -0
  148. package/dist/esm/assets/images/marker_chap-off.svg +0 -11
  149. package/dist/esm/assets/images/marker_chap-off_ia.png +0 -0
  150. package/dist/esm/assets/images/marker_chap-on.png +0 -0
  151. package/dist/esm/assets/images/marker_chap-on.svg +0 -11
  152. package/dist/esm/assets/images/marker_srch-on.svg +0 -11
  153. package/dist/esm/assets/images/marker_srchchap-off.png +0 -0
  154. package/dist/esm/assets/images/marker_srchchap-on.png +0 -0
  155. package/dist/esm/assets/images/nav_control-dn.png +0 -0
  156. package/dist/esm/assets/images/nav_control-dn_ia.png +0 -0
  157. package/dist/esm/assets/images/nav_control-up.png +0 -0
  158. package/dist/esm/assets/images/nav_control-up_ia.png +0 -0
  159. package/dist/esm/assets/images/nav_control.png +0 -0
  160. package/dist/esm/assets/images/one_page_mode_icon.png +0 -0
  161. package/dist/esm/assets/images/paper-badge.png +0 -0
  162. package/dist/esm/assets/images/print_icon.png +0 -0
  163. package/dist/esm/assets/images/progressbar.gif +0 -0
  164. package/dist/esm/assets/images/right_edges.png +0 -0
  165. package/dist/esm/assets/images/slider.png +0 -0
  166. package/dist/esm/assets/images/slider_ia.png +0 -0
  167. package/dist/esm/assets/images/thumbnail_mode_icon.png +0 -0
  168. package/dist/esm/assets/images/transparent.png +0 -0
  169. package/dist/esm/assets/images/two_page_mode_icon.png +0 -0
  170. package/dist/esm/assets/images/unviewable_page.png +0 -0
  171. package/dist/esm/assets/images/zoom_in_icon.png +0 -0
  172. package/dist/esm/assets/images/zoom_out_icon.png +0 -0
  173. package/dist/esm/css/BookReader.scss +0 -85
  174. package/dist/esm/css/_BRBookmarks.scss +0 -29
  175. package/dist/esm/css/_BRComponent.scss +0 -13
  176. package/dist/esm/css/_BRfloat.scss +0 -197
  177. package/dist/esm/css/_BRicon.scss +0 -54
  178. package/dist/esm/css/_BRmain.scss +0 -262
  179. package/dist/esm/css/_BRnav.scss +0 -354
  180. package/dist/esm/css/_BRpages.scss +0 -213
  181. package/dist/esm/css/_BRsearch.scss +0 -268
  182. package/dist/esm/css/_BRtoolbar.scss +0 -84
  183. package/dist/esm/css/_BRvendor.scss +0 -5
  184. package/dist/esm/css/_TextSelection.scss +0 -108
  185. package/dist/esm/css/_colorbox.scss +0 -52
  186. package/dist/esm/css/_controls.scss +0 -257
  187. package/dist/esm/css/_icons.scss +0 -121
  188. package/dist/esm/ia-bookreader/ia-bookreader.js +0 -141
  189. package/dist/esm/jquery-wrapper.js +0 -3
  190. package/dist/esm/plugins/plugin.archive_analytics.js +0 -72
  191. package/dist/esm/plugins/plugin.autoplay.js +0 -119
  192. package/dist/esm/plugins/plugin.chapters.js +0 -288
  193. package/dist/esm/plugins/plugin.iframe.js +0 -44
  194. package/dist/esm/plugins/plugin.iiif.js +0 -146
  195. package/dist/esm/plugins/plugin.resume.js +0 -66
  196. package/dist/esm/plugins/plugin.text_selection.js +0 -621
  197. package/dist/esm/plugins/plugin.vendor-fullscreen.js +0 -227
  198. package/dist/esm/plugins/search/plugin.search.js +0 -499
  199. package/dist/esm/plugins/search/utils.js +0 -42
  200. package/dist/esm/plugins/search/view.js +0 -360
  201. package/dist/esm/plugins/tts/AbstractTTSEngine.js +0 -282
  202. package/dist/esm/plugins/tts/FestivalTTSEngine.js +0 -192
  203. package/dist/esm/plugins/tts/PageChunk.js +0 -105
  204. package/dist/esm/plugins/tts/PageChunkIterator.js +0 -155
  205. package/dist/esm/plugins/tts/WebTTSEngine.js +0 -364
  206. package/dist/esm/plugins/tts/plugin.tts.js +0 -315
  207. package/dist/esm/plugins/tts/tooltip_dict.js +0 -14
  208. package/dist/esm/plugins/tts/utils.js +0 -79
  209. package/dist/esm/plugins/url/UrlPlugin.js +0 -197
  210. package/dist/esm/plugins/url/plugin.url.js +0 -212
  211. package/dist/esm/util/browserSniffing.js +0 -56
  212. package/dist/esm/util/debouncer.js +0 -25
  213. package/dist/esm/util/docCookies.js +0 -75
  214. package/dist/esm/util/strings.js +0 -34
  215. package/index.js +0 -2
@@ -1,364 +0,0 @@
1
- function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
2
- function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
3
- /* global br */
4
- import { isChrome, isFirefox } from '../../util/browserSniffing.js';
5
- import { isAndroid } from './utils.js';
6
- import { promisifyEvent, sleep } from '../../BookReader/utils.js';
7
- import AbstractTTSEngine from './AbstractTTSEngine.js';
8
- /** @typedef {import("./AbstractTTSEngine.js").PageChunk} PageChunk */
9
- /** @typedef {import("./AbstractTTSEngine.js").AbstractTTSSound} AbstractTTSSound */
10
- /** @typedef {import("./AbstractTTSEngine.js").TTSEngineOptions} TTSEngineOptions */
11
-
12
- /**
13
- * @extends AbstractTTSEngine
14
- * TTS using Web Speech APIs
15
- **/
16
- export default class WebTTSEngine extends AbstractTTSEngine {
17
- static isSupported() {
18
- return typeof window.speechSynthesis !== 'undefined' && !/samsungbrowser/i.test(navigator.userAgent);
19
- }
20
-
21
- /** @param {TTSEngineOptions} options */
22
- constructor(options) {
23
- super(options);
24
-
25
- // SAFARI doesn't have addEventListener on speechSynthesis
26
- if (speechSynthesis.addEventListener) {
27
- speechSynthesis.addEventListener('voiceschanged', () => this.events.trigger('voiceschanged'));
28
- }
29
- }
30
-
31
- /** @override */
32
- start(leafIndex, numLeafs) {
33
- // Need to run in this function to capture user intent to start playing audio
34
- if ('mediaSession' in navigator) {
35
- var audio = new Audio(SILENCE_6S_MP3);
36
- audio.loop = true;
37
- this.events.on('pause', () => audio.pause());
38
- this.events.on('resume', () => audio.play());
39
- // apparently this is what you need to do to make the media session notification go away
40
- // See https://developers.google.com/web/updates/2017/02/media-session#implementation_notes
41
- this.events.on('stop', () => audio.src = '');
42
- audio.play().then(() => {
43
- navigator.mediaSession.metadata = new MediaMetadata({
44
- title: br.bookTitle,
45
- artist: br.options.metadata.filter(m => m.label == 'Author').map(m => m.value)[0],
46
- // album: 'The Ultimate Collection (Remastered)',
47
- artwork: [{
48
- src: br.options.thumbnail,
49
- type: 'image/jpg'
50
- }]
51
- });
52
- navigator.mediaSession.setActionHandler('play', () => {
53
- audio.play();
54
- this.resume();
55
- });
56
- navigator.mediaSession.setActionHandler('pause', () => {
57
- audio.pause();
58
- this.pause();
59
- });
60
-
61
- // navigator.mediaSession.setActionHandler('seekbackward', () => this.jumpBackward());
62
- // navigator.mediaSession.setActionHandler('seekforward', () => this.jumpForward());
63
- // Some devices only support the previoustrack/nexttrack (e.g. Win10), so show those.
64
- // Android devices do support the seek actions, but we don't want to show both buttons
65
- // and have them do the same thing.
66
- navigator.mediaSession.setActionHandler('previoustrack', () => this.jumpBackward());
67
- navigator.mediaSession.setActionHandler('nexttrack', () => this.jumpForward());
68
- });
69
- }
70
- return super.start(leafIndex, numLeafs);
71
- }
72
-
73
- /** @override */
74
- getVoices() {
75
- var voices = speechSynthesis.getVoices();
76
- if (voices.filter(v => v.default).length != 1) {
77
- // iOS bug where the default system voice is sometimes
78
- // missing from the list
79
- voices.unshift({
80
- voiceURI: 'bookreader.SystemDefault',
81
- name: 'System Default',
82
- // Not necessarily true, but very likely
83
- lang: navigator.language,
84
- default: true,
85
- localService: true
86
- });
87
- }
88
- return voices;
89
- }
90
-
91
- /** @override */
92
- createSound(chunk) {
93
- return new WebTTSSound(chunk.text);
94
- }
95
- }
96
-
97
- /** @extends AbstractTTSSound */
98
- export class WebTTSSound {
99
- /** @param {string} text **/
100
- constructor(text) {
101
- this.text = text;
102
- this.loaded = false;
103
- this.paused = false;
104
- this.started = false;
105
- /** Whether the audio was stopped with a .stop() call */
106
- this.stopped = false;
107
- this.rate = 1;
108
-
109
- /** @type {SpeechSynthesisUtterance} */
110
- this.utterance = null;
111
-
112
- /** @type {SpeechSynthesisVoice} */
113
- this.voice = null;
114
- this._lastEvents = {
115
- /** @type {SpeechSynthesisEvent} */
116
- pause: null,
117
- /** @type {SpeechSynthesisEvent} */
118
- boundary: null,
119
- /** @type {SpeechSynthesisEvent} */
120
- start: null
121
- };
122
-
123
- /** Store where we are in the text. Only works on some browsers. */
124
- this._charIndex = 0;
125
-
126
- /** @type {Function} resolve function called when playback finished */
127
- this._finishResolver = null;
128
-
129
- /** @type {Promise} promise resolved by _finishResolver */
130
- this._finishPromise = null;
131
- }
132
-
133
- /** @override **/
134
- load(onload) {
135
- var _this$voice;
136
- this.loaded = false;
137
- this.started = false;
138
- this.utterance = new SpeechSynthesisUtterance(this.text.slice(this._charIndex));
139
- // iOS bug where the default system voice is sometimes
140
- // missing from the list
141
- if (((_this$voice = this.voice) === null || _this$voice === void 0 ? void 0 : _this$voice.voiceURI) !== 'bookreader.SystemDefault') {
142
- this.utterance.voice = this.voice;
143
- }
144
- // Need to also set lang (for some reason); won't set voice on Chrome@Android otherwise
145
- if (this.voice) this.utterance.lang = this.voice.lang;
146
- this.utterance.rate = this.rate;
147
-
148
- // Useful for debugging things
149
- if (location.toString().indexOf('_debugReadAloud=true') != -1) {
150
- this.utterance.addEventListener('pause', () => console.log('pause'));
151
- this.utterance.addEventListener('resume', () => console.log('resume'));
152
- this.utterance.addEventListener('start', () => console.log('start'));
153
- this.utterance.addEventListener('end', () => console.log('end'));
154
- this.utterance.addEventListener('error', () => console.log('error'));
155
- this.utterance.addEventListener('boundary', () => console.log('boundary'));
156
- this.utterance.addEventListener('mark', () => console.log('mark'));
157
- this.utterance.addEventListener('finish', () => console.log('finish'));
158
- }
159
-
160
- // Keep track of the speech synthesis events that come in; they have useful info
161
- // about progress (like charIndex)
162
- this.utterance.addEventListener('start', ev => this._lastEvents.start = ev);
163
- this.utterance.addEventListener('boundary', ev => this._lastEvents.boundary = ev);
164
- this.utterance.addEventListener('pause', ev => this._lastEvents.pause = ev);
165
-
166
- // Update our state
167
- this.utterance.addEventListener('start', () => {
168
- this.started = true;
169
- this.stopped = false;
170
- this.paused = false;
171
- });
172
- this.utterance.addEventListener('pause', () => this.paused = true);
173
- this.utterance.addEventListener('resume', () => this.paused = false);
174
- this.utterance.addEventListener('end', ev => {
175
- if (!this.paused && !this.stopped) {
176
- // Trigger a new event, finish, which only fires when audio fully completed
177
- this.utterance.dispatchEvent(new CustomEvent('finish', ev));
178
- }
179
- });
180
- this.loaded = true;
181
- onload && onload();
182
- }
183
-
184
- /**
185
- * Run whenever properties have changed. Tries to restart in the same spot it
186
- * left off.
187
- * @return {Promise<void>}
188
- */
189
- reload() {
190
- var _this = this;
191
- return _asyncToGenerator(function* () {
192
- // We'll restore the pause state, so copy it here
193
- var wasPaused = _this.paused;
194
- // Use recent event to determine where we'll restart from
195
- // Browser support for this is mixed, but it degrades to restarting the chunk
196
- // and that's ok
197
- var recentEvent = _this._lastEvents.boundary || _this._lastEvents.pause;
198
- if (recentEvent) {
199
- _this._charIndex = _this.text.indexOf(recentEvent.target.text) + recentEvent.charIndex;
200
- }
201
-
202
- // We can't modify the utterance object, so we have to make a new one
203
- yield _this.stop();
204
- _this.load();
205
- // Instead of playing and immediately pausing, we don't start playing. Note
206
- // this is a requirement because pause doesn't work consistently across
207
- // browsers.
208
- if (!wasPaused) _this.play();
209
- })();
210
- }
211
- play() {
212
- this._finishPromise = this._finishPromise || new Promise(res => this._finishResolver = res);
213
- this.utterance.addEventListener('finish', this._finishResolver);
214
-
215
- // clear the queue
216
- speechSynthesis.cancel();
217
- // reset pause state
218
- speechSynthesis.resume();
219
- // Speak
220
- speechSynthesis.speak(this.utterance);
221
- var isLocalVoice = this.utterance.voice && this.utterance.voice.localService;
222
- if (isChrome() && !isLocalVoice) this._chromePausingBugFix();
223
- return this._finishPromise;
224
- }
225
-
226
- /** @return {Promise} */
227
- stop() {
228
- // 'end' won't fire if already stopped
229
- var endPromise = Promise.resolve();
230
- if (!this.stopped) {
231
- endPromise = Promise.race([promisifyEvent(this.utterance, 'end'),
232
- // Safari triggers an error when you call cancel mid-sound
233
- promisifyEvent(this.utterance, 'error')]);
234
- }
235
- this.stopped = true;
236
- speechSynthesis.cancel();
237
- return endPromise;
238
- }
239
- finish() {
240
- var _this2 = this;
241
- return _asyncToGenerator(function* () {
242
- yield _this2.stop();
243
- _this2.utterance.dispatchEvent(new Event('finish'));
244
- })();
245
- }
246
-
247
- /**
248
- * @override
249
- * Will fire a pause event unless already paused
250
- **/
251
- pause() {
252
- var _this3 = this;
253
- return _asyncToGenerator(function* () {
254
- if (_this3.paused) return;
255
- var pausePromise = promisifyEvent(_this3.utterance, 'pause');
256
- speechSynthesis.pause();
257
-
258
- // There are a few awful browser cases:
259
- // 1. Pause works and fires
260
- // 2. Pause doesn't work and doesn't fire
261
- // 3. Pause works but doesn't fire
262
- var pauseMightNotWork = isFirefox() && isAndroid();
263
- var pauseMightNotFire = isChrome() || pauseMightNotWork;
264
- if (pauseMightNotFire) {
265
- // wait for it just in case
266
- var timeoutPromise = sleep(100).then(() => 'timeout');
267
- var result = yield Promise.race([pausePromise, timeoutPromise]);
268
- // We got our pause event; nothing to do!
269
- if (result != 'timeout') return;
270
- _this3.utterance.dispatchEvent(new CustomEvent('pause', _this3._lastEvents.start));
271
-
272
- // if pause might not work, then we'll stop entirely and restart later
273
- if (pauseMightNotWork) _this3.stop();
274
- }
275
- })();
276
- }
277
- resume() {
278
- var _this4 = this;
279
- return _asyncToGenerator(function* () {
280
- if (!_this4.started) {
281
- _this4.play();
282
- return;
283
- }
284
- if (!_this4.paused) return;
285
-
286
- // Browser cases:
287
- // 1. Resume works + fires
288
- // 2. Resume works + doesn't fire (Chrome Desktop)
289
- // 3. Resume doesn't work + doesn't fire (Chrome/FF Android)
290
- var resumeMightNotWork = isChrome() && isAndroid() || isFirefox() && isAndroid();
291
- var resumeMightNotFire = isChrome() || resumeMightNotWork;
292
-
293
- // Try resume
294
- var resumePromise = promisifyEvent(_this4.utterance, 'resume');
295
- speechSynthesis.resume();
296
- if (resumeMightNotFire) {
297
- var result = yield Promise.race([resumePromise, sleep(100).then(() => 'timeout')]);
298
- if (result != 'timeout') return;
299
- _this4.utterance.dispatchEvent(new CustomEvent('resume', {}));
300
- if (resumeMightNotWork) {
301
- yield _this4.reload();
302
- _this4.play();
303
- }
304
- }
305
- })();
306
- }
307
- setPlaybackRate(rate) {
308
- this.rate = rate;
309
- this.reload();
310
- }
311
-
312
- /** @param {SpeechSynthesisVoice} voice */
313
- setVoice(voice) {
314
- this.voice = voice;
315
- this.reload();
316
- }
317
- /**
318
- * @private
319
- * Chrome has a bug where it only plays 15 seconds of TTS and then
320
- * suddenly stops (see https://bugs.chromium.org/p/chromium/issues/detail?id=679437 )
321
- * We avoid this (as described here: https://bugs.chromium.org/p/chromium/issues/detail?id=679437#c15 )
322
- * by pausing after 14 seconds and ~instantly resuming.
323
- */
324
- _chromePausingBugFix() {
325
- var _this5 = this;
326
- return _asyncToGenerator(function* () {
327
- var timeoutPromise = sleep(14000).then(() => 'timeout');
328
- var pausePromise = promisifyEvent(_this5.utterance, 'pause').then(() => 'paused');
329
- var endPromise = promisifyEvent(_this5.utterance, 'end').then(() => 'ended');
330
- var result = yield Promise.race([timeoutPromise, pausePromise, endPromise]);
331
- if (location.toString().indexOf('_debugReadAloud=true') != -1) {
332
- console.log("CHROME-PAUSE-HACK: ".concat(result));
333
- }
334
- switch (result) {
335
- case 'ended':
336
- // audio was stopped/finished; nothing to do
337
- break;
338
- case 'paused':
339
- // audio was paused; wait for resume
340
- // Chrome won't let you resume the audio if 14s have passed 🤷‍
341
- // We could do the same as before (but resume+pause instead of pause+resume),
342
- // but that means we'd _constantly_ be running in the background. So in that
343
- // case, let's just restart the chunk
344
- yield Promise.race([promisifyEvent(_this5.utterance, 'resume'), sleep(14000).then(() => 'timeout')]);
345
- result == 'timeout' ? _this5.reload() : _this5._chromePausingBugFix();
346
- break;
347
- case 'timeout':
348
- // We hit Chrome's secret cut off time. Pause/resume
349
- // to be able to keep TTS-ing
350
- speechSynthesis.pause();
351
- yield sleep(25);
352
- speechSynthesis.resume();
353
- _this5._chromePausingBugFix();
354
- break;
355
- }
356
- })();
357
- }
358
- }
359
-
360
- /**
361
- * According to https://developers.google.com/web/updates/2017/02/media-session#implementation_notes , it needs to be at least 5 seconds
362
- * long to allow usage of the media sessions api
363
- */
364
- var SILENCE_6S_MP3 = 'data:audio/mp3;base64,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';