@pie-players/pie-assessment-toolkit 0.3.73 → 0.3.75
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/README.md +469 -318
- package/dist/attempt/AssessmentSession.d.ts +1 -5
- package/dist/attempt/AssessmentSession.js +0 -29
- package/dist/attempt/TestSession.d.ts +1 -20
- package/dist/attempt/TestSession.js +8 -99
- package/dist/components/ItemScope.custom-element.d.ts +1 -0
- package/dist/components/ItemScope.custom-element.js +1 -0
- package/dist/components/ItemToolBar.custom-element.d.ts +1 -0
- package/dist/components/ItemToolBar.custom-element.js +1 -1
- package/dist/components/PieAssessmentToolkit.custom-element.d.ts +1 -0
- package/dist/components/PieAssessmentToolkit.custom-element.js +13 -13
- package/dist/components/SectionToolBar.custom-element.d.ts +1 -0
- package/dist/components/SectionToolBar.custom-element.js +1 -1
- package/dist/components/chunks/ItemToolBar-7tt7v2xp.js +4 -0
- package/dist/components/chunks/ItemToolBar-97hx60z4.js +2 -0
- package/dist/components/chunks/ItemToolBar-bxwyhr5p.js +2 -0
- package/dist/components/chunks/ItemToolBar-jx38w99s.js +2 -0
- package/dist/components/chunks/ItemToolBar-rd2bb4p6.js +40 -0
- package/dist/components/chunks/ItemToolBar-vdkyn9f3.js +2 -0
- package/dist/components/chunks/sre-engine-jd8cc08x.js +1 -0
- package/dist/components/item-scope-element.d.ts +2 -0
- package/dist/components/item-scope-element.js +2 -0
- package/dist/context/assessment-toolkit-context.d.ts +32 -6
- package/dist/index.d.ts +23 -48
- package/dist/index.js +12 -37
- package/dist/policy/core/PolicySource.d.ts +1 -1
- package/dist/policy/core/ToolPolicyEngine.d.ts +85 -34
- package/dist/policy/core/ToolPolicyEngine.js +148 -46
- package/dist/policy/core/compose-decision.d.ts +28 -7
- package/dist/policy/core/compose-decision.js +75 -54
- package/dist/policy/core/decision-types.d.ts +65 -34
- package/dist/policy/core/feature-decision.d.ts +16 -9
- package/dist/policy/core/feature-decision.js +14 -8
- package/dist/policy/core/pnp-policy-inputs.d.ts +24 -33
- package/dist/policy/core/pnp-policy-inputs.js +28 -56
- package/dist/policy/core/provenance.d.ts +9 -4
- package/dist/policy/core/provenance.js +1 -1
- package/dist/policy/engine.d.ts +7 -23
- package/dist/policy/engine.js +5 -20
- package/dist/policy/sources/PnpPolicySource.d.ts +44 -61
- package/dist/policy/sources/PnpPolicySource.js +117 -118
- package/dist/runtime/SectionControllerBinding.d.ts +279 -0
- package/dist/runtime/SectionControllerBinding.js +450 -0
- package/dist/runtime/SectionRuntimeEngine.d.ts +26 -289
- package/dist/runtime/SectionRuntimeEngine.js +30 -372
- package/dist/runtime/adapter/SectionEngineAdapter.d.ts +19 -80
- package/dist/runtime/adapter/SectionEngineAdapter.js +19 -92
- package/dist/runtime/adapter/dom-event-bridge.d.ts +15 -30
- package/dist/runtime/adapter/dom-event-bridge.js +17 -44
- package/dist/runtime/adapter/subscriber-fanout.d.ts +1 -2
- package/dist/runtime/adapter/subscriber-fanout.js +1 -2
- package/dist/runtime/catalog-context.d.ts +19 -0
- package/dist/runtime/catalog-context.js +28 -0
- package/dist/runtime/composition-emit-scheduler.d.ts +9 -15
- package/dist/runtime/composition-emit-scheduler.js +9 -15
- package/dist/runtime/content-language.d.ts +32 -0
- package/dist/runtime/content-language.js +46 -0
- package/dist/runtime/content-region.d.ts +17 -0
- package/dist/runtime/content-region.js +29 -0
- package/dist/runtime/content-registration.d.ts +13 -0
- package/dist/runtime/content-registration.js +25 -0
- package/dist/runtime/core/SectionEngineCore.d.ts +2 -2
- package/dist/runtime/core/SectionEngineCore.js +2 -2
- package/dist/runtime/core/cohort.d.ts +1 -1
- package/dist/runtime/core/cohort.js +1 -1
- package/dist/runtime/core/engine-input.d.ts +18 -30
- package/dist/runtime/core/engine-input.js +11 -12
- package/dist/runtime/core/engine-output.d.ts +16 -40
- package/dist/runtime/core/engine-output.js +9 -21
- package/dist/runtime/core/engine-readiness.d.ts +5 -37
- package/dist/runtime/core/engine-readiness.js +8 -41
- package/dist/runtime/core/engine-resolver.d.ts +46 -74
- package/dist/runtime/core/engine-resolver.js +18 -43
- package/dist/runtime/core/engine-state.d.ts +28 -36
- package/dist/runtime/core/engine-state.js +18 -21
- package/dist/runtime/core/engine-transition.d.ts +14 -19
- package/dist/runtime/core/engine-transition.js +71 -51
- package/dist/runtime/engine.d.ts +16 -19
- package/dist/runtime/engine.js +12 -19
- package/dist/runtime/page-warnings.d.ts +13 -0
- package/dist/runtime/page-warnings.js +22 -0
- package/dist/runtime/policy-input-forwarding.d.ts +25 -0
- package/dist/runtime/policy-input-forwarding.js +25 -0
- package/dist/runtime/registration-events.d.ts +14 -0
- package/dist/runtime/runtime-event-claim.d.ts +8 -0
- package/dist/runtime/runtime-event-claim.js +15 -0
- package/dist/runtime/shell-event-bridge.d.ts +45 -0
- package/dist/runtime/shell-event-bridge.js +127 -0
- package/dist/runtime/shell-registration.d.ts +73 -0
- package/dist/runtime/shell-registration.js +95 -0
- package/dist/runtime/shell-scope.d.ts +68 -0
- package/dist/runtime/shell-scope.js +188 -0
- package/dist/runtime/tool-host-contract.d.ts +28 -10
- package/dist/runtime/tool-host-contract.js +31 -10
- package/dist/runtime/unclaimed-registration-watch.d.ts +7 -0
- package/dist/runtime/unclaimed-registration-watch.js +23 -0
- package/dist/services/ElementToolStateStore.d.ts +14 -10
- package/dist/services/ElementToolStateStore.js +37 -13
- package/dist/services/HighlightCoordinator.d.ts +22 -28
- package/dist/services/HighlightCoordinator.js +431 -327
- package/dist/services/RangeSerializer.d.ts +10 -1
- package/dist/services/RangeSerializer.js +87 -14
- package/dist/services/TTSService.d.ts +130 -69
- package/dist/services/TTSService.js +713 -570
- package/dist/services/ToolCoordinator.d.ts +43 -66
- package/dist/services/ToolCoordinator.js +164 -208
- package/dist/services/ToolRegistry.d.ts +78 -54
- package/dist/services/ToolRegistry.js +107 -105
- package/dist/services/ToolkitCoordinator.d.ts +261 -175
- package/dist/services/ToolkitCoordinator.js +667 -433
- package/dist/services/audio-handoff.d.ts +6 -7
- package/dist/services/audio-handoff.js +20 -14
- package/dist/services/catalog-owner.d.ts +11 -1
- package/dist/services/catalog-owner.js +25 -0
- package/dist/services/create-tools-config.d.ts +2 -2
- package/dist/services/framework-error-bus.d.ts +3 -6
- package/dist/services/framework-error-bus.js +3 -6
- package/dist/services/framework-error.d.ts +30 -8
- package/dist/services/framework-error.js +9 -7
- package/dist/services/interfaces.d.ts +158 -129
- package/dist/services/section-controller-types.d.ts +15 -0
- package/dist/services/section-session-assignment.d.ts +21 -0
- package/dist/services/section-session-assignment.js +82 -0
- package/dist/services/spoken-audio-cards.d.ts +0 -2
- package/dist/services/spoken-audio-cards.js +1 -3
- package/dist/services/tool-config-defaults.d.ts +3 -3
- package/dist/services/tool-config-defaults.js +3 -3
- package/dist/services/tool-config-validation.d.ts +14 -4
- package/dist/services/tool-config-validation.js +63 -71
- package/dist/services/tool-context.d.ts +0 -8
- package/dist/services/tool-context.js +29 -25
- package/dist/services/tool-icons.d.ts +4 -3
- package/dist/services/tool-icons.js +4 -3
- package/dist/services/tool-instance-id.d.ts +0 -1
- package/dist/services/tool-instance-id.js +0 -6
- package/dist/services/tool-providers/TTSToolProvider.d.ts +41 -80
- package/dist/services/tool-providers/TTSToolProvider.js +81 -40
- package/dist/services/tool-providers/ToolProviderApi.d.ts +10 -4
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts +32 -28
- package/dist/services/tool-providers/ToolProviderRegistry.js +121 -107
- package/dist/services/tool-providers/index.d.ts +0 -9
- package/dist/services/tool-providers/index.js +2 -4
- package/dist/services/tool-request.d.ts +27 -12
- package/dist/services/tool-request.js +24 -14
- package/dist/services/toolbar-decided-tools.d.ts +15 -0
- package/dist/services/toolbar-decided-tools.js +28 -0
- package/dist/services/tools-config-normalizer.d.ts +37 -23
- package/dist/services/tools-config-normalizer.js +2 -10
- package/dist/services/tts/browser-provider.d.ts +38 -2
- package/dist/services/tts/browser-provider.js +276 -140
- package/dist/services/tts/catalog-span-alignment.d.ts +2 -17
- package/dist/services/tts/catalog-span-alignment.js +17 -142
- package/dist/services/tts/flat-tree.d.ts +71 -0
- package/dist/services/tts/flat-tree.js +269 -0
- package/dist/services/tts/generated-speech/assemble-plan.d.ts +3 -1
- package/dist/services/tts/generated-speech/assemble-plan.js +3 -1
- package/dist/services/tts/generated-speech/dom/build-dom-plan.d.ts +2 -0
- package/dist/services/tts/generated-speech/dom/build-dom-plan.js +1 -1
- package/dist/services/tts/generated-speech/math-speech-cache.d.ts +3 -1
- package/dist/services/tts/generated-speech/math-speech-cache.js +5 -1
- package/dist/services/tts/generated-speech/types.d.ts +0 -12
- package/dist/services/tts/highlight-pipeline/boundary-normalizer.js +12 -62
- package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +38 -5
- package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js +3 -6
- package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js +6 -4
- package/dist/services/tts/highlight-pipeline/readable-region.js +3 -2
- package/dist/services/tts/highlight-pipeline/types.d.ts +5 -0
- package/dist/services/tts/highlight-pipeline/visible-map-range.d.ts +12 -0
- package/dist/services/tts/highlight-pipeline/visible-map-range.js +73 -7
- package/dist/services/tts/math-alignment/index.d.ts +0 -5
- package/dist/services/tts/math-alignment/index.js +5 -25
- package/dist/services/tts/math-alignment/speech-tokenizer.d.ts +9 -1
- package/dist/services/tts/math-alignment/speech-tokenizer.js +38 -36
- package/dist/services/tts/math-alignment/types.d.ts +4 -2
- package/dist/services/tts/math-aware-text-processing.d.ts +5 -1
- package/dist/services/tts/math-aware-text-processing.js +74 -18
- package/dist/services/tts/math-control-names.d.ts +21 -0
- package/dist/services/tts/math-control-names.js +142 -0
- package/dist/services/tts/math-speech.d.ts +16 -1
- package/dist/services/tts/math-speech.js +55 -19
- package/dist/services/tts/provider-options.d.ts +41 -0
- package/dist/services/tts/provider-options.js +1 -0
- package/dist/services/tts/sre-engine.d.ts +7 -0
- package/dist/services/tts/sre-engine.js +11 -0
- package/dist/services/tts/sre-locales.d.ts +14 -0
- package/dist/services/tts/sre-locales.js +57 -0
- package/dist/services/tts/ssml/spoken-text.d.ts +2 -26
- package/dist/services/tts/ssml/spoken-text.js +29 -0
- package/dist/services/tts/start-failure.d.ts +11 -0
- package/dist/services/tts/start-failure.js +19 -0
- package/dist/services/tts/text-processing.d.ts +9 -1
- package/dist/services/tts/text-processing.js +46 -34
- package/dist/services/tts/text-segmentation.d.ts +7 -0
- package/dist/services/tts/text-segmentation.js +47 -0
- package/dist/services/tts-runtime-config.d.ts +44 -18
- package/dist/services/tts-runtime-config.js +22 -93
- package/dist/tools/content-capability-resolution.d.ts +7 -9
- package/dist/tools/content-capability-resolution.js +8 -45
- package/dist/tools/registration.d.ts +58 -0
- package/dist/tools/registration.js +54 -0
- package/dist/tools/registrations/toolbar-registration-helpers.js +2 -2
- package/dist/tools/tool-surface-host.d.ts +6 -10
- package/dist/tools/tool-surface-host.js +44 -42
- package/dist/tools/tool-tag-map.d.ts +5 -15
- package/dist/tools/tool-tag-map.js +6 -20
- package/dist/utils/structural-equality.d.ts +11 -0
- package/dist/utils/structural-equality.js +47 -0
- package/package.json +24 -59
- package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts +0 -49
- package/dist/attempt/adapters/activity-to-test-attempt-session.js +0 -138
- package/dist/components/chunks/ItemToolBar-byq80z42.js +0 -40
- package/dist/components/chunks/ItemToolBar-d36g3aw4.js +0 -49
- package/dist/context/runtime-context-consumer.d.ts +0 -13
- package/dist/context/runtime-context-consumer.js +0 -51
- package/dist/item-loader.d.ts +0 -44
- package/dist/item-loader.js +0 -61
- package/dist/policy/core/engine-context.d.ts +0 -29
- package/dist/policy/core/engine-context.js +0 -27
- package/dist/policy/core/policy-source-tag.d.ts +0 -31
- package/dist/policy/core/policy-source-tag.js +0 -28
- package/dist/policy/internal.d.ts +0 -25
- package/dist/policy/internal.js +0 -25
- package/dist/runtime/adapter/coordinator-bridge.d.ts +0 -92
- package/dist/runtime/adapter/coordinator-bridge.js +0 -126
- package/dist/runtime/adapter/framework-error-bridge.d.ts +0 -36
- package/dist/runtime/adapter/framework-error-bridge.js +0 -46
- package/dist/runtime/adapter/instrumentation-bridge.d.ts +0 -31
- package/dist/runtime/adapter/instrumentation-bridge.js +0 -38
- package/dist/runtime/core/engine-stage-derivation.d.ts +0 -18
- package/dist/runtime/core/engine-stage-derivation.js +0 -29
- package/dist/runtime/engine-context.d.ts +0 -39
- package/dist/runtime/engine-context.js +0 -31
- package/dist/runtime/internal.d.ts +0 -43
- package/dist/runtime/internal.js +0 -45
- package/dist/runtime/section-runtime-engine-host-context.d.ts +0 -80
- package/dist/runtime/section-runtime-engine-host-context.js +0 -95
- package/dist/runtime/stage-emit-gate.d.ts +0 -92
- package/dist/runtime/stage-emit-gate.js +0 -82
- package/dist/services/I18nService.d.ts +0 -47
- package/dist/services/I18nService.js +0 -84
- package/dist/services/ThemeProvider.d.ts +0 -95
- package/dist/services/ThemeProvider.js +0 -334
- package/dist/services/ToolConfigResolver.d.ts +0 -161
- package/dist/services/ToolConfigResolver.js +0 -229
- package/dist/services/catalog-media.d.ts +0 -56
- package/dist/services/catalog-media.js +0 -190
- package/dist/services/createDefaultToolRegistry.d.ts +0 -53
- package/dist/services/createDefaultToolRegistry.js +0 -45
- package/dist/services/defaultPersonalNeedsProfile.d.ts +0 -21
- package/dist/services/defaultPersonalNeedsProfile.js +0 -26
- package/dist/services/pnp-standard-features.d.ts +0 -238
- package/dist/services/pnp-standard-features.js +0 -338
- package/dist/services/tool-providers/CortexToolProvider.d.ts +0 -13
- package/dist/services/tool-providers/CortexToolProvider.js +0 -32
- package/dist/services/tool-providers/DesmosToolProvider.d.ts +0 -14
- package/dist/services/tool-providers/DesmosToolProvider.js +0 -32
- package/dist/services/tool-providers/GeoGebraToolProvider.d.ts +0 -16
- package/dist/services/tool-providers/GeoGebraToolProvider.js +0 -32
- package/dist/services/tool-providers/LazyCalculatorToolProvider.d.ts +0 -35
- package/dist/services/tool-providers/LazyCalculatorToolProvider.js +0 -95
- package/dist/services/tts/visual-line-ranges.d.ts +0 -18
- package/dist/services/tts/visual-line-ranges.js +0 -226
- package/dist/tools/client.d.ts +0 -12
- package/dist/tools/client.js +0 -14
- package/dist/tools/index.d.ts +0 -8
- package/dist/tools/index.js +0 -10
- package/dist/tools/internal.d.ts +0 -43
- package/dist/tools/internal.js +0 -41
- package/dist/tools/response-discovery.d.ts +0 -61
- package/dist/tools/response-discovery.js +0 -113
- package/dist/tools/types.d.ts +0 -337
- package/dist/tools/types.js +0 -12
- package/dist/tools/variant-resolver.d.ts +0 -41
- package/dist/tools/variant-resolver.js +0 -207
|
@@ -6,9 +6,81 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Part of PIE Assessment Toolkit.
|
|
8
8
|
*/
|
|
9
|
+
import { createPieLogger, isTtsDebugEnabled, } from "@pie-players/pie-players-shared/pie";
|
|
10
|
+
import { extractSpokenText, isSsmlDocument } from "./ssml/spoken-text.js";
|
|
9
11
|
import { segmentSentences as segmentTextToSentences } from "./text-segmentation.js";
|
|
12
|
+
const logger = createPieLogger("browser-tts-provider", isTtsDebugEnabled);
|
|
10
13
|
const NATIVE_START_TIMEOUT_MS = 5_000;
|
|
11
14
|
const VOICE_INVENTORY_TIMEOUT_MS = 2_000;
|
|
15
|
+
// The provider options the toolkit and this provider read. Every other option,
|
|
16
|
+
// and `voice`, belongs to the provider it was configured for: a Polly voice such
|
|
17
|
+
// as "Joanna" resolves to no browser voice, and an unresolved voice fails speak.
|
|
18
|
+
const PORTABLE_PROVIDER_OPTIONS = [
|
|
19
|
+
"__pieTelemetry",
|
|
20
|
+
"contentLanguage",
|
|
21
|
+
"highlightMode",
|
|
22
|
+
"locale",
|
|
23
|
+
"mathSpeech",
|
|
24
|
+
"segmenter",
|
|
25
|
+
"structuralPauses",
|
|
26
|
+
"textNormalization",
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* The part of another provider's configuration that keeps its meaning on
|
|
30
|
+
* browser speech. Every path that replaces a provider with browser speech
|
|
31
|
+
* starts it with this.
|
|
32
|
+
*/
|
|
33
|
+
export const browserFallbackConfig = (config) => {
|
|
34
|
+
const fallback = {};
|
|
35
|
+
if (config.rate !== undefined)
|
|
36
|
+
fallback.rate = config.rate;
|
|
37
|
+
if (config.pitch !== undefined)
|
|
38
|
+
fallback.pitch = config.pitch;
|
|
39
|
+
if (config.mathTokenHighlighting !== undefined) {
|
|
40
|
+
fallback.mathTokenHighlighting = config.mathTokenHighlighting;
|
|
41
|
+
}
|
|
42
|
+
const options = config.providerOptions;
|
|
43
|
+
if (options && typeof options === "object") {
|
|
44
|
+
const portable = {};
|
|
45
|
+
for (const key of PORTABLE_PROVIDER_OPTIONS) {
|
|
46
|
+
if (options[key] !== undefined)
|
|
47
|
+
portable[key] = options[key];
|
|
48
|
+
}
|
|
49
|
+
if (Object.keys(portable).length > 0)
|
|
50
|
+
fallback.providerOptions = portable;
|
|
51
|
+
}
|
|
52
|
+
return fallback;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* The Web Speech API reads SSML tags aloud, so a `<speak>` document is voiced
|
|
56
|
+
* as its spoken text. Boundary offsets still index the text as sent: that is
|
|
57
|
+
* the space the highlight pipeline resolves a raw-SSML chunk in.
|
|
58
|
+
*/
|
|
59
|
+
const toUtteranceText = (text) => {
|
|
60
|
+
if (!isSsmlDocument(text)) {
|
|
61
|
+
return { text, sourceOffset: (offset) => offset };
|
|
62
|
+
}
|
|
63
|
+
const { spokenText, rawToSpokenOffsetMap } = extractSpokenText(text);
|
|
64
|
+
const spokenToRaw = [];
|
|
65
|
+
for (const [raw, spoken] of rawToSpokenOffsetMap) {
|
|
66
|
+
const known = spokenToRaw[spoken];
|
|
67
|
+
if (known === undefined || raw < known)
|
|
68
|
+
spokenToRaw[spoken] = raw;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
text: spokenText,
|
|
72
|
+
// Alias text (`<sub alias>`) has no raw offset; it takes the nearest
|
|
73
|
+
// preceding one.
|
|
74
|
+
sourceOffset: (offset) => {
|
|
75
|
+
for (let index = offset; index >= 0; index--) {
|
|
76
|
+
const raw = spokenToRaw[index];
|
|
77
|
+
if (raw !== undefined)
|
|
78
|
+
return raw;
|
|
79
|
+
}
|
|
80
|
+
return 0;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
};
|
|
12
84
|
const normalizeLanguageCode = (value) => String(value || "")
|
|
13
85
|
.trim()
|
|
14
86
|
.toLowerCase();
|
|
@@ -18,19 +90,28 @@ const browserLanguage = () => {
|
|
|
18
90
|
: "";
|
|
19
91
|
return normalizeLanguageCode(navigatorLanguage || "en-US");
|
|
20
92
|
};
|
|
21
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Whether a voice speaks `contentLanguage`, or the browser's own language when
|
|
95
|
+
* the content names none: the same language, or the same primary subtag.
|
|
96
|
+
*/
|
|
97
|
+
export const browserVoiceMatchesLanguage = (voice, contentLanguage) => {
|
|
98
|
+
const language = normalizeLanguageCode(contentLanguage) || browserLanguage();
|
|
99
|
+
const languagePrefix = language.split("-")[0] || "en";
|
|
100
|
+
const voiceLanguage = normalizeLanguageCode(voice.lang);
|
|
101
|
+
return (voiceLanguage === language || voiceLanguage.startsWith(`${languagePrefix}-`));
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* The voice to read with: the configured one, else one for `contentLanguage`
|
|
105
|
+
* (the language of the content being read), else one for the browser's own
|
|
106
|
+
* language.
|
|
107
|
+
*/
|
|
108
|
+
export const findBrowserVoice = (voices, preferredVoice, contentLanguage) => {
|
|
22
109
|
if (preferredVoice) {
|
|
23
110
|
return (voices.find((voice) => voice.voiceURI === preferredVoice) ||
|
|
24
111
|
voices.find((voice) => voice.name === preferredVoice) ||
|
|
25
112
|
null);
|
|
26
113
|
}
|
|
27
|
-
const
|
|
28
|
-
const languagePrefix = language.split("-")[0] || "en";
|
|
29
|
-
const matchesLanguage = (voice) => {
|
|
30
|
-
const voiceLanguage = normalizeLanguageCode(voice.lang);
|
|
31
|
-
return (voiceLanguage === language ||
|
|
32
|
-
voiceLanguage.startsWith(`${languagePrefix}-`));
|
|
33
|
-
};
|
|
114
|
+
const matchesLanguage = (voice) => browserVoiceMatchesLanguage(voice, contentLanguage);
|
|
34
115
|
const ranked = [
|
|
35
116
|
(voice) => voice.localService && matchesLanguage(voice),
|
|
36
117
|
(voice) => voice.default && matchesLanguage(voice),
|
|
@@ -45,6 +126,64 @@ const findBrowserVoice = (voices, preferredVoice) => {
|
|
|
45
126
|
}
|
|
46
127
|
return voices[0] || null;
|
|
47
128
|
};
|
|
129
|
+
/**
|
|
130
|
+
* Waits for `synth` to publish its voice inventory, which browsers fill
|
|
131
|
+
* asynchronously after page load. Resolves `true` once `getVoices()` is
|
|
132
|
+
* non-empty, at once when it already is; `false` after `timeoutMs`, once
|
|
133
|
+
* `isCurrent` reports the wait superseded, or when `synth` offers no
|
|
134
|
+
* `voiceschanged` channel to wait on. A previous `onvoiceschanged` handler
|
|
135
|
+
* keeps running and is restored afterwards.
|
|
136
|
+
*/
|
|
137
|
+
export const waitForBrowserVoices = (synth, timeoutMs, isCurrent = () => true) => {
|
|
138
|
+
if (synth.getVoices().length > 0)
|
|
139
|
+
return Promise.resolve(true);
|
|
140
|
+
const canUseEventTarget = typeof synth.addEventListener === "function" &&
|
|
141
|
+
typeof synth.removeEventListener === "function";
|
|
142
|
+
if (!canUseEventTarget && !("onvoiceschanged" in synth)) {
|
|
143
|
+
return Promise.resolve(false);
|
|
144
|
+
}
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
const previousHandler = canUseEventTarget ? null : synth.onvoiceschanged;
|
|
147
|
+
let settled = false;
|
|
148
|
+
const finish = (voicesAvailable) => {
|
|
149
|
+
if (settled)
|
|
150
|
+
return;
|
|
151
|
+
settled = true;
|
|
152
|
+
clearTimeout(timeout);
|
|
153
|
+
if (canUseEventTarget) {
|
|
154
|
+
synth.removeEventListener("voiceschanged", onVoicesChanged);
|
|
155
|
+
}
|
|
156
|
+
else if (synth.onvoiceschanged === propertyHandler) {
|
|
157
|
+
synth.onvoiceschanged = previousHandler;
|
|
158
|
+
}
|
|
159
|
+
resolve(voicesAvailable);
|
|
160
|
+
};
|
|
161
|
+
const inspectVoiceInventory = () => {
|
|
162
|
+
if (!isCurrent())
|
|
163
|
+
finish(false);
|
|
164
|
+
else if (synth.getVoices().length > 0)
|
|
165
|
+
finish(true);
|
|
166
|
+
};
|
|
167
|
+
const onVoicesChanged = () => inspectVoiceInventory();
|
|
168
|
+
const propertyHandler = (event) => {
|
|
169
|
+
try {
|
|
170
|
+
previousHandler?.call(synth, event);
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
inspectVoiceInventory();
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
const timeout = setTimeout(() => finish(false), timeoutMs);
|
|
177
|
+
if (canUseEventTarget) {
|
|
178
|
+
synth.addEventListener("voiceschanged", onVoicesChanged);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
synth.onvoiceschanged = propertyHandler;
|
|
182
|
+
}
|
|
183
|
+
// Close the getVoices()/listener-registration race without polling.
|
|
184
|
+
inspectVoiceInventory();
|
|
185
|
+
});
|
|
186
|
+
};
|
|
48
187
|
const shouldAssignBrowserVoice = (voice) => !voice.default;
|
|
49
188
|
/**
|
|
50
189
|
* Browser TTS Provider
|
|
@@ -64,17 +203,20 @@ export class BrowserTTSProvider {
|
|
|
64
203
|
if (!("speechSynthesis" in window)) {
|
|
65
204
|
throw new Error("Browser does not support Speech Synthesis API");
|
|
66
205
|
}
|
|
206
|
+
// Only a configured voice needs the inventory; waiting otherwise would
|
|
207
|
+
// hold readiness on a browser that publishes no voices.
|
|
208
|
+
if (typeof config.voice === "string" && config.voice.trim()) {
|
|
209
|
+
await waitForBrowserVoices(window.speechSynthesis, VOICE_INVENTORY_TIMEOUT_MS);
|
|
210
|
+
}
|
|
67
211
|
return new BrowserTTSProviderImpl(config);
|
|
68
212
|
}
|
|
69
|
-
supportsFeature(feature) {
|
|
70
|
-
// Browser supports all TTS features
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
213
|
getCapabilities() {
|
|
74
214
|
return {
|
|
75
215
|
supportsPause: true,
|
|
76
216
|
supportsResume: true,
|
|
77
217
|
supportsWordBoundary: true,
|
|
218
|
+
// Boundary events depend on the voice: several network voices send none.
|
|
219
|
+
defaultHighlightMode: "sentence",
|
|
78
220
|
supportsVoiceSelection: true,
|
|
79
221
|
supportsRateControl: true,
|
|
80
222
|
supportsPitchControl: true,
|
|
@@ -95,99 +237,45 @@ class BrowserTTSProviderImpl {
|
|
|
95
237
|
utterance = null;
|
|
96
238
|
config = null;
|
|
97
239
|
_isPlaying = false;
|
|
240
|
+
// The run's pause, which outlasts the utterance it paused: a pause between
|
|
241
|
+
// utterances holds the next one until resume.
|
|
98
242
|
_isPaused = false;
|
|
99
243
|
speakRunId = 0;
|
|
100
|
-
|
|
244
|
+
// The run speaking now, between its utterances as well as during them.
|
|
245
|
+
activeRunId = null;
|
|
246
|
+
releasePauseHold = null;
|
|
101
247
|
settlePendingChunk = null;
|
|
248
|
+
settlePendingGap = null;
|
|
102
249
|
onPlaybackStart = undefined;
|
|
103
250
|
constructor(config) {
|
|
104
251
|
this.config = config;
|
|
105
252
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
if (canUseEventTarget) {
|
|
121
|
-
synth.removeEventListener("voiceschanged", onVoicesChanged);
|
|
122
|
-
}
|
|
123
|
-
else if (synth.onvoiceschanged === propertyHandler) {
|
|
124
|
-
synth.onvoiceschanged = previousHandler;
|
|
125
|
-
}
|
|
126
|
-
if (pendingSettlement &&
|
|
127
|
-
this.settlePendingVoiceWait === pendingSettlement) {
|
|
128
|
-
this.settlePendingVoiceWait = null;
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
const finish = (voicesAvailable, error) => {
|
|
132
|
-
if (settled)
|
|
133
|
-
return;
|
|
134
|
-
settled = true;
|
|
135
|
-
cleanup();
|
|
136
|
-
if (error) {
|
|
137
|
-
reject(error);
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
resolve(voicesAvailable);
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
const inspectVoiceInventory = () => {
|
|
144
|
-
if (runId !== this.speakRunId) {
|
|
145
|
-
finish(false);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
if (synth.getVoices().length > 0)
|
|
149
|
-
finish(true);
|
|
150
|
-
};
|
|
151
|
-
const onVoicesChanged = () => inspectVoiceInventory();
|
|
152
|
-
const propertyHandler = (event) => {
|
|
153
|
-
try {
|
|
154
|
-
previousHandler?.call(synth, event);
|
|
155
|
-
}
|
|
156
|
-
finally {
|
|
157
|
-
inspectVoiceInventory();
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
pendingSettlement = () => finish(false);
|
|
161
|
-
this.settlePendingVoiceWait = pendingSettlement;
|
|
162
|
-
timeout = setTimeout(() => finish(false, new Error(`Configured browser voice "${configuredVoice}" could not be resolved because the browser did not publish its voice inventory within ${VOICE_INVENTORY_TIMEOUT_MS / 1_000} seconds.`)), VOICE_INVENTORY_TIMEOUT_MS);
|
|
163
|
-
if (canUseEventTarget) {
|
|
164
|
-
synth.addEventListener("voiceschanged", onVoicesChanged);
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
synth.onvoiceschanged = propertyHandler;
|
|
168
|
-
}
|
|
169
|
-
// Close the getVoices()/listener-registration race without polling.
|
|
170
|
-
inspectVoiceInventory();
|
|
171
|
-
});
|
|
253
|
+
/** Whether run `runId` is still current once any pause between utterances ends. */
|
|
254
|
+
async waitWhilePaused(runId) {
|
|
255
|
+
while (this._isPaused && runId === this.speakRunId) {
|
|
256
|
+
await new Promise((resolve) => {
|
|
257
|
+
this.releasePauseHold = resolve;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return runId === this.speakRunId;
|
|
261
|
+
}
|
|
262
|
+
releaseHold() {
|
|
263
|
+
const release = this.releasePauseHold;
|
|
264
|
+
this.releasePauseHold = null;
|
|
265
|
+
release?.();
|
|
172
266
|
}
|
|
173
|
-
|
|
267
|
+
/** The voice to read with; `initialize()` already waited for the inventory. */
|
|
268
|
+
resolveBrowserVoice() {
|
|
174
269
|
const configuredVoice = typeof this.config?.voice === "string" ? this.config.voice.trim() : "";
|
|
175
|
-
|
|
270
|
+
const voices = speechSynthesis.getVoices();
|
|
176
271
|
if (configuredVoice && voices.length === 0) {
|
|
177
|
-
|
|
178
|
-
if (!voicesAvailable || runId !== this.speakRunId) {
|
|
179
|
-
return { shouldContinue: false, voice: null };
|
|
180
|
-
}
|
|
181
|
-
voices = speechSynthesis.getVoices();
|
|
182
|
-
}
|
|
183
|
-
if (runId !== this.speakRunId) {
|
|
184
|
-
return { shouldContinue: false, voice: null };
|
|
272
|
+
throw new Error(`Configured browser voice "${configuredVoice}" could not be resolved because the browser did not publish its voice inventory within ${VOICE_INVENTORY_TIMEOUT_MS / 1_000} seconds.`);
|
|
185
273
|
}
|
|
186
|
-
const voice = findBrowserVoice(voices, configuredVoice || undefined);
|
|
274
|
+
const voice = findBrowserVoice(voices, configuredVoice || undefined, this.getContentLocale());
|
|
187
275
|
if (configuredVoice && !voice) {
|
|
188
276
|
throw new Error(`Configured browser voice "${configuredVoice}" is unavailable. Select a voice exposed by this browser using its voiceURI or name.`);
|
|
189
277
|
}
|
|
190
|
-
return
|
|
278
|
+
return voice;
|
|
191
279
|
}
|
|
192
280
|
async speak(text) {
|
|
193
281
|
if (!this.config) {
|
|
@@ -196,19 +284,24 @@ class BrowserTTSProviderImpl {
|
|
|
196
284
|
// Invalidate any in-flight run and cancel current utterance.
|
|
197
285
|
this.stop();
|
|
198
286
|
const runId = this.speakRunId;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
287
|
+
this.activeRunId = runId;
|
|
288
|
+
try {
|
|
289
|
+
const voice = this.resolveBrowserVoice();
|
|
290
|
+
const utterance = toUtteranceText(text);
|
|
291
|
+
const chunks = this.splitIntoChunks(utterance.text);
|
|
292
|
+
for (const chunk of chunks) {
|
|
293
|
+
if (this._isPaused && !(await this.waitWhilePaused(runId)))
|
|
294
|
+
break;
|
|
295
|
+
const shouldContinue = await this.speakChunk(chunk.text, chunk.offset, runId, voice, utterance.sourceOffset);
|
|
296
|
+
if (!shouldContinue) {
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
210
299
|
}
|
|
211
300
|
}
|
|
301
|
+
finally {
|
|
302
|
+
if (this.activeRunId === runId)
|
|
303
|
+
this.activeRunId = null;
|
|
304
|
+
}
|
|
212
305
|
}
|
|
213
306
|
async speakSegments(segments) {
|
|
214
307
|
if (!this.config) {
|
|
@@ -216,30 +309,45 @@ class BrowserTTSProviderImpl {
|
|
|
216
309
|
}
|
|
217
310
|
this.stop();
|
|
218
311
|
const runId = this.speakRunId;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (runId !== this.speakRunId)
|
|
224
|
-
break;
|
|
225
|
-
const chunks = this.splitIntoChunks(segment.text);
|
|
226
|
-
for (const chunk of chunks) {
|
|
312
|
+
this.activeRunId = runId;
|
|
313
|
+
try {
|
|
314
|
+
const voice = this.resolveBrowserVoice();
|
|
315
|
+
for (const segment of segments) {
|
|
227
316
|
if (runId !== this.speakRunId)
|
|
228
317
|
break;
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
318
|
+
const utterance = toUtteranceText(segment.text);
|
|
319
|
+
const chunks = this.splitIntoChunks(utterance.text);
|
|
320
|
+
for (const chunk of chunks) {
|
|
321
|
+
if (this._isPaused && !(await this.waitWhilePaused(runId)))
|
|
322
|
+
break;
|
|
323
|
+
const shouldContinue = await this.speakChunk(chunk.text, chunk.offset, runId, voice, (offset) => segment.startOffset + utterance.sourceOffset(offset));
|
|
324
|
+
if (!shouldContinue)
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
const pauseMsAfter = Math.max(0, Number(segment.pauseMsAfter || 0));
|
|
328
|
+
if (pauseMsAfter > 0 && runId === this.speakRunId) {
|
|
329
|
+
const shouldContinue = await this.waitForPause(pauseMsAfter, runId);
|
|
330
|
+
if (!shouldContinue)
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
238
333
|
}
|
|
239
334
|
}
|
|
335
|
+
finally {
|
|
336
|
+
if (this.activeRunId === runId)
|
|
337
|
+
this.activeRunId = null;
|
|
338
|
+
}
|
|
240
339
|
}
|
|
241
340
|
async waitForPause(pauseMs, runId) {
|
|
242
|
-
await new Promise((resolve) =>
|
|
341
|
+
await new Promise((resolve) => {
|
|
342
|
+
const settle = () => {
|
|
343
|
+
clearTimeout(timer);
|
|
344
|
+
if (this.settlePendingGap === settle)
|
|
345
|
+
this.settlePendingGap = null;
|
|
346
|
+
resolve();
|
|
347
|
+
};
|
|
348
|
+
const timer = setTimeout(settle, pauseMs);
|
|
349
|
+
this.settlePendingGap = settle;
|
|
350
|
+
});
|
|
243
351
|
return runId === this.speakRunId;
|
|
244
352
|
}
|
|
245
353
|
splitIntoChunks(text) {
|
|
@@ -277,13 +385,19 @@ class BrowserTTSProviderImpl {
|
|
|
277
385
|
}
|
|
278
386
|
return chunks.length ? chunks : [{ text, offset: 0 }];
|
|
279
387
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
388
|
+
/**
|
|
389
|
+
* The language of the content being read, which the toolkit sets per speak as
|
|
390
|
+
* `providerOptions.contentLanguage` when the content or the host names one.
|
|
391
|
+
*/
|
|
392
|
+
getContentLocale() {
|
|
393
|
+
const language = this.config?.providerOptions?.contentLanguage;
|
|
394
|
+
return typeof language === "string" && language.trim()
|
|
395
|
+
? language.trim()
|
|
396
|
+
: undefined;
|
|
283
397
|
}
|
|
284
398
|
getSegmentationPolicy() {
|
|
285
|
-
const providerOptions =
|
|
286
|
-
const segmenter =
|
|
399
|
+
const providerOptions = this.config?.providerOptions || {};
|
|
400
|
+
const segmenter = providerOptions.segmenter || {};
|
|
287
401
|
const mode = segmenter.mode;
|
|
288
402
|
const useSegmenter = mode !== "regexOnly";
|
|
289
403
|
const locale = typeof segmenter.locale === "string" && segmenter.locale.trim().length > 0
|
|
@@ -311,7 +425,11 @@ class BrowserTTSProviderImpl {
|
|
|
311
425
|
const match = slice.match(/^\s*([^\s]+)/);
|
|
312
426
|
return match?.[1]?.length || 1;
|
|
313
427
|
}
|
|
314
|
-
|
|
428
|
+
/**
|
|
429
|
+
* `chunkOffset` places the chunk in the utterance text; `sourceOffset` maps an
|
|
430
|
+
* utterance offset to the boundary position reported for it.
|
|
431
|
+
*/
|
|
432
|
+
async speakChunk(chunkText, chunkOffset, runId, voice, sourceOffset) {
|
|
315
433
|
return new Promise((resolve, reject) => {
|
|
316
434
|
if (runId !== this.speakRunId) {
|
|
317
435
|
resolve(false);
|
|
@@ -319,7 +437,11 @@ class BrowserTTSProviderImpl {
|
|
|
319
437
|
}
|
|
320
438
|
const utterance = new SpeechSynthesisUtterance(chunkText);
|
|
321
439
|
this.utterance = utterance;
|
|
322
|
-
// Apply config
|
|
440
|
+
// Apply config. The language tells the engine how to read the text, and
|
|
441
|
+
// lets it choose a voice when the default one is left in place.
|
|
442
|
+
const contentLocale = this.getContentLocale();
|
|
443
|
+
if (contentLocale)
|
|
444
|
+
utterance.lang = contentLocale;
|
|
323
445
|
if (voice && shouldAssignBrowserVoice(voice))
|
|
324
446
|
utterance.voice = voice;
|
|
325
447
|
if (this.config?.rate)
|
|
@@ -362,7 +484,6 @@ class BrowserTTSProviderImpl {
|
|
|
362
484
|
}
|
|
363
485
|
if (runId === this.speakRunId) {
|
|
364
486
|
this._isPlaying = false;
|
|
365
|
-
this._isPaused = false;
|
|
366
487
|
}
|
|
367
488
|
if (error) {
|
|
368
489
|
reject(error);
|
|
@@ -384,7 +505,9 @@ class BrowserTTSProviderImpl {
|
|
|
384
505
|
didStart = true;
|
|
385
506
|
clearStartTimeout();
|
|
386
507
|
this._isPlaying = true;
|
|
387
|
-
this.
|
|
508
|
+
// A pause that landed while the engine was starting holds this utterance.
|
|
509
|
+
if (this._isPaused)
|
|
510
|
+
speechSynthesis.pause();
|
|
388
511
|
try {
|
|
389
512
|
this.onPlaybackStart?.();
|
|
390
513
|
}
|
|
@@ -427,12 +550,9 @@ class BrowserTTSProviderImpl {
|
|
|
427
550
|
utterance.onboundary = (event) => {
|
|
428
551
|
if (runId !== this.speakRunId)
|
|
429
552
|
return;
|
|
430
|
-
|
|
553
|
+
logger.debug(`boundary event: ${event.name}, charIndex ${event.charIndex}, charLength ${event.charLength}`);
|
|
431
554
|
if (event.name !== "word" || !this.onWordBoundary)
|
|
432
555
|
return;
|
|
433
|
-
if (this.getHighlightMode() === "sentence") {
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
556
|
const charIndex = Math.max(0, Math.min(event.charIndex, Math.max(0, chunkText.length - 1)));
|
|
437
557
|
const reportedLength = Number(event.charLength || 0);
|
|
438
558
|
const inferredLength = this.inferWordLength(chunkText, charIndex);
|
|
@@ -445,9 +565,13 @@ class BrowserTTSProviderImpl {
|
|
|
445
565
|
const word = chunkText
|
|
446
566
|
.substring(charIndex, Math.min(chunkText.length, charIndex + wordLength))
|
|
447
567
|
.trim();
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
568
|
+
const utteranceStart = chunkOffset + charIndex;
|
|
569
|
+
const absoluteBoundaryStart = sourceOffset(utteranceStart);
|
|
570
|
+
const boundaryLength = sourceOffset(utteranceStart + wordLength - 1) +
|
|
571
|
+
1 -
|
|
572
|
+
absoluteBoundaryStart;
|
|
573
|
+
logger.debug(`word boundary "${word}" at ${absoluteBoundaryStart}`);
|
|
574
|
+
this.onWordBoundary(word, absoluteBoundaryStart, boundaryLength);
|
|
451
575
|
};
|
|
452
576
|
startTimeout = setTimeout(() => {
|
|
453
577
|
if (settled || didStart || runId !== this.speakRunId)
|
|
@@ -466,27 +590,39 @@ class BrowserTTSProviderImpl {
|
|
|
466
590
|
});
|
|
467
591
|
}
|
|
468
592
|
pause() {
|
|
469
|
-
if (this.
|
|
593
|
+
if (this._isPaused)
|
|
594
|
+
return;
|
|
595
|
+
if (this._isPlaying) {
|
|
470
596
|
speechSynthesis.pause();
|
|
597
|
+
this._isPaused = true;
|
|
598
|
+
}
|
|
599
|
+
else if (this.activeRunId === this.speakRunId) {
|
|
600
|
+
// Between utterances, or in a structural gap: the next one holds.
|
|
601
|
+
this._isPaused = true;
|
|
471
602
|
}
|
|
472
603
|
}
|
|
473
604
|
resume() {
|
|
474
|
-
if (this.
|
|
605
|
+
if (!this._isPaused)
|
|
606
|
+
return;
|
|
607
|
+
this._isPaused = false;
|
|
608
|
+
if (this._isPlaying)
|
|
475
609
|
speechSynthesis.resume();
|
|
476
|
-
|
|
610
|
+
this.releaseHold();
|
|
477
611
|
}
|
|
478
612
|
stop() {
|
|
479
613
|
const utterance = this.utterance;
|
|
480
614
|
const shouldCancel = this._isPlaying || utterance !== null;
|
|
481
615
|
this.speakRunId += 1;
|
|
482
|
-
this.
|
|
616
|
+
this.activeRunId = null;
|
|
483
617
|
this.settlePendingChunk?.();
|
|
618
|
+
this.settlePendingGap?.();
|
|
484
619
|
if (shouldCancel) {
|
|
485
620
|
speechSynthesis.cancel();
|
|
486
621
|
}
|
|
487
622
|
this.utterance = null;
|
|
488
623
|
this._isPlaying = false;
|
|
489
624
|
this._isPaused = false;
|
|
625
|
+
this.releaseHold();
|
|
490
626
|
}
|
|
491
627
|
isPlaying() {
|
|
492
628
|
return this._isPlaying && !this._isPaused;
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
+
import { type SpeechSourceTokenization } from "./math-alignment/speech-tokenizer.js";
|
|
1
2
|
export type CatalogChunkPlaybackMode = "exact-word" | "anchor-span" | "region-fallback";
|
|
2
|
-
export type BoundaryOffsetMode = "plain-spoken-text" | "raw-ssml" | "unsupported";
|
|
3
|
-
export interface CatalogTextToken {
|
|
4
|
-
text: string;
|
|
5
|
-
normalized: string;
|
|
6
|
-
start: number;
|
|
7
|
-
end: number;
|
|
8
|
-
}
|
|
9
3
|
export interface CatalogSpanAnchor {
|
|
10
4
|
spokenStart: number;
|
|
11
5
|
spokenEnd: number;
|
|
@@ -14,25 +8,16 @@ export interface CatalogSpanAnchor {
|
|
|
14
8
|
score: number;
|
|
15
9
|
}
|
|
16
10
|
export interface CatalogSpanAlignment {
|
|
17
|
-
|
|
18
|
-
spokenText: string;
|
|
11
|
+
speech: SpeechSourceTokenization;
|
|
19
12
|
visibleText: string;
|
|
20
13
|
playbackMode: CatalogChunkPlaybackMode;
|
|
21
|
-
boundaryOffsetMode: BoundaryOffsetMode;
|
|
22
14
|
anchors: CatalogSpanAnchor[];
|
|
23
15
|
confidence: number;
|
|
24
|
-
spokenTokens: CatalogTextToken[];
|
|
25
|
-
visibleTokens: CatalogTextToken[];
|
|
26
|
-
rawToSpokenOffsetMap: Map<number, number>;
|
|
27
16
|
}
|
|
28
17
|
export declare const createCatalogSpanAlignment: (args: {
|
|
29
18
|
speechText: string;
|
|
30
19
|
visibleText: string;
|
|
31
20
|
}) => CatalogSpanAlignment;
|
|
32
|
-
export declare const resolveSpokenBoundaryOffset: (alignment: CatalogSpanAlignment, position: number, length?: number, boundaryWord?: string) => {
|
|
33
|
-
start: number;
|
|
34
|
-
length: number;
|
|
35
|
-
} | null;
|
|
36
21
|
export declare const resolveVisibleSpanForBoundary: (alignment: CatalogSpanAlignment, spokenOffset: number) => {
|
|
37
22
|
start: number;
|
|
38
23
|
end: number;
|