@readium/navigator 2.4.0-alpha.8 → 2.4.0-beta.1
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/dist/index.js +1809 -1055
- package/dist/index.umd.cjs +170 -23
- package/package.json +10 -10
- package/src/Navigator.ts +55 -1
- package/src/audio/AudioNavigator.ts +497 -0
- package/src/audio/AudioPoolManager.ts +120 -0
- package/src/audio/engine/AudioEngine.ts +26 -10
- package/src/audio/engine/PreservePitchProcessor.js +149 -0
- package/src/audio/engine/PreservePitchWorklet.ts +79 -0
- package/src/audio/engine/WebAudioEngine.ts +558 -259
- package/src/audio/index.ts +3 -1
- package/src/audio/preferences/AudioDefaults.ts +43 -0
- package/src/audio/preferences/AudioPreferences.ts +54 -0
- package/src/audio/preferences/AudioPreferencesEditor.ts +123 -0
- package/src/audio/preferences/AudioSettings.ts +36 -0
- package/src/audio/preferences/index.ts +4 -0
- package/src/epub/EpubNavigator.ts +2 -2
- package/src/epub/frame/FrameBlobBuilder.ts +33 -84
- package/src/epub/frame/FramePoolManager.ts +23 -18
- package/src/epub/fxl/FXLFrameManager.ts +4 -11
- package/src/epub/fxl/FXLFramePoolManager.ts +22 -26
- package/src/epub/preferences/EpubPreferences.ts +4 -4
- package/src/injection/Injector.ts +5 -5
- package/src/preferences/Configurable.ts +2 -3
- package/src/preferences/PreferencesEditor.ts +1 -1
- package/src/preferences/Types.ts +19 -0
- package/src/webpub/WebPubNavigator.ts +1 -2
- package/src/webpub/preferences/WebPubPreferences.ts +3 -3
- package/types/src/Navigator.d.ts +46 -0
- package/types/src/audio/AudioNavigator.d.ts +79 -0
- package/types/src/audio/AudioPoolManager.d.ts +52 -0
- package/types/src/audio/engine/AudioEngine.d.ts +21 -7
- package/types/src/audio/engine/PreservePitchWorklet.d.ts +18 -0
- package/types/src/audio/engine/WebAudioEngine.d.ts +52 -7
- package/types/src/audio/index.d.ts +2 -0
- package/types/src/audio/preferences/AudioDefaults.d.ts +21 -0
- package/types/src/audio/preferences/AudioPreferences.d.ts +23 -0
- package/types/src/audio/preferences/AudioPreferencesEditor.d.ts +19 -0
- package/types/src/audio/preferences/AudioSettings.d.ts +24 -0
- package/types/src/audio/preferences/index.d.ts +4 -0
- package/types/src/epub/EpubNavigator.d.ts +2 -2
- package/types/src/epub/frame/FrameBlobBuilder.d.ts +3 -6
- package/types/src/epub/fxl/FXLFrameManager.d.ts +0 -2
- package/types/src/epub/preferences/EpubPreferences.d.ts +2 -2
- package/types/src/preferences/Configurable.d.ts +2 -3
- package/types/src/preferences/PreferencesEditor.d.ts +1 -1
- package/types/src/preferences/Types.d.ts +3 -0
- package/types/src/webpub/WebPubNavigator.d.ts +2 -2
- package/types/src/webpub/preferences/WebPubPreferences.d.ts +2 -2
- package/LICENSE +0 -28
- package/src/divina/DivinaNavigator.ts +0 -0
- package/src/divina/index.ts +0 -0
- package/types/src/divina/DivinaNavigator.d.ts +0 -0
- package/types/src/divina/index.d.ts +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ModuleName } from "@readium/navigator-html-injectables";
|
|
2
2
|
import { Locator, Publication, ReadingProgression, Page, Link } from "@readium/shared";
|
|
3
3
|
import { FrameCommsListener } from "../frame";
|
|
4
|
-
import
|
|
4
|
+
import FrameBlobBuider from "../frame/FrameBlobBuilder";
|
|
5
5
|
import { FXLFrameManager } from "./FXLFrameManager";
|
|
6
6
|
import { FXLPeripherals } from "./FXLPeripherals";
|
|
7
7
|
import { FXLSpreader, Orientation, Spread } from "./FXLSpreader";
|
|
@@ -21,7 +21,7 @@ export class FXLFramePoolManager {
|
|
|
21
21
|
private readonly container: HTMLElement;
|
|
22
22
|
private readonly positions: Locator[];
|
|
23
23
|
private readonly pool: Map<string, FXLFrameManager> = new Map();
|
|
24
|
-
private readonly blobs: Map<string,
|
|
24
|
+
private readonly blobs: Map<string, string> = new Map();
|
|
25
25
|
private readonly inprogress: Map<string, Promise<void>> = new Map();
|
|
26
26
|
private readonly delayedShow: Map<string, Promise<void>> = new Map();
|
|
27
27
|
private readonly delayedTimeout: Map<string, number> = new Map();
|
|
@@ -86,18 +86,16 @@ export class FXLFramePoolManager {
|
|
|
86
86
|
|
|
87
87
|
this.peripherals = new FXLPeripherals(this);
|
|
88
88
|
|
|
89
|
-
const fragment = document.createDocumentFragment();
|
|
90
89
|
this.pub.readingOrder.items.forEach((link) => {
|
|
91
90
|
// Create <iframe>
|
|
92
91
|
const fm = new FXLFrameManager(this.peripherals, this.pub.metadata.effectiveReadingProgression, link.href, this.contentProtectionConfig, this.keyboardPeripheralsConfig);
|
|
92
|
+
this.spineElement.appendChild(fm.element);
|
|
93
93
|
|
|
94
|
+
// this.pages.push(fm);
|
|
94
95
|
this.pool.set(link.href, fm);
|
|
95
96
|
fm.width = 100 / this.length * (link.properties?.otherProperties["orientation"] === Orientation.landscape || link.properties?.otherProperties["addBlank"] ? this.perPage : 1);
|
|
96
97
|
fm.height = this.height;
|
|
97
|
-
fragment.appendChild(fm.wrapper);
|
|
98
98
|
});
|
|
99
|
-
this.spineElement.appendChild(fragment);
|
|
100
|
-
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
private _listener!: FrameCommsListener;
|
|
@@ -136,18 +134,15 @@ export class FXLFramePoolManager {
|
|
|
136
134
|
|
|
137
135
|
clearTimeout(this.resizeTimeout);
|
|
138
136
|
this.resizeTimeout = window.setTimeout(() => {
|
|
139
|
-
|
|
140
|
-
const itemLookup = new Map(this.pub.readingOrder.items.map(item => [item.href, item]));
|
|
137
|
+
// TODO optimize this expensive set of loops and operations
|
|
141
138
|
this.pool.forEach((frm, linkHref) => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
frm.update(this.spreadPosition(spread, link));
|
|
150
|
-
});
|
|
139
|
+
let i = this.pub.readingOrder.items.findIndex(l => l.href === linkHref);
|
|
140
|
+
const link = this.pub.readingOrder.items[i];
|
|
141
|
+
frm.width = 100 / this.length * (link.properties?.otherProperties["orientation"] === Orientation.landscape || link.properties?.otherProperties["addBlank"] ? this.perPage : 1);
|
|
142
|
+
frm.height = this.height;
|
|
143
|
+
if(!frm.loaded) return;
|
|
144
|
+
const spread = this.spreader.findByLink(link)!;
|
|
145
|
+
frm.update(this.spreadPosition(spread, link));
|
|
151
146
|
});
|
|
152
147
|
}, RESIZE_UPDATE_TIMEOUT);
|
|
153
148
|
}
|
|
@@ -410,8 +405,7 @@ export class FXLFramePoolManager {
|
|
|
410
405
|
this.pool.clear();
|
|
411
406
|
|
|
412
407
|
// Revoke all blobs
|
|
413
|
-
this.blobs.forEach(v =>
|
|
414
|
-
this.blobs.clear();
|
|
408
|
+
this.blobs.forEach(v => URL.revokeObjectURL(v));
|
|
415
409
|
|
|
416
410
|
// Clean up injector if it exists
|
|
417
411
|
this.injector?.dispose();
|
|
@@ -502,13 +496,13 @@ export class FXLFramePoolManager {
|
|
|
502
496
|
if(!this.pool.has(href)) return;
|
|
503
497
|
this.cancelShowing(href);
|
|
504
498
|
await this.pool.get(href)?.unload();
|
|
505
|
-
this.
|
|
499
|
+
// this.pool.delete(href);
|
|
506
500
|
});
|
|
507
501
|
|
|
508
502
|
// Check if base URL of publication has changed
|
|
509
503
|
if(this.currentBaseURL !== undefined && pub.baseURL !== this.currentBaseURL) {
|
|
510
504
|
// Revoke all blobs
|
|
511
|
-
this.blobs.forEach(v =>
|
|
505
|
+
this.blobs.forEach(v => URL.revokeObjectURL(v));
|
|
512
506
|
this.blobs.clear();
|
|
513
507
|
}
|
|
514
508
|
this.currentBaseURL = pub.baseURL;
|
|
@@ -518,14 +512,16 @@ export class FXLFramePoolManager {
|
|
|
518
512
|
const itm = pub.readingOrder.items[index];
|
|
519
513
|
if(!itm) return; // TODO throw?
|
|
520
514
|
if(!this.blobs.has(href)) {
|
|
521
|
-
|
|
515
|
+
const blobBuilder = new FrameBlobBuider(
|
|
522
516
|
pub,
|
|
523
517
|
this.currentBaseURL || "",
|
|
524
518
|
itm,
|
|
525
519
|
{
|
|
526
520
|
injector: this.injector
|
|
527
521
|
}
|
|
528
|
-
)
|
|
522
|
+
);
|
|
523
|
+
const blobURL = await blobBuilder.build(true);
|
|
524
|
+
this.blobs.set(href, blobURL);
|
|
529
525
|
}
|
|
530
526
|
|
|
531
527
|
// Show future offscreen frame in advance after a delay
|
|
@@ -539,7 +535,7 @@ export class FXLFramePoolManager {
|
|
|
539
535
|
const spread = this.makeSpread(this.reAlign(index));
|
|
540
536
|
const page = this.spreadPosition(spread, itm);
|
|
541
537
|
const fm = this.pool.get(href)!;
|
|
542
|
-
await fm.load(modules,
|
|
538
|
+
await fm.load(modules, this.blobs.get(href)!);
|
|
543
539
|
if(!this.peripherals.isScaled) // When scaled, positioning is screwed up, so wait to show
|
|
544
540
|
await fm.show(page); // Show/activate new frame
|
|
545
541
|
this.delayedShow.delete(href);
|
|
@@ -563,10 +559,10 @@ export class FXLFramePoolManager {
|
|
|
563
559
|
for (const s of spread) {
|
|
564
560
|
const newFrame = this.pool.get(s.href)!;
|
|
565
561
|
const source = this.blobs.get(s.href);
|
|
566
|
-
if(!source) continue; //
|
|
562
|
+
if(!source) continue; // This can get destroyed
|
|
567
563
|
|
|
568
564
|
this.cancelShowing(s.href);
|
|
569
|
-
await newFrame.load(modules,
|
|
565
|
+
await newFrame.load(modules, source); // In order to ensure modules match the latest configuration
|
|
570
566
|
await newFrame.show(this.spreadPosition(spread, s)); // Show/activate new frame
|
|
571
567
|
this.previousFrames.push(newFrame);
|
|
572
568
|
await newFrame.activate();
|
|
@@ -59,7 +59,7 @@ export interface IEpubPreferences {
|
|
|
59
59
|
wordSpacing?: number | null
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
export class EpubPreferences implements ConfigurablePreferences {
|
|
62
|
+
export class EpubPreferences implements ConfigurablePreferences<EpubPreferences> {
|
|
63
63
|
backgroundColor?: string | null;
|
|
64
64
|
blendFilter?: boolean | null;
|
|
65
65
|
constraint?: number | null;
|
|
@@ -160,7 +160,7 @@ export class EpubPreferences implements ConfigurablePreferences {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
merging(other:
|
|
163
|
+
merging(other: EpubPreferences): EpubPreferences {
|
|
164
164
|
const merged: IEpubPreferences = { ...this };
|
|
165
165
|
for (const key of Object.keys(other) as (keyof IEpubPreferences)[]) {
|
|
166
166
|
if (
|
|
@@ -172,11 +172,11 @@ export class EpubPreferences implements ConfigurablePreferences {
|
|
|
172
172
|
) &&
|
|
173
173
|
(
|
|
174
174
|
key !== "minimalLineLength" ||
|
|
175
|
-
other[key] === null ||
|
|
175
|
+
other[key] === null ||
|
|
176
176
|
(other[key] <= (other.optimalLineLength ?? merged.optimalLineLength ?? 65))
|
|
177
177
|
)
|
|
178
178
|
) {
|
|
179
|
-
merged[key] = other[key];
|
|
179
|
+
(merged as Record<string, unknown>)[key] = other[key];
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
return new EpubPreferences(merged);
|
|
@@ -155,7 +155,7 @@ export class Injector implements IInjector {
|
|
|
155
155
|
return [...this.allowedDomains]; // Return a copy to prevent external modification
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
public async injectForDocument(doc: Document, link: Link): Promise<void> {
|
|
158
|
+
public async injectForDocument(doc: Document, link: Link): Promise<void> {
|
|
159
159
|
for (const rule of this.rules) {
|
|
160
160
|
if (this.matchesRule(rule, link)) {
|
|
161
161
|
await this.applyRule(doc, rule);
|
|
@@ -178,7 +178,7 @@ export class Injector implements IInjector {
|
|
|
178
178
|
private async getOrCreateBlobUrl(resource: IInjectable): Promise<string> {
|
|
179
179
|
// Use the injectable ID as the cache key
|
|
180
180
|
const cacheKey = resource.id!; // ID is guaranteed to exist after constructor
|
|
181
|
-
|
|
181
|
+
|
|
182
182
|
if (this.blobStore.has(cacheKey)) {
|
|
183
183
|
const entry = this.blobStore.get(cacheKey)!;
|
|
184
184
|
entry.refCount++;
|
|
@@ -191,7 +191,7 @@ export class Injector implements IInjector {
|
|
|
191
191
|
this.createdBlobUrls.add(url);
|
|
192
192
|
return url;
|
|
193
193
|
}
|
|
194
|
-
|
|
194
|
+
|
|
195
195
|
throw new Error("Resource must have a blob property");
|
|
196
196
|
}
|
|
197
197
|
|
|
@@ -304,13 +304,13 @@ export class Injector implements IInjector {
|
|
|
304
304
|
let url: string | null = null;
|
|
305
305
|
try {
|
|
306
306
|
url = await this.getResourceUrl(resource, doc);
|
|
307
|
-
|
|
307
|
+
|
|
308
308
|
if (resource.rel === "preload" && "url" in resource) {
|
|
309
309
|
this.createPreloadLink(doc, resource, url);
|
|
310
310
|
} else {
|
|
311
311
|
const element = this.createElement(doc, resource, url);
|
|
312
312
|
createdElements.push({ element, url });
|
|
313
|
-
|
|
313
|
+
|
|
314
314
|
if (position === "prepend") {
|
|
315
315
|
target.prepend(element);
|
|
316
316
|
} else {
|
|
@@ -4,9 +4,8 @@ export interface ConfigurableSettings {
|
|
|
4
4
|
[key: string]: any;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export interface ConfigurablePreferences {
|
|
8
|
-
|
|
9
|
-
merging(other: ConfigurablePreferences): ConfigurablePreferences;
|
|
7
|
+
export interface ConfigurablePreferences<T> {
|
|
8
|
+
merging(other: T): T;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
export interface Configurable<ConfigurableSettings, ConfigurablePreferences> {
|
package/src/preferences/Types.ts
CHANGED
|
@@ -16,6 +16,8 @@ export type RangeConfig = {
|
|
|
16
16
|
step: number
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// ReadiumCSS preferences
|
|
20
|
+
|
|
19
21
|
export const filterRangeConfig: RangeConfig = {
|
|
20
22
|
range: [0, 100],
|
|
21
23
|
step: 1
|
|
@@ -69,4 +71,21 @@ export const wordSpacingRangeConfig: RangeConfig = {
|
|
|
69
71
|
export const zoomRangeConfig: RangeConfig = {
|
|
70
72
|
range: [0.7, 4],
|
|
71
73
|
step: 0.05
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Audio preferences
|
|
77
|
+
|
|
78
|
+
export const volumeRangeConfig: RangeConfig = {
|
|
79
|
+
range: [0, 1],
|
|
80
|
+
step: 0.1
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const playbackRateRangeConfig: RangeConfig = {
|
|
84
|
+
range: [0.5, 2],
|
|
85
|
+
step: 0.25
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const skipIntervalRangeConfig: RangeConfig = {
|
|
89
|
+
range: [5, 60],
|
|
90
|
+
step: 5
|
|
72
91
|
}
|
|
@@ -12,7 +12,6 @@ import { WebUserProperties, WebRSProperties } from "./css/Properties";
|
|
|
12
12
|
import { IWebPubPreferences, WebPubPreferences } from "./preferences/WebPubPreferences";
|
|
13
13
|
import { IWebPubDefaults, WebPubDefaults } from "./preferences/WebPubDefaults";
|
|
14
14
|
import { WebPubSettings } from "./preferences/WebPubSettings";
|
|
15
|
-
import { IPreferencesEditor } from "../preferences/PreferencesEditor";
|
|
16
15
|
import { WebPubPreferencesEditor } from "./preferences/WebPubPreferencesEditor";
|
|
17
16
|
import { Injector } from "../injection/Injector";
|
|
18
17
|
import { createReadiumWebPubRules } from "../injection/webpubInjectables";
|
|
@@ -181,7 +180,7 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
|
|
|
181
180
|
return Object.freeze({ ...this._settings });
|
|
182
181
|
}
|
|
183
182
|
|
|
184
|
-
public get preferencesEditor():
|
|
183
|
+
public get preferencesEditor(): WebPubPreferencesEditor {
|
|
185
184
|
if (this._preferencesEditor === null) {
|
|
186
185
|
this._preferencesEditor = new WebPubPreferencesEditor(this._preferences, this.settings, this.pub.metadata);
|
|
187
186
|
}
|
|
@@ -32,7 +32,7 @@ export interface IWebPubPreferences {
|
|
|
32
32
|
zoom?: number | null
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export class WebPubPreferences implements ConfigurablePreferences {
|
|
35
|
+
export class WebPubPreferences implements ConfigurablePreferences<WebPubPreferences> {
|
|
36
36
|
fontFamily?: string | null;
|
|
37
37
|
fontWeight?: number | null;
|
|
38
38
|
hyphens?: boolean | null;
|
|
@@ -82,11 +82,11 @@ export class WebPubPreferences implements ConfigurablePreferences {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
merging(other:
|
|
85
|
+
merging(other: WebPubPreferences): WebPubPreferences {
|
|
86
86
|
const merged: IWebPubPreferences = { ...this };
|
|
87
87
|
for (const key of Object.keys(other) as (keyof IWebPubPreferences)[]) {
|
|
88
88
|
if (other[key] !== undefined) {
|
|
89
|
-
merged[key] = other[key];
|
|
89
|
+
(merged as Record<string, unknown>)[key] = other[key];
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
return new WebPubPreferences(merged);
|
package/types/src/Navigator.d.ts
CHANGED
|
@@ -61,4 +61,50 @@ export declare abstract class VisualNavigator extends Navigator {
|
|
|
61
61
|
*/
|
|
62
62
|
goRight(animated: boolean | undefined, completion: cbb): void;
|
|
63
63
|
}
|
|
64
|
+
export declare abstract class MediaNavigator extends Navigator {
|
|
65
|
+
/**
|
|
66
|
+
* Current playback state - is media currently playing?
|
|
67
|
+
*/
|
|
68
|
+
abstract get isPlaying(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Current playback state - is media currently paused?
|
|
71
|
+
*/
|
|
72
|
+
abstract get isPaused(): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Duration of current media resource in seconds
|
|
75
|
+
*/
|
|
76
|
+
abstract get duration(): number;
|
|
77
|
+
/**
|
|
78
|
+
* Current time in seconds within the media resource
|
|
79
|
+
*/
|
|
80
|
+
abstract get currentTime(): number;
|
|
81
|
+
/**
|
|
82
|
+
* Play the current media resource
|
|
83
|
+
*/
|
|
84
|
+
abstract play(): void;
|
|
85
|
+
/**
|
|
86
|
+
* Pause the currently playing media
|
|
87
|
+
*/
|
|
88
|
+
abstract pause(): void;
|
|
89
|
+
/**
|
|
90
|
+
* Stop playback and reset to beginning
|
|
91
|
+
*/
|
|
92
|
+
abstract stop(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Seek to specific time in seconds
|
|
95
|
+
*/
|
|
96
|
+
abstract seek(time: number): void;
|
|
97
|
+
/**
|
|
98
|
+
* Jump forward or backward by specified seconds
|
|
99
|
+
*/
|
|
100
|
+
abstract jump(seconds: number): void;
|
|
101
|
+
/**
|
|
102
|
+
* Skip forward by the configured interval
|
|
103
|
+
*/
|
|
104
|
+
abstract skipForward(): void;
|
|
105
|
+
/**
|
|
106
|
+
* Skip backward by the configured interval
|
|
107
|
+
*/
|
|
108
|
+
abstract skipBackward(): void;
|
|
109
|
+
}
|
|
64
110
|
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Link, Locator, Publication } from "@readium/shared";
|
|
2
|
+
import { MediaNavigator } from "../Navigator";
|
|
3
|
+
import { Configurable } from "../preferences";
|
|
4
|
+
import { AudioPreferences, AudioSettings, AudioPreferencesEditor, IAudioPreferences, IAudioDefaults } from "./preferences";
|
|
5
|
+
export interface AudioNavigatorListeners {
|
|
6
|
+
trackLoaded: (media: HTMLMediaElement) => void;
|
|
7
|
+
positionChanged: (locator: Locator) => void;
|
|
8
|
+
error: (error: any, locator: Locator) => void;
|
|
9
|
+
trackEnded: (locator: Locator) => void;
|
|
10
|
+
play: (locator: Locator) => void;
|
|
11
|
+
pause: (locator: Locator) => void;
|
|
12
|
+
metadataLoaded: (duration: number) => void;
|
|
13
|
+
stalled: (isStalled: boolean) => void;
|
|
14
|
+
seeking: (isSeeking: boolean) => void;
|
|
15
|
+
seekable: (seekable: TimeRanges) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface AudioNavigatorConfiguration {
|
|
18
|
+
preferences: IAudioPreferences;
|
|
19
|
+
defaults: IAudioDefaults;
|
|
20
|
+
}
|
|
21
|
+
export declare class AudioNavigator extends MediaNavigator implements Configurable<AudioSettings, AudioPreferences> {
|
|
22
|
+
private readonly pub;
|
|
23
|
+
private positionPollInterval;
|
|
24
|
+
private navigationId;
|
|
25
|
+
private listeners;
|
|
26
|
+
private currentLocation;
|
|
27
|
+
private _preferences;
|
|
28
|
+
private _defaults;
|
|
29
|
+
private _settings;
|
|
30
|
+
private _preferencesEditor;
|
|
31
|
+
private pool;
|
|
32
|
+
constructor(publication: Publication, listeners: AudioNavigatorListeners, initialPosition?: Locator, configuration?: AudioNavigatorConfiguration);
|
|
33
|
+
get settings(): AudioSettings;
|
|
34
|
+
get preferencesEditor(): AudioPreferencesEditor;
|
|
35
|
+
submitPreferences(preferences: AudioPreferences): Promise<void>;
|
|
36
|
+
private applyPreferences;
|
|
37
|
+
get publication(): Publication;
|
|
38
|
+
private ensureLocatorLocations;
|
|
39
|
+
/** Resolves a bare href (no fragment) to its index in the reading order. Returns -1 if not found. */
|
|
40
|
+
private hrefToTrackIndex;
|
|
41
|
+
/** Current track index derived from the current location's href. */
|
|
42
|
+
private currentTrackIndex;
|
|
43
|
+
get currentLocator(): Locator;
|
|
44
|
+
get isPlaying(): boolean;
|
|
45
|
+
get isPaused(): boolean;
|
|
46
|
+
get duration(): number;
|
|
47
|
+
get currentTime(): number;
|
|
48
|
+
private createLocator;
|
|
49
|
+
/**
|
|
50
|
+
* Waits for the current audio to be ready to play, then seeks to seekTime if > 0.
|
|
51
|
+
* Rejects if an error event fires before the audio is ready.
|
|
52
|
+
* When navId is provided, skips the seek if that navigation has been superseded.
|
|
53
|
+
*/
|
|
54
|
+
private waitForLoadedAndSeeked;
|
|
55
|
+
private setupEventListeners;
|
|
56
|
+
private setupMediaSession;
|
|
57
|
+
private updateMediaSessionMetadata;
|
|
58
|
+
private startPositionPolling;
|
|
59
|
+
private stopPositionPolling;
|
|
60
|
+
go(locator: Locator, _animated: boolean, cb: (ok: boolean) => void): Promise<void>;
|
|
61
|
+
goLink(link: Link, _animated: boolean, cb: (ok: boolean) => void): Promise<void>;
|
|
62
|
+
goForward(_animated: boolean, cb: (ok: boolean) => void): Promise<void>;
|
|
63
|
+
goBackward(_animated: boolean, cb: (ok: boolean) => void): Promise<void>;
|
|
64
|
+
play(): void;
|
|
65
|
+
pause(): void;
|
|
66
|
+
stop(): void;
|
|
67
|
+
private nextTrack;
|
|
68
|
+
private previousTrack;
|
|
69
|
+
seek(time: number): void;
|
|
70
|
+
jump(seconds: number): void;
|
|
71
|
+
skipForward(): void;
|
|
72
|
+
skipBackward(): void;
|
|
73
|
+
get isTrackStart(): boolean;
|
|
74
|
+
get isTrackEnd(): boolean;
|
|
75
|
+
get canGoBackward(): boolean;
|
|
76
|
+
get canGoForward(): boolean;
|
|
77
|
+
private destroyMediaSession;
|
|
78
|
+
destroy(): void;
|
|
79
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Publication } from "@readium/shared";
|
|
2
|
+
import { WebAudioEngine } from "./engine/WebAudioEngine";
|
|
3
|
+
export declare class AudioPoolManager {
|
|
4
|
+
private preloadedElements;
|
|
5
|
+
private _audioEngine;
|
|
6
|
+
constructor(audioEngine: WebAudioEngine);
|
|
7
|
+
get audioEngine(): WebAudioEngine;
|
|
8
|
+
/**
|
|
9
|
+
* Sets the current audio by href, using preloaded element if available or loading otherwise,
|
|
10
|
+
* and preloads adjacent tracks.
|
|
11
|
+
* @param href The URL of the audio resource.
|
|
12
|
+
* @param publication The publication containing the reading order.
|
|
13
|
+
* @param currentIndex The current track index.
|
|
14
|
+
* @param direction The navigation direction ('forward' or 'backward').
|
|
15
|
+
*/
|
|
16
|
+
setCurrentAudio(href: string, publication: Publication, currentIndex: number, direction: 'forward' | 'backward'): void;
|
|
17
|
+
preload(href: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Retrieves a preloaded audio element by URL.
|
|
20
|
+
* @param href The URL of the audio resource.
|
|
21
|
+
* @returns The preloaded HTMLAudioElement, or undefined if not preloaded.
|
|
22
|
+
*/
|
|
23
|
+
get(href: string): HTMLAudioElement | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Removes a preloaded element from the pool.
|
|
26
|
+
* @param href The URL of the audio resource.
|
|
27
|
+
*/
|
|
28
|
+
clear(href: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Preloads the next track in the reading order.
|
|
31
|
+
* @param publication The publication containing the reading order.
|
|
32
|
+
* @param currentIndex The current track index.
|
|
33
|
+
*/
|
|
34
|
+
preloadNext(publication: Publication, currentIndex: number): void;
|
|
35
|
+
/**
|
|
36
|
+
* Preloads the previous track in the reading order.
|
|
37
|
+
* @param publication The publication containing the reading order.
|
|
38
|
+
* @param currentIndex The current track index.
|
|
39
|
+
*/
|
|
40
|
+
preloadPrevious(publication: Publication, currentIndex: number): void;
|
|
41
|
+
/**
|
|
42
|
+
* Preloads adjacent tracks (previous and next) for smoother navigation.
|
|
43
|
+
* @param publication The publication containing the reading order.
|
|
44
|
+
* @param currentIndex The current track index.
|
|
45
|
+
* @param direction The navigation direction ('forward' or 'backward').
|
|
46
|
+
*/
|
|
47
|
+
preloadAdjacent(publication: Publication, currentIndex: number, direction?: 'forward' | 'backward'): void;
|
|
48
|
+
/**
|
|
49
|
+
* Destroys the pool by stopping the engine and clearing all preloaded elements.
|
|
50
|
+
*/
|
|
51
|
+
destroy(): void;
|
|
52
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { Locator } from '@readium/shared';
|
|
2
|
-
import { Publication } from '@readium/shared';
|
|
3
1
|
/**
|
|
4
2
|
* Initial state of the audio engine playback.
|
|
5
3
|
*/
|
|
@@ -43,9 +41,10 @@ export interface AudioEngine {
|
|
|
43
41
|
*/
|
|
44
42
|
playback: Playback;
|
|
45
43
|
/**
|
|
46
|
-
*
|
|
44
|
+
* Loads the audio resource at the given URL.
|
|
45
|
+
* @param url The URL of the audio resource.
|
|
47
46
|
*/
|
|
48
|
-
|
|
47
|
+
loadAudio(url: string): void;
|
|
49
48
|
/**
|
|
50
49
|
* Adds an event listener to the audio engine.
|
|
51
50
|
* @param event The event name to listen.
|
|
@@ -59,10 +58,10 @@ export interface AudioEngine {
|
|
|
59
58
|
*/
|
|
60
59
|
off(event: string, callback: (data: any) => void): void;
|
|
61
60
|
/**
|
|
62
|
-
*
|
|
63
|
-
* @param
|
|
61
|
+
* Sets the media element for playback, enabling use of preloaded elements from the pool.
|
|
62
|
+
* @param element The HTML audio element to use for playback.
|
|
64
63
|
*/
|
|
65
|
-
|
|
64
|
+
setMediaElement(element: HTMLAudioElement): void;
|
|
66
65
|
/**
|
|
67
66
|
* Plays the current audio resource.
|
|
68
67
|
*/
|
|
@@ -83,6 +82,17 @@ export interface AudioEngine {
|
|
|
83
82
|
* Returns the duration of the audio resource.
|
|
84
83
|
*/
|
|
85
84
|
duration(): number;
|
|
85
|
+
/**
|
|
86
|
+
* Sets the volume of the audio resource.
|
|
87
|
+
* @param volume The volume to set, in the range [0, 1].
|
|
88
|
+
*/
|
|
89
|
+
setVolume(volume: number): void;
|
|
90
|
+
/**
|
|
91
|
+
* Sets the playback rate of the audio resource.
|
|
92
|
+
* @param rate The playback rate to set.
|
|
93
|
+
* @param preservePitch Whether to preserve pitch when changing playback rate.
|
|
94
|
+
*/
|
|
95
|
+
setPlaybackRate(rate: number, preservePitch: boolean): void;
|
|
86
96
|
/**
|
|
87
97
|
* Returns whether the audio resource is currently playing.
|
|
88
98
|
*/
|
|
@@ -91,6 +101,10 @@ export interface AudioEngine {
|
|
|
91
101
|
* Returns whether the audio resource is currently paused.
|
|
92
102
|
*/
|
|
93
103
|
isPaused(): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Returns the HTML media element used for playback.
|
|
106
|
+
*/
|
|
107
|
+
getMediaElement(): HTMLMediaElement;
|
|
94
108
|
/**
|
|
95
109
|
* Returns whether the audio resource is currently stopped.
|
|
96
110
|
*/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface PreservePitchWorkletOptions {
|
|
2
|
+
ctx: AudioContext;
|
|
3
|
+
mediaElement?: HTMLMediaElement;
|
|
4
|
+
pitchFactor?: number;
|
|
5
|
+
modulePath?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class PreservePitchWorklet {
|
|
8
|
+
mediaElement: HTMLMediaElement | null;
|
|
9
|
+
source: MediaElementAudioSourceNode | null;
|
|
10
|
+
ctx: AudioContext;
|
|
11
|
+
workletNode: AudioWorkletNode | null;
|
|
12
|
+
url: string | null;
|
|
13
|
+
static createWorklet(options: PreservePitchWorkletOptions): Promise<PreservePitchWorklet>;
|
|
14
|
+
constructor(ctx: AudioContext);
|
|
15
|
+
updatePitchFactor(factor: number): void;
|
|
16
|
+
destroy(): void;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { AudioEngine, Playback } from "./AudioEngine";
|
|
2
|
-
import { Publication } from "@readium/shared";
|
|
3
|
-
import { Locator } from "@readium/shared";
|
|
4
2
|
type EventCallback = (data: any) => void;
|
|
5
3
|
export declare class WebAudioEngine implements AudioEngine {
|
|
6
4
|
readonly playback: Playback;
|
|
@@ -9,15 +7,33 @@ export declare class WebAudioEngine implements AudioEngine {
|
|
|
9
7
|
private sourceNode;
|
|
10
8
|
private gainNode;
|
|
11
9
|
private listeners;
|
|
10
|
+
private currentPlaybackRate;
|
|
12
11
|
private isMutedValue;
|
|
13
12
|
private isPlayingValue;
|
|
14
13
|
private isPausedValue;
|
|
15
14
|
private isLoadingValue;
|
|
16
15
|
private isLoadedValue;
|
|
17
16
|
private isEndedValue;
|
|
17
|
+
private isStoppedValue;
|
|
18
|
+
private worklet;
|
|
19
|
+
private webAudioActive;
|
|
20
|
+
private readonly boundOnCanPlayThrough;
|
|
21
|
+
private readonly boundOnTimeUpdate;
|
|
22
|
+
private readonly boundOnError;
|
|
23
|
+
private readonly boundOnEnded;
|
|
24
|
+
private readonly boundOnStalled;
|
|
25
|
+
private readonly boundOnEmptied;
|
|
26
|
+
private readonly boundOnSuspend;
|
|
27
|
+
private readonly boundOnWaiting;
|
|
28
|
+
private readonly boundOnLoadedMetadata;
|
|
29
|
+
private readonly boundOnSeeking;
|
|
30
|
+
private readonly boundOnSeeked;
|
|
31
|
+
private readonly boundOnPlay;
|
|
32
|
+
private readonly boundOnPlaying;
|
|
33
|
+
private readonly boundOnPause;
|
|
34
|
+
private readonly boundOnProgress;
|
|
18
35
|
constructor(values: {
|
|
19
36
|
playback: Playback;
|
|
20
|
-
audioContext: AudioContext;
|
|
21
37
|
});
|
|
22
38
|
/**
|
|
23
39
|
* Adds an event listener to the audio engine.
|
|
@@ -36,16 +52,30 @@ export declare class WebAudioEngine implements AudioEngine {
|
|
|
36
52
|
* @param url The URL of the audio resource.
|
|
37
53
|
* */
|
|
38
54
|
loadAudio(url: string): void;
|
|
55
|
+
private deactivateWebAudio;
|
|
56
|
+
/**
|
|
57
|
+
* Sets the media element for playback.
|
|
58
|
+
* @param element The HTML audio element to use.
|
|
59
|
+
*/
|
|
60
|
+
setMediaElement(element: HTMLAudioElement): void;
|
|
39
61
|
private ensureAudioContextRunning;
|
|
62
|
+
private getOrCreateAudioContext;
|
|
40
63
|
private onTimeUpdate;
|
|
41
64
|
private onCanPlayThrough;
|
|
42
65
|
private onError;
|
|
43
66
|
private onEnded;
|
|
67
|
+
private onStalled;
|
|
68
|
+
private onEmptied;
|
|
69
|
+
private onSuspend;
|
|
70
|
+
private onWaiting;
|
|
71
|
+
private onLoadedMetadata;
|
|
72
|
+
private onSeeking;
|
|
73
|
+
private onSeeked;
|
|
74
|
+
private onPlay;
|
|
75
|
+
private onPlaying;
|
|
76
|
+
private onPause;
|
|
77
|
+
private onProgress;
|
|
44
78
|
private emit;
|
|
45
|
-
/**
|
|
46
|
-
* Plays the audio resource at the given locator.
|
|
47
|
-
*/
|
|
48
|
-
playLocator(_publication: Publication, _locator: Locator): Promise<void>;
|
|
49
79
|
/**
|
|
50
80
|
* Plays the current audio resource.
|
|
51
81
|
*/
|
|
@@ -103,5 +133,20 @@ export declare class WebAudioEngine implements AudioEngine {
|
|
|
103
133
|
* Returns whether the audio resource is currently muted.
|
|
104
134
|
*/
|
|
105
135
|
isMuted(): boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Sets the playback rate of the audio resource with pitch preservation.
|
|
138
|
+
*/
|
|
139
|
+
setPlaybackRate(rate: number, preservePitch: boolean): void;
|
|
140
|
+
/**
|
|
141
|
+
* Activates the Web Audio graph for the current media element.
|
|
142
|
+
* Sets crossOrigin = "anonymous" and reloads so MediaElementAudioSourceNode can be used.
|
|
143
|
+
* No-ops if Web Audio is already active.
|
|
144
|
+
*/
|
|
145
|
+
private activateWebAudio;
|
|
146
|
+
get isWebAudioActive(): boolean;
|
|
147
|
+
/**
|
|
148
|
+
* Returns the HTML media element used for playback.
|
|
149
|
+
*/
|
|
150
|
+
getMediaElement(): HTMLMediaElement;
|
|
106
151
|
}
|
|
107
152
|
export {};
|