@xhub-reel/feed 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 XHubReel Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,462 @@
1
+ # @xhub-reel/feed
2
+
3
+ > Virtualized video feed for XHubReel - TikTok-style infinite scroll
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @xhub-reel/feed @xhub-reel/core @xhub-reel/player @xhub-reel/ui
9
+ # or
10
+ pnpm add @xhub-reel/feed @xhub-reel/core @xhub-reel/player @xhub-reel/ui
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - πŸ“œ **3-Node Carousel** - Only renders prev/current/next videos (minimal DOM)
16
+ - ♾️ **Infinite Scroll** - Automatic loading of more content
17
+ - 🎯 **Video Activation** - Smart play/pause based on viewport
18
+ - ⚑ **Preloading** - Preloads next videos for instant playback via @xhub-reel/player-core
19
+ - πŸ”„ **Pull to Refresh** - Native-feeling refresh gesture
20
+ - πŸ’Ύ **Memory Efficient** - Max 5 videos in DOM at once
21
+ - πŸ”Œ **Two Modes** - Manual (pass videos) or API (automatic fetching)
22
+ - 🎨 **Design System** - Uses tokens from @xhub-reel/core
23
+ - 🧩 **Composable** - VideoOverlay and ActionBar from @xhub-reel/ui
24
+
25
+ ## Breaking Changes in v0.0.1
26
+
27
+ ### usePreloader Hook
28
+
29
+ `usePreloader` now re-exports `usePreload` from @xhub-reel/player-core:
30
+
31
+ ```tsx
32
+ // Before (v0.0.0)
33
+ const { preloadStates, preloadVideo } = usePreloader({ videos, currentIndex })
34
+
35
+ // After (v0.0.1)
36
+ import { usePreload, getPreloadPriorityForFeed } from '@xhub-reel/feed'
37
+
38
+ const { preload, statuses, isPreloaded } = usePreload({ enabled: true })
39
+ const priority = getPreloadPriorityForFeed(index, currentIndex)
40
+ preload(video.url, priority, 'segment')
41
+ ```
42
+
43
+ ### ActionBar Integration
44
+
45
+ VideoFeedItem now uses ActionBar from @xhub-reel/ui internally. No API changes, but custom styling may behave differently.
46
+
47
+ ## Usage
48
+
49
+ ### Manual Mode (Pass Videos Directly)
50
+
51
+ ```tsx
52
+ import { VideoFeed } from '@xhub-reel/feed'
53
+ import type { Video } from '@xhub-reel/core'
54
+
55
+ function App() {
56
+ const videos: Video[] = [...]
57
+
58
+ return (
59
+ <VideoFeed
60
+ videos={videos}
61
+ onVideoChange={(video, index) => {
62
+ console.log('Now playing:', video.id)
63
+ }}
64
+ onLike={() => console.log('Liked!')}
65
+ onComment={() => console.log('Comment!')}
66
+ onShare={() => console.log('Share!')}
67
+ />
68
+ )
69
+ }
70
+ ```
71
+
72
+ ### API Mode (Automatic Fetching)
73
+
74
+ Use `ConnectedVideoFeed` for automatic data fetching from your backend:
75
+
76
+ ```tsx
77
+ import { XHubReelProvider } from '@xhub-reel/core/api'
78
+ import { ConnectedVideoFeed } from '@xhub-reel/feed'
79
+
80
+ function App() {
81
+ return (
82
+ <XHubReelProvider
83
+ config={{
84
+ baseUrl: 'https://api.yoursite.com/v1',
85
+ auth: {
86
+ accessToken: userToken,
87
+ onTokenExpired: async () => {
88
+ const newToken = await refreshToken()
89
+ return { accessToken: newToken }
90
+ },
91
+ },
92
+ }}
93
+ >
94
+ <ConnectedVideoFeed
95
+ userId="user123"
96
+ tag="funny"
97
+ onLike={handleLike}
98
+ onComment={handleComment}
99
+ onShare={handleShare}
100
+ />
101
+ </XHubReelProvider>
102
+ )
103
+ }
104
+ ```
105
+
106
+ ### Using Individual Components
107
+
108
+ VideoFeedItem now exports VideoOverlay separately for custom layouts:
109
+
110
+ ```tsx
111
+ import { VideoFeedItem, VideoOverlay } from '@xhub-reel/feed'
112
+ import { ActionBar } from '@xhub-reel/ui'
113
+
114
+ function CustomVideoItem({ video }) {
115
+ return (
116
+ <div className="custom-container">
117
+ <video src={video.url} />
118
+
119
+ {/* Use VideoOverlay */}
120
+ <VideoOverlay
121
+ video={video}
122
+ onAuthorClick={() => navigate(`/user/${video.author.id}`)}
123
+ timelineExpanded={false}
124
+ />
125
+
126
+ {/* Or use ActionBar directly */}
127
+ <ActionBar
128
+ likeCount={video.stats.likes}
129
+ commentCount={video.stats.comments}
130
+ shareCount={video.stats.shares}
131
+ isLiked={video.isLiked}
132
+ onLike={() => likeVideo(video.id)}
133
+ onComment={() => openComments(video.id)}
134
+ onShare={() => shareVideo(video.id)}
135
+ />
136
+ </div>
137
+ )
138
+ }
139
+ ```
140
+
141
+ ### Using useVideoFeed Hook
142
+
143
+ For custom implementations, use the `useVideoFeed` hook:
144
+
145
+ ```tsx
146
+ import { useVideoFeed } from '@xhub-reel/feed'
147
+ import { VideoFeed } from '@xhub-reel/feed'
148
+
149
+ function CustomFeedPage() {
150
+ const {
151
+ videos,
152
+ isLoading,
153
+ hasMore,
154
+ fetchNextPage,
155
+ error,
156
+ refetch,
157
+ } = useVideoFeed({
158
+ config: {
159
+ baseUrl: 'https://api.yoursite.com',
160
+ auth: { accessToken: token },
161
+ },
162
+ userId: 'user123',
163
+ limit: 10,
164
+ onSuccess: (videos) => console.log('Fetched', videos.length),
165
+ onError: (error) => console.error('Error:', error),
166
+ })
167
+
168
+ if (isLoading) return <LoadingSpinner />
169
+ if (error) return <ErrorMessage error={error} onRetry={refetch} />
170
+
171
+ return (
172
+ <VideoFeed
173
+ videos={videos}
174
+ isLoading={isLoading}
175
+ hasMore={hasMore}
176
+ onLoadMore={fetchNextPage}
177
+ />
178
+ )
179
+ }
180
+ ```
181
+
182
+ ### With Actions
183
+
184
+ ```tsx
185
+ <VideoFeed
186
+ videos={videos}
187
+ onLike={() => likeVideo()}
188
+ onComment={() => openComments()}
189
+ onShare={() => shareVideo()}
190
+ onAuthorClick={() => viewProfile()}
191
+ />
192
+ ```
193
+
194
+ ## Components
195
+
196
+ ### VideoFeed
197
+
198
+ Main feed component with swipe gestures and virtualization.
199
+
200
+ | Prop | Type | Default | Description |
201
+ |------|------|---------|-------------|
202
+ | `videos` | `Video[]` | `[]` | Array of video objects |
203
+ | `initialIndex` | `number` | `0` | Starting video index |
204
+ | `onVideoChange` | `(video, index) => void` | - | Called when active video changes |
205
+ | `onLoadMore` | `() => void \| Promise<void>` | - | Called when scrolling near end |
206
+ | `onLike` | `(video) => void` | - | Called when like button pressed |
207
+ | `onComment` | `(video) => void` | - | Called when comment button pressed |
208
+ | `onShare` | `(video) => void` | - | Called when share button pressed |
209
+ | `onAuthorClick` | `(video) => void` | - | Called when author clicked |
210
+ | `loadMoreThreshold` | `number` | `3` | Videos from end to trigger load |
211
+ | `transitionDuration` | `number` | `300` | Swipe animation duration (ms) |
212
+ | `swipeThreshold` | `number` | `50` | Swipe threshold (px) |
213
+ | `velocityThreshold` | `number` | `0.3` | Velocity threshold (px/ms) |
214
+ | `gesturesDisabled` | `boolean` | `false` | Disable swipe gestures |
215
+ | `hapticEnabled` | `boolean` | `true` | Enable haptic feedback |
216
+
217
+ ### VideoFeedItem
218
+
219
+ Individual video item with built-in controls.
220
+
221
+ | Prop | Type | Default | Description |
222
+ |------|------|---------|-------------|
223
+ | `video` | `Video` | **required** | Video object |
224
+ | `isActive` | `boolean` | `false` | Whether currently active |
225
+ | `priority` | `PreloadPriority` | `'none'` | Preload priority |
226
+ | `showTimeline` | `boolean` | `true` | Show timeline/seekbar |
227
+ | `onLike` | `() => void` | - | Like handler |
228
+ | `onComment` | `() => void` | - | Comment handler |
229
+ | `onShare` | `() => void` | - | Share handler |
230
+ | `onAuthorClick` | `() => void` | - | Author click handler |
231
+
232
+ ### VideoOverlay
233
+
234
+ Info overlay component (author, caption, hashtags).
235
+
236
+ | Prop | Type | Default | Description |
237
+ |------|------|---------|-------------|
238
+ | `video` | `Video` | **required** | Video object |
239
+ | `onAuthorClick` | `() => void` | - | Author click handler |
240
+ | `timelineExpanded` | `boolean` | `false` | Adjust padding for timeline |
241
+
242
+ ### ConnectedVideoFeed (API Mode)
243
+
244
+ | Prop | Type | Default | Description |
245
+ |------|------|---------|-------------|
246
+ | `config` | `XHubReelConfig` | - | API configuration (optional if using XHubReelProvider) |
247
+ | `userId` | `string` | - | User ID for user-specific feed |
248
+ | `tag` | `string` | - | Tag/hashtag filter |
249
+ | `searchQuery` | `string` | - | Search query |
250
+ | `pageSize` | `number` | `10` | Videos per page |
251
+ | `initialVideos` | `Video[]` | - | Initial videos while loading |
252
+ | `onFetchSuccess` | `(videos) => void` | - | Success callback |
253
+ | `onFetchError` | `(error) => void` | - | Error callback |
254
+ | `renderLoading` | `() => ReactNode` | - | Custom loading UI |
255
+ | `renderError` | `(error, retry) => ReactNode` | - | Custom error UI |
256
+ | `renderEmpty` | `() => ReactNode` | - | Custom empty UI |
257
+
258
+ > **Note:** `PullToRefresh` component Δ‘Γ£ được chuyển sang `@xhub-reel/ui` package. Import tα»« `@xhub-reel/ui` thay vΓ¬ `@xhub-reel/feed`.
259
+
260
+ ## Hooks
261
+
262
+ ### useVideoFeed
263
+
264
+ Fetch videos with infinite scroll support.
265
+
266
+ ```tsx
267
+ const {
268
+ videos, // Flattened videos array
269
+ isLoading, // Initial loading state
270
+ isFetchingMore, // Loading more state
271
+ hasMore, // Has more to load
272
+ fetchNextPage, // Load next page
273
+ refetch, // Refetch all
274
+ error, // Error if any
275
+ isApiMode, // Whether API mode active
276
+ totalCount, // Total count (if provided by API)
277
+ } = useVideoFeed({
278
+ config, // XHubReelConfig (required for API mode)
279
+ userId, // User ID filter
280
+ tag, // Tag filter
281
+ searchQuery, // Search query
282
+ limit, // Page size
283
+ enabled, // Enable/disable
284
+ initialVideos, // Initial data
285
+ staleTime, // Cache time
286
+ onSuccess, // Success callback
287
+ onError, // Error callback
288
+ })
289
+ ```
290
+
291
+ ### useVideoActivation
292
+
293
+ Control video activation based on visibility.
294
+
295
+ ```tsx
296
+ const {
297
+ isActive, // Whether video is active
298
+ isVisible, // Whether video is visible
299
+ visibilityRatio, // Visibility ratio (0-1)
300
+ activate, // Manual activate
301
+ deactivate, // Manual deactivate
302
+ } = useVideoActivation({
303
+ containerRef, // Container element ref
304
+ videoRef, // Video element ref
305
+ isCurrentVideo, // Whether current in feed
306
+ onActivate, // Activate callback
307
+ onDeactivate, // Deactivate callback
308
+ autoActivate, // Enable auto-activation
309
+ })
310
+ ```
311
+
312
+ ### usePreload (from @xhub-reel/player-core)
313
+
314
+ Preload videos with priority queue.
315
+
316
+ ```tsx
317
+ import { usePreload, getPreloadPriorityForFeed } from '@xhub-reel/feed'
318
+
319
+ const {
320
+ preload, // Enqueue preload
321
+ preloadMany, // Enqueue multiple
322
+ cancel, // Cancel preload
323
+ cancelAll, // Cancel all
324
+ setPaused, // Pause/resume
325
+ handleScrollVelocity, // Handle scroll
326
+ isPreloaded, // Check if preloaded
327
+ getStatus, // Get status
328
+ preloadedUrls, // Preloaded URLs
329
+ statuses, // All statuses
330
+ isPaused, // Paused state
331
+ manager, // PreloadManager instance
332
+ } = usePreload({
333
+ enabled: true,
334
+ maxConcurrent: 2,
335
+ maxPreloaded: 5,
336
+ })
337
+
338
+ // Get priority for feed
339
+ const priority = getPreloadPriorityForFeed(index, currentIndex)
340
+ preload(video.url, priority, 'segment')
341
+ ```
342
+
343
+ ### Helper Functions
344
+
345
+ ```tsx
346
+ import {
347
+ getPreloadPriorityForFeed, // Get numeric priority
348
+ mapPriorityToNumeric, // Map enum to number
349
+ getPreloadPriority, // Get PreloadPriority enum
350
+ preloadThumbnail, // Preload thumbnail
351
+ } from '@xhub-reel/feed'
352
+
353
+ // Get priority based on distance from current
354
+ const priority = getPreloadPriorityForFeed(videoIndex, currentIndex)
355
+ // Returns: 1 (current), 3 (adjacent), 5 (near), 7 (far), 10 (dispose)
356
+
357
+ // Map priority enum to number
358
+ const numPriority = mapPriorityToNumeric('high') // 1
359
+
360
+ // Get enum priority
361
+ const enumPriority = getPreloadPriority(videoIndex, currentIndex)
362
+ // Returns: 'high' | 'medium' | 'low' | 'metadata' | 'none'
363
+
364
+ // Preload thumbnail
365
+ preloadThumbnail('https://example.com/thumbnail.jpg')
366
+ ```
367
+
368
+ ## Video Activation Rules
369
+
370
+ Videos are activated based on viewport visibility:
371
+
372
+ | Condition | Action |
373
+ |-----------|--------|
374
+ | > 50% visible | Play |
375
+ | < 30% visible | Pause + Reset |
376
+ | Scroll velocity > 2000px/s | Skip activation |
377
+ | Scroll stopped > 300ms | Activate nearest |
378
+
379
+ ## Memory Management
380
+
381
+ The feed automatically manages memory:
382
+
383
+ - **Max 5 videos** in DOM at once
384
+ - **Max 3 decoded** video frames
385
+ - **Aggressive cleanup** on scroll
386
+ - **< 150MB** total memory usage
387
+
388
+ ## Performance Tips
389
+
390
+ 1. **Use video.id as key** - Ensures proper virtualization
391
+ 2. **Preload thumbnails** - Use blur placeholders
392
+ 3. **Let the system handle preloading** - usePreload manages queue automatically
393
+ 4. **Dispose properly** - Memory manager handles cleanup
394
+ 5. **Use design tokens** - All components use @xhub-reel/core tokens
395
+
396
+ ## Design System Integration
397
+
398
+ All components use design tokens from @xhub-reel/core:
399
+
400
+ ```tsx
401
+ import {
402
+ colors, // colors.background, colors.accent, etc.
403
+ spacing, // spacing[1] - spacing[8]
404
+ fontSizes, // fontSizes.xs, sm, md, lg
405
+ fontWeights, // fontWeights.medium, semibold, bold
406
+ radii, // radii.sm, md, lg, full
407
+ zIndices, // zIndices.base, sticky, overlay
408
+ durations, // durations.fast, normal, slow
409
+ easings, // easings.xhubReel (cubic-bezier)
410
+ } from '@xhub-reel/core'
411
+ ```
412
+
413
+ This ensures consistent styling across all packages.
414
+
415
+ ## Migration Guide
416
+
417
+ ### From v0.0.0 to v0.0.1
418
+
419
+ #### 1. Update usePreloader
420
+
421
+ ```tsx
422
+ // Before
423
+ import { usePreloader } from '@xhub-reel/feed'
424
+ const { preloadStates } = usePreloader({ videos, currentIndex })
425
+
426
+ // After
427
+ import { usePreload, getPreloadPriorityForFeed } from '@xhub-reel/feed'
428
+ const { statuses } = usePreload()
429
+ const priority = getPreloadPriorityForFeed(index, currentIndex)
430
+ ```
431
+
432
+ #### 2. ActionBar Styling
433
+
434
+ If you were overriding ActionBar styles, they may not work anymore since VideoFeedItem now uses @xhub-reel/ui ActionBar component. Use ActionBar directly for custom styling:
435
+
436
+ ```tsx
437
+ import { ActionBar } from '@xhub-reel/ui'
438
+
439
+ <ActionBar
440
+ likeCount={video.stats.likes}
441
+ // ... props with custom styling
442
+ style={{ right: 24 }}
443
+ />
444
+ ```
445
+
446
+ #### 3. VideoOverlay
447
+
448
+ If you were accessing internal overlay elements, use the new VideoOverlay component:
449
+
450
+ ```tsx
451
+ import { VideoOverlay } from '@xhub-reel/feed'
452
+
453
+ <VideoOverlay
454
+ video={video}
455
+ onAuthorClick={handleAuthorClick}
456
+ timelineExpanded={timelineExpanded}
457
+ />
458
+ ```
459
+
460
+ ## License
461
+
462
+ MIT