@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@readium/navigator",
3
- "version": "2.4.0-beta.5",
3
+ "version": "2.4.0-beta.6",
4
4
  "type": "module",
5
5
  "description": "Next generation SDK for publications in Web Apps",
6
6
  "author": "readium",
@@ -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 preloadedElements: Map<string, HTMLAudioElement> = new Map();
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
- * Sets the current audio by href, using preloaded element if available or loading otherwise,
52
- * and preloads adjacent tracks.
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
- setCurrentAudio(currentIndex: number, direction: 'forward' | 'backward'): void {
59
- const href = this.pickPlayableHref(this._publication.readingOrder.items[currentIndex]);
60
- // When Web Audio is active, preloaded elements lack crossOrigin="anonymous"
61
- // and cannot be connected to MediaElementAudioSourceNode, so bypass the pool.
62
- const preloadedElement = !this.audioEngine.isWebAudioActive ? this.get(href) : undefined;
63
- if (preloadedElement) {
64
- this.audioEngine.setMediaElement(preloadedElement);
65
- this.clear(href);
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
- this.preloadAdjacent(currentIndex, direction);
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
- * Retrieves a preloaded audio element by URL.
87
- * @param href The URL of the audio resource.
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
- get(href: string): HTMLAudioElement | undefined {
91
- return this.preloadedElements.get(href);
92
- }
73
+ private update(currentIndex: number): void {
74
+ const items = this._publication.readingOrder.items;
75
+ const keep = new Set<string>();
93
76
 
94
- /**
95
- * Removes a preloaded element from the pool.
96
- * @param href The URL of the audio resource.
97
- */
98
- clear(href: string): void {
99
- this.preloadedElements.delete(href);
100
- }
101
-
102
- /**
103
- * Preloads the next track in the reading order.
104
- * @param publication The publication containing the reading order.
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
- * Preloads the previous track in the reading order.
117
- * @param currentIndex The current track index.
118
- */
119
- preloadPrevious(currentIndex: number): void {
120
- const prevIndex = currentIndex - 1;
121
- if (prevIndex >= 0) {
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
- * Preloads adjacent tracks (previous and next) for smoother navigation.
129
- * @param currentIndex The current track index.
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
- preloadAdjacent(currentIndex: number, direction: 'forward' | 'backward' = 'forward'): void {
133
- if (direction === 'forward') {
134
- this.preloadNext(currentIndex);
135
- this.preloadPrevious(currentIndex);
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.preloadPrevious(currentIndex);
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.preloadedElements.clear();
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 preloadedElements;
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
- * Sets the current audio by href, using preloaded element if available or loading otherwise,
14
- * and preloads adjacent tracks.
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
- setCurrentAudio(currentIndex: number, direction: 'forward' | 'backward'): void;
21
- preload(href: string): void;
16
+ private ensure;
22
17
  /**
23
- * Retrieves a preloaded audio element by URL.
24
- * @param href The URL of the audio resource.
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
- get(href: string): HTMLAudioElement | undefined;
21
+ private update;
28
22
  /**
29
- * Removes a preloaded element from the pool.
30
- * @param href The URL of the audio resource.
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
  }