@xhub-short/core 0.1.0-beta.2 → 0.1.0-beta.20
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 +817 -629
- package/dist/index.js +867 -463
- 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,52 @@ 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);
|
|
495
|
+
/** Static memory cache for explicit prefetching */
|
|
496
|
+
private static globalMemoryCache;
|
|
497
|
+
/**
|
|
498
|
+
* Prefetch feed data and store in global memory cache
|
|
499
|
+
*
|
|
500
|
+
* @param dataSource - Data source to fetch from
|
|
501
|
+
* @param options - Prefetch options
|
|
502
|
+
*/
|
|
503
|
+
static prefetch(dataSource: IDataSource, options?: {
|
|
504
|
+
ttl?: number;
|
|
505
|
+
}): Promise<void>;
|
|
506
|
+
/**
|
|
507
|
+
* Check if prefetch cache exists and is valid
|
|
508
|
+
*/
|
|
509
|
+
static hasPrefetchCache(): boolean;
|
|
510
|
+
/**
|
|
511
|
+
* Clear prefetch cache
|
|
512
|
+
*/
|
|
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;
|
|
173
529
|
/**
|
|
174
530
|
* Load initial feed data
|
|
175
531
|
*
|
|
@@ -182,7 +538,9 @@ declare class FeedManager {
|
|
|
182
538
|
* - If a request for the same cursor is already in-flight, returns the existing Promise
|
|
183
539
|
* - Prevents duplicate API calls from rapid UI interactions
|
|
184
540
|
*/
|
|
185
|
-
loadInitial(
|
|
541
|
+
loadInitial(options?: {
|
|
542
|
+
replace?: boolean;
|
|
543
|
+
}): Promise<void>;
|
|
186
544
|
/**
|
|
187
545
|
* Internal: Execute load initial logic
|
|
188
546
|
*/
|
|
@@ -207,18 +565,59 @@ declare class FeedManager {
|
|
|
207
565
|
*/
|
|
208
566
|
revalidate(): Promise<void>;
|
|
209
567
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
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
|
|
212
591
|
*/
|
|
213
592
|
getVideo(id: string): VideoItem | undefined;
|
|
214
593
|
/**
|
|
215
|
-
*
|
|
594
|
+
* @deprecated Use getItems instead
|
|
216
595
|
*/
|
|
217
596
|
getVideos(): VideoItem[];
|
|
218
597
|
/**
|
|
219
|
-
*
|
|
598
|
+
* @deprecated Use updateItem instead
|
|
220
599
|
*/
|
|
221
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;
|
|
222
621
|
/**
|
|
223
622
|
* Check if data is stale and needs revalidation
|
|
224
623
|
*/
|
|
@@ -241,20 +640,37 @@ declare class FeedManager {
|
|
|
241
640
|
* Used by LifecycleManager to restore state without API call.
|
|
242
641
|
* This bypasses normal data flow for state restoration.
|
|
243
642
|
*
|
|
244
|
-
* @param items -
|
|
643
|
+
* @param items - Content items from snapshot
|
|
245
644
|
* @param cursor - Pagination cursor from snapshot
|
|
246
645
|
* @param options - Additional hydration options
|
|
247
646
|
*/
|
|
248
|
-
hydrateFromSnapshot(items:
|
|
249
|
-
/** Whether to mark data as stale for background revalidation */
|
|
250
|
-
markAsStale?: boolean;
|
|
251
|
-
}): void;
|
|
647
|
+
hydrateFromSnapshot(items: ContentItem[], cursor: string | null, options?: {
|
|
648
|
+
/** Whether to mark data as stale for background revalidation */
|
|
649
|
+
markAsStale?: boolean;
|
|
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;
|
|
252
668
|
/**
|
|
253
669
|
* Update prefetch cache with current feed tail
|
|
254
670
|
* Called automatically after loadInitial() and loadMore()
|
|
255
671
|
*
|
|
256
|
-
* Strategy: Cache the LAST N
|
|
257
|
-
* 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
|
|
258
674
|
*/
|
|
259
675
|
updatePrefetchCache(): void;
|
|
260
676
|
/**
|
|
@@ -271,21 +687,37 @@ declare class FeedManager {
|
|
|
271
687
|
* Marks data as stale to trigger background revalidation
|
|
272
688
|
*/
|
|
273
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;
|
|
274
706
|
/**
|
|
275
707
|
* Clear prefetch cache
|
|
276
708
|
* Call when user logs out or data should be invalidated
|
|
277
709
|
*/
|
|
278
710
|
clearPrefetchCache(): Promise<void>;
|
|
279
711
|
/**
|
|
280
|
-
* Evict
|
|
712
|
+
* Evict items that user has scrolled past from prefetch cache
|
|
281
713
|
* Called when user's focusedIndex changes
|
|
282
714
|
*
|
|
283
|
-
* Strategy: Remove all
|
|
284
|
-
* 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
|
|
285
717
|
*
|
|
286
|
-
* @param currentIndex - Current focused
|
|
718
|
+
* @param currentIndex - Current focused item index in feed
|
|
287
719
|
*/
|
|
288
|
-
|
|
720
|
+
evictViewedItemsFromCache(currentIndex: number): Promise<void>;
|
|
289
721
|
/**
|
|
290
722
|
* Get prefetch cache configuration (for external access)
|
|
291
723
|
*/
|
|
@@ -295,15 +727,15 @@ declare class FeedManager {
|
|
|
295
727
|
*/
|
|
296
728
|
private fetchWithRetry;
|
|
297
729
|
/**
|
|
298
|
-
* Add
|
|
730
|
+
* Add items with deduplication
|
|
299
731
|
* Triggers garbage collection if cache exceeds maxCacheSize
|
|
300
732
|
*/
|
|
301
|
-
private
|
|
733
|
+
private addItems;
|
|
302
734
|
/**
|
|
303
|
-
* Merge
|
|
304
|
-
* Updates existing
|
|
735
|
+
* Merge items (for SWR revalidation)
|
|
736
|
+
* Updates existing items, adds new ones at the beginning
|
|
305
737
|
*/
|
|
306
|
-
private
|
|
738
|
+
private mergeItems;
|
|
307
739
|
/**
|
|
308
740
|
* Handle and categorize errors
|
|
309
741
|
*/
|
|
@@ -320,9 +752,9 @@ declare class FeedManager {
|
|
|
320
752
|
* Run garbage collection using LRU (Least Recently Used) policy
|
|
321
753
|
*
|
|
322
754
|
* When cache size exceeds maxCacheSize:
|
|
323
|
-
* 1. Sort
|
|
324
|
-
* 2. Evict oldest
|
|
325
|
-
* 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)
|
|
326
758
|
*
|
|
327
759
|
* @returns Number of evicted items
|
|
328
760
|
*/
|
|
@@ -769,6 +1201,7 @@ declare class PlayerEngine {
|
|
|
769
1201
|
private emitEvent;
|
|
770
1202
|
/**
|
|
771
1203
|
* Start watch time tracking
|
|
1204
|
+
* Increments watchTime every second and sends analytics heartbeat
|
|
772
1205
|
*/
|
|
773
1206
|
private startWatchTimeTracking;
|
|
774
1207
|
/**
|
|
@@ -776,9 +1209,14 @@ declare class PlayerEngine {
|
|
|
776
1209
|
*/
|
|
777
1210
|
private stopWatchTimeTracking;
|
|
778
1211
|
/**
|
|
779
|
-
* Track completion analytics
|
|
1212
|
+
* Track completion analytics (when video loops/ends)
|
|
780
1213
|
*/
|
|
781
1214
|
private trackCompletion;
|
|
1215
|
+
/**
|
|
1216
|
+
* Track when user leaves current video (scrolls to next video)
|
|
1217
|
+
* Sends final analytics event before video change
|
|
1218
|
+
*/
|
|
1219
|
+
private trackLeaveVideo;
|
|
782
1220
|
/**
|
|
783
1221
|
* Categorize media error
|
|
784
1222
|
*/
|
|
@@ -838,543 +1276,244 @@ declare function isValidTransition(from: PlayerStatus, to: PlayerStatus): boolea
|
|
|
838
1276
|
/**
|
|
839
1277
|
* Check if player is in a "active" state (can receive playback commands)
|
|
840
1278
|
*/
|
|
841
|
-
declare function isActiveState(status: PlayerStatus): boolean;
|
|
842
|
-
/**
|
|
843
|
-
* Check if player can start playback
|
|
844
|
-
*/
|
|
845
|
-
declare function canPlay(status: PlayerStatus): boolean;
|
|
846
|
-
/**
|
|
847
|
-
* Check if player can pause
|
|
848
|
-
*/
|
|
849
|
-
declare function canPause(status: PlayerStatus): boolean;
|
|
850
|
-
/**
|
|
851
|
-
* Check if player can seek
|
|
852
|
-
*/
|
|
853
|
-
declare function canSeek(status: PlayerStatus): boolean;
|
|
854
|
-
|
|
855
|
-
/**
|
|
856
|
-
* Lifecycle state for zustand store
|
|
857
|
-
*/
|
|
858
|
-
interface LifecycleState {
|
|
859
|
-
/** Whether manager is initialized */
|
|
860
|
-
isInitialized: boolean;
|
|
861
|
-
/** Whether there's a pending save operation */
|
|
862
|
-
isSaving: boolean;
|
|
863
|
-
/** Whether there's a pending restore operation */
|
|
864
|
-
isRestoring: boolean;
|
|
865
|
-
/** Last saved timestamp */
|
|
866
|
-
lastSavedAt: number | null;
|
|
867
|
-
/** Last restored timestamp */
|
|
868
|
-
lastRestoredAt: number | null;
|
|
869
|
-
/** Whether the restored snapshot needs revalidation */
|
|
870
|
-
needsRevalidation: boolean;
|
|
871
|
-
/** Current visibility state */
|
|
872
|
-
visibilityState: DocumentVisibilityState;
|
|
873
|
-
}
|
|
874
|
-
/**
|
|
875
|
-
* Restore result
|
|
876
|
-
*/
|
|
877
|
-
interface RestoreResult {
|
|
878
|
-
/** Whether restore was successful */
|
|
879
|
-
success: boolean;
|
|
880
|
-
/** Restored snapshot data (null if no valid snapshot) */
|
|
881
|
-
snapshot: SessionSnapshot | null;
|
|
882
|
-
/** Whether the restored data is stale and needs revalidation */
|
|
883
|
-
needsRevalidation: boolean;
|
|
884
|
-
/** Reason for failure (if any) */
|
|
885
|
-
reason?: 'no_snapshot' | 'expired' | 'invalid' | 'error';
|
|
886
|
-
/** Playback time in seconds (only present if restorePlaybackPosition config is enabled) */
|
|
887
|
-
playbackTime?: number;
|
|
888
|
-
/** Video ID that was playing (only present if restorePlaybackPosition config is enabled) */
|
|
889
|
-
currentVideoId?: string;
|
|
890
|
-
/** Captured video frame at playback position (base64 JPEG, only present if restorePlaybackPosition is enabled) */
|
|
891
|
-
restoreFrame?: string;
|
|
892
|
-
}
|
|
893
|
-
/**
|
|
894
|
-
* LifecycleManager configuration
|
|
895
|
-
*/
|
|
896
|
-
interface LifecycleConfig {
|
|
897
|
-
/** Storage adapter for persistence */
|
|
898
|
-
storage?: ISessionStorage;
|
|
899
|
-
/** Logger adapter */
|
|
900
|
-
logger?: ILogger;
|
|
901
|
-
/** Snapshot expiry time in ms (default: 24 hours) */
|
|
902
|
-
snapshotExpiryMs?: number;
|
|
903
|
-
/** Revalidation threshold in ms (default: 5 minutes) */
|
|
904
|
-
revalidationThresholdMs?: number;
|
|
905
|
-
/** Auto-save on visibility change (default: true) */
|
|
906
|
-
autoSaveOnHidden?: boolean;
|
|
907
|
-
/**
|
|
908
|
-
* Enable restoring video playback position (default: false)
|
|
909
|
-
* When enabled, saves and restores the exact playback time (seconds)
|
|
910
|
-
* so video can seek() to the exact position user was watching
|
|
911
|
-
*/
|
|
912
|
-
restorePlaybackPosition?: boolean;
|
|
913
|
-
/** SDK version for compatibility */
|
|
914
|
-
version?: string;
|
|
915
|
-
}
|
|
916
|
-
/**
|
|
917
|
-
* Default lifecycle configuration
|
|
918
|
-
*/
|
|
919
|
-
declare const DEFAULT_LIFECYCLE_CONFIG: Required<Omit<LifecycleConfig, 'storage' | 'logger'>>;
|
|
920
|
-
/**
|
|
921
|
-
* Lifecycle events for external listening
|
|
922
|
-
*/
|
|
923
|
-
type LifecycleEvent = {
|
|
924
|
-
type: 'saveStart';
|
|
925
|
-
} | {
|
|
926
|
-
type: 'saveComplete';
|
|
927
|
-
timestamp: number;
|
|
928
|
-
} | {
|
|
929
|
-
type: 'saveFailed';
|
|
930
|
-
error: Error;
|
|
931
|
-
} | {
|
|
932
|
-
type: 'restoreStart';
|
|
933
|
-
} | {
|
|
934
|
-
type: 'restoreComplete';
|
|
935
|
-
result: RestoreResult;
|
|
936
|
-
} | {
|
|
937
|
-
type: 'visibilityChange';
|
|
938
|
-
state: DocumentVisibilityState;
|
|
939
|
-
};
|
|
940
|
-
/**
|
|
941
|
-
* Lifecycle event listener
|
|
942
|
-
*/
|
|
943
|
-
type LifecycleEventListener = (event: LifecycleEvent) => void;
|
|
944
|
-
|
|
945
|
-
/**
|
|
946
|
-
* LifecycleManager - Handles session persistence and restoration
|
|
947
|
-
*
|
|
948
|
-
* Strategy: "State Persistence, DOM Destruction"
|
|
949
|
-
* - Save snapshot to storage when user leaves
|
|
950
|
-
* - Restore state from storage when user returns
|
|
951
|
-
* - Video DOM nodes are destroyed to free RAM
|
|
952
|
-
*
|
|
953
|
-
* Features:
|
|
954
|
-
* - Auto-save on visibility change (optional)
|
|
955
|
-
* - Snapshot expiry (24 hours default)
|
|
956
|
-
* - Stale data detection for revalidation
|
|
957
|
-
* - Event system for UI coordination
|
|
958
|
-
*
|
|
959
|
-
* @example
|
|
960
|
-
* ```typescript
|
|
961
|
-
* const lifecycle = new LifecycleManager({ storage, logger });
|
|
962
|
-
*
|
|
963
|
-
* // Initialize and attempt restore
|
|
964
|
-
* const result = await lifecycle.initialize();
|
|
965
|
-
* if (result.success) {
|
|
966
|
-
* feedManager.restoreFromSnapshot(result.snapshot);
|
|
967
|
-
* if (result.needsRevalidation) {
|
|
968
|
-
* feedManager.revalidate();
|
|
969
|
-
* }
|
|
970
|
-
* }
|
|
971
|
-
*
|
|
972
|
-
* // Save snapshot before leaving
|
|
973
|
-
* lifecycle.saveSnapshot({
|
|
974
|
-
* items: feedManager.getVideos(),
|
|
975
|
-
* cursor: feedManager.store.getState().cursor,
|
|
976
|
-
* focusedIndex: resourceGovernor.store.getState().focusedIndex,
|
|
977
|
-
* });
|
|
978
|
-
* ```
|
|
979
|
-
*/
|
|
980
|
-
declare class LifecycleManager {
|
|
981
|
-
/** Zustand vanilla store - Single Source of Truth */
|
|
982
|
-
readonly store: StoreApi<LifecycleState>;
|
|
983
|
-
/** Resolved configuration */
|
|
984
|
-
private readonly config;
|
|
985
|
-
/** Storage adapter */
|
|
986
|
-
private readonly storage?;
|
|
987
|
-
/** Logger adapter */
|
|
988
|
-
private readonly logger?;
|
|
989
|
-
/** Event listeners */
|
|
990
|
-
private readonly eventListeners;
|
|
991
|
-
/** Visibility change handler reference (for cleanup) */
|
|
992
|
-
private visibilityHandler?;
|
|
993
|
-
/** Pending save data (for debouncing) */
|
|
994
|
-
private pendingSaveData?;
|
|
995
|
-
constructor(config?: LifecycleConfig);
|
|
996
|
-
/**
|
|
997
|
-
* Initialize lifecycle manager and attempt to restore session
|
|
998
|
-
*/
|
|
999
|
-
initialize(): Promise<RestoreResult>;
|
|
1000
|
-
/**
|
|
1001
|
-
* Destroy lifecycle manager and cleanup
|
|
1002
|
-
*/
|
|
1003
|
-
destroy(): void;
|
|
1004
|
-
/**
|
|
1005
|
-
* Restore session from storage
|
|
1006
|
-
*/
|
|
1007
|
-
restoreSession(): Promise<RestoreResult>;
|
|
1008
|
-
/**
|
|
1009
|
-
* Save session snapshot to storage
|
|
1010
|
-
*
|
|
1011
|
-
* @param data - Snapshot data to save
|
|
1012
|
-
* @param data.playbackTime - Current video playback position (only saved if restorePlaybackPosition config is enabled)
|
|
1013
|
-
* @param data.currentVideoId - Current video ID (only saved if restorePlaybackPosition config is enabled)
|
|
1014
|
-
* @param data.restoreFrame - Captured video frame at playback position (only saved if restorePlaybackPosition is enabled)
|
|
1015
|
-
*/
|
|
1016
|
-
saveSnapshot(data: {
|
|
1017
|
-
items: VideoItem[];
|
|
1018
|
-
cursor: string | null;
|
|
1019
|
-
focusedIndex: number;
|
|
1020
|
-
scrollPosition?: number;
|
|
1021
|
-
/** Current video playback time in seconds (only used when restorePlaybackPosition is enabled) */
|
|
1022
|
-
playbackTime?: number;
|
|
1023
|
-
/** Current video ID (only used when restorePlaybackPosition is enabled) */
|
|
1024
|
-
currentVideoId?: string;
|
|
1025
|
-
/** Captured video frame at playback position (only used when restorePlaybackPosition is enabled) */
|
|
1026
|
-
restoreFrame?: string;
|
|
1027
|
-
}): Promise<boolean>;
|
|
1028
|
-
/**
|
|
1029
|
-
* Clear saved snapshot
|
|
1030
|
-
*/
|
|
1031
|
-
clearSnapshot(): Promise<void>;
|
|
1032
|
-
/**
|
|
1033
|
-
* Mark that pending data should be saved (for use with debouncing)
|
|
1034
|
-
*
|
|
1035
|
-
* @param data - Data to save when flush is called
|
|
1036
|
-
*/
|
|
1037
|
-
setPendingSave(data: Omit<SessionSnapshot, 'savedAt' | 'version'>): void;
|
|
1038
|
-
/**
|
|
1039
|
-
* Check if restorePlaybackPosition config is enabled
|
|
1040
|
-
* SDK can use this to decide whether to collect playbackTime
|
|
1041
|
-
*/
|
|
1042
|
-
isPlaybackPositionRestoreEnabled(): boolean;
|
|
1043
|
-
/**
|
|
1044
|
-
* Flush pending save (called on visibility hidden)
|
|
1045
|
-
*/
|
|
1046
|
-
flushPendingSave(): Promise<void>;
|
|
1047
|
-
/**
|
|
1048
|
-
* Handle visibility state change
|
|
1049
|
-
*/
|
|
1050
|
-
onVisibilityChange(state: DocumentVisibilityState): void;
|
|
1051
|
-
/**
|
|
1052
|
-
* Get current visibility state
|
|
1053
|
-
*/
|
|
1054
|
-
getVisibilityState(): DocumentVisibilityState;
|
|
1055
|
-
/**
|
|
1056
|
-
* Add event listener
|
|
1057
|
-
*/
|
|
1058
|
-
addEventListener(listener: LifecycleEventListener): () => void;
|
|
1059
|
-
/**
|
|
1060
|
-
* Remove event listener
|
|
1061
|
-
*/
|
|
1062
|
-
removeEventListener(listener: LifecycleEventListener): void;
|
|
1063
|
-
/**
|
|
1064
|
-
* Setup visibility change listener
|
|
1065
|
-
*/
|
|
1066
|
-
private setupVisibilityListener;
|
|
1067
|
-
/**
|
|
1068
|
-
* Validate snapshot data
|
|
1069
|
-
*/
|
|
1070
|
-
private validateSnapshot;
|
|
1071
|
-
/**
|
|
1072
|
-
* Check if snapshot is stale (needs revalidation)
|
|
1073
|
-
*/
|
|
1074
|
-
private isSnapshotStale;
|
|
1075
|
-
/**
|
|
1076
|
-
* Emit event to all listeners
|
|
1077
|
-
*/
|
|
1078
|
-
private emitEvent;
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
/**
|
|
1082
|
-
* Network type for prefetch strategy
|
|
1083
|
-
* Simplified from contracts NetworkType for prefetch decisions
|
|
1084
|
-
*/
|
|
1085
|
-
type NetworkType = 'wifi' | 'cellular' | 'offline';
|
|
1279
|
+
declare function isActiveState(status: PlayerStatus): boolean;
|
|
1086
1280
|
/**
|
|
1087
|
-
*
|
|
1281
|
+
* Check if player can start playback
|
|
1088
1282
|
*/
|
|
1089
|
-
declare function
|
|
1283
|
+
declare function canPlay(status: PlayerStatus): boolean;
|
|
1090
1284
|
/**
|
|
1091
|
-
*
|
|
1285
|
+
* Check if player can pause
|
|
1092
1286
|
*/
|
|
1093
|
-
|
|
1094
|
-
/** Currently allocated video slot indices (max 3) */
|
|
1095
|
-
activeAllocations: Set<number>;
|
|
1096
|
-
/** Queue of indices to preload */
|
|
1097
|
-
preloadQueue: number[];
|
|
1098
|
-
/** Currently focused/playing video index */
|
|
1099
|
-
focusedIndex: number;
|
|
1100
|
-
/** Current network type */
|
|
1101
|
-
networkType: NetworkType;
|
|
1102
|
-
/** Total number of items in feed (for bounds checking) */
|
|
1103
|
-
totalItems: number;
|
|
1104
|
-
/** Whether resource governor is active */
|
|
1105
|
-
isActive: boolean;
|
|
1106
|
-
/** Whether preloading is throttled due to scroll thrashing */
|
|
1107
|
-
isThrottled: boolean;
|
|
1108
|
-
/** Indices currently being preloaded */
|
|
1109
|
-
preloadingIndices: Set<number>;
|
|
1110
|
-
}
|
|
1287
|
+
declare function canPause(status: PlayerStatus): boolean;
|
|
1111
1288
|
/**
|
|
1112
|
-
*
|
|
1289
|
+
* Check if player can seek
|
|
1113
1290
|
*/
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
toMount: number[];
|
|
1117
|
-
/** Indices that should unmount video elements */
|
|
1118
|
-
toUnmount: number[];
|
|
1119
|
-
/** Currently active allocations after this operation */
|
|
1120
|
-
activeAllocations: number[];
|
|
1121
|
-
/** Whether focus change was successful */
|
|
1122
|
-
success: boolean;
|
|
1123
|
-
}
|
|
1291
|
+
declare function canSeek(status: PlayerStatus): boolean;
|
|
1292
|
+
|
|
1124
1293
|
/**
|
|
1125
|
-
*
|
|
1294
|
+
* Lifecycle state for zustand store
|
|
1126
1295
|
*/
|
|
1127
|
-
interface
|
|
1128
|
-
/**
|
|
1129
|
-
|
|
1130
|
-
/**
|
|
1131
|
-
|
|
1132
|
-
/** Whether
|
|
1133
|
-
|
|
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;
|
|
1134
1311
|
}
|
|
1135
1312
|
/**
|
|
1136
|
-
*
|
|
1137
|
-
* Prevents excessive preloading when user scrolls too fast
|
|
1313
|
+
* Restore result
|
|
1138
1314
|
*/
|
|
1139
|
-
interface
|
|
1140
|
-
/**
|
|
1141
|
-
|
|
1142
|
-
/**
|
|
1143
|
-
|
|
1144
|
-
/**
|
|
1145
|
-
|
|
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;
|
|
1146
1330
|
}
|
|
1147
1331
|
/**
|
|
1148
|
-
*
|
|
1332
|
+
* LifecycleManager configuration
|
|
1149
1333
|
*/
|
|
1150
|
-
interface
|
|
1151
|
-
/**
|
|
1152
|
-
|
|
1153
|
-
/** Focus debounce time in ms (default: 150) */
|
|
1154
|
-
focusDebounceMs?: number;
|
|
1155
|
-
/** Prefetch configuration overrides by network type */
|
|
1156
|
-
prefetch?: Partial<Record<NetworkType, Partial<PrefetchConfig>>>;
|
|
1157
|
-
/** Scroll thrashing configuration */
|
|
1158
|
-
scrollThrashing?: Partial<ScrollThrashingConfig>;
|
|
1159
|
-
/** Network adapter for detecting connection type */
|
|
1160
|
-
networkAdapter?: INetworkAdapter;
|
|
1161
|
-
/** Video loader adapter for preloading video data */
|
|
1162
|
-
videoLoader?: IVideoLoader;
|
|
1163
|
-
/** Poster loader adapter for preloading thumbnails */
|
|
1164
|
-
posterLoader?: IPosterLoader;
|
|
1334
|
+
interface LifecycleConfig {
|
|
1335
|
+
/** Storage adapter for persistence */
|
|
1336
|
+
storage?: ISessionStorage;
|
|
1165
1337
|
/** Logger adapter */
|
|
1166
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;
|
|
1167
1353
|
}
|
|
1168
1354
|
/**
|
|
1169
|
-
* Default
|
|
1170
|
-
*/
|
|
1171
|
-
declare const DEFAULT_PREFETCH_CONFIG: Record<NetworkType, PrefetchConfig>;
|
|
1172
|
-
/**
|
|
1173
|
-
* Default resource configuration
|
|
1355
|
+
* Default lifecycle configuration
|
|
1174
1356
|
*/
|
|
1175
|
-
declare const
|
|
1176
|
-
prefetch: Record<NetworkType, PrefetchConfig>;
|
|
1177
|
-
scrollThrashing: ScrollThrashingConfig;
|
|
1178
|
-
};
|
|
1357
|
+
declare const DEFAULT_LIFECYCLE_CONFIG: Required<Omit<LifecycleConfig, 'storage' | 'logger'>>;
|
|
1179
1358
|
/**
|
|
1180
|
-
*
|
|
1359
|
+
* Lifecycle events for external listening
|
|
1181
1360
|
*/
|
|
1182
|
-
type
|
|
1183
|
-
type: '
|
|
1184
|
-
toMount: number[];
|
|
1185
|
-
toUnmount: number[];
|
|
1361
|
+
type LifecycleEvent = {
|
|
1362
|
+
type: 'saveStart';
|
|
1186
1363
|
} | {
|
|
1187
|
-
type: '
|
|
1188
|
-
|
|
1189
|
-
previousIndex: number;
|
|
1364
|
+
type: 'saveComplete';
|
|
1365
|
+
timestamp: number;
|
|
1190
1366
|
} | {
|
|
1191
|
-
type: '
|
|
1192
|
-
|
|
1367
|
+
type: 'saveFailed';
|
|
1368
|
+
error: Error;
|
|
1193
1369
|
} | {
|
|
1194
|
-
type: '
|
|
1195
|
-
|
|
1370
|
+
type: 'restoreStart';
|
|
1371
|
+
} | {
|
|
1372
|
+
type: 'restoreComplete';
|
|
1373
|
+
result: RestoreResult;
|
|
1374
|
+
} | {
|
|
1375
|
+
type: 'visibilityChange';
|
|
1376
|
+
state: DocumentVisibilityState;
|
|
1196
1377
|
};
|
|
1197
1378
|
/**
|
|
1198
|
-
*
|
|
1379
|
+
* Lifecycle event listener
|
|
1199
1380
|
*/
|
|
1200
|
-
type
|
|
1381
|
+
type LifecycleEventListener = (event: LifecycleEvent) => void;
|
|
1201
1382
|
|
|
1202
1383
|
/**
|
|
1203
|
-
*
|
|
1204
|
-
* ResourceGovernor needs this to get video sources for preloading
|
|
1205
|
-
*/
|
|
1206
|
-
type VideoSourceGetter = (index: number) => {
|
|
1207
|
-
id: string;
|
|
1208
|
-
source: VideoSource;
|
|
1209
|
-
poster?: string;
|
|
1210
|
-
} | null;
|
|
1211
|
-
/**
|
|
1212
|
-
* ResourceGovernor - Manages video DOM allocation and prefetch strategy
|
|
1384
|
+
* LifecycleManager - Handles session persistence and restoration
|
|
1213
1385
|
*
|
|
1214
|
-
*
|
|
1215
|
-
* -
|
|
1216
|
-
* -
|
|
1217
|
-
* -
|
|
1218
|
-
* - 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
|
|
1219
1390
|
*
|
|
1220
|
-
*
|
|
1221
|
-
* -
|
|
1222
|
-
* -
|
|
1223
|
-
* -
|
|
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
|
|
1224
1396
|
*
|
|
1225
1397
|
* @example
|
|
1226
1398
|
* ```typescript
|
|
1227
|
-
* const
|
|
1228
|
-
*
|
|
1229
|
-
* // Initialize with feed size
|
|
1230
|
-
* governor.setTotalItems(20);
|
|
1231
|
-
* governor.activate();
|
|
1232
|
-
*
|
|
1233
|
-
* // Handle scroll/swipe
|
|
1234
|
-
* governor.setFocusedIndex(5); // Debounced
|
|
1399
|
+
* const lifecycle = new LifecycleManager({ storage, logger });
|
|
1235
1400
|
*
|
|
1236
|
-
* //
|
|
1237
|
-
*
|
|
1238
|
-
*
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
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();
|
|
1241
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,
|
|
1242
1415
|
* });
|
|
1243
|
-
* ```
|
|
1244
|
-
*/
|
|
1245
|
-
declare class
|
|
1246
|
-
/** Zustand vanilla store - Single Source of Truth */
|
|
1247
|
-
readonly store: StoreApi<
|
|
1248
|
-
/** Resolved configuration */
|
|
1249
|
-
private readonly config;
|
|
1250
|
-
/**
|
|
1251
|
-
private readonly
|
|
1252
|
-
/**
|
|
1253
|
-
private readonly
|
|
1254
|
-
/**
|
|
1255
|
-
private readonly
|
|
1256
|
-
/**
|
|
1257
|
-
private
|
|
1258
|
-
/**
|
|
1259
|
-
private
|
|
1260
|
-
|
|
1261
|
-
private focusDebounceTimer;
|
|
1262
|
-
/** Pending focused index (before debounce completes) */
|
|
1263
|
-
private pendingFocusedIndex;
|
|
1264
|
-
/** Network change unsubscribe function */
|
|
1265
|
-
private networkUnsubscribe?;
|
|
1266
|
-
/** Video source getter (injected via setVideoSourceGetter) */
|
|
1267
|
-
private videoSourceGetter?;
|
|
1268
|
-
/** Scroll thrashing detection - timestamps of recent focus changes */
|
|
1269
|
-
private focusChangeTimestamps;
|
|
1270
|
-
/** Scroll thrashing cooldown timer */
|
|
1271
|
-
private thrashingCooldownTimer;
|
|
1272
|
-
constructor(config?: ResourceConfig);
|
|
1273
|
-
/**
|
|
1274
|
-
* Activate the resource governor
|
|
1275
|
-
* Starts network monitoring and performs initial allocation
|
|
1276
|
-
*/
|
|
1277
|
-
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);
|
|
1278
1434
|
/**
|
|
1279
|
-
*
|
|
1280
|
-
* Stops network monitoring and clears allocations
|
|
1435
|
+
* Initialize lifecycle manager and attempt to restore session
|
|
1281
1436
|
*/
|
|
1282
|
-
|
|
1437
|
+
initialize(): Promise<RestoreResult>;
|
|
1283
1438
|
/**
|
|
1284
|
-
* Destroy
|
|
1439
|
+
* Destroy lifecycle manager and cleanup
|
|
1285
1440
|
*/
|
|
1286
1441
|
destroy(): void;
|
|
1287
1442
|
/**
|
|
1288
|
-
*
|
|
1289
|
-
*/
|
|
1290
|
-
setTotalItems(count: number): void;
|
|
1291
|
-
/**
|
|
1292
|
-
* Set focused index with debouncing
|
|
1293
|
-
* This is called during scroll/swipe
|
|
1294
|
-
*/
|
|
1295
|
-
setFocusedIndex(index: number): void;
|
|
1296
|
-
/**
|
|
1297
|
-
* Set focused index immediately (skip debounce)
|
|
1298
|
-
* Use this for programmatic navigation, not scroll
|
|
1299
|
-
*/
|
|
1300
|
-
setFocusedIndexImmediate(index: number): void;
|
|
1301
|
-
/**
|
|
1302
|
-
* Request allocation for given indices
|
|
1303
|
-
* Returns what needs to be mounted/unmounted
|
|
1304
|
-
*/
|
|
1305
|
-
requestAllocation(indices: number[]): AllocationResult;
|
|
1306
|
-
/**
|
|
1307
|
-
* Get current active allocations
|
|
1443
|
+
* Restore session from storage
|
|
1308
1444
|
*/
|
|
1309
|
-
|
|
1445
|
+
restoreSession(): Promise<RestoreResult>;
|
|
1310
1446
|
/**
|
|
1311
|
-
*
|
|
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)
|
|
1312
1453
|
*/
|
|
1313
|
-
|
|
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>;
|
|
1314
1466
|
/**
|
|
1315
|
-
*
|
|
1467
|
+
* Clear saved snapshot
|
|
1316
1468
|
*/
|
|
1317
|
-
|
|
1469
|
+
clearSnapshot(): Promise<void>;
|
|
1318
1470
|
/**
|
|
1319
|
-
*
|
|
1471
|
+
* Mark that pending data should be saved (for use with debouncing)
|
|
1472
|
+
*
|
|
1473
|
+
* @param data - Data to save when flush is called
|
|
1320
1474
|
*/
|
|
1321
|
-
|
|
1475
|
+
setPendingSave(data: Omit<SessionSnapshot, 'savedAt' | 'version'>): void;
|
|
1322
1476
|
/**
|
|
1323
|
-
*
|
|
1324
|
-
*
|
|
1325
|
-
*
|
|
1326
|
-
* @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
|
|
1327
1479
|
*/
|
|
1328
|
-
|
|
1480
|
+
isPlaybackPositionRestoreEnabled(): boolean;
|
|
1329
1481
|
/**
|
|
1330
|
-
*
|
|
1482
|
+
* Flush pending save (called on visibility hidden)
|
|
1331
1483
|
*/
|
|
1332
|
-
|
|
1484
|
+
flushPendingSave(): Promise<void>;
|
|
1333
1485
|
/**
|
|
1334
|
-
*
|
|
1486
|
+
* Handle visibility state change
|
|
1335
1487
|
*/
|
|
1336
|
-
|
|
1488
|
+
onVisibilityChange(state: DocumentVisibilityState): void;
|
|
1337
1489
|
/**
|
|
1338
|
-
*
|
|
1339
|
-
* Respects throttling and network conditions
|
|
1490
|
+
* Get current visibility state
|
|
1340
1491
|
*/
|
|
1341
|
-
|
|
1492
|
+
getVisibilityState(): DocumentVisibilityState;
|
|
1342
1493
|
/**
|
|
1343
1494
|
* Add event listener
|
|
1344
1495
|
*/
|
|
1345
|
-
addEventListener(listener:
|
|
1496
|
+
addEventListener(listener: LifecycleEventListener): () => void;
|
|
1346
1497
|
/**
|
|
1347
1498
|
* Remove event listener
|
|
1348
1499
|
*/
|
|
1349
|
-
removeEventListener(listener:
|
|
1350
|
-
/**
|
|
1351
|
-
* Initialize network detection
|
|
1352
|
-
*/
|
|
1353
|
-
private initializeNetwork;
|
|
1354
|
-
/**
|
|
1355
|
-
* Perform allocation for a given focused index
|
|
1356
|
-
*/
|
|
1357
|
-
private performAllocation;
|
|
1358
|
-
/**
|
|
1359
|
-
* Update prefetch queue based on current state
|
|
1360
|
-
*/
|
|
1361
|
-
private updatePrefetchQueue;
|
|
1500
|
+
removeEventListener(listener: LifecycleEventListener): void;
|
|
1362
1501
|
/**
|
|
1363
|
-
*
|
|
1502
|
+
* Setup visibility change listener
|
|
1364
1503
|
*/
|
|
1365
|
-
private
|
|
1504
|
+
private setupVisibilityListener;
|
|
1366
1505
|
/**
|
|
1367
|
-
*
|
|
1506
|
+
* Validate snapshot data
|
|
1368
1507
|
*/
|
|
1369
|
-
private
|
|
1508
|
+
private validateSnapshot;
|
|
1370
1509
|
/**
|
|
1371
|
-
*
|
|
1510
|
+
* Check if snapshot is stale (needs revalidation)
|
|
1372
1511
|
*/
|
|
1373
|
-
private
|
|
1512
|
+
private isSnapshotStale;
|
|
1374
1513
|
/**
|
|
1375
|
-
*
|
|
1514
|
+
* Create session snapshot from data
|
|
1376
1515
|
*/
|
|
1377
|
-
private
|
|
1516
|
+
private createSnapshot;
|
|
1378
1517
|
/**
|
|
1379
1518
|
* Emit event to all listeners
|
|
1380
1519
|
*/
|
|
@@ -1504,33 +1643,6 @@ type OptimisticEventListener = (event: OptimisticEvent) => void;
|
|
|
1504
1643
|
|
|
1505
1644
|
/**
|
|
1506
1645
|
* OptimisticManager - Handles optimistic UI updates with rollback
|
|
1507
|
-
*
|
|
1508
|
-
* Pattern:
|
|
1509
|
-
* 1. User action (like) -> Update UI immediately
|
|
1510
|
-
* 2. Call API in background
|
|
1511
|
-
* 3. On success -> Keep UI state, remove pending action
|
|
1512
|
-
* 4. On error -> Rollback to previous state
|
|
1513
|
-
*
|
|
1514
|
-
* Features:
|
|
1515
|
-
* - Immediate UI feedback
|
|
1516
|
-
* - Automatic rollback on failure
|
|
1517
|
-
* - Retry queue for failed actions
|
|
1518
|
-
* - Prevents duplicate pending actions
|
|
1519
|
-
*
|
|
1520
|
-
* @example
|
|
1521
|
-
* ```typescript
|
|
1522
|
-
* const optimistic = new OptimisticManager({
|
|
1523
|
-
* interaction,
|
|
1524
|
-
* feedManager,
|
|
1525
|
-
* logger,
|
|
1526
|
-
* });
|
|
1527
|
-
*
|
|
1528
|
-
* // User taps like button
|
|
1529
|
-
* await optimistic.like(videoId);
|
|
1530
|
-
*
|
|
1531
|
-
* // User taps follow button
|
|
1532
|
-
* await optimistic.follow(videoId);
|
|
1533
|
-
* ```
|
|
1534
1646
|
*/
|
|
1535
1647
|
declare class OptimisticManager {
|
|
1536
1648
|
/** Zustand vanilla store - Single Source of Truth */
|
|
@@ -1539,7 +1651,7 @@ declare class OptimisticManager {
|
|
|
1539
1651
|
private readonly config;
|
|
1540
1652
|
/** Interaction adapter */
|
|
1541
1653
|
private readonly interaction?;
|
|
1542
|
-
/** Feed manager for updating
|
|
1654
|
+
/** Feed manager for updating item state */
|
|
1543
1655
|
private readonly feedManager?;
|
|
1544
1656
|
/** Logger adapter */
|
|
1545
1657
|
private readonly logger?;
|
|
@@ -1547,137 +1659,65 @@ declare class OptimisticManager {
|
|
|
1547
1659
|
private readonly eventListeners;
|
|
1548
1660
|
/** Retry timer */
|
|
1549
1661
|
private retryTimer;
|
|
1550
|
-
/** Debounce timers for like/unlike per
|
|
1662
|
+
/** Debounce timers for like/unlike per item */
|
|
1551
1663
|
private readonly likeDebounceTimers;
|
|
1664
|
+
/** Intended like state while debouncing (itemId -> isLiked) */
|
|
1665
|
+
private readonly intendedLikeState;
|
|
1552
1666
|
/** Debounce delay in ms */
|
|
1553
1667
|
private readonly debounceDelay;
|
|
1554
1668
|
constructor(config?: OptimisticConfig);
|
|
1555
1669
|
/**
|
|
1556
|
-
* Like
|
|
1670
|
+
* Like an item with optimistic update
|
|
1557
1671
|
*/
|
|
1558
|
-
like(
|
|
1672
|
+
like(itemId: string): Promise<boolean>;
|
|
1559
1673
|
/**
|
|
1560
|
-
* Unlike
|
|
1674
|
+
* Unlike an item with optimistic update
|
|
1561
1675
|
*/
|
|
1562
|
-
unlike(
|
|
1676
|
+
unlike(itemId: string): Promise<boolean>;
|
|
1563
1677
|
/**
|
|
1564
|
-
* Toggle like state with DEBOUNCE
|
|
1565
|
-
*
|
|
1566
|
-
* This method:
|
|
1567
|
-
* 1. Updates UI immediately (optimistic)
|
|
1568
|
-
* 2. Debounces API call - only sends after user stops clicking
|
|
1569
|
-
* 3. Sends final state to API after debounce delay
|
|
1570
|
-
*
|
|
1571
|
-
* Perfect for rapid tapping like TikTok/Instagram behavior.
|
|
1678
|
+
* Toggle like state with DEBOUNCE
|
|
1572
1679
|
*/
|
|
1573
|
-
toggleLike(
|
|
1680
|
+
toggleLike(itemId: string): void;
|
|
1574
1681
|
/**
|
|
1575
|
-
* Execute
|
|
1576
|
-
* Reads current state from FeedManager to get final intended state
|
|
1682
|
+
* Execute API call after debounce delay
|
|
1577
1683
|
*/
|
|
1578
1684
|
private executeDebouncedLikeApi;
|
|
1579
1685
|
/**
|
|
1580
|
-
*
|
|
1581
|
-
* Legacy toggle that waits for API response
|
|
1686
|
+
* Handle errors from debounced API calls
|
|
1582
1687
|
*/
|
|
1583
|
-
|
|
1688
|
+
private handleDebouncedApiError;
|
|
1584
1689
|
/**
|
|
1585
|
-
*
|
|
1690
|
+
* Remove a pending action by ID
|
|
1586
1691
|
*/
|
|
1587
|
-
|
|
1588
|
-
/**
|
|
1589
|
-
* Unfollow a video author with optimistic update
|
|
1590
|
-
*/
|
|
1591
|
-
unfollow(videoId: string): Promise<boolean>;
|
|
1692
|
+
private removePendingAction;
|
|
1592
1693
|
/**
|
|
1593
1694
|
* Toggle follow state
|
|
1594
1695
|
*/
|
|
1595
|
-
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;
|
|
1596
1701
|
/**
|
|
1597
|
-
*
|
|
1702
|
+
* Reset all optimistic state
|
|
1598
1703
|
*/
|
|
1704
|
+
reset(): void;
|
|
1599
1705
|
getPendingActions(): PendingAction[];
|
|
1600
|
-
|
|
1601
|
-
* Check if there's a pending action for a video
|
|
1602
|
-
* Only returns true for actions with status 'pending' (not 'failed')
|
|
1603
|
-
*/
|
|
1604
|
-
hasPendingAction(videoId: string, type?: ActionType): boolean;
|
|
1605
|
-
/**
|
|
1606
|
-
* Get failed actions queue
|
|
1607
|
-
*/
|
|
1706
|
+
hasPendingAction(itemId: string, type?: ActionType): boolean;
|
|
1608
1707
|
getFailedQueue(): PendingAction[];
|
|
1609
|
-
/**
|
|
1610
|
-
* Manually retry failed actions
|
|
1611
|
-
*/
|
|
1612
1708
|
retryFailed(): Promise<void>;
|
|
1613
|
-
/**
|
|
1614
|
-
* Clear all failed actions
|
|
1615
|
-
*/
|
|
1616
1709
|
clearFailed(): void;
|
|
1617
|
-
/**
|
|
1618
|
-
* Reset manager state
|
|
1619
|
-
*/
|
|
1620
|
-
reset(): void;
|
|
1621
|
-
/**
|
|
1622
|
-
* Destroy manager and cleanup
|
|
1623
|
-
*/
|
|
1624
1710
|
destroy(): void;
|
|
1625
|
-
|
|
1626
|
-
* Add event listener
|
|
1627
|
-
*/
|
|
1628
|
-
addEventListener(listener: OptimisticEventListener): () => void;
|
|
1629
|
-
/**
|
|
1630
|
-
* Remove event listener
|
|
1631
|
-
*/
|
|
1632
|
-
removeEventListener(listener: OptimisticEventListener): void;
|
|
1633
|
-
/**
|
|
1634
|
-
* Perform an optimistic action
|
|
1635
|
-
*/
|
|
1711
|
+
private emit;
|
|
1636
1712
|
private performAction;
|
|
1637
|
-
/**
|
|
1638
|
-
* Create rollback data based on action type
|
|
1639
|
-
*/
|
|
1640
1713
|
private createRollbackData;
|
|
1641
|
-
/**
|
|
1642
|
-
* Apply optimistic update to feed
|
|
1643
|
-
*/
|
|
1644
1714
|
private applyOptimisticUpdate;
|
|
1645
|
-
/**
|
|
1646
|
-
* Apply rollback
|
|
1647
|
-
*/
|
|
1648
1715
|
private applyRollback;
|
|
1649
|
-
/**
|
|
1650
|
-
* Add pending action to store
|
|
1651
|
-
*/
|
|
1652
1716
|
private addPendingAction;
|
|
1653
|
-
/**
|
|
1654
|
-
* Mark action as success
|
|
1655
|
-
*/
|
|
1656
1717
|
private markActionSuccess;
|
|
1657
|
-
/**
|
|
1658
|
-
* Mark action as failed
|
|
1659
|
-
*/
|
|
1660
1718
|
private markActionFailed;
|
|
1661
|
-
/**
|
|
1662
|
-
* Retry a failed action
|
|
1663
|
-
*/
|
|
1664
1719
|
private retryAction;
|
|
1665
|
-
/**
|
|
1666
|
-
* Execute API call based on action type
|
|
1667
|
-
*/
|
|
1668
|
-
private executeApiCall;
|
|
1669
|
-
/**
|
|
1670
|
-
* Schedule retry of failed actions
|
|
1671
|
-
*/
|
|
1672
1720
|
private scheduleRetry;
|
|
1673
|
-
/**
|
|
1674
|
-
* Cancel retry timer
|
|
1675
|
-
*/
|
|
1676
|
-
private cancelRetryTimer;
|
|
1677
|
-
/**
|
|
1678
|
-
* Emit event to all listeners
|
|
1679
|
-
*/
|
|
1680
|
-
private emitEvent;
|
|
1681
1721
|
}
|
|
1682
1722
|
|
|
1683
1723
|
/**
|
|
@@ -1914,4 +1954,152 @@ declare class CommentManager {
|
|
|
1914
1954
|
private createError;
|
|
1915
1955
|
}
|
|
1916
1956
|
|
|
1917
|
-
|
|
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 };
|