@readium/navigator 2.4.0-beta.1 → 2.4.0-beta.10
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 +723 -647
- package/dist/index.umd.cjs +22 -22
- package/package.json +1 -1
- package/src/audio/AudioNavigator.ts +120 -16
- package/src/audio/AudioPoolManager.ts +93 -83
- package/src/audio/engine/AudioEngine.ts +0 -11
- package/src/audio/engine/WebAudioEngine.ts +36 -54
- package/src/audio/protection/AudioNavigatorProtector.ts +38 -0
- package/src/epub/frame/FrameManager.ts +1 -1
- package/src/epub/fxl/FXLFrameManager.ts +1 -1
- package/src/preferences/Types.ts +2 -2
- package/src/protection/CopyProtector.ts +22 -0
- package/src/protection/DevToolsDetector.ts +1 -0
- package/src/protection/DragAndDropProtector.ts +34 -0
- package/src/protection/NavigatorProtector.ts +3 -3
- package/src/webpub/WebPubFrameManager.ts +1 -1
- package/src/webpub/WebPubNavigator.ts +6 -2
- package/types/src/audio/AudioNavigator.d.ts +18 -2
- package/types/src/audio/AudioPoolManager.d.ts +15 -39
- package/types/src/audio/engine/AudioEngine.d.ts +0 -9
- package/types/src/audio/engine/WebAudioEngine.d.ts +1 -5
- package/types/src/audio/protection/AudioNavigatorProtector.d.ts +8 -0
- package/types/src/protection/CopyProtector.d.ts +8 -0
- package/types/src/protection/DragAndDropProtector.d.ts +10 -0
- package/types/src/protection/NavigatorProtector.d.ts +1 -1
|
@@ -16,6 +16,7 @@ export class WebAudioEngine implements AudioEngine {
|
|
|
16
16
|
private sourceNode: MediaElementAudioSourceNode | null = null;
|
|
17
17
|
private gainNode: GainNode | null = null;
|
|
18
18
|
private listeners: { [event: string]: EventCallback[] } = {};
|
|
19
|
+
private currentVolume: number = 1;
|
|
19
20
|
private currentPlaybackRate: number = 1;
|
|
20
21
|
private isMutedValue: boolean = false;
|
|
21
22
|
private isPlayingValue: boolean = false;
|
|
@@ -48,7 +49,6 @@ export class WebAudioEngine implements AudioEngine {
|
|
|
48
49
|
|
|
49
50
|
// crossOrigin is set lazily in activateWebAudio() only when the worklet is needed
|
|
50
51
|
this.mediaElement = document.createElement("audio");
|
|
51
|
-
this.setVolume(this.playback.state.volume);
|
|
52
52
|
|
|
53
53
|
// Event listeners (to report the client app about some async events)
|
|
54
54
|
this.mediaElement.addEventListener("canplaythrough", this.boundOnCanPlayThrough);
|
|
@@ -95,44 +95,6 @@ export class WebAudioEngine implements AudioEngine {
|
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
/**
|
|
99
|
-
* Load the audio resource at the given URL.
|
|
100
|
-
* @param url The URL of the audio resource.
|
|
101
|
-
* */
|
|
102
|
-
public loadAudio(url: string): void {
|
|
103
|
-
this.isLoadingValue = true;
|
|
104
|
-
this.isLoadedValue = false;
|
|
105
|
-
this.isPlayingValue = false;
|
|
106
|
-
this.isPausedValue = false;
|
|
107
|
-
|
|
108
|
-
if (this.webAudioActive) {
|
|
109
|
-
this.mediaElement.crossOrigin = "anonymous";
|
|
110
|
-
this.mediaElement.src = url;
|
|
111
|
-
this.mediaElement.load();
|
|
112
|
-
|
|
113
|
-
// If the server doesn't honour the CORS preflight, fall back to a
|
|
114
|
-
// non-CORS load and tear down the Web Audio graph so the element
|
|
115
|
-
// is never passed to MediaElementAudioSourceNode in a tainted state.
|
|
116
|
-
const cleanup = () => {
|
|
117
|
-
this.mediaElement.removeEventListener("error", onCORSError);
|
|
118
|
-
this.mediaElement.removeEventListener("canplaythrough", onCORSSuccess);
|
|
119
|
-
};
|
|
120
|
-
const onCORSError = () => {
|
|
121
|
-
cleanup();
|
|
122
|
-
this.deactivateWebAudio();
|
|
123
|
-
this.mediaElement.removeAttribute("crossOrigin");
|
|
124
|
-
this.mediaElement.src = url;
|
|
125
|
-
this.mediaElement.load();
|
|
126
|
-
};
|
|
127
|
-
const onCORSSuccess = () => cleanup();
|
|
128
|
-
this.mediaElement.addEventListener("error", onCORSError);
|
|
129
|
-
this.mediaElement.addEventListener("canplaythrough", onCORSSuccess);
|
|
130
|
-
} else {
|
|
131
|
-
this.mediaElement.src = url;
|
|
132
|
-
this.mediaElement.load();
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
98
|
private deactivateWebAudio(): void {
|
|
137
99
|
if (this.worklet) {
|
|
138
100
|
this.worklet.destroy();
|
|
@@ -154,18 +116,7 @@ export class WebAudioEngine implements AudioEngine {
|
|
|
154
116
|
* @param element The HTML audio element to use.
|
|
155
117
|
*/
|
|
156
118
|
public setMediaElement(element: HTMLAudioElement): void {
|
|
157
|
-
//
|
|
158
|
-
this.mediaElement.pause();
|
|
159
|
-
this.isPlayingValue = false;
|
|
160
|
-
this.isPausedValue = false;
|
|
161
|
-
|
|
162
|
-
// Disconnect old source node if it exists
|
|
163
|
-
if (this.sourceNode) {
|
|
164
|
-
this.sourceNode.disconnect();
|
|
165
|
-
this.sourceNode = null;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Remove old event listeners from current mediaElement
|
|
119
|
+
// Remove listeners BEFORE pausing so the pause doesn't leak through
|
|
169
120
|
this.mediaElement.removeEventListener("canplaythrough", this.boundOnCanPlayThrough);
|
|
170
121
|
this.mediaElement.removeEventListener("timeupdate", this.boundOnTimeUpdate);
|
|
171
122
|
this.mediaElement.removeEventListener("error", this.boundOnError);
|
|
@@ -182,6 +133,17 @@ export class WebAudioEngine implements AudioEngine {
|
|
|
182
133
|
this.mediaElement.removeEventListener("pause", this.boundOnPause);
|
|
183
134
|
this.mediaElement.removeEventListener("progress", this.boundOnProgress);
|
|
184
135
|
|
|
136
|
+
// Now safe to pause the outgoing element
|
|
137
|
+
this.mediaElement.pause();
|
|
138
|
+
this.isPlayingValue = false;
|
|
139
|
+
this.isPausedValue = false;
|
|
140
|
+
|
|
141
|
+
// Disconnect old source node if it exists
|
|
142
|
+
if (this.sourceNode) {
|
|
143
|
+
this.sourceNode.disconnect();
|
|
144
|
+
this.sourceNode = null;
|
|
145
|
+
}
|
|
146
|
+
|
|
185
147
|
// Set new media element
|
|
186
148
|
this.mediaElement = element;
|
|
187
149
|
|
|
@@ -203,9 +165,29 @@ export class WebAudioEngine implements AudioEngine {
|
|
|
203
165
|
this.mediaElement.addEventListener("progress", this.boundOnProgress);
|
|
204
166
|
|
|
205
167
|
// Re-apply current volume and playback rate to the new element
|
|
206
|
-
this.mediaElement.volume = this.isMutedValue ? 0 : this.
|
|
168
|
+
this.mediaElement.volume = this.isMutedValue ? 0 : this.currentVolume;
|
|
207
169
|
this.mediaElement.playbackRate = this.currentPlaybackRate;
|
|
208
170
|
|
|
171
|
+
// Reconnect the Web Audio graph to the new element
|
|
172
|
+
if (this.webAudioActive) {
|
|
173
|
+
try {
|
|
174
|
+
const ctx = this.getOrCreateAudioContext();
|
|
175
|
+
this.sourceNode = new MediaElementAudioSourceNode(ctx, { mediaElement: this.mediaElement });
|
|
176
|
+
if (!this.gainNode) {
|
|
177
|
+
this.gainNode = ctx.createGain();
|
|
178
|
+
this.gainNode.connect(ctx.destination);
|
|
179
|
+
}
|
|
180
|
+
if (this.worklet?.workletNode) {
|
|
181
|
+
this.sourceNode.connect(this.worklet.workletNode);
|
|
182
|
+
} else {
|
|
183
|
+
this.sourceNode.connect(this.gainNode);
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
// CORS failed on this element — deactivate Web Audio gracefully
|
|
187
|
+
this.deactivateWebAudio();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
209
191
|
// Check if metadata is already loaded (common with preloaded elements)
|
|
210
192
|
if (this.mediaElement.readyState >= 1) {
|
|
211
193
|
this.onLoadedMetadata(new Event('loadedmetadata'));
|
|
@@ -370,23 +352,23 @@ export class WebAudioEngine implements AudioEngine {
|
|
|
370
352
|
*/
|
|
371
353
|
public setVolume(volume: number): void {
|
|
372
354
|
if (volume < 0) {
|
|
355
|
+
this.currentVolume = 0;
|
|
373
356
|
this.mediaElement.volume = 0;
|
|
374
357
|
if (this.gainNode) {
|
|
375
358
|
this.gainNode.gain.value = 0;
|
|
376
359
|
}
|
|
377
360
|
this.isMutedValue = true;
|
|
378
|
-
this.playback.state.volume = 0;
|
|
379
361
|
return;
|
|
380
362
|
}
|
|
381
363
|
if (volume > 1) {
|
|
382
364
|
this.setVolume(volume / 100);
|
|
383
365
|
return;
|
|
384
366
|
}
|
|
367
|
+
this.currentVolume = volume;
|
|
385
368
|
this.mediaElement.volume = volume;
|
|
386
369
|
if (this.gainNode) {
|
|
387
370
|
this.gainNode.gain.value = volume;
|
|
388
371
|
}
|
|
389
|
-
this.playback.state.volume = volume;
|
|
390
372
|
}
|
|
391
373
|
|
|
392
374
|
/**
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { NavigatorProtector } from "../../protection/NavigatorProtector";
|
|
2
|
+
import { DragAndDropProtector } from "../../protection/DragAndDropProtector";
|
|
3
|
+
import { CopyProtector } from "../../protection/CopyProtector";
|
|
4
|
+
import { IContentProtectionConfig } from "../../Navigator";
|
|
5
|
+
|
|
6
|
+
export class AudioNavigatorProtector extends NavigatorProtector {
|
|
7
|
+
private dragAndDropProtector?: DragAndDropProtector;
|
|
8
|
+
private copyProtector?: CopyProtector;
|
|
9
|
+
|
|
10
|
+
constructor(config: IContentProtectionConfig = {}) {
|
|
11
|
+
super(config);
|
|
12
|
+
|
|
13
|
+
if (config.disableDragAndDrop) {
|
|
14
|
+
this.dragAndDropProtector = new DragAndDropProtector({
|
|
15
|
+
onDragDetected: (dataTransferTypes) => {
|
|
16
|
+
this.dispatchSuspiciousActivity("drag_detected", { dataTransferTypes, targetFrameSrc: "" });
|
|
17
|
+
},
|
|
18
|
+
onDropDetected: (dataTransferTypes, fileCount) => {
|
|
19
|
+
this.dispatchSuspiciousActivity("drop_detected", { dataTransferTypes, fileCount, targetFrameSrc: "" });
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (config.protectCopy) {
|
|
25
|
+
this.copyProtector = new CopyProtector({
|
|
26
|
+
onCopyBlocked: () => {
|
|
27
|
+
this.dispatchSuspiciousActivity("bulk_copy", { targetFrameSrc: "" });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public override destroy() {
|
|
34
|
+
super.destroy();
|
|
35
|
+
this.dragAndDropProtector?.destroy();
|
|
36
|
+
this.copyProtector?.destroy();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -85,7 +85,7 @@ export class FrameManager {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// Apply print protection if configured
|
|
88
|
-
if (this.contentProtectionConfig.protectPrinting) {
|
|
88
|
+
if (this.contentProtectionConfig.protectPrinting?.disable) {
|
|
89
89
|
this.comms!.send("print_protection", this.contentProtectionConfig.protectPrinting);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -219,7 +219,7 @@ export class FXLFrameManager {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
// Apply print protection if configured
|
|
222
|
-
if (this.contentProtectionConfig.protectPrinting) {
|
|
222
|
+
if (this.contentProtectionConfig.protectPrinting?.disable) {
|
|
223
223
|
this.comms!.send("print_protection", this.contentProtectionConfig.protectPrinting);
|
|
224
224
|
}
|
|
225
225
|
}
|
package/src/preferences/Types.ts
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface CopyProtectionOptions {
|
|
2
|
+
onCopyBlocked?: () => void;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export class CopyProtector {
|
|
6
|
+
private copyHandler: (event: ClipboardEvent) => void;
|
|
7
|
+
|
|
8
|
+
constructor(options: CopyProtectionOptions = {}) {
|
|
9
|
+
this.copyHandler = (event: ClipboardEvent) => {
|
|
10
|
+
event.preventDefault();
|
|
11
|
+
event.stopPropagation();
|
|
12
|
+
options.onCopyBlocked?.();
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
document.addEventListener("copy", this.copyHandler, true);
|
|
16
|
+
window.addEventListener("unload", () => this.destroy());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public destroy() {
|
|
20
|
+
document.removeEventListener("copy", this.copyHandler, true);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface DragAndDropProtectionOptions {
|
|
2
|
+
onDragDetected?: (dataTransferTypes: readonly string[]) => void;
|
|
3
|
+
onDropDetected?: (dataTransferTypes: readonly string[], fileCount: number) => void;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export class DragAndDropProtector {
|
|
7
|
+
private dragstartHandler: (event: DragEvent) => void;
|
|
8
|
+
private dropHandler: (event: DragEvent) => void;
|
|
9
|
+
|
|
10
|
+
constructor(options: DragAndDropProtectionOptions = {}) {
|
|
11
|
+
this.dragstartHandler = (event: DragEvent) => {
|
|
12
|
+
event.preventDefault();
|
|
13
|
+
event.stopPropagation();
|
|
14
|
+
options.onDragDetected?.(Array.from(event.dataTransfer?.types ?? []));
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
this.dropHandler = (event: DragEvent) => {
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
event.stopPropagation();
|
|
20
|
+
const types = Array.from(event.dataTransfer?.types ?? []);
|
|
21
|
+
const fileCount = event.dataTransfer?.files.length ?? 0;
|
|
22
|
+
options.onDropDetected?.(types, fileCount);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
document.addEventListener("dragstart", this.dragstartHandler, true);
|
|
26
|
+
document.addEventListener("drop", this.dropHandler, true);
|
|
27
|
+
window.addEventListener("unload", () => this.destroy());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public destroy() {
|
|
31
|
+
document.removeEventListener("dragstart", this.dragstartHandler, true);
|
|
32
|
+
document.removeEventListener("drop", this.dropHandler, true);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -15,7 +15,7 @@ export class NavigatorProtector {
|
|
|
15
15
|
private printProtector?: PrintProtector;
|
|
16
16
|
private contextMenuProtector?: ContextMenuProtector;
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
protected dispatchSuspiciousActivity(type: string, detail: Record<string, unknown>) {
|
|
19
19
|
const event = new CustomEvent(NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT, {
|
|
20
20
|
detail: {
|
|
21
21
|
type,
|
|
@@ -55,7 +55,7 @@ export class NavigatorProtector {
|
|
|
55
55
|
}
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
// Enable iframe embedding detection if explicitly enabled in config
|
|
60
60
|
if (config.checkIFrameEmbedding) {
|
|
61
61
|
this.iframeEmbeddingDetector = new IframeEmbeddingDetector({
|
|
@@ -74,7 +74,7 @@ export class NavigatorProtector {
|
|
|
74
74
|
}
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
// Enable context menu protection if configured
|
|
79
79
|
if (config.disableContextMenu) {
|
|
80
80
|
this.contextMenuProtector = new ContextMenuProtector({
|
|
@@ -83,7 +83,7 @@ export class WebPubFrameManager {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// Apply print protection if configured
|
|
86
|
-
if (this.contentProtectionConfig.protectPrinting) {
|
|
86
|
+
if (this.contentProtectionConfig.protectPrinting?.disable) {
|
|
87
87
|
this.comms!.send("print_protection", this.contentProtectionConfig.protectPrinting);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -128,8 +128,12 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
|
|
|
128
128
|
|
|
129
129
|
// Listen for custom events from NavigatorProtector
|
|
130
130
|
this._suspiciousActivityListener = (event: Event) => {
|
|
131
|
-
const
|
|
132
|
-
|
|
131
|
+
const { type, ...activity } = (event as CustomEvent).detail;
|
|
132
|
+
if (type === "context_menu") {
|
|
133
|
+
this.listeners.contextMenu(activity as ContextMenuEvent);
|
|
134
|
+
} else {
|
|
135
|
+
this.listeners.contentProtection(type, activity);
|
|
136
|
+
}
|
|
133
137
|
};
|
|
134
138
|
window.addEventListener(NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT, this._suspiciousActivityListener);
|
|
135
139
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { Link, Locator, Publication } from "@readium/shared";
|
|
2
|
-
import { MediaNavigator } from "../Navigator";
|
|
1
|
+
import { Link, Locator, Publication, Timeline, TimelineItem } from "@readium/shared";
|
|
2
|
+
import { MediaNavigator, IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../Navigator";
|
|
3
3
|
import { Configurable } from "../preferences";
|
|
4
4
|
import { AudioPreferences, AudioSettings, AudioPreferencesEditor, IAudioPreferences, IAudioDefaults } from "./preferences";
|
|
5
|
+
import { ContextMenuEvent, KeyboardEventData, SuspiciousActivityEvent } from "@readium/navigator-html-injectables";
|
|
5
6
|
export interface AudioNavigatorListeners {
|
|
6
7
|
trackLoaded: (media: HTMLMediaElement) => void;
|
|
7
8
|
positionChanged: (locator: Locator) => void;
|
|
9
|
+
timelineItemChanged: (item: TimelineItem | undefined) => void;
|
|
8
10
|
error: (error: any, locator: Locator) => void;
|
|
9
11
|
trackEnded: (locator: Locator) => void;
|
|
10
12
|
play: (locator: Locator) => void;
|
|
@@ -13,28 +15,42 @@ export interface AudioNavigatorListeners {
|
|
|
13
15
|
stalled: (isStalled: boolean) => void;
|
|
14
16
|
seeking: (isSeeking: boolean) => void;
|
|
15
17
|
seekable: (seekable: TimeRanges) => void;
|
|
18
|
+
contentProtection: (type: string, data: SuspiciousActivityEvent) => void;
|
|
19
|
+
peripheral: (data: KeyboardEventData) => void;
|
|
20
|
+
contextMenu: (data: ContextMenuEvent) => void;
|
|
16
21
|
}
|
|
17
22
|
export interface AudioNavigatorConfiguration {
|
|
18
23
|
preferences: IAudioPreferences;
|
|
19
24
|
defaults: IAudioDefaults;
|
|
25
|
+
contentProtection?: IContentProtectionConfig;
|
|
26
|
+
keyboardPeripherals?: IKeyboardPeripheralsConfig;
|
|
20
27
|
}
|
|
21
28
|
export declare class AudioNavigator extends MediaNavigator implements Configurable<AudioSettings, AudioPreferences> {
|
|
22
29
|
private readonly pub;
|
|
23
30
|
private positionPollInterval;
|
|
24
31
|
private navigationId;
|
|
32
|
+
private _playIntent;
|
|
25
33
|
private listeners;
|
|
26
34
|
private currentLocation;
|
|
27
35
|
private _preferences;
|
|
28
36
|
private _defaults;
|
|
29
37
|
private _settings;
|
|
30
38
|
private _preferencesEditor;
|
|
39
|
+
private _mediaSessionEnabled;
|
|
31
40
|
private pool;
|
|
41
|
+
private readonly _navigatorProtector;
|
|
42
|
+
private _currentTimelineItem;
|
|
43
|
+
private readonly _keyboardPeripheralsManager;
|
|
44
|
+
private readonly _suspiciousActivityListener;
|
|
45
|
+
private readonly _keyboardPeripheralListener;
|
|
32
46
|
constructor(publication: Publication, listeners: AudioNavigatorListeners, initialPosition?: Locator, configuration?: AudioNavigatorConfiguration);
|
|
33
47
|
get settings(): AudioSettings;
|
|
34
48
|
get preferencesEditor(): AudioPreferencesEditor;
|
|
35
49
|
submitPreferences(preferences: AudioPreferences): Promise<void>;
|
|
36
50
|
private applyPreferences;
|
|
37
51
|
get publication(): Publication;
|
|
52
|
+
get timeline(): Timeline;
|
|
53
|
+
private _notifyTimelineChange;
|
|
38
54
|
private ensureLocatorLocations;
|
|
39
55
|
/** Resolves a bare href (no fragment) to its index in the reading order. Returns -1 if not found. */
|
|
40
56
|
private hrefToTrackIndex;
|
|
@@ -1,52 +1,28 @@
|
|
|
1
1
|
import { Publication } from "@readium/shared";
|
|
2
2
|
import { WebAudioEngine } from "./engine/WebAudioEngine";
|
|
3
3
|
export declare class AudioPoolManager {
|
|
4
|
-
private
|
|
4
|
+
private readonly pool;
|
|
5
5
|
private _audioEngine;
|
|
6
|
-
|
|
6
|
+
private readonly _publication;
|
|
7
|
+
private readonly _supportedAudioTypes;
|
|
8
|
+
constructor(audioEngine: WebAudioEngine, publication: Publication);
|
|
9
|
+
private detectSupportedAudioTypes;
|
|
10
|
+
private pickPlayableHref;
|
|
7
11
|
get audioEngine(): WebAudioEngine;
|
|
8
12
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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').
|
|
13
|
+
* Ensures an audio element exists in the pool for the given href.
|
|
14
|
+
* If one already exists, it is left untouched (preserving its buffered data).
|
|
15
15
|
*/
|
|
16
|
-
|
|
17
|
-
preload(href: string): void;
|
|
16
|
+
private ensure;
|
|
18
17
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @returns The preloaded HTMLAudioElement, or undefined if not preloaded.
|
|
18
|
+
* Updates the pool around the given index: ensures elements exist within
|
|
19
|
+
* the LOWER_BOUNDARY and disposes those beyond the UPPER_BOUNDARY.
|
|
22
20
|
*/
|
|
23
|
-
|
|
21
|
+
private update;
|
|
24
22
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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.
|
|
23
|
+
* Sets the current audio for playback at the given track index.
|
|
24
|
+
* The element is always sourced from the pool — never loaded ad-hoc on the engine.
|
|
50
25
|
*/
|
|
26
|
+
setCurrentAudio(currentIndex: number, _direction: 'forward' | 'backward'): void;
|
|
51
27
|
destroy(): void;
|
|
52
28
|
}
|
|
@@ -10,10 +10,6 @@ export interface PlaybackState {
|
|
|
10
10
|
* The duration of the audio resource.
|
|
11
11
|
*/
|
|
12
12
|
duration: number;
|
|
13
|
-
/**
|
|
14
|
-
* The volume of the audio resource.
|
|
15
|
-
*/
|
|
16
|
-
volume: number;
|
|
17
13
|
}
|
|
18
14
|
/**
|
|
19
15
|
* Playback interface for an audio engine state
|
|
@@ -40,11 +36,6 @@ export interface AudioEngine {
|
|
|
40
36
|
* The current playback state.
|
|
41
37
|
*/
|
|
42
38
|
playback: Playback;
|
|
43
|
-
/**
|
|
44
|
-
* Loads the audio resource at the given URL.
|
|
45
|
-
* @param url The URL of the audio resource.
|
|
46
|
-
*/
|
|
47
|
-
loadAudio(url: string): void;
|
|
48
39
|
/**
|
|
49
40
|
* Adds an event listener to the audio engine.
|
|
50
41
|
* @param event The event name to listen.
|
|
@@ -7,6 +7,7 @@ export declare class WebAudioEngine implements AudioEngine {
|
|
|
7
7
|
private sourceNode;
|
|
8
8
|
private gainNode;
|
|
9
9
|
private listeners;
|
|
10
|
+
private currentVolume;
|
|
10
11
|
private currentPlaybackRate;
|
|
11
12
|
private isMutedValue;
|
|
12
13
|
private isPlayingValue;
|
|
@@ -47,11 +48,6 @@ export declare class WebAudioEngine implements AudioEngine {
|
|
|
47
48
|
* @param callback - callback function to be removed.
|
|
48
49
|
*/
|
|
49
50
|
off(event: string, callback: EventCallback): void;
|
|
50
|
-
/**
|
|
51
|
-
* Load the audio resource at the given URL.
|
|
52
|
-
* @param url The URL of the audio resource.
|
|
53
|
-
* */
|
|
54
|
-
loadAudio(url: string): void;
|
|
55
51
|
private deactivateWebAudio;
|
|
56
52
|
/**
|
|
57
53
|
* Sets the media element for playback.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NavigatorProtector } from "../../protection/NavigatorProtector";
|
|
2
|
+
import { IContentProtectionConfig } from "../../Navigator";
|
|
3
|
+
export declare class AudioNavigatorProtector extends NavigatorProtector {
|
|
4
|
+
private dragAndDropProtector?;
|
|
5
|
+
private copyProtector?;
|
|
6
|
+
constructor(config?: IContentProtectionConfig);
|
|
7
|
+
destroy(): void;
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface DragAndDropProtectionOptions {
|
|
2
|
+
onDragDetected?: (dataTransferTypes: readonly string[]) => void;
|
|
3
|
+
onDropDetected?: (dataTransferTypes: readonly string[], fileCount: number) => void;
|
|
4
|
+
}
|
|
5
|
+
export declare class DragAndDropProtector {
|
|
6
|
+
private dragstartHandler;
|
|
7
|
+
private dropHandler;
|
|
8
|
+
constructor(options?: DragAndDropProtectionOptions);
|
|
9
|
+
destroy(): void;
|
|
10
|
+
}
|
|
@@ -6,7 +6,7 @@ export declare class NavigatorProtector {
|
|
|
6
6
|
private iframeEmbeddingDetector?;
|
|
7
7
|
private printProtector?;
|
|
8
8
|
private contextMenuProtector?;
|
|
9
|
-
|
|
9
|
+
protected dispatchSuspiciousActivity(type: string, detail: Record<string, unknown>): void;
|
|
10
10
|
constructor(config?: IContentProtectionConfig);
|
|
11
11
|
destroy(): void;
|
|
12
12
|
}
|