@rixl/media-core 1.0.2 → 1.1.0

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 (173) hide show
  1. package/README.md +74 -84
  2. package/dist/chunks/B-QvSqjj.js +1 -0
  3. package/dist/chunks/B2DDE32u.js +1 -0
  4. package/dist/chunks/BZFoe94X.js +1 -0
  5. package/dist/chunks/DMkwQjgA.js +1 -0
  6. package/dist/index.d.ts +2005 -82
  7. package/dist/index.js +6 -73
  8. package/package.json +13 -6
  9. package/dist/api/analytics.d.ts +0 -16
  10. package/dist/api/analytics.js +0 -227
  11. package/dist/api/feed.js +0 -52
  12. package/dist/api/heatmap.d.ts +0 -6
  13. package/dist/api/heatmap.js +0 -28
  14. package/dist/api/hot-segments.d.ts +0 -7
  15. package/dist/api/hot-segments.js +0 -20
  16. package/dist/api/image.js +0 -11
  17. package/dist/api/rixl-client.d.ts +0 -3
  18. package/dist/api/rixl-client.js +0 -14
  19. package/dist/api/upload.d.ts +0 -76
  20. package/dist/api/upload.js +0 -150
  21. package/dist/api/video.js +0 -11
  22. package/dist/lib/analytics-builders.d.ts +0 -59
  23. package/dist/lib/analytics-builders.js +0 -88
  24. package/dist/lib/analytics-initialize.d.ts +0 -1
  25. package/dist/lib/analytics-initialize.js +0 -59
  26. package/dist/lib/analytics-utils.d.ts +0 -43
  27. package/dist/lib/analytics-utils.js +0 -85
  28. package/dist/lib/analytics-wire.js +0 -153
  29. package/dist/lib/buffer-progress.d.ts +0 -20
  30. package/dist/lib/buffer-progress.js +0 -37
  31. package/dist/lib/caption-styles.d.ts +0 -20
  32. package/dist/lib/caption-styles.js +0 -56
  33. package/dist/lib/caption-utils.d.ts +0 -80
  34. package/dist/lib/caption-utils.js +0 -111
  35. package/dist/lib/chapter-utils.d.ts +0 -102
  36. package/dist/lib/chapter-utils.js +0 -147
  37. package/dist/lib/click-disambiguation.d.ts +0 -44
  38. package/dist/lib/click-disambiguation.js +0 -80
  39. package/dist/lib/click-outside.d.ts +0 -68
  40. package/dist/lib/click-outside.js +0 -64
  41. package/dist/lib/colors.d.ts +0 -6
  42. package/dist/lib/colors.js +0 -16
  43. package/dist/lib/content-analytics.d.ts +0 -27
  44. package/dist/lib/content-analytics.js +0 -38
  45. package/dist/lib/environment-effects.d.ts +0 -89
  46. package/dist/lib/environment-effects.js +0 -139
  47. package/dist/lib/feed-autoscroll.d.ts +0 -10
  48. package/dist/lib/feed-autoscroll.js +0 -13
  49. package/dist/lib/feed-container-height.d.ts +0 -7
  50. package/dist/lib/feed-container-height.js +0 -11
  51. package/dist/lib/feed-description-overflow.d.ts +0 -1
  52. package/dist/lib/feed-description-overflow.js +0 -9
  53. package/dist/lib/feed-dwell.d.ts +0 -2
  54. package/dist/lib/feed-dwell.js +0 -8
  55. package/dist/lib/feed-loop.d.ts +0 -10
  56. package/dist/lib/feed-loop.js +0 -21
  57. package/dist/lib/feed-scroll.d.ts +0 -11
  58. package/dist/lib/feed-scroll.js +0 -19
  59. package/dist/lib/feed-transforms.d.ts +0 -5
  60. package/dist/lib/feed-transforms.js +0 -30
  61. package/dist/lib/filter-undefined.js +0 -11
  62. package/dist/lib/fullscreen-utils.d.ts +0 -1
  63. package/dist/lib/fullscreen-utils.js +0 -7
  64. package/dist/lib/hls-buffer-flush.js +0 -46
  65. package/dist/lib/hls-error-recovery.d.ts +0 -35
  66. package/dist/lib/hls-error-recovery.js +0 -75
  67. package/dist/lib/hls-event-handlers.js +0 -61
  68. package/dist/lib/hls-loader.js +0 -23
  69. package/dist/lib/hls-setup.d.ts +0 -72
  70. package/dist/lib/hls-setup.js +0 -144
  71. package/dist/lib/hls-utils.d.ts +0 -31
  72. package/dist/lib/hls-utils.js +0 -34
  73. package/dist/lib/image-render.d.ts +0 -29
  74. package/dist/lib/image-render.js +0 -63
  75. package/dist/lib/keyboard-config.d.ts +0 -66
  76. package/dist/lib/keyboard-config.js +0 -127
  77. package/dist/lib/keyboard-utils.d.ts +0 -4
  78. package/dist/lib/keyboard-utils.js +0 -11
  79. package/dist/lib/lifecycle-effects.d.ts +0 -67
  80. package/dist/lib/lifecycle-effects.js +0 -78
  81. package/dist/lib/loading-debounce.d.ts +0 -26
  82. package/dist/lib/loading-debounce.js +0 -38
  83. package/dist/lib/numbers.d.ts +0 -6
  84. package/dist/lib/numbers.js +0 -36
  85. package/dist/lib/pip-utils.d.ts +0 -16
  86. package/dist/lib/pip-utils.js +0 -32
  87. package/dist/lib/playback-handlers.d.ts +0 -33
  88. package/dist/lib/playback-handlers.js +0 -51
  89. package/dist/lib/playback-slider.d.ts +0 -23
  90. package/dist/lib/playback-slider.js +0 -24
  91. package/dist/lib/player-labels.d.ts +0 -23
  92. package/dist/lib/player-labels.js +0 -132
  93. package/dist/lib/player-menu-labels.d.ts +0 -80
  94. package/dist/lib/player-menu-labels.js +0 -829
  95. package/dist/lib/player-ui-actions.d.ts +0 -10
  96. package/dist/lib/player-ui-actions.js +0 -58
  97. package/dist/lib/progress-fraction.d.ts +0 -11
  98. package/dist/lib/progress-fraction.js +0 -20
  99. package/dist/lib/progress-persist.d.ts +0 -28
  100. package/dist/lib/progress-persist.js +0 -54
  101. package/dist/lib/segment-analytics.d.ts +0 -20
  102. package/dist/lib/segment-analytics.js +0 -29
  103. package/dist/lib/subtitle-logic.d.ts +0 -31
  104. package/dist/lib/subtitle-logic.js +0 -75
  105. package/dist/lib/subtitle-utils.d.ts +0 -16
  106. package/dist/lib/subtitle-utils.js +0 -37
  107. package/dist/lib/time.d.ts +0 -1
  108. package/dist/lib/time.js +0 -17
  109. package/dist/lib/track-persistence.d.ts +0 -4
  110. package/dist/lib/track-persistence.js +0 -35
  111. package/dist/lib/video-preload-config.d.ts +0 -34
  112. package/dist/lib/video-preload-config.js +0 -41
  113. package/dist/lib/video-spinner.d.ts +0 -25
  114. package/dist/lib/video-spinner.js +0 -35
  115. package/dist/lib/video-stats.d.ts +0 -28
  116. package/dist/lib/video-stats.js +0 -44
  117. package/dist/lib/volume-utils.d.ts +0 -59
  118. package/dist/lib/volume-utils.js +0 -55
  119. package/dist/progressbar.d.ts +0 -10
  120. package/dist/progressbar.js +0 -125
  121. package/dist/store/analytics-context-store.d.ts +0 -4
  122. package/dist/store/analytics-context-store.js +0 -13
  123. package/dist/store/analytics-device-store.d.ts +0 -8
  124. package/dist/store/analytics-device-store.js +0 -44
  125. package/dist/store/analytics-session-store.d.ts +0 -8
  126. package/dist/store/analytics-session-store.js +0 -40
  127. package/dist/store/analytics-store.d.ts +0 -30
  128. package/dist/store/analytics-store.js +0 -88
  129. package/dist/store/controls-store.d.ts +0 -16
  130. package/dist/store/controls-store.js +0 -53
  131. package/dist/store/feed-cache-store.d.ts +0 -24
  132. package/dist/store/feed-cache-store.js +0 -165
  133. package/dist/store/feed-interaction-store.d.ts +0 -17
  134. package/dist/store/feed-interaction-store.js +0 -32
  135. package/dist/store/feed-store.d.ts +0 -16
  136. package/dist/store/feed-store.js +0 -38
  137. package/dist/store/generic-store.js +0 -24
  138. package/dist/store/image-store.d.ts +0 -2
  139. package/dist/store/image-store.js +0 -8
  140. package/dist/store/player-container-element-store.d.ts +0 -10
  141. package/dist/store/player-container-element-store.js +0 -38
  142. package/dist/store/player-global-actions.js +0 -53
  143. package/dist/store/player-media-element-store.d.ts +0 -10
  144. package/dist/store/player-media-element-store.js +0 -38
  145. package/dist/store/player-store-actions.js +0 -197
  146. package/dist/store/player-store.d.ts +0 -94
  147. package/dist/store/player-store.js +0 -127
  148. package/dist/store/player-track-actions.js +0 -44
  149. package/dist/store/postStore.d.ts +0 -20
  150. package/dist/store/postStore.js +0 -112
  151. package/dist/store/settings/constants.d.ts +0 -15
  152. package/dist/store/settings/constants.js +0 -47
  153. package/dist/store/settings/defaults.d.ts +0 -2
  154. package/dist/store/settings/defaults.js +0 -24
  155. package/dist/store/settings/storage.d.ts +0 -3
  156. package/dist/store/settings/storage.js +0 -28
  157. package/dist/store/settings/store.d.ts +0 -32
  158. package/dist/store/settings/store.js +0 -59
  159. package/dist/store/settings/types.d.ts +0 -27
  160. package/dist/store/settings/validation.d.ts +0 -7
  161. package/dist/store/settings/validation.js +0 -55
  162. package/dist/store/settings-menu-store.d.ts +0 -19
  163. package/dist/store/settings-menu-store.js +0 -30
  164. package/dist/store/video-store.d.ts +0 -2
  165. package/dist/store/video-store.js +0 -8
  166. package/dist/types/analytics.d.ts +0 -113
  167. package/dist/types/feed.d.ts +0 -21
  168. package/dist/types/file.d.ts +0 -12
  169. package/dist/types/heatmap.d.ts +0 -5
  170. package/dist/types/hot-segments.d.ts +0 -9
  171. package/dist/types/image.d.ts +0 -9
  172. package/dist/types/uuid.d.ts +0 -1
  173. package/dist/types/video.d.ts +0 -22
