@readium/navigator 2.4.0-beta.9 → 2.5.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.
Files changed (138) hide show
  1. package/dist/ReadiumCSS-after-B_e3a-PY.js +592 -0
  2. package/dist/ReadiumCSS-after-C-T_0paD.js +530 -0
  3. package/dist/ReadiumCSS-after-lr-n3fz2.js +475 -0
  4. package/dist/ReadiumCSS-after-mXeKKPap.js +490 -0
  5. package/dist/ReadiumCSS-before-Bjd3POej.js +426 -0
  6. package/dist/ReadiumCSS-before-CfXPAGaQ.js +425 -0
  7. package/dist/ReadiumCSS-before-CrNWvuyE.js +425 -0
  8. package/dist/ReadiumCSS-before-KVen5ceo.js +425 -0
  9. package/dist/ReadiumCSS-default-BKAG5pGU.js +162 -0
  10. package/dist/ReadiumCSS-default-C63bYOYF.js +183 -0
  11. package/dist/ReadiumCSS-default-CclvbeNC.js +162 -0
  12. package/dist/ReadiumCSS-default-DnlgDaBu.js +180 -0
  13. package/dist/ReadiumCSS-ebpaj_fonts_patch-Dt2XliTg.js +82 -0
  14. package/dist/index.js +2642 -3430
  15. package/dist/index.umd.cjs +4407 -995
  16. package/package.json +2 -2
  17. package/src/audio/AudioNavigator.ts +155 -42
  18. package/src/audio/AudioPoolManager.ts +27 -14
  19. package/src/audio/engine/AudioEngine.ts +4 -3
  20. package/src/audio/engine/PreservePitchProcessor.js +166 -101
  21. package/src/audio/engine/PreservePitchWorklet.ts +2 -17
  22. package/src/audio/engine/WebAudioEngine.ts +138 -160
  23. package/src/audio/engine/index.ts +2 -2
  24. package/src/audio/index.ts +3 -3
  25. package/src/audio/preferences/AudioDefaults.ts +2 -2
  26. package/src/audio/preferences/AudioPreferences.ts +11 -11
  27. package/src/audio/preferences/AudioPreferencesEditor.ts +13 -13
  28. package/src/audio/preferences/AudioSettings.ts +3 -3
  29. package/src/audio/preferences/index.ts +4 -4
  30. package/src/audio/protection/AudioNavigatorProtector.ts +4 -4
  31. package/src/css/index.ts +1 -1
  32. package/src/epub/EpubNavigator.ts +113 -78
  33. package/src/epub/css/Properties.ts +15 -15
  34. package/src/epub/css/ReadiumCSS.ts +43 -43
  35. package/src/epub/css/index.ts +2 -2
  36. package/src/epub/frame/FrameBlobBuilder.ts +31 -31
  37. package/src/epub/frame/FrameComms.ts +1 -1
  38. package/src/epub/frame/FrameManager.ts +13 -9
  39. package/src/epub/frame/FramePoolManager.ts +13 -13
  40. package/src/epub/frame/index.ts +4 -4
  41. package/src/epub/fxl/FXLCoordinator.ts +3 -3
  42. package/src/epub/fxl/FXLFrameManager.ts +8 -8
  43. package/src/epub/fxl/FXLFramePoolManager.ts +18 -14
  44. package/src/epub/fxl/FXLPeripherals.ts +4 -4
  45. package/src/epub/fxl/index.ts +5 -5
  46. package/src/epub/helpers/scriptMode.ts +45 -0
  47. package/src/epub/index.ts +6 -5
  48. package/src/epub/preferences/EpubDefaults.ts +23 -23
  49. package/src/epub/preferences/EpubPreferences.ts +16 -16
  50. package/src/epub/preferences/EpubPreferencesEditor.ts +53 -53
  51. package/src/epub/preferences/EpubSettings.ts +101 -101
  52. package/src/epub/preferences/index.ts +4 -4
  53. package/src/helpers/index.ts +2 -2
  54. package/src/index.ts +8 -8
  55. package/src/injection/Injector.ts +42 -42
  56. package/src/injection/epubInjectables.ts +86 -17
  57. package/src/injection/index.ts +2 -2
  58. package/src/injection/webpubInjectables.ts +2 -2
  59. package/src/preferences/Configurable.ts +2 -2
  60. package/src/preferences/PreferencesEditor.ts +2 -2
  61. package/src/preferences/guards.ts +2 -2
  62. package/src/preferences/index.ts +5 -5
  63. package/src/protection/CopyProtector.ts +5 -1
  64. package/src/protection/DevToolsDetector.ts +16 -16
  65. package/src/protection/DragAndDropProtector.ts +14 -1
  66. package/src/protection/NavigatorProtector.ts +6 -6
  67. package/src/webpub/WebPubBlobBuilder.ts +1 -1
  68. package/src/webpub/WebPubFrameManager.ts +8 -8
  69. package/src/webpub/WebPubFramePoolManager.ts +7 -7
  70. package/src/webpub/WebPubNavigator.ts +27 -27
  71. package/src/webpub/css/Properties.ts +3 -3
  72. package/src/webpub/css/WebPubCSS.ts +11 -11
  73. package/src/webpub/css/index.ts +2 -2
  74. package/src/webpub/index.ts +6 -6
  75. package/src/webpub/preferences/WebPubDefaults.ts +12 -12
  76. package/src/webpub/preferences/WebPubPreferences.ts +8 -8
  77. package/src/webpub/preferences/WebPubPreferencesEditor.ts +31 -31
  78. package/src/webpub/preferences/WebPubSettings.ts +45 -45
  79. package/src/webpub/preferences/index.ts +4 -4
  80. package/types/src/audio/AudioNavigator.d.ts +34 -5
  81. package/types/src/audio/AudioPoolManager.d.ts +7 -4
  82. package/types/src/audio/engine/AudioEngine.d.ts +4 -3
  83. package/types/src/audio/engine/PreservePitchWorklet.d.ts +1 -4
  84. package/types/src/audio/engine/WebAudioEngine.d.ts +15 -9
  85. package/types/src/audio/engine/index.d.ts +2 -2
  86. package/types/src/audio/index.d.ts +3 -3
  87. package/types/src/audio/preferences/AudioPreferences.d.ts +9 -9
  88. package/types/src/audio/preferences/AudioPreferencesEditor.d.ts +4 -4
  89. package/types/src/audio/preferences/AudioSettings.d.ts +3 -3
  90. package/types/src/audio/preferences/index.d.ts +4 -4
  91. package/types/src/audio/protection/AudioNavigatorProtector.d.ts +2 -2
  92. package/types/src/css/index.d.ts +1 -1
  93. package/types/src/epub/EpubNavigator.d.ts +15 -14
  94. package/types/src/epub/css/Properties.d.ts +2 -2
  95. package/types/src/epub/css/ReadiumCSS.d.ts +3 -3
  96. package/types/src/epub/css/index.d.ts +2 -2
  97. package/types/src/epub/frame/FrameBlobBuilder.d.ts +1 -1
  98. package/types/src/epub/frame/FrameComms.d.ts +1 -1
  99. package/types/src/epub/frame/FrameManager.d.ts +3 -2
  100. package/types/src/epub/frame/FramePoolManager.d.ts +3 -3
  101. package/types/src/epub/frame/index.d.ts +4 -4
  102. package/types/src/epub/fxl/FXLFrameManager.d.ts +3 -3
  103. package/types/src/epub/fxl/FXLFramePoolManager.d.ts +5 -5
  104. package/types/src/epub/fxl/FXLPeripherals.d.ts +2 -2
  105. package/types/src/epub/fxl/index.d.ts +5 -5
  106. package/types/src/epub/helpers/scriptMode.d.ts +16 -0
  107. package/types/src/epub/index.d.ts +6 -5
  108. package/types/src/epub/preferences/EpubDefaults.d.ts +1 -1
  109. package/types/src/epub/preferences/EpubPreferences.d.ts +2 -2
  110. package/types/src/epub/preferences/EpubPreferencesEditor.d.ts +5 -5
  111. package/types/src/epub/preferences/EpubSettings.d.ts +4 -4
  112. package/types/src/epub/preferences/index.d.ts +4 -4
  113. package/types/src/helpers/index.d.ts +2 -2
  114. package/types/src/index.d.ts +8 -8
  115. package/types/src/injection/Injector.d.ts +1 -1
  116. package/types/src/injection/epubInjectables.d.ts +5 -3
  117. package/types/src/injection/index.d.ts +2 -2
  118. package/types/src/injection/webpubInjectables.d.ts +1 -1
  119. package/types/src/preferences/Configurable.d.ts +1 -1
  120. package/types/src/preferences/PreferencesEditor.d.ts +1 -1
  121. package/types/src/preferences/guards.d.ts +1 -1
  122. package/types/src/preferences/index.d.ts +5 -5
  123. package/types/src/protection/CopyProtector.d.ts +1 -0
  124. package/types/src/protection/DragAndDropProtector.d.ts +2 -0
  125. package/types/src/protection/NavigatorProtector.d.ts +1 -1
  126. package/types/src/webpub/WebPubBlobBuilder.d.ts +1 -1
  127. package/types/src/webpub/WebPubFrameManager.d.ts +2 -2
  128. package/types/src/webpub/WebPubFramePoolManager.d.ts +3 -3
  129. package/types/src/webpub/WebPubNavigator.d.ts +10 -10
  130. package/types/src/webpub/css/Properties.d.ts +2 -2
  131. package/types/src/webpub/css/WebPubCSS.d.ts +2 -2
  132. package/types/src/webpub/css/index.d.ts +2 -2
  133. package/types/src/webpub/index.d.ts +6 -6
  134. package/types/src/webpub/preferences/WebPubDefaults.d.ts +1 -1
  135. package/types/src/webpub/preferences/WebPubPreferences.d.ts +2 -2
  136. package/types/src/webpub/preferences/WebPubPreferencesEditor.d.ts +5 -5
  137. package/types/src/webpub/preferences/WebPubSettings.d.ts +4 -4
  138. package/types/src/webpub/preferences/index.d.ts +4 -4
