@readium/navigator 2.4.0-beta.5 → 2.4.0-beta.6
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 +347 -381
- package/dist/index.umd.cjs +18 -18
- package/package.json +1 -1
- package/src/audio/AudioNavigator.ts +1 -0
- package/src/audio/AudioPoolManager.ts +63 -78
- package/src/audio/engine/WebAudioEngine.ts +5 -0
- package/types/src/audio/AudioPoolManager.d.ts +10 -36
package/package.json
CHANGED
|
@@ -326,6 +326,7 @@ export class AudioNavigator extends MediaNavigator implements Configurable<Audio
|
|
|
326
326
|
fragments: [`t=${this.duration}`]
|
|
327
327
|
}));
|
|
328
328
|
this.listeners.trackEnded(this.currentLocator);
|
|
329
|
+
if (!this.canGoForward) return;
|
|
329
330
|
await this.nextTrack();
|
|
330
331
|
if (this._settings.autoPlay) this.play();
|
|
331
332
|
});
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Link, Publication } from "@readium/shared";
|
|
2
2
|
import { WebAudioEngine } from "./engine/WebAudioEngine";
|
|
3
3
|
|
|
4
|
+
const UPPER_BOUNDARY = 1;
|
|
5
|
+
const LOWER_BOUNDARY = 1;
|
|
6
|
+
|
|
4
7
|
export class AudioPoolManager {
|
|
5
|
-
private
|
|
8
|
+
private readonly pool: Map<string, HTMLAudioElement> = new Map();
|
|
6
9
|
private _audioEngine: WebAudioEngine;
|
|
7
10
|
private readonly _publication: Publication;
|
|
8
11
|
private readonly _supportedAudioTypes: Map<string, "probably" | "maybe">;
|
|
@@ -48,102 +51,84 @@ export class AudioPoolManager {
|
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* @param href The URL of the audio resource.
|
|
54
|
-
* @param publication The publication containing the reading order.
|
|
55
|
-
* @param currentIndex The current track index.
|
|
56
|
-
* @param direction The navigation direction ('forward' or 'backward').
|
|
54
|
+
* Ensures an audio element exists in the pool for the given href.
|
|
55
|
+
* If one already exists, it is left untouched (preserving its buffered data).
|
|
57
56
|
*/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.
|
|
66
|
-
} else {
|
|
67
|
-
this.clear(href);
|
|
68
|
-
this.audioEngine.loadAudio(href);
|
|
57
|
+
private ensure(href: string): HTMLAudioElement {
|
|
58
|
+
let element = this.pool.get(href);
|
|
59
|
+
if (!element) {
|
|
60
|
+
element = document.createElement("audio");
|
|
61
|
+
element.preload = "auto";
|
|
62
|
+
element.src = href;
|
|
63
|
+
element.load();
|
|
64
|
+
this.pool.set(href, element);
|
|
69
65
|
}
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
preload(href: string): void {
|
|
73
|
-
if (this.preloadedElements.has(href)) {
|
|
74
|
-
return; // Already preloaded
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const audioElement = document.createElement("audio");
|
|
78
|
-
audioElement.preload = "auto";
|
|
79
|
-
audioElement.src = href;
|
|
80
|
-
audioElement.load(); // Start buffering
|
|
81
|
-
|
|
82
|
-
this.preloadedElements.set(href, audioElement);
|
|
66
|
+
return element;
|
|
83
67
|
}
|
|
84
68
|
|
|
85
69
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* @returns The preloaded HTMLAudioElement, or undefined if not preloaded.
|
|
70
|
+
* Updates the pool around the given index: ensures elements exist within
|
|
71
|
+
* the LOWER_BOUNDARY and disposes those beyond the UPPER_BOUNDARY.
|
|
89
72
|
*/
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
73
|
+
private update(currentIndex: number): void {
|
|
74
|
+
const items = this._publication.readingOrder.items;
|
|
75
|
+
const keep = new Set<string>();
|
|
93
76
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
* @param currentIndex The current track index.
|
|
106
|
-
*/
|
|
107
|
-
preloadNext(currentIndex: number): void {
|
|
108
|
-
const nextIndex = currentIndex + 1;
|
|
109
|
-
if (nextIndex < this._publication.readingOrder.items.length) {
|
|
110
|
-
const nextLink = this._publication.readingOrder.items[nextIndex];
|
|
111
|
-
this.preload(this.pickPlayableHref(nextLink));
|
|
77
|
+
for (let j = 0; j < items.length; j++) {
|
|
78
|
+
const href = this.pickPlayableHref(items[j]);
|
|
79
|
+
if (j >= currentIndex - LOWER_BOUNDARY && j <= currentIndex + LOWER_BOUNDARY) {
|
|
80
|
+
this.ensure(href);
|
|
81
|
+
keep.add(href);
|
|
82
|
+
} else if (j >= currentIndex - UPPER_BOUNDARY && j <= currentIndex + UPPER_BOUNDARY) {
|
|
83
|
+
// Between lower and upper: keep if already loaded, don't create
|
|
84
|
+
if (this.pool.has(href)) {
|
|
85
|
+
keep.add(href);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
112
88
|
}
|
|
113
|
-
}
|
|
114
89
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const prevLink = this._publication.readingOrder.items[prevIndex];
|
|
123
|
-
this.preload(this.pickPlayableHref(prevLink));
|
|
90
|
+
// Dispose elements beyond the upper boundary
|
|
91
|
+
for (const [href, element] of this.pool) {
|
|
92
|
+
if (!keep.has(href)) {
|
|
93
|
+
element.removeAttribute("src");
|
|
94
|
+
element.load(); // release network resources
|
|
95
|
+
this.pool.delete(href);
|
|
96
|
+
}
|
|
124
97
|
}
|
|
125
98
|
}
|
|
126
99
|
|
|
127
100
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* @param direction The navigation direction ('forward' or 'backward').
|
|
101
|
+
* Sets the current audio for playback at the given track index.
|
|
102
|
+
* The element is always sourced from the pool — never loaded ad-hoc on the engine.
|
|
131
103
|
*/
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
104
|
+
setCurrentAudio(currentIndex: number, _direction: 'forward' | 'backward'): void {
|
|
105
|
+
const href = this.pickPlayableHref(this._publication.readingOrder.items[currentIndex]);
|
|
106
|
+
const element = this.ensure(href);
|
|
107
|
+
|
|
108
|
+
// Hand the element to the engine. When Web Audio is active, the pooled
|
|
109
|
+
// element doesn't have crossOrigin set (it would break non-CORS servers
|
|
110
|
+
// during preload), so we swap in the fresh element and let loadAudio
|
|
111
|
+
// handle CORS setup + fallback on the engine's own mediaElement.
|
|
112
|
+
if (this.audioEngine.isWebAudioActive) {
|
|
113
|
+
this.audioEngine.setMediaElement(element);
|
|
114
|
+
this.audioEngine.loadAudio(href);
|
|
136
115
|
} else {
|
|
137
|
-
this.
|
|
138
|
-
this.preloadNext(currentIndex);
|
|
116
|
+
this.audioEngine.setMediaElement(element);
|
|
139
117
|
}
|
|
118
|
+
|
|
119
|
+
// Remove from pool so the engine fully owns it and we don't dispose it
|
|
120
|
+
this.pool.delete(href);
|
|
121
|
+
|
|
122
|
+
// Manage the pool around the new position
|
|
123
|
+
this.update(currentIndex);
|
|
140
124
|
}
|
|
141
125
|
|
|
142
|
-
/**
|
|
143
|
-
* Destroys the pool by stopping the engine and clearing all preloaded elements.
|
|
144
|
-
*/
|
|
145
126
|
destroy(): void {
|
|
146
127
|
this.audioEngine.stop();
|
|
147
|
-
this.
|
|
128
|
+
for (const [, element] of this.pool) {
|
|
129
|
+
element.removeAttribute("src");
|
|
130
|
+
element.load();
|
|
131
|
+
}
|
|
132
|
+
this.pool.clear();
|
|
148
133
|
}
|
|
149
134
|
}
|
|
@@ -100,6 +100,11 @@ export class WebAudioEngine implements AudioEngine {
|
|
|
100
100
|
* @param url The URL of the audio resource.
|
|
101
101
|
* */
|
|
102
102
|
public loadAudio(url: string): void {
|
|
103
|
+
// Abort any in-progress load before starting a new one.
|
|
104
|
+
this.mediaElement.pause();
|
|
105
|
+
this.mediaElement.removeAttribute("src");
|
|
106
|
+
this.mediaElement.load();
|
|
107
|
+
|
|
103
108
|
this.isLoadingValue = true;
|
|
104
109
|
this.isLoadedValue = false;
|
|
105
110
|
this.isPlayingValue = false;
|
|
@@ -1,7 +1,7 @@
|
|
|
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
7
|
private readonly _supportedAudioTypes;
|
|
@@ -10,45 +10,19 @@ export declare class AudioPoolManager {
|
|
|
10
10
|
private pickPlayableHref;
|
|
11
11
|
get audioEngine(): WebAudioEngine;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* @param href The URL of the audio resource.
|
|
16
|
-
* @param publication The publication containing the reading order.
|
|
17
|
-
* @param currentIndex The current track index.
|
|
18
|
-
* @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).
|
|
19
15
|
*/
|
|
20
|
-
|
|
21
|
-
preload(href: string): void;
|
|
16
|
+
private ensure;
|
|
22
17
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* @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.
|
|
26
20
|
*/
|
|
27
|
-
|
|
21
|
+
private update;
|
|
28
22
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*/
|
|
32
|
-
clear(href: string): void;
|
|
33
|
-
/**
|
|
34
|
-
* Preloads the next track in the reading order.
|
|
35
|
-
* @param publication The publication containing the reading order.
|
|
36
|
-
* @param currentIndex The current track index.
|
|
37
|
-
*/
|
|
38
|
-
preloadNext(currentIndex: number): void;
|
|
39
|
-
/**
|
|
40
|
-
* Preloads the previous track in the reading order.
|
|
41
|
-
* @param currentIndex The current track index.
|
|
42
|
-
*/
|
|
43
|
-
preloadPrevious(currentIndex: number): void;
|
|
44
|
-
/**
|
|
45
|
-
* Preloads adjacent tracks (previous and next) for smoother navigation.
|
|
46
|
-
* @param currentIndex The current track index.
|
|
47
|
-
* @param direction The navigation direction ('forward' or 'backward').
|
|
48
|
-
*/
|
|
49
|
-
preloadAdjacent(currentIndex: number, direction?: 'forward' | 'backward'): void;
|
|
50
|
-
/**
|
|
51
|
-
* 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.
|
|
52
25
|
*/
|
|
26
|
+
setCurrentAudio(currentIndex: number, _direction: 'forward' | 'backward'): void;
|
|
53
27
|
destroy(): void;
|
|
54
28
|
}
|