@rixl/media-core 1.0.1 → 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 -123
  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,165 +0,0 @@
1
- import { atom } from "nanostores";
2
-
3
- // src/store/feed-cache-store.ts
4
- const CACHE_KEY = "rixl_feed_cache";
5
- const CACHE_VERSION = 6;
6
- const MAX_CACHED_POSTS = 50;
7
- const feedCacheAtom = atom({
8
- cachedPosts: [],
9
- isLoadingFromCache: false
10
- });
11
- /**
12
- * Check that a value is a non-null object (narrows from unknown)
13
- */
14
- function isObject(value) {
15
- return typeof value === "object" && value !== null;
16
- }
17
- /**
18
- * Check that all common string fields are present on a post-like object
19
- */
20
- function hasRequiredStringFields(post) {
21
- return typeof post.id === "string" && typeof post.creatorId === "string" && typeof post.feedId === "string" && typeof post.description === "string" && typeof post.createdAt === "string";
22
- }
23
- /**
24
- * Validate a single post to ensure it has all required fields
25
- */
26
- function isValidPost(post) {
27
- if (!isObject(post)) return false;
28
- if (!hasRequiredStringFields(post)) return false;
29
- const isVideo = post.type === "video";
30
- const isImage = post.type === "image";
31
- if (!isVideo && !isImage) return false;
32
- return isVideo ? post.video !== void 0 : post.image !== void 0;
33
- }
34
- /**
35
- * Load cached posts from localStorage.
36
- * If feedId is provided and doesn't match the cached feedId, cache is cleared.
37
- */
38
- function loadFromLocalStorage(feedId) {
39
- try {
40
- const cached = localStorage.getItem(CACHE_KEY);
41
- if (!cached) return [];
42
- const data = JSON.parse(cached);
43
- if (data.version !== CACHE_VERSION) {
44
- localStorage.removeItem(CACHE_KEY);
45
- return [];
46
- }
47
- if (feedId && data.feedId && data.feedId !== feedId) {
48
- localStorage.removeItem(CACHE_KEY);
49
- return [];
50
- }
51
- const validPosts = data.posts.filter((post) => {
52
- if (!isValidPost(post)) {
53
- console.warn("[FeedCache] Invalid post found in cache:", post);
54
- return false;
55
- }
56
- return true;
57
- });
58
- if (validPosts.length !== data.posts.length) saveToLocalStorage(validPosts, feedId);
59
- return validPosts;
60
- } catch {
61
- localStorage.removeItem(CACHE_KEY);
62
- return [];
63
- }
64
- }
65
- /**
66
- * Save posts to localStorage
67
- */
68
- function saveToLocalStorage(posts, feedId) {
69
- try {
70
- const postsToCache = posts.slice(0, MAX_CACHED_POSTS);
71
- const data = {
72
- version: CACHE_VERSION,
73
- feedId,
74
- posts: postsToCache,
75
- timestamp: Date.now()
76
- };
77
- localStorage.setItem(CACHE_KEY, JSON.stringify(data));
78
- } catch (error) {
79
- if (error instanceof DOMException && error.name === "QuotaExceededError") localStorage.removeItem(CACHE_KEY);
80
- }
81
- }
82
- const feedCacheStore = {
83
- /** The current feed ID associated with the cache */
84
- currentFeedId: void 0,
85
- /**
86
- * Load cached posts from localStorage on app init.
87
- * Clears cache if feedId changed since last session.
88
- */
89
- loadCache(feedId) {
90
- this.currentFeedId = feedId;
91
- feedCacheAtom.set({
92
- ...feedCacheAtom.get(),
93
- isLoadingFromCache: true
94
- });
95
- const cachedPosts = loadFromLocalStorage(feedId);
96
- feedCacheAtom.set({
97
- cachedPosts,
98
- isLoadingFromCache: false
99
- });
100
- return cachedPosts;
101
- },
102
- /**
103
- * Save new posts to cache (append to existing)
104
- */
105
- saveNewPosts(newPosts, feedId) {
106
- if (feedId !== void 0 && feedId !== this.currentFeedId) return;
107
- const currentState = feedCacheAtom.get();
108
- const existingIds = new Set(currentState.cachedPosts.map((p) => p.id));
109
- const uniqueNewPosts = newPosts.filter((post) => !existingIds.has(post.id));
110
- if (uniqueNewPosts.length === 0) return;
111
- const updatedPosts = [...currentState.cachedPosts, ...uniqueNewPosts];
112
- feedCacheAtom.set({
113
- ...currentState,
114
- cachedPosts: updatedPosts
115
- });
116
- saveToLocalStorage(updatedPosts, this.currentFeedId);
117
- },
118
- /**
119
- * Remove a viewed post from cache
120
- */
121
- removePost(postId) {
122
- const currentState = feedCacheAtom.get();
123
- const filteredPosts = currentState.cachedPosts.filter((post) => post.id !== postId);
124
- if (filteredPosts.length === currentState.cachedPosts.length) return;
125
- feedCacheAtom.set({
126
- ...currentState,
127
- cachedPosts: filteredPosts
128
- });
129
- saveToLocalStorage(filteredPosts, this.currentFeedId);
130
- },
131
- /**
132
- * Clear all cached posts
133
- */
134
- clearCache() {
135
- localStorage.removeItem(CACHE_KEY);
136
- feedCacheAtom.set({
137
- cachedPosts: [],
138
- isLoadingFromCache: false
139
- });
140
- },
141
- /**
142
- * Get cache statistics
143
- */
144
- getCacheStats() {
145
- try {
146
- const cached = localStorage.getItem(CACHE_KEY);
147
- if (!cached) return {
148
- size: 0,
149
- timestamp: null
150
- };
151
- const data = JSON.parse(cached);
152
- return {
153
- size: data.posts.length,
154
- timestamp: data.timestamp
155
- };
156
- } catch {
157
- return {
158
- size: 0,
159
- timestamp: null
160
- };
161
- }
162
- }
163
- };
164
-
165
- export { feedCacheAtom, feedCacheStore };
@@ -1,17 +0,0 @@
1
- import { WritableAtom } from "nanostores";
2
- export declare const FEED_COMPONENTS_COUNT = 5;
3
- export declare const FEED_TRANSITION_THRESHOLD: number;
4
- interface FeedInteractionStore {
5
- hasInteracted: WritableAtom<boolean>;
6
- whenInteracted: WritableAtom<Date>;
7
- hasInitialized: WritableAtom<boolean>;
8
- videosInitialized: WritableAtom<number>;
9
- components: WritableAtom<number[]>;
10
- originalPositions: WritableAtom<number[]>;
11
- transforms: WritableAtom<number[]>;
12
- setTransforms: (transforms: number[]) => void;
13
- }
14
- export declare const feedStore: FeedInteractionStore;
15
- export declare const setHasInteracted: () => boolean;
16
- export declare const initializeVideo: () => void;
17
- export {};
@@ -1,32 +0,0 @@
1
- import { atom } from "nanostores";
2
-
3
- // src/store/feed-interaction-store.ts
4
- const FEED_COMPONENTS_COUNT = 5;
5
- const FEED_TRANSITION_THRESHOLD = Math.floor(5 / 2);
6
- const feedStore = {
7
- hasInteracted: atom(false),
8
- whenInteracted: atom(/* @__PURE__ */ new Date(0)),
9
- hasInitialized: atom(false),
10
- videosInitialized: atom(5),
11
- components: atom(Array.from({ length: 5 })),
12
- originalPositions: atom(Array.from({ length: 5 }).map((_, i) => i)),
13
- transforms: atom(Array.from({ length: 5 }).map((_, i) => i)),
14
- setTransforms: (transforms) => {
15
- const current = feedStore.transforms.get();
16
- if (current.length === transforms.length && current.every((v, i) => v === transforms[i])) return;
17
- feedStore.transforms.set(transforms);
18
- }
19
- };
20
- const setHasInteracted = () => {
21
- if (feedStore.hasInitialized.get()) {
22
- feedStore.hasInteracted.set(true);
23
- feedStore.whenInteracted.set(/* @__PURE__ */ new Date());
24
- }
25
- return feedStore.hasInteracted.get();
26
- };
27
- const initializeVideo = () => {
28
- feedStore.videosInitialized.set(feedStore.videosInitialized.get() + 1);
29
- if (feedStore.videosInitialized.get() >= 5) feedStore.hasInitialized.set(true);
30
- };
31
-
32
- export { FEED_COMPONENTS_COUNT, FEED_TRANSITION_THRESHOLD, feedStore, initializeVideo, setHasInteracted };
@@ -1,16 +0,0 @@
1
- import { FeedPost, FeedPostsResponse } from "../types/feed.js";
2
- /**
3
- * Get a feed post by ID, using cache if available
4
- * @param feedId ID of the feed
5
- * @param postId ID of the post to fetch
6
- * @returns Promise with the feed post
7
- */
8
- export declare const getFeedPost: (feedId: string, postId: string) => Promise<FeedPost>;
9
- /**
10
- * Get posts from a specific feed, with pagination
11
- * @param feedId ID of the feed
12
- * @param offset Pagination offset
13
- * @param limit Maximum number of items to return
14
- * @returns Promise with feed posts response
15
- */
16
- export declare const getFeedPosts: (feedId: string, offset?: number, limit?: number) => Promise<FeedPostsResponse>;
@@ -1,38 +0,0 @@
1
- import { fetchFeedPost, fetchFeedPosts } from "../api/feed.js";
2
- import { createStore } from "./generic-store.js";
3
-
4
- // src/store/feed-store.ts
5
- const store = createStore();
6
- const feedListCache = /* @__PURE__ */ new Map();
7
- /**
8
- * Get a feed post by ID, using cache if available
9
- * @param feedId ID of the feed
10
- * @param postId ID of the post to fetch
11
- * @returns Promise with the feed post
12
- */
13
- const getFeedPost = async (feedId, postId) => {
14
- const cacheKey = `${feedId}:${postId}`;
15
- return store.get(cacheKey, () => fetchFeedPost(feedId, postId));
16
- };
17
- /**
18
- * Get posts from a specific feed, with pagination
19
- * @param feedId ID of the feed
20
- * @param offset Pagination offset
21
- * @param limit Maximum number of items to return
22
- * @returns Promise with feed posts response
23
- */
24
- const getFeedPosts = async (feedId, offset = 0, limit = 10) => {
25
- if (offset > 0) return fetchFeedPosts(feedId, offset, limit);
26
- const cacheKey = `feed:${feedId}`;
27
- const cachedPosts = feedListCache.get(cacheKey);
28
- if (cachedPosts) return cachedPosts;
29
- const posts = await fetchFeedPosts(feedId, offset, limit);
30
- feedListCache.set(cacheKey, posts);
31
- posts.posts.forEach((post) => {
32
- const postKey = `${feedId}:${post.id}`;
33
- store.get(postKey, () => Promise.resolve(post));
34
- });
35
- return posts;
36
- };
37
-
38
- export { getFeedPost, getFeedPosts };
@@ -1,24 +0,0 @@
1
- // src/store/generic-store.ts
2
- const createStore = () => {
3
- const store = /* @__PURE__ */ new Map();
4
- const pendingRequests = /* @__PURE__ */ new Map();
5
- const get = async (id, fetcher) => {
6
- const item = store.get(id);
7
- if (item) return item;
8
- const pendingRequest = pendingRequests.get(id);
9
- if (pendingRequest) return pendingRequest;
10
- const request = fetcher(id).then((apiItem) => {
11
- store.set(id, apiItem);
12
- pendingRequests.delete(id);
13
- return apiItem;
14
- }).catch((error) => {
15
- pendingRequests.delete(id);
16
- throw error;
17
- });
18
- pendingRequests.set(id, request);
19
- return request;
20
- };
21
- return { get };
22
- };
23
-
24
- export { createStore };
@@ -1,2 +0,0 @@
1
- import { ImageData } from "../types/image.js";
2
- export declare const getImage: (imageId: string) => Promise<ImageData>;
@@ -1,8 +0,0 @@
1
- import { fetchImage } from "../api/image.js";
2
- import { createStore } from "./generic-store.js";
3
-
4
- // src/store/image-store.ts
5
- const imageStore = createStore();
6
- const getImage = (imageId) => imageStore.get(imageId, fetchImage);
7
-
8
- export { getImage };
@@ -1,10 +0,0 @@
1
- import { UUID } from "../types/uuid.js";
2
- interface PlayerContainerElementStore {
3
- register: (playerId: UUID, element: HTMLDivElement) => void;
4
- unregister: (playerId: UUID) => void;
5
- get: (playerId: UUID) => HTMLDivElement | null;
6
- subscribe: (playerId: UUID, listener: () => void) => () => void;
7
- clear: () => void;
8
- }
9
- export declare const playerContainerElementStore: PlayerContainerElementStore;
10
- export {};
@@ -1,38 +0,0 @@
1
- // src/store/player-container-element-store.ts
2
- const state = /* @__PURE__ */ new Map();
3
- const listeners = /* @__PURE__ */ new Map();
4
- function notify(playerId) {
5
- listeners.get(playerId)?.forEach((listener) => listener());
6
- }
7
- const playerContainerElementStore = {
8
- register: (playerId, element) => {
9
- state.set(playerId, element);
10
- notify(playerId);
11
- },
12
- unregister: (playerId) => {
13
- state.delete(playerId);
14
- notify(playerId);
15
- },
16
- get: (playerId) => state.get(playerId) ?? null,
17
- subscribe: (playerId, listener) => {
18
- let playerListeners = listeners.get(playerId);
19
- if (!playerListeners) {
20
- playerListeners = /* @__PURE__ */ new Set();
21
- listeners.set(playerId, playerListeners);
22
- }
23
- playerListeners.add(listener);
24
- return () => {
25
- const currentListeners = listeners.get(playerId);
26
- if (!currentListeners) return;
27
- currentListeners.delete(listener);
28
- if (currentListeners.size === 0) listeners.delete(playerId);
29
- };
30
- },
31
- clear: () => {
32
- const playerIds = /* @__PURE__ */ new Set([...state.keys(), ...listeners.keys()]);
33
- state.clear();
34
- playerIds.forEach((playerId) => notify(playerId));
35
- }
36
- };
37
-
38
- export { playerContainerElementStore };
@@ -1,53 +0,0 @@
1
- import { settingsStore } from "./settings/store.js";
2
-
3
- // src/store/player-global-actions.ts
4
- function findTrackIndexByLanguage(tracks, language) {
5
- return tracks.findIndex((track) => track.language === language);
6
- }
7
- const playerGlobalActions = {
8
- setGlobalMuted(muted) {
9
- this.state.forEach((_atom, playerId) => {
10
- if (!muted) this.setHasInteracted(playerId);
11
- this.setMuted(playerId, muted);
12
- });
13
- settingsStore.setMuted(muted);
14
- },
15
- setGlobalVolume(volume) {
16
- this.state.forEach((_atom, playerId) => this.setVolume(playerId, volume));
17
- settingsStore.setVolume(volume);
18
- },
19
- setGlobalPlaybackRate(rate) {
20
- this.state.forEach((_atom, playerId) => this.setPlaybackRate(playerId, rate));
21
- settingsStore.setPlaybackSpeed(rate);
22
- },
23
- setGlobalPreferredQuality(quality) {
24
- settingsStore.setPreferredQuality(quality);
25
- },
26
- setGlobalPreferredAudioLanguage(language) {
27
- settingsStore.setPreferredAudioLanguage(language);
28
- if (!language) return;
29
- this.state.forEach((_atom, playerId) => {
30
- const { availableAudioTracks } = this.get(playerId).get();
31
- const index = findTrackIndexByLanguage(availableAudioTracks, language);
32
- if (index >= 0) this.setAudioTrack(playerId, index);
33
- });
34
- },
35
- setGlobalPreferredSubtitleLanguage(language) {
36
- settingsStore.setPreferredSubtitleLanguage(language);
37
- if (language === void 0) return;
38
- if (language === "") {
39
- this.state.forEach((_atom, playerId) => this.setSubtitleTrack(playerId, -1));
40
- return;
41
- }
42
- this.state.forEach((_atom, playerId) => {
43
- const { availableSubtitleTracks } = this.get(playerId).get();
44
- const index = findTrackIndexByLanguage(availableSubtitleTracks, language);
45
- if (index >= 0) this.setSubtitleTrack(playerId, index);
46
- });
47
- },
48
- pauseAllPlayers() {
49
- this.state.forEach((_atom, playerId) => this.setPaused(playerId, true));
50
- }
51
- };
52
-
53
- export { playerGlobalActions };
@@ -1,10 +0,0 @@
1
- import { UUID } from "../types/uuid.js";
2
- interface PlayerMediaElementStore {
3
- register: (playerId: UUID, element: HTMLVideoElement) => void;
4
- unregister: (playerId: UUID) => void;
5
- get: (playerId: UUID) => HTMLVideoElement | null;
6
- subscribe: (playerId: UUID, listener: () => void) => () => void;
7
- clear: () => void;
8
- }
9
- export declare const playerMediaElementStore: PlayerMediaElementStore;
10
- export {};
@@ -1,38 +0,0 @@
1
- // src/store/player-media-element-store.ts
2
- const state = /* @__PURE__ */ new Map();
3
- const listeners = /* @__PURE__ */ new Map();
4
- function notify(playerId) {
5
- listeners.get(playerId)?.forEach((listener) => listener());
6
- }
7
- const playerMediaElementStore = {
8
- register: (playerId, element) => {
9
- state.set(playerId, element);
10
- notify(playerId);
11
- },
12
- unregister: (playerId) => {
13
- state.delete(playerId);
14
- notify(playerId);
15
- },
16
- get: (playerId) => state.get(playerId) ?? null,
17
- subscribe: (playerId, listener) => {
18
- let playerListeners = listeners.get(playerId);
19
- if (!playerListeners) {
20
- playerListeners = /* @__PURE__ */ new Set();
21
- listeners.set(playerId, playerListeners);
22
- }
23
- playerListeners.add(listener);
24
- return () => {
25
- const currentListeners = listeners.get(playerId);
26
- if (!currentListeners) return;
27
- currentListeners.delete(listener);
28
- if (currentListeners.size === 0) listeners.delete(playerId);
29
- };
30
- },
31
- clear: () => {
32
- const playerIds = /* @__PURE__ */ new Set([...state.keys(), ...listeners.keys()]);
33
- state.clear();
34
- playerIds.forEach((playerId) => notify(playerId));
35
- }
36
- };
37
-
38
- export { playerMediaElementStore };
@@ -1,197 +0,0 @@
1
- import { clamp } from "../lib/numbers.js";
2
- import { clampVolume, getRestoredVolume } from "../lib/volume-utils.js";
3
- import { playerMediaElementStore } from "./player-media-element-store.js";
4
-
5
- // src/store/player-store-actions.ts
6
- const PROGRESS_EVENTS = [
7
- "loadedmetadata",
8
- "durationchange",
9
- "timeupdate",
10
- "progress",
11
- "seeking",
12
- "seeked",
13
- "emptied"
14
- ];
15
- const progressSnapshotCache = /* @__PURE__ */ new Map();
16
- function toSafeDuration(video) {
17
- const duration = video?.duration ?? 0;
18
- return Number.isFinite(duration) && duration > 0 ? duration : 0;
19
- }
20
- function toSafeCurrentTime(video, duration) {
21
- if (!video || duration <= 0) return 0;
22
- const currentTime = video.currentTime;
23
- if (!Number.isFinite(currentTime)) return 0;
24
- return clamp(currentTime, 0, duration);
25
- }
26
- function toSafeBuffered(video, duration) {
27
- if (!video || duration <= 0 || video.buffered.length === 0) return 0;
28
- try {
29
- const bufferedEnd = video.buffered.end(video.buffered.length - 1);
30
- if (!Number.isFinite(bufferedEnd)) return 0;
31
- return clamp(bufferedEnd / duration, 0, 1);
32
- } catch {
33
- return 0;
34
- }
35
- }
36
- function subscribeToProgressVideo(video, onStoreChange) {
37
- if (!video) return () => {};
38
- let active = true;
39
- let queued = false;
40
- const handleStoreChange = () => {
41
- if (queued) return;
42
- queued = true;
43
- queueMicrotask(() => {
44
- queued = false;
45
- if (!active) return;
46
- onStoreChange();
47
- });
48
- };
49
- PROGRESS_EVENTS.forEach((eventName) => {
50
- video.addEventListener(eventName, handleStoreChange);
51
- });
52
- return () => {
53
- active = false;
54
- PROGRESS_EVENTS.forEach((eventName) => {
55
- video.removeEventListener(eventName, handleStoreChange);
56
- });
57
- };
58
- }
59
- /**
60
- * Apply the store's volume/muted to the registered media element — same
61
- * pattern as playMedia/pauseMedia, so frameworks never re-apply by hand.
62
- */
63
- function applyVolumeStateToElement(id, state) {
64
- const video = playerMediaElementStore.get(id);
65
- if (!video) return;
66
- video.volume = state.volume;
67
- video.muted = state.muted;
68
- }
69
- const playerStateActions = {
70
- getEffectiveMediaMuted: (player) => player.muted || player.volume === 0 || !player.hasInteracted,
71
- muteMedia(id) {
72
- const player = this.get(id).get();
73
- this.setHasInteracted(id);
74
- if (player.volume > 0) this.setPreviousVolume(id, player.volume);
75
- this.setVolume(id, 0);
76
- this.setMuted(id, true);
77
- applyVolumeStateToElement(id, this.get(id).get());
78
- },
79
- unmuteMedia(id) {
80
- const player = this.get(id).get();
81
- const restoredVolume = getRestoredVolume(player.volume, player.previousVolume);
82
- this.setHasInteracted(id);
83
- if (player.volume === 0) this.setVolume(id, restoredVolume);
84
- this.setMuted(id, false);
85
- applyVolumeStateToElement(id, this.get(id).get());
86
- },
87
- toggleMediaMute(id) {
88
- const player = this.get(id).get();
89
- if (this.getEffectiveMediaMuted(player)) {
90
- this.unmuteMedia(id);
91
- return;
92
- }
93
- this.muteMedia(id);
94
- },
95
- setMediaVolume(id, volume) {
96
- const player = this.get(id).get();
97
- const nextVolume = clampVolume(volume);
98
- this.setHasInteracted(id);
99
- if (nextVolume === 0 && player.volume > 0) this.setPreviousVolume(id, player.volume);
100
- this.setVolume(id, nextVolume);
101
- this.setMuted(id, nextVolume === 0);
102
- applyVolumeStateToElement(id, this.get(id).get());
103
- },
104
- async playMedia(id) {
105
- const video = playerMediaElementStore.get(id);
106
- if (!video) return;
107
- this.setHasInteracted(id);
108
- this.setEnded(id, false);
109
- this.setPaused(id, false);
110
- try {
111
- await video.play();
112
- } catch {
113
- this.setPaused(id, true);
114
- }
115
- },
116
- pauseMedia(id) {
117
- const video = playerMediaElementStore.get(id);
118
- if (!video) return;
119
- video.pause();
120
- this.setPaused(id, true);
121
- },
122
- async toggleMediaPlayback(id) {
123
- if (this.get(id).get().paused) {
124
- await this.playMedia(id);
125
- return;
126
- }
127
- this.pauseMedia(id);
128
- },
129
- pauseOtherMedia(except) {
130
- this.state.forEach((_player, playerId) => {
131
- if (playerId === except) return;
132
- const video = playerMediaElementStore.get(playerId);
133
- if (video) video.pause();
134
- this.setPaused(playerId, true);
135
- });
136
- },
137
- setMediaPlaybackRate(id, playbackRate) {
138
- const video = playerMediaElementStore.get(id);
139
- this.setPlaybackRate(id, playbackRate);
140
- if (video) video.playbackRate = this.get(id).get().playbackRate;
141
- },
142
- readProgressSnapshot(id) {
143
- const video = playerMediaElementStore.get(id);
144
- const duration = toSafeDuration(video);
145
- const currentTime = toSafeCurrentTime(video, duration);
146
- const nextSnapshot = {
147
- currentTime,
148
- duration,
149
- progress: duration > 0 ? clamp(currentTime / duration, 0, 1) : 0,
150
- buffered: toSafeBuffered(video, duration)
151
- };
152
- const cachedSnapshot = progressSnapshotCache.get(id);
153
- if (cachedSnapshot && cachedSnapshot.currentTime === nextSnapshot.currentTime && cachedSnapshot.duration === nextSnapshot.duration && cachedSnapshot.progress === nextSnapshot.progress && cachedSnapshot.buffered === nextSnapshot.buffered) return cachedSnapshot;
154
- progressSnapshotCache.set(id, nextSnapshot);
155
- return nextSnapshot;
156
- },
157
- subscribeToProgress(id, onStoreChange) {
158
- let currentVideo = playerMediaElementStore.get(id);
159
- let unsubscribeVideo = subscribeToProgressVideo(currentVideo, onStoreChange);
160
- const unsubscribeRegistry = playerMediaElementStore.subscribe(id, () => {
161
- const nextVideo = playerMediaElementStore.get(id);
162
- if (nextVideo === currentVideo) return;
163
- unsubscribeVideo();
164
- currentVideo = nextVideo;
165
- unsubscribeVideo = subscribeToProgressVideo(currentVideo, onStoreChange);
166
- onStoreChange();
167
- });
168
- return () => {
169
- unsubscribeRegistry();
170
- unsubscribeVideo();
171
- };
172
- },
173
- seekProgressTo(id, seconds) {
174
- const video = playerMediaElementStore.get(id);
175
- if (!video) return;
176
- const duration = toSafeDuration(video);
177
- if (duration <= 0) {
178
- video.currentTime = 0;
179
- return;
180
- }
181
- video.currentTime = clamp(Number.isFinite(seconds) ? seconds : 0, 0, duration);
182
- },
183
- seekProgressBy(id, deltaSeconds) {
184
- const video = playerMediaElementStore.get(id);
185
- if (!video) return;
186
- const duration = toSafeDuration(video);
187
- if (duration <= 0) {
188
- video.currentTime = 0;
189
- return;
190
- }
191
- const delta = Number.isFinite(deltaSeconds) ? deltaSeconds : 0;
192
- const currentTime = toSafeCurrentTime(video, duration);
193
- video.currentTime = clamp(currentTime + delta, 0, duration);
194
- }
195
- };
196
-
197
- export { playerStateActions };