@xhub-short/core 0.1.0-beta.8 → 1.0.0-beta.21
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 +788 -625
- package/dist/index.js +780 -459
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,321 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { INetworkAdapter, IVideoLoader, IPosterLoader, ILogger, VideoSource, ContentItem, IDataSource, IStorage, VideoItem, PrefetchCacheData, IAnalytics, ISessionStorage, SessionSnapshot, IInteraction, CommentItem, ReplyItem, ICommentAdapter, InternalLogger, CommentAuthor, PlaylistData, 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
|
*/
|
|
8
315
|
interface FeedState {
|
|
9
|
-
/** Normalized
|
|
10
|
-
itemsById: Map<string,
|
|
11
|
-
/** Ordered list of
|
|
316
|
+
/** Normalized content data - Map for O(1) lookup */
|
|
317
|
+
itemsById: Map<string, ContentItem>;
|
|
318
|
+
/** Ordered list of content IDs for rendering */
|
|
12
319
|
displayOrder: string[];
|
|
13
320
|
/** Initial loading state */
|
|
14
321
|
loading: boolean;
|
|
@@ -53,8 +360,8 @@ interface FeedConfig {
|
|
|
53
360
|
/** Whether to enable SWR pattern (default: true) */
|
|
54
361
|
enableSWR?: boolean;
|
|
55
362
|
/**
|
|
56
|
-
* Maximum number of
|
|
57
|
-
* When exceeded, older
|
|
363
|
+
* Maximum number of items to keep in memory cache
|
|
364
|
+
* When exceeded, older items will be evicted (LRU policy)
|
|
58
365
|
* @default 100
|
|
59
366
|
*/
|
|
60
367
|
maxCacheSize?: number;
|
|
@@ -92,11 +399,14 @@ interface PrefetchCacheConfig {
|
|
|
92
399
|
*/
|
|
93
400
|
enabled: boolean;
|
|
94
401
|
/**
|
|
95
|
-
* Maximum number of
|
|
402
|
+
* Maximum number of items to cache
|
|
96
403
|
* Higher = more instant content, but more storage
|
|
97
|
-
* @default 10
|
|
98
404
|
*/
|
|
99
|
-
|
|
405
|
+
maxItems: number;
|
|
406
|
+
/**
|
|
407
|
+
* @deprecated Use maxItems instead. Will be removed in v3.0
|
|
408
|
+
*/
|
|
409
|
+
maxVideos?: number;
|
|
100
410
|
/**
|
|
101
411
|
* Storage key for prefetch cache
|
|
102
412
|
* @default 'sv-prefetch-cache'
|
|
@@ -104,8 +414,8 @@ interface PrefetchCacheConfig {
|
|
|
104
414
|
storageKey: string;
|
|
105
415
|
/**
|
|
106
416
|
* Enable dynamic cache eviction
|
|
107
|
-
* When user scrolls past cached
|
|
108
|
-
* Prevents user from rewatching same
|
|
417
|
+
* When user scrolls past cached items, remove them from cache
|
|
418
|
+
* Prevents user from rewatching same items on reload
|
|
109
419
|
* @default true
|
|
110
420
|
*/
|
|
111
421
|
enableDynamicEviction: boolean;
|
|
@@ -122,7 +432,7 @@ interface PrefetchCacheConfig {
|
|
|
122
432
|
declare const DEFAULT_PREFETCH_CACHE_CONFIG: PrefetchCacheConfig;
|
|
123
433
|
|
|
124
434
|
/**
|
|
125
|
-
* FeedManager - Manages
|
|
435
|
+
* FeedManager - Manages content feed data with zustand/vanilla store
|
|
126
436
|
*
|
|
127
437
|
* Features:
|
|
128
438
|
* - Data normalization (Map for O(1) lookup + ordered IDs)
|
|
@@ -148,7 +458,8 @@ declare const DEFAULT_PREFETCH_CACHE_CONFIG: PrefetchCacheConfig;
|
|
|
148
458
|
* ```
|
|
149
459
|
*/
|
|
150
460
|
declare class FeedManager {
|
|
151
|
-
private
|
|
461
|
+
private dataSource;
|
|
462
|
+
private readonly logger?;
|
|
152
463
|
/** Zustand vanilla store - Single Source of Truth */
|
|
153
464
|
readonly store: StoreApi<FeedState>;
|
|
154
465
|
/** Resolved configuration */
|
|
@@ -169,7 +480,18 @@ declare class FeedManager {
|
|
|
169
480
|
* Used for garbage collection
|
|
170
481
|
*/
|
|
171
482
|
private accessOrder;
|
|
172
|
-
|
|
483
|
+
/**
|
|
484
|
+
* Track items that have already triggered predictive preload
|
|
485
|
+
* to avoid duplicate requests.
|
|
486
|
+
*/
|
|
487
|
+
private preloadedItemIds;
|
|
488
|
+
/**
|
|
489
|
+
* Recommend feed snapshot — preserved when switching to playlist mode.
|
|
490
|
+
* Stored here (not in React refs) because FeedManager is a singleton
|
|
491
|
+
* that survives component remounts caused by conditional rendering.
|
|
492
|
+
*/
|
|
493
|
+
private recommendSnapshot;
|
|
494
|
+
constructor(dataSource: IDataSource, config?: FeedConfig, storage?: IStorage, prefetchConfig?: Partial<PrefetchCacheConfig>, logger?: ILogger | undefined);
|
|
173
495
|
/** Static memory cache for explicit prefetching */
|
|
174
496
|
private static globalMemoryCache;
|
|
175
497
|
/**
|
|
@@ -189,6 +511,21 @@ declare class FeedManager {
|
|
|
189
511
|
* Clear prefetch cache
|
|
190
512
|
*/
|
|
191
513
|
static clearPrefetchCache(): void;
|
|
514
|
+
/**
|
|
515
|
+
* Get current data source
|
|
516
|
+
*/
|
|
517
|
+
getDataSource(): IDataSource;
|
|
518
|
+
/**
|
|
519
|
+
* Update data source dynamically
|
|
520
|
+
* Used for switching between Recommendation and Playlist modes
|
|
521
|
+
*
|
|
522
|
+
* @param dataSource - New data source adapter
|
|
523
|
+
* @param options - Options for state transition
|
|
524
|
+
*/
|
|
525
|
+
setDataSource(dataSource: IDataSource, options?: {
|
|
526
|
+
/** Whether to reset the feed state immediately */
|
|
527
|
+
reset?: boolean;
|
|
528
|
+
}): void;
|
|
192
529
|
/**
|
|
193
530
|
* Load initial feed data
|
|
194
531
|
*
|
|
@@ -201,7 +538,9 @@ declare class FeedManager {
|
|
|
201
538
|
* - If a request for the same cursor is already in-flight, returns the existing Promise
|
|
202
539
|
* - Prevents duplicate API calls from rapid UI interactions
|
|
203
540
|
*/
|
|
204
|
-
loadInitial(
|
|
541
|
+
loadInitial(options?: {
|
|
542
|
+
replace?: boolean;
|
|
543
|
+
}): Promise<void>;
|
|
205
544
|
/**
|
|
206
545
|
* Internal: Execute load initial logic
|
|
207
546
|
*/
|
|
@@ -226,18 +565,59 @@ declare class FeedManager {
|
|
|
226
565
|
*/
|
|
227
566
|
revalidate(): Promise<void>;
|
|
228
567
|
/**
|
|
229
|
-
*
|
|
230
|
-
*
|
|
568
|
+
* Handle playback progress and trigger predictive preloading
|
|
569
|
+
*
|
|
570
|
+
* @param itemId - ID of the currently playing item
|
|
571
|
+
* @param progress - Current playback progress (0-1)
|
|
572
|
+
* @param governor - Resource governor to trigger preload
|
|
573
|
+
* @param threshold - Progress threshold to trigger preload (default: 0.2)
|
|
574
|
+
*/
|
|
575
|
+
handlePlaybackProgress(itemId: string, progress: number, governor: ResourceGovernor, threshold?: number): void;
|
|
576
|
+
getItem(id: string): ContentItem | undefined;
|
|
577
|
+
/**
|
|
578
|
+
* Get ordered list of items
|
|
579
|
+
*/
|
|
580
|
+
getItems(): ContentItem[];
|
|
581
|
+
/**
|
|
582
|
+
* Update an item in the feed (for optimistic updates)
|
|
583
|
+
*/
|
|
584
|
+
updateItem(id: string, updates: Partial<ContentItem>): void;
|
|
585
|
+
/**
|
|
586
|
+
* Replace all items in the feed (e.g. for Playlist synchronization)
|
|
587
|
+
*/
|
|
588
|
+
replaceItems(items: ContentItem[]): void;
|
|
589
|
+
/**
|
|
590
|
+
* @deprecated Use getItem instead
|
|
231
591
|
*/
|
|
232
592
|
getVideo(id: string): VideoItem | undefined;
|
|
233
593
|
/**
|
|
234
|
-
*
|
|
594
|
+
* @deprecated Use getItems instead
|
|
235
595
|
*/
|
|
236
596
|
getVideos(): VideoItem[];
|
|
237
597
|
/**
|
|
238
|
-
*
|
|
598
|
+
* @deprecated Use updateItem instead
|
|
239
599
|
*/
|
|
240
600
|
updateVideo(id: string, updates: Partial<VideoItem>): void;
|
|
601
|
+
/**
|
|
602
|
+
* Remove an item from the feed
|
|
603
|
+
*
|
|
604
|
+
* Used for:
|
|
605
|
+
* - Report: Remove reported content from feed
|
|
606
|
+
* - Not Interested: Remove content user doesn't want to see
|
|
607
|
+
*
|
|
608
|
+
* @param id - Content ID to remove
|
|
609
|
+
* @returns true if item was removed, false if not found
|
|
610
|
+
*
|
|
611
|
+
* @example
|
|
612
|
+
* ```typescript
|
|
613
|
+
* // User reports a content item
|
|
614
|
+
* const wasRemoved = feedManager.removeItem(itemId);
|
|
615
|
+
* if (wasRemoved) {
|
|
616
|
+
* // Navigate to next item
|
|
617
|
+
* }
|
|
618
|
+
* ```
|
|
619
|
+
*/
|
|
620
|
+
removeItem(id: string): boolean;
|
|
241
621
|
/**
|
|
242
622
|
* Check if data is stale and needs revalidation
|
|
243
623
|
*/
|
|
@@ -260,20 +640,37 @@ declare class FeedManager {
|
|
|
260
640
|
* Used by LifecycleManager to restore state without API call.
|
|
261
641
|
* This bypasses normal data flow for state restoration.
|
|
262
642
|
*
|
|
263
|
-
* @param items -
|
|
643
|
+
* @param items - Content items from snapshot
|
|
264
644
|
* @param cursor - Pagination cursor from snapshot
|
|
265
645
|
* @param options - Additional hydration options
|
|
266
646
|
*/
|
|
267
|
-
hydrateFromSnapshot(items:
|
|
647
|
+
hydrateFromSnapshot(items: ContentItem[], cursor: string | null, options?: {
|
|
268
648
|
/** Whether to mark data as stale for background revalidation */
|
|
269
649
|
markAsStale?: boolean;
|
|
270
650
|
}): void;
|
|
651
|
+
/**
|
|
652
|
+
* Save the current recommend feed state before switching to playlist mode.
|
|
653
|
+
*
|
|
654
|
+
* @param activeIndex - Current scroll position (active item index)
|
|
655
|
+
*/
|
|
656
|
+
saveRecommendSnapshot(activeIndex: number): void;
|
|
657
|
+
/**
|
|
658
|
+
* Restore the recommend feed state after exiting playlist mode.
|
|
659
|
+
* Delegates to hydrateFromSnapshot() internally to avoid duplicating hydration logic.
|
|
660
|
+
*
|
|
661
|
+
* @returns The saved activeIndex, or null if no snapshot was available
|
|
662
|
+
*/
|
|
663
|
+
restoreRecommendSnapshot(): number | null;
|
|
664
|
+
/**
|
|
665
|
+
* Check if a recommend snapshot exists (for conditional logic in hooks)
|
|
666
|
+
*/
|
|
667
|
+
hasRecommendSnapshot(): boolean;
|
|
271
668
|
/**
|
|
272
669
|
* Update prefetch cache with current feed tail
|
|
273
670
|
* Called automatically after loadInitial() and loadMore()
|
|
274
671
|
*
|
|
275
|
-
* Strategy: Cache the LAST N
|
|
276
|
-
* These are
|
|
672
|
+
* Strategy: Cache the LAST N items (tail of feed)
|
|
673
|
+
* These are items user hasn't seen yet, perfect for instant display
|
|
277
674
|
*/
|
|
278
675
|
updatePrefetchCache(): void;
|
|
279
676
|
/**
|
|
@@ -290,21 +687,37 @@ declare class FeedManager {
|
|
|
290
687
|
* Marks data as stale to trigger background revalidation
|
|
291
688
|
*/
|
|
292
689
|
hydrateFromPrefetchCache(cache: PrefetchCacheData): void;
|
|
690
|
+
/**
|
|
691
|
+
* Load prefetch cache synchronously (zero-flash optimization)
|
|
692
|
+
*
|
|
693
|
+
* Uses `storage.getSync()` for synchronous localStorage access.
|
|
694
|
+
* This allows hydrating the feed during React's synchronous render phase,
|
|
695
|
+
* preventing any flash of loading/empty state when cached data exists.
|
|
696
|
+
*
|
|
697
|
+
* Returns null if:
|
|
698
|
+
* - Prefetch cache is disabled
|
|
699
|
+
* - No storage adapter
|
|
700
|
+
* - Storage doesn't support sync reads (e.g., IndexedDB)
|
|
701
|
+
* - No cached data
|
|
702
|
+
*
|
|
703
|
+
* @returns Cached feed data or null
|
|
704
|
+
*/
|
|
705
|
+
loadPrefetchCacheSync(): PrefetchCacheData | null;
|
|
293
706
|
/**
|
|
294
707
|
* Clear prefetch cache
|
|
295
708
|
* Call when user logs out or data should be invalidated
|
|
296
709
|
*/
|
|
297
710
|
clearPrefetchCache(): Promise<void>;
|
|
298
711
|
/**
|
|
299
|
-
* Evict
|
|
712
|
+
* Evict items that user has scrolled past from prefetch cache
|
|
300
713
|
* Called when user's focusedIndex changes
|
|
301
714
|
*
|
|
302
|
-
* Strategy: Remove all
|
|
303
|
-
* This ensures user doesn't rewatch
|
|
715
|
+
* Strategy: Remove all items at or before current position
|
|
716
|
+
* This ensures user doesn't rewatch items on reload
|
|
304
717
|
*
|
|
305
|
-
* @param currentIndex - Current focused
|
|
718
|
+
* @param currentIndex - Current focused item index in feed
|
|
306
719
|
*/
|
|
307
|
-
|
|
720
|
+
evictViewedItemsFromCache(currentIndex: number): Promise<void>;
|
|
308
721
|
/**
|
|
309
722
|
* Get prefetch cache configuration (for external access)
|
|
310
723
|
*/
|
|
@@ -314,15 +727,15 @@ declare class FeedManager {
|
|
|
314
727
|
*/
|
|
315
728
|
private fetchWithRetry;
|
|
316
729
|
/**
|
|
317
|
-
* Add
|
|
730
|
+
* Add items with deduplication
|
|
318
731
|
* Triggers garbage collection if cache exceeds maxCacheSize
|
|
319
732
|
*/
|
|
320
|
-
private
|
|
733
|
+
private addItems;
|
|
321
734
|
/**
|
|
322
|
-
* Merge
|
|
323
|
-
* Updates existing
|
|
735
|
+
* Merge items (for SWR revalidation)
|
|
736
|
+
* Updates existing items, adds new ones at the beginning
|
|
324
737
|
*/
|
|
325
|
-
private
|
|
738
|
+
private mergeItems;
|
|
326
739
|
/**
|
|
327
740
|
* Handle and categorize errors
|
|
328
741
|
*/
|
|
@@ -339,9 +752,9 @@ declare class FeedManager {
|
|
|
339
752
|
* Run garbage collection using LRU (Least Recently Used) policy
|
|
340
753
|
*
|
|
341
754
|
* When cache size exceeds maxCacheSize:
|
|
342
|
-
* 1. Sort
|
|
343
|
-
* 2. Evict oldest
|
|
344
|
-
* 3. Keep
|
|
755
|
+
* 1. Sort items by last access time (oldest first)
|
|
756
|
+
* 2. Evict oldest items until cache is within limit
|
|
757
|
+
* 3. Keep items that are currently in viewport (most recent in displayOrder)
|
|
345
758
|
*
|
|
346
759
|
* @returns Number of evicted items
|
|
347
760
|
*/
|
|
@@ -863,543 +1276,244 @@ declare function isValidTransition(from: PlayerStatus, to: PlayerStatus): boolea
|
|
|
863
1276
|
/**
|
|
864
1277
|
* Check if player is in a "active" state (can receive playback commands)
|
|
865
1278
|
*/
|
|
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)
|
|
1040
|
-
*/
|
|
1041
|
-
saveSnapshot(data: {
|
|
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>;
|
|
1053
|
-
/**
|
|
1054
|
-
* Clear saved snapshot
|
|
1055
|
-
*/
|
|
1056
|
-
clearSnapshot(): Promise<void>;
|
|
1057
|
-
/**
|
|
1058
|
-
* Mark that pending data should be saved (for use with debouncing)
|
|
1059
|
-
*
|
|
1060
|
-
* @param data - Data to save when flush is called
|
|
1061
|
-
*/
|
|
1062
|
-
setPendingSave(data: Omit<SessionSnapshot, 'savedAt' | 'version'>): void;
|
|
1063
|
-
/**
|
|
1064
|
-
* Check if restorePlaybackPosition config is enabled
|
|
1065
|
-
* SDK can use this to decide whether to collect playbackTime
|
|
1066
|
-
*/
|
|
1067
|
-
isPlaybackPositionRestoreEnabled(): boolean;
|
|
1068
|
-
/**
|
|
1069
|
-
* Flush pending save (called on visibility hidden)
|
|
1070
|
-
*/
|
|
1071
|
-
flushPendingSave(): Promise<void>;
|
|
1072
|
-
/**
|
|
1073
|
-
* Handle visibility state change
|
|
1074
|
-
*/
|
|
1075
|
-
onVisibilityChange(state: DocumentVisibilityState): void;
|
|
1076
|
-
/**
|
|
1077
|
-
* Get current visibility state
|
|
1078
|
-
*/
|
|
1079
|
-
getVisibilityState(): DocumentVisibilityState;
|
|
1080
|
-
/**
|
|
1081
|
-
* Add event listener
|
|
1082
|
-
*/
|
|
1083
|
-
addEventListener(listener: LifecycleEventListener): () => void;
|
|
1084
|
-
/**
|
|
1085
|
-
* Remove event listener
|
|
1086
|
-
*/
|
|
1087
|
-
removeEventListener(listener: LifecycleEventListener): void;
|
|
1088
|
-
/**
|
|
1089
|
-
* Setup visibility change listener
|
|
1090
|
-
*/
|
|
1091
|
-
private setupVisibilityListener;
|
|
1092
|
-
/**
|
|
1093
|
-
* Validate snapshot data
|
|
1094
|
-
*/
|
|
1095
|
-
private validateSnapshot;
|
|
1096
|
-
/**
|
|
1097
|
-
* Check if snapshot is stale (needs revalidation)
|
|
1098
|
-
*/
|
|
1099
|
-
private isSnapshotStale;
|
|
1100
|
-
/**
|
|
1101
|
-
* Emit event to all listeners
|
|
1102
|
-
*/
|
|
1103
|
-
private emitEvent;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
/**
|
|
1107
|
-
* Network type for prefetch strategy
|
|
1108
|
-
* Simplified from contracts NetworkType for prefetch decisions
|
|
1109
|
-
*/
|
|
1110
|
-
type NetworkType = 'wifi' | 'cellular' | 'offline';
|
|
1279
|
+
declare function isActiveState(status: PlayerStatus): boolean;
|
|
1111
1280
|
/**
|
|
1112
|
-
*
|
|
1281
|
+
* Check if player can start playback
|
|
1113
1282
|
*/
|
|
1114
|
-
declare function
|
|
1283
|
+
declare function canPlay(status: PlayerStatus): boolean;
|
|
1115
1284
|
/**
|
|
1116
|
-
*
|
|
1285
|
+
* Check if player can pause
|
|
1117
1286
|
*/
|
|
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
|
-
}
|
|
1287
|
+
declare function canPause(status: PlayerStatus): boolean;
|
|
1136
1288
|
/**
|
|
1137
|
-
*
|
|
1289
|
+
* Check if player can seek
|
|
1138
1290
|
*/
|
|
1139
|
-
|
|
1140
|
-
|
|
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
|
-
}
|
|
1291
|
+
declare function canSeek(status: PlayerStatus): boolean;
|
|
1292
|
+
|
|
1149
1293
|
/**
|
|
1150
|
-
*
|
|
1294
|
+
* Lifecycle state for zustand store
|
|
1151
1295
|
*/
|
|
1152
|
-
interface
|
|
1153
|
-
/**
|
|
1154
|
-
|
|
1155
|
-
/**
|
|
1156
|
-
|
|
1157
|
-
/** Whether
|
|
1158
|
-
|
|
1296
|
+
interface LifecycleState {
|
|
1297
|
+
/** Whether manager is initialized */
|
|
1298
|
+
isInitialized: boolean;
|
|
1299
|
+
/** Whether there's a pending save operation */
|
|
1300
|
+
isSaving: boolean;
|
|
1301
|
+
/** Whether there's a pending restore operation */
|
|
1302
|
+
isRestoring: boolean;
|
|
1303
|
+
/** Last saved timestamp */
|
|
1304
|
+
lastSavedAt: number | null;
|
|
1305
|
+
/** Last restored timestamp */
|
|
1306
|
+
lastRestoredAt: number | null;
|
|
1307
|
+
/** Whether the restored snapshot needs revalidation */
|
|
1308
|
+
needsRevalidation: boolean;
|
|
1309
|
+
/** Current visibility state */
|
|
1310
|
+
visibilityState: DocumentVisibilityState;
|
|
1159
1311
|
}
|
|
1160
1312
|
/**
|
|
1161
|
-
*
|
|
1162
|
-
* Prevents excessive preloading when user scrolls too fast
|
|
1313
|
+
* Restore result
|
|
1163
1314
|
*/
|
|
1164
|
-
interface
|
|
1165
|
-
/**
|
|
1166
|
-
|
|
1167
|
-
/**
|
|
1168
|
-
|
|
1169
|
-
/**
|
|
1170
|
-
|
|
1315
|
+
interface RestoreResult {
|
|
1316
|
+
/** Whether restore was successful */
|
|
1317
|
+
success: boolean;
|
|
1318
|
+
/** Restored snapshot data (null if no valid snapshot) */
|
|
1319
|
+
snapshot: SessionSnapshot | null;
|
|
1320
|
+
/** Whether the restored data is stale and needs revalidation */
|
|
1321
|
+
needsRevalidation: boolean;
|
|
1322
|
+
/** Reason for failure (if any) */
|
|
1323
|
+
reason?: 'no_snapshot' | 'expired' | 'invalid' | 'error';
|
|
1324
|
+
/** Playback time in seconds (only present if restorePlaybackPosition config is enabled) */
|
|
1325
|
+
playbackTime?: number;
|
|
1326
|
+
/** Video ID that was playing (only present if restorePlaybackPosition config is enabled) */
|
|
1327
|
+
currentVideoId?: string;
|
|
1328
|
+
/** Captured video frame at playback position (base64 JPEG, only present if restorePlaybackPosition is enabled) */
|
|
1329
|
+
restoreFrame?: string;
|
|
1171
1330
|
}
|
|
1172
1331
|
/**
|
|
1173
|
-
*
|
|
1332
|
+
* LifecycleManager configuration
|
|
1174
1333
|
*/
|
|
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;
|
|
1334
|
+
interface LifecycleConfig {
|
|
1335
|
+
/** Storage adapter for persistence */
|
|
1336
|
+
storage?: ISessionStorage;
|
|
1190
1337
|
/** Logger adapter */
|
|
1191
1338
|
logger?: ILogger;
|
|
1339
|
+
/** Snapshot expiry time in ms (default: 24 hours) */
|
|
1340
|
+
snapshotExpiryMs?: number;
|
|
1341
|
+
/** Revalidation threshold in ms (default: 5 minutes) */
|
|
1342
|
+
revalidationThresholdMs?: number;
|
|
1343
|
+
/** Auto-save on visibility change (default: true) */
|
|
1344
|
+
autoSaveOnHidden?: boolean;
|
|
1345
|
+
/**
|
|
1346
|
+
* Enable restoring video playback position (default: false)
|
|
1347
|
+
* When enabled, saves and restores the exact playback time (seconds)
|
|
1348
|
+
* so video can seek() to the exact position user was watching
|
|
1349
|
+
*/
|
|
1350
|
+
restorePlaybackPosition?: boolean;
|
|
1351
|
+
/** SDK version for compatibility */
|
|
1352
|
+
version?: string;
|
|
1192
1353
|
}
|
|
1193
1354
|
/**
|
|
1194
|
-
* Default
|
|
1195
|
-
*/
|
|
1196
|
-
declare const DEFAULT_PREFETCH_CONFIG: Record<NetworkType, PrefetchConfig>;
|
|
1197
|
-
/**
|
|
1198
|
-
* Default resource configuration
|
|
1355
|
+
* Default lifecycle configuration
|
|
1199
1356
|
*/
|
|
1200
|
-
declare const
|
|
1201
|
-
prefetch: Record<NetworkType, PrefetchConfig>;
|
|
1202
|
-
scrollThrashing: ScrollThrashingConfig;
|
|
1203
|
-
};
|
|
1357
|
+
declare const DEFAULT_LIFECYCLE_CONFIG: Required<Omit<LifecycleConfig, 'storage' | 'logger'>>;
|
|
1204
1358
|
/**
|
|
1205
|
-
*
|
|
1359
|
+
* Lifecycle events for external listening
|
|
1206
1360
|
*/
|
|
1207
|
-
type
|
|
1208
|
-
type: '
|
|
1209
|
-
toMount: number[];
|
|
1210
|
-
toUnmount: number[];
|
|
1361
|
+
type LifecycleEvent = {
|
|
1362
|
+
type: 'saveStart';
|
|
1211
1363
|
} | {
|
|
1212
|
-
type: '
|
|
1213
|
-
|
|
1214
|
-
previousIndex: number;
|
|
1364
|
+
type: 'saveComplete';
|
|
1365
|
+
timestamp: number;
|
|
1215
1366
|
} | {
|
|
1216
|
-
type: '
|
|
1217
|
-
|
|
1367
|
+
type: 'saveFailed';
|
|
1368
|
+
error: Error;
|
|
1218
1369
|
} | {
|
|
1219
|
-
type: '
|
|
1220
|
-
|
|
1370
|
+
type: 'restoreStart';
|
|
1371
|
+
} | {
|
|
1372
|
+
type: 'restoreComplete';
|
|
1373
|
+
result: RestoreResult;
|
|
1374
|
+
} | {
|
|
1375
|
+
type: 'visibilityChange';
|
|
1376
|
+
state: DocumentVisibilityState;
|
|
1221
1377
|
};
|
|
1222
1378
|
/**
|
|
1223
|
-
*
|
|
1379
|
+
* Lifecycle event listener
|
|
1224
1380
|
*/
|
|
1225
|
-
type
|
|
1381
|
+
type LifecycleEventListener = (event: LifecycleEvent) => void;
|
|
1226
1382
|
|
|
1227
1383
|
/**
|
|
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
|
|
1384
|
+
* LifecycleManager - Handles session persistence and restoration
|
|
1238
1385
|
*
|
|
1239
|
-
*
|
|
1240
|
-
* -
|
|
1241
|
-
* -
|
|
1242
|
-
* -
|
|
1243
|
-
* - Event system for UI synchronization
|
|
1386
|
+
* Strategy: "State Persistence, DOM Destruction"
|
|
1387
|
+
* - Save snapshot to storage when user leaves
|
|
1388
|
+
* - Restore state from storage when user returns
|
|
1389
|
+
* - Video DOM nodes are destroyed to free RAM
|
|
1244
1390
|
*
|
|
1245
|
-
*
|
|
1246
|
-
* -
|
|
1247
|
-
* -
|
|
1248
|
-
* -
|
|
1391
|
+
* Features:
|
|
1392
|
+
* - Auto-save on visibility change (optional)
|
|
1393
|
+
* - Snapshot expiry (24 hours default)
|
|
1394
|
+
* - Stale data detection for revalidation
|
|
1395
|
+
* - Event system for UI coordination
|
|
1249
1396
|
*
|
|
1250
1397
|
* @example
|
|
1251
1398
|
* ```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
|
|
1399
|
+
* const lifecycle = new LifecycleManager({ storage, logger });
|
|
1260
1400
|
*
|
|
1261
|
-
* //
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1401
|
+
* // Initialize and attempt restore
|
|
1402
|
+
* const result = await lifecycle.initialize();
|
|
1403
|
+
* if (result.success) {
|
|
1404
|
+
* feedManager.restoreFromSnapshot(result.snapshot);
|
|
1405
|
+
* if (result.needsRevalidation) {
|
|
1406
|
+
* feedManager.revalidate();
|
|
1266
1407
|
* }
|
|
1408
|
+
* }
|
|
1409
|
+
*
|
|
1410
|
+
* // Save snapshot before leaving
|
|
1411
|
+
* lifecycle.saveSnapshot({
|
|
1412
|
+
* items: feedManager.getVideos(),
|
|
1413
|
+
* cursor: feedManager.store.getState().cursor,
|
|
1414
|
+
* focusedIndex: resourceGovernor.store.getState().focusedIndex,
|
|
1267
1415
|
* });
|
|
1268
|
-
* ```
|
|
1269
|
-
*/
|
|
1270
|
-
declare class
|
|
1271
|
-
/** Zustand vanilla store - Single Source of Truth */
|
|
1272
|
-
readonly store: StoreApi<
|
|
1273
|
-
/** Resolved configuration */
|
|
1274
|
-
private readonly config;
|
|
1275
|
-
/**
|
|
1276
|
-
private readonly
|
|
1277
|
-
/**
|
|
1278
|
-
private readonly
|
|
1279
|
-
/**
|
|
1280
|
-
private readonly
|
|
1281
|
-
/**
|
|
1282
|
-
private
|
|
1283
|
-
/**
|
|
1284
|
-
private
|
|
1285
|
-
|
|
1286
|
-
private focusDebounceTimer;
|
|
1287
|
-
/** Pending focused index (before debounce completes) */
|
|
1288
|
-
private pendingFocusedIndex;
|
|
1289
|
-
/** Network change unsubscribe function */
|
|
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>;
|
|
1416
|
+
* ```
|
|
1417
|
+
*/
|
|
1418
|
+
declare class LifecycleManager {
|
|
1419
|
+
/** Zustand vanilla store - Single Source of Truth */
|
|
1420
|
+
readonly store: StoreApi<LifecycleState>;
|
|
1421
|
+
/** Resolved configuration */
|
|
1422
|
+
private readonly config;
|
|
1423
|
+
/** Storage adapter */
|
|
1424
|
+
private readonly storage?;
|
|
1425
|
+
/** Logger adapter */
|
|
1426
|
+
private readonly logger?;
|
|
1427
|
+
/** Event listeners */
|
|
1428
|
+
private readonly eventListeners;
|
|
1429
|
+
/** Visibility change handler reference (for cleanup) */
|
|
1430
|
+
private visibilityHandler?;
|
|
1431
|
+
/** Pending save data (for debouncing) */
|
|
1432
|
+
private pendingSaveData?;
|
|
1433
|
+
constructor(config?: LifecycleConfig);
|
|
1303
1434
|
/**
|
|
1304
|
-
*
|
|
1305
|
-
* Stops network monitoring and clears allocations
|
|
1435
|
+
* Initialize lifecycle manager and attempt to restore session
|
|
1306
1436
|
*/
|
|
1307
|
-
|
|
1437
|
+
initialize(): Promise<RestoreResult>;
|
|
1308
1438
|
/**
|
|
1309
|
-
* Destroy
|
|
1439
|
+
* Destroy lifecycle manager and cleanup
|
|
1310
1440
|
*/
|
|
1311
1441
|
destroy(): void;
|
|
1312
1442
|
/**
|
|
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
|
|
1443
|
+
* Restore session from storage
|
|
1333
1444
|
*/
|
|
1334
|
-
|
|
1445
|
+
restoreSession(): Promise<RestoreResult>;
|
|
1335
1446
|
/**
|
|
1336
|
-
*
|
|
1447
|
+
* Save session snapshot to storage
|
|
1448
|
+
*
|
|
1449
|
+
* @param data - Snapshot data to save
|
|
1450
|
+
* @param data.playbackTime - Current video playback position (only saved if restorePlaybackPosition config is enabled)
|
|
1451
|
+
* @param data.currentVideoId - Current video ID (only saved if restorePlaybackPosition config is enabled)
|
|
1452
|
+
* @param data.restoreFrame - Captured video frame at playback position (only saved if restorePlaybackPosition is enabled)
|
|
1337
1453
|
*/
|
|
1338
|
-
|
|
1454
|
+
saveSnapshot(data: {
|
|
1455
|
+
items: ContentItem[];
|
|
1456
|
+
cursor: string | null;
|
|
1457
|
+
focusedIndex: number;
|
|
1458
|
+
scrollPosition?: number;
|
|
1459
|
+
/** Current video playback time in seconds (only used when restorePlaybackPosition is enabled) */
|
|
1460
|
+
playbackTime?: number;
|
|
1461
|
+
/** Current video ID (only used when restorePlaybackPosition is enabled) */
|
|
1462
|
+
currentVideoId?: string;
|
|
1463
|
+
/** Captured video frame at playback position (only used when restorePlaybackPosition is enabled) */
|
|
1464
|
+
restoreFrame?: string;
|
|
1465
|
+
}): Promise<boolean>;
|
|
1339
1466
|
/**
|
|
1340
|
-
*
|
|
1467
|
+
* Clear saved snapshot
|
|
1341
1468
|
*/
|
|
1342
|
-
|
|
1469
|
+
clearSnapshot(): Promise<void>;
|
|
1343
1470
|
/**
|
|
1344
|
-
*
|
|
1471
|
+
* Mark that pending data should be saved (for use with debouncing)
|
|
1472
|
+
*
|
|
1473
|
+
* @param data - Data to save when flush is called
|
|
1345
1474
|
*/
|
|
1346
|
-
|
|
1475
|
+
setPendingSave(data: Omit<SessionSnapshot, 'savedAt' | 'version'>): void;
|
|
1347
1476
|
/**
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
*
|
|
1351
|
-
* @param getter - Function that returns video info for a given index
|
|
1477
|
+
* Check if restorePlaybackPosition config is enabled
|
|
1478
|
+
* SDK can use this to decide whether to collect playbackTime
|
|
1352
1479
|
*/
|
|
1353
|
-
|
|
1480
|
+
isPlaybackPositionRestoreEnabled(): boolean;
|
|
1354
1481
|
/**
|
|
1355
|
-
*
|
|
1482
|
+
* Flush pending save (called on visibility hidden)
|
|
1356
1483
|
*/
|
|
1357
|
-
|
|
1484
|
+
flushPendingSave(): Promise<void>;
|
|
1358
1485
|
/**
|
|
1359
|
-
*
|
|
1486
|
+
* Handle visibility state change
|
|
1360
1487
|
*/
|
|
1361
|
-
|
|
1488
|
+
onVisibilityChange(state: DocumentVisibilityState): void;
|
|
1362
1489
|
/**
|
|
1363
|
-
*
|
|
1364
|
-
* Respects throttling and network conditions
|
|
1490
|
+
* Get current visibility state
|
|
1365
1491
|
*/
|
|
1366
|
-
|
|
1492
|
+
getVisibilityState(): DocumentVisibilityState;
|
|
1367
1493
|
/**
|
|
1368
1494
|
* Add event listener
|
|
1369
1495
|
*/
|
|
1370
|
-
addEventListener(listener:
|
|
1496
|
+
addEventListener(listener: LifecycleEventListener): () => void;
|
|
1371
1497
|
/**
|
|
1372
1498
|
* Remove event listener
|
|
1373
1499
|
*/
|
|
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;
|
|
1500
|
+
removeEventListener(listener: LifecycleEventListener): void;
|
|
1387
1501
|
/**
|
|
1388
|
-
*
|
|
1502
|
+
* Setup visibility change listener
|
|
1389
1503
|
*/
|
|
1390
|
-
private
|
|
1504
|
+
private setupVisibilityListener;
|
|
1391
1505
|
/**
|
|
1392
|
-
*
|
|
1506
|
+
* Validate snapshot data
|
|
1393
1507
|
*/
|
|
1394
|
-
private
|
|
1508
|
+
private validateSnapshot;
|
|
1395
1509
|
/**
|
|
1396
|
-
*
|
|
1510
|
+
* Check if snapshot is stale (needs revalidation)
|
|
1397
1511
|
*/
|
|
1398
|
-
private
|
|
1512
|
+
private isSnapshotStale;
|
|
1399
1513
|
/**
|
|
1400
|
-
*
|
|
1514
|
+
* Create session snapshot from data
|
|
1401
1515
|
*/
|
|
1402
|
-
private
|
|
1516
|
+
private createSnapshot;
|
|
1403
1517
|
/**
|
|
1404
1518
|
* Emit event to all listeners
|
|
1405
1519
|
*/
|
|
@@ -1529,33 +1643,6 @@ type OptimisticEventListener = (event: OptimisticEvent) => void;
|
|
|
1529
1643
|
|
|
1530
1644
|
/**
|
|
1531
1645
|
* OptimisticManager - Handles optimistic UI updates with rollback
|
|
1532
|
-
*
|
|
1533
|
-
* Pattern:
|
|
1534
|
-
* 1. User action (like) -> Update UI immediately
|
|
1535
|
-
* 2. Call API in background
|
|
1536
|
-
* 3. On success -> Keep UI state, remove pending action
|
|
1537
|
-
* 4. On error -> Rollback to previous state
|
|
1538
|
-
*
|
|
1539
|
-
* Features:
|
|
1540
|
-
* - Immediate UI feedback
|
|
1541
|
-
* - Automatic rollback on failure
|
|
1542
|
-
* - Retry queue for failed actions
|
|
1543
|
-
* - Prevents duplicate pending actions
|
|
1544
|
-
*
|
|
1545
|
-
* @example
|
|
1546
|
-
* ```typescript
|
|
1547
|
-
* const optimistic = new OptimisticManager({
|
|
1548
|
-
* interaction,
|
|
1549
|
-
* feedManager,
|
|
1550
|
-
* logger,
|
|
1551
|
-
* });
|
|
1552
|
-
*
|
|
1553
|
-
* // User taps like button
|
|
1554
|
-
* await optimistic.like(videoId);
|
|
1555
|
-
*
|
|
1556
|
-
* // User taps follow button
|
|
1557
|
-
* await optimistic.follow(videoId);
|
|
1558
|
-
* ```
|
|
1559
1646
|
*/
|
|
1560
1647
|
declare class OptimisticManager {
|
|
1561
1648
|
/** Zustand vanilla store - Single Source of Truth */
|
|
@@ -1564,7 +1651,7 @@ declare class OptimisticManager {
|
|
|
1564
1651
|
private readonly config;
|
|
1565
1652
|
/** Interaction adapter */
|
|
1566
1653
|
private readonly interaction?;
|
|
1567
|
-
/** Feed manager for updating
|
|
1654
|
+
/** Feed manager for updating item state */
|
|
1568
1655
|
private readonly feedManager?;
|
|
1569
1656
|
/** Logger adapter */
|
|
1570
1657
|
private readonly logger?;
|
|
@@ -1572,137 +1659,65 @@ declare class OptimisticManager {
|
|
|
1572
1659
|
private readonly eventListeners;
|
|
1573
1660
|
/** Retry timer */
|
|
1574
1661
|
private retryTimer;
|
|
1575
|
-
/** Debounce timers for like/unlike per
|
|
1662
|
+
/** Debounce timers for like/unlike per item */
|
|
1576
1663
|
private readonly likeDebounceTimers;
|
|
1664
|
+
/** Intended like state while debouncing (itemId -> isLiked) */
|
|
1665
|
+
private readonly intendedLikeState;
|
|
1577
1666
|
/** Debounce delay in ms */
|
|
1578
1667
|
private readonly debounceDelay;
|
|
1579
1668
|
constructor(config?: OptimisticConfig);
|
|
1580
1669
|
/**
|
|
1581
|
-
* Like
|
|
1670
|
+
* Like an item with optimistic update
|
|
1582
1671
|
*/
|
|
1583
|
-
like(
|
|
1672
|
+
like(itemId: string): Promise<boolean>;
|
|
1584
1673
|
/**
|
|
1585
|
-
* Unlike
|
|
1674
|
+
* Unlike an item with optimistic update
|
|
1586
1675
|
*/
|
|
1587
|
-
unlike(
|
|
1676
|
+
unlike(itemId: string): Promise<boolean>;
|
|
1588
1677
|
/**
|
|
1589
|
-
* Toggle like state with DEBOUNCE
|
|
1590
|
-
*
|
|
1591
|
-
* This method:
|
|
1592
|
-
* 1. Updates UI immediately (optimistic)
|
|
1593
|
-
* 2. Debounces API call - only sends after user stops clicking
|
|
1594
|
-
* 3. Sends final state to API after debounce delay
|
|
1595
|
-
*
|
|
1596
|
-
* Perfect for rapid tapping like TikTok/Instagram behavior.
|
|
1678
|
+
* Toggle like state with DEBOUNCE
|
|
1597
1679
|
*/
|
|
1598
|
-
toggleLike(
|
|
1680
|
+
toggleLike(itemId: string): void;
|
|
1599
1681
|
/**
|
|
1600
|
-
* Execute
|
|
1601
|
-
* Reads current state from FeedManager to get final intended state
|
|
1682
|
+
* Execute API call after debounce delay
|
|
1602
1683
|
*/
|
|
1603
1684
|
private executeDebouncedLikeApi;
|
|
1604
1685
|
/**
|
|
1605
|
-
*
|
|
1606
|
-
* Legacy toggle that waits for API response
|
|
1686
|
+
* Handle errors from debounced API calls
|
|
1607
1687
|
*/
|
|
1608
|
-
|
|
1688
|
+
private handleDebouncedApiError;
|
|
1609
1689
|
/**
|
|
1610
|
-
*
|
|
1690
|
+
* Remove a pending action by ID
|
|
1611
1691
|
*/
|
|
1612
|
-
|
|
1613
|
-
/**
|
|
1614
|
-
* Unfollow a video author with optimistic update
|
|
1615
|
-
*/
|
|
1616
|
-
unfollow(videoId: string): Promise<boolean>;
|
|
1692
|
+
private removePendingAction;
|
|
1617
1693
|
/**
|
|
1618
1694
|
* Toggle follow state
|
|
1619
1695
|
*/
|
|
1620
|
-
toggleFollow(
|
|
1696
|
+
toggleFollow(itemId: string): Promise<boolean>;
|
|
1697
|
+
follow(itemId: string): Promise<boolean>;
|
|
1698
|
+
unfollow(itemId: string): Promise<boolean>;
|
|
1699
|
+
addEventListener(listener: OptimisticEventListener): () => void;
|
|
1700
|
+
removeEventListener(listener: OptimisticEventListener): void;
|
|
1621
1701
|
/**
|
|
1622
|
-
*
|
|
1702
|
+
* Reset all optimistic state
|
|
1623
1703
|
*/
|
|
1704
|
+
reset(): void;
|
|
1624
1705
|
getPendingActions(): PendingAction[];
|
|
1625
|
-
|
|
1626
|
-
* Check if there's a pending action for a video
|
|
1627
|
-
* Only returns true for actions with status 'pending' (not 'failed')
|
|
1628
|
-
*/
|
|
1629
|
-
hasPendingAction(videoId: string, type?: ActionType): boolean;
|
|
1630
|
-
/**
|
|
1631
|
-
* Get failed actions queue
|
|
1632
|
-
*/
|
|
1706
|
+
hasPendingAction(itemId: string, type?: ActionType): boolean;
|
|
1633
1707
|
getFailedQueue(): PendingAction[];
|
|
1634
|
-
/**
|
|
1635
|
-
* Manually retry failed actions
|
|
1636
|
-
*/
|
|
1637
1708
|
retryFailed(): Promise<void>;
|
|
1638
|
-
/**
|
|
1639
|
-
* Clear all failed actions
|
|
1640
|
-
*/
|
|
1641
1709
|
clearFailed(): void;
|
|
1642
|
-
/**
|
|
1643
|
-
* Reset manager state
|
|
1644
|
-
*/
|
|
1645
|
-
reset(): void;
|
|
1646
|
-
/**
|
|
1647
|
-
* Destroy manager and cleanup
|
|
1648
|
-
*/
|
|
1649
1710
|
destroy(): void;
|
|
1650
|
-
|
|
1651
|
-
* Add event listener
|
|
1652
|
-
*/
|
|
1653
|
-
addEventListener(listener: OptimisticEventListener): () => void;
|
|
1654
|
-
/**
|
|
1655
|
-
* Remove event listener
|
|
1656
|
-
*/
|
|
1657
|
-
removeEventListener(listener: OptimisticEventListener): void;
|
|
1658
|
-
/**
|
|
1659
|
-
* Perform an optimistic action
|
|
1660
|
-
*/
|
|
1711
|
+
private emit;
|
|
1661
1712
|
private performAction;
|
|
1662
|
-
/**
|
|
1663
|
-
* Create rollback data based on action type
|
|
1664
|
-
*/
|
|
1665
1713
|
private createRollbackData;
|
|
1666
|
-
/**
|
|
1667
|
-
* Apply optimistic update to feed
|
|
1668
|
-
*/
|
|
1669
1714
|
private applyOptimisticUpdate;
|
|
1670
|
-
/**
|
|
1671
|
-
* Apply rollback
|
|
1672
|
-
*/
|
|
1673
1715
|
private applyRollback;
|
|
1674
|
-
/**
|
|
1675
|
-
* Add pending action to store
|
|
1676
|
-
*/
|
|
1677
1716
|
private addPendingAction;
|
|
1678
|
-
/**
|
|
1679
|
-
* Mark action as success
|
|
1680
|
-
*/
|
|
1681
1717
|
private markActionSuccess;
|
|
1682
|
-
/**
|
|
1683
|
-
* Mark action as failed
|
|
1684
|
-
*/
|
|
1685
1718
|
private markActionFailed;
|
|
1686
|
-
/**
|
|
1687
|
-
* Retry a failed action
|
|
1688
|
-
*/
|
|
1689
1719
|
private retryAction;
|
|
1690
|
-
/**
|
|
1691
|
-
* Execute API call based on action type
|
|
1692
|
-
*/
|
|
1693
|
-
private executeApiCall;
|
|
1694
|
-
/**
|
|
1695
|
-
* Schedule retry of failed actions
|
|
1696
|
-
*/
|
|
1697
1720
|
private scheduleRetry;
|
|
1698
|
-
/**
|
|
1699
|
-
* Cancel retry timer
|
|
1700
|
-
*/
|
|
1701
|
-
private cancelRetryTimer;
|
|
1702
|
-
/**
|
|
1703
|
-
* Emit event to all listeners
|
|
1704
|
-
*/
|
|
1705
|
-
private emitEvent;
|
|
1706
1721
|
}
|
|
1707
1722
|
|
|
1708
1723
|
/**
|
|
@@ -1939,4 +1954,152 @@ declare class CommentManager {
|
|
|
1939
1954
|
private createError;
|
|
1940
1955
|
}
|
|
1941
1956
|
|
|
1942
|
-
|
|
1957
|
+
/**
|
|
1958
|
+
* Playlist state for zustand store
|
|
1959
|
+
*/
|
|
1960
|
+
interface PlaylistState {
|
|
1961
|
+
/** Currently loaded playlist metadata */
|
|
1962
|
+
playlist: PlaylistData | null;
|
|
1963
|
+
/** Current active item index */
|
|
1964
|
+
currentIndex: number;
|
|
1965
|
+
/** List of items in the playlist (shortcut to playlist.items) */
|
|
1966
|
+
items: ContentItem[];
|
|
1967
|
+
/** Loading state */
|
|
1968
|
+
loading: boolean;
|
|
1969
|
+
/** Error state */
|
|
1970
|
+
error: Error | null;
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Configuration for PlaylistManager
|
|
1974
|
+
*/
|
|
1975
|
+
interface PlaylistConfig {
|
|
1976
|
+
/**
|
|
1977
|
+
* Number of items to keep full metadata for around the current index
|
|
1978
|
+
* @default 10
|
|
1979
|
+
*/
|
|
1980
|
+
metadataWindowSize?: number;
|
|
1981
|
+
}
|
|
1982
|
+
/**
|
|
1983
|
+
* Actions for PlaylistManager
|
|
1984
|
+
*/
|
|
1985
|
+
interface PlaylistActions {
|
|
1986
|
+
/** Load a playlist by ID */
|
|
1987
|
+
loadPlaylist: (id: string) => Promise<void>;
|
|
1988
|
+
/** Set a playlist directly */
|
|
1989
|
+
setPlaylist: (playlist: PlaylistData) => void;
|
|
1990
|
+
/** Navigate to next item */
|
|
1991
|
+
next: () => void;
|
|
1992
|
+
/** Navigate to previous item */
|
|
1993
|
+
prev: () => void;
|
|
1994
|
+
/** Jump to specific index */
|
|
1995
|
+
jumpTo: (index: number) => void;
|
|
1996
|
+
/** Reset state */
|
|
1997
|
+
reset: () => void;
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* State for PlaylistCollectionManager
|
|
2001
|
+
*/
|
|
2002
|
+
interface PlaylistCollectionState {
|
|
2003
|
+
/** Array of playlist summaries */
|
|
2004
|
+
playlists: PlaylistSummary[];
|
|
2005
|
+
/** Whether loading is in progress */
|
|
2006
|
+
loading: boolean;
|
|
2007
|
+
/** Current pagination cursor */
|
|
2008
|
+
cursor: string | null;
|
|
2009
|
+
/** Whether more items can be loaded */
|
|
2010
|
+
hasMore: boolean;
|
|
2011
|
+
/** Error state */
|
|
2012
|
+
error: Error | null;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
/**
|
|
2016
|
+
* PlaylistManager - Manages playlist state and navigation
|
|
2017
|
+
*
|
|
2018
|
+
* Features:
|
|
2019
|
+
* - Tracks current playlist and active index
|
|
2020
|
+
* - Provides navigation actions (next, prev, jumpTo)
|
|
2021
|
+
* - Uses vanilla Zustand store for state management
|
|
2022
|
+
*/
|
|
2023
|
+
declare class PlaylistManager {
|
|
2024
|
+
private readonly dataSource?;
|
|
2025
|
+
private readonly governor?;
|
|
2026
|
+
/** Zustand vanilla store */
|
|
2027
|
+
readonly store: StoreApi<PlaylistState>;
|
|
2028
|
+
/** Resolved configuration */
|
|
2029
|
+
private readonly config;
|
|
2030
|
+
/**
|
|
2031
|
+
* Internal cache of full metadata items.
|
|
2032
|
+
* Items in store.items may be minified to save memory.
|
|
2033
|
+
*/
|
|
2034
|
+
private fullMetadataItems;
|
|
2035
|
+
/** Unsubscribe from governor events */
|
|
2036
|
+
private governorUnsubscribe?;
|
|
2037
|
+
constructor(dataSource?: IPlaylistDataSource | undefined, config?: PlaylistConfig, governor?: ResourceGovernor | undefined);
|
|
2038
|
+
/**
|
|
2039
|
+
* Load a playlist by ID
|
|
2040
|
+
*/
|
|
2041
|
+
loadPlaylist(id: string): Promise<void>;
|
|
2042
|
+
/**
|
|
2043
|
+
* Set playlist data directly
|
|
2044
|
+
*/
|
|
2045
|
+
setPlaylist(playlist: PlaylistData): void;
|
|
2046
|
+
/**
|
|
2047
|
+
* Navigate to next item
|
|
2048
|
+
*/
|
|
2049
|
+
next(): void;
|
|
2050
|
+
/**
|
|
2051
|
+
* Navigate to previous item
|
|
2052
|
+
*/
|
|
2053
|
+
prev(): void;
|
|
2054
|
+
/**
|
|
2055
|
+
* Jump to specific index
|
|
2056
|
+
*/
|
|
2057
|
+
jumpTo(index: number): void;
|
|
2058
|
+
/**
|
|
2059
|
+
* Reset state
|
|
2060
|
+
*/
|
|
2061
|
+
reset(): void;
|
|
2062
|
+
/**
|
|
2063
|
+
* Destroy the manager
|
|
2064
|
+
*/
|
|
2065
|
+
destroy(): void;
|
|
2066
|
+
/**
|
|
2067
|
+
* Update the sliding window of full metadata items.
|
|
2068
|
+
* Items outside the window are minified to save memory.
|
|
2069
|
+
*/
|
|
2070
|
+
private updateMetadataWindow;
|
|
2071
|
+
/**
|
|
2072
|
+
* Create a minified version of a ContentItem to save memory.
|
|
2073
|
+
* Keeps only essential fields for identification and basic UI.
|
|
2074
|
+
*/
|
|
2075
|
+
private minifyItem;
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
/**
|
|
2079
|
+
* PlaylistCollectionManager - Manages the discovery of playlists.
|
|
2080
|
+
*
|
|
2081
|
+
* Responsibilities:
|
|
2082
|
+
* - Fetching playlist summaries from DataSource
|
|
2083
|
+
* - Handling pagination and cursor management
|
|
2084
|
+
* - Maintaining loading and error states
|
|
2085
|
+
*/
|
|
2086
|
+
declare class PlaylistCollectionManager {
|
|
2087
|
+
private readonly dataSource?;
|
|
2088
|
+
/** Zustand vanilla store */
|
|
2089
|
+
readonly store: StoreApi<PlaylistCollectionState>;
|
|
2090
|
+
constructor(dataSource?: IPlaylistDataSource | undefined);
|
|
2091
|
+
/**
|
|
2092
|
+
* Load more playlists (pagination)
|
|
2093
|
+
*/
|
|
2094
|
+
loadMore(): Promise<void>;
|
|
2095
|
+
/**
|
|
2096
|
+
* Refresh the collection (reset and re-fetch)
|
|
2097
|
+
*/
|
|
2098
|
+
refresh(): Promise<void>;
|
|
2099
|
+
/**
|
|
2100
|
+
* Reset the store to initial state
|
|
2101
|
+
*/
|
|
2102
|
+
reset(): void;
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
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 };
|