@readium/navigator 2.4.0-beta.1 → 2.4.0-beta.3
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 +520 -414
- package/dist/index.umd.cjs +18 -18
- package/package.json +1 -1
- package/src/audio/AudioNavigator.ts +79 -6
- package/src/audio/AudioPoolManager.ts +51 -22
- package/src/audio/protection/AudioNavigatorProtector.ts +38 -0
- package/src/protection/CopyProtector.ts +22 -0
- package/src/protection/DragAndDropProtector.ts +34 -0
- package/src/protection/NavigatorProtector.ts +3 -3
- package/types/src/audio/AudioNavigator.d.ts +11 -1
- package/types/src/audio/AudioPoolManager.d.ts +9 -7
- 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
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Link, Locator, LocatorLocations, Publication } from "@readium/shared";
|
|
2
|
-
import { MediaNavigator } from "../Navigator";
|
|
2
|
+
import { MediaNavigator, IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../Navigator";
|
|
3
3
|
import { Configurable } from "../preferences";
|
|
4
4
|
import { WebAudioEngine, PlaybackState } from "./engine";
|
|
5
5
|
import {
|
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
IAudioDefaults
|
|
12
12
|
} from "./preferences";
|
|
13
13
|
import { AudioPoolManager } from "./AudioPoolManager";
|
|
14
|
+
import { ContextMenuEvent, KeyboardEventData, SuspiciousActivityEvent } from "@readium/navigator-html-injectables";
|
|
15
|
+
import { AudioNavigatorProtector } from "./protection/AudioNavigatorProtector";
|
|
16
|
+
import { NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT } from "../protection/NavigatorProtector";
|
|
17
|
+
import { KeyboardPeripherals, NAVIGATOR_KEYBOARD_PERIPHERAL_EVENT } from "../peripherals/KeyboardPeripherals";
|
|
14
18
|
|
|
15
19
|
export interface AudioNavigatorListeners {
|
|
16
20
|
trackLoaded: (media: HTMLMediaElement) => void;
|
|
@@ -23,11 +27,32 @@ export interface AudioNavigatorListeners {
|
|
|
23
27
|
stalled: (isStalled: boolean) => void;
|
|
24
28
|
seeking: (isSeeking: boolean) => void;
|
|
25
29
|
seekable: (seekable: TimeRanges) => void;
|
|
30
|
+
contentProtection: (type: string, data: SuspiciousActivityEvent) => void;
|
|
31
|
+
peripheral: (data: KeyboardEventData) => void;
|
|
32
|
+
contextMenu: (data: ContextMenuEvent) => void;
|
|
26
33
|
}
|
|
27
34
|
|
|
35
|
+
const defaultListeners = (listeners: Partial<AudioNavigatorListeners>): AudioNavigatorListeners => ({
|
|
36
|
+
trackLoaded: listeners.trackLoaded ?? (() => {}),
|
|
37
|
+
positionChanged: listeners.positionChanged ?? (() => {}),
|
|
38
|
+
error: listeners.error ?? (() => {}),
|
|
39
|
+
trackEnded: listeners.trackEnded ?? (() => {}),
|
|
40
|
+
play: listeners.play ?? (() => {}),
|
|
41
|
+
pause: listeners.pause ?? (() => {}),
|
|
42
|
+
metadataLoaded: listeners.metadataLoaded ?? (() => {}),
|
|
43
|
+
stalled: listeners.stalled ?? (() => {}),
|
|
44
|
+
seeking: listeners.seeking ?? (() => {}),
|
|
45
|
+
seekable: listeners.seekable ?? (() => {}),
|
|
46
|
+
contentProtection: listeners.contentProtection ?? (() => {}),
|
|
47
|
+
peripheral: listeners.peripheral ?? (() => {}),
|
|
48
|
+
contextMenu: listeners.contextMenu ?? (() => {}),
|
|
49
|
+
});
|
|
50
|
+
|
|
28
51
|
export interface AudioNavigatorConfiguration {
|
|
29
52
|
preferences: IAudioPreferences;
|
|
30
53
|
defaults: IAudioDefaults;
|
|
54
|
+
contentProtection?: IContentProtectionConfig;
|
|
55
|
+
keyboardPeripherals?: IKeyboardPeripheralsConfig;
|
|
31
56
|
}
|
|
32
57
|
|
|
33
58
|
export class AudioNavigator extends MediaNavigator implements Configurable<AudioSettings, AudioPreferences> {
|
|
@@ -42,6 +67,10 @@ export class AudioNavigator extends MediaNavigator implements Configurable<Audio
|
|
|
42
67
|
private _settings: AudioSettings;
|
|
43
68
|
private _preferencesEditor: AudioPreferencesEditor | null = null;
|
|
44
69
|
private pool: AudioPoolManager;
|
|
70
|
+
private readonly _navigatorProtector: AudioNavigatorProtector | null = null;
|
|
71
|
+
private readonly _keyboardPeripheralsManager: KeyboardPeripherals | null = null;
|
|
72
|
+
private readonly _suspiciousActivityListener: ((event: Event) => void) | null = null;
|
|
73
|
+
private readonly _keyboardPeripheralListener: ((event: Event) => void) | null = null;
|
|
45
74
|
|
|
46
75
|
constructor(publication: Publication, listeners: AudioNavigatorListeners, initialPosition?: Locator, configuration: AudioNavigatorConfiguration = {
|
|
47
76
|
preferences: {},
|
|
@@ -49,11 +78,11 @@ export class AudioNavigator extends MediaNavigator implements Configurable<Audio
|
|
|
49
78
|
}) {
|
|
50
79
|
super();
|
|
51
80
|
this.pub = publication;
|
|
81
|
+
this.listeners = defaultListeners(listeners);
|
|
52
82
|
|
|
53
83
|
this._preferences = new AudioPreferences(configuration.preferences);
|
|
54
84
|
this._defaults = new AudioDefaults(configuration.defaults);
|
|
55
85
|
this._settings = new AudioSettings(this._preferences, this._defaults);
|
|
56
|
-
this.listeners = listeners;
|
|
57
86
|
|
|
58
87
|
if (initialPosition) {
|
|
59
88
|
this.currentLocation = this.ensureLocatorLocations(initialPosition);
|
|
@@ -88,14 +117,49 @@ export class AudioNavigator extends MediaNavigator implements Configurable<Audio
|
|
|
88
117
|
}
|
|
89
118
|
});
|
|
90
119
|
|
|
91
|
-
this.pool = new AudioPoolManager(audioEngine);
|
|
120
|
+
this.pool = new AudioPoolManager(audioEngine, publication);
|
|
121
|
+
|
|
122
|
+
// Initialize content protection
|
|
123
|
+
const contentProtection = configuration.contentProtection || {};
|
|
124
|
+
const keyboardPeripherals = this.mergeKeyboardPeripherals(
|
|
125
|
+
contentProtection,
|
|
126
|
+
configuration.keyboardPeripherals || []
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (contentProtection.disableContextMenu ||
|
|
130
|
+
contentProtection.checkAutomation ||
|
|
131
|
+
contentProtection.checkIFrameEmbedding ||
|
|
132
|
+
contentProtection.monitorDevTools ||
|
|
133
|
+
contentProtection.protectPrinting?.disable ||
|
|
134
|
+
contentProtection.disableDragAndDrop ||
|
|
135
|
+
contentProtection.protectCopy) {
|
|
136
|
+
this._navigatorProtector = new AudioNavigatorProtector(contentProtection);
|
|
137
|
+
this._suspiciousActivityListener = (event: Event) => {
|
|
138
|
+
const { type, ...detail } = (event as CustomEvent).detail;
|
|
139
|
+
if (type === "context_menu") {
|
|
140
|
+
this.listeners.contextMenu(detail as ContextMenuEvent);
|
|
141
|
+
} else {
|
|
142
|
+
this.listeners.contentProtection(type, detail as SuspiciousActivityEvent);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
window.addEventListener(NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT, this._suspiciousActivityListener);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (keyboardPeripherals.length > 0) {
|
|
149
|
+
this._keyboardPeripheralsManager = new KeyboardPeripherals({ keyboardPeripherals });
|
|
150
|
+
this._keyboardPeripheralListener = (event: Event) => {
|
|
151
|
+
this.listeners.peripheral((event as CustomEvent).detail);
|
|
152
|
+
};
|
|
153
|
+
window.addEventListener(NAVIGATOR_KEYBOARD_PERIPHERAL_EVENT, this._keyboardPeripheralListener);
|
|
154
|
+
}
|
|
155
|
+
|
|
92
156
|
this.setupEventListeners();
|
|
93
157
|
|
|
94
158
|
if (this._settings.enableMediaSession) {
|
|
95
159
|
this.setupMediaSession();
|
|
96
160
|
}
|
|
97
161
|
|
|
98
|
-
this.pool.setCurrentAudio(
|
|
162
|
+
this.pool.setCurrentAudio(trackIndex, "forward");
|
|
99
163
|
|
|
100
164
|
// Load and seek to initial position, then notify consumer.
|
|
101
165
|
// No cancellation needed here — the constructor runs once.
|
|
@@ -370,7 +434,7 @@ export class AudioNavigator extends MediaNavigator implements Configurable<Audio
|
|
|
370
434
|
const wasPlaying = this.isPlaying;
|
|
371
435
|
|
|
372
436
|
this.stopPositionPolling();
|
|
373
|
-
this.pool.setCurrentAudio(
|
|
437
|
+
this.pool.setCurrentAudio(trackIndex, direction);
|
|
374
438
|
this.currentLocation = locator.copyWithLocations(locator.locations);
|
|
375
439
|
|
|
376
440
|
await this.waitForLoadedAndSeeked(time, id);
|
|
@@ -399,7 +463,8 @@ export class AudioNavigator extends MediaNavigator implements Configurable<Audio
|
|
|
399
463
|
cb(false);
|
|
400
464
|
return;
|
|
401
465
|
}
|
|
402
|
-
const
|
|
466
|
+
const time = link.locator.locations?.time() ?? 0;
|
|
467
|
+
const locator = this.createLocator(trackIndex, time);
|
|
403
468
|
await this.go(locator, _animated, cb);
|
|
404
469
|
}
|
|
405
470
|
|
|
@@ -492,6 +557,14 @@ export class AudioNavigator extends MediaNavigator implements Configurable<Audio
|
|
|
492
557
|
destroy(): void {
|
|
493
558
|
this.stopPositionPolling();
|
|
494
559
|
this.destroyMediaSession();
|
|
560
|
+
if (this._suspiciousActivityListener) {
|
|
561
|
+
window.removeEventListener(NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT, this._suspiciousActivityListener);
|
|
562
|
+
}
|
|
563
|
+
if (this._keyboardPeripheralListener) {
|
|
564
|
+
window.removeEventListener(NAVIGATOR_KEYBOARD_PERIPHERAL_EVENT, this._keyboardPeripheralListener);
|
|
565
|
+
}
|
|
566
|
+
this._navigatorProtector?.destroy();
|
|
567
|
+
this._keyboardPeripheralsManager?.destroy();
|
|
495
568
|
this.pool.destroy();
|
|
496
569
|
}
|
|
497
570
|
}
|
|
@@ -1,12 +1,46 @@
|
|
|
1
|
-
import { Publication } from "@readium/shared";
|
|
1
|
+
import { Link, Publication } from "@readium/shared";
|
|
2
2
|
import { WebAudioEngine } from "./engine/WebAudioEngine";
|
|
3
3
|
|
|
4
4
|
export class AudioPoolManager {
|
|
5
5
|
private preloadedElements: Map<string, HTMLAudioElement> = new Map();
|
|
6
6
|
private _audioEngine: WebAudioEngine;
|
|
7
|
+
private readonly _publication: Publication;
|
|
8
|
+
private readonly _supportedAudioTypes: Map<string, "probably" | "maybe">;
|
|
7
9
|
|
|
8
|
-
constructor(audioEngine: WebAudioEngine) {
|
|
10
|
+
constructor(audioEngine: WebAudioEngine, publication: Publication) {
|
|
9
11
|
this._audioEngine = audioEngine;
|
|
12
|
+
this._publication = publication;
|
|
13
|
+
this._supportedAudioTypes = this.detectSupportedAudioTypes();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private detectSupportedAudioTypes(): Map<string, "probably" | "maybe"> {
|
|
17
|
+
const audio = document.createElement("audio");
|
|
18
|
+
const unique = new Set<string>();
|
|
19
|
+
for (const link of this._publication.readingOrder.items) {
|
|
20
|
+
if (link.type) unique.add(link.type);
|
|
21
|
+
for (const alt of link.alternates?.items ?? []) {
|
|
22
|
+
if (alt.type) unique.add(alt.type);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const supported = new Map<string, "probably" | "maybe">();
|
|
26
|
+
for (const type of unique) {
|
|
27
|
+
const result = audio.canPlayType(type);
|
|
28
|
+
if (result !== "") supported.set(type, result as "probably" | "maybe");
|
|
29
|
+
}
|
|
30
|
+
return supported;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private pickPlayableHref(link: Link): string {
|
|
34
|
+
const candidates = [link, ...(link.alternates?.items ?? [])];
|
|
35
|
+
let best: { href: string; confidence: "probably" | "maybe" } | undefined;
|
|
36
|
+
for (const candidate of candidates) {
|
|
37
|
+
if (!candidate.type) continue;
|
|
38
|
+
const confidence = this._supportedAudioTypes.get(candidate.type);
|
|
39
|
+
if (!confidence) continue;
|
|
40
|
+
if (confidence === "probably") return candidate.href;
|
|
41
|
+
if (!best) best = { href: candidate.href, confidence };
|
|
42
|
+
}
|
|
43
|
+
return best?.href ?? link.href;
|
|
10
44
|
}
|
|
11
45
|
|
|
12
46
|
get audioEngine(): WebAudioEngine {
|
|
@@ -21,7 +55,8 @@ export class AudioPoolManager {
|
|
|
21
55
|
* @param currentIndex The current track index.
|
|
22
56
|
* @param direction The navigation direction ('forward' or 'backward').
|
|
23
57
|
*/
|
|
24
|
-
setCurrentAudio(
|
|
58
|
+
setCurrentAudio(currentIndex: number, direction: 'forward' | 'backward'): void {
|
|
59
|
+
const href = this.pickPlayableHref(this._publication.readingOrder.items[currentIndex]);
|
|
25
60
|
// When Web Audio is active, preloaded elements lack crossOrigin="anonymous"
|
|
26
61
|
// and cannot be connected to MediaElementAudioSourceNode, so bypass the pool.
|
|
27
62
|
const preloadedElement = !this.audioEngine.isWebAudioActive ? this.get(href) : undefined;
|
|
@@ -32,7 +67,7 @@ export class AudioPoolManager {
|
|
|
32
67
|
this.clear(href);
|
|
33
68
|
this.audioEngine.loadAudio(href);
|
|
34
69
|
}
|
|
35
|
-
this.preloadAdjacent(
|
|
70
|
+
this.preloadAdjacent(currentIndex, direction);
|
|
36
71
|
}
|
|
37
72
|
preload(href: string): void {
|
|
38
73
|
if (this.preloadedElements.has(href)) {
|
|
@@ -69,44 +104,38 @@ export class AudioPoolManager {
|
|
|
69
104
|
* @param publication The publication containing the reading order.
|
|
70
105
|
* @param currentIndex The current track index.
|
|
71
106
|
*/
|
|
72
|
-
preloadNext(
|
|
107
|
+
preloadNext(currentIndex: number): void {
|
|
73
108
|
const nextIndex = currentIndex + 1;
|
|
74
|
-
if (nextIndex <
|
|
75
|
-
const nextLink =
|
|
76
|
-
|
|
77
|
-
this.preload(nextLink.href);
|
|
78
|
-
}
|
|
109
|
+
if (nextIndex < this._publication.readingOrder.items.length) {
|
|
110
|
+
const nextLink = this._publication.readingOrder.items[nextIndex];
|
|
111
|
+
this.preload(this.pickPlayableHref(nextLink));
|
|
79
112
|
}
|
|
80
113
|
}
|
|
81
114
|
|
|
82
115
|
/**
|
|
83
116
|
* Preloads the previous track in the reading order.
|
|
84
|
-
* @param publication The publication containing the reading order.
|
|
85
117
|
* @param currentIndex The current track index.
|
|
86
118
|
*/
|
|
87
|
-
preloadPrevious(
|
|
119
|
+
preloadPrevious(currentIndex: number): void {
|
|
88
120
|
const prevIndex = currentIndex - 1;
|
|
89
121
|
if (prevIndex >= 0) {
|
|
90
|
-
const prevLink =
|
|
91
|
-
|
|
92
|
-
this.preload(prevLink.href);
|
|
93
|
-
}
|
|
122
|
+
const prevLink = this._publication.readingOrder.items[prevIndex];
|
|
123
|
+
this.preload(this.pickPlayableHref(prevLink));
|
|
94
124
|
}
|
|
95
125
|
}
|
|
96
126
|
|
|
97
127
|
/**
|
|
98
128
|
* Preloads adjacent tracks (previous and next) for smoother navigation.
|
|
99
|
-
* @param publication The publication containing the reading order.
|
|
100
129
|
* @param currentIndex The current track index.
|
|
101
130
|
* @param direction The navigation direction ('forward' or 'backward').
|
|
102
131
|
*/
|
|
103
|
-
preloadAdjacent(
|
|
132
|
+
preloadAdjacent(currentIndex: number, direction: 'forward' | 'backward' = 'forward'): void {
|
|
104
133
|
if (direction === 'forward') {
|
|
105
|
-
this.preloadNext(
|
|
106
|
-
this.preloadPrevious(
|
|
134
|
+
this.preloadNext(currentIndex);
|
|
135
|
+
this.preloadPrevious(currentIndex);
|
|
107
136
|
} else {
|
|
108
|
-
this.preloadPrevious(
|
|
109
|
-
this.preloadNext(
|
|
137
|
+
this.preloadPrevious(currentIndex);
|
|
138
|
+
this.preloadNext(currentIndex);
|
|
110
139
|
}
|
|
111
140
|
}
|
|
112
141
|
|
|
@@ -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
|
+
}
|
|
@@ -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({
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Link, Locator, Publication } from "@readium/shared";
|
|
2
|
-
import { MediaNavigator } from "../Navigator";
|
|
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;
|
|
@@ -13,10 +14,15 @@ export interface AudioNavigatorListeners {
|
|
|
13
14
|
stalled: (isStalled: boolean) => void;
|
|
14
15
|
seeking: (isSeeking: boolean) => void;
|
|
15
16
|
seekable: (seekable: TimeRanges) => void;
|
|
17
|
+
contentProtection: (type: string, data: SuspiciousActivityEvent) => void;
|
|
18
|
+
peripheral: (data: KeyboardEventData) => void;
|
|
19
|
+
contextMenu: (data: ContextMenuEvent) => void;
|
|
16
20
|
}
|
|
17
21
|
export interface AudioNavigatorConfiguration {
|
|
18
22
|
preferences: IAudioPreferences;
|
|
19
23
|
defaults: IAudioDefaults;
|
|
24
|
+
contentProtection?: IContentProtectionConfig;
|
|
25
|
+
keyboardPeripherals?: IKeyboardPeripheralsConfig;
|
|
20
26
|
}
|
|
21
27
|
export declare class AudioNavigator extends MediaNavigator implements Configurable<AudioSettings, AudioPreferences> {
|
|
22
28
|
private readonly pub;
|
|
@@ -29,6 +35,10 @@ export declare class AudioNavigator extends MediaNavigator implements Configurab
|
|
|
29
35
|
private _settings;
|
|
30
36
|
private _preferencesEditor;
|
|
31
37
|
private pool;
|
|
38
|
+
private readonly _navigatorProtector;
|
|
39
|
+
private readonly _keyboardPeripheralsManager;
|
|
40
|
+
private readonly _suspiciousActivityListener;
|
|
41
|
+
private readonly _keyboardPeripheralListener;
|
|
32
42
|
constructor(publication: Publication, listeners: AudioNavigatorListeners, initialPosition?: Locator, configuration?: AudioNavigatorConfiguration);
|
|
33
43
|
get settings(): AudioSettings;
|
|
34
44
|
get preferencesEditor(): AudioPreferencesEditor;
|
|
@@ -3,7 +3,11 @@ import { WebAudioEngine } from "./engine/WebAudioEngine";
|
|
|
3
3
|
export declare class AudioPoolManager {
|
|
4
4
|
private preloadedElements;
|
|
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
13
|
* Sets the current audio by href, using preloaded element if available or loading otherwise,
|
|
@@ -13,7 +17,7 @@ export declare class AudioPoolManager {
|
|
|
13
17
|
* @param currentIndex The current track index.
|
|
14
18
|
* @param direction The navigation direction ('forward' or 'backward').
|
|
15
19
|
*/
|
|
16
|
-
setCurrentAudio(
|
|
20
|
+
setCurrentAudio(currentIndex: number, direction: 'forward' | 'backward'): void;
|
|
17
21
|
preload(href: string): void;
|
|
18
22
|
/**
|
|
19
23
|
* Retrieves a preloaded audio element by URL.
|
|
@@ -31,20 +35,18 @@ export declare class AudioPoolManager {
|
|
|
31
35
|
* @param publication The publication containing the reading order.
|
|
32
36
|
* @param currentIndex The current track index.
|
|
33
37
|
*/
|
|
34
|
-
preloadNext(
|
|
38
|
+
preloadNext(currentIndex: number): void;
|
|
35
39
|
/**
|
|
36
40
|
* Preloads the previous track in the reading order.
|
|
37
|
-
* @param publication The publication containing the reading order.
|
|
38
41
|
* @param currentIndex The current track index.
|
|
39
42
|
*/
|
|
40
|
-
preloadPrevious(
|
|
43
|
+
preloadPrevious(currentIndex: number): void;
|
|
41
44
|
/**
|
|
42
45
|
* Preloads adjacent tracks (previous and next) for smoother navigation.
|
|
43
|
-
* @param publication The publication containing the reading order.
|
|
44
46
|
* @param currentIndex The current track index.
|
|
45
47
|
* @param direction The navigation direction ('forward' or 'backward').
|
|
46
48
|
*/
|
|
47
|
-
preloadAdjacent(
|
|
49
|
+
preloadAdjacent(currentIndex: number, direction?: 'forward' | 'backward'): void;
|
|
48
50
|
/**
|
|
49
51
|
* Destroys the pool by stopping the engine and clearing all preloaded elements.
|
|
50
52
|
*/
|
|
@@ -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
|
}
|