@pie-players/pie-assessment-toolkit 0.3.50 → 0.3.52
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 +25 -0
- package/dist/components/ItemToolBar.custom-element.js +98 -14
- package/dist/components/PieAssessmentToolkit.custom-element.js +293 -48
- package/dist/components/SectionToolBar.custom-element.js +98 -14
- package/dist/context/assessment-toolkit-context.d.ts +2 -0
- package/dist/context/assessment-toolkit-context.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/services/HighlightCoordinator.d.ts +6 -0
- package/dist/services/HighlightCoordinator.js +20 -5
- package/dist/services/HighlightCoordinator.js.map +1 -1
- package/dist/services/TTSService.d.ts +19 -0
- package/dist/services/TTSService.js +238 -35
- package/dist/services/TTSService.js.map +1 -1
- package/dist/services/ToolkitCoordinator.d.ts +3 -2
- package/dist/services/ToolkitCoordinator.js.map +1 -1
- package/dist/services/createDefaultToolRegistry.d.ts +2 -1
- package/dist/services/createDefaultToolRegistry.js +2 -1
- package/dist/services/createDefaultToolRegistry.js.map +1 -1
- package/dist/services/interfaces.d.ts +15 -0
- package/dist/services/interfaces.js.map +1 -1
- package/dist/services/tool-config-defaults.d.ts +11 -0
- package/dist/services/tool-config-defaults.js +18 -0
- package/dist/services/tool-config-defaults.js.map +1 -1
- package/dist/services/tts/highlight-target-resolver.d.ts +17 -0
- package/dist/services/tts/highlight-target-resolver.js +2 -0
- package/dist/services/tts/highlight-target-resolver.js.map +1 -0
- package/dist/services/tts-runtime-config.d.ts +15 -1
- package/dist/services/tts-runtime-config.js +76 -10
- package/dist/services/tts-runtime-config.js.map +1 -1
- package/dist/tools/registrations/tts.js +6 -8
- package/dist/tools/registrations/tts.js.map +1 -1
- package/package.json +7 -7
|
@@ -18971,22 +18971,81 @@ var isServerBackend = (backend) => backend === "polly" || backend === "google" |
|
|
|
18971
18971
|
var withDefault = (value, fallback2) => value === undefined ? fallback2 : value;
|
|
18972
18972
|
var normalizeTTSLayoutMode = (value, fallback2 = "left-aligned") => typeof value === "string" && VALID_TTS_LAYOUT_MODES.has(value) ? value : fallback2;
|
|
18973
18973
|
var DEFAULT_TTS_SPEED_OPTIONS = Object.freeze([0.8, 1.25]);
|
|
18974
|
-
var
|
|
18974
|
+
var normalizeSpeedRate = (entry) => {
|
|
18975
|
+
if (typeof entry !== "number" || !Number.isFinite(entry) || entry <= 0) {
|
|
18976
|
+
return;
|
|
18977
|
+
}
|
|
18978
|
+
const rounded = Math.round(entry * 100) / 100;
|
|
18979
|
+
return rounded === 1 ? undefined : rounded;
|
|
18980
|
+
};
|
|
18981
|
+
var trimOptionalText = (value) => {
|
|
18982
|
+
if (typeof value !== "string")
|
|
18983
|
+
return;
|
|
18984
|
+
const trimmed = value.trim();
|
|
18985
|
+
return trimmed.length ? trimmed : undefined;
|
|
18986
|
+
};
|
|
18987
|
+
var formatSpeedLabel = (rate) => `${rate}x`;
|
|
18988
|
+
var formatSpeedAriaLabel = (label, usedDefaultLabel) => usedDefaultLabel ? `Speed ${label}` : `${label} speed`;
|
|
18989
|
+
var normalizeSpeedAriaLabel = (label, ariaLabel, usedDefaultLabel) => {
|
|
18990
|
+
if (!ariaLabel)
|
|
18991
|
+
return formatSpeedAriaLabel(label, usedDefaultLabel);
|
|
18992
|
+
if (ariaLabel.toLowerCase().includes(label.toLowerCase()))
|
|
18993
|
+
return ariaLabel;
|
|
18994
|
+
return `${label} ${ariaLabel}`;
|
|
18995
|
+
};
|
|
18996
|
+
var normalizeTTSSpeedOptionConfig = (entry) => {
|
|
18997
|
+
if (typeof entry === "number")
|
|
18998
|
+
return normalizeSpeedRate(entry);
|
|
18999
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
19000
|
+
return;
|
|
19001
|
+
const record = entry;
|
|
19002
|
+
const rate = normalizeSpeedRate(record.rate);
|
|
19003
|
+
if (rate === undefined)
|
|
19004
|
+
return;
|
|
19005
|
+
const label = trimOptionalText(record.label);
|
|
19006
|
+
const ariaLabel = trimOptionalText(record.ariaLabel);
|
|
19007
|
+
return {
|
|
19008
|
+
rate,
|
|
19009
|
+
...label ? { label } : {},
|
|
19010
|
+
...ariaLabel ? { ariaLabel } : {}
|
|
19011
|
+
};
|
|
19012
|
+
};
|
|
19013
|
+
var normalizeTTSSpeedOptionConfigs = (value) => {
|
|
18975
19014
|
if (!Array.isArray(value))
|
|
18976
19015
|
return [...DEFAULT_TTS_SPEED_OPTIONS];
|
|
18977
19016
|
if (value.length === 0)
|
|
18978
19017
|
return [];
|
|
18979
|
-
const
|
|
19018
|
+
const dedupedRates = new Set;
|
|
19019
|
+
const normalized = [];
|
|
18980
19020
|
for (const entry of value) {
|
|
18981
|
-
|
|
19021
|
+
const option = normalizeTTSSpeedOptionConfig(entry);
|
|
19022
|
+
if (option === undefined)
|
|
18982
19023
|
continue;
|
|
18983
|
-
const
|
|
18984
|
-
if (
|
|
19024
|
+
const rate = typeof option === "number" ? option : option.rate;
|
|
19025
|
+
if (dedupedRates.has(rate))
|
|
18985
19026
|
continue;
|
|
18986
|
-
|
|
19027
|
+
dedupedRates.add(rate);
|
|
19028
|
+
normalized.push(option);
|
|
18987
19029
|
}
|
|
18988
|
-
return
|
|
19030
|
+
return normalized.length ? normalized : [...DEFAULT_TTS_SPEED_OPTIONS];
|
|
18989
19031
|
};
|
|
19032
|
+
var normalizeTTSSpeedControlOptions = (value) => normalizeTTSSpeedOptionConfigs(value).map((option) => {
|
|
19033
|
+
if (typeof option === "number") {
|
|
19034
|
+
const label2 = formatSpeedLabel(option);
|
|
19035
|
+
return {
|
|
19036
|
+
rate: option,
|
|
19037
|
+
label: label2,
|
|
19038
|
+
ariaLabel: formatSpeedAriaLabel(label2, true)
|
|
19039
|
+
};
|
|
19040
|
+
}
|
|
19041
|
+
const defaultLabel = formatSpeedLabel(option.rate);
|
|
19042
|
+
const label = option.label || defaultLabel;
|
|
19043
|
+
return {
|
|
19044
|
+
rate: option.rate,
|
|
19045
|
+
label,
|
|
19046
|
+
ariaLabel: normalizeSpeedAriaLabel(label, option.ariaLabel, label === defaultLabel)
|
|
19047
|
+
};
|
|
19048
|
+
});
|
|
18990
19049
|
var applyRuntimeDefaults = (config) => {
|
|
18991
19050
|
const withLayoutDefaults = {
|
|
18992
19051
|
...config,
|
|
@@ -19143,7 +19202,7 @@ var ttsToolRegistration = {
|
|
|
19143
19202
|
settings.layoutMode = normalizeTTSLayoutMode(settings.layoutMode);
|
|
19144
19203
|
}
|
|
19145
19204
|
if (settings && "speedOptions" in settings) {
|
|
19146
|
-
settings.speedOptions =
|
|
19205
|
+
settings.speedOptions = normalizeTTSSpeedOptionConfigs(settings.speedOptions);
|
|
19147
19206
|
}
|
|
19148
19207
|
const normalizedConfig = {
|
|
19149
19208
|
...config
|
|
@@ -19152,7 +19211,7 @@ var ttsToolRegistration = {
|
|
|
19152
19211
|
normalizedConfig.layoutMode = normalizeTTSLayoutMode(normalizedConfig.layoutMode);
|
|
19153
19212
|
}
|
|
19154
19213
|
if ("speedOptions" in normalizedConfig) {
|
|
19155
|
-
normalizedConfig.speedOptions =
|
|
19214
|
+
normalizedConfig.speedOptions = normalizeTTSSpeedOptionConfigs(normalizedConfig.speedOptions);
|
|
19156
19215
|
}
|
|
19157
19216
|
if (settings) {
|
|
19158
19217
|
normalizedConfig.settings = settings;
|
|
@@ -19175,7 +19234,7 @@ var ttsToolRegistration = {
|
|
|
19175
19234
|
const resolveRuntimeSettings = () => resolveTTSRuntimeSettings(toolbarContext.toolkitCoordinator?.getToolConfig(this.toolId) || undefined);
|
|
19176
19235
|
const resolveElementSpeedOptions = () => {
|
|
19177
19236
|
const runtimeSettings = resolveRuntimeSettings();
|
|
19178
|
-
return
|
|
19237
|
+
return normalizeTTSSpeedControlOptions(runtimeSettings.speedOptions);
|
|
19179
19238
|
};
|
|
19180
19239
|
const resolveLayoutMode = () => resolveTTSLayoutMode(resolveRuntimeSettings());
|
|
19181
19240
|
const resolveHostLayout = () => resolveTTSHostToolbarLayout(resolveRuntimeSettings());
|
|
@@ -21007,27 +21066,27 @@ class HighlightCoordinator {
|
|
|
21007
21066
|
|
|
21008
21067
|
/* Annotation highlights - persistent */
|
|
21009
21068
|
::highlight(annotation-yellow) {
|
|
21010
|
-
background-color:
|
|
21069
|
+
background-color: var(--pie-annotation-yellow-highlight, rgba(253, 233, 149, 0.5));
|
|
21011
21070
|
color: inherit;
|
|
21012
21071
|
}
|
|
21013
21072
|
|
|
21014
21073
|
::highlight(annotation-green) {
|
|
21015
|
-
background-color:
|
|
21074
|
+
background-color: var(--pie-annotation-green-highlight, rgba(166, 225, 197, 0.5));
|
|
21016
21075
|
color: inherit;
|
|
21017
21076
|
}
|
|
21018
21077
|
|
|
21019
21078
|
::highlight(annotation-blue) {
|
|
21020
|
-
background-color:
|
|
21079
|
+
background-color: var(--pie-annotation-blue-highlight, rgba(167, 224, 246, 0.5));
|
|
21021
21080
|
color: inherit;
|
|
21022
21081
|
}
|
|
21023
21082
|
|
|
21024
21083
|
::highlight(annotation-pink) {
|
|
21025
|
-
background-color:
|
|
21084
|
+
background-color: var(--pie-annotation-pink-highlight, rgba(255, 159, 174, 0.5));
|
|
21026
21085
|
color: inherit;
|
|
21027
21086
|
}
|
|
21028
21087
|
|
|
21029
21088
|
::highlight(annotation-orange) {
|
|
21030
|
-
background-color:
|
|
21089
|
+
background-color: var(--pie-annotation-orange-highlight, rgba(255, 165, 0, 0.5));
|
|
21031
21090
|
color: inherit;
|
|
21032
21091
|
}
|
|
21033
21092
|
|
|
@@ -21070,6 +21129,16 @@ class HighlightCoordinator {
|
|
|
21070
21129
|
}
|
|
21071
21130
|
this.highlightTTSElementFallbacks(ranges);
|
|
21072
21131
|
}
|
|
21132
|
+
highlightTTSSentenceElements(elements) {
|
|
21133
|
+
if (!this.ttsSentenceHighlight)
|
|
21134
|
+
return;
|
|
21135
|
+
this.applyAdaptiveTTSStyle(elements[0] || null);
|
|
21136
|
+
this.clearTTSSentence();
|
|
21137
|
+
for (const element2 of elements) {
|
|
21138
|
+
element2.setAttribute("data-pie-tts-sentence-element", "true");
|
|
21139
|
+
this.ttsSentenceElementHighlights.add(element2);
|
|
21140
|
+
}
|
|
21141
|
+
}
|
|
21073
21142
|
static SENTENCE_FALLBACK_SELECTOR = "math, mjx-container, svg, img, canvas, [role='img']";
|
|
21074
21143
|
static WORD_FALLBACK_SELECTOR = "svg, img, canvas, [role='img']";
|
|
21075
21144
|
highlightTTSElementFallbacks(ranges) {
|
|
@@ -23829,6 +23898,7 @@ class TTSService {
|
|
|
23829
23898
|
currentProvider = null;
|
|
23830
23899
|
provider = null;
|
|
23831
23900
|
highlightCoordinator = null;
|
|
23901
|
+
highlightTargetResolverProvider = null;
|
|
23832
23902
|
catalogResolver = null;
|
|
23833
23903
|
state = PlaybackState.IDLE;
|
|
23834
23904
|
ttsConfig = {};
|
|
@@ -23839,6 +23909,7 @@ class TTSService {
|
|
|
23839
23909
|
lastError = null;
|
|
23840
23910
|
speakRunId = 0;
|
|
23841
23911
|
currentBoundaryOffset = 0;
|
|
23912
|
+
activeWordBoundaryOffset = 0;
|
|
23842
23913
|
seekSegments = [];
|
|
23843
23914
|
sentenceHighlightSegments = [];
|
|
23844
23915
|
currentSeekSegmentIndex = 0;
|
|
@@ -23918,9 +23989,137 @@ class TTSService {
|
|
|
23918
23989
|
setHighlightCoordinator(coordinator) {
|
|
23919
23990
|
this.highlightCoordinator = coordinator;
|
|
23920
23991
|
}
|
|
23992
|
+
setHighlightTargetResolverProvider(provider) {
|
|
23993
|
+
this.highlightTargetResolverProvider = provider;
|
|
23994
|
+
return () => {
|
|
23995
|
+
if (this.highlightTargetResolverProvider === provider) {
|
|
23996
|
+
this.highlightTargetResolverProvider = null;
|
|
23997
|
+
}
|
|
23998
|
+
};
|
|
23999
|
+
}
|
|
23921
24000
|
setCatalogResolver(resolver) {
|
|
23922
24001
|
this.catalogResolver = resolver;
|
|
23923
24002
|
}
|
|
24003
|
+
getHighlightResolverRuntime() {
|
|
24004
|
+
let provided = null;
|
|
24005
|
+
try {
|
|
24006
|
+
provided = this.highlightTargetResolverProvider?.() ?? null;
|
|
24007
|
+
} catch {
|
|
24008
|
+
provided = null;
|
|
24009
|
+
}
|
|
24010
|
+
const providedContext = provided?.context ?? {};
|
|
24011
|
+
const fallbackScope = typeof HTMLElement !== "undefined" && this.currentContentElement instanceof HTMLElement ? this.currentContentElement : null;
|
|
24012
|
+
return {
|
|
24013
|
+
context: {
|
|
24014
|
+
...providedContext,
|
|
24015
|
+
scopeElement: providedContext.scopeElement ?? fallbackScope
|
|
24016
|
+
},
|
|
24017
|
+
resolver: provided?.resolver ?? null
|
|
24018
|
+
};
|
|
24019
|
+
}
|
|
24020
|
+
getNodeElement(node) {
|
|
24021
|
+
if (!node)
|
|
24022
|
+
return null;
|
|
24023
|
+
return node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
24024
|
+
}
|
|
24025
|
+
isElementWithinScope(element2, scope) {
|
|
24026
|
+
if (!scope)
|
|
24027
|
+
return true;
|
|
24028
|
+
return element2 === scope || scope.contains(element2);
|
|
24029
|
+
}
|
|
24030
|
+
isRangeWithinScope(range, scope) {
|
|
24031
|
+
const startElement = this.getNodeElement(range.startContainer);
|
|
24032
|
+
const endElement = this.getNodeElement(range.endContainer);
|
|
24033
|
+
if (!startElement || !endElement)
|
|
24034
|
+
return false;
|
|
24035
|
+
return this.isElementWithinScope(startElement, scope) && this.isElementWithinScope(endElement, scope);
|
|
24036
|
+
}
|
|
24037
|
+
resolveWordHighlightRange(nativeRange) {
|
|
24038
|
+
const { context, resolver } = this.getHighlightResolverRuntime();
|
|
24039
|
+
const scope = context.scopeElement ?? this.currentContentElement;
|
|
24040
|
+
try {
|
|
24041
|
+
const resolved = resolver?.resolveWordRange?.(nativeRange, context);
|
|
24042
|
+
if (resolved && this.isRangeWithinScope(resolved, scope)) {
|
|
24043
|
+
return { range: resolved, remapped: !sameRange(nativeRange, resolved) };
|
|
24044
|
+
}
|
|
24045
|
+
} catch {}
|
|
24046
|
+
return { range: nativeRange, remapped: false };
|
|
24047
|
+
}
|
|
24048
|
+
resolveSentenceHighlightTargets(nativeRanges) {
|
|
24049
|
+
const { context, resolver } = this.getHighlightResolverRuntime();
|
|
24050
|
+
const scope = context.scopeElement ?? this.currentContentElement;
|
|
24051
|
+
try {
|
|
24052
|
+
const resolved = resolver?.resolveSentenceRanges?.(nativeRanges, context);
|
|
24053
|
+
if (!resolved)
|
|
24054
|
+
return { ranges: nativeRanges, elements: [] };
|
|
24055
|
+
const ranges = [];
|
|
24056
|
+
const elements = [];
|
|
24057
|
+
for (const target of resolved) {
|
|
24058
|
+
if (typeof Range !== "undefined" && target instanceof Range) {
|
|
24059
|
+
if (!this.isRangeWithinScope(target, scope)) {
|
|
24060
|
+
return { ranges: nativeRanges, elements: [] };
|
|
24061
|
+
}
|
|
24062
|
+
ranges.push(target);
|
|
24063
|
+
continue;
|
|
24064
|
+
}
|
|
24065
|
+
if (typeof Element !== "undefined" && target instanceof Element) {
|
|
24066
|
+
if (!this.isElementWithinScope(target, scope)) {
|
|
24067
|
+
return { ranges: nativeRanges, elements: [] };
|
|
24068
|
+
}
|
|
24069
|
+
elements.push(target);
|
|
24070
|
+
continue;
|
|
24071
|
+
}
|
|
24072
|
+
return { ranges: nativeRanges, elements: [] };
|
|
24073
|
+
}
|
|
24074
|
+
return { ranges, elements };
|
|
24075
|
+
} catch {
|
|
24076
|
+
return { ranges: nativeRanges, elements: [] };
|
|
24077
|
+
}
|
|
24078
|
+
}
|
|
24079
|
+
createTextRange(node, startOffset, endOffset) {
|
|
24080
|
+
if (typeof document === "undefined")
|
|
24081
|
+
return null;
|
|
24082
|
+
if (typeof document.createRange !== "function")
|
|
24083
|
+
return null;
|
|
24084
|
+
try {
|
|
24085
|
+
const range = document.createRange();
|
|
24086
|
+
range.setStart(node, startOffset);
|
|
24087
|
+
range.setEnd(node, endOffset);
|
|
24088
|
+
if (range.startContainer !== node || range.endContainer !== node) {
|
|
24089
|
+
return null;
|
|
24090
|
+
}
|
|
24091
|
+
return range;
|
|
24092
|
+
} catch {
|
|
24093
|
+
return null;
|
|
24094
|
+
}
|
|
24095
|
+
}
|
|
24096
|
+
paintTTSWordRange(range) {
|
|
24097
|
+
if (!this.highlightCoordinator)
|
|
24098
|
+
return;
|
|
24099
|
+
try {
|
|
24100
|
+
if (range.startContainer === range.endContainer && range.startContainer.nodeType === Node.TEXT_NODE) {
|
|
24101
|
+
this.highlightCoordinator.highlightTTSWord(range.startContainer, range.startOffset, range.endOffset);
|
|
24102
|
+
return;
|
|
24103
|
+
}
|
|
24104
|
+
} catch {}
|
|
24105
|
+
this.highlightCoordinator.highlightRange(range, HighlightType.TTS_WORD, HighlightColor.YELLOW);
|
|
24106
|
+
}
|
|
24107
|
+
paintTTSSentenceRanges(nativeRanges) {
|
|
24108
|
+
if (!this.highlightCoordinator)
|
|
24109
|
+
return;
|
|
24110
|
+
const { ranges, elements } = this.resolveSentenceHighlightTargets(nativeRanges);
|
|
24111
|
+
if (elements.length > 0 && ranges.length === 0 && this.highlightCoordinator.highlightTTSSentenceElements) {
|
|
24112
|
+
this.highlightCoordinator.highlightTTSSentenceElements(elements);
|
|
24113
|
+
return;
|
|
24114
|
+
}
|
|
24115
|
+
const paintedRanges = [...ranges];
|
|
24116
|
+
for (const element2 of elements) {
|
|
24117
|
+
const elementRange = document.createRange();
|
|
24118
|
+
elementRange.selectNodeContents(element2);
|
|
24119
|
+
paintedRanges.push(elementRange);
|
|
24120
|
+
}
|
|
24121
|
+
this.highlightCoordinator.highlightTTSSentence(paintedRanges);
|
|
24122
|
+
}
|
|
23924
24123
|
getCapabilities() {
|
|
23925
24124
|
return this.currentProvider?.getCapabilities() || null;
|
|
23926
24125
|
}
|
|
@@ -24232,11 +24431,11 @@ class TTSService {
|
|
|
24232
24431
|
this.currentBoundaryOffset = 0;
|
|
24233
24432
|
const originalOnWordBoundary = this.provider.onWordBoundary;
|
|
24234
24433
|
this.provider.onWordBoundary = (word, position, length) => {
|
|
24434
|
+
originalOnWordBoundary?.(word, position, length);
|
|
24235
24435
|
if (Number.isFinite(position)) {
|
|
24236
24436
|
this.currentBoundaryOffset = position;
|
|
24237
24437
|
this.currentSeekSegmentIndex = this.getCurrentSeekSegmentIndex();
|
|
24238
24438
|
}
|
|
24239
|
-
originalOnWordBoundary?.(word, position, length);
|
|
24240
24439
|
};
|
|
24241
24440
|
try {
|
|
24242
24441
|
await providerWithPlan.speakSegments(segments);
|
|
@@ -24247,6 +24446,10 @@ class TTSService {
|
|
|
24247
24446
|
}
|
|
24248
24447
|
return;
|
|
24249
24448
|
}
|
|
24449
|
+
this.configureWordBoundaryHighlighting({
|
|
24450
|
+
highlightMode: options?.highlightMode || "word",
|
|
24451
|
+
wordBoundaryOffset: this.activeWordBoundaryOffset
|
|
24452
|
+
});
|
|
24250
24453
|
for (const segment of segments) {
|
|
24251
24454
|
if (runId !== this.speakRunId)
|
|
24252
24455
|
return;
|
|
@@ -24278,7 +24481,7 @@ class TTSService {
|
|
|
24278
24481
|
const range = document.createRange();
|
|
24279
24482
|
range.setStart(start.node, start.offset);
|
|
24280
24483
|
range.setEnd(end.node, end.offset + 1);
|
|
24281
|
-
this.
|
|
24484
|
+
this.paintTTSSentenceRanges([range]);
|
|
24282
24485
|
this.activeSentenceStartOffset = startOffset;
|
|
24283
24486
|
}
|
|
24284
24487
|
getSegmentIndexForOffset(segments, offset) {
|
|
@@ -24366,6 +24569,7 @@ class TTSService {
|
|
|
24366
24569
|
this.initializeSpeakTracking(highlightText, options);
|
|
24367
24570
|
const highlightMode = options?.highlightModeOverride || (speechMatchesVisibleText ? this.resolveHighlightMode() : "sentence");
|
|
24368
24571
|
this.activeHighlightMode = highlightMode;
|
|
24572
|
+
this.activeWordBoundaryOffset = options?.wordBoundaryOffset || 0;
|
|
24369
24573
|
const hasExplicitBreaks = this.hasExplicitBreakSemantics(contentToSpeak);
|
|
24370
24574
|
const shouldUsePlan = !!this.currentContentElement && !usedCatalogSpoken && !hasExplicitBreaks && speechMatchesVisibleText;
|
|
24371
24575
|
this.seekSegments = hasExplicitBreaks || !speechMatchesVisibleText ? [] : shouldUsePlan && this.currentContentElement ? this.createSpeechPlan(this.currentContentElement, normalizedText) : this.createSeekSegmentsFromText(highlightText);
|
|
@@ -24743,7 +24947,7 @@ class TTSService {
|
|
|
24743
24947
|
try {
|
|
24744
24948
|
const range = document.createRange();
|
|
24745
24949
|
range.selectNodeContents(this.currentContentElement);
|
|
24746
|
-
this.
|
|
24950
|
+
this.paintTTSSentenceRanges([range]);
|
|
24747
24951
|
} catch {}
|
|
24748
24952
|
}
|
|
24749
24953
|
}
|
|
@@ -24759,7 +24963,12 @@ class TTSService {
|
|
|
24759
24963
|
if (highlightRange && this.highlightCoordinator) {
|
|
24760
24964
|
const highlightText = highlightRange.node.textContent?.substring(highlightRange.start, highlightRange.end) || "";
|
|
24761
24965
|
this.debugLog(`[TTSService] Highlighting "${highlightText}" (word: "${word}") at position ${globalIndex}`);
|
|
24762
|
-
this.
|
|
24966
|
+
const nativeRange = this.createTextRange(highlightRange.node, highlightRange.start, highlightRange.end);
|
|
24967
|
+
if (nativeRange) {
|
|
24968
|
+
this.paintTTSWordRange(this.resolveWordHighlightRange(nativeRange).range);
|
|
24969
|
+
} else {
|
|
24970
|
+
this.highlightCoordinator.highlightTTSWord(highlightRange.node, highlightRange.start, highlightRange.end);
|
|
24971
|
+
}
|
|
24763
24972
|
} else {
|
|
24764
24973
|
console.warn(`[TTSService] Could not find highlight range for position ${globalIndex}, length ${wordLength}`);
|
|
24765
24974
|
}
|
|
@@ -24778,7 +24987,7 @@ class TTSService {
|
|
|
24778
24987
|
this.lastRenderedRegionTarget = null;
|
|
24779
24988
|
if (chunk.regionRange) {
|
|
24780
24989
|
this.highlightCoordinator.clearHighlights?.(HighlightType.TTS_WORD);
|
|
24781
|
-
this.
|
|
24990
|
+
this.paintTTSSentenceRanges([chunk.regionRange]);
|
|
24782
24991
|
return;
|
|
24783
24992
|
}
|
|
24784
24993
|
if (!element2)
|
|
@@ -24786,23 +24995,19 @@ class TTSService {
|
|
|
24786
24995
|
const range = document.createRange();
|
|
24787
24996
|
range.selectNodeContents(element2);
|
|
24788
24997
|
this.highlightCoordinator.clearHighlights?.(HighlightType.TTS_WORD);
|
|
24789
|
-
this.
|
|
24998
|
+
this.paintTTSSentenceRanges([range]);
|
|
24790
24999
|
} catch {}
|
|
24791
25000
|
}
|
|
24792
25001
|
highlightCatalogActiveRange(range) {
|
|
24793
25002
|
if (!this.highlightCoordinator)
|
|
24794
25003
|
return;
|
|
24795
|
-
|
|
24796
|
-
this.highlightCoordinator.highlightTTSWord(range.startContainer, range.startOffset, range.endOffset);
|
|
24797
|
-
return;
|
|
24798
|
-
}
|
|
24799
|
-
this.highlightCoordinator.highlightRange(range, HighlightType.TTS_WORD, HighlightColor.YELLOW);
|
|
25004
|
+
this.paintTTSWordRange(this.resolveWordHighlightRange(range).range);
|
|
24800
25005
|
}
|
|
24801
25006
|
highlightRenderableRegionTarget(target) {
|
|
24802
25007
|
if (!this.highlightCoordinator || !target || typeof document === "undefined") {
|
|
24803
25008
|
return;
|
|
24804
25009
|
}
|
|
24805
|
-
if (sameRenderableHighlightTarget(this.lastRenderedRegionTarget, target)) {
|
|
25010
|
+
if (!this.highlightTargetResolverProvider && sameRenderableHighlightTarget(this.lastRenderedRegionTarget, target)) {
|
|
24806
25011
|
return;
|
|
24807
25012
|
}
|
|
24808
25013
|
try {
|
|
@@ -24810,14 +25015,14 @@ class TTSService {
|
|
|
24810
25015
|
if (target.type === "element") {
|
|
24811
25016
|
range.selectNodeContents(target.element);
|
|
24812
25017
|
} else if (target.type === "range") {
|
|
24813
|
-
this.
|
|
25018
|
+
this.paintTTSSentenceRanges([target.range]);
|
|
24814
25019
|
this.lastRenderedRegionTarget = target;
|
|
24815
25020
|
return;
|
|
24816
25021
|
} else {
|
|
24817
25022
|
range.setStart(target.node, target.startOffset);
|
|
24818
25023
|
range.setEnd(target.node, target.endOffset);
|
|
24819
25024
|
}
|
|
24820
|
-
this.
|
|
25025
|
+
this.paintTTSSentenceRanges([range]);
|
|
24821
25026
|
this.lastRenderedRegionTarget = target;
|
|
24822
25027
|
} catch {}
|
|
24823
25028
|
}
|
|
@@ -24830,13 +25035,25 @@ class TTSService {
|
|
|
24830
25035
|
return;
|
|
24831
25036
|
}
|
|
24832
25037
|
if (target.type === "text-range") {
|
|
24833
|
-
this.
|
|
25038
|
+
const nativeRange = this.createTextRange(target.node, target.startOffset, target.endOffset);
|
|
25039
|
+
if (nativeRange) {
|
|
25040
|
+
this.paintTTSWordRange(this.resolveWordHighlightRange(nativeRange).range);
|
|
25041
|
+
} else {
|
|
25042
|
+
this.highlightCoordinator.highlightTTSWord(target.node, target.startOffset, target.endOffset);
|
|
25043
|
+
}
|
|
24834
25044
|
return;
|
|
24835
25045
|
}
|
|
24836
25046
|
if (target.type === "range") {
|
|
24837
25047
|
this.highlightCatalogActiveRange(target.range);
|
|
24838
25048
|
return;
|
|
24839
25049
|
}
|
|
25050
|
+
const elementNativeRange = document.createRange();
|
|
25051
|
+
elementNativeRange.selectNodeContents(target.element);
|
|
25052
|
+
const resolvedElementRange = this.resolveWordHighlightRange(elementNativeRange);
|
|
25053
|
+
if (resolvedElementRange.remapped) {
|
|
25054
|
+
this.paintTTSWordRange(resolvedElementRange.range);
|
|
25055
|
+
return;
|
|
25056
|
+
}
|
|
24840
25057
|
const onlyChild = target.element.childNodes.length === 1 ? target.element.firstChild : null;
|
|
24841
25058
|
if (target.quality === "semantic-token" && onlyChild?.nodeType === Node.TEXT_NODE) {
|
|
24842
25059
|
this.highlightCoordinator.highlightTTSWord(onlyChild, 0, onlyChild.textContent?.length || 0);
|
|
@@ -24951,6 +25168,7 @@ class TTSService {
|
|
|
24951
25168
|
this.currentContentElement = null;
|
|
24952
25169
|
this.normalizedToDOM.clear();
|
|
24953
25170
|
this.currentBoundaryOffset = 0;
|
|
25171
|
+
this.activeWordBoundaryOffset = 0;
|
|
24954
25172
|
this.seekSegments = [];
|
|
24955
25173
|
this.sentenceHighlightSegments = [];
|
|
24956
25174
|
this.currentSeekSegmentIndex = 0;
|
|
@@ -25005,31 +25223,27 @@ class TTSService {
|
|
|
25005
25223
|
this.setState(PlaybackState.PLAYING);
|
|
25006
25224
|
}
|
|
25007
25225
|
}
|
|
25008
|
-
|
|
25009
|
-
if (!
|
|
25010
|
-
return;
|
|
25011
|
-
|
|
25012
|
-
|
|
25013
|
-
|
|
25014
|
-
if (this.
|
|
25015
|
-
return;
|
|
25016
|
-
if (this.seekSegments.length === 0)
|
|
25017
|
-
return;
|
|
25018
|
-
const delta = Number.isFinite(units) ? Math.trunc(units) : 0;
|
|
25019
|
-
if (delta === 0)
|
|
25020
|
-
return;
|
|
25021
|
-
const currentIndex = this.getCurrentSeekSegmentIndex();
|
|
25022
|
-
const targetIndex = Math.max(0, Math.min(this.seekSegments.length - 1, currentIndex + delta));
|
|
25023
|
-
if (targetIndex === currentIndex)
|
|
25226
|
+
normalizePlaybackRate(rate) {
|
|
25227
|
+
if (!Number.isFinite(rate))
|
|
25228
|
+
return 1;
|
|
25229
|
+
return Math.max(0.25, Math.min(4, rate));
|
|
25230
|
+
}
|
|
25231
|
+
async restartFromSeekIndex(targetIndex) {
|
|
25232
|
+
if (!this.provider || this.seekSegments.length === 0)
|
|
25024
25233
|
return;
|
|
25234
|
+
const safeTargetIndex = Math.max(0, Math.min(this.seekSegments.length - 1, targetIndex));
|
|
25025
25235
|
this.speakRunId += 1;
|
|
25026
25236
|
this.provider.onWordBoundary = undefined;
|
|
25027
25237
|
this.provider.stop();
|
|
25028
|
-
this.currentSeekSegmentIndex =
|
|
25238
|
+
this.currentSeekSegmentIndex = safeTargetIndex;
|
|
25029
25239
|
const runId = ++this.speakRunId;
|
|
25030
|
-
const restartSegments = this.seekSegments.slice(
|
|
25240
|
+
const restartSegments = this.seekSegments.slice(safeTargetIndex);
|
|
25031
25241
|
this.setState(PlaybackState.PLAYING);
|
|
25032
25242
|
try {
|
|
25243
|
+
this.configureWordBoundaryHighlighting({
|
|
25244
|
+
highlightMode: this.activeHighlightMode,
|
|
25245
|
+
wordBoundaryOffset: this.activeWordBoundaryOffset
|
|
25246
|
+
});
|
|
25033
25247
|
await this.speakWithPlan(restartSegments, runId, {
|
|
25034
25248
|
highlightMode: this.activeHighlightMode
|
|
25035
25249
|
});
|
|
@@ -25037,15 +25251,44 @@ class TTSService {
|
|
|
25037
25251
|
return;
|
|
25038
25252
|
this.setState(PlaybackState.IDLE);
|
|
25039
25253
|
this.clearHighlightsAndTracking();
|
|
25254
|
+
this.highlightTargetResolverProvider = null;
|
|
25040
25255
|
} catch (error) {
|
|
25041
25256
|
if (runId !== this.speakRunId)
|
|
25042
25257
|
return;
|
|
25043
25258
|
this.lastError = error instanceof Error ? error.message : String(error);
|
|
25044
25259
|
this.setState(PlaybackState.ERROR);
|
|
25045
25260
|
this.clearHighlightsAndTracking();
|
|
25261
|
+
this.highlightTargetResolverProvider = null;
|
|
25046
25262
|
throw error;
|
|
25047
25263
|
}
|
|
25048
25264
|
}
|
|
25265
|
+
async setPlaybackRate(rate) {
|
|
25266
|
+
const nextRate = this.normalizePlaybackRate(rate);
|
|
25267
|
+
await this.updateSettings({ rate: nextRate });
|
|
25268
|
+
if (this.state !== PlaybackState.PLAYING || !this.currentText || this.seekSegments.length === 0 || this.hasExplicitBreakSemantics(this.currentText)) {
|
|
25269
|
+
return;
|
|
25270
|
+
}
|
|
25271
|
+
await this.restartFromSeekIndex(this.getCurrentSeekSegmentIndex());
|
|
25272
|
+
}
|
|
25273
|
+
async seekBy(units) {
|
|
25274
|
+
if (!this.provider || !this.currentText)
|
|
25275
|
+
return;
|
|
25276
|
+
if (this.state !== PlaybackState.PLAYING && this.state !== PlaybackState.PAUSED) {
|
|
25277
|
+
return;
|
|
25278
|
+
}
|
|
25279
|
+
if (this.hasExplicitBreakSemantics(this.currentText))
|
|
25280
|
+
return;
|
|
25281
|
+
if (this.seekSegments.length === 0)
|
|
25282
|
+
return;
|
|
25283
|
+
const delta = Number.isFinite(units) ? Math.trunc(units) : 0;
|
|
25284
|
+
if (delta === 0)
|
|
25285
|
+
return;
|
|
25286
|
+
const currentIndex = this.getCurrentSeekSegmentIndex();
|
|
25287
|
+
const targetIndex = Math.max(0, Math.min(this.seekSegments.length - 1, currentIndex + delta));
|
|
25288
|
+
if (targetIndex === currentIndex)
|
|
25289
|
+
return;
|
|
25290
|
+
await this.restartFromSeekIndex(targetIndex);
|
|
25291
|
+
}
|
|
25049
25292
|
async seekForward(units = 1) {
|
|
25050
25293
|
const step = Number.isFinite(units) ? Math.max(1, Math.trunc(units)) : 1;
|
|
25051
25294
|
await this.seekBy(step);
|
|
@@ -25060,6 +25303,7 @@ class TTSService {
|
|
|
25060
25303
|
this.speakRunId += 1;
|
|
25061
25304
|
this.provider.onWordBoundary = undefined;
|
|
25062
25305
|
this.provider.stop();
|
|
25306
|
+
this.highlightTargetResolverProvider = null;
|
|
25063
25307
|
this.setState(PlaybackState.IDLE);
|
|
25064
25308
|
this.currentText = null;
|
|
25065
25309
|
if (this.highlightCoordinator) {
|
|
@@ -25069,6 +25313,7 @@ class TTSService {
|
|
|
25069
25313
|
this.currentContentElement = null;
|
|
25070
25314
|
this.normalizedToDOM.clear();
|
|
25071
25315
|
this.currentBoundaryOffset = 0;
|
|
25316
|
+
this.activeWordBoundaryOffset = 0;
|
|
25072
25317
|
this.seekSegments = [];
|
|
25073
25318
|
this.sentenceHighlightSegments = [];
|
|
25074
25319
|
this.currentSeekSegmentIndex = 0;
|