@readium/speech 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +10 -1
  2. package/build/SpeechServer/speechServerEngine.d.ts +5 -0
  3. package/build/WebSpeech/webSpeechEngine.d.ts +2 -0
  4. package/build/decorator/createLocator.d.ts +8 -4
  5. package/build/gnd/a11y.d.ts +2 -0
  6. package/build/gnd/converter.d.ts +35 -17
  7. package/build/gnd/dom.d.ts +1 -0
  8. package/build/gnd/domRangeGenerator.d.ts +9 -0
  9. package/build/gnd/elementHandlers.d.ts +5 -0
  10. package/build/gnd/idAllocator.d.ts +6 -0
  11. package/build/gnd/index.d.ts +3 -0
  12. package/build/gnd/makeGnd.d.ts +2 -1
  13. package/build/gnd/options.d.ts +15 -0
  14. package/build/gnd/prescan.d.ts +1 -0
  15. package/build/gnd/roles.d.ts +1 -6
  16. package/build/gnd/selectorGenerator.d.ts +2 -0
  17. package/build/gnd/textFragmentGenerator.d.ts +2 -0
  18. package/build/gnd/textrefFragment.d.ts +38 -0
  19. package/build/index.cjs +35 -35
  20. package/build/index.js +5082 -2028
  21. package/build/navigator.d.ts +1 -1
  22. package/build/preferences/Configurable.d.ts +1 -1
  23. package/build/preferences/index.d.ts +1 -1
  24. package/build/preferences/verbosityTables.d.ts +7 -1
  25. package/build/speechNavigator.d.ts +11 -2
  26. package/build/utils/clamp.d.ts +1 -0
  27. package/build/utterance.d.ts +3 -0
  28. package/build/utterances/contextualizations.d.ts +3 -0
  29. package/build/utterances/extractUtterances.d.ts +3 -3
  30. package/build/utterances/index.d.ts +2 -2
  31. package/build/utterances/roles.d.ts +8 -0
  32. package/build/utterances/tableStructure.d.ts +9 -0
  33. package/build/utterances/types.d.ts +11 -10
  34. package/package.json +8 -4
  35. package/build/utterances/announcements.d.ts +0 -2
package/README.md CHANGED
@@ -51,7 +51,11 @@ We are now on the sixth phase: verbosity and prosody, in particular contextualiz
51
51
 
52
52
  ### [In-context demo](https://readium.org/speech/demo/article)
53
53
 
54
- In-context reading with seamless voice selection (grouped by region and sorted based on quality), and playback control, providing an optional read-along experience that integrates naturally with the content. Also showcases highlighting, as above.
54
+ In-context reading with seamless voice selection (grouped by region and sorted based on quality), and playback control, providing an optional read-along experience that integrates naturally with the content.
55
+
56
+ Settings include a verbosity selector (none/few/some/most) demonstrating [contextualization](docs/Preferences.md) — synthesized announcements spoken around content based on its role (entering/leaving a section, a footnote, a pullquote…) — and, for the read-along experience, independent highlight styles (highlight, underline, strikethrough, outline, text color, mask) and colors for the current utterance and the current word.
57
+
58
+ The panel alongside the article shows the [Guided Navigation](docs/GuidedNavigation.md) object and the [utterances](docs/UtteranceExtraction.md) extracted from it, live.
55
59
 
56
60
  ### [Extraction playground](https://readium.org/speech/demo/playground)
57
61
 
