Stories Core
@reelkit/react-stories-player के पीछे का इंजन। शुद्ध TypeScript, किसी फ़्रेमवर्क पर निर्भर नहीं। Angular, Vue या vanilla JS के लिए stories प्लेयर बनाने में इसका इस्तेमाल करें।
इंस्टॉलेशन
npm i @reelkit/stories-coreStories कंट्रोलर
createStoriesController(config, events?) एक StoriesController लौटाता है, जो ग्रुप और stories के बीच नेविगेशन संभालता है। यह pause/resume स्टेट पर नज़र रखता है, हर ग्रुप में आख़िरी देखी गई story याद रखता है, और हर बदलाव पर callbacks चलाता है।
कॉन्फ़िग (StoriesControllerConfig)
| प्रॉपर्टी | टाइप | डिफ़ॉल्ट | विवरण |
|---|---|---|---|
groupCount | number | ज़रूरी | story ग्रुप की कुल संख्या |
storyCounts | number[] | ज़रूरी | हर ग्रुप में stories की संख्या |
initialGroupIndex | number | 0 | शुरुआती ग्रुप index |
initialStoryIndex | number | resumeStoryIndex(initialGroupIndex), नहीं तो 0 | ग्रुप के अंदर शुरुआती story index। इसे देने पर यह याद रखी गई किसी भी स्थिति से ऊपर रहता है; न देने पर खुलने वाला ग्रुप भी बाकी ग्रुप की तरह वहीं से शुरू होता है। |
defaultImageDuration | number | 5000 | इमेज stories के अपने-आप आगे बढ़ने की डिफ़ॉल्ट अवधि, ms में |
resumeStoryIndex | (groupIndex: number) => number | undefined | वह story जिस पर न देखा गया ग्रुप खुलता है, ग्रुप में मौजूद stories तक सीमित |
इवेंट (StoriesControllerEvents)
| इवेंट | टाइप | विवरण |
|---|---|---|
onStoryChange | (groupIndex, storyIndex) => void | सक्रिय story बदलने पर चलता है |
onGroupChange | (groupIndex) => void | सक्रिय ग्रुप बदलने पर चलता है |
onStoryViewed | (groupIndex, storyIndex) => void | किसी story के दिखने पर चलता है |
onStoryComplete | (groupIndex, storyIndex) => void | story का टाइमर पूरा होने पर चलता है (आगे बढ़ने से पहले) |
onComplete | () => void | आख़िरी ग्रुप की आख़िरी story ख़त्म होने पर चलता है |
onClose | () => void | जब overlay बंद होना चाहिए, तब चलता है |
स्टेट (reactive signals)
| सिग्नल | टाइप | विवरण |
|---|---|---|
state.activeGroupIndex | Signal<number> | अभी सक्रिय ग्रुप का index |
state.activeStoryIndex | Signal<number> | ग्रुप के अंदर अभी सक्रिय story का index |
state.isPaused | Signal<boolean> | अपने-आप आगे बढ़ना रुका है या नहीं |
मेथड
| मेथड | टाइप | विवरण |
|---|---|---|
nextStory() | () => void | ग्रुप के अंदर आगे बढ़ता है; सीमा पार करके अगले ग्रुप में जाता है |
prevStory() | () => void | ग्रुप के अंदर पीछे जाता है; सीमा पार करके पिछले ग्रुप में जाता है |
nextGroup() | () => void | अगले ग्रुप पर जाता है, आख़िरी देखी गई story से शुरू करके |
prevGroup() | () => void | पिछले ग्रुप पर जाता है, आख़िरी देखी गई story से शुरू करके |
goToGroup(index) | (number) => void | index से किसी खास ग्रुप पर सीधे जाता है |
pause() | () => void | अपने-आप आगे बढ़ना रोकता है |
resume() | () => void | अपने-आप आगे बढ़ना फिर शुरू करता है |
onStoryTimerComplete() | () => void | टाइमर पूरा होने पर कॉल होता है; पहले onStoryComplete चलाता है, फिर आगे बढ़ता है |
getLastStoryIndex(groupIndex) | (number) => number | ग्रुप कहाँ खुलेगा: इस सेशन में जिस story पर छोड़ा गया, नहीं तो वह जो resumeStoryIndex बताए |
reportInitialView() | () => void | प्लेयर जिस story पर खुला, उसे एक बार देखी गई के रूप में दर्ज करता है। mount के बाद कॉल करें, रेंडर के दौरान नहीं। |
updateConfig({ groupCount, storyCounts }) | (config) => void | प्लेयर खुला रहते हुए feed बदलने पर groups और stories की गिनती बदल देता है, ताकि बाद में लोड हुए groups तक पहुँचा जा सके। कोई event नहीं भेजता, हर group जहाँ छोड़ा गया था वह याद रखता है, और feed छोटा होने पर positions को उसके भीतर ले आता है। |
उदाहरण
import { createStoriesController } from '@reelkit/stories-core';
import { createTimerController, reaction } from '@reelkit/core';
const groups = [
{ stories: ['s1', 's2', 's3'] },
{ stories: ['s4', 's5'] },
];
const controller = createStoriesController(
{
groupCount: groups.length,
storyCounts: groups.map((g) => g.stories.length),
defaultImageDuration: 5000,
},
{
onStoryChange(groupIndex, storyIndex) {
console.log('Story changed:', groupIndex, storyIndex);
},
onComplete() {
console.log('All stories viewed');
},
onClose() {
console.log('Overlay closed');
},
},
);
// Wire up a timer for auto-advance
const timer = createTimerController({
duration: 5000,
onComplete: () => controller.onStoryTimerComplete(),
});
// React to story changes and restart the timer
const dispose = reaction(
() => [
controller.state.activeGroupIndex,
controller.state.activeStoryIndex,
],
() => timer.start(),
);
// Start playback
timer.start();
// Navigation
controller.nextStory();
controller.pause();
controller.resume();
// Cleanup
dispose();
timer.dispose();टाइमर कंट्रोलर
हर story की प्रोग्रेस चलाने वाला टाइमर @reelkit/core में createTimerController के रूप में रहता है, और lightbox स्लाइडशो के साथ साझा है। इसे @reelkit/core से या अपनी binding (@reelkit/react, @reelkit/vue, @reelkit/angular) से import करें; यह पैकेज अब इसे export नहीं करता। इसके config, स्टेट और मेथड के लिए core API में ऑटोप्ले देखें, और इसे onStoryTimerComplete से जोड़ने के लिए ऊपर का Stories कंट्रोलर उदाहरण देखें।
Canvas प्रोग्रेस रेंडरर
createCanvasProgressRenderer(config?) canvas पर segmented प्रोग्रेस बार बनाता है। यह Retina डिस्प्ले के हिसाब से स्केल होता है, @reelkit/core के observeElementSize से अपने कंटेनर की चौड़ाई पर नज़र रखता है, और segments न समाने पर खिसकती विंडो इस्तेमाल करता है।
कॉन्फ़िग (CanvasProgressRendererConfig)
| प्रॉपर्टी | टाइप | डिफ़ॉल्ट | विवरण |
|---|---|---|---|
gap | number | 2 | segments के बीच पिक्सेल में दूरी |
barHeight | number | 2 | पिक्सेल में बार की ऊँचाई |
minSegmentWidth | number | 8 | segment की न्यूनतम चौड़ाई, जिसके बाद खिसकती विंडो शुरू होती है |
bgColor | string | 'rgba(255,255,255,0.3)' | न भरे segments का बैकग्राउंड रंग |
fillColor | string | '#ffffff' | पूरे/सक्रिय segments का भरने वाला रंग |
onResize | () => void | - | कंटेनर का साइज़ बदलने और canvas के दोबारा नापे जाने के बाद कॉल होता है। canvas का साइज़ बदलने से वह साफ़ हो जाता है, इसलिए यहाँ दोबारा ड्रॉ करें। |
मेथड
createCanvasProgressRenderer एक CanvasProgressRenderer लौटाता है। जब आप रेंडरर रखने वाली किसी कंपोनेंट फ़ील्ड या ref को टाइप करें, तो यही ऑब्जेक्ट रखें:
| सदस्य | टाइप | विवरण |
|---|---|---|
attach(canvas) | (HTMLCanvasElement) => void | canvas एलिमेंट से जुड़ता है; parent के साइज़ पर नज़र रखना शुरू करता है |
draw(totalStories, activeIndex, progress) | (number, number, number) => void | दी गई स्टेट के लिए प्रोग्रेस बार बनाता है |
width | number (readonly) | CSS पिक्सेल में अभी नापी गई चौड़ाई |
dispose() | () => void | parent के साइज़ पर नज़र रखना बंद करता है और अंदरूनी स्टेट साफ़ करता है |
उदाहरण
import { createCanvasProgressRenderer } from '@reelkit/stories-core';
const renderer = createCanvasProgressRenderer({
gap: 2,
barHeight: 2,
fillColor: '#ffffff',
bgColor: 'rgba(255, 255, 255, 0.3)',
});
// Attach to a canvas element
const canvas = document.querySelector('canvas')!;
renderer.attach(canvas);
// Draw on each animation frame
let frameId: number;
function loop() {
const totalStories = 5;
const activeIndex = 2;
const progress = timer.progress.value; // 0-1
renderer.draw(totalStories, activeIndex, progress);
frameId = requestAnimationFrame(loop);
}
frameId = requestAnimationFrame(loop);
// Cleanup
cancelAnimationFrame(frameId);
renderer.dispose();देखे गए आइटम का कंट्रोलर
createStoriesViewedStateController(config) एक ही कॉल में वह सब देता है जो किसी feed को देखी गई चीज़ें याद रखने के लिए चाहिए: rings की गिनती, हर group कहाँ से दोबारा शुरू हो, और देखे जाने का रिकॉर्ड, ऐसे storage पर जो reload के बाद भी बना रहता है। यह कोई framework import नहीं करता, इसलिए React, Vue और Angular इसे एक ही तरह इस्तेमाल करते हैं: इसे वहाँ एक बार बनाएँ जहाँ feed रहता है, और ring list तथा player को दे दें, जो इसे खुद attach करते हैं और follow करते हैं। यह core के createViewedStateController और स्थिर ids वाली two-axis key पर बना है; जब यह फिट न हो तो सीधे उन्हें इस्तेमाल करें। Entry देखे जाने की गिनती नहीं, बल्कि सबसे दूर पहुँची story का नाम है, इसलिए फ़ीड का क्रम बदलने पर भी ग्रुप की जगह बनी रह ती है, जबकि ग्रुप के बीच से हटाई गई story उसकी गिनती घटा देती है और उसकी ring फिर से जल उठती है।
कॉन्फ़िग (StoriesViewedStateControllerConfig)
| प्रॉपर्टी | टाइप | डिफ़ॉल्ट | विवरण |
|---|---|---|---|
storageKey | string | ज़रूरी | वह storage key जिसके नीचे entries लिखी जाती हैं |
groups | () => StoriesGroup<T>[] | ज़रूरी | मौजूदा groups पढ़ता है। यह getter है और हर बार कॉल होता है, इसलिए और groups लोड करने वाले feed को बस उन्हें लौटाना होता है। इसे ताज़ा रखें: पहले array पर अटका getter बाद में ल ोड हुए हर group की गिनती गलत करता है। |
key | UrlKey<Id, TwoAxisPosition> | स्थिर ids | storage में position कैसे लिखी जाती है। वही key पास करें जो URL कंट्रोलर इस्तेमाल करता है, तो स्टोर की गई entry शेयर किए गए लिंक जैसी पढ़ी जाती है। |
storage | StorageAdapter | localStorage | entries कहाँ रखी जाती हैं। अपना adapter देना, अपना देखे-गए का store जोड़ने का तरीका भी है। |
ttlMs | number | कभी नहीं | किसी group की entry आख़िरी बार दर्ज होने के बाद कितनी देर याद रहे। हर group अपनी घड़ी से expire होता है। |
maxTracks | number | सभी रहते हैं | कितने groups रखने हैं। इससे ऊपर, सबसे पुराना दर्ज group अगली बार लिखते समय हटा दिया जाता है। |
storageKey, key और storage एक ही बार पढ़े जाते हैं, कंट्रोलर बनाते समय। storage बदलने क े लिए दूसरा कंट्रोलर बनाएँ।
StoriesViewedStateController
| सदस्य | टाइप | विवरण |
|---|---|---|
viewedState | Subscribable<Map<string, number>> | हर author id के लिए देखी गई stories, signal के रूप में। attach() से पहले खाली। |
controller | ViewedStateController<TwoAxisPosition> | नीचे का store, उन ज़रूरतों के लिए जो यहाँ शामिल नहीं हैं |
resumeStoryIndex(groupIndex) | (number) => number | पहली न देखी गई story, या ग्रुप पूरा देख लेने पर 0 |
markViewed(groupIndex, storyIndex) | (number, number) => void | story को देखी गई के रूप में दर्ज करता है। player हर दिखाई गई story के लिए इसे कॉल करता है। |
forget() | () => void | देखा हुआ सब भुला देता है, यहाँ भी और storage में भी |
attach() | () => Dispose | storage पढ़ता है और दूसरे tabs को follow करता है। गिनती रखता है, इसलिए ring list और player किसी भी क्रम में attach और unmount हो सकते हैं। player के components इसे खुद कॉल करते हैं। |
उदाहरण
import { createStoriesViewedStateController } from '@reelkit/stories-core';
// Once, where the feed lives. The getter is read every time, so it has to
// return the groups as they are now.
const viewed = createStoriesViewedStateController({
storageKey: 'stories-seen',
groups: () => groups,
});
// The player components do this themselves when given `viewed`.
const detach = viewed.attach();
viewed.viewedState.value; // Map { 'user_42' => 2 }, and a signal to follow
viewed.resumeStoryIndex(0); // 2 — the first story not yet seen
viewed.markViewed(0, 2); // furthest point wins; a rewatch never rewinds
viewed.forget(); // clears it all
detach();यूटिलिटी फ़ंक्शन
टैप ज़ोन पहचानने और प्रोग्रेस बार के हिसाब के लिए शुद्ध फ़ंक्शन।
| फ़ंक्शन | टाइप | विवरण |
|---|---|---|
getTapAction(tapX, containerWidth, splitRatio?) | (number, number, number?) => 'prev' | 'next' | स्थिति के आधार पर तय करता है कि टैप 'prev' चलाएगा या 'next'। डिफ़ॉल्ट splitRatio 0.3 है। |
getSegments(totalStories, activeIndex, progress) | (number, number, number) => SegmentState[] | प्रोग्रेस बार के हर segment की स्थिति और भराव का प्रतिशत निकालता है |
getVisibleWindow(totalStories, activeIndex, progress, containerWidth, minSegmentWidth?, gap?) | (number, number, number, number, number?, number?) => VisibleWindow | कुल संख्या कंटेनर की क्षमता से ज़्यादा होने पर segments की दिखने वाली खिसकती विंडो निकालता है |
लेआउट हेल्पर
हर बाइंडिंग का प्लेयर इन्हीं शुद्ध फ़ंक्शन को साझा करता है, इसलिए React, Vue और Angular में आकार और सजावट एक जैसी रहती है। इन्हें बाइंडिंग बुलाती है; ऐप को इनकी ज़रूरत सिर्फ़ तब पड़ती है जब वह अपना प्लेयर बना रहा हो।
आकार
| फ़ंक्शन | टाइप | विवरण |
|---|---|---|
getStoriesSize() | () => [number, number] | स्टोरी कैनवास का आकार। फ़ोन पर पूरी स्क्रीन; डेस्कटॉप पर विंडो की ऊँचाई में से 16px का हाशिया घटाकर 9:16 में, और तभी सँकरा होता है जब कैनवास तथा दोनों तीर आड़े में न समाएँ। window न हो (सर्वर रेंडरिंग) तो [0, 0] लौटाता है। |
parseDurationMs(value) | (string) => number | गणना किए गए transition-duration की सूची में सबसे लंबा समय, मिलीसेकंड में। जो समय नहीं है वह शून्य गिना जाता है। |
getStoriesSize जिस फ़ोन breakpoint का इस्तेमाल करता है वह isMobileWidth है, जो @reelkit/core और हर binding से export होता है: 768px और उससे कम फ़ोन है। देखें साइज़ पहचान।
डेस्कटॉप कैरोसेल की ज्यामिति
ऑफ़सेट चल रहे समूह के सापेक्ष हैं: बाईं ओर ऋणात्मक, दाईं ओर धनात्मक, और 0 का मतलब चल रही स्टोरी ही।
| फ़ंक्शन | टाइप | विवरण |
|---|---|---|
getCardSize(activeSize) | ([number, number]) => [number, number] | बग़ल के कार्ड का आकार: चल रही स्टोरी की ऊँचाई का 0.4, उसी 9:16 अनुपात में। |
getCarouselSlot(offset, activeSize) | (number, [number, number]) => CarouselSlot | विंडो के केंद्र के सापेक्ष कार्ड कहाँ बैठता है: { x, width, height }। हर ओर का पहला कार्ड तीर से आगे रहता है, और उसके बाद हर कार्ड एक कार्ड की चौड़ाई तथा एक अंतर और आगे। |
getCardOffsets(activeGroupIndex, groupCount) | (number, number) => number[] | ठहरी हुई स्थिति में चल रहे समूह के आसपास बनने वाले कार्डों के ऑफ़सेट, हर ओर अधिकतम दो, चल रहे समूह को छोड़कर। |
isCardShown(offset) | (number) => boolean | इतनी दूरी का कार्ड धुँधला होने के बजाय बनाया जाएगा या नहीं। |
getSlotOffset(offset) | (number) => number | कार्ड जिस जगह जाता है, दिखने वाली सीमा से एक क़दम आगे तक सीमित, ताकि कार्ड आख़िरी दिखते कार्ड के बग़ल से सरककर आए। |
getSlideGroupIndexes(from, to, groupCount) | (number, number, number) => number[] | दो समूहों के बीच सरकने पर कौन-से समूह बनते हैं: दोनों सिरों के आसपास के कार्ड, समूह के क्रम में। लंबी छलाँग जिन समूहों के ऊपर से गुज़रती है, वे छोड़ दिए जाते हैं। |
प्रस्तुति
| फ़ंक्शन | टाइप | विवरण |
|---|---|---|
getRingPresentation(options) | (RingPresentationOptions) => RingPresentation | totalStories, viewedCount और size से स्टोरी रिंग का क्लास नाम, इनलाइन स्टाइल और अवतार का आकार। कुछ देखना बाक़ी हो तो घूमता हुआ शंक्वाकार ग्रेडिएंट, पूरा देख लेने पर सपाट viewedColor, और ख़ाली समूह पर कोई रिंग नहीं। आकार px इकाई के साथ आते हैं, इसलिए यह स्टाइल ऑब्जेक्ट किसी भी फ़्रेमवर्क में चलता है। रंग --rk-stories-ring-active-gradient और --rk-stories-ring-viewed-color टोकन से आते हैं, इसलिए पेज इन्हें CSS से बदल सकता है; gradientColors और viewedColor एक रिंग का रंग तय करते हैं। |
kStoriesRingSize, kStoriesRingListRingSize, kStoriesCardRingSize | number | हर binding डिफ़ॉल्ट रूप से जिन रिंग व्यासों को बनाता है, पिक्सेल में: अकेली रिंग के लिए 68, रिंग सूची में 64, डेस्कटॉप carousel कार्ड पर 52। प्लेयरों से मेल के लिए इनमें से एक को size के रूप में दें। |
getPreviewSource(story) | (StoryItem | undefined) => string | undefined | कैरोसेल कार्ड जो तस्वीर दिखाता है: स्टोरी का poster, तस्वीर होने पर वही तस्वीर, और बिना पोस्टर वाले वीडियो के लिए कुछ नहीं। बिना तस्वीर वाला कार्ड उसी सादे का र्ड पर लौट आता है जो दिखाने को कुछ न रखने वाली स्टोरी के लिए बनता है। |
formatTimeAgo(date) | (string | Date) => string | हेडर और कैरोसेल कार्ड जो छोटा सापेक्ष समय दिखाते हैं: now, 5m, 3h, 2d, 2w। |
इनके साथ CarouselSlot, RingPresentation और RingPresentationOptions टाइप भी एक्सपोर्ट होते हैं।
टाइप
@reelkit/stories-core से export होने वाली सभी टाइप डेफ़िनिशन।
type MediaType = 'image' | 'video';
interface StoryItem {
id: string;
mediaType: MediaType;
src: string;
poster?: string;
duration?: number;
createdAt?: string | Date;
aspectRatio?: number;
}
interface AuthorInfo {
id: string;
name: string;
avatar: string;
verified?: boolean;
}
interface StoriesGroup<T extends StoryItem = StoryItem> {
author: AuthorInfo;
stories: T[];
}
type SegmentStatus = 'completed' | 'active' | 'upcoming';
interface SegmentState {
status: SegmentStatus;
fillPercentage: number; // 0-100
}
interface VisibleWindow {
startIndex: number;
endIndex: number;
segments: SegmentState[];
}
type TapAction = 'prev' | 'next';
interface CarouselSlot {
x: number;
width: number;
height: number;
}
interface RingPresentationOptions {
totalStories: number;
viewedCount: number;
size: number; // px
gradientColors?: string[];
viewedColor?: string;
}
interface RingPresentation {
avatarSize: number;
className: string;
style: Record<string, string>;
}यह पैकेज इंजन है, प्लेयर नहीं: इसमें state machine और ज्यामिति है, और रेंडरिंग binding पर छोड़ दी जाती है। पह ले कोई तैयार प्लेयर आज़माएँ, और अपना प्लेयर बनाते समय फ़्रेमवर्क binding का सहारा लें — virtualized Reel, signal bridge, overlay helpers।
अगले कदम
- Stories Player - इसी इंजन पर बना तैयार प्लेयरStories Player - इसी इंजन पर बना तैयार प्लेयरStories Player - इसी इंजन पर बना तैयार प्लेयर
- फ़्रेमवर्क गाइड - वह binding जिससे आपका अपना प्लेयर रेंडर होता हैफ़्रेमवर्क गाइड - वह binding जिससे आपका अ पना प्लेयर रेंडर होता हैफ़्रेमवर्क गाइड - वह binding जिससे आपका अपना प्लेयर रेंडर होता है
- फ़्रेमवर्क API रेफ़रेंस - slider props, signal bridges, overlay helpersफ़्रेमवर्क API रेफ़रेंस - slider props, signal bridges, overlay helpersफ़्रेमवर्क API रेफ़रेंस - slider props, signal bridges, overlay helpers