@xhub-reels/sdk 0.2.11 → 0.2.13
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/README.md +91 -13
- package/dist/index.cjs +767 -57
- package/dist/index.d.cts +222 -4
- package/dist/index.d.ts +222 -4
- package/dist/index.js +763 -59
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -443,6 +443,7 @@ declare class PlayerEngine {
|
|
|
443
443
|
private circuitResetTimer;
|
|
444
444
|
private watchTimeInterval;
|
|
445
445
|
private lastStatus;
|
|
446
|
+
private _destroyed;
|
|
446
447
|
constructor(config?: PlayerConfig, analytics?: IAnalytics, logger?: ILogger);
|
|
447
448
|
/**
|
|
448
449
|
* Load a video and prepare for playback.
|
|
@@ -498,11 +499,14 @@ declare class FeedManager {
|
|
|
498
499
|
private inFlightRequests;
|
|
499
500
|
/** LRU tracking: itemId → lastAccessTime */
|
|
500
501
|
private accessOrder;
|
|
502
|
+
/** Idempotency guard for destroy() */
|
|
503
|
+
private _destroyed;
|
|
501
504
|
/** Prefetch cache — instance-scoped (not static) */
|
|
502
505
|
private prefetchCache;
|
|
503
506
|
constructor(dataSource: IDataSource, config?: FeedConfig, logger?: ILogger);
|
|
504
507
|
getDataSource(): IDataSource;
|
|
505
508
|
setDataSource(dataSource: IDataSource, reset?: boolean): void;
|
|
509
|
+
setInitialItems(items: ContentItem[]): void;
|
|
506
510
|
prefetch(ttlMs?: number): Promise<void>;
|
|
507
511
|
hasPrefetchCache(): boolean;
|
|
508
512
|
clearPrefetchCache(): void;
|
|
@@ -556,6 +560,8 @@ declare class OptimisticManager {
|
|
|
556
560
|
private likeDebounceTimers;
|
|
557
561
|
/** Pending like direction: contentId → final intended state */
|
|
558
562
|
private pendingLikeState;
|
|
563
|
+
/** Idempotency guard for destroy() */
|
|
564
|
+
private _destroyed;
|
|
559
565
|
constructor(interaction: Partial<IInteraction>, logger?: ILogger);
|
|
560
566
|
/**
|
|
561
567
|
* Debounced like toggle — prevents rapid API spam on double-tap.
|
|
@@ -626,6 +632,7 @@ declare class ResourceGovernor {
|
|
|
626
632
|
private readonly logger?;
|
|
627
633
|
private focusDebounceTimer;
|
|
628
634
|
private networkUnsubscribe?;
|
|
635
|
+
private _destroyed;
|
|
629
636
|
constructor(config?: ResourceConfig, videoLoader?: IVideoLoader, network?: INetworkAdapter, logger?: ILogger);
|
|
630
637
|
activate(): Promise<void>;
|
|
631
638
|
deactivate(): void;
|
|
@@ -658,6 +665,103 @@ declare class ResourceGovernor {
|
|
|
658
665
|
private recalculate;
|
|
659
666
|
}
|
|
660
667
|
|
|
668
|
+
/**
|
|
669
|
+
* NavigationManager — Owns the open/close lifecycle of the SDK reels viewer.
|
|
670
|
+
*
|
|
671
|
+
* Framework-agnostic class (zustand/vanilla) mirroring FeedManager /
|
|
672
|
+
* ResourceGovernor / PlayerEngine conventions. It is the single source of
|
|
673
|
+
* truth for "is the reels viewer open, and at which index".
|
|
674
|
+
*
|
|
675
|
+
* WHY THIS EXISTS:
|
|
676
|
+
* Previously the host app owned the modal entirely (via onThumbnailClick).
|
|
677
|
+
* That meant the SDK could not warm a video before the host mounted
|
|
678
|
+
* <ReelsFeed>, producing the "poster stuck ~3s" stall. By owning the
|
|
679
|
+
* open/close phase, the SDK can:
|
|
680
|
+
* 1. mount <ReelsFeed> early (hidden behind the animating container),
|
|
681
|
+
* 2. prewarm the focused video DURING the open animation,
|
|
682
|
+
* 3. reveal an already-decoded first frame → no poster stall.
|
|
683
|
+
*
|
|
684
|
+
* PHASE MODEL (state machine):
|
|
685
|
+
* closed → opening : open(index) called (e.g. from a thumbnail tap)
|
|
686
|
+
* opening → open : ReelsModal finished its enter animation
|
|
687
|
+
* open → closing : close() called
|
|
688
|
+
* closing → closed : ReelsModal finished its exit animation
|
|
689
|
+
*
|
|
690
|
+
* Invalid transitions are dropped (see isValidNavTransition). This mirrors
|
|
691
|
+
* the PlayerEngine state-machine discipline used elsewhere in the SDK.
|
|
692
|
+
*
|
|
693
|
+
* The component layer (ReelsModal) drives the animation timing and calls
|
|
694
|
+
* `setPhase('open')` / `setPhase('closed')` when its Web Animations API
|
|
695
|
+
* transitions finish. NavigationManager itself holds no timers tied to
|
|
696
|
+
* animation — it only holds a safety fallback timer so a missing/janky
|
|
697
|
+
* animationend can never strand the phase forever.
|
|
698
|
+
*/
|
|
699
|
+
|
|
700
|
+
type NavigationPhase = 'closed' | 'opening' | 'open' | 'closing';
|
|
701
|
+
interface NavigationState {
|
|
702
|
+
/** Current lifecycle phase of the reels viewer. */
|
|
703
|
+
phase: NavigationPhase;
|
|
704
|
+
/**
|
|
705
|
+
* Index the viewer was opened at. Held through the whole open lifecycle
|
|
706
|
+
* (opening → open → closing) and cleared back to null only once fully
|
|
707
|
+
* closed. ReelsModal mounts <ReelsFeed> whenever this is non-null so the
|
|
708
|
+
* feed can warm during `opening`.
|
|
709
|
+
*/
|
|
710
|
+
openIndex: number | null;
|
|
711
|
+
}
|
|
712
|
+
interface NavigationConfig {
|
|
713
|
+
/**
|
|
714
|
+
* Safety fallback (ms). If the component never reports its enter/exit
|
|
715
|
+
* animation finished, the manager force-advances the phase so the viewer
|
|
716
|
+
* can never get stuck in `opening`/`closing`. Set generously above the
|
|
717
|
+
* real animation duration. Default: 1200.
|
|
718
|
+
*/
|
|
719
|
+
phaseTimeoutMs?: number;
|
|
720
|
+
}
|
|
721
|
+
declare const DEFAULT_NAVIGATION_CONFIG: Required<NavigationConfig>;
|
|
722
|
+
declare function isValidNavTransition(from: NavigationPhase, to: NavigationPhase): boolean;
|
|
723
|
+
declare class NavigationManager {
|
|
724
|
+
readonly store: StoreApi<NavigationState>;
|
|
725
|
+
private readonly config;
|
|
726
|
+
private readonly logger?;
|
|
727
|
+
/** Fallback timer guarding against a missing animationend report. */
|
|
728
|
+
private phaseTimer;
|
|
729
|
+
private _destroyed;
|
|
730
|
+
constructor(config?: NavigationConfig, logger?: ILogger);
|
|
731
|
+
/**
|
|
732
|
+
* Open the reels viewer at `index`. Safe to call from a thumbnail click
|
|
733
|
+
* handler — it transitions into `opening`, at which point ReelsModal mounts
|
|
734
|
+
* <ReelsFeed> hidden and begins prewarming.
|
|
735
|
+
*
|
|
736
|
+
* Calling open() while already open/opening just retargets the index
|
|
737
|
+
* (e.g. user taps a different thumbnail before the animation settles).
|
|
738
|
+
*/
|
|
739
|
+
open(index: number): void;
|
|
740
|
+
/**
|
|
741
|
+
* Begin closing the viewer. ReelsModal animates out then calls
|
|
742
|
+
* `setPhase('closed')`. Idempotent if already closing/closed.
|
|
743
|
+
*/
|
|
744
|
+
close(): void;
|
|
745
|
+
/**
|
|
746
|
+
* Report an animation-driven phase change from the component layer.
|
|
747
|
+
* Invalid transitions are dropped. When reaching `closed`, openIndex is
|
|
748
|
+
* cleared so <ReelsFeed> unmounts.
|
|
749
|
+
*/
|
|
750
|
+
setPhase(next: NavigationPhase): void;
|
|
751
|
+
getPhase(): NavigationPhase;
|
|
752
|
+
getOpenIndex(): number | null;
|
|
753
|
+
isOpen(): boolean;
|
|
754
|
+
/** Whether <ReelsFeed> should be mounted (any non-closed phase). */
|
|
755
|
+
shouldMount(): boolean;
|
|
756
|
+
destroy(): void;
|
|
757
|
+
/**
|
|
758
|
+
* Apply a guarded phase transition. Returns true if it was applied.
|
|
759
|
+
* Manages the fallback timer for transient phases (opening/closing).
|
|
760
|
+
*/
|
|
761
|
+
private transition;
|
|
762
|
+
private clearPhaseTimer;
|
|
763
|
+
}
|
|
764
|
+
|
|
661
765
|
/**
|
|
662
766
|
* Player State Machine — Guarded transitions
|
|
663
767
|
*
|
|
@@ -776,15 +880,20 @@ interface SDKContextValue {
|
|
|
776
880
|
playerEngine: PlayerEngine;
|
|
777
881
|
resourceGovernor: ResourceGovernor;
|
|
778
882
|
optimisticManager: OptimisticManager;
|
|
883
|
+
navigationManager: NavigationManager;
|
|
779
884
|
adapters: Required<Omit<SDKAdapters, 'dataSource'>> & Pick<SDKAdapters, 'dataSource'>;
|
|
780
885
|
}
|
|
781
886
|
interface ReelsProviderProps {
|
|
782
887
|
children: ReactNode;
|
|
783
888
|
adapters: SDKAdapters;
|
|
889
|
+
/** Seed initial items into feedManager */
|
|
890
|
+
initialItems?: ContentItem[];
|
|
784
891
|
/** Enable verbose logging (default: false) */
|
|
785
892
|
debug?: boolean;
|
|
893
|
+
/** Config for the owned reels viewer open/close lifecycle (used by ReelsModal) */
|
|
894
|
+
navigationConfig?: NavigationConfig;
|
|
786
895
|
}
|
|
787
|
-
declare function ReelsProvider({ children, adapters, debug }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
|
|
896
|
+
declare function ReelsProvider({ children, adapters, initialItems, debug, navigationConfig, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
|
|
788
897
|
declare function useSDK(): SDKContextValue;
|
|
789
898
|
|
|
790
899
|
declare function ReelsFeed({ renderOverlay, renderActions, renderPauseAction, renderLoading, renderEmpty, renderError: _renderError, showFps, loadMoreThreshold, onSlotChange, gestureConfig, snapConfig, initialMuted, onAutoplayBlocked, }: ReelsFeedProps): string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element | null | undefined;
|
|
@@ -809,12 +918,97 @@ interface ReelsFeedThumbnailProps {
|
|
|
809
918
|
wrap?: boolean;
|
|
810
919
|
/** Enable click → update focused index on ReelsProvider before host reacts. Default: true. */
|
|
811
920
|
setFocusOnClick?: boolean;
|
|
812
|
-
/**
|
|
921
|
+
/**
|
|
922
|
+
* Open the SDK-owned <ReelsModal> on click (calls NavigationManager.open).
|
|
923
|
+
* This is the recommended path — the SDK controls modal timing and prewarms
|
|
924
|
+
* the video during the open animation, eliminating the "poster stuck ~3s"
|
|
925
|
+
* stall. Requires a <ReelsModal> mounted under the same <ReelsProvider>.
|
|
926
|
+
*
|
|
927
|
+
* Leave false (default) to keep the headless behaviour where the host owns
|
|
928
|
+
* the modal and reacts via `onThumbnailClick`.
|
|
929
|
+
*
|
|
930
|
+
* @default false
|
|
931
|
+
*/
|
|
932
|
+
openOnClick?: boolean;
|
|
933
|
+
/**
|
|
934
|
+
* Prefetch HLS metadata for this slot on pointer enter (desktop hover).
|
|
935
|
+
* Useless on mobile WebView (no hover) — use `prewarmOnClick` there.
|
|
936
|
+
* Off by default.
|
|
937
|
+
*/
|
|
813
938
|
prefetchOnHover?: boolean;
|
|
939
|
+
/**
|
|
940
|
+
* Prefetch HLS metadata for the tapped slot synchronously on click, inside
|
|
941
|
+
* the tap gesture. This is the mobile-friendly counterpart to
|
|
942
|
+
* `prefetchOnHover` and starts buffering the instant the user taps — before
|
|
943
|
+
* any modal mounts. Recommended for mobile WebView. Default: true.
|
|
944
|
+
*/
|
|
945
|
+
prewarmOnClick?: boolean;
|
|
814
946
|
/** Key override — useful when host renders multiple lists from the same feed. */
|
|
815
947
|
getKey?: (item: ContentItem, index: number) => string;
|
|
816
948
|
}
|
|
817
|
-
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
949
|
+
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, openOnClick, prefetchOnHover, prewarmOnClick, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
950
|
+
|
|
951
|
+
interface ReelsModalAnimationConfig {
|
|
952
|
+
/** Enter/exit duration in ms (default: 300) */
|
|
953
|
+
duration?: number;
|
|
954
|
+
/** CSS easing (default: 'cubic-bezier(0.22, 1, 0.36, 1)') */
|
|
955
|
+
easing?: string;
|
|
956
|
+
/**
|
|
957
|
+
* Panel travel direction:
|
|
958
|
+
* - 'up' (default): slides up from the bottom edge (bottom-sheet drawer)
|
|
959
|
+
* - 'down': slides down from the top edge
|
|
960
|
+
* - 'fade': no translate, opacity only
|
|
961
|
+
*/
|
|
962
|
+
direction?: 'up' | 'down' | 'fade';
|
|
963
|
+
}
|
|
964
|
+
/** State handed to render-prop customisation callbacks. */
|
|
965
|
+
interface ReelsModalRenderState {
|
|
966
|
+
phase: NavigationPhase;
|
|
967
|
+
openIndex: number | null;
|
|
968
|
+
/** Begin closing the viewer (runs the exit animation). */
|
|
969
|
+
close: () => void;
|
|
970
|
+
}
|
|
971
|
+
interface ReelsModalProps {
|
|
972
|
+
/** Props forwarded to the inner <ReelsFeed> (render callbacks, initialMuted, etc.). */
|
|
973
|
+
feedProps?: ReelsFeedProps;
|
|
974
|
+
/** Enter/exit animation overrides. Defaults applied when omitted. */
|
|
975
|
+
animationConfig?: ReelsModalAnimationConfig;
|
|
976
|
+
/**
|
|
977
|
+
* Custom backdrop visual. Rendered inside the SDK-owned backdrop layer,
|
|
978
|
+
* whose opacity the SDK animates. If omitted, a semi-transparent black
|
|
979
|
+
* backdrop is used. Return null to suppress the default visual.
|
|
980
|
+
*/
|
|
981
|
+
renderBackdrop?: (state: ReelsModalRenderState) => ReactNode;
|
|
982
|
+
/**
|
|
983
|
+
* Custom close affordance, rendered above <ReelsFeed> inside the panel.
|
|
984
|
+
* If omitted, a top-right ✕ button is shown. Return null to hide it
|
|
985
|
+
* (e.g. when the host relies on swipe-to-dismiss or its own chrome).
|
|
986
|
+
*/
|
|
987
|
+
renderCloseButton?: (state: ReelsModalRenderState) => ReactNode;
|
|
988
|
+
/** Fired when the enter animation completes (viewer fully open). */
|
|
989
|
+
onOpen?: (index: number | null) => void;
|
|
990
|
+
/** Fired when the exit animation completes (viewer fully closed). */
|
|
991
|
+
onClose?: () => void;
|
|
992
|
+
/** Close when the backdrop is clicked. Default: true. */
|
|
993
|
+
closeOnBackdropClick?: boolean;
|
|
994
|
+
/** Close when Escape is pressed. Default: true. */
|
|
995
|
+
closeOnEscape?: boolean;
|
|
996
|
+
/** Lock body scroll while open. Default: true. */
|
|
997
|
+
lockBodyScroll?: boolean;
|
|
998
|
+
/**
|
|
999
|
+
* How many slots to prewarm on open. `prewarmForward` items ahead of the
|
|
1000
|
+
* focused index plus 1 behind get an immediate preloadMetadata() call so
|
|
1001
|
+
* buffering starts in the same tick as the tap. Default: 2.
|
|
1002
|
+
*/
|
|
1003
|
+
prewarmForward?: number;
|
|
1004
|
+
/** className on the sliding panel element. */
|
|
1005
|
+
className?: string;
|
|
1006
|
+
/** z-index for the modal root. Default: 1000. */
|
|
1007
|
+
zIndex?: number;
|
|
1008
|
+
/** Portal target. Default: document.body. */
|
|
1009
|
+
portalTarget?: HTMLElement | null;
|
|
1010
|
+
}
|
|
1011
|
+
declare function ReelsModal({ feedProps, animationConfig, renderBackdrop, renderCloseButton, onOpen, onClose, closeOnBackdropClick, closeOnEscape, lockBodyScroll, prewarmForward, className, zIndex, portalTarget, }: ReelsModalProps): react.ReactPortal | null;
|
|
818
1012
|
|
|
819
1013
|
/**
|
|
820
1014
|
* useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
|
|
@@ -1001,6 +1195,30 @@ declare function useResource(): {
|
|
|
1001
1195
|
getTier: (index: number) => 3 | 1 | 2 | 4 | null;
|
|
1002
1196
|
};
|
|
1003
1197
|
|
|
1198
|
+
/**
|
|
1199
|
+
* useNavigation — React hook for NavigationManager
|
|
1200
|
+
*
|
|
1201
|
+
* Subscribes to the reels viewer open/close lifecycle via useSyncExternalStore.
|
|
1202
|
+
* Mirrors the useResource / useFeed selector pattern — no Zustand React bindings.
|
|
1203
|
+
*
|
|
1204
|
+
* Primary consumers:
|
|
1205
|
+
* - ReelsModal: reads `phase` / `openIndex` to mount + animate the viewer,
|
|
1206
|
+
* and calls `setPhase` when its enter/exit animation finishes.
|
|
1207
|
+
* - ReelsFeedThumbnail: calls `open(index)` on click when `openOnClick` is set.
|
|
1208
|
+
* - Host app: can read `isOpen` / call `close()` to drive its own chrome.
|
|
1209
|
+
*/
|
|
1210
|
+
|
|
1211
|
+
declare function useNavigationSelector<T>(selector: (state: NavigationState) => T): T;
|
|
1212
|
+
declare function useNavigation(): {
|
|
1213
|
+
phase: NavigationPhase;
|
|
1214
|
+
openIndex: number | null;
|
|
1215
|
+
isOpen: boolean;
|
|
1216
|
+
shouldMount: boolean;
|
|
1217
|
+
open: (index: number) => void;
|
|
1218
|
+
close: () => void;
|
|
1219
|
+
setPhase: (next: NavigationPhase) => void;
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1004
1222
|
/**
|
|
1005
1223
|
* Mock adapters for development and testing
|
|
1006
1224
|
*/
|
|
@@ -1201,4 +1419,4 @@ declare class HttpError extends Error {
|
|
|
1201
1419
|
constructor(status: number, message: string, body?: string | undefined);
|
|
1202
1420
|
}
|
|
1203
1421
|
|
|
1204
|
-
export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NetworkType, OptimisticManager, type PauseSlotActions, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsFeedThumbnail, type ReelsFeedThumbnailProps, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|
|
1422
|
+
export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_NAVIGATION_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NavigationConfig, NavigationManager, type NavigationPhase, type NavigationState, type NetworkType, OptimisticManager, type PauseSlotActions, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsFeedThumbnail, type ReelsFeedThumbnailProps, ReelsModal, type ReelsModalAnimationConfig, type ReelsModalProps, type ReelsModalRenderState, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidNavTransition, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, useNavigation, useNavigationSelector, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|
package/dist/index.d.ts
CHANGED
|
@@ -443,6 +443,7 @@ declare class PlayerEngine {
|
|
|
443
443
|
private circuitResetTimer;
|
|
444
444
|
private watchTimeInterval;
|
|
445
445
|
private lastStatus;
|
|
446
|
+
private _destroyed;
|
|
446
447
|
constructor(config?: PlayerConfig, analytics?: IAnalytics, logger?: ILogger);
|
|
447
448
|
/**
|
|
448
449
|
* Load a video and prepare for playback.
|
|
@@ -498,11 +499,14 @@ declare class FeedManager {
|
|
|
498
499
|
private inFlightRequests;
|
|
499
500
|
/** LRU tracking: itemId → lastAccessTime */
|
|
500
501
|
private accessOrder;
|
|
502
|
+
/** Idempotency guard for destroy() */
|
|
503
|
+
private _destroyed;
|
|
501
504
|
/** Prefetch cache — instance-scoped (not static) */
|
|
502
505
|
private prefetchCache;
|
|
503
506
|
constructor(dataSource: IDataSource, config?: FeedConfig, logger?: ILogger);
|
|
504
507
|
getDataSource(): IDataSource;
|
|
505
508
|
setDataSource(dataSource: IDataSource, reset?: boolean): void;
|
|
509
|
+
setInitialItems(items: ContentItem[]): void;
|
|
506
510
|
prefetch(ttlMs?: number): Promise<void>;
|
|
507
511
|
hasPrefetchCache(): boolean;
|
|
508
512
|
clearPrefetchCache(): void;
|
|
@@ -556,6 +560,8 @@ declare class OptimisticManager {
|
|
|
556
560
|
private likeDebounceTimers;
|
|
557
561
|
/** Pending like direction: contentId → final intended state */
|
|
558
562
|
private pendingLikeState;
|
|
563
|
+
/** Idempotency guard for destroy() */
|
|
564
|
+
private _destroyed;
|
|
559
565
|
constructor(interaction: Partial<IInteraction>, logger?: ILogger);
|
|
560
566
|
/**
|
|
561
567
|
* Debounced like toggle — prevents rapid API spam on double-tap.
|
|
@@ -626,6 +632,7 @@ declare class ResourceGovernor {
|
|
|
626
632
|
private readonly logger?;
|
|
627
633
|
private focusDebounceTimer;
|
|
628
634
|
private networkUnsubscribe?;
|
|
635
|
+
private _destroyed;
|
|
629
636
|
constructor(config?: ResourceConfig, videoLoader?: IVideoLoader, network?: INetworkAdapter, logger?: ILogger);
|
|
630
637
|
activate(): Promise<void>;
|
|
631
638
|
deactivate(): void;
|
|
@@ -658,6 +665,103 @@ declare class ResourceGovernor {
|
|
|
658
665
|
private recalculate;
|
|
659
666
|
}
|
|
660
667
|
|
|
668
|
+
/**
|
|
669
|
+
* NavigationManager — Owns the open/close lifecycle of the SDK reels viewer.
|
|
670
|
+
*
|
|
671
|
+
* Framework-agnostic class (zustand/vanilla) mirroring FeedManager /
|
|
672
|
+
* ResourceGovernor / PlayerEngine conventions. It is the single source of
|
|
673
|
+
* truth for "is the reels viewer open, and at which index".
|
|
674
|
+
*
|
|
675
|
+
* WHY THIS EXISTS:
|
|
676
|
+
* Previously the host app owned the modal entirely (via onThumbnailClick).
|
|
677
|
+
* That meant the SDK could not warm a video before the host mounted
|
|
678
|
+
* <ReelsFeed>, producing the "poster stuck ~3s" stall. By owning the
|
|
679
|
+
* open/close phase, the SDK can:
|
|
680
|
+
* 1. mount <ReelsFeed> early (hidden behind the animating container),
|
|
681
|
+
* 2. prewarm the focused video DURING the open animation,
|
|
682
|
+
* 3. reveal an already-decoded first frame → no poster stall.
|
|
683
|
+
*
|
|
684
|
+
* PHASE MODEL (state machine):
|
|
685
|
+
* closed → opening : open(index) called (e.g. from a thumbnail tap)
|
|
686
|
+
* opening → open : ReelsModal finished its enter animation
|
|
687
|
+
* open → closing : close() called
|
|
688
|
+
* closing → closed : ReelsModal finished its exit animation
|
|
689
|
+
*
|
|
690
|
+
* Invalid transitions are dropped (see isValidNavTransition). This mirrors
|
|
691
|
+
* the PlayerEngine state-machine discipline used elsewhere in the SDK.
|
|
692
|
+
*
|
|
693
|
+
* The component layer (ReelsModal) drives the animation timing and calls
|
|
694
|
+
* `setPhase('open')` / `setPhase('closed')` when its Web Animations API
|
|
695
|
+
* transitions finish. NavigationManager itself holds no timers tied to
|
|
696
|
+
* animation — it only holds a safety fallback timer so a missing/janky
|
|
697
|
+
* animationend can never strand the phase forever.
|
|
698
|
+
*/
|
|
699
|
+
|
|
700
|
+
type NavigationPhase = 'closed' | 'opening' | 'open' | 'closing';
|
|
701
|
+
interface NavigationState {
|
|
702
|
+
/** Current lifecycle phase of the reels viewer. */
|
|
703
|
+
phase: NavigationPhase;
|
|
704
|
+
/**
|
|
705
|
+
* Index the viewer was opened at. Held through the whole open lifecycle
|
|
706
|
+
* (opening → open → closing) and cleared back to null only once fully
|
|
707
|
+
* closed. ReelsModal mounts <ReelsFeed> whenever this is non-null so the
|
|
708
|
+
* feed can warm during `opening`.
|
|
709
|
+
*/
|
|
710
|
+
openIndex: number | null;
|
|
711
|
+
}
|
|
712
|
+
interface NavigationConfig {
|
|
713
|
+
/**
|
|
714
|
+
* Safety fallback (ms). If the component never reports its enter/exit
|
|
715
|
+
* animation finished, the manager force-advances the phase so the viewer
|
|
716
|
+
* can never get stuck in `opening`/`closing`. Set generously above the
|
|
717
|
+
* real animation duration. Default: 1200.
|
|
718
|
+
*/
|
|
719
|
+
phaseTimeoutMs?: number;
|
|
720
|
+
}
|
|
721
|
+
declare const DEFAULT_NAVIGATION_CONFIG: Required<NavigationConfig>;
|
|
722
|
+
declare function isValidNavTransition(from: NavigationPhase, to: NavigationPhase): boolean;
|
|
723
|
+
declare class NavigationManager {
|
|
724
|
+
readonly store: StoreApi<NavigationState>;
|
|
725
|
+
private readonly config;
|
|
726
|
+
private readonly logger?;
|
|
727
|
+
/** Fallback timer guarding against a missing animationend report. */
|
|
728
|
+
private phaseTimer;
|
|
729
|
+
private _destroyed;
|
|
730
|
+
constructor(config?: NavigationConfig, logger?: ILogger);
|
|
731
|
+
/**
|
|
732
|
+
* Open the reels viewer at `index`. Safe to call from a thumbnail click
|
|
733
|
+
* handler — it transitions into `opening`, at which point ReelsModal mounts
|
|
734
|
+
* <ReelsFeed> hidden and begins prewarming.
|
|
735
|
+
*
|
|
736
|
+
* Calling open() while already open/opening just retargets the index
|
|
737
|
+
* (e.g. user taps a different thumbnail before the animation settles).
|
|
738
|
+
*/
|
|
739
|
+
open(index: number): void;
|
|
740
|
+
/**
|
|
741
|
+
* Begin closing the viewer. ReelsModal animates out then calls
|
|
742
|
+
* `setPhase('closed')`. Idempotent if already closing/closed.
|
|
743
|
+
*/
|
|
744
|
+
close(): void;
|
|
745
|
+
/**
|
|
746
|
+
* Report an animation-driven phase change from the component layer.
|
|
747
|
+
* Invalid transitions are dropped. When reaching `closed`, openIndex is
|
|
748
|
+
* cleared so <ReelsFeed> unmounts.
|
|
749
|
+
*/
|
|
750
|
+
setPhase(next: NavigationPhase): void;
|
|
751
|
+
getPhase(): NavigationPhase;
|
|
752
|
+
getOpenIndex(): number | null;
|
|
753
|
+
isOpen(): boolean;
|
|
754
|
+
/** Whether <ReelsFeed> should be mounted (any non-closed phase). */
|
|
755
|
+
shouldMount(): boolean;
|
|
756
|
+
destroy(): void;
|
|
757
|
+
/**
|
|
758
|
+
* Apply a guarded phase transition. Returns true if it was applied.
|
|
759
|
+
* Manages the fallback timer for transient phases (opening/closing).
|
|
760
|
+
*/
|
|
761
|
+
private transition;
|
|
762
|
+
private clearPhaseTimer;
|
|
763
|
+
}
|
|
764
|
+
|
|
661
765
|
/**
|
|
662
766
|
* Player State Machine — Guarded transitions
|
|
663
767
|
*
|
|
@@ -776,15 +880,20 @@ interface SDKContextValue {
|
|
|
776
880
|
playerEngine: PlayerEngine;
|
|
777
881
|
resourceGovernor: ResourceGovernor;
|
|
778
882
|
optimisticManager: OptimisticManager;
|
|
883
|
+
navigationManager: NavigationManager;
|
|
779
884
|
adapters: Required<Omit<SDKAdapters, 'dataSource'>> & Pick<SDKAdapters, 'dataSource'>;
|
|
780
885
|
}
|
|
781
886
|
interface ReelsProviderProps {
|
|
782
887
|
children: ReactNode;
|
|
783
888
|
adapters: SDKAdapters;
|
|
889
|
+
/** Seed initial items into feedManager */
|
|
890
|
+
initialItems?: ContentItem[];
|
|
784
891
|
/** Enable verbose logging (default: false) */
|
|
785
892
|
debug?: boolean;
|
|
893
|
+
/** Config for the owned reels viewer open/close lifecycle (used by ReelsModal) */
|
|
894
|
+
navigationConfig?: NavigationConfig;
|
|
786
895
|
}
|
|
787
|
-
declare function ReelsProvider({ children, adapters, debug }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
|
|
896
|
+
declare function ReelsProvider({ children, adapters, initialItems, debug, navigationConfig, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
|
|
788
897
|
declare function useSDK(): SDKContextValue;
|
|
789
898
|
|
|
790
899
|
declare function ReelsFeed({ renderOverlay, renderActions, renderPauseAction, renderLoading, renderEmpty, renderError: _renderError, showFps, loadMoreThreshold, onSlotChange, gestureConfig, snapConfig, initialMuted, onAutoplayBlocked, }: ReelsFeedProps): string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element | null | undefined;
|
|
@@ -809,12 +918,97 @@ interface ReelsFeedThumbnailProps {
|
|
|
809
918
|
wrap?: boolean;
|
|
810
919
|
/** Enable click → update focused index on ReelsProvider before host reacts. Default: true. */
|
|
811
920
|
setFocusOnClick?: boolean;
|
|
812
|
-
/**
|
|
921
|
+
/**
|
|
922
|
+
* Open the SDK-owned <ReelsModal> on click (calls NavigationManager.open).
|
|
923
|
+
* This is the recommended path — the SDK controls modal timing and prewarms
|
|
924
|
+
* the video during the open animation, eliminating the "poster stuck ~3s"
|
|
925
|
+
* stall. Requires a <ReelsModal> mounted under the same <ReelsProvider>.
|
|
926
|
+
*
|
|
927
|
+
* Leave false (default) to keep the headless behaviour where the host owns
|
|
928
|
+
* the modal and reacts via `onThumbnailClick`.
|
|
929
|
+
*
|
|
930
|
+
* @default false
|
|
931
|
+
*/
|
|
932
|
+
openOnClick?: boolean;
|
|
933
|
+
/**
|
|
934
|
+
* Prefetch HLS metadata for this slot on pointer enter (desktop hover).
|
|
935
|
+
* Useless on mobile WebView (no hover) — use `prewarmOnClick` there.
|
|
936
|
+
* Off by default.
|
|
937
|
+
*/
|
|
813
938
|
prefetchOnHover?: boolean;
|
|
939
|
+
/**
|
|
940
|
+
* Prefetch HLS metadata for the tapped slot synchronously on click, inside
|
|
941
|
+
* the tap gesture. This is the mobile-friendly counterpart to
|
|
942
|
+
* `prefetchOnHover` and starts buffering the instant the user taps — before
|
|
943
|
+
* any modal mounts. Recommended for mobile WebView. Default: true.
|
|
944
|
+
*/
|
|
945
|
+
prewarmOnClick?: boolean;
|
|
814
946
|
/** Key override — useful when host renders multiple lists from the same feed. */
|
|
815
947
|
getKey?: (item: ContentItem, index: number) => string;
|
|
816
948
|
}
|
|
817
|
-
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
949
|
+
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, openOnClick, prefetchOnHover, prewarmOnClick, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
950
|
+
|
|
951
|
+
interface ReelsModalAnimationConfig {
|
|
952
|
+
/** Enter/exit duration in ms (default: 300) */
|
|
953
|
+
duration?: number;
|
|
954
|
+
/** CSS easing (default: 'cubic-bezier(0.22, 1, 0.36, 1)') */
|
|
955
|
+
easing?: string;
|
|
956
|
+
/**
|
|
957
|
+
* Panel travel direction:
|
|
958
|
+
* - 'up' (default): slides up from the bottom edge (bottom-sheet drawer)
|
|
959
|
+
* - 'down': slides down from the top edge
|
|
960
|
+
* - 'fade': no translate, opacity only
|
|
961
|
+
*/
|
|
962
|
+
direction?: 'up' | 'down' | 'fade';
|
|
963
|
+
}
|
|
964
|
+
/** State handed to render-prop customisation callbacks. */
|
|
965
|
+
interface ReelsModalRenderState {
|
|
966
|
+
phase: NavigationPhase;
|
|
967
|
+
openIndex: number | null;
|
|
968
|
+
/** Begin closing the viewer (runs the exit animation). */
|
|
969
|
+
close: () => void;
|
|
970
|
+
}
|
|
971
|
+
interface ReelsModalProps {
|
|
972
|
+
/** Props forwarded to the inner <ReelsFeed> (render callbacks, initialMuted, etc.). */
|
|
973
|
+
feedProps?: ReelsFeedProps;
|
|
974
|
+
/** Enter/exit animation overrides. Defaults applied when omitted. */
|
|
975
|
+
animationConfig?: ReelsModalAnimationConfig;
|
|
976
|
+
/**
|
|
977
|
+
* Custom backdrop visual. Rendered inside the SDK-owned backdrop layer,
|
|
978
|
+
* whose opacity the SDK animates. If omitted, a semi-transparent black
|
|
979
|
+
* backdrop is used. Return null to suppress the default visual.
|
|
980
|
+
*/
|
|
981
|
+
renderBackdrop?: (state: ReelsModalRenderState) => ReactNode;
|
|
982
|
+
/**
|
|
983
|
+
* Custom close affordance, rendered above <ReelsFeed> inside the panel.
|
|
984
|
+
* If omitted, a top-right ✕ button is shown. Return null to hide it
|
|
985
|
+
* (e.g. when the host relies on swipe-to-dismiss or its own chrome).
|
|
986
|
+
*/
|
|
987
|
+
renderCloseButton?: (state: ReelsModalRenderState) => ReactNode;
|
|
988
|
+
/** Fired when the enter animation completes (viewer fully open). */
|
|
989
|
+
onOpen?: (index: number | null) => void;
|
|
990
|
+
/** Fired when the exit animation completes (viewer fully closed). */
|
|
991
|
+
onClose?: () => void;
|
|
992
|
+
/** Close when the backdrop is clicked. Default: true. */
|
|
993
|
+
closeOnBackdropClick?: boolean;
|
|
994
|
+
/** Close when Escape is pressed. Default: true. */
|
|
995
|
+
closeOnEscape?: boolean;
|
|
996
|
+
/** Lock body scroll while open. Default: true. */
|
|
997
|
+
lockBodyScroll?: boolean;
|
|
998
|
+
/**
|
|
999
|
+
* How many slots to prewarm on open. `prewarmForward` items ahead of the
|
|
1000
|
+
* focused index plus 1 behind get an immediate preloadMetadata() call so
|
|
1001
|
+
* buffering starts in the same tick as the tap. Default: 2.
|
|
1002
|
+
*/
|
|
1003
|
+
prewarmForward?: number;
|
|
1004
|
+
/** className on the sliding panel element. */
|
|
1005
|
+
className?: string;
|
|
1006
|
+
/** z-index for the modal root. Default: 1000. */
|
|
1007
|
+
zIndex?: number;
|
|
1008
|
+
/** Portal target. Default: document.body. */
|
|
1009
|
+
portalTarget?: HTMLElement | null;
|
|
1010
|
+
}
|
|
1011
|
+
declare function ReelsModal({ feedProps, animationConfig, renderBackdrop, renderCloseButton, onOpen, onClose, closeOnBackdropClick, closeOnEscape, lockBodyScroll, prewarmForward, className, zIndex, portalTarget, }: ReelsModalProps): react.ReactPortal | null;
|
|
818
1012
|
|
|
819
1013
|
/**
|
|
820
1014
|
* useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
|
|
@@ -1001,6 +1195,30 @@ declare function useResource(): {
|
|
|
1001
1195
|
getTier: (index: number) => 3 | 1 | 2 | 4 | null;
|
|
1002
1196
|
};
|
|
1003
1197
|
|
|
1198
|
+
/**
|
|
1199
|
+
* useNavigation — React hook for NavigationManager
|
|
1200
|
+
*
|
|
1201
|
+
* Subscribes to the reels viewer open/close lifecycle via useSyncExternalStore.
|
|
1202
|
+
* Mirrors the useResource / useFeed selector pattern — no Zustand React bindings.
|
|
1203
|
+
*
|
|
1204
|
+
* Primary consumers:
|
|
1205
|
+
* - ReelsModal: reads `phase` / `openIndex` to mount + animate the viewer,
|
|
1206
|
+
* and calls `setPhase` when its enter/exit animation finishes.
|
|
1207
|
+
* - ReelsFeedThumbnail: calls `open(index)` on click when `openOnClick` is set.
|
|
1208
|
+
* - Host app: can read `isOpen` / call `close()` to drive its own chrome.
|
|
1209
|
+
*/
|
|
1210
|
+
|
|
1211
|
+
declare function useNavigationSelector<T>(selector: (state: NavigationState) => T): T;
|
|
1212
|
+
declare function useNavigation(): {
|
|
1213
|
+
phase: NavigationPhase;
|
|
1214
|
+
openIndex: number | null;
|
|
1215
|
+
isOpen: boolean;
|
|
1216
|
+
shouldMount: boolean;
|
|
1217
|
+
open: (index: number) => void;
|
|
1218
|
+
close: () => void;
|
|
1219
|
+
setPhase: (next: NavigationPhase) => void;
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1004
1222
|
/**
|
|
1005
1223
|
* Mock adapters for development and testing
|
|
1006
1224
|
*/
|
|
@@ -1201,4 +1419,4 @@ declare class HttpError extends Error {
|
|
|
1201
1419
|
constructor(status: number, message: string, body?: string | undefined);
|
|
1202
1420
|
}
|
|
1203
1421
|
|
|
1204
|
-
export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NetworkType, OptimisticManager, type PauseSlotActions, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsFeedThumbnail, type ReelsFeedThumbnailProps, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|
|
1422
|
+
export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_NAVIGATION_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NavigationConfig, NavigationManager, type NavigationPhase, type NavigationState, type NetworkType, OptimisticManager, type PauseSlotActions, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsFeedThumbnail, type ReelsFeedThumbnailProps, ReelsModal, type ReelsModalAnimationConfig, type ReelsModalProps, type ReelsModalRenderState, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidNavTransition, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, useNavigation, useNavigationSelector, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
|