@@ -144,6 +148,11 @@ Documentation provides guides for:
144
148
  - [Guided Navigation](docs/GuidedNavigation.md) — extracting [Guided Navigation objects](https://readium.org/guided-navigation) from HTML/XHTML content
145
149
  - [Utterance Extraction](docs/UtteranceExtraction.md) — extracting utterances from Guided Navigation objects
146
150
  - [Provider Registry](docs/ProviderRegistry.md) — using more than one `ReadiumSpeechEngineProvider` (e.g. WebSpeech and speech-server) side by side
151
+ - [WebSpeechEngine](docs/WebSpeechEngine.md) — the `ReadiumSpeechPlaybackEngine` backed by the browser's Web Speech API
152
+ - [SpeechServerEngine](docs/SpeechServerEngine.md) — the `ReadiumSpeechPlaybackEngine` backed by a remote [Readium Speech Server](https://github.com/readium/speech-server) instance
153
+ - [FallbackEngine](docs/FallbackEngine.md) — pairing a primary and fallback engine so playback swaps transparently when one is unreachable or stalls
154
+
155
+ See [CHANGELOG.md](CHANGELOG.md) for what's new in each release.
147
156
 
148
157
  ## Development
149
158
 
@@ -40,6 +40,9 @@ export declare class SpeechServerEngine implements ReadiumSpeechPlaybackEngine {
40
40
  private prefetchCache;
41
41
  private prefetchChainTail;
42
42
  private activeControllers;
43
+ private liveControllers;
44
+ private isSpeakingInternal;
45
+ private restartPending;
43
46
  private audioContext;
44
47
  private masterGain;
45
48
  private scheduledChunks;
@@ -66,6 +69,7 @@ export declare class SpeechServerEngine implements ReadiumSpeechPlaybackEngine {
66
69
  private fillPrefetchWindow;
67
70
  private queuePrefetch;
68
71
  private clearPrefetchCache;
72
+ private abortLiveControllers;
69
73
  private synthesizeStream;
70
74
  private synthesizeChunk;
71
75
  private ensureAudioContext;
@@ -85,6 +89,7 @@ export declare class SpeechServerEngine implements ReadiumSpeechPlaybackEngine {
85
89
  getPitch(): number;
86
90
  setVolume(volume: number): void;
87
91
  getVolume(): number;
92
+ private scheduleRestartIfSpeaking;
88
93
  getState(): ReadiumSpeechPlaybackState;
89
94
  getCurrentUtteranceIndex(): number;
90
95
  setCurrentUtteranceIndex(index: number, onComplete?: (success: boolean) => void): void;
@@ -19,6 +19,7 @@ export declare class WebSpeechEngine implements ReadiumSpeechPlaybackEngine {
19
19
  private speakGeneration;
20
20
  private resumeInfinityTimer?;
21
21
  private isSpeakingInternal;
22
+ private restartPending;
22
23
  private isPausedInternal;
23
24
  private isAndroidPaused;
24
25
  private pausedAtUtteranceIndex;
@@ -66,6 +67,7 @@ export declare class WebSpeechEngine implements ReadiumSpeechPlaybackEngine {
66
67
  getPitch(): number;
67
68
  setVolume(volume: number): void;
68
69
  getVolume(): number;
70
+ private scheduleRestartIfSpeaking;
69
71
  getState(): ReadiumSpeechPlaybackState;
70
72
  getCurrentUtteranceIndex(): number;
71
73
  setCurrentUtteranceIndex(index: number, onComplete?: (success: boolean) => void): void;
@@ -1,9 +1,13 @@
1
1
  import { Locator } from '@readium/shared';
2
+ import { DomRangeJSON } from '../gnd/textrefFragment.js';
2
3
  export interface LocatorOptions {
3
- highlight?: string;
4
- before?: string;
5
- after?: string;
6
- selector?: string;
4
+ text?: {
5
+ highlight?: string;
6
+ before?: string;
7
+ after?: string;
8
+ };
9
+ cssSelector?: string;
10
+ domRange?: DomRangeJSON;
7
11
  fragment?: string;
8
12
  }
9
13
  export declare function createLocator(options: LocatorOptions, wnd?: Window): Locator;
@@ -1,6 +1,8 @@
1
1
  import { GndText } from './types.js';
2
2
  /** Normalized (whitespace-coalesced and trimmed) text content of a node's subtree. */
3
3
  export declare function normalizedNodeText(el: Node): string;
4
+ /** Same as `normalizedNodeText`, excluding explicitly-role-bearing descendants. */
5
+ export declare function normalizedNodeTextExcludingExplicitRoles(el: Node): string;
4
6
  /**
5
7
  * Computes the text that becomes a node's `GndObject.description`, and
6
8
  * whether the node is visible in the first place. Follows the AccName
@@ -1,26 +1,36 @@
1
- import { GndObject } from './types.js';
1
+ import { GndObject, GndRole } from './types.js';
2
+ import { ObjBuilder } from './object.js';
2
3
  import { GndMediaType } from './dom.js';
4
+ import { GndGenerationOptions } from './options.js';
5
+ import { IdAllocator } from './idAllocator.js';
3
6
  /** Walks a DOM subtree, building the Guided Navigation object tree. */
4
7
  export declare class Converter {
5
8
  xmlParsed: boolean;
6
9
  ids: Map<string, Element>;
7
10
  suppressed: Set<Element>;
8
- idAlloc: {
9
- claimed: Set<string>;
10
- counters: Map<string, number>;
11
- };
11
+ idAlloc: IdAllocator;
12
12
  noterefDepth: number;
13
13
  allowNode: Element | null;
14
+ selectorPredicate: ((roles: GndRole[]) => boolean) | null;
15
+ domRangeEnabled: boolean;
16
+ textFragmentEnabled: boolean;
17
+ docRoot: Document | null;
14
18
  private root;
15
19
  private current;
20
+ private foldedCaptions;
16
21
  private segments;
17
22
  private textAcc;
18
23
  private currentCtx;
19
24
  private flowEndsWithSpace;
20
25
  private pendingChildren;
26
+ private flowFirstNode;
27
+ private flowFirstOffset;
28
+ private flowLastNode;
29
+ private flowLastOffset;
30
+ private lastFlowRange;
31
+ private lastFlowText;
21
32
  constructor(xmlParsed: boolean);
22
- private allocateId;
23
- private claimId;
33
+ spawnChild(allowNode: Element, depthDelta?: number): Converter;
24
34
  prescan(root: Element): void;
25
35
  convert(root: Element): void;
26
36
  convertChildren(root: Element): void;
@@ -29,24 +39,32 @@ export declare class Converter {
29
39
  private appendChild;
30
40
  private walk;
31
41
  private head;
42
+ private implicitCaptionOf;
32
43
  private tail;
44
+ private applyTextref;
33
45
  private text;
34
46
  private textContext;
35
47
  private updateFlowSpace;
36
48
  private closeSegment;
37
49
  private resetFlow;
38
- private placeholder;
39
- private pagebreak;
40
- private noteref;
41
- private link;
50
+ placeholder(el: Element, tag: string, object: ObjBuilder, candidateID?: string): void;
42
51
  private flushText;
43
52
  }
44
53
  /**
45
54
  * Converts an HTML or XHTML fragment or document into Guided Navigation
46
- * objects, reflecting exactly the input it's given: a real, author-written
47
- * <body> becomes its own role: ["body"] node like any other element; a
48
- * <body> synthesized only by text/html parsing around a bodyless fragment
49
- * is not content and is skipped through; a bodyless XHTML fragment's root
50
- * element is itself the content.
55
+ * objects.
56
+ *
57
+ * Given a string, `input` is parsed into a detached document that's never
58
+ * seen again `options.textrefs.domRange` has nothing to resolve back
59
+ * against there, so it's ignored. Reflects exactly the input it's given: a
60
+ * real, author-written <body> becomes its own role: ["body"] node like any
61
+ * other element; a <body> synthesized only by text/html parsing around a
62
+ * bodyless fragment is not content and is skipped through; a bodyless XHTML
63
+ * fragment's root element is itself the content.
64
+ *
65
+ * Given a live, already-rendered element instead, it's converted in place —
66
+ * no parsing, no detached copy — so `domRange` can pinpoint exact text
67
+ * nodes a DOM-highlighting consumer can resolve straight back against that
68
+ * same document.
51
69
  */
52
- export declare function parseMarkup(input: string, mediaType?: GndMediaType): GndObject[];
70
+ export declare function parseMarkup(input: string | Element, mediaType?: GndMediaType, options?: GndGenerationOptions): GndObject[];
@@ -1,5 +1,6 @@
1
1
  export declare function nodeLanguage(el: Element | null): string;
2
2
  export declare function hasElementChild(el: Element): boolean;
3
3
  export declare function isAncestorOf(anc: Element, n: Element): boolean;
4
+ export declare function isInlineTag(tagName: string): boolean;
4
5
  export type GndMediaType = "text/html" | "application/xhtml+xml";
5
6
  export declare function sniffMediaType(input: string): GndMediaType;
@@ -0,0 +1,9 @@
1
+ import { DomRangeJSON } from './textrefFragment.js';
2
+ export interface KnownSelector {
3
+ el: Element;
4
+ selector: string;
5
+ }
6
+ export declare function generateDomRange(range: {
7
+ first: [Text, number];
8
+ last: [Text, number];
9
+ }, docRoot: Document | null, known?: KnownSelector): DomRangeJSON | undefined;
@@ -0,0 +1,5 @@
1
+ import { GndRole, GndText } from './types.js';
2
+ import { Converter } from './converter.js';
3
+ export declare function pagebreak(converter: Converter, el: Element, aria: GndText | null, roles: GndRole[]): boolean;
4
+ export declare function noteref(converter: Converter, el: Element, roles: GndRole[]): void;
5
+ export declare function link(converter: Converter, el: Element, roles: GndRole[], aria: GndText | null): void;
@@ -0,0 +1,6 @@
1
+ export declare class IdAllocator {
2
+ private claimed;
3
+ private counters;
4
+ allocate(prefix: string, isTaken: (id: string) => boolean): string;
5
+ claim(id: string): boolean;
6
+ }
@@ -2,3 +2,6 @@ export type { GndRole, GndText, GndObject, GndDocument } from './types.js';
2
2
  export { makeGnd } from './makeGnd.js';
3
3
  export { parseMarkup } from './converter.js';
4
4
  export type { GndMediaType } from './dom.js';
5
+ export type { GndGenerationOptions, TextrefOptions } from './options.js';
6
+ export { encodeCssSelectorFragment, decodeCssSelectorFragment, encodeDomRangeFragment, decodeDomRangeFragment, decodeTextref, } from './textrefFragment.js';
7
+ export type { DomRangeJSON } from './textrefFragment.js';
@@ -1,8 +1,9 @@
1
1
  import { GndDocument } from './types.js';
2
2
  import { GndMediaType } from './dom.js';
3
+ import { GndGenerationOptions } from './options.js';
3
4
  export type { GndMediaType };
4
5
  /**
5
6
  * Builds a Guided Navigation document from an HTML or XHTML fragment or
6
7
  * document, following https://github.com/readium/guided-navigation.
7
8
  */
8
- export declare function makeGnd(input: string, mediaType?: GndMediaType): GndDocument;
9
+ export declare function makeGnd(input: string | Element, mediaType?: GndMediaType, options?: GndGenerationOptions): GndDocument;
@@ -0,0 +1,15 @@
1
+ import { GndRole } from './types.js';
2
+ export interface TextrefOptions {
3
+ roles?: boolean | GndRole[];
4
+ domRange?: boolean;
5
+ textFragment?: boolean;
6
+ }
7
+ export interface GndGenerationOptions {
8
+ textrefs?: boolean | GndRole[] | TextrefOptions;
9
+ }
10
+ export interface NormalizedTextrefOptions {
11
+ predicate: ((roles: GndRole[]) => boolean) | null;
12
+ domRange: boolean;
13
+ textFragment: boolean;
14
+ }
15
+ export declare function normalizeTextrefOptions(opt: GndGenerationOptions["textrefs"]): NormalizedTextrefOptions;
@@ -0,0 +1 @@
1
+ export declare function prescan(root: Element, ids: Map<string, Element>, suppressed: Set<Element>): void;
@@ -1,8 +1,3 @@
1
1
  import { GndRole } from './types.js';
2
- /**
3
- * Determines the Guided Navigation roles of an element, combining the roles
4
- * derived from the element type itself with the ones from its ARIA `role` and
5
- * `epub:type` attributes, e.g. `<section epub:type="chapter">` -> `[section, chapter]`.
6
- * An ARIA role of "presentation"/"none" strips the element of its native semantics.
7
- */
2
+ export declare function hasExplicitRole(el: Element): boolean;
8
3
  export declare function extractNodeRoles(el: Element): GndRole[];
@@ -0,0 +1,2 @@
1
+ export declare function selectorForElement(el: Element, docRoot: Document | null): string | undefined;
2
+ export declare function textrefForSelector(selector: string | undefined): string | undefined;
@@ -0,0 +1,2 @@
1
+ import { TextFragmentDirective } from './textrefFragment.js';
2
+ export declare function textFragmentDirectiveFor(range: Range): TextFragmentDirective | undefined;
@@ -0,0 +1,38 @@
1
+ export declare function encodeCssSelectorFragment(selector: string): string;
2
+ export declare function decodeCssSelectorFragment(textref: string | undefined): string | undefined;
3
+ export interface DomRangeJSON {
4
+ start: {
5
+ cssSelector: string;
6
+ textNodeIndex: number;
7
+ charOffset?: number;
8
+ };
9
+ end?: {
10
+ cssSelector: string;
11
+ textNodeIndex: number;
12
+ charOffset?: number;
13
+ };
14
+ }
15
+ export declare function encodeDomRangeFragment(domRange: DomRangeJSON): string;
16
+ export declare function decodeDomRangeFragment(textref: string | undefined): DomRangeJSON | undefined;
17
+ export interface TextFragmentDirective {
18
+ textStart: string;
19
+ textEnd?: string;
20
+ prefix?: string;
21
+ suffix?: string;
22
+ }
23
+ export declare function encodeTextFragmentDirective(fragment: TextFragmentDirective): string;
24
+ export declare function decodeTextFragmentDirective(textref: string | undefined): TextFragmentDirective | undefined;
25
+ export interface DecodedTextref {
26
+ cssSelector?: string;
27
+ domRange?: DomRangeJSON;
28
+ text?: {
29
+ highlight?: string;
30
+ before?: string;
31
+ after?: string;
32
+ };
33
+ fragment?: string;
34
+ }
35
+ export declare function decodeTextref(node: {
36
+ id?: string;
37
+ textref?: string;
38
+ } | undefined): DecodedTextref | undefined;