@@ -1,139 +0,0 @@
1
- import { getRestoredVolume, shouldBeMuted } from "./volume-utils.js";
2
- import { isDocumentFullscreen } from "./fullscreen-utils.js";
3
- import { isVideoPictureInPicture } from "./pip-utils.js";
4
-
5
- // src/lib/environment-effects.ts
6
- /**
7
- * Pure helpers for video-element environment effects.
8
- *
9
- * "Environment effects" = things the player has to keep in sync with the
10
- * surrounding document/window: fullscreen state, picture-in-picture state,
11
- * tab visibility, viewport size, and the volume/mute state derived from
12
- * player + user-interaction inputs.
13
- *
14
- * Everything in this module is framework-agnostic. React/Lit/Vue wrappers
15
- * supply the DOM listeners and the store-update callbacks; this module
16
- * supplies the predicates, the state derivations, and the listener factories.
17
- */
18
- /**
19
- * Bind the `document` `fullscreenchange` event to a callback that receives the
20
- * current fullscreen state.
21
- *
22
- * Returns a teardown function that removes the listener.
23
- */
24
- function bindFullscreenChangeListener(onChange) {
25
- const handler = () => onChange(isDocumentFullscreen());
26
- document.addEventListener("fullscreenchange", handler);
27
- return () => document.removeEventListener("fullscreenchange", handler);
28
- }
29
- /**
30
- * Bind `enterpictureinpicture` and `leavepictureinpicture` events on a
31
- * video element to a callback that receives the current PiP state.
32
- *
33
- * Returns a teardown function.
34
- */
35
- function bindPictureInPictureChangeListener(videoElement, onChange) {
36
- if (!videoElement) return () => void 0;
37
- const handler = () => onChange(isVideoPictureInPicture(videoElement));
38
- videoElement.addEventListener("enterpictureinpicture", handler);
39
- videoElement.addEventListener("leavepictureinpicture", handler);
40
- return () => {
41
- videoElement.removeEventListener("enterpictureinpicture", handler);
42
- videoElement.removeEventListener("leavepictureinpicture", handler);
43
- };
44
- }
45
- /**
46
- * Decide whether to re-request PiP when the document becomes hidden.
47
- *
48
- * Returns `true` when the caller should re-enter PiP on the supplied
49
- * video element. The actual `requestPictureInPicture()` call is left to the
50
- * caller (since it returns a promise and the caller chooses how to handle
51
- * errors).
52
- */
53
- function shouldReenterPictureInPicture({ videoElement, isCurrentlyInPip }) {
54
- if (typeof document === "undefined") return false;
55
- if (document.visibilityState !== "hidden") return false;
56
- if (!isCurrentlyInPip) return false;
57
- if (!videoElement) return false;
58
- if (isVideoPictureInPicture(videoElement)) return false;
59
- return true;
60
- }
61
- /**
62
- * Bind a `visibilitychange` handler that re-enters PiP whenever the document
63
- * becomes hidden and the player believes itself to be in PiP but the browser
64
- * has dropped out.
65
- *
66
- * Returns a teardown function.
67
- */
68
- function bindPipVisibilityReentry(getOptions, onError = (err) => console.error("Error maintaining PIP mode:", err)) {
69
- const handler = () => {
70
- const options = getOptions();
71
- if (!shouldReenterPictureInPicture(options)) return;
72
- options.videoElement.requestPictureInPicture().catch(onError);
73
- };
74
- document.addEventListener("visibilitychange", handler);
75
- return () => document.removeEventListener("visibilitychange", handler);
76
- }
77
- /**
78
- * Pure derivation of the values that should be written to the video element
79
- * (and back to the store) given the current player state.
80
- *
81
- * Mirrors the behaviour previously implemented inside `useVolumeEffect`:
82
- *
83
- * 1. When the caller forces mute, nothing else is touched.
84
- * 2. Otherwise, `shouldBeMuted` decides the effective mute state.
85
- * 3. When unmuted, `getRestoredVolume` resolves the volume to assign, and the
86
- * store is updated only when the current persisted volume is `0`.
87
- */
88
- function deriveVolumeSync({ forcedMuted, playerMuted, playerInteracted, playerVolume, playerPreviousVolume }) {
89
- if (forcedMuted) return null;
90
- if (shouldBeMuted({
91
- playerMuted,
92
- playerInteracted
93
- })) return {
94
- muted: true,
95
- volume: null,
96
- storeVolumeUpdate: null
97
- };
98
- const volumeToSet = getRestoredVolume(playerVolume, playerPreviousVolume);
99
- return {
100
- muted: false,
101
- volume: volumeToSet,
102
- storeVolumeUpdate: playerVolume === 0 ? volumeToSet : null
103
- };
104
- }
105
- /**
106
- * Observe a container element's viewport size, invoking the callback with the
107
- * current `{width, height}` whenever it changes (via `ResizeObserver`) or
108
- * the window resizes.
109
- *
110
- * Returns a teardown function.
111
- */
112
- function observeViewport(container, onChange) {
113
- if (!container) return () => void 0;
114
- const emit = () => {
115
- onChange({
116
- width: container.clientWidth,
117
- height: container.clientHeight
118
- });
119
- };
120
- emit();
121
- const resizeObserver = new ResizeObserver(emit);
122
- resizeObserver.observe(container);
123
- let rafHandle = null;
124
- const onWindowResize = () => {
125
- if (rafHandle !== null) return;
126
- rafHandle = requestAnimationFrame(() => {
127
- rafHandle = null;
128
- emit();
129
- });
130
- };
131
- window.addEventListener("resize", onWindowResize);
132
- return () => {
133
- resizeObserver.disconnect();
134
- window.removeEventListener("resize", onWindowResize);
135
- if (rafHandle !== null) cancelAnimationFrame(rafHandle);
136
- };
137
- }
138
-
139
- export { bindFullscreenChangeListener, bindPictureInPictureChangeListener, bindPipVisibilityReentry, deriveVolumeSync, observeViewport, shouldReenterPictureInPicture };
@@ -1,10 +0,0 @@
1
- export declare function gateVideoEnded(isCurrent: boolean, autoscroll: boolean): boolean;
2
- export interface AutoAdvanceInput {
3
- isCurrent: boolean;
4
- autoscroll: boolean;
5
- loop: boolean;
6
- currentIndex: number;
7
- postsLength: number;
8
- total: number;
9
- }
10
- export declare function shouldAutoAdvance({ isCurrent, autoscroll, loop, currentIndex, postsLength, total }: AutoAdvanceInput): boolean;
@@ -1,13 +0,0 @@
1
- import { isAtEndOfFeed, isFeedExhausted } from "./feed-scroll.js";
2
-
3
- // src/lib/feed-autoscroll.ts
4
- function gateVideoEnded(isCurrent, autoscroll) {
5
- return isCurrent && autoscroll;
6
- }
7
- function shouldAutoAdvance({ isCurrent, autoscroll, loop, currentIndex, postsLength, total }) {
8
- if (!isCurrent || !autoscroll) return false;
9
- if (isAtEndOfFeed(currentIndex, postsLength) && !loop && isFeedExhausted(postsLength, total)) return false;
10
- return true;
11
- }
12
-
13
- export { gateVideoEnded, shouldAutoAdvance };
@@ -1,7 +0,0 @@
1
- export declare function getViewportFallbackHeight(safeAreaTabBar: number): number;
2
- export interface ContainerHeightInput {
3
- measuredHeight: number;
4
- scrollContainerHeight: number;
5
- safeAreaTabBar: number;
6
- }
7
- export declare function resolveContainerHeight({ measuredHeight, scrollContainerHeight, safeAreaTabBar }: ContainerHeightInput): number;
@@ -1,11 +0,0 @@
1
- // src/lib/feed-container-height.ts
2
- function getViewportFallbackHeight(safeAreaTabBar) {
3
- return Math.max(window.innerHeight - safeAreaTabBar, 1);
4
- }
5
- function resolveContainerHeight({ measuredHeight, scrollContainerHeight, safeAreaTabBar }) {
6
- if (scrollContainerHeight > 0) return scrollContainerHeight;
7
- if (measuredHeight > 0) return measuredHeight;
8
- return getViewportFallbackHeight(safeAreaTabBar);
9
- }
10
-
11
- export { getViewportFallbackHeight, resolveContainerHeight };
@@ -1 +0,0 @@
1
- export declare function checkIsCut(el: HTMLElement | null): boolean;
@@ -1,9 +0,0 @@
1
- // src/lib/feed-description-overflow.ts
2
- function checkIsCut(el) {
3
- if (!el) return false;
4
- const isOverflowingHeight = el.scrollHeight - el.clientHeight > 1;
5
- const isOverflowingWidth = el.scrollWidth - el.clientWidth > 1;
6
- return isOverflowingHeight || isOverflowingWidth;
7
- }
8
-
9
- export { checkIsCut };
@@ -1,2 +0,0 @@
1
- export declare const IMAGE_DWELL_MS = 5e3;
2
- export declare function shouldDwellAdvance(autoscroll: boolean, isCurrent: boolean, isImage: boolean): boolean;
@@ -1,8 +0,0 @@
1
- // src/lib/feed-dwell.ts
2
- const IMAGE_DWELL_MS = 5e3;
3
- function shouldDwellAdvance(autoscroll, isCurrent, isImage) {
4
- if (!autoscroll || !isCurrent) return false;
5
- return isImage;
6
- }
7
-
8
- export { IMAGE_DWELL_MS, shouldDwellAdvance };
@@ -1,10 +0,0 @@
1
- export declare function resolvePostIndex(transform: number, postsLength: number, loop: boolean): number;
2
- export declare function getLoopRebaseThreshold(postsLength: number): number;
3
- export interface LoopRebaseInput {
4
- currentIndex: number;
5
- postsLength: number;
6
- loop: boolean;
7
- total: number;
8
- }
9
- export declare function shouldRebaseLoopIndex({ currentIndex, postsLength, loop, total }: LoopRebaseInput): boolean;
10
- export declare function getRebasedLoopIndex(currentIndex: number, postsLength: number): number;
@@ -1,21 +0,0 @@
1
- import { FEED_COMPONENTS_COUNT } from "../store/feed-interaction-store.js";
2
- import { isFeedExhausted } from "./feed-scroll.js";
3
- import { LOOP_REBASE_BASE_MULTIPLIER, LOOP_REBASE_THRESHOLD_MULTIPLIER } from "./feed-transforms.js";
4
-
5
- // src/lib/feed-loop.ts
6
- function resolvePostIndex(transform, postsLength, loop) {
7
- if (!loop || postsLength === 0) return transform;
8
- return (transform % postsLength + postsLength) % postsLength;
9
- }
10
- function getLoopRebaseThreshold(postsLength) {
11
- return Math.max(5 * 50, postsLength * 50);
12
- }
13
- function shouldRebaseLoopIndex({ currentIndex, postsLength, loop, total }) {
14
- return loop && postsLength > 0 && isFeedExhausted(postsLength, total) && currentIndex >= getLoopRebaseThreshold(postsLength);
15
- }
16
- function getRebasedLoopIndex(currentIndex, postsLength) {
17
- const normalizedOffset = (currentIndex % postsLength + postsLength) % postsLength;
18
- return postsLength * 2 + normalizedOffset;
19
- }
20
-
21
- export { getLoopRebaseThreshold, getRebasedLoopIndex, resolvePostIndex, shouldRebaseLoopIndex };
@@ -1,11 +0,0 @@
1
- export declare function computeScrollIndex(scrollTop: number, containerHeight: number, gap?: number): number;
2
- export declare function isAtEndOfFeed(currentIndex: number, postsLength: number): boolean;
3
- export declare function isFeedExhausted(postsLength: number, total: number): boolean;
4
- export interface ShouldFetchMoreInput {
5
- currentIndex: number;
6
- postsLength: number;
7
- total: number;
8
- isFetching: boolean;
9
- isLoaded: boolean;
10
- }
11
- export declare function shouldFetchMore({ currentIndex, postsLength, total, isFetching, isLoaded }: ShouldFetchMoreInput): boolean;
@@ -1,19 +0,0 @@
1
- // src/lib/feed-scroll.ts
2
- const DEFAULT_GAP = .3;
3
- function computeScrollIndex(scrollTop, containerHeight, gap = DEFAULT_GAP) {
4
- if (containerHeight <= 0 || scrollTop <= 0) return 0;
5
- return Math.ceil(scrollTop / containerHeight - gap);
6
- }
7
- function isAtEndOfFeed(currentIndex, postsLength) {
8
- return currentIndex >= postsLength - 1;
9
- }
10
- function isFeedExhausted(postsLength, total) {
11
- return total > 0 && postsLength >= total;
12
- }
13
- function shouldFetchMore({ currentIndex, postsLength, total, isFetching, isLoaded }) {
14
- if (isFetching || !isLoaded) return false;
15
- if (isFeedExhausted(postsLength, total)) return false;
16
- return currentIndex >= postsLength - 3;
17
- }
18
-
19
- export { computeScrollIndex, isAtEndOfFeed, isFeedExhausted, shouldFetchMore };
@@ -1,5 +0,0 @@
1
- export declare const LOOP_REBASE_BASE_MULTIPLIER = 2;
2
- export declare const LOOP_REBASE_THRESHOLD_MULTIPLIER = 50;
3
- export declare function shiftArray(arr: number[], shiftBy: number): number[];
4
- export declare function calcShiftCountArray(index: number, arraySize: number): number[];
5
- export declare function buildTransforms(currentIndex: number, originalPositions: number[]): number[];
@@ -1,30 +0,0 @@
1
- import { FEED_COMPONENTS_COUNT, FEED_TRANSITION_THRESHOLD } from "../store/feed-interaction-store.js";
2
-
3
- // src/lib/feed-transforms.ts
4
- const LOOP_REBASE_BASE_MULTIPLIER = 2;
5
- const LOOP_REBASE_THRESHOLD_MULTIPLIER = 50;
6
- function shiftArray(arr, shiftBy) {
7
- const length = arr.length;
8
- shiftBy = shiftBy % length;
9
- if (shiftBy < 0) shiftBy = length + shiftBy;
10
- return [...arr.slice(shiftBy), ...arr.slice(0, shiftBy)];
11
- }
12
- function calcShiftCountArray(index, arraySize) {
13
- const count = Math.floor(index / arraySize);
14
- const remainder = index % arraySize;
15
- const output = [];
16
- for (let i = 0; i < arraySize; i++) if (i < arraySize - remainder) output.push(count);
17
- else output.push(count + 1);
18
- return output;
19
- }
20
- function buildTransforms(currentIndex, originalPositions) {
21
- if (currentIndex <= 2) return originalPositions;
22
- const shiftCount = calcShiftCountArray(currentIndex - 2, 5);
23
- const shiftedArray = shiftArray(originalPositions, currentIndex - 2);
24
- shiftCount.forEach((shift, index) => {
25
- shiftedArray[index] += shift * 5;
26
- });
27
- return shiftedArray;
28
- }
29
-
30
- export { LOOP_REBASE_BASE_MULTIPLIER, LOOP_REBASE_THRESHOLD_MULTIPLIER, buildTransforms, calcShiftCountArray, shiftArray };
@@ -1,11 +0,0 @@
1
- // src/lib/filter-undefined.ts
2
- /**
3
- * Returns a copy of `obj` with all `undefined` values removed.
4
- * A type assertion is required because `Object.fromEntries` cannot infer
5
- * `Partial<T>` from filtered entries.
6
- */
7
- function filterUndefined(obj) {
8
- return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== void 0));
9
- }
10
-
11
- export { filterUndefined };
@@ -1 +0,0 @@
1
- export declare function isDocumentFullscreen(): boolean;
@@ -1,7 +0,0 @@
1
- // src/lib/fullscreen-utils.ts
2
- function isDocumentFullscreen() {
3
- const doc = document;
4
- return !!(doc.fullscreenElement || doc.webkitFullscreenElement || doc.mozFullScreenElement || doc.msFullscreenElement);
5
- }
6
-
7
- export { isDocumentFullscreen };
@@ -1,46 +0,0 @@
1
- // src/lib/hls-buffer-flush.ts
2
- /**
3
- * Buffer-flush-on-far-seek heuristic for HLS playback.
4
- *
5
- * When the user seeks more than `NEARBY_BUFFER_SECONDS` outside the currently
6
- * buffered ranges, HLS.js often stalls because it tries to reuse stale buffer
7
- * data. We flush any buffer ranges further than `DISTANT_BUFFER_SECONDS` from
8
- * the new playhead so HLS can fetch fresh segments.
9
- */
10
- const NEARBY_BUFFER_SECONDS = 5;
11
- const DISTANT_BUFFER_SECONDS = 30;
12
- function flushDistantBuffers({ hlsInstance, events, videoElement }, currentTime) {
13
- for (let i = 0; i < videoElement.buffered.length; i++) {
14
- const start = videoElement.buffered.start(i);
15
- const end = videoElement.buffered.end(i);
16
- if (start < currentTime - 30 || end > currentTime + 30) hlsInstance.trigger(events.BUFFER_FLUSHING, {
17
- startOffset: start,
18
- endOffset: end,
19
- type: "video"
20
- });
21
- }
22
- }
23
- function hasNearbyBuffer(videoElement, currentTime) {
24
- for (let i = 0; i < videoElement.buffered.length; i++) {
25
- const start = videoElement.buffered.start(i);
26
- const end = videoElement.buffered.end(i);
27
- if (currentTime >= start - 5 && currentTime <= end + 5) return true;
28
- }
29
- return false;
30
- }
31
- /**
32
- * Build a `seeking` event handler that flushes distant buffers and resets
33
- * `maxBufferLength` when the seek lands far from any buffered range.
34
- */
35
- function createSeekingHandler(opts) {
36
- const { hlsInstance, videoElement } = opts;
37
- return () => {
38
- const currentTime = videoElement.currentTime;
39
- if (!hasNearbyBuffer(videoElement, currentTime)) {
40
- hlsInstance.config.maxBufferLength = 30;
41
- flushDistantBuffers(opts, currentTime);
42
- }
43
- };
44
- }
45
-
46
- export { createSeekingHandler, hasNearbyBuffer };
@@ -1,35 +0,0 @@
1
- export declare const MAX_NETWORK_RETRIES = 3;
2
- export declare const MAX_MEDIA_RECOVERIES = 1;
3
- export interface HlsErrorPayload {
4
- /** The hls.js error type, e.g. `"networkError"`, `"mediaError"`. */
5
- error: string;
6
- /** Specific hls.js error details (e.g. `"manifestLoadError"`) or undefined. */
7
- code?: string;
8
- }
9
- export interface HlsErrorRecoveryState {
10
- networkRetries: number;
11
- mediaRecoveries: number;
12
- }
13
- export declare function createHlsErrorRecoveryState(): HlsErrorRecoveryState;
14
- export type HlsErrorAction = {
15
- kind: "retry-network";
16
- attempt: number;
17
- } | {
18
- kind: "recover-media";
19
- attempt: number;
20
- } | {
21
- kind: "give-up";
22
- payload: HlsErrorPayload;
23
- };
24
- export interface HlsErrorInput {
25
- type: string;
26
- details?: string;
27
- fatal: boolean;
28
- }
29
- /**
30
- * Decide what to do given an HLS error and the current recovery counters.
31
- *
32
- * Mutates `state` to advance the appropriate counter when retry/recover is
33
- * selected. Non-fatal errors return `null` (the caller should ignore them).
34
- */
35
- export declare function decideHlsErrorAction(error: HlsErrorInput, state: HlsErrorRecoveryState): HlsErrorAction | null;
@@ -1,75 +0,0 @@
1
- // src/lib/hls-error-recovery.ts
2
- /**
3
- * Pure HLS error-recovery state machine.
4
- *
5
- * Policy (per `hls-streaming.md` migration spec):
6
- * - Network errors (fatal): retry manifest load via `hls.startLoad()` up to
7
- * `MAX_NETWORK_RETRIES` attempts. After that, give up.
8
- * - Media errors (fatal): attempt `hls.recoverMediaError()` up to
9
- * `MAX_MEDIA_RECOVERIES` times. After that, give up.
10
- * - Anything else (fatal): give up immediately.
11
- * - Non-fatal errors: ignore (return null).
12
- *
13
- * The state machine itself is framework-agnostic and side-effect-free.
14
- * Callers wire it to actual HLS instances in `hls-setup.ts`.
15
- */
16
- const NETWORK_ERROR = "networkError";
17
- const MEDIA_ERROR = "mediaError";
18
- const MAX_NETWORK_RETRIES = 3;
19
- const MAX_MEDIA_RECOVERIES = 1;
20
- function createHlsErrorRecoveryState() {
21
- return {
22
- networkRetries: 0,
23
- mediaRecoveries: 0
24
- };
25
- }
26
- /**
27
- * Decide what to do given an HLS error and the current recovery counters.
28
- *
29
- * Mutates `state` to advance the appropriate counter when retry/recover is
30
- * selected. Non-fatal errors return `null` (the caller should ignore them).
31
- */
32
- function decideHlsErrorAction(error, state) {
33
- if (!error.fatal) return null;
34
- if (error.type === NETWORK_ERROR) {
35
- if (state.networkRetries < 3) {
36
- state.networkRetries += 1;
37
- return {
38
- kind: "retry-network",
39
- attempt: state.networkRetries
40
- };
41
- }
42
- return {
43
- kind: "give-up",
44
- payload: {
45
- error: error.type,
46
- code: error.details
47
- }
48
- };
49
- }
50
- if (error.type === MEDIA_ERROR) {
51
- if (state.mediaRecoveries < 1) {
52
- state.mediaRecoveries += 1;
53
- return {
54
- kind: "recover-media",
55
- attempt: state.mediaRecoveries
56
- };
57
- }
58
- return {
59
- kind: "give-up",
60
- payload: {
61
- error: error.type,
62
- code: error.details
63
- }
64
- };
65
- }
66
- return {
67
- kind: "give-up",
68
- payload: {
69
- error: error.type,
70
- code: error.details
71
- }
72
- };
73
- }
74
-
75
- export { MAX_MEDIA_RECOVERIES, MAX_NETWORK_RETRIES, createHlsErrorRecoveryState, decideHlsErrorAction };
@@ -1,61 +0,0 @@
1
- import { generateLevelName } from "./hls-utils.js";
2
- import { persistAudioTrack } from "./track-persistence.js";
3
- import { playerStateStore } from "../store/player-store.js";
4
-
5
- // src/lib/hls-event-handlers.ts
6
- function setupAudioTracksHandler(playerId) {
7
- return (_event, data) => {
8
- if (!data.audioTracks || data.audioTracks.length <= 1) return;
9
- const audioTracks = data.audioTracks.map((track, index) => ({
10
- index,
11
- label: track.name || track.lang || `Audio Track ${index + 1}`,
12
- language: track.lang || "unknown",
13
- kind: "main",
14
- track
15
- }));
16
- playerStateStore.setAvailableAudioTracks(playerId, audioTracks);
17
- };
18
- }
19
- function handleAudioTrackSwitch(playerId, _event, data) {
20
- const player = playerStateStore.get(playerId).get();
21
- if (player.audioTrack !== data.id) {
22
- playerStateStore.get(playerId).set({
23
- ...player,
24
- audioTrack: data.id
25
- });
26
- persistAudioTrack(data.id);
27
- }
28
- }
29
- function updateQualityLevels(hlsInstance, onLevelsChange) {
30
- onLevelsChange(hlsInstance.levels.map((level, index) => ({
31
- height: level.height,
32
- width: level.width,
33
- bitrate: level.bitrate,
34
- name: generateLevelName(level),
35
- index
36
- })));
37
- }
38
- /**
39
- * Register all HLS event handlers needed by the SDK on the given instance.
40
- *
41
- * Wires media-attached → loadSource, audio-tracks-updated → store, audio-track
42
- * switched → store + persistence, levels-updated/manifest-parsed → onLevelsChange,
43
- * level-switched → onLevelSwitch.
44
- */
45
- function setupHlsEventHandlers(opts) {
46
- const { hlsInstance, events, playerId, videoUrl, onLevelsChange, onLevelSwitch } = opts;
47
- const onAudioTracks = setupAudioTracksHandler(playerId);
48
- const onAudioSwitch = (_event, data) => handleAudioTrackSwitch(playerId, _event, data);
49
- const onLevels = () => updateQualityLevels(hlsInstance, onLevelsChange);
50
- const onSwitch = (_event, data) => onLevelSwitch(data.level);
51
- hlsInstance.on(events.MEDIA_ATTACHED, () => {
52
- hlsInstance.loadSource(videoUrl);
53
- });
54
- hlsInstance.on(events.AUDIO_TRACKS_UPDATED, onAudioTracks);
55
- hlsInstance.on(events.AUDIO_TRACK_SWITCHED, onAudioSwitch);
56
- hlsInstance.on(events.LEVELS_UPDATED, onLevels);
57
- hlsInstance.on(events.MANIFEST_PARSED, onLevels);
58
- hlsInstance.on(events.LEVEL_SWITCHED, onSwitch);
59
- }
60
-
61
- export { setupHlsEventHandlers };
@@ -1,23 +0,0 @@
1
- // src/lib/hls-loader.ts
2
- /**
3
- * Loads hls.js on first use.
4
- *
5
- * hls.js is ~560 KB minified, larger than the rest of the SDK combined. A
6
- * static import puts it on every page that imports `@rixl/media`, including
7
- * ones that only show images or never start a video. Loading it here, when a
8
- * player first attaches an HLS stream, lets the app's bundler split it into a
9
- * chunk that only video playback downloads.
10
- *
11
- * The promise is shared, so concurrent players trigger one download. A failed
12
- * load is not cached: the next player retries.
13
- */
14
- let hlsModule = null;
15
- function loadHls() {
16
- hlsModule ??= import("hls.js").then((module) => module.default, (error) => {
17
- hlsModule = null;
18
- throw error;
19
- });
20
- return hlsModule;
21
- }
22
-
23
- export { loadHls };
@@ -1,72 +0,0 @@
1
- import { QualityLevel } from "./hls-utils.js";
2
- import { UUID } from "../types/uuid.js";
3
- import { HlsErrorAction, HlsErrorInput, HlsErrorPayload, HlsErrorRecoveryState, MAX_MEDIA_RECOVERIES, MAX_NETWORK_RETRIES, createHlsErrorRecoveryState, decideHlsErrorAction } from "./hls-error-recovery.js";
4
- import Hls from "hls.js";
5
- /**
6
- * Framework-agnostic HLS setup.
7
- *
8
- * Owns:
9
- * - HLS.js configuration tuned for the SDK's playback profile.
10
- * - Media-attached / manifest-parsed / level-switched / audio-track-switched
11
- * event handlers.
12
- * - Buffer-flush-on-far-seek heuristic (see `createSeekingHandler`).
13
- * - Error recovery policy (see Error recovery section below).
14
- * - Safari native HLS fallback.
15
- *
16
- * hls.js itself is loaded on first use (see `loadHls`), so `setupHls` is
17
- * async and pages that never attach an HLS stream never download it.
18
- *
19
- * Resolves to a `HlsController` whose lifetime the caller manages via `destroy()`.
20
- * Caller responsibilities:
21
- * - Provide the `<video>` element and a `playerId`.
22
- * - Subscribe to quality/level/audio changes via the controller's getters or
23
- * by reading `playerStateStore`.
24
- * - Surface errors to UI via the `onError` callback.
25
- */
26
- export type HlsInstance = Hls;
27
- export interface HlsController {
28
- hls: Hls | null;
29
- levels: QualityLevel[];
30
- currentLevel: number;
31
- isAuto: boolean;
32
- destroy: () => void;
33
- setQuality: (index: number) => void;
34
- }
35
- export interface SetupHlsOptions {
36
- videoElement: HTMLVideoElement;
37
- videoUrl: string;
38
- playerId: UUID;
39
- isPreloading?: boolean;
40
- /**
41
- * Invoked when an HLS error is given up on (after retries are exhausted, or
42
- * for non-recoverable error types). Callers typically dispatch a custom event
43
- * and/or show an error overlay.
44
- */
45
- onError?: (payload: HlsErrorPayload) => void;
46
- /**
47
- * Invoked whenever HLS reports updated quality levels (after manifest parse
48
- * or level update). Useful for framework wrappers that mirror controller
49
- * state into reactive store/state (e.g. React `useState`).
50
- */
51
- onLevelsChange?: (levels: QualityLevel[]) => void;
52
- /**
53
- * Invoked whenever HLS switches to a different quality level.
54
- */
55
- onLevelSwitch?: (level: number) => void;
56
- }
57
- export declare function createHlsConfig(isPreloading?: boolean): Partial<Hls["config"]>;
58
- /**
59
- * Set up HLS streaming on a video element, resolving to a controller for
60
- * quality/audio switching and teardown.
61
- *
62
- * - For non-HLS URLs: sets `videoElement.src` directly and resolves to `null`
63
- * without loading hls.js.
64
- * - For HLS URLs on Safari (no `Hls.isSupported()` but native canPlayType):
65
- * sets `videoElement.src` and returns `null` (UI features that depend on
66
- * the HLS controller — quality switching, audio tracks — won't be available
67
- * in native mode).
68
- * - Otherwise: instantiates HLS.js, attaches the media, wires events
69
- * (including error recovery), and returns the controller.
70
- */
71
- export declare function setupHls(opts: SetupHlsOptions): Promise<HlsController | null>;
72
- export { type HlsErrorAction, type HlsErrorInput, type HlsErrorPayload, type HlsErrorRecoveryState, MAX_MEDIA_RECOVERIES, MAX_NETWORK_RETRIES, createHlsErrorRecoveryState, decideHlsErrorAction };