@internetarchive/bookreader 5.0.0-88-alpha.7 → 5.0.0-88-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/babel.config.js +30 -12
- package/dist/esm/BookNavigator/assets/bookmark-colors.js +4 -0
- package/dist/esm/BookNavigator/assets/button-base.js +4 -0
- package/dist/esm/BookNavigator/assets/ia-logo.js +4 -0
- package/dist/esm/BookNavigator/assets/icon_checkmark.js +8 -0
- package/dist/esm/BookNavigator/assets/icon_close.js +4 -0
- package/dist/esm/BookNavigator/book-navigator.js +612 -0
- package/dist/esm/BookNavigator/bookmarks/bookmark-button.js +35 -0
- package/dist/esm/BookNavigator/bookmarks/bookmark-edit.js +78 -0
- package/dist/esm/BookNavigator/bookmarks/bookmarks-list.js +160 -0
- package/dist/esm/BookNavigator/bookmarks/bookmarks-loginCTA.js +24 -0
- package/dist/esm/BookNavigator/bookmarks/bookmarks-provider.js +55 -0
- package/dist/esm/BookNavigator/bookmarks/ia-bookmarks.js +521 -0
- package/dist/esm/BookNavigator/delete-modal-actions.js +29 -0
- package/dist/esm/BookNavigator/downloads/downloads-provider.js +84 -0
- package/dist/esm/BookNavigator/downloads/downloads.js +69 -0
- package/dist/esm/BookNavigator/search/search-provider.js +238 -0
- package/dist/esm/BookNavigator/search/search-results.js +161 -0
- package/dist/esm/BookNavigator/sharing.js +26 -0
- package/dist/esm/BookNavigator/viewable-files.js +94 -0
- package/dist/esm/BookNavigator/visual-adjustments/visual-adjustments-provider.js +83 -0
- package/dist/esm/BookNavigator/visual-adjustments/visual-adjustments.js +131 -0
- package/dist/esm/BookReader/BookModel.js +575 -0
- package/dist/esm/BookReader/DragScrollable.js +224 -0
- package/dist/esm/BookReader/ImageCache.js +122 -0
- package/dist/esm/BookReader/Mode1Up.js +114 -0
- package/dist/esm/BookReader/Mode1UpLit.js +579 -0
- package/dist/esm/BookReader/Mode2Up.js +106 -0
- package/dist/esm/BookReader/Mode2UpLit.js +1020 -0
- package/dist/esm/BookReader/ModeCoordinateSpace.js +28 -0
- package/dist/esm/BookReader/ModeSmoothZoom.js +318 -0
- package/dist/esm/BookReader/ModeThumb.js +366 -0
- package/dist/esm/BookReader/Navbar/Navbar.js +253 -0
- package/dist/esm/BookReader/PageContainer.js +165 -0
- package/dist/esm/BookReader/ReduceSet.js +27 -0
- package/dist/esm/BookReader/Toolbar/Toolbar.js +242 -0
- package/dist/esm/BookReader/events.js +20 -0
- package/dist/esm/BookReader/options.js +331 -0
- package/dist/esm/BookReader/utils/HTMLDimensionsCacher.js +48 -0
- package/dist/esm/BookReader/utils/ScrollClassAdder.js +31 -0
- package/dist/esm/BookReader/utils/SelectionObserver.js +42 -0
- package/dist/esm/BookReader/utils/classes.js +37 -0
- package/dist/esm/BookReader/utils.js +315 -0
- package/dist/esm/BookReader.js +1827 -0
- package/dist/esm/assets/icons/1up.svg +12 -0
- package/dist/esm/assets/icons/2up.svg +15 -0
- package/dist/esm/assets/icons/advance.svg +26 -0
- package/dist/esm/assets/icons/chevron-right.svg +1 -0
- package/dist/esm/assets/icons/close-circle-dark.svg +1 -0
- package/dist/esm/assets/icons/close-circle.svg +1 -0
- package/dist/esm/assets/icons/fullscreen.svg +17 -0
- package/dist/esm/assets/icons/fullscreen_exit.svg +17 -0
- package/dist/esm/assets/icons/hamburger.svg +15 -0
- package/dist/esm/assets/icons/left-arrow.svg +12 -0
- package/dist/esm/assets/icons/magnify-minus.svg +12 -0
- package/dist/esm/assets/icons/magnify-plus.svg +13 -0
- package/dist/esm/assets/icons/magnify.svg +15 -0
- package/dist/esm/assets/icons/pause.svg +23 -0
- package/dist/esm/assets/icons/play.svg +22 -0
- package/dist/esm/assets/icons/playback-speed.svg +34 -0
- package/dist/esm/assets/icons/read-aloud.svg +22 -0
- package/dist/esm/assets/icons/review.svg +22 -0
- package/dist/esm/assets/icons/thumbnails.svg +17 -0
- package/dist/esm/assets/icons/voice.svg +1 -0
- package/dist/esm/assets/icons/volume-full.svg +22 -0
- package/dist/esm/assets/images/BRicons.png +0 -0
- package/dist/esm/assets/images/BRicons.svg +94 -0
- package/dist/esm/assets/images/BRicons_ia.png +0 -0
- package/dist/esm/assets/images/back_pages.png +0 -0
- package/dist/esm/assets/images/book_bottom_icon.png +0 -0
- package/dist/esm/assets/images/book_down_icon.png +0 -0
- package/dist/esm/assets/images/book_left_icon.png +0 -0
- package/dist/esm/assets/images/book_leftmost_icon.png +0 -0
- package/dist/esm/assets/images/book_right_icon.png +0 -0
- package/dist/esm/assets/images/book_rightmost_icon.png +0 -0
- package/dist/esm/assets/images/book_top_icon.png +0 -0
- package/dist/esm/assets/images/book_up_icon.png +0 -0
- package/dist/esm/assets/images/books_graphic.svg +177 -0
- package/dist/esm/assets/images/booksplit.png +0 -0
- package/dist/esm/assets/images/control_pause_icon.png +0 -0
- package/dist/esm/assets/images/control_play_icon.png +0 -0
- package/dist/esm/assets/images/embed_icon.png +0 -0
- package/dist/esm/assets/images/icon-home-ia.png +0 -0
- package/dist/esm/assets/images/icon_OL-logo-xs.png +0 -0
- package/dist/esm/assets/images/icon_alert-xs.png +0 -0
- package/dist/esm/assets/images/icon_book.svg +12 -0
- package/dist/esm/assets/images/icon_bookmark.svg +12 -0
- package/dist/esm/assets/images/icon_close-pop.png +0 -0
- package/dist/esm/assets/images/icon_download.png +0 -0
- package/dist/esm/assets/images/icon_gear.svg +14 -0
- package/dist/esm/assets/images/icon_hamburger.svg +20 -0
- package/dist/esm/assets/images/icon_home.png +0 -0
- package/dist/esm/assets/images/icon_home.svg +21 -0
- package/dist/esm/assets/images/icon_home_ia.png +0 -0
- package/dist/esm/assets/images/icon_indicator.png +0 -0
- package/dist/esm/assets/images/icon_info.svg +11 -0
- package/dist/esm/assets/images/icon_one_page.svg +8 -0
- package/dist/esm/assets/images/icon_pause.svg +1 -0
- package/dist/esm/assets/images/icon_play.svg +1 -0
- package/dist/esm/assets/images/icon_playback-rate.svg +15 -0
- package/dist/esm/assets/images/icon_return.png +0 -0
- package/dist/esm/assets/images/icon_search_button.svg +8 -0
- package/dist/esm/assets/images/icon_share.svg +9 -0
- package/dist/esm/assets/images/icon_skip-ahead.svg +6 -0
- package/dist/esm/assets/images/icon_skip-back.svg +13 -0
- package/dist/esm/assets/images/icon_speaker.svg +18 -0
- package/dist/esm/assets/images/icon_speaker_open.svg +10 -0
- package/dist/esm/assets/images/icon_thumbnails.svg +12 -0
- package/dist/esm/assets/images/icon_toc.svg +5 -0
- package/dist/esm/assets/images/icon_two_pages.svg +9 -0
- package/dist/esm/assets/images/icon_zoomer.png +0 -0
- package/dist/esm/assets/images/loading.gif +0 -0
- package/dist/esm/assets/images/logo_icon.png +0 -0
- package/dist/esm/assets/images/marker_chap-off.png +0 -0
- package/dist/esm/assets/images/marker_chap-off.svg +11 -0
- package/dist/esm/assets/images/marker_chap-off_ia.png +0 -0
- package/dist/esm/assets/images/marker_chap-on.png +0 -0
- package/dist/esm/assets/images/marker_chap-on.svg +11 -0
- package/dist/esm/assets/images/marker_srch-on.svg +11 -0
- package/dist/esm/assets/images/marker_srchchap-off.png +0 -0
- package/dist/esm/assets/images/marker_srchchap-on.png +0 -0
- package/dist/esm/assets/images/nav_control-dn.png +0 -0
- package/dist/esm/assets/images/nav_control-dn_ia.png +0 -0
- package/dist/esm/assets/images/nav_control-up.png +0 -0
- package/dist/esm/assets/images/nav_control-up_ia.png +0 -0
- package/dist/esm/assets/images/nav_control.png +0 -0
- package/dist/esm/assets/images/one_page_mode_icon.png +0 -0
- package/dist/esm/assets/images/paper-badge.png +0 -0
- package/dist/esm/assets/images/print_icon.png +0 -0
- package/dist/esm/assets/images/progressbar.gif +0 -0
- package/dist/esm/assets/images/right_edges.png +0 -0
- package/dist/esm/assets/images/slider.png +0 -0
- package/dist/esm/assets/images/slider_ia.png +0 -0
- package/dist/esm/assets/images/thumbnail_mode_icon.png +0 -0
- package/dist/esm/assets/images/transparent.png +0 -0
- package/dist/esm/assets/images/two_page_mode_icon.png +0 -0
- package/dist/esm/assets/images/unviewable_page.png +0 -0
- package/dist/esm/assets/images/zoom_in_icon.png +0 -0
- package/dist/esm/assets/images/zoom_out_icon.png +0 -0
- package/dist/esm/css/BookReader.scss +85 -0
- package/dist/esm/css/_BRBookmarks.scss +29 -0
- package/dist/esm/css/_BRComponent.scss +13 -0
- package/dist/esm/css/_BRfloat.scss +197 -0
- package/dist/esm/css/_BRicon.scss +54 -0
- package/dist/esm/css/_BRmain.scss +262 -0
- package/dist/esm/css/_BRnav.scss +354 -0
- package/dist/esm/css/_BRpages.scss +213 -0
- package/dist/esm/css/_BRsearch.scss +268 -0
- package/dist/esm/css/_BRtoolbar.scss +84 -0
- package/dist/esm/css/_BRvendor.scss +5 -0
- package/dist/esm/css/_TextSelection.scss +108 -0
- package/dist/esm/css/_colorbox.scss +52 -0
- package/dist/esm/css/_controls.scss +257 -0
- package/dist/esm/css/_icons.scss +121 -0
- package/dist/esm/ia-bookreader/ia-bookreader.js +141 -0
- package/dist/esm/jquery-wrapper.js +3 -0
- package/dist/esm/plugins/plugin.archive_analytics.js +72 -0
- package/dist/esm/plugins/plugin.autoplay.js +119 -0
- package/dist/esm/plugins/plugin.chapters.js +288 -0
- package/dist/esm/plugins/plugin.iframe.js +44 -0
- package/dist/esm/plugins/plugin.iiif.js +146 -0
- package/dist/esm/plugins/plugin.resume.js +66 -0
- package/dist/esm/plugins/plugin.text_selection.js +621 -0
- package/dist/esm/plugins/plugin.vendor-fullscreen.js +227 -0
- package/dist/esm/plugins/search/plugin.search.js +499 -0
- package/dist/esm/plugins/search/utils.js +42 -0
- package/dist/esm/plugins/search/view.js +360 -0
- package/dist/esm/plugins/tts/AbstractTTSEngine.js +282 -0
- package/dist/esm/plugins/tts/FestivalTTSEngine.js +192 -0
- package/dist/esm/plugins/tts/PageChunk.js +105 -0
- package/dist/esm/plugins/tts/PageChunkIterator.js +155 -0
- package/dist/esm/plugins/tts/WebTTSEngine.js +364 -0
- package/dist/esm/plugins/tts/plugin.tts.js +315 -0
- package/dist/esm/plugins/tts/tooltip_dict.js +14 -0
- package/dist/esm/plugins/tts/utils.js +79 -0
- package/dist/esm/plugins/url/UrlPlugin.js +197 -0
- package/dist/esm/plugins/url/plugin.url.js +212 -0
- package/dist/esm/util/browserSniffing.js +56 -0
- package/dist/esm/util/debouncer.js +25 -0
- package/dist/esm/util/docCookies.js +75 -0
- package/dist/esm/util/strings.js +34 -0
- package/jsconfig.json +1 -0
- package/package.json +13 -6
@@ -0,0 +1,364 @@
|
|
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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';
|