@designesy/read-along 0.1.1 → 0.1.3
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/package.json +1 -1
- package/src/engines/webspeech.js +6 -2
- package/src/highlight.js +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@designesy/read-along",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Embeddable bimodal read-along web component: karaoke-sync word highlighting with pluggable TTS engines (Web Speech, pre-synthesized audio, or your own).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/read-along.js",
|
package/src/engines/webspeech.js
CHANGED
|
@@ -26,7 +26,10 @@ const CHARS_PER_SEC = 14.5; // ~150 wpm × ~5.8 chars/word, heuristic at rate 1
|
|
|
26
26
|
|
|
27
27
|
export class WebSpeechEngine {
|
|
28
28
|
constructor(options = {}) {
|
|
29
|
-
|
|
29
|
+
// navigator is NOT a global on Node < 21, and this engine must be
|
|
30
|
+
// constructible outside a browser (the component imports it
|
|
31
|
+
// unconditionally). Read it defensively rather than assuming a DOM.
|
|
32
|
+
this.lang = options.lang ?? (typeof navigator !== "undefined" ? navigator.language : null) ?? "en-US";
|
|
30
33
|
this.rate = options.rate ?? 1;
|
|
31
34
|
this.pitch = options.pitch ?? 1;
|
|
32
35
|
this.voiceName = options.voiceName ?? null;
|
|
@@ -341,7 +344,8 @@ export class WebSpeechEngine {
|
|
|
341
344
|
// Desktop-Chrome-only belt-and-suspenders for the ~15s watchdog
|
|
342
345
|
// (chromium:41294170). On Android, pause()/resume() mid-utterance
|
|
343
346
|
// breaks synthesis entirely — sentence chunking alone is the fix there.
|
|
344
|
-
|
|
347
|
+
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
348
|
+
if (/Android/i.test(ua)) return;
|
|
345
349
|
this._keepalive = setInterval(() => {
|
|
346
350
|
if (this._stopped || this._paused || this._visualOnly) return;
|
|
347
351
|
if (speechSynthesis.speaking && !speechSynthesis.paused) {
|
package/src/highlight.js
CHANGED
|
@@ -126,6 +126,12 @@ export class Highlighter {
|
|
|
126
126
|
hl.add(r);
|
|
127
127
|
this._wordRanges.push(r);
|
|
128
128
|
}
|
|
129
|
+
// Track the active token on the NATIVE path too. Only the fallback
|
|
130
|
+
// path maintained _markIndex, so `activeToken` read the -2 sentinel in
|
|
131
|
+
// every browser that supports the Highlight API — it was always wrong
|
|
132
|
+
// exactly where it worked best. Any host that reads the reading
|
|
133
|
+
// position (eye-gaze, a BCI bridge, a progress bar) got nothing.
|
|
134
|
+
this._markIndex = r ? i : -2;
|
|
129
135
|
} else {
|
|
130
136
|
this._fallbackMark(i);
|
|
131
137
|
}
|
|
@@ -154,6 +160,10 @@ export class Highlighter {
|
|
|
154
160
|
this._sentenceRanges.length = 0;
|
|
155
161
|
}
|
|
156
162
|
this._unwrapMark();
|
|
163
|
+
// _unwrapMark only resets the index when a fallback <mark> existed, so
|
|
164
|
+
// the native path needs its own reset or a cleared player keeps claiming
|
|
165
|
+
// an active word.
|
|
166
|
+
this._markIndex = -2;
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
destroy() {
|