@@ -3,8 +3,8 @@
3
3
  import {
4
4
  AudioEngine,
5
5
  Playback,
6
- } from "./AudioEngine";
7
- import { PreservePitchWorklet } from "./PreservePitchWorklet";
6
+ } from "./AudioEngine.ts";
7
+ import { PreservePitchWorklet } from "./PreservePitchWorklet.ts";
8
8
 
9
9
  type EventCallback = (data: any) => void;
10
10
 
@@ -16,8 +16,6 @@ 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;
20
- private currentPlaybackRate: number = 1;
21
19
  private isMutedValue: boolean = false;
22
20
  private isPlayingValue: boolean = false;
23
21
  private isPausedValue: boolean = false;
@@ -95,120 +93,6 @@ export class WebAudioEngine implements AudioEngine {
95
93
  );
96
94
  }
97
95
 
98
- private deactivateWebAudio(): void {
99
- if (this.worklet) {
100
- this.worklet.destroy();
101
- this.worklet = null;
102
- }
103
- if (this.sourceNode) {
104
- this.sourceNode.disconnect();
105
- this.sourceNode = null;
106
- }
107
- if (this.gainNode) {
108
- this.gainNode.disconnect();
109
- this.gainNode = null;
110
- }
111
- this.webAudioActive = false;
112
- }
113
-
114
- /**
115
- * Sets the media element for playback.
116
- * @param element The HTML audio element to use.
117
- */
118
- public setMediaElement(element: HTMLAudioElement): void {
119
- // Remove listeners BEFORE pausing so the pause doesn't leak through
120
- this.mediaElement.removeEventListener("canplaythrough", this.boundOnCanPlayThrough);
121
- this.mediaElement.removeEventListener("timeupdate", this.boundOnTimeUpdate);
122
- this.mediaElement.removeEventListener("error", this.boundOnError);
123
- this.mediaElement.removeEventListener("ended", this.boundOnEnded);
124
- this.mediaElement.removeEventListener("stalled", this.boundOnStalled);
125
- this.mediaElement.removeEventListener("emptied", this.boundOnEmptied);
126
- this.mediaElement.removeEventListener("suspend", this.boundOnSuspend);
127
- this.mediaElement.removeEventListener("waiting", this.boundOnWaiting);
128
- this.mediaElement.removeEventListener("loadedmetadata", this.boundOnLoadedMetadata);
129
- this.mediaElement.removeEventListener("seeking", this.boundOnSeeking);
130
- this.mediaElement.removeEventListener("seeked", this.boundOnSeeked);
131
- this.mediaElement.removeEventListener("play", this.boundOnPlay);
132
- this.mediaElement.removeEventListener("playing", this.boundOnPlaying);
133
- this.mediaElement.removeEventListener("pause", this.boundOnPause);
134
- this.mediaElement.removeEventListener("progress", this.boundOnProgress);
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
-
147
- // Set new media element
148
- this.mediaElement = element;
149
-
150
- // Add event listeners to new element
151
- this.mediaElement.addEventListener("canplaythrough", this.boundOnCanPlayThrough);
152
- this.mediaElement.addEventListener("timeupdate", this.boundOnTimeUpdate);
153
- this.mediaElement.addEventListener("error", this.boundOnError);
154
- this.mediaElement.addEventListener("ended", this.boundOnEnded);
155
- this.mediaElement.addEventListener("stalled", this.boundOnStalled);
156
- this.mediaElement.addEventListener("emptied", this.boundOnEmptied);
157
- this.mediaElement.addEventListener("suspend", this.boundOnSuspend);
158
- this.mediaElement.addEventListener("waiting", this.boundOnWaiting);
159
- this.mediaElement.addEventListener("loadedmetadata", this.boundOnLoadedMetadata);
160
- this.mediaElement.addEventListener("seeking", this.boundOnSeeking);
161
- this.mediaElement.addEventListener("seeked", this.boundOnSeeked);
162
- this.mediaElement.addEventListener("play", this.boundOnPlay);
163
- this.mediaElement.addEventListener("playing", this.boundOnPlaying);
164
- this.mediaElement.addEventListener("pause", this.boundOnPause);
165
- this.mediaElement.addEventListener("progress", this.boundOnProgress);
166
-
167
- // Re-apply current volume and playback rate to the new element
168
- this.mediaElement.volume = this.isMutedValue ? 0 : this.currentVolume;
169
- this.mediaElement.playbackRate = this.currentPlaybackRate;
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
-
191
- // Check if metadata is already loaded (common with preloaded elements)
192
- if (this.mediaElement.readyState >= 1) {
193
- this.onLoadedMetadata(new Event('loadedmetadata'));
194
- }
195
-
196
- // Preloaded elements may have already buffered data before being swapped in,
197
- // so progress events would have fired before we were listening. Emit now if
198
- // seekable ranges are already available.
199
- if (this.mediaElement.seekable.length > 0) {
200
- this.onProgress();
201
- }
202
-
203
- // Check if the element is already loaded and trigger appropriate events
204
- if (this.mediaElement.readyState >= 4) {
205
- this.onCanPlayThrough();
206
- } else {
207
- this.isLoadingValue = true;
208
- this.isLoadedValue = false;
209
- }
210
- }
211
-
212
96
  // Ensure AudioContext is running
