@shortkitsdk/react-native 0.2.0 → 0.2.2
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/android/src/main/java/com/shortkit/reactnative/ShortKitCarouselOverlayBridge.kt +48 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +48 -6
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +180 -2
- package/android/src/main/java/com/shortkit/reactnative/ShortKitOverlayBridge.kt +28 -1
- package/android/src/main/java/com/shortkit/reactnative/ShortKitPackage.kt +5 -1
- package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerNativeView.kt +136 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerViewManager.kt +35 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitWidgetNativeView.kt +133 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitWidgetViewManager.kt +30 -0
- package/ios/ShortKitBridge.swift +134 -2
- package/ios/ShortKitCarouselOverlayBridge.swift +54 -0
- package/ios/ShortKitFeedView.swift +46 -7
- package/ios/ShortKitModule.mm +42 -0
- package/ios/ShortKitOverlayBridge.swift +23 -1
- package/ios/ShortKitPlayerNativeView.swift +186 -0
- package/ios/ShortKitPlayerNativeViewManager.mm +28 -0
- package/ios/ShortKitWidgetNativeView.swift +168 -0
- package/ios/ShortKitWidgetNativeViewManager.mm +27 -0
- package/package.json +1 -1
- package/src/CarouselOverlayManager.tsx +71 -0
- package/src/ShortKitContext.ts +18 -0
- package/src/ShortKitFeed.tsx +13 -0
- package/src/ShortKitPlayer.tsx +61 -0
- package/src/ShortKitProvider.tsx +161 -2
- package/src/ShortKitWidget.tsx +63 -0
- package/src/index.ts +15 -1
- package/src/serialization.ts +16 -2
- package/src/specs/NativeShortKitModule.ts +37 -0
- package/src/specs/ShortKitPlayerViewNativeComponent.ts +13 -0
- package/src/specs/ShortKitWidgetViewNativeComponent.ts +12 -0
- package/src/types.ts +82 -3
- package/src/useShortKit.ts +5 -1
- package/src/useShortKitCarousel.ts +29 -0
- package/src/useShortKitPlayer.ts +10 -2
package/src/ShortKitProvider.tsx
CHANGED
|
@@ -6,6 +6,8 @@ import type { ShortKitContextValue } from './ShortKitContext';
|
|
|
6
6
|
import type {
|
|
7
7
|
ShortKitProviderProps,
|
|
8
8
|
ContentItem,
|
|
9
|
+
ImageCarouselItem,
|
|
10
|
+
CustomFeedItem,
|
|
9
11
|
PlayerTime,
|
|
10
12
|
PlayerState,
|
|
11
13
|
CaptionTrack,
|
|
@@ -13,6 +15,7 @@ import type {
|
|
|
13
15
|
} from './types';
|
|
14
16
|
import {
|
|
15
17
|
serializeFeedConfigForModule,
|
|
18
|
+
serializeCustomFeedItems,
|
|
16
19
|
deserializePlayerState,
|
|
17
20
|
deserializeContentItem,
|
|
18
21
|
deserializePlayerTime,
|
|
@@ -34,10 +37,16 @@ interface State {
|
|
|
34
37
|
activeCaptionTrack: CaptionTrack | null;
|
|
35
38
|
activeCue: { text: string; startTime: number; endTime: number } | null;
|
|
36
39
|
prefetchedAheadCount: number;
|
|
40
|
+
remainingContentCount: number;
|
|
37
41
|
isActive: boolean;
|
|
38
42
|
isTransitioning: boolean;
|
|
39
43
|
lastOverlayTap: number;
|
|
40
44
|
lastOverlayDoubleTap: { x: number; y: number; id: number } | null;
|
|
45
|
+
currentCarouselItem: ImageCarouselItem | null;
|
|
46
|
+
nextCarouselItem: ImageCarouselItem | null;
|
|
47
|
+
isCarouselActive: boolean;
|
|
48
|
+
isCarouselTransitioning: boolean;
|
|
49
|
+
activeCellType: 'video' | 'carousel' | null;
|
|
41
50
|
}
|
|
42
51
|
|
|
43
52
|
const initialState: State = {
|
|
@@ -51,10 +60,16 @@ const initialState: State = {
|
|
|
51
60
|
activeCaptionTrack: null,
|
|
52
61
|
activeCue: null,
|
|
53
62
|
prefetchedAheadCount: 0,
|
|
63
|
+
remainingContentCount: 0,
|
|
54
64
|
isActive: false,
|
|
55
65
|
isTransitioning: false,
|
|
56
66
|
lastOverlayTap: 0,
|
|
57
67
|
lastOverlayDoubleTap: null,
|
|
68
|
+
currentCarouselItem: null,
|
|
69
|
+
nextCarouselItem: null,
|
|
70
|
+
isCarouselActive: false,
|
|
71
|
+
isCarouselTransitioning: false,
|
|
72
|
+
activeCellType: null,
|
|
58
73
|
};
|
|
59
74
|
|
|
60
75
|
type Action =
|
|
@@ -67,13 +82,20 @@ type Action =
|
|
|
67
82
|
| { type: 'CAPTION_TRACK'; payload: CaptionTrack | null }
|
|
68
83
|
| { type: 'CUE'; payload: { text: string; startTime: number; endTime: number } | null }
|
|
69
84
|
| { type: 'PREFETCH_COUNT'; payload: number }
|
|
85
|
+
| { type: 'REMAINING_COUNT'; payload: number }
|
|
70
86
|
| { type: 'OVERLAY_CONFIGURE'; payload: ContentItem }
|
|
71
87
|
| { type: 'OVERLAY_ACTIVATE'; payload: ContentItem }
|
|
72
88
|
| { type: 'OVERLAY_RESET' }
|
|
73
89
|
| { type: 'OVERLAY_FADE_OUT' }
|
|
74
90
|
| { type: 'OVERLAY_RESTORE' }
|
|
75
91
|
| { type: 'OVERLAY_TAP' }
|
|
76
|
-
| { type: 'OVERLAY_DOUBLE_TAP'; payload: { x: number; y: number } }
|
|
92
|
+
| { type: 'OVERLAY_DOUBLE_TAP'; payload: { x: number; y: number } }
|
|
93
|
+
| { type: 'ACTIVE_CELL_TYPE'; payload: 'video' | 'carousel' }
|
|
94
|
+
| { type: 'CAROUSEL_OVERLAY_CONFIGURE'; payload: ImageCarouselItem }
|
|
95
|
+
| { type: 'CAROUSEL_OVERLAY_ACTIVATE'; payload: ImageCarouselItem }
|
|
96
|
+
| { type: 'CAROUSEL_OVERLAY_RESET' }
|
|
97
|
+
| { type: 'CAROUSEL_OVERLAY_FADE_OUT' }
|
|
98
|
+
| { type: 'CAROUSEL_OVERLAY_RESTORE' };
|
|
77
99
|
|
|
78
100
|
function reducer(state: State, action: Action): State {
|
|
79
101
|
switch (action.type) {
|
|
@@ -86,10 +108,13 @@ function reducer(state: State, action: Action): State {
|
|
|
86
108
|
action.payload === 'paused' ||
|
|
87
109
|
action.payload === 'buffering' ||
|
|
88
110
|
action.payload === 'seeking';
|
|
111
|
+
const becameActive = !state.isActive && isPlaybackActive;
|
|
89
112
|
return {
|
|
90
113
|
...state,
|
|
91
114
|
playerState: action.payload,
|
|
92
115
|
isActive: state.isActive || isPlaybackActive,
|
|
116
|
+
// First playback means a video cell is active (initial load)
|
|
117
|
+
activeCellType: becameActive ? 'video' : state.activeCellType,
|
|
93
118
|
};
|
|
94
119
|
}
|
|
95
120
|
case 'CURRENT_ITEM':
|
|
@@ -108,10 +133,25 @@ function reducer(state: State, action: Action): State {
|
|
|
108
133
|
return { ...state, activeCue: action.payload };
|
|
109
134
|
case 'PREFETCH_COUNT':
|
|
110
135
|
return { ...state, prefetchedAheadCount: action.payload };
|
|
136
|
+
case 'REMAINING_COUNT':
|
|
137
|
+
return { ...state, remainingContentCount: action.payload };
|
|
111
138
|
case 'OVERLAY_CONFIGURE':
|
|
112
139
|
return { ...state, nextItem: action.payload };
|
|
113
140
|
case 'OVERLAY_ACTIVATE':
|
|
114
|
-
|
|
141
|
+
// Reset time/cue/transition state to prevent stale playback data from
|
|
142
|
+
// the previous cell flashing on the new cell's overlay.
|
|
143
|
+
// Note: nextItem is NOT cleared here — the native-side deferred-configure
|
|
144
|
+
// (ShortKitOverlayBridge) already prevents handleSwipe from polluting it.
|
|
145
|
+
// Clearing it would cause overlay-next to unmount its content ~80ms before
|
|
146
|
+
// the page settle swaps overlays, creating a visible dim-layer gap.
|
|
147
|
+
return {
|
|
148
|
+
...state,
|
|
149
|
+
currentItem: action.payload,
|
|
150
|
+
isActive: true,
|
|
151
|
+
time: { current: 0, duration: action.payload.duration, buffered: 0 },
|
|
152
|
+
activeCue: null,
|
|
153
|
+
isTransitioning: false,
|
|
154
|
+
};
|
|
115
155
|
case 'OVERLAY_RESET':
|
|
116
156
|
// Don't set isActive = false — the overlay stays mounted during
|
|
117
157
|
// transitions. In the native SDK each cell has its own overlay
|
|
@@ -133,6 +173,18 @@ function reducer(state: State, action: Action): State {
|
|
|
133
173
|
id: (state.lastOverlayDoubleTap?.id ?? 0) + 1,
|
|
134
174
|
},
|
|
135
175
|
};
|
|
176
|
+
case 'ACTIVE_CELL_TYPE':
|
|
177
|
+
return { ...state, activeCellType: action.payload };
|
|
178
|
+
case 'CAROUSEL_OVERLAY_CONFIGURE':
|
|
179
|
+
return { ...state, nextCarouselItem: action.payload };
|
|
180
|
+
case 'CAROUSEL_OVERLAY_ACTIVATE':
|
|
181
|
+
return { ...state, currentCarouselItem: action.payload, isCarouselActive: true };
|
|
182
|
+
case 'CAROUSEL_OVERLAY_RESET':
|
|
183
|
+
return { ...state, isCarouselTransitioning: false };
|
|
184
|
+
case 'CAROUSEL_OVERLAY_FADE_OUT':
|
|
185
|
+
return { ...state, isCarouselTransitioning: true };
|
|
186
|
+
case 'CAROUSEL_OVERLAY_RESTORE':
|
|
187
|
+
return { ...state, isCarouselTransitioning: false };
|
|
136
188
|
default:
|
|
137
189
|
return state;
|
|
138
190
|
}
|
|
@@ -145,6 +197,7 @@ function reducer(state: State, action: Action): State {
|
|
|
145
197
|
export function ShortKitProvider({
|
|
146
198
|
apiKey,
|
|
147
199
|
config,
|
|
200
|
+
embedId,
|
|
148
201
|
userId,
|
|
149
202
|
clientAppName,
|
|
150
203
|
clientAppVersion,
|
|
@@ -168,6 +221,7 @@ export function ShortKitProvider({
|
|
|
168
221
|
NativeShortKitModule.initialize(
|
|
169
222
|
apiKey,
|
|
170
223
|
serializedConfig,
|
|
224
|
+
embedId,
|
|
171
225
|
clientAppName,
|
|
172
226
|
clientAppVersion,
|
|
173
227
|
serializedDimensions,
|
|
@@ -245,6 +299,7 @@ export function ShortKitProvider({
|
|
|
245
299
|
NativeShortKitModule.onCurrentItemChanged((event) => {
|
|
246
300
|
const item: ContentItem = {
|
|
247
301
|
id: event.id,
|
|
302
|
+
playbackId: event.playbackId,
|
|
248
303
|
title: event.title,
|
|
249
304
|
description: event.description,
|
|
250
305
|
duration: event.duration,
|
|
@@ -327,6 +382,13 @@ export function ShortKitProvider({
|
|
|
327
382
|
}),
|
|
328
383
|
);
|
|
329
384
|
|
|
385
|
+
// Remaining content count
|
|
386
|
+
subscriptions.push(
|
|
387
|
+
NativeShortKitModule.onRemainingContentCountChanged((event) => {
|
|
388
|
+
dispatch({ type: 'REMAINING_COUNT', payload: event.count });
|
|
389
|
+
}),
|
|
390
|
+
);
|
|
391
|
+
|
|
330
392
|
// Overlay lifecycle events
|
|
331
393
|
subscriptions.push(
|
|
332
394
|
NativeShortKitModule.onOverlayConfigure((event) => {
|
|
@@ -380,6 +442,61 @@ export function ShortKitProvider({
|
|
|
380
442
|
}),
|
|
381
443
|
);
|
|
382
444
|
|
|
445
|
+
// Feed transition — track active cell type
|
|
446
|
+
subscriptions.push(
|
|
447
|
+
NativeShortKitModule.onFeedTransition((event) => {
|
|
448
|
+
if (event.phase === 'ended') {
|
|
449
|
+
// toItem is null when the destination cell is non-video (carousel, survey, ad)
|
|
450
|
+
const isVideo = event.toItem != null;
|
|
451
|
+
dispatch({
|
|
452
|
+
type: 'ACTIVE_CELL_TYPE',
|
|
453
|
+
payload: isVideo ? 'video' : 'carousel',
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}),
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
// Carousel overlay lifecycle events
|
|
460
|
+
subscriptions.push(
|
|
461
|
+
NativeShortKitModule.onCarouselOverlayConfigure((event) => {
|
|
462
|
+
try {
|
|
463
|
+
const item = JSON.parse(event.item) as ImageCarouselItem;
|
|
464
|
+
dispatch({ type: 'CAROUSEL_OVERLAY_CONFIGURE', payload: item });
|
|
465
|
+
} catch {
|
|
466
|
+
// Ignore malformed JSON
|
|
467
|
+
}
|
|
468
|
+
}),
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
subscriptions.push(
|
|
472
|
+
NativeShortKitModule.onCarouselOverlayActivate((event) => {
|
|
473
|
+
try {
|
|
474
|
+
const item = JSON.parse(event.item) as ImageCarouselItem;
|
|
475
|
+
dispatch({ type: 'CAROUSEL_OVERLAY_ACTIVATE', payload: item });
|
|
476
|
+
} catch {
|
|
477
|
+
// Ignore malformed JSON
|
|
478
|
+
}
|
|
479
|
+
}),
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
subscriptions.push(
|
|
483
|
+
NativeShortKitModule.onCarouselOverlayReset((_event) => {
|
|
484
|
+
dispatch({ type: 'CAROUSEL_OVERLAY_RESET' });
|
|
485
|
+
}),
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
subscriptions.push(
|
|
489
|
+
NativeShortKitModule.onCarouselOverlayFadeOut((_event) => {
|
|
490
|
+
dispatch({ type: 'CAROUSEL_OVERLAY_FADE_OUT' });
|
|
491
|
+
}),
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
subscriptions.push(
|
|
495
|
+
NativeShortKitModule.onCarouselOverlayRestore((_event) => {
|
|
496
|
+
dispatch({ type: 'CAROUSEL_OVERLAY_RESTORE' });
|
|
497
|
+
}),
|
|
498
|
+
);
|
|
499
|
+
|
|
383
500
|
// Note: Feed-level callback events (onDidLoop, onFeedTransition,
|
|
384
501
|
// onShareTapped, etc.) are subscribed by the ShortKitFeed component
|
|
385
502
|
// (Task 11), not here. The provider only manages state-driving events.
|
|
@@ -450,6 +567,24 @@ export function ShortKitProvider({
|
|
|
450
567
|
NativeShortKitModule?.clearUserId();
|
|
451
568
|
}, []);
|
|
452
569
|
|
|
570
|
+
const setFeedItemsCmd = useCallback((items: CustomFeedItem[]) => {
|
|
571
|
+
NativeShortKitModule?.setFeedItems(serializeCustomFeedItems(items));
|
|
572
|
+
}, []);
|
|
573
|
+
|
|
574
|
+
const appendFeedItemsCmd = useCallback((items: CustomFeedItem[]) => {
|
|
575
|
+
NativeShortKitModule?.appendFeedItems(serializeCustomFeedItems(items));
|
|
576
|
+
}, []);
|
|
577
|
+
|
|
578
|
+
const fetchContentCmd = useCallback(async (limit: number = 10): Promise<ContentItem[]> => {
|
|
579
|
+
if (!NativeShortKitModule) return [];
|
|
580
|
+
const json = await NativeShortKitModule.fetchContent(limit);
|
|
581
|
+
try {
|
|
582
|
+
return JSON.parse(json) as ContentItem[];
|
|
583
|
+
} catch {
|
|
584
|
+
return [];
|
|
585
|
+
}
|
|
586
|
+
}, []);
|
|
587
|
+
|
|
453
588
|
// -----------------------------------------------------------------------
|
|
454
589
|
// Context value (memoized to avoid unnecessary re-renders)
|
|
455
590
|
// -----------------------------------------------------------------------
|
|
@@ -466,6 +601,7 @@ export function ShortKitProvider({
|
|
|
466
601
|
activeCaptionTrack: state.activeCaptionTrack,
|
|
467
602
|
activeCue: state.activeCue,
|
|
468
603
|
prefetchedAheadCount: state.prefetchedAheadCount,
|
|
604
|
+
remainingContentCount: state.remainingContentCount,
|
|
469
605
|
isActive: state.isActive,
|
|
470
606
|
isTransitioning: state.isTransitioning,
|
|
471
607
|
lastOverlayTap: state.lastOverlayTap,
|
|
@@ -486,9 +622,22 @@ export function ShortKitProvider({
|
|
|
486
622
|
setMaxBitrate,
|
|
487
623
|
setUserId: setUserIdCmd,
|
|
488
624
|
clearUserId: clearUserIdCmd,
|
|
625
|
+
setFeedItems: setFeedItemsCmd,
|
|
626
|
+
appendFeedItems: appendFeedItemsCmd,
|
|
627
|
+
fetchContent: fetchContentCmd,
|
|
628
|
+
|
|
629
|
+
// Active cell type
|
|
630
|
+
activeCellType: state.activeCellType,
|
|
631
|
+
|
|
632
|
+
// Carousel overlay state
|
|
633
|
+
currentCarouselItem: state.currentCarouselItem,
|
|
634
|
+
nextCarouselItem: state.nextCarouselItem,
|
|
635
|
+
isCarouselActive: state.isCarouselActive,
|
|
636
|
+
isCarouselTransitioning: state.isCarouselTransitioning,
|
|
489
637
|
|
|
490
638
|
// Internal — consumed by ShortKitFeed to pass to OverlayManager
|
|
491
639
|
_overlayConfig: config.overlay ?? 'none',
|
|
640
|
+
_carouselOverlayConfig: config.carouselOverlay ?? 'none',
|
|
492
641
|
}),
|
|
493
642
|
[
|
|
494
643
|
state.playerState,
|
|
@@ -501,10 +650,16 @@ export function ShortKitProvider({
|
|
|
501
650
|
state.activeCaptionTrack,
|
|
502
651
|
state.activeCue,
|
|
503
652
|
state.prefetchedAheadCount,
|
|
653
|
+
state.remainingContentCount,
|
|
504
654
|
state.isActive,
|
|
505
655
|
state.isTransitioning,
|
|
506
656
|
state.lastOverlayTap,
|
|
507
657
|
state.lastOverlayDoubleTap,
|
|
658
|
+
state.activeCellType,
|
|
659
|
+
state.currentCarouselItem,
|
|
660
|
+
state.nextCarouselItem,
|
|
661
|
+
state.isCarouselActive,
|
|
662
|
+
state.isCarouselTransitioning,
|
|
508
663
|
play,
|
|
509
664
|
pause,
|
|
510
665
|
seek,
|
|
@@ -519,7 +674,11 @@ export function ShortKitProvider({
|
|
|
519
674
|
setMaxBitrate,
|
|
520
675
|
setUserIdCmd,
|
|
521
676
|
clearUserIdCmd,
|
|
677
|
+
setFeedItemsCmd,
|
|
678
|
+
appendFeedItemsCmd,
|
|
679
|
+
fetchContentCmd,
|
|
522
680
|
config.overlay,
|
|
681
|
+
config.carouselOverlay,
|
|
523
682
|
],
|
|
524
683
|
);
|
|
525
684
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React, { useContext, useMemo } from 'react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import type { ShortKitWidgetProps } from './types';
|
|
4
|
+
import ShortKitWidgetView from './specs/ShortKitWidgetViewNativeComponent';
|
|
5
|
+
import { ShortKitContext } from './ShortKitContext';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Horizontal carousel widget component. Displays a row of video cards
|
|
9
|
+
* with automatic rotation. Wraps a native Fabric view.
|
|
10
|
+
*
|
|
11
|
+
* Must be rendered inside a `<ShortKitProvider>`.
|
|
12
|
+
*/
|
|
13
|
+
export function ShortKitWidget(props: ShortKitWidgetProps) {
|
|
14
|
+
const { config, items, style } = props;
|
|
15
|
+
|
|
16
|
+
const context = useContext(ShortKitContext);
|
|
17
|
+
if (!context) {
|
|
18
|
+
throw new Error('ShortKitWidget must be used within a ShortKitProvider');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const serializedConfig = useMemo(() => {
|
|
22
|
+
const cfg = config ?? {};
|
|
23
|
+
return JSON.stringify({
|
|
24
|
+
cardCount: cfg.cardCount ?? 3,
|
|
25
|
+
cardSpacing: cfg.cardSpacing ?? 8,
|
|
26
|
+
cornerRadius: cfg.cornerRadius ?? 12,
|
|
27
|
+
autoplay: cfg.autoplay ?? true,
|
|
28
|
+
muteOnStart: cfg.muteOnStart ?? true,
|
|
29
|
+
loop: cfg.loop ?? true,
|
|
30
|
+
rotationInterval: cfg.rotationInterval ?? 10000,
|
|
31
|
+
clickAction: cfg.clickAction ?? 'feed',
|
|
32
|
+
overlay: cfg.overlay
|
|
33
|
+
? typeof cfg.overlay === 'string'
|
|
34
|
+
? cfg.overlay
|
|
35
|
+
: { type: 'custom' }
|
|
36
|
+
: 'none',
|
|
37
|
+
});
|
|
38
|
+
}, [config]);
|
|
39
|
+
|
|
40
|
+
const serializedItems = useMemo(() => {
|
|
41
|
+
if (!items || items.length === 0) return undefined;
|
|
42
|
+
return JSON.stringify(items);
|
|
43
|
+
}, [items]);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<View style={[styles.container, style]}>
|
|
47
|
+
<ShortKitWidgetView
|
|
48
|
+
style={styles.widget}
|
|
49
|
+
config={serializedConfig}
|
|
50
|
+
items={serializedItems}
|
|
51
|
+
/>
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const styles = StyleSheet.create({
|
|
57
|
+
container: {
|
|
58
|
+
overflow: 'hidden',
|
|
59
|
+
},
|
|
60
|
+
widget: {
|
|
61
|
+
flex: 1,
|
|
62
|
+
},
|
|
63
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
export { ShortKitProvider } from './ShortKitProvider';
|
|
2
2
|
export { ShortKitFeed } from './ShortKitFeed';
|
|
3
|
+
export { ShortKitPlayer } from './ShortKitPlayer';
|
|
4
|
+
export { ShortKitWidget } from './ShortKitWidget';
|
|
3
5
|
export { useShortKitPlayer } from './useShortKitPlayer';
|
|
4
6
|
export { useShortKit } from './useShortKit';
|
|
7
|
+
export { useShortKitCarousel } from './useShortKitCarousel';
|
|
5
8
|
export type {
|
|
6
9
|
FeedConfig,
|
|
7
10
|
FeedHeight,
|
|
11
|
+
FeedSource,
|
|
8
12
|
OverlayConfig,
|
|
9
|
-
|
|
13
|
+
CarouselOverlayConfig,
|
|
10
14
|
SurveyMode,
|
|
11
15
|
|
|
16
|
+
PlayerConfig,
|
|
17
|
+
PlayerClickAction,
|
|
18
|
+
WidgetConfig,
|
|
19
|
+
|
|
12
20
|
ContentItem,
|
|
21
|
+
CarouselImage,
|
|
22
|
+
ImageCarouselItem,
|
|
23
|
+
CustomFeedItem,
|
|
13
24
|
JSONValue,
|
|
14
25
|
CaptionTrack,
|
|
15
26
|
PlayerTime,
|
|
@@ -22,5 +33,8 @@ export type {
|
|
|
22
33
|
ShortKitError,
|
|
23
34
|
ShortKitProviderProps,
|
|
24
35
|
ShortKitFeedProps,
|
|
36
|
+
ShortKitPlayerProps,
|
|
37
|
+
ShortKitWidgetProps,
|
|
25
38
|
ShortKitPlayerState,
|
|
26
39
|
} from './types';
|
|
40
|
+
export type { ShortKitCarouselState } from './useShortKitCarousel';
|
package/src/serialization.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
FeedConfig,
|
|
3
3
|
ContentItem,
|
|
4
|
+
CustomFeedItem,
|
|
4
5
|
PlayerState,
|
|
5
6
|
PlayerTime,
|
|
6
7
|
} from './types';
|
|
@@ -12,9 +13,10 @@ import type {
|
|
|
12
13
|
export interface SerializedFeedConfig {
|
|
13
14
|
feedHeight: string;
|
|
14
15
|
overlay: string;
|
|
15
|
-
|
|
16
|
+
carouselOverlay: string;
|
|
16
17
|
surveyMode: string;
|
|
17
18
|
muteOnStart: boolean;
|
|
19
|
+
feedSource: string;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -35,9 +37,14 @@ export function serializeFeedConfig(config: FeedConfig): SerializedFeedConfig {
|
|
|
35
37
|
return {
|
|
36
38
|
feedHeight: JSON.stringify(config.feedHeight ?? { type: 'fullscreen' }),
|
|
37
39
|
overlay,
|
|
38
|
-
|
|
40
|
+
carouselOverlay: (() => {
|
|
41
|
+
const raw = config.carouselOverlay ?? 'none';
|
|
42
|
+
if (typeof raw === 'string') return JSON.stringify(raw);
|
|
43
|
+
return JSON.stringify({ type: 'custom' });
|
|
44
|
+
})(),
|
|
39
45
|
surveyMode: JSON.stringify(config.surveyMode ?? 'none'),
|
|
40
46
|
muteOnStart: config.muteOnStart ?? true,
|
|
47
|
+
feedSource: config.feedSource ?? 'algorithmic',
|
|
41
48
|
};
|
|
42
49
|
}
|
|
43
50
|
|
|
@@ -93,3 +100,10 @@ export function deserializePlayerTime(event: {
|
|
|
93
100
|
buffered: event.buffered,
|
|
94
101
|
};
|
|
95
102
|
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Serialize CustomFeedItem[] to a JSON string for the bridge.
|
|
106
|
+
*/
|
|
107
|
+
export function serializeCustomFeedItems(items: CustomFeedItem[]): string {
|
|
108
|
+
return JSON.stringify(items);
|
|
109
|
+
}
|
|
@@ -11,6 +11,7 @@ type PlayerStateEvent = Readonly<{
|
|
|
11
11
|
|
|
12
12
|
type CurrentItemEvent = Readonly<{
|
|
13
13
|
id: string;
|
|
14
|
+
playbackId?: string;
|
|
14
15
|
title: string;
|
|
15
16
|
description?: string;
|
|
16
17
|
duration: Double;
|
|
@@ -77,6 +78,10 @@ type PrefetchedAheadCountEvent = Readonly<{
|
|
|
77
78
|
count: Int32;
|
|
78
79
|
}>;
|
|
79
80
|
|
|
81
|
+
type RemainingContentCountEvent = Readonly<{
|
|
82
|
+
count: Int32;
|
|
83
|
+
}>;
|
|
84
|
+
|
|
80
85
|
type ErrorEvent = Readonly<{
|
|
81
86
|
code: string;
|
|
82
87
|
message: string;
|
|
@@ -112,6 +117,25 @@ type OverlayRestoreEvent = Readonly<{
|
|
|
112
117
|
item: string; // JSON-serialized ContentItem
|
|
113
118
|
}>;
|
|
114
119
|
|
|
120
|
+
type ContentTappedEvent = Readonly<{
|
|
121
|
+
contentId: string;
|
|
122
|
+
index: Int32;
|
|
123
|
+
}>;
|
|
124
|
+
|
|
125
|
+
type CarouselOverlayConfigureEvent = Readonly<{
|
|
126
|
+
item: string; // JSON-serialized ImageCarouselItem
|
|
127
|
+
}>;
|
|
128
|
+
|
|
129
|
+
type CarouselOverlayActivateEvent = Readonly<{
|
|
130
|
+
item: string; // JSON-serialized ImageCarouselItem
|
|
131
|
+
}>;
|
|
132
|
+
|
|
133
|
+
type CarouselOverlayResetEvent = Readonly<{}>;
|
|
134
|
+
|
|
135
|
+
type CarouselOverlayFadeOutEvent = Readonly<{}>;
|
|
136
|
+
|
|
137
|
+
type CarouselOverlayRestoreEvent = Readonly<{}>;
|
|
138
|
+
|
|
115
139
|
type OverlayTapEvent = Readonly<{}>;
|
|
116
140
|
|
|
117
141
|
type OverlayDoubleTapEvent = Readonly<{
|
|
@@ -124,6 +148,7 @@ export interface Spec extends TurboModule {
|
|
|
124
148
|
initialize(
|
|
125
149
|
apiKey: string,
|
|
126
150
|
config: string, // JSON-serialized FeedConfig
|
|
151
|
+
embedId?: string,
|
|
127
152
|
clientAppName?: string,
|
|
128
153
|
clientAppVersion?: string,
|
|
129
154
|
customDimensions?: string, // JSON-serialized Record<string, string>
|
|
@@ -148,6 +173,11 @@ export interface Spec extends TurboModule {
|
|
|
148
173
|
sendContentSignal(signal: string): void;
|
|
149
174
|
setMaxBitrate(bitrate: Double): void;
|
|
150
175
|
|
|
176
|
+
// --- Custom feed ---
|
|
177
|
+
setFeedItems(items: string): void;
|
|
178
|
+
appendFeedItems(items: string): void;
|
|
179
|
+
fetchContent(limit: Int32): Promise<string>;
|
|
180
|
+
|
|
151
181
|
// --- Event emitters ---
|
|
152
182
|
readonly onPlayerStateChanged: EventEmitter<PlayerStateEvent>;
|
|
153
183
|
readonly onCurrentItemChanged: EventEmitter<CurrentItemEvent>;
|
|
@@ -161,6 +191,7 @@ export interface Spec extends TurboModule {
|
|
|
161
191
|
readonly onFeedTransition: EventEmitter<FeedTransitionEvent>;
|
|
162
192
|
readonly onFormatChange: EventEmitter<FormatChangeEvent>;
|
|
163
193
|
readonly onPrefetchedAheadCountChanged: EventEmitter<PrefetchedAheadCountEvent>;
|
|
194
|
+
readonly onRemainingContentCountChanged: EventEmitter<RemainingContentCountEvent>;
|
|
164
195
|
readonly onError: EventEmitter<ErrorEvent>;
|
|
165
196
|
readonly onShareTapped: EventEmitter<ShareTappedEvent>;
|
|
166
197
|
readonly onSurveyResponse: EventEmitter<SurveyResponseEvent>;
|
|
@@ -171,6 +202,12 @@ export interface Spec extends TurboModule {
|
|
|
171
202
|
readonly onOverlayRestore: EventEmitter<OverlayRestoreEvent>;
|
|
172
203
|
readonly onOverlayTap: EventEmitter<OverlayTapEvent>;
|
|
173
204
|
readonly onOverlayDoubleTap: EventEmitter<OverlayDoubleTapEvent>;
|
|
205
|
+
readonly onContentTapped: EventEmitter<ContentTappedEvent>;
|
|
206
|
+
readonly onCarouselOverlayConfigure: EventEmitter<CarouselOverlayConfigureEvent>;
|
|
207
|
+
readonly onCarouselOverlayActivate: EventEmitter<CarouselOverlayActivateEvent>;
|
|
208
|
+
readonly onCarouselOverlayReset: EventEmitter<CarouselOverlayResetEvent>;
|
|
209
|
+
readonly onCarouselOverlayFadeOut: EventEmitter<CarouselOverlayFadeOutEvent>;
|
|
210
|
+
readonly onCarouselOverlayRestore: EventEmitter<CarouselOverlayRestoreEvent>;
|
|
174
211
|
}
|
|
175
212
|
|
|
176
213
|
export default TurboModuleRegistry.getEnforcing<Spec>('ShortKitModule');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HostComponent, ViewProps } from 'react-native';
|
|
2
|
+
import { codegenNativeComponent } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface NativeProps extends ViewProps {
|
|
5
|
+
config: string;
|
|
6
|
+
contentItem?: string;
|
|
7
|
+
active?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default codegenNativeComponent<NativeProps>(
|
|
11
|
+
'ShortKitPlayerView',
|
|
12
|
+
{},
|
|
13
|
+
) as HostComponent<NativeProps>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HostComponent, ViewProps } from 'react-native';
|
|
2
|
+
import { codegenNativeComponent } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface NativeProps extends ViewProps {
|
|
5
|
+
config: string;
|
|
6
|
+
items?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default codegenNativeComponent<NativeProps>(
|
|
10
|
+
'ShortKitWidgetView',
|
|
11
|
+
{},
|
|
12
|
+
) as HostComponent<NativeProps>;
|