@xhub-short/core 0.1.0-beta.10 → 0.1.0-beta.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +707 -495
- package/dist/index.js +402 -59
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,314 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { INetworkAdapter, IVideoLoader, IPosterLoader, ILogger, VideoSource, VideoItem, IDataSource, IStorage, PrefetchCacheData, IAnalytics, ISessionStorage, SessionSnapshot, IInteraction, CommentItem, ReplyItem, ICommentAdapter, InternalLogger, CommentAuthor, PlaylistData, ContentItem, PlaylistSummary, IPlaylistDataSource } from '@xhub-short/contracts';
|
|
2
2
|
export { SessionSnapshot } from '@xhub-short/contracts';
|
|
3
3
|
import { StoreApi } from 'zustand/vanilla';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Network type for prefetch strategy
|
|
7
|
+
* Simplified from contracts NetworkType for prefetch decisions
|
|
8
|
+
*/
|
|
9
|
+
type NetworkType = 'wifi' | 'cellular' | 'offline';
|
|
10
|
+
/**
|
|
11
|
+
* Map contracts NetworkType to simplified NetworkType
|
|
12
|
+
*/
|
|
13
|
+
declare function mapNetworkType(type: string): NetworkType;
|
|
14
|
+
/**
|
|
15
|
+
* Resource state for zustand store
|
|
16
|
+
*/
|
|
17
|
+
interface ResourceState {
|
|
18
|
+
/** Currently allocated video slot indices (max 3) */
|
|
19
|
+
activeAllocations: Set<number>;
|
|
20
|
+
/** Queue of indices to preload */
|
|
21
|
+
preloadQueue: number[];
|
|
22
|
+
/** Currently focused/playing video index */
|
|
23
|
+
focusedIndex: number;
|
|
24
|
+
/** Current network type */
|
|
25
|
+
networkType: NetworkType;
|
|
26
|
+
/** Total number of items in feed (for bounds checking) */
|
|
27
|
+
totalItems: number;
|
|
28
|
+
/** Whether resource governor is active */
|
|
29
|
+
isActive: boolean;
|
|
30
|
+
/** Whether preloading is throttled due to scroll thrashing */
|
|
31
|
+
isThrottled: boolean;
|
|
32
|
+
/** Indices currently being preloaded */
|
|
33
|
+
preloadingIndices: Set<number>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Allocation result returned by requestAllocation
|
|
37
|
+
*/
|
|
38
|
+
interface AllocationResult {
|
|
39
|
+
/** Indices that should mount video elements */
|
|
40
|
+
toMount: number[];
|
|
41
|
+
/** Indices that should unmount video elements */
|
|
42
|
+
toUnmount: number[];
|
|
43
|
+
/** Currently active allocations after this operation */
|
|
44
|
+
activeAllocations: number[];
|
|
45
|
+
/** Whether focus change was successful */
|
|
46
|
+
success: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Prefetch configuration by network type
|
|
50
|
+
*/
|
|
51
|
+
interface PrefetchConfig {
|
|
52
|
+
/** Number of posters to prefetch ahead (default: wifi=5, cellular=3, offline=1) */
|
|
53
|
+
posterCount: number;
|
|
54
|
+
/** Number of video segments to prefetch (default: wifi=2, cellular=1, offline=0) */
|
|
55
|
+
videoSegmentCount: number;
|
|
56
|
+
/** Whether to prefetch video at all (default: true for wifi/cellular) */
|
|
57
|
+
prefetchVideo: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Scroll thrashing configuration
|
|
61
|
+
* Prevents excessive preloading when user scrolls too fast
|
|
62
|
+
*/
|
|
63
|
+
interface ScrollThrashingConfig {
|
|
64
|
+
/** Time window to measure scroll rate (ms) - default: 1000 */
|
|
65
|
+
windowMs: number;
|
|
66
|
+
/** Max focus changes allowed in window before throttling - default: 3 */
|
|
67
|
+
maxChangesInWindow: number;
|
|
68
|
+
/** Cooldown time after throttle before resuming preload (ms) - default: 500 */
|
|
69
|
+
cooldownMs: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* ResourceGovernor configuration
|
|
73
|
+
*/
|
|
74
|
+
interface ResourceConfig {
|
|
75
|
+
/** Maximum video DOM nodes (default: 3) */
|
|
76
|
+
maxAllocations?: number;
|
|
77
|
+
/** Focus debounce time in ms (default: 150) */
|
|
78
|
+
focusDebounceMs?: number;
|
|
79
|
+
/** Prefetch configuration overrides by network type */
|
|
80
|
+
prefetch?: Partial<Record<NetworkType, Partial<PrefetchConfig>>>;
|
|
81
|
+
/** Scroll thrashing configuration */
|
|
82
|
+
scrollThrashing?: Partial<ScrollThrashingConfig>;
|
|
83
|
+
/** Network adapter for detecting connection type */
|
|
84
|
+
networkAdapter?: INetworkAdapter;
|
|
85
|
+
/** Video loader adapter for preloading video data */
|
|
86
|
+
videoLoader?: IVideoLoader;
|
|
87
|
+
/** Poster loader adapter for preloading thumbnails */
|
|
88
|
+
posterLoader?: IPosterLoader;
|
|
89
|
+
/** Logger adapter */
|
|
90
|
+
logger?: ILogger;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Default prefetch configuration by network type
|
|
94
|
+
*/
|
|
95
|
+
declare const DEFAULT_PREFETCH_CONFIG: Record<NetworkType, PrefetchConfig>;
|
|
96
|
+
/**
|
|
97
|
+
* Default resource configuration
|
|
98
|
+
*/
|
|
99
|
+
declare const DEFAULT_RESOURCE_CONFIG: Required<Omit<ResourceConfig, 'networkAdapter' | 'videoLoader' | 'posterLoader' | 'logger' | 'prefetch' | 'scrollThrashing'>> & {
|
|
100
|
+
prefetch: Record<NetworkType, PrefetchConfig>;
|
|
101
|
+
scrollThrashing: ScrollThrashingConfig;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Resource events for external listening
|
|
105
|
+
*/
|
|
106
|
+
type ResourceEvent = {
|
|
107
|
+
type: 'allocationChange';
|
|
108
|
+
toMount: number[];
|
|
109
|
+
toUnmount: number[];
|
|
110
|
+
} | {
|
|
111
|
+
type: 'focusChange';
|
|
112
|
+
index: number;
|
|
113
|
+
previousIndex: number;
|
|
114
|
+
} | {
|
|
115
|
+
type: 'networkChange';
|
|
116
|
+
networkType: NetworkType;
|
|
117
|
+
} | {
|
|
118
|
+
type: 'prefetchRequest';
|
|
119
|
+
indices: number[];
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Resource event listener
|
|
123
|
+
*/
|
|
124
|
+
type ResourceEventListener = (event: ResourceEvent) => void;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Video source getter function type
|
|
128
|
+
* ResourceGovernor needs this to get video sources for preloading
|
|
129
|
+
*/
|
|
130
|
+
type VideoSourceGetter = (index: number) => {
|
|
131
|
+
id: string;
|
|
132
|
+
source: VideoSource;
|
|
133
|
+
poster?: string;
|
|
134
|
+
} | null;
|
|
135
|
+
/**
|
|
136
|
+
* ResourceGovernor - Manages video DOM allocation and prefetch strategy
|
|
137
|
+
*
|
|
138
|
+
* Features:
|
|
139
|
+
* - Sliding window allocation (max 3 video DOM nodes)
|
|
140
|
+
* - Focus debouncing for smooth swipe
|
|
141
|
+
* - Network-aware prefetch strategy
|
|
142
|
+
* - Event system for UI synchronization
|
|
143
|
+
*
|
|
144
|
+
* Memory Strategy:
|
|
145
|
+
* - Only 3 video elements exist in DOM at any time
|
|
146
|
+
* - Sliding window: [previous, current, next]
|
|
147
|
+
* - When user scrolls, we unmount far elements and mount new ones
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* const governor = new ResourceGovernor({ maxAllocations: 3 });
|
|
152
|
+
*
|
|
153
|
+
* // Initialize with feed size
|
|
154
|
+
* governor.setTotalItems(20);
|
|
155
|
+
* governor.activate();
|
|
156
|
+
*
|
|
157
|
+
* // Handle scroll/swipe
|
|
158
|
+
* governor.setFocusedIndex(5); // Debounced
|
|
159
|
+
*
|
|
160
|
+
* // Listen to allocation changes
|
|
161
|
+
* governor.addEventListener((event) => {
|
|
162
|
+
* if (event.type === 'allocationChange') {
|
|
163
|
+
* event.toMount.forEach(i => mountVideoAt(i));
|
|
164
|
+
* event.toUnmount.forEach(i => unmountVideoAt(i));
|
|
165
|
+
* }
|
|
166
|
+
* });
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
declare class ResourceGovernor {
|
|
170
|
+
/** Zustand vanilla store - Single Source of Truth */
|
|
171
|
+
readonly store: StoreApi<ResourceState>;
|
|
172
|
+
/** Resolved configuration */
|
|
173
|
+
private readonly config;
|
|
174
|
+
/** Network adapter */
|
|
175
|
+
private readonly networkAdapter?;
|
|
176
|
+
/** Video loader adapter for preloading video data */
|
|
177
|
+
private readonly videoLoader?;
|
|
178
|
+
/** Poster loader adapter for preloading thumbnails */
|
|
179
|
+
private readonly posterLoader?;
|
|
180
|
+
/** Logger adapter */
|
|
181
|
+
private readonly logger?;
|
|
182
|
+
/** Event listeners */
|
|
183
|
+
private readonly eventListeners;
|
|
184
|
+
/** Focus debounce timer */
|
|
185
|
+
private focusDebounceTimer;
|
|
186
|
+
/** Pending focused index (before debounce completes) */
|
|
187
|
+
private pendingFocusedIndex;
|
|
188
|
+
/** Network change unsubscribe function */
|
|
189
|
+
private networkUnsubscribe?;
|
|
190
|
+
/** Video source getter (injected via setVideoSourceGetter) */
|
|
191
|
+
private videoSourceGetter?;
|
|
192
|
+
/** Scroll thrashing detection - timestamps of recent focus changes */
|
|
193
|
+
private focusChangeTimestamps;
|
|
194
|
+
/** Scroll thrashing cooldown timer */
|
|
195
|
+
private thrashingCooldownTimer;
|
|
196
|
+
constructor(config?: ResourceConfig);
|
|
197
|
+
/**
|
|
198
|
+
* Activate the resource governor
|
|
199
|
+
* Starts network monitoring and performs initial allocation
|
|
200
|
+
*/
|
|
201
|
+
activate(): Promise<void>;
|
|
202
|
+
/**
|
|
203
|
+
* Deactivate the resource governor
|
|
204
|
+
* Stops network monitoring and clears allocations
|
|
205
|
+
*/
|
|
206
|
+
deactivate(): void;
|
|
207
|
+
/**
|
|
208
|
+
* Destroy the resource governor
|
|
209
|
+
*/
|
|
210
|
+
destroy(): void;
|
|
211
|
+
/**
|
|
212
|
+
* Set total number of items in feed
|
|
213
|
+
*/
|
|
214
|
+
setTotalItems(count: number): void;
|
|
215
|
+
/**
|
|
216
|
+
* Set focused index with debouncing
|
|
217
|
+
* This is called during scroll/swipe
|
|
218
|
+
*/
|
|
219
|
+
setFocusedIndex(index: number): void;
|
|
220
|
+
/**
|
|
221
|
+
* Set focused index immediately (skip debounce)
|
|
222
|
+
* Use this for programmatic navigation, not scroll
|
|
223
|
+
*/
|
|
224
|
+
setFocusedIndexImmediate(index: number): void;
|
|
225
|
+
/**
|
|
226
|
+
* Request allocation for given indices
|
|
227
|
+
* Returns what needs to be mounted/unmounted
|
|
228
|
+
*/
|
|
229
|
+
requestAllocation(indices: number[]): AllocationResult;
|
|
230
|
+
/**
|
|
231
|
+
* Get current active allocations
|
|
232
|
+
*/
|
|
233
|
+
getActiveAllocations(): number[];
|
|
234
|
+
/**
|
|
235
|
+
* Check if an index is currently allocated
|
|
236
|
+
*/
|
|
237
|
+
isAllocated(index: number): boolean;
|
|
238
|
+
/**
|
|
239
|
+
* Get current network type
|
|
240
|
+
*/
|
|
241
|
+
getNetworkType(): NetworkType;
|
|
242
|
+
/**
|
|
243
|
+
* Get prefetch configuration for current network
|
|
244
|
+
*/
|
|
245
|
+
getPrefetchConfig(): PrefetchConfig;
|
|
246
|
+
/**
|
|
247
|
+
* Set video source getter function
|
|
248
|
+
* This is called by SDK to provide video data for preloading
|
|
249
|
+
*
|
|
250
|
+
* @param getter - Function that returns video info for a given index
|
|
251
|
+
*/
|
|
252
|
+
setVideoSourceGetter(getter: VideoSourceGetter): void;
|
|
253
|
+
/**
|
|
254
|
+
* Check if preloading is currently throttled due to scroll thrashing
|
|
255
|
+
*/
|
|
256
|
+
isPreloadThrottled(): boolean;
|
|
257
|
+
/**
|
|
258
|
+
* Get indices currently being preloaded
|
|
259
|
+
*/
|
|
260
|
+
getPreloadingIndices(): number[];
|
|
261
|
+
/**
|
|
262
|
+
* Manually trigger preload for specific indices
|
|
263
|
+
* Respects throttling and network conditions
|
|
264
|
+
*/
|
|
265
|
+
triggerPreload(indices: number[]): Promise<void>;
|
|
266
|
+
/**
|
|
267
|
+
* Add event listener
|
|
268
|
+
*/
|
|
269
|
+
addEventListener(listener: ResourceEventListener): () => void;
|
|
270
|
+
/**
|
|
271
|
+
* Remove event listener
|
|
272
|
+
*/
|
|
273
|
+
removeEventListener(listener: ResourceEventListener): void;
|
|
274
|
+
/**
|
|
275
|
+
* Initialize network detection
|
|
276
|
+
*/
|
|
277
|
+
private initializeNetwork;
|
|
278
|
+
/**
|
|
279
|
+
* Perform allocation for a given focused index
|
|
280
|
+
*/
|
|
281
|
+
private performAllocation;
|
|
282
|
+
/**
|
|
283
|
+
* Update prefetch queue based on current state
|
|
284
|
+
*/
|
|
285
|
+
private updatePrefetchQueue;
|
|
286
|
+
/**
|
|
287
|
+
* Track focus change timestamp for scroll thrashing detection
|
|
288
|
+
*/
|
|
289
|
+
private trackFocusChange;
|
|
290
|
+
/**
|
|
291
|
+
* Cancel all in-progress preloads
|
|
292
|
+
*/
|
|
293
|
+
private cancelAllPreloads;
|
|
294
|
+
/**
|
|
295
|
+
* Execute video preloading for given indices
|
|
296
|
+
*/
|
|
297
|
+
private executePreload;
|
|
298
|
+
/**
|
|
299
|
+
* Internal helper to preload a single video
|
|
300
|
+
*/
|
|
301
|
+
private preloadOne;
|
|
302
|
+
/**
|
|
303
|
+
* Execute poster preloading for given indices
|
|
304
|
+
*/
|
|
305
|
+
private executePreloadPosters;
|
|
306
|
+
/**
|
|
307
|
+
* Emit event to all listeners
|
|
308
|
+
*/
|
|
309
|
+
private emitEvent;
|
|
310
|
+
}
|
|
311
|
+
|
|
5
312
|
/**
|
|
6
313
|
* Feed state for zustand store
|
|
7
314
|
*/
|
|
@@ -148,7 +455,8 @@ declare const DEFAULT_PREFETCH_CACHE_CONFIG: PrefetchCacheConfig;
|
|
|
148
455
|
* ```
|
|
149
456
|
*/
|
|
150
457
|
declare class FeedManager {
|
|
151
|
-
private
|
|
458
|
+
private dataSource;
|
|
459
|
+
private readonly logger?;
|
|
152
460
|
/** Zustand vanilla store - Single Source of Truth */
|
|
153
461
|
readonly store: StoreApi<FeedState>;
|
|
154
462
|
/** Resolved configuration */
|
|
@@ -169,7 +477,12 @@ declare class FeedManager {
|
|
|
169
477
|
* Used for garbage collection
|
|
170
478
|
*/
|
|
171
479
|
private accessOrder;
|
|
172
|
-
|
|
480
|
+
/**
|
|
481
|
+
* Track videos that have already triggered predictive preload
|
|
482
|
+
* to avoid duplicate requests.
|
|
483
|
+
*/
|
|
484
|
+
private preloadedVideoIds;
|
|
485
|
+
constructor(dataSource: IDataSource, config?: FeedConfig, storage?: IStorage, prefetchConfig?: Partial<PrefetchCacheConfig>, logger?: ILogger | undefined);
|
|
173
486
|
/** Static memory cache for explicit prefetching */
|
|
174
487
|
private static globalMemoryCache;
|
|
175
488
|
/**
|
|
@@ -189,6 +502,21 @@ declare class FeedManager {
|
|
|
189
502
|
* Clear prefetch cache
|
|
190
503
|
*/
|
|
191
504
|
static clearPrefetchCache(): void;
|
|
505
|
+
/**
|
|
506
|
+
* Get current data source
|
|
507
|
+
*/
|
|
508
|
+
getDataSource(): IDataSource;
|
|
509
|
+
/**
|
|
510
|
+
* Update data source dynamically
|
|
511
|
+
* Used for switching between Recommendation and Playlist modes
|
|
512
|
+
*
|
|
513
|
+
* @param dataSource - New data source adapter
|
|
514
|
+
* @param options - Options for state transition
|
|
515
|
+
*/
|
|
516
|
+
setDataSource(dataSource: IDataSource, options?: {
|
|
517
|
+
/** Whether to reset the feed state immediately */
|
|
518
|
+
reset?: boolean;
|
|
519
|
+
}): void;
|
|
192
520
|
/**
|
|
193
521
|
* Load initial feed data
|
|
194
522
|
*
|
|
@@ -201,7 +529,9 @@ declare class FeedManager {
|
|
|
201
529
|
* - If a request for the same cursor is already in-flight, returns the existing Promise
|
|
202
530
|
* - Prevents duplicate API calls from rapid UI interactions
|
|
203
531
|
*/
|
|
204
|
-
loadInitial(
|
|
532
|
+
loadInitial(options?: {
|
|
533
|
+
replace?: boolean;
|
|
534
|
+
}): Promise<void>;
|
|
205
535
|
/**
|
|
206
536
|
* Internal: Execute load initial logic
|
|
207
537
|
*/
|
|
@@ -225,6 +555,15 @@ declare class FeedManager {
|
|
|
225
555
|
* Used when cached data is stale but still shown to user
|
|
226
556
|
*/
|
|
227
557
|
revalidate(): Promise<void>;
|
|
558
|
+
/**
|
|
559
|
+
* Handle playback progress and trigger predictive preloading
|
|
560
|
+
*
|
|
561
|
+
* @param videoId - ID of the currently playing video
|
|
562
|
+
* @param progress - Current playback progress (0-1)
|
|
563
|
+
* @param governor - Resource governor to trigger preload
|
|
564
|
+
* @param threshold - Progress threshold to trigger preload (default: 0.2)
|
|
565
|
+
*/
|
|
566
|
+
handlePlaybackProgress(videoId: string, progress: number, governor: ResourceGovernor, threshold?: number): void;
|
|
228
567
|
/**
|
|
229
568
|
* Get a video by ID
|
|
230
569
|
* Also updates LRU access time for garbage collection
|
|
@@ -238,6 +577,30 @@ declare class FeedManager {
|
|
|
238
577
|
* Update a video in the feed (for optimistic updates)
|
|
239
578
|
*/
|
|
240
579
|
updateVideo(id: string, updates: Partial<VideoItem>): void;
|
|
580
|
+
/**
|
|
581
|
+
* Replace all items in the feed (e.g. for Playlist synchronization)
|
|
582
|
+
*/
|
|
583
|
+
replaceItems(items: VideoItem[]): void;
|
|
584
|
+
/**
|
|
585
|
+
* Remove an item from the feed
|
|
586
|
+
*
|
|
587
|
+
* Used for:
|
|
588
|
+
* - Report: Remove reported content from feed
|
|
589
|
+
* - Not Interested: Remove content user doesn't want to see
|
|
590
|
+
*
|
|
591
|
+
* @param id - Content ID to remove
|
|
592
|
+
* @returns true if item was removed, false if not found
|
|
593
|
+
*
|
|
594
|
+
* @example
|
|
595
|
+
* ```typescript
|
|
596
|
+
* // User reports a video
|
|
597
|
+
* const wasRemoved = feedManager.removeItem(videoId);
|
|
598
|
+
* if (wasRemoved) {
|
|
599
|
+
* // Navigate to next video
|
|
600
|
+
* }
|
|
601
|
+
* ```
|
|
602
|
+
*/
|
|
603
|
+
removeItem(id: string): boolean;
|
|
241
604
|
/**
|
|
242
605
|
* Check if data is stale and needs revalidation
|
|
243
606
|
*/
|
|
@@ -800,606 +1163,307 @@ declare class PlayerEngine {
|
|
|
800
1163
|
*/
|
|
801
1164
|
private trackCompletion;
|
|
802
1165
|
/**
|
|
803
|
-
* Track when user leaves current video (scrolls to next video)
|
|
804
|
-
* Sends final analytics event before video change
|
|
805
|
-
*/
|
|
806
|
-
private trackLeaveVideo;
|
|
807
|
-
/**
|
|
808
|
-
* Categorize media error
|
|
809
|
-
*/
|
|
810
|
-
private categorizeError;
|
|
811
|
-
/**
|
|
812
|
-
* Get current circuit breaker state
|
|
813
|
-
*/
|
|
814
|
-
getCircuitBreakerState(): CircuitBreakerState;
|
|
815
|
-
/**
|
|
816
|
-
* Check if circuit breaker is open (blocking loads)
|
|
817
|
-
*/
|
|
818
|
-
isCircuitOpen(): boolean;
|
|
819
|
-
/**
|
|
820
|
-
* Manually reset circuit breaker to CLOSED state
|
|
821
|
-
* Useful for user-triggered retry after showing error UI
|
|
822
|
-
*/
|
|
823
|
-
resetCircuitBreaker(): void;
|
|
824
|
-
/**
|
|
825
|
-
* Check if circuit breaker allows load operation
|
|
826
|
-
* Also handles OPEN → HALF_OPEN transition based on timeout
|
|
827
|
-
*/
|
|
828
|
-
private checkCircuitBreaker;
|
|
829
|
-
/**
|
|
830
|
-
* Record a recoverable error for circuit breaker tracking
|
|
831
|
-
*/
|
|
832
|
-
private recordCircuitBreakerError;
|
|
833
|
-
/**
|
|
834
|
-
* Record a successful load for circuit breaker tracking
|
|
835
|
-
*/
|
|
836
|
-
private recordCircuitBreakerSuccess;
|
|
837
|
-
/**
|
|
838
|
-
* Trip the circuit breaker (CLOSED/HALF_OPEN → OPEN)
|
|
839
|
-
*/
|
|
840
|
-
private tripCircuit;
|
|
841
|
-
/**
|
|
842
|
-
* Transition from OPEN to HALF_OPEN
|
|
843
|
-
*/
|
|
844
|
-
private transitionToHalfOpen;
|
|
845
|
-
/**
|
|
846
|
-
* Close the circuit breaker (HALF_OPEN → CLOSED)
|
|
847
|
-
*/
|
|
848
|
-
private closeCircuit;
|
|
849
|
-
/**
|
|
850
|
-
* Schedule automatic transition from OPEN to HALF_OPEN
|
|
851
|
-
*/
|
|
852
|
-
private scheduleHalfOpenTransition;
|
|
853
|
-
/**
|
|
854
|
-
* Clear circuit reset timer
|
|
855
|
-
*/
|
|
856
|
-
private clearCircuitResetTimer;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
/**
|
|
860
|
-
* Check if a state transition is valid
|
|
861
|
-
*/
|
|
862
|
-
declare function isValidTransition(from: PlayerStatus, to: PlayerStatus): boolean;
|
|
863
|
-
/**
|
|
864
|
-
* Check if player is in a "active" state (can receive playback commands)
|
|
865
|
-
*/
|
|
866
|
-
declare function isActiveState(status: PlayerStatus): boolean;
|
|
867
|
-
/**
|
|
868
|
-
* Check if player can start playback
|
|
869
|
-
*/
|
|
870
|
-
declare function canPlay(status: PlayerStatus): boolean;
|
|
871
|
-
/**
|
|
872
|
-
* Check if player can pause
|
|
873
|
-
*/
|
|
874
|
-
declare function canPause(status: PlayerStatus): boolean;
|
|
875
|
-
/**
|
|
876
|
-
* Check if player can seek
|
|
877
|
-
*/
|
|
878
|
-
declare function canSeek(status: PlayerStatus): boolean;
|
|
879
|
-
|
|
880
|
-
/**
|
|
881
|
-
* Lifecycle state for zustand store
|
|
882
|
-
*/
|
|
883
|
-
interface LifecycleState {
|
|
884
|
-
/** Whether manager is initialized */
|
|
885
|
-
isInitialized: boolean;
|
|
886
|
-
/** Whether there's a pending save operation */
|
|
887
|
-
isSaving: boolean;
|
|
888
|
-
/** Whether there's a pending restore operation */
|
|
889
|
-
isRestoring: boolean;
|
|
890
|
-
/** Last saved timestamp */
|
|
891
|
-
lastSavedAt: number | null;
|
|
892
|
-
/** Last restored timestamp */
|
|
893
|
-
lastRestoredAt: number | null;
|
|
894
|
-
/** Whether the restored snapshot needs revalidation */
|
|
895
|
-
needsRevalidation: boolean;
|
|
896
|
-
/** Current visibility state */
|
|
897
|
-
visibilityState: DocumentVisibilityState;
|
|
898
|
-
}
|
|
899
|
-
/**
|
|
900
|
-
* Restore result
|
|
901
|
-
*/
|
|
902
|
-
interface RestoreResult {
|
|
903
|
-
/** Whether restore was successful */
|
|
904
|
-
success: boolean;
|
|
905
|
-
/** Restored snapshot data (null if no valid snapshot) */
|
|
906
|
-
snapshot: SessionSnapshot | null;
|
|
907
|
-
/** Whether the restored data is stale and needs revalidation */
|
|
908
|
-
needsRevalidation: boolean;
|
|
909
|
-
/** Reason for failure (if any) */
|
|
910
|
-
reason?: 'no_snapshot' | 'expired' | 'invalid' | 'error';
|
|
911
|
-
/** Playback time in seconds (only present if restorePlaybackPosition config is enabled) */
|
|
912
|
-
playbackTime?: number;
|
|
913
|
-
/** Video ID that was playing (only present if restorePlaybackPosition config is enabled) */
|
|
914
|
-
currentVideoId?: string;
|
|
915
|
-
/** Captured video frame at playback position (base64 JPEG, only present if restorePlaybackPosition is enabled) */
|
|
916
|
-
restoreFrame?: string;
|
|
917
|
-
}
|
|
918
|
-
/**
|
|
919
|
-
* LifecycleManager configuration
|
|
920
|
-
*/
|
|
921
|
-
interface LifecycleConfig {
|
|
922
|
-
/** Storage adapter for persistence */
|
|
923
|
-
storage?: ISessionStorage;
|
|
924
|
-
/** Logger adapter */
|
|
925
|
-
logger?: ILogger;
|
|
926
|
-
/** Snapshot expiry time in ms (default: 24 hours) */
|
|
927
|
-
snapshotExpiryMs?: number;
|
|
928
|
-
/** Revalidation threshold in ms (default: 5 minutes) */
|
|
929
|
-
revalidationThresholdMs?: number;
|
|
930
|
-
/** Auto-save on visibility change (default: true) */
|
|
931
|
-
autoSaveOnHidden?: boolean;
|
|
932
|
-
/**
|
|
933
|
-
* Enable restoring video playback position (default: false)
|
|
934
|
-
* When enabled, saves and restores the exact playback time (seconds)
|
|
935
|
-
* so video can seek() to the exact position user was watching
|
|
936
|
-
*/
|
|
937
|
-
restorePlaybackPosition?: boolean;
|
|
938
|
-
/** SDK version for compatibility */
|
|
939
|
-
version?: string;
|
|
940
|
-
}
|
|
941
|
-
/**
|
|
942
|
-
* Default lifecycle configuration
|
|
943
|
-
*/
|
|
944
|
-
declare const DEFAULT_LIFECYCLE_CONFIG: Required<Omit<LifecycleConfig, 'storage' | 'logger'>>;
|
|
945
|
-
/**
|
|
946
|
-
* Lifecycle events for external listening
|
|
947
|
-
*/
|
|
948
|
-
type LifecycleEvent = {
|
|
949
|
-
type: 'saveStart';
|
|
950
|
-
} | {
|
|
951
|
-
type: 'saveComplete';
|
|
952
|
-
timestamp: number;
|
|
953
|
-
} | {
|
|
954
|
-
type: 'saveFailed';
|
|
955
|
-
error: Error;
|
|
956
|
-
} | {
|
|
957
|
-
type: 'restoreStart';
|
|
958
|
-
} | {
|
|
959
|
-
type: 'restoreComplete';
|
|
960
|
-
result: RestoreResult;
|
|
961
|
-
} | {
|
|
962
|
-
type: 'visibilityChange';
|
|
963
|
-
state: DocumentVisibilityState;
|
|
964
|
-
};
|
|
965
|
-
/**
|
|
966
|
-
* Lifecycle event listener
|
|
967
|
-
*/
|
|
968
|
-
type LifecycleEventListener = (event: LifecycleEvent) => void;
|
|
969
|
-
|
|
970
|
-
/**
|
|
971
|
-
* LifecycleManager - Handles session persistence and restoration
|
|
972
|
-
*
|
|
973
|
-
* Strategy: "State Persistence, DOM Destruction"
|
|
974
|
-
* - Save snapshot to storage when user leaves
|
|
975
|
-
* - Restore state from storage when user returns
|
|
976
|
-
* - Video DOM nodes are destroyed to free RAM
|
|
977
|
-
*
|
|
978
|
-
* Features:
|
|
979
|
-
* - Auto-save on visibility change (optional)
|
|
980
|
-
* - Snapshot expiry (24 hours default)
|
|
981
|
-
* - Stale data detection for revalidation
|
|
982
|
-
* - Event system for UI coordination
|
|
983
|
-
*
|
|
984
|
-
* @example
|
|
985
|
-
* ```typescript
|
|
986
|
-
* const lifecycle = new LifecycleManager({ storage, logger });
|
|
987
|
-
*
|
|
988
|
-
* // Initialize and attempt restore
|
|
989
|
-
* const result = await lifecycle.initialize();
|
|
990
|
-
* if (result.success) {
|
|
991
|
-
* feedManager.restoreFromSnapshot(result.snapshot);
|
|
992
|
-
* if (result.needsRevalidation) {
|
|
993
|
-
* feedManager.revalidate();
|
|
994
|
-
* }
|
|
995
|
-
* }
|
|
996
|
-
*
|
|
997
|
-
* // Save snapshot before leaving
|
|
998
|
-
* lifecycle.saveSnapshot({
|
|
999
|
-
* items: feedManager.getVideos(),
|
|
1000
|
-
* cursor: feedManager.store.getState().cursor,
|
|
1001
|
-
* focusedIndex: resourceGovernor.store.getState().focusedIndex,
|
|
1002
|
-
* });
|
|
1003
|
-
* ```
|
|
1004
|
-
*/
|
|
1005
|
-
declare class LifecycleManager {
|
|
1006
|
-
/** Zustand vanilla store - Single Source of Truth */
|
|
1007
|
-
readonly store: StoreApi<LifecycleState>;
|
|
1008
|
-
/** Resolved configuration */
|
|
1009
|
-
private readonly config;
|
|
1010
|
-
/** Storage adapter */
|
|
1011
|
-
private readonly storage?;
|
|
1012
|
-
/** Logger adapter */
|
|
1013
|
-
private readonly logger?;
|
|
1014
|
-
/** Event listeners */
|
|
1015
|
-
private readonly eventListeners;
|
|
1016
|
-
/** Visibility change handler reference (for cleanup) */
|
|
1017
|
-
private visibilityHandler?;
|
|
1018
|
-
/** Pending save data (for debouncing) */
|
|
1019
|
-
private pendingSaveData?;
|
|
1020
|
-
constructor(config?: LifecycleConfig);
|
|
1021
|
-
/**
|
|
1022
|
-
* Initialize lifecycle manager and attempt to restore session
|
|
1023
|
-
*/
|
|
1024
|
-
initialize(): Promise<RestoreResult>;
|
|
1025
|
-
/**
|
|
1026
|
-
* Destroy lifecycle manager and cleanup
|
|
1027
|
-
*/
|
|
1028
|
-
destroy(): void;
|
|
1029
|
-
/**
|
|
1030
|
-
* Restore session from storage
|
|
1031
|
-
*/
|
|
1032
|
-
restoreSession(): Promise<RestoreResult>;
|
|
1033
|
-
/**
|
|
1034
|
-
* Save session snapshot to storage
|
|
1035
|
-
*
|
|
1036
|
-
* @param data - Snapshot data to save
|
|
1037
|
-
* @param data.playbackTime - Current video playback position (only saved if restorePlaybackPosition config is enabled)
|
|
1038
|
-
* @param data.currentVideoId - Current video ID (only saved if restorePlaybackPosition config is enabled)
|
|
1039
|
-
* @param data.restoreFrame - Captured video frame at playback position (only saved if restorePlaybackPosition is enabled)
|
|
1166
|
+
* Track when user leaves current video (scrolls to next video)
|
|
1167
|
+
* Sends final analytics event before video change
|
|
1040
1168
|
*/
|
|
1041
|
-
|
|
1042
|
-
items: VideoItem[];
|
|
1043
|
-
cursor: string | null;
|
|
1044
|
-
focusedIndex: number;
|
|
1045
|
-
scrollPosition?: number;
|
|
1046
|
-
/** Current video playback time in seconds (only used when restorePlaybackPosition is enabled) */
|
|
1047
|
-
playbackTime?: number;
|
|
1048
|
-
/** Current video ID (only used when restorePlaybackPosition is enabled) */
|
|
1049
|
-
currentVideoId?: string;
|
|
1050
|
-
/** Captured video frame at playback position (only used when restorePlaybackPosition is enabled) */
|
|
1051
|
-
restoreFrame?: string;
|
|
1052
|
-
}): Promise<boolean>;
|
|
1169
|
+
private trackLeaveVideo;
|
|
1053
1170
|
/**
|
|
1054
|
-
*
|
|
1171
|
+
* Categorize media error
|
|
1055
1172
|
*/
|
|
1056
|
-
|
|
1173
|
+
private categorizeError;
|
|
1057
1174
|
/**
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
* @param data - Data to save when flush is called
|
|
1175
|
+
* Get current circuit breaker state
|
|
1061
1176
|
*/
|
|
1062
|
-
|
|
1177
|
+
getCircuitBreakerState(): CircuitBreakerState;
|
|
1063
1178
|
/**
|
|
1064
|
-
* Check if
|
|
1065
|
-
* SDK can use this to decide whether to collect playbackTime
|
|
1179
|
+
* Check if circuit breaker is open (blocking loads)
|
|
1066
1180
|
*/
|
|
1067
|
-
|
|
1181
|
+
isCircuitOpen(): boolean;
|
|
1068
1182
|
/**
|
|
1069
|
-
*
|
|
1183
|
+
* Manually reset circuit breaker to CLOSED state
|
|
1184
|
+
* Useful for user-triggered retry after showing error UI
|
|
1070
1185
|
*/
|
|
1071
|
-
|
|
1186
|
+
resetCircuitBreaker(): void;
|
|
1072
1187
|
/**
|
|
1073
|
-
*
|
|
1188
|
+
* Check if circuit breaker allows load operation
|
|
1189
|
+
* Also handles OPEN → HALF_OPEN transition based on timeout
|
|
1074
1190
|
*/
|
|
1075
|
-
|
|
1191
|
+
private checkCircuitBreaker;
|
|
1076
1192
|
/**
|
|
1077
|
-
*
|
|
1193
|
+
* Record a recoverable error for circuit breaker tracking
|
|
1078
1194
|
*/
|
|
1079
|
-
|
|
1195
|
+
private recordCircuitBreakerError;
|
|
1080
1196
|
/**
|
|
1081
|
-
*
|
|
1197
|
+
* Record a successful load for circuit breaker tracking
|
|
1082
1198
|
*/
|
|
1083
|
-
|
|
1199
|
+
private recordCircuitBreakerSuccess;
|
|
1084
1200
|
/**
|
|
1085
|
-
*
|
|
1201
|
+
* Trip the circuit breaker (CLOSED/HALF_OPEN → OPEN)
|
|
1086
1202
|
*/
|
|
1087
|
-
|
|
1203
|
+
private tripCircuit;
|
|
1088
1204
|
/**
|
|
1089
|
-
*
|
|
1205
|
+
* Transition from OPEN to HALF_OPEN
|
|
1090
1206
|
*/
|
|
1091
|
-
private
|
|
1207
|
+
private transitionToHalfOpen;
|
|
1092
1208
|
/**
|
|
1093
|
-
*
|
|
1209
|
+
* Close the circuit breaker (HALF_OPEN → CLOSED)
|
|
1094
1210
|
*/
|
|
1095
|
-
private
|
|
1211
|
+
private closeCircuit;
|
|
1096
1212
|
/**
|
|
1097
|
-
*
|
|
1213
|
+
* Schedule automatic transition from OPEN to HALF_OPEN
|
|
1098
1214
|
*/
|
|
1099
|
-
private
|
|
1215
|
+
private scheduleHalfOpenTransition;
|
|
1100
1216
|
/**
|
|
1101
|
-
*
|
|
1217
|
+
* Clear circuit reset timer
|
|
1102
1218
|
*/
|
|
1103
|
-
private
|
|
1219
|
+
private clearCircuitResetTimer;
|
|
1104
1220
|
}
|
|
1105
1221
|
|
|
1106
1222
|
/**
|
|
1107
|
-
*
|
|
1108
|
-
* Simplified from contracts NetworkType for prefetch decisions
|
|
1223
|
+
* Check if a state transition is valid
|
|
1109
1224
|
*/
|
|
1110
|
-
|
|
1225
|
+
declare function isValidTransition(from: PlayerStatus, to: PlayerStatus): boolean;
|
|
1111
1226
|
/**
|
|
1112
|
-
*
|
|
1227
|
+
* Check if player is in a "active" state (can receive playback commands)
|
|
1113
1228
|
*/
|
|
1114
|
-
declare function
|
|
1229
|
+
declare function isActiveState(status: PlayerStatus): boolean;
|
|
1115
1230
|
/**
|
|
1116
|
-
*
|
|
1231
|
+
* Check if player can start playback
|
|
1117
1232
|
*/
|
|
1118
|
-
|
|
1119
|
-
/** Currently allocated video slot indices (max 3) */
|
|
1120
|
-
activeAllocations: Set<number>;
|
|
1121
|
-
/** Queue of indices to preload */
|
|
1122
|
-
preloadQueue: number[];
|
|
1123
|
-
/** Currently focused/playing video index */
|
|
1124
|
-
focusedIndex: number;
|
|
1125
|
-
/** Current network type */
|
|
1126
|
-
networkType: NetworkType;
|
|
1127
|
-
/** Total number of items in feed (for bounds checking) */
|
|
1128
|
-
totalItems: number;
|
|
1129
|
-
/** Whether resource governor is active */
|
|
1130
|
-
isActive: boolean;
|
|
1131
|
-
/** Whether preloading is throttled due to scroll thrashing */
|
|
1132
|
-
isThrottled: boolean;
|
|
1133
|
-
/** Indices currently being preloaded */
|
|
1134
|
-
preloadingIndices: Set<number>;
|
|
1135
|
-
}
|
|
1233
|
+
declare function canPlay(status: PlayerStatus): boolean;
|
|
1136
1234
|
/**
|
|
1137
|
-
*
|
|
1235
|
+
* Check if player can pause
|
|
1138
1236
|
*/
|
|
1139
|
-
|
|
1140
|
-
/** Indices that should mount video elements */
|
|
1141
|
-
toMount: number[];
|
|
1142
|
-
/** Indices that should unmount video elements */
|
|
1143
|
-
toUnmount: number[];
|
|
1144
|
-
/** Currently active allocations after this operation */
|
|
1145
|
-
activeAllocations: number[];
|
|
1146
|
-
/** Whether focus change was successful */
|
|
1147
|
-
success: boolean;
|
|
1148
|
-
}
|
|
1237
|
+
declare function canPause(status: PlayerStatus): boolean;
|
|
1149
1238
|
/**
|
|
1150
|
-
*
|
|
1239
|
+
* Check if player can seek
|
|
1151
1240
|
*/
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1241
|
+
declare function canSeek(status: PlayerStatus): boolean;
|
|
1242
|
+
|
|
1243
|
+
/**
|
|
1244
|
+
* Lifecycle state for zustand store
|
|
1245
|
+
*/
|
|
1246
|
+
interface LifecycleState {
|
|
1247
|
+
/** Whether manager is initialized */
|
|
1248
|
+
isInitialized: boolean;
|
|
1249
|
+
/** Whether there's a pending save operation */
|
|
1250
|
+
isSaving: boolean;
|
|
1251
|
+
/** Whether there's a pending restore operation */
|
|
1252
|
+
isRestoring: boolean;
|
|
1253
|
+
/** Last saved timestamp */
|
|
1254
|
+
lastSavedAt: number | null;
|
|
1255
|
+
/** Last restored timestamp */
|
|
1256
|
+
lastRestoredAt: number | null;
|
|
1257
|
+
/** Whether the restored snapshot needs revalidation */
|
|
1258
|
+
needsRevalidation: boolean;
|
|
1259
|
+
/** Current visibility state */
|
|
1260
|
+
visibilityState: DocumentVisibilityState;
|
|
1159
1261
|
}
|
|
1160
1262
|
/**
|
|
1161
|
-
*
|
|
1162
|
-
* Prevents excessive preloading when user scrolls too fast
|
|
1263
|
+
* Restore result
|
|
1163
1264
|
*/
|
|
1164
|
-
interface
|
|
1165
|
-
/**
|
|
1166
|
-
|
|
1167
|
-
/**
|
|
1168
|
-
|
|
1169
|
-
/**
|
|
1170
|
-
|
|
1265
|
+
interface RestoreResult {
|
|
1266
|
+
/** Whether restore was successful */
|
|
1267
|
+
success: boolean;
|
|
1268
|
+
/** Restored snapshot data (null if no valid snapshot) */
|
|
1269
|
+
snapshot: SessionSnapshot | null;
|
|
1270
|
+
/** Whether the restored data is stale and needs revalidation */
|
|
1271
|
+
needsRevalidation: boolean;
|
|
1272
|
+
/** Reason for failure (if any) */
|
|
1273
|
+
reason?: 'no_snapshot' | 'expired' | 'invalid' | 'error';
|
|
1274
|
+
/** Playback time in seconds (only present if restorePlaybackPosition config is enabled) */
|
|
1275
|
+
playbackTime?: number;
|
|
1276
|
+
/** Video ID that was playing (only present if restorePlaybackPosition config is enabled) */
|
|
1277
|
+
currentVideoId?: string;
|
|
1278
|
+
/** Captured video frame at playback position (base64 JPEG, only present if restorePlaybackPosition is enabled) */
|
|
1279
|
+
restoreFrame?: string;
|
|
1171
1280
|
}
|
|
1172
1281
|
/**
|
|
1173
|
-
*
|
|
1282
|
+
* LifecycleManager configuration
|
|
1174
1283
|
*/
|
|
1175
|
-
interface
|
|
1176
|
-
/**
|
|
1177
|
-
|
|
1178
|
-
/** Focus debounce time in ms (default: 150) */
|
|
1179
|
-
focusDebounceMs?: number;
|
|
1180
|
-
/** Prefetch configuration overrides by network type */
|
|
1181
|
-
prefetch?: Partial<Record<NetworkType, Partial<PrefetchConfig>>>;
|
|
1182
|
-
/** Scroll thrashing configuration */
|
|
1183
|
-
scrollThrashing?: Partial<ScrollThrashingConfig>;
|
|
1184
|
-
/** Network adapter for detecting connection type */
|
|
1185
|
-
networkAdapter?: INetworkAdapter;
|
|
1186
|
-
/** Video loader adapter for preloading video data */
|
|
1187
|
-
videoLoader?: IVideoLoader;
|
|
1188
|
-
/** Poster loader adapter for preloading thumbnails */
|
|
1189
|
-
posterLoader?: IPosterLoader;
|
|
1284
|
+
interface LifecycleConfig {
|
|
1285
|
+
/** Storage adapter for persistence */
|
|
1286
|
+
storage?: ISessionStorage;
|
|
1190
1287
|
/** Logger adapter */
|
|
1191
1288
|
logger?: ILogger;
|
|
1289
|
+
/** Snapshot expiry time in ms (default: 24 hours) */
|
|
1290
|
+
snapshotExpiryMs?: number;
|
|
1291
|
+
/** Revalidation threshold in ms (default: 5 minutes) */
|
|
1292
|
+
revalidationThresholdMs?: number;
|
|
1293
|
+
/** Auto-save on visibility change (default: true) */
|
|
1294
|
+
autoSaveOnHidden?: boolean;
|
|
1295
|
+
/**
|
|
1296
|
+
* Enable restoring video playback position (default: false)
|
|
1297
|
+
* When enabled, saves and restores the exact playback time (seconds)
|
|
1298
|
+
* so video can seek() to the exact position user was watching
|
|
1299
|
+
*/
|
|
1300
|
+
restorePlaybackPosition?: boolean;
|
|
1301
|
+
/** SDK version for compatibility */
|
|
1302
|
+
version?: string;
|
|
1192
1303
|
}
|
|
1193
1304
|
/**
|
|
1194
|
-
* Default
|
|
1195
|
-
*/
|
|
1196
|
-
declare const DEFAULT_PREFETCH_CONFIG: Record<NetworkType, PrefetchConfig>;
|
|
1197
|
-
/**
|
|
1198
|
-
* Default resource configuration
|
|
1305
|
+
* Default lifecycle configuration
|
|
1199
1306
|
*/
|
|
1200
|
-
declare const
|
|
1201
|
-
prefetch: Record<NetworkType, PrefetchConfig>;
|
|
1202
|
-
scrollThrashing: ScrollThrashingConfig;
|
|
1203
|
-
};
|
|
1307
|
+
declare const DEFAULT_LIFECYCLE_CONFIG: Required<Omit<LifecycleConfig, 'storage' | 'logger'>>;
|
|
1204
1308
|
/**
|
|
1205
|
-
*
|
|
1309
|
+
* Lifecycle events for external listening
|
|
1206
1310
|
*/
|
|
1207
|
-
type
|
|
1208
|
-
type: '
|
|
1209
|
-
toMount: number[];
|
|
1210
|
-
toUnmount: number[];
|
|
1311
|
+
type LifecycleEvent = {
|
|
1312
|
+
type: 'saveStart';
|
|
1211
1313
|
} | {
|
|
1212
|
-
type: '
|
|
1213
|
-
|
|
1214
|
-
previousIndex: number;
|
|
1314
|
+
type: 'saveComplete';
|
|
1315
|
+
timestamp: number;
|
|
1215
1316
|
} | {
|
|
1216
|
-
type: '
|
|
1217
|
-
|
|
1317
|
+
type: 'saveFailed';
|
|
1318
|
+
error: Error;
|
|
1218
1319
|
} | {
|
|
1219
|
-
type: '
|
|
1220
|
-
|
|
1320
|
+
type: 'restoreStart';
|
|
1321
|
+
} | {
|
|
1322
|
+
type: 'restoreComplete';
|
|
1323
|
+
result: RestoreResult;
|
|
1324
|
+
} | {
|
|
1325
|
+
type: 'visibilityChange';
|
|
1326
|
+
state: DocumentVisibilityState;
|
|
1221
1327
|
};
|
|
1222
1328
|
/**
|
|
1223
|
-
*
|
|
1329
|
+
* Lifecycle event listener
|
|
1224
1330
|
*/
|
|
1225
|
-
type
|
|
1331
|
+
type LifecycleEventListener = (event: LifecycleEvent) => void;
|
|
1226
1332
|
|
|
1227
1333
|
/**
|
|
1228
|
-
*
|
|
1229
|
-
* ResourceGovernor needs this to get video sources for preloading
|
|
1230
|
-
*/
|
|
1231
|
-
type VideoSourceGetter = (index: number) => {
|
|
1232
|
-
id: string;
|
|
1233
|
-
source: VideoSource;
|
|
1234
|
-
poster?: string;
|
|
1235
|
-
} | null;
|
|
1236
|
-
/**
|
|
1237
|
-
* ResourceGovernor - Manages video DOM allocation and prefetch strategy
|
|
1334
|
+
* LifecycleManager - Handles session persistence and restoration
|
|
1238
1335
|
*
|
|
1239
|
-
*
|
|
1240
|
-
* -
|
|
1241
|
-
* -
|
|
1242
|
-
* -
|
|
1243
|
-
* - Event system for UI synchronization
|
|
1336
|
+
* Strategy: "State Persistence, DOM Destruction"
|
|
1337
|
+
* - Save snapshot to storage when user leaves
|
|
1338
|
+
* - Restore state from storage when user returns
|
|
1339
|
+
* - Video DOM nodes are destroyed to free RAM
|
|
1244
1340
|
*
|
|
1245
|
-
*
|
|
1246
|
-
* -
|
|
1247
|
-
* -
|
|
1248
|
-
* -
|
|
1341
|
+
* Features:
|
|
1342
|
+
* - Auto-save on visibility change (optional)
|
|
1343
|
+
* - Snapshot expiry (24 hours default)
|
|
1344
|
+
* - Stale data detection for revalidation
|
|
1345
|
+
* - Event system for UI coordination
|
|
1249
1346
|
*
|
|
1250
1347
|
* @example
|
|
1251
1348
|
* ```typescript
|
|
1252
|
-
* const
|
|
1253
|
-
*
|
|
1254
|
-
* // Initialize with feed size
|
|
1255
|
-
* governor.setTotalItems(20);
|
|
1256
|
-
* governor.activate();
|
|
1257
|
-
*
|
|
1258
|
-
* // Handle scroll/swipe
|
|
1259
|
-
* governor.setFocusedIndex(5); // Debounced
|
|
1349
|
+
* const lifecycle = new LifecycleManager({ storage, logger });
|
|
1260
1350
|
*
|
|
1261
|
-
* //
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1351
|
+
* // Initialize and attempt restore
|
|
1352
|
+
* const result = await lifecycle.initialize();
|
|
1353
|
+
* if (result.success) {
|
|
1354
|
+
* feedManager.restoreFromSnapshot(result.snapshot);
|
|
1355
|
+
* if (result.needsRevalidation) {
|
|
1356
|
+
* feedManager.revalidate();
|
|
1266
1357
|
* }
|
|
1358
|
+
* }
|
|
1359
|
+
*
|
|
1360
|
+
* // Save snapshot before leaving
|
|
1361
|
+
* lifecycle.saveSnapshot({
|
|
1362
|
+
* items: feedManager.getVideos(),
|
|
1363
|
+
* cursor: feedManager.store.getState().cursor,
|
|
1364
|
+
* focusedIndex: resourceGovernor.store.getState().focusedIndex,
|
|
1267
1365
|
* });
|
|
1268
1366
|
* ```
|
|
1269
1367
|
*/
|
|
1270
|
-
declare class
|
|
1368
|
+
declare class LifecycleManager {
|
|
1271
1369
|
/** Zustand vanilla store - Single Source of Truth */
|
|
1272
|
-
readonly store: StoreApi<
|
|
1370
|
+
readonly store: StoreApi<LifecycleState>;
|
|
1273
1371
|
/** Resolved configuration */
|
|
1274
1372
|
private readonly config;
|
|
1275
|
-
/**
|
|
1276
|
-
private readonly
|
|
1277
|
-
/** Video loader adapter for preloading video data */
|
|
1278
|
-
private readonly videoLoader?;
|
|
1279
|
-
/** Poster loader adapter for preloading thumbnails */
|
|
1280
|
-
private readonly posterLoader?;
|
|
1373
|
+
/** Storage adapter */
|
|
1374
|
+
private readonly storage?;
|
|
1281
1375
|
/** Logger adapter */
|
|
1282
1376
|
private readonly logger?;
|
|
1283
1377
|
/** Event listeners */
|
|
1284
1378
|
private readonly eventListeners;
|
|
1285
|
-
/**
|
|
1286
|
-
private
|
|
1287
|
-
/** Pending
|
|
1288
|
-
private
|
|
1289
|
-
|
|
1290
|
-
private networkUnsubscribe?;
|
|
1291
|
-
/** Video source getter (injected via setVideoSourceGetter) */
|
|
1292
|
-
private videoSourceGetter?;
|
|
1293
|
-
/** Scroll thrashing detection - timestamps of recent focus changes */
|
|
1294
|
-
private focusChangeTimestamps;
|
|
1295
|
-
/** Scroll thrashing cooldown timer */
|
|
1296
|
-
private thrashingCooldownTimer;
|
|
1297
|
-
constructor(config?: ResourceConfig);
|
|
1298
|
-
/**
|
|
1299
|
-
* Activate the resource governor
|
|
1300
|
-
* Starts network monitoring and performs initial allocation
|
|
1301
|
-
*/
|
|
1302
|
-
activate(): Promise<void>;
|
|
1379
|
+
/** Visibility change handler reference (for cleanup) */
|
|
1380
|
+
private visibilityHandler?;
|
|
1381
|
+
/** Pending save data (for debouncing) */
|
|
1382
|
+
private pendingSaveData?;
|
|
1383
|
+
constructor(config?: LifecycleConfig);
|
|
1303
1384
|
/**
|
|
1304
|
-
*
|
|
1305
|
-
* Stops network monitoring and clears allocations
|
|
1385
|
+
* Initialize lifecycle manager and attempt to restore session
|
|
1306
1386
|
*/
|
|
1307
|
-
|
|
1387
|
+
initialize(): Promise<RestoreResult>;
|
|
1308
1388
|
/**
|
|
1309
|
-
* Destroy
|
|
1389
|
+
* Destroy lifecycle manager and cleanup
|
|
1310
1390
|
*/
|
|
1311
1391
|
destroy(): void;
|
|
1312
1392
|
/**
|
|
1313
|
-
*
|
|
1314
|
-
*/
|
|
1315
|
-
setTotalItems(count: number): void;
|
|
1316
|
-
/**
|
|
1317
|
-
* Set focused index with debouncing
|
|
1318
|
-
* This is called during scroll/swipe
|
|
1319
|
-
*/
|
|
1320
|
-
setFocusedIndex(index: number): void;
|
|
1321
|
-
/**
|
|
1322
|
-
* Set focused index immediately (skip debounce)
|
|
1323
|
-
* Use this for programmatic navigation, not scroll
|
|
1324
|
-
*/
|
|
1325
|
-
setFocusedIndexImmediate(index: number): void;
|
|
1326
|
-
/**
|
|
1327
|
-
* Request allocation for given indices
|
|
1328
|
-
* Returns what needs to be mounted/unmounted
|
|
1329
|
-
*/
|
|
1330
|
-
requestAllocation(indices: number[]): AllocationResult;
|
|
1331
|
-
/**
|
|
1332
|
-
* Get current active allocations
|
|
1393
|
+
* Restore session from storage
|
|
1333
1394
|
*/
|
|
1334
|
-
|
|
1395
|
+
restoreSession(): Promise<RestoreResult>;
|
|
1335
1396
|
/**
|
|
1336
|
-
*
|
|
1397
|
+
* Save session snapshot to storage
|
|
1398
|
+
*
|
|
1399
|
+
* @param data - Snapshot data to save
|
|
1400
|
+
* @param data.playbackTime - Current video playback position (only saved if restorePlaybackPosition config is enabled)
|
|
1401
|
+
* @param data.currentVideoId - Current video ID (only saved if restorePlaybackPosition config is enabled)
|
|
1402
|
+
* @param data.restoreFrame - Captured video frame at playback position (only saved if restorePlaybackPosition is enabled)
|
|
1337
1403
|
*/
|
|
1338
|
-
|
|
1404
|
+
saveSnapshot(data: {
|
|
1405
|
+
items: VideoItem[];
|
|
1406
|
+
cursor: string | null;
|
|
1407
|
+
focusedIndex: number;
|
|
1408
|
+
scrollPosition?: number;
|
|
1409
|
+
/** Current video playback time in seconds (only used when restorePlaybackPosition is enabled) */
|
|
1410
|
+
playbackTime?: number;
|
|
1411
|
+
/** Current video ID (only used when restorePlaybackPosition is enabled) */
|
|
1412
|
+
currentVideoId?: string;
|
|
1413
|
+
/** Captured video frame at playback position (only used when restorePlaybackPosition is enabled) */
|
|
1414
|
+
restoreFrame?: string;
|
|
1415
|
+
}): Promise<boolean>;
|
|
1339
1416
|
/**
|
|
1340
|
-
*
|
|
1417
|
+
* Clear saved snapshot
|
|
1341
1418
|
*/
|
|
1342
|
-
|
|
1419
|
+
clearSnapshot(): Promise<void>;
|
|
1343
1420
|
/**
|
|
1344
|
-
*
|
|
1421
|
+
* Mark that pending data should be saved (for use with debouncing)
|
|
1422
|
+
*
|
|
1423
|
+
* @param data - Data to save when flush is called
|
|
1345
1424
|
*/
|
|
1346
|
-
|
|
1425
|
+
setPendingSave(data: Omit<SessionSnapshot, 'savedAt' | 'version'>): void;
|
|
1347
1426
|
/**
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
*
|
|
1351
|
-
* @param getter - Function that returns video info for a given index
|
|
1427
|
+
* Check if restorePlaybackPosition config is enabled
|
|
1428
|
+
* SDK can use this to decide whether to collect playbackTime
|
|
1352
1429
|
*/
|
|
1353
|
-
|
|
1430
|
+
isPlaybackPositionRestoreEnabled(): boolean;
|
|
1354
1431
|
/**
|
|
1355
|
-
*
|
|
1432
|
+
* Flush pending save (called on visibility hidden)
|
|
1356
1433
|
*/
|
|
1357
|
-
|
|
1434
|
+
flushPendingSave(): Promise<void>;
|
|
1358
1435
|
/**
|
|
1359
|
-
*
|
|
1436
|
+
* Handle visibility state change
|
|
1360
1437
|
*/
|
|
1361
|
-
|
|
1438
|
+
onVisibilityChange(state: DocumentVisibilityState): void;
|
|
1362
1439
|
/**
|
|
1363
|
-
*
|
|
1364
|
-
* Respects throttling and network conditions
|
|
1440
|
+
* Get current visibility state
|
|
1365
1441
|
*/
|
|
1366
|
-
|
|
1442
|
+
getVisibilityState(): DocumentVisibilityState;
|
|
1367
1443
|
/**
|
|
1368
1444
|
* Add event listener
|
|
1369
1445
|
*/
|
|
1370
|
-
addEventListener(listener:
|
|
1446
|
+
addEventListener(listener: LifecycleEventListener): () => void;
|
|
1371
1447
|
/**
|
|
1372
1448
|
* Remove event listener
|
|
1373
1449
|
*/
|
|
1374
|
-
removeEventListener(listener:
|
|
1375
|
-
/**
|
|
1376
|
-
* Initialize network detection
|
|
1377
|
-
*/
|
|
1378
|
-
private initializeNetwork;
|
|
1379
|
-
/**
|
|
1380
|
-
* Perform allocation for a given focused index
|
|
1381
|
-
*/
|
|
1382
|
-
private performAllocation;
|
|
1383
|
-
/**
|
|
1384
|
-
* Update prefetch queue based on current state
|
|
1385
|
-
*/
|
|
1386
|
-
private updatePrefetchQueue;
|
|
1450
|
+
removeEventListener(listener: LifecycleEventListener): void;
|
|
1387
1451
|
/**
|
|
1388
|
-
*
|
|
1452
|
+
* Setup visibility change listener
|
|
1389
1453
|
*/
|
|
1390
|
-
private
|
|
1454
|
+
private setupVisibilityListener;
|
|
1391
1455
|
/**
|
|
1392
|
-
*
|
|
1456
|
+
* Validate snapshot data
|
|
1393
1457
|
*/
|
|
1394
|
-
private
|
|
1458
|
+
private validateSnapshot;
|
|
1395
1459
|
/**
|
|
1396
|
-
*
|
|
1460
|
+
* Check if snapshot is stale (needs revalidation)
|
|
1397
1461
|
*/
|
|
1398
|
-
private
|
|
1462
|
+
private isSnapshotStale;
|
|
1399
1463
|
/**
|
|
1400
|
-
*
|
|
1464
|
+
* Create session snapshot from data
|
|
1401
1465
|
*/
|
|
1402
|
-
private
|
|
1466
|
+
private createSnapshot;
|
|
1403
1467
|
/**
|
|
1404
1468
|
* Emit event to all listeners
|
|
1405
1469
|
*/
|
|
@@ -1939,4 +2003,152 @@ declare class CommentManager {
|
|
|
1939
2003
|
private createError;
|
|
1940
2004
|
}
|
|
1941
2005
|
|
|
1942
|
-
|
|
2006
|
+
/**
|
|
2007
|
+
* Playlist state for zustand store
|
|
2008
|
+
*/
|
|
2009
|
+
interface PlaylistState {
|
|
2010
|
+
/** Currently loaded playlist metadata */
|
|
2011
|
+
playlist: PlaylistData | null;
|
|
2012
|
+
/** Current active item index */
|
|
2013
|
+
currentIndex: number;
|
|
2014
|
+
/** List of items in the playlist (shortcut to playlist.items) */
|
|
2015
|
+
items: ContentItem[];
|
|
2016
|
+
/** Loading state */
|
|
2017
|
+
loading: boolean;
|
|
2018
|
+
/** Error state */
|
|
2019
|
+
error: Error | null;
|
|
2020
|
+
}
|
|
2021
|
+
/**
|
|
2022
|
+
* Configuration for PlaylistManager
|
|
2023
|
+
*/
|
|
2024
|
+
interface PlaylistConfig {
|
|
2025
|
+
/**
|
|
2026
|
+
* Number of items to keep full metadata for around the current index
|
|
2027
|
+
* @default 10
|
|
2028
|
+
*/
|
|
2029
|
+
metadataWindowSize?: number;
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Actions for PlaylistManager
|
|
2033
|
+
*/
|
|
2034
|
+
interface PlaylistActions {
|
|
2035
|
+
/** Load a playlist by ID */
|
|
2036
|
+
loadPlaylist: (id: string) => Promise<void>;
|
|
2037
|
+
/** Set a playlist directly */
|
|
2038
|
+
setPlaylist: (playlist: PlaylistData) => void;
|
|
2039
|
+
/** Navigate to next item */
|
|
2040
|
+
next: () => void;
|
|
2041
|
+
/** Navigate to previous item */
|
|
2042
|
+
prev: () => void;
|
|
2043
|
+
/** Jump to specific index */
|
|
2044
|
+
jumpTo: (index: number) => void;
|
|
2045
|
+
/** Reset state */
|
|
2046
|
+
reset: () => void;
|
|
2047
|
+
}
|
|
2048
|
+
/**
|
|
2049
|
+
* State for PlaylistCollectionManager
|
|
2050
|
+
*/
|
|
2051
|
+
interface PlaylistCollectionState {
|
|
2052
|
+
/** Array of playlist summaries */
|
|
2053
|
+
playlists: PlaylistSummary[];
|
|
2054
|
+
/** Whether loading is in progress */
|
|
2055
|
+
loading: boolean;
|
|
2056
|
+
/** Current pagination cursor */
|
|
2057
|
+
cursor: string | null;
|
|
2058
|
+
/** Whether more items can be loaded */
|
|
2059
|
+
hasMore: boolean;
|
|
2060
|
+
/** Error state */
|
|
2061
|
+
error: Error | null;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
/**
|
|
2065
|
+
* PlaylistManager - Manages playlist state and navigation
|
|
2066
|
+
*
|
|
2067
|
+
* Features:
|
|
2068
|
+
* - Tracks current playlist and active index
|
|
2069
|
+
* - Provides navigation actions (next, prev, jumpTo)
|
|
2070
|
+
* - Uses vanilla Zustand store for state management
|
|
2071
|
+
*/
|
|
2072
|
+
declare class PlaylistManager {
|
|
2073
|
+
private readonly dataSource?;
|
|
2074
|
+
private readonly governor?;
|
|
2075
|
+
/** Zustand vanilla store */
|
|
2076
|
+
readonly store: StoreApi<PlaylistState>;
|
|
2077
|
+
/** Resolved configuration */
|
|
2078
|
+
private readonly config;
|
|
2079
|
+
/**
|
|
2080
|
+
* Internal cache of full metadata items.
|
|
2081
|
+
* Items in store.items may be minified to save memory.
|
|
2082
|
+
*/
|
|
2083
|
+
private fullMetadataItems;
|
|
2084
|
+
/** Unsubscribe from governor events */
|
|
2085
|
+
private governorUnsubscribe?;
|
|
2086
|
+
constructor(dataSource?: IPlaylistDataSource | undefined, config?: PlaylistConfig, governor?: ResourceGovernor | undefined);
|
|
2087
|
+
/**
|
|
2088
|
+
* Load a playlist by ID
|
|
2089
|
+
*/
|
|
2090
|
+
loadPlaylist(id: string): Promise<void>;
|
|
2091
|
+
/**
|
|
2092
|
+
* Set playlist data directly
|
|
2093
|
+
*/
|
|
2094
|
+
setPlaylist(playlist: PlaylistData): void;
|
|
2095
|
+
/**
|
|
2096
|
+
* Navigate to next item
|
|
2097
|
+
*/
|
|
2098
|
+
next(): void;
|
|
2099
|
+
/**
|
|
2100
|
+
* Navigate to previous item
|
|
2101
|
+
*/
|
|
2102
|
+
prev(): void;
|
|
2103
|
+
/**
|
|
2104
|
+
* Jump to specific index
|
|
2105
|
+
*/
|
|
2106
|
+
jumpTo(index: number): void;
|
|
2107
|
+
/**
|
|
2108
|
+
* Reset state
|
|
2109
|
+
*/
|
|
2110
|
+
reset(): void;
|
|
2111
|
+
/**
|
|
2112
|
+
* Destroy the manager
|
|
2113
|
+
*/
|
|
2114
|
+
destroy(): void;
|
|
2115
|
+
/**
|
|
2116
|
+
* Update the sliding window of full metadata items.
|
|
2117
|
+
* Items outside the window are minified to save memory.
|
|
2118
|
+
*/
|
|
2119
|
+
private updateMetadataWindow;
|
|
2120
|
+
/**
|
|
2121
|
+
* Create a minified version of a ContentItem to save memory.
|
|
2122
|
+
* Keeps only essential fields for identification and basic UI.
|
|
2123
|
+
*/
|
|
2124
|
+
private minifyItem;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
/**
|
|
2128
|
+
* PlaylistCollectionManager - Manages the discovery of playlists.
|
|
2129
|
+
*
|
|
2130
|
+
* Responsibilities:
|
|
2131
|
+
* - Fetching playlist summaries from DataSource
|
|
2132
|
+
* - Handling pagination and cursor management
|
|
2133
|
+
* - Maintaining loading and error states
|
|
2134
|
+
*/
|
|
2135
|
+
declare class PlaylistCollectionManager {
|
|
2136
|
+
private readonly dataSource?;
|
|
2137
|
+
/** Zustand vanilla store */
|
|
2138
|
+
readonly store: StoreApi<PlaylistCollectionState>;
|
|
2139
|
+
constructor(dataSource?: IPlaylistDataSource | undefined);
|
|
2140
|
+
/**
|
|
2141
|
+
* Load more playlists (pagination)
|
|
2142
|
+
*/
|
|
2143
|
+
loadMore(): Promise<void>;
|
|
2144
|
+
/**
|
|
2145
|
+
* Refresh the collection (reset and re-fetch)
|
|
2146
|
+
*/
|
|
2147
|
+
refresh(): Promise<void>;
|
|
2148
|
+
/**
|
|
2149
|
+
* Reset the store to initial state
|
|
2150
|
+
*/
|
|
2151
|
+
reset(): void;
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
export { type ActionType, type AllocationResult, type CommentError, CommentManager, type CommentManagerConfig, type CommentManagerState, DEFAULT_COMMENT_MANAGER_CONFIG, DEFAULT_FEED_CONFIG, DEFAULT_LIFECYCLE_CONFIG, DEFAULT_OPTIMISTIC_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_PREFETCH_CACHE_CONFIG, DEFAULT_PREFETCH_CONFIG, DEFAULT_RESOURCE_CONFIG, type FeedConfig, type FeedError, FeedManager, type FeedState, type LifecycleConfig, type LifecycleEvent, type LifecycleEventListener, LifecycleManager, type LifecycleState, type NetworkType, type OptimisticComment, type OptimisticConfig, type OptimisticEvent, type OptimisticEventListener, OptimisticManager, type OptimisticReply, type OptimisticState, type PendingAction, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PlaylistActions, PlaylistCollectionManager, type PlaylistCollectionState, PlaylistManager, type PlaylistState, type PrefetchCacheConfig, type PrefetchConfig, type ResourceConfig, type ResourceEvent, type ResourceEventListener, ResourceGovernor, type ResourceState, type RestoreResult, type VideoCommentState, calculatePrefetchIndices, calculateWindowIndices, canPause, canPlay, canSeek, computeAllocationChanges, createInitialCommentState, createInitialVideoCommentState, isActiveState, isValidTransition, mapNetworkType };
|