213
97
  private async ensureAudioContextRunning() {
214
98
  if (!this.audioContext) {
@@ -313,6 +197,9 @@ export class WebAudioEngine implements AudioEngine {
313
197
  }
314
198
 
315
199
  try {
200
+ if (this.audioContext) {
201
+ await this.ensureAudioContextRunning();
202
+ }
316
203
  await this.mediaElement.play();
317
204
  this.isPlayingValue = true;
318
205
  this.isPausedValue = false;
@@ -351,24 +238,17 @@ export class WebAudioEngine implements AudioEngine {
351
238
  * @volume The volume to set, in the range [0, 1].
352
239
  */
353
240
  public setVolume(volume: number): void {
354
- if (volume < 0) {
355
- this.currentVolume = 0;
356
- this.mediaElement.volume = 0;
357
- if (this.gainNode) {
358
- this.gainNode.gain.value = 0;
359
- }
360
- this.isMutedValue = true;
361
- return;
362
- }
363
- if (volume > 1) {
364
- this.setVolume(volume / 100);
365
- return;
366
- }
367
- this.currentVolume = volume;
368
- this.mediaElement.volume = volume;
241
+ const clamped = Math.max(0, Math.min(1, volume));
369
242
  if (this.gainNode) {
370
- this.gainNode.gain.value = volume;
243
+ // When the Web Audio graph is active, keep the media element at full
244
+ // volume and control level exclusively through the GainNode to avoid
245
+ // double-attenuation (e.g. 0.5 × 0.5 = 0.25).
246
+ this.mediaElement.volume = 1;
247
+ this.gainNode.gain.value = clamped;
248
+ } else {
249
+ this.mediaElement.volume = clamped;
371
250
  }
251
+ this.isMutedValue = clamped === 0;
372
252
  }
373
253
 
374
254
  /**
@@ -452,7 +332,6 @@ export class WebAudioEngine implements AudioEngine {
452
332
  * Sets the playback rate of the audio resource with pitch preservation.
453
333
  */
454
334
  public setPlaybackRate(rate: number, preservePitch: boolean): void {
455
- this.currentPlaybackRate = rate;
456
335
  this.mediaElement.playbackRate = rate;
457
336
  if (preservePitch) {
458
337
  if ('preservesPitch' in this.mediaElement) {
@@ -461,16 +340,14 @@ export class WebAudioEngine implements AudioEngine {
461
340
  // Activate Web Audio graph first, then attach the worklet
462
341
  this.activateWebAudio().then(() => {
463
342
  if (!this.worklet) {
464
- if (this.sourceNode) {
465
- this.sourceNode.disconnect();
466
- this.sourceNode = null;
467
- }
468
343
  PreservePitchWorklet.createWorklet({
469
344
  ctx: this.getOrCreateAudioContext(),
470
- mediaElement: this.mediaElement,
471
345
  pitchFactor: 1.0
472
346
  }).then(worklet => {
347
+ // Rewire: sourceNode → workletNode → gainNode
348
+ if (this.sourceNode) this.sourceNode.disconnect();
473
349
  this.worklet = worklet;
350
+ this.sourceNode?.connect(this.worklet.workletNode!);
474
351
  this.worklet.workletNode!.connect(this.gainNode!);
475
352
  this.worklet.updatePitchFactor(1 / rate);
476
353
  }).catch(err => {
@@ -484,12 +361,17 @@ export class WebAudioEngine implements AudioEngine {
484
361
  });
485
362
  }
486
363
  } else {
364
+ // Disable native pitch preservation (mirrors the check in the true branch)
365
+ if ('preservesPitch' in this.mediaElement) {
366
+ (this.mediaElement as any).preservesPitch = false;
367
+ }
368
+
487
369
  if (this.worklet) {
488
370
  this.worklet.destroy();
489
371
  this.worklet = null;
490
- // Worklet is gone; restore the direct source gain path
491
- if (this.webAudioActive) {
492
- this.sourceNode = new MediaElementAudioSourceNode(this.getOrCreateAudioContext(), { mediaElement: this.mediaElement });
372
+ // Restore: sourceNodegainNode
373
+ if (this.webAudioActive && this.sourceNode) {
374
+ this.sourceNode.disconnect();
493
375
  this.sourceNode.connect(this.gainNode!);
494
376
  }
495
377
  }
@@ -519,28 +401,57 @@ export class WebAudioEngine implements AudioEngine {
519
401
  this.mediaElement.src = src;
520
402
  this.mediaElement.load();
521
403
 
522
- await new Promise<void>((resolve, reject) => {
523
- const onReady = () => {
524
- this.mediaElement.removeEventListener("canplaythrough", onReady);
525
- this.mediaElement.removeEventListener("error", onFail);
526
- resolve();
527
- };
528
- const onFail = () => {
529
- this.mediaElement.removeEventListener("canplaythrough", onReady);
530
- this.mediaElement.removeEventListener("error", onFail);
531
- reject(new Error("Audio reload with CORS failed — server may not send Access-Control-Allow-Origin"));
532
- };
533
- this.mediaElement.addEventListener("canplaythrough", onReady);
534
- this.mediaElement.addEventListener("error", onFail);
535
- });
404
+ try {
405
+ await new Promise<void>((resolve, reject) => {
406
+ const onReady = () => {
407
+ this.mediaElement.removeEventListener("canplaythrough", onReady);
408
+ this.mediaElement.removeEventListener("error", onFail);
409
+ resolve();
410
+ };
411
+ const onFail = () => {
412
+ this.mediaElement.removeEventListener("canplaythrough", onReady);
413
+ this.mediaElement.removeEventListener("error", onFail);
414
+ reject(new Error("Audio reload with CORS failed — server may not send Access-Control-Allow-Origin"));
415
+ };
416
+ this.mediaElement.addEventListener("canplaythrough", onReady);
417
+ this.mediaElement.addEventListener("error", onFail);
418
+ });
419
+ } catch (err) {
420
+ // Roll back to non-CORS mode so the element remains playable.
421
+ // crossOrigin = "" still maps to "anonymous"; remove the attribute entirely
422
+ // to disable CORS on the reload.
423
+ this.mediaElement.removeAttribute("crossorigin");
424
+ this.mediaElement.src = src;
425
+ this.mediaElement.load();
426
+ if (wasPlaying) {
427
+ await new Promise<void>(resolve => {
428
+ const onReady = () => {
429
+ this.mediaElement.removeEventListener("canplaythrough", onReady);
430
+ resolve();
431
+ };
432
+ this.mediaElement.addEventListener("canplaythrough", onReady);
433
+ });
434
+ this.mediaElement.currentTime = currentTime;
435
+ await this.mediaElement.play();
436
+ this.isPlayingValue = true;
437
+ this.isPausedValue = false;
438
+ } else {
439
+ this.mediaElement.currentTime = currentTime;
440
+ }
441
+ throw err;
442
+ }
536
443
 
537
444
  this.mediaElement.currentTime = currentTime;
538
445
 
539
446
  this.sourceNode = new MediaElementAudioSourceNode(this.getOrCreateAudioContext(), { mediaElement: this.mediaElement });
540
-
541
- // Create gainNode lazily when Web Audio is activated
447
+
448
+ // Create gainNode lazily when Web Audio is activated.
449
+ // Seed its gain from the current element volume, then reset the element to
450
+ // 1.0 so volume isn't applied twice once setVolume routes through the node.
542
451
  const audioContext = this.getOrCreateAudioContext();
543
452
  this.gainNode = audioContext.createGain();
453
+ this.gainNode.gain.value = this.mediaElement.volume;
454
+ this.mediaElement.volume = 1;
544
455
  this.sourceNode.connect(this.gainNode);
545
456
  this.gainNode.connect(audioContext.destination);
546
457
 
@@ -558,10 +469,77 @@ export class WebAudioEngine implements AudioEngine {
558
469
  return this.webAudioActive;
559
470
  }
560
471
 
472
+ /**
473
+ * Tears down the Web Audio graph and restores the media element to standalone
474
+ * playback. Safe to call even if Web Audio was never activated.
475
+ */
476
+ private tearDownWebAudio(): void {
477
+ if (this.worklet) {
478
+ this.worklet.destroy();
479
+ this.worklet = null;
480
+ }
481
+ if (this.sourceNode) {
482
+ this.sourceNode.disconnect();
483
+ this.sourceNode = null;
484
+ }
485
+ if (this.gainNode) {
486
+ // Restore the volume that was previously managed by the GainNode.
487
+ this.mediaElement.volume = this.gainNode.gain.value;
488
+ this.gainNode.disconnect();
489
+ this.gainNode = null;
490
+ }
491
+ this.webAudioActive = false;
492
+ }
493
+
494
+ /**
495
+ * Changes the src of the primary media element without swapping the element.
496
+ * Preserves the RemotePlayback session and all attached event listeners.
497
+ * When the Web Audio graph is active, the new src is loaded with
498
+ * crossOrigin="anonymous". If the CORS request fails (server does not send
499
+ * the required headers), the graph is torn down and the src is reloaded
500
+ * without CORS so playback continues — just without pitch correction.
501
+ */
502
+ public changeSrc(href: string): void {
503
+ if (this.mediaElement.src === href) {
504
+ return;
505
+ }
506
+ this.mediaElement.pause();
507
+ this.isPlayingValue = false;
508
+ this.isPausedValue = false;
509
+ this.isLoadedValue = false;
510
+ this.isLoadingValue = true;
511
+ this.isEndedValue = false;
512
+
513
+ if (this.webAudioActive) {
514
+ this.mediaElement.crossOrigin = "anonymous";
515
+ this.mediaElement.src = href;
516
+ this.mediaElement.load();
517
+
518
+ const onReady = () => { cleanup(); };
519
+ const onFail = () => {
520
+ cleanup();
521
+ console.warn("CORS reload failed for new track — disabling Web Audio graph:", href);
522
+ this.tearDownWebAudio();
523
+ this.mediaElement.removeAttribute("crossorigin");
524
+ this.mediaElement.src = href;
525
+ this.mediaElement.load();
526
+ };
527
+ const cleanup = () => {
528
+ this.mediaElement.removeEventListener("canplaythrough", onReady);
529
+ this.mediaElement.removeEventListener("error", onFail);
530
+ };
531
+ this.mediaElement.addEventListener("canplaythrough", onReady);
532
+ this.mediaElement.addEventListener("error", onFail);
533
+ } else {
534
+ this.mediaElement.src = href;
535
+ this.mediaElement.load();
536
+ }
537
+ }
538
+
561
539
  /**
562
540
  * Returns the HTML media element used for playback.
563
541
  */
564
542
  public getMediaElement(): HTMLMediaElement {
565
543
  return this.mediaElement;
566
544
  }
567
- }
545
+ }
@@ -1,2 +1,2 @@
1
- export * from './AudioEngine';
2
- export * from './WebAudioEngine';
1
+ export * from './AudioEngine.ts';
2
+ export * from './WebAudioEngine.ts';
@@ -1,3 +1,3 @@
1
- export * from './engine';
2
- export * from './preferences';
3
- export * from './AudioNavigator';
1
+ export * from './engine/index.ts';
2
+ export * from './preferences/index.ts';
3
+ export * from './AudioNavigator.ts';
@@ -2,12 +2,12 @@ import {
2
2
  ensureBoolean,
3
3
  ensureValueInRange,
4
4
  ensureNonNegative
5
- } from "../../preferences/guards";
5
+ } from "../../preferences/guards.ts";
6
6
  import {
7
7
  volumeRangeConfig,
8
8
  playbackRateRangeConfig,
9
9
  skipIntervalRangeConfig
10
- } from "../../preferences/Types";
10
+ } from "../../preferences/Types.ts";
11
11
 
12
12
  export interface IAudioDefaults {
13
13
  volume?: number | null;
@@ -1,14 +1,14 @@
1
- import { ConfigurablePreferences } from "../../preferences/Configurable";
1
+ import { ConfigurablePreferences } from "../../preferences/Configurable.ts";
2
2
  import {
3
3
  ensureBoolean,
4
4
  ensureValueInRange,
5
5
  ensureNonNegative
6
- } from "../../preferences/guards";
6
+ } from "../../preferences/guards.ts";
7
7
  import {
8
8
  volumeRangeConfig,
9
9
  playbackRateRangeConfig,
10
10
  skipIntervalRangeConfig
11
- } from "../../preferences/Types";
11
+ } from "../../preferences/Types.ts";
12
12
 
13
13
  export interface IAudioPreferences {
14
14
  volume?: number | null;
@@ -22,14 +22,14 @@ export interface IAudioPreferences {
22
22
  }
23
23
 
24
24
  export class AudioPreferences implements ConfigurablePreferences<AudioPreferences> {
25
- public readonly volume: number | null | undefined;
26
- public readonly playbackRate: number | null | undefined;
27
- public readonly preservePitch: boolean | null | undefined;
28
- public readonly skipBackwardInterval: number | null | undefined;
29
- public readonly skipForwardInterval: number | null | undefined;
30
- public readonly pollInterval: number | null | undefined;
31
- public readonly autoPlay: boolean | null | undefined;
32
- public readonly enableMediaSession: boolean | null | undefined;
25
+ public volume: number | null | undefined;
26
+ public playbackRate: number | null | undefined;
27
+ public preservePitch: boolean | null | undefined;
28
+ public skipBackwardInterval: number | null | undefined;
29
+ public skipForwardInterval: number | null | undefined;
30
+ public pollInterval: number | null | undefined;
31
+ public autoPlay: boolean | null | undefined;
32
+ public enableMediaSession: boolean | null | undefined;
33
33
 
34
34
  constructor(preferences: IAudioPreferences = {}) {
35
35
  this.volume = ensureValueInRange(preferences.volume, volumeRangeConfig.range);
@@ -1,12 +1,12 @@
1
- import { IPreferencesEditor } from "../../preferences/PreferencesEditor";
2
- import { AudioPreferences } from "./AudioPreferences";
3
- import { AudioSettings } from "./AudioSettings";
4
- import { Preference, BooleanPreference, RangePreference } from "../../preferences/Preference";
1
+ import { IPreferencesEditor } from "../../preferences/PreferencesEditor.ts";
2
+ import { AudioPreferences } from "./AudioPreferences.ts";
3
+ import { AudioSettings } from "./AudioSettings.ts";
4
+ import { Preference, BooleanPreference, RangePreference } from "../../preferences/Preference.ts";
5
5
  import {
6
6
  volumeRangeConfig,
7
7
  playbackRateRangeConfig,
8
8
  skipIntervalRangeConfig
9
- } from "../../preferences/Types";
9
+ } from "../../preferences/Types.ts";
10
10
 
11
11
  export class AudioPreferencesEditor implements IPreferencesEditor {
12
12
  preferences: AudioPreferences;
@@ -31,7 +31,7 @@ export class AudioPreferencesEditor implements IPreferencesEditor {
31
31
  effectiveValue: this.settings.volume,
32
32
  isEffective: this.preferences.volume !== null,
33
33
  onChange: (newValue: number | null | undefined) => {
34
- this.updatePreference("volume", newValue ?? 1);
34
+ this.updatePreference("volume", newValue ?? null);
35
35
  },
36
36
  supportedRange: volumeRangeConfig.range,
37
37
  step: volumeRangeConfig.step
@@ -44,7 +44,7 @@ export class AudioPreferencesEditor implements IPreferencesEditor {
44
44
  effectiveValue: this.settings.playbackRate,
45
45
  isEffective: this.preferences.playbackRate !== null,
46
46
  onChange: (newValue: number | null | undefined) => {
47
- this.updatePreference("playbackRate", newValue ?? 1);
47
+ this.updatePreference("playbackRate", newValue ?? null);
48
48
  },
49
49
  supportedRange: playbackRateRangeConfig.range,
50
50
  step: playbackRateRangeConfig.step
@@ -57,7 +57,7 @@ export class AudioPreferencesEditor implements IPreferencesEditor {
57
57
  effectiveValue: this.settings.preservePitch,
58
58
  isEffective: this.preferences.preservePitch !== null,
59
59
  onChange: (newValue: boolean | null | undefined) => {
60
- this.updatePreference("preservePitch", newValue ?? true);
60
+ this.updatePreference("preservePitch", newValue ?? null);
61
61
  }
62
62
  });
63
63
  }
@@ -68,7 +68,7 @@ export class AudioPreferencesEditor implements IPreferencesEditor {
68
68
  effectiveValue: this.settings.skipBackwardInterval,
69
69
  isEffective: this.preferences.skipBackwardInterval !== null,
70
70
  onChange: (newValue: number | null | undefined) => {
71
- this.updatePreference("skipBackwardInterval", newValue ?? 10);
71
+ this.updatePreference("skipBackwardInterval", newValue ?? null);
72
72
  },
73
73
  supportedRange: skipIntervalRangeConfig.range,
74
74
  step: skipIntervalRangeConfig.step
@@ -81,7 +81,7 @@ export class AudioPreferencesEditor implements IPreferencesEditor {
81
81
  effectiveValue: this.settings.skipForwardInterval,
82
82
  isEffective: this.preferences.skipForwardInterval !== null,
83
83
  onChange: (newValue: number | null | undefined) => {
84
- this.updatePreference("skipForwardInterval", newValue ?? 10);
84
+ this.updatePreference("skipForwardInterval", newValue ?? null);
85
85
  },
86
86
  supportedRange: skipIntervalRangeConfig.range,
87
87
  step: skipIntervalRangeConfig.step
@@ -94,7 +94,7 @@ export class AudioPreferencesEditor implements IPreferencesEditor {
94
94
  effectiveValue: this.settings.pollInterval,
95
95
  isEffective: this.preferences.pollInterval !== null,
96
96
  onChange: (newValue: number | null | undefined) => {
97
- this.updatePreference("pollInterval", newValue ?? 1000);
97
+ this.updatePreference("pollInterval", newValue ?? null);
98
98
  }
99
99
  });
100
100
  }
@@ -105,7 +105,7 @@ export class AudioPreferencesEditor implements IPreferencesEditor {
105
105
  effectiveValue: this.settings.autoPlay,
106
106
  isEffective: this.preferences.autoPlay !== null,
107
107
  onChange: (newValue: boolean | null | undefined) => {
108
- this.updatePreference("autoPlay", newValue ?? true);
108
+ this.updatePreference("autoPlay", newValue ?? null);
109
109
  }
110
110
  });
111
111
  }
@@ -116,7 +116,7 @@ export class AudioPreferencesEditor implements IPreferencesEditor {
116
116
  effectiveValue: this.settings.enableMediaSession,
117
117
  isEffective: this.preferences.enableMediaSession !== null,
118
118
  onChange: (newValue: boolean | null | undefined) => {
119
- this.updatePreference("enableMediaSession", newValue ?? true);
119
+ this.updatePreference("enableMediaSession", newValue ?? null);
120
120
  }
121
121
  });
122
122
  }
@@ -1,6 +1,6 @@
1
- import { AudioPreferences } from "./AudioPreferences";
2
- import { AudioDefaults } from "./AudioDefaults";
3
- import { ConfigurableSettings } from "../../preferences/Configurable";
1
+ import { AudioPreferences } from "./AudioPreferences.ts";
2
+ import { AudioDefaults } from "./AudioDefaults.ts";
3
+ import { ConfigurableSettings } from "../../preferences/Configurable.ts";
4
4
 
5
5
  export interface IAudioSettings extends ConfigurableSettings {
6
6
  volume: number;
@@ -1,4 +1,4 @@
1
- export * from './AudioPreferences';
2
- export * from './AudioDefaults';
3
- export * from './AudioSettings';
4
- export * from './AudioPreferencesEditor';
1
+ export * from './AudioPreferences.ts';
2
+ export * from './AudioDefaults.ts';
3
+ export * from './AudioSettings.ts';
4
+ export * from './AudioPreferencesEditor.ts';
@@ -1,7 +1,7 @@
1
- import { NavigatorProtector } from "../../protection/NavigatorProtector";
2
- import { DragAndDropProtector } from "../../protection/DragAndDropProtector";
3
- import { CopyProtector } from "../../protection/CopyProtector";
4
- import { IContentProtectionConfig } from "../../Navigator";
1
+ import { NavigatorProtector } from "../../protection/NavigatorProtector.ts";
2
+ import { DragAndDropProtector } from "../../protection/DragAndDropProtector.ts";
3
+ import { CopyProtector } from "../../protection/CopyProtector.ts";
4
+ import { IContentProtectionConfig } from "../../Navigator.ts";
5
5
 
6
6
  export class AudioNavigatorProtector extends NavigatorProtector {
7
7
  private dragAndDropProtector?: DragAndDropProtector;
package/src/css/index.ts CHANGED
@@ -1 +1 @@
1
- export * from "./Properties";
1
+ export * from "./Properties.ts";