Vue Reel Player

Vue 3 के लिए फ़ुलस्क्रीन, Instagram/TikTok जैसा वर्टिकल मीडिया प्लेयर, जो @reelkit/vue-reel-player पर बना है।

लाइव डेमो देखें →

फ़ीचर

वर्टिकल स्वाइप
टच, खींचना, कीबोर्ड, व्हील
वीडियो autoplay
दिखते ही चलता है
साउंड टॉगल
iOS पर आवाज़ बनी रहती है
मल्टी-मीडिया
हॉरिज़ॉन्टल nested carousels
स्थिति याद रखना
जहाँ छोड़ा था, वहीं से शुरू
फ़्रेम कैप्चर
poster से वीडियो तक crossfade
वर्चुअलाइज़्ड
DOM में सिर्फ़ 3 स्लाइड
Aspect ratio
डेस्कटॉप पर 9:16, मोबाइल पर पूरी स्क्रीन
डेस्कटॉप नेविगेशन
तीर वाले बटन
Generic टाइप
कंटेंट के कस्टम डेटा मॉडल
Scoped slots
हर UI एलिमेंट कस्टमाइज़ करें
v-model:is-open
दिखने पर दोतरफ़ा binding
URL स्टेट
शेयर होने वाले लिंक, back बटन से बंद

इंस्टॉलेशन

bash
npm install @reelkit/vue-reel-player @reelkit/vue lucide-vue-next

stylesheet को अपने ऐप के entry (या किसी भी कंपोनेंट) में एक बार import करें:

typescript
import '@reelkit/vue-reel-player/styles.css';
आइकन

डिफ़ॉल्ट कंट्रोल आइकन (बंद, साउंड, नेविगेशन तीर) के लिए lucide-vue-next इस्तेमाल करते हैं। अगर आप कोई और आइकन लाइब्रेरी पसंद करते हैं, तो अपने देने के लिए #controls और #navigation scoped slots इस्तेमाल करें।

Vue 3.3 या नया

ओवरले जेनेरिक कंपोनेंट हैं, जिन्हें Vue 3.3 से सपोर्ट करता है। पुराने Vue पर runtime उनके props और events छोड़ देता है, इसलिए यह पैकेज peer dependency के रूप में vue 3.3 या नया माँगता है।

क्विक स्टार्ट

थंबनेल का grid रेंडर करें और क्लिक किए index पर overlay खोलें। v-model:is-open bind करने का मतलब है कि यूज़र बटन, जेस्चर या Escape से प्लेयर बंद करे, तो parent का ref भी साथ में बदल जाता है।

App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { ReelPlayerOverlay, type ContentItem } from '@reelkit/vue-reel-player';
import '@reelkit/vue-reel-player/styles.css';

const content: ContentItem[] = [
  {
    id: '1',
    media: [{
      id: 'v1',
      type: 'video',
      src: '/cdn/samples/videos/video-01.mp4',
      poster: '/cdn/samples/videos/video-poster-01.jpg',
      aspectRatio: 16 / 9,
    }],
    author: { name: 'Alex Johnson', avatar: '/cdn/samples/avatars/avatar-01.jpg' },
    likes: 1234,
    description: 'Amazing sunset vibes',
  },
  {
    id: '2',
    media: [{
      id: 'img1',
      type: 'image',
      src: '/cdn/samples/images/image-01.jpg',
      aspectRatio: 2 / 3,
    }],
    author: { name: 'Sarah Miller', avatar: '/cdn/samples/avatars/avatar-02.jpg' },
    likes: 5678,
    description: 'Nature at its finest',
  },
  {
    id: '3',
    media: [{
      id: 'v2',
      type: 'video',
      src: '/cdn/samples/videos/video-02.mp4',
      poster: '/cdn/samples/videos/video-poster-02.jpg',
      aspectRatio: 16 / 9,
    }],
    author: { name: 'Mike Chen', avatar: '/cdn/samples/avatars/avatar-03.jpg' },
    likes: 3456,
    description: 'Adventure awaits',
  },
];

const isOpen = ref(false);
const startIndex = ref(0);

function openAt(i: number) {
  startIndex.value = i;
  isOpen.value = true;
}
</script>

<template>
  <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:6px">
    <button
      v-for="(item, i) in content"
      :key="item.id"
      @click="openAt(i)"
      style="aspect-ratio:9/16;cursor:pointer;overflow:hidden;padding:0;border:0"
    >
      <img
        :src="item.media[0].poster || item.media[0].src"
        style="width:100%;height:100%;object-fit:cover"
      />
    </button>
  </div>

  <ReelPlayerOverlay
    v-model:is-open="isOpen"
    :content="content"
    :initial-index="startIndex"
  />
</template>

Scoped slots

आठ scoped slots से आप प्लेयर UI का कोई भी हिस्सा बदल सकते हैं। हर एक को strongly-typed scope object मिलता है। जो slots आप नहीं देते, वे डिफ़ॉल्ट पर लौट जाते हैं।

SlotScopeविवरण
#controls{ item, soundState, activeIndex, content, onClose }कस्टम global कंट्रोल बार (बंद, साउंड, शेयर वगैरह)
#error{ item, activeIndex, innerActiveIndex }कस्टम एरर इंडिकेटर (डिफ़ॉल्ट आइकन की जगह)
#loading{ item, activeIndex, innerActiveIndex }कस्टम लोडिंग इंडिकेटर (डिफ़ॉल्ट wave loader की जगह)
#navigation{ item, activeIndex, count, onPrev, onNext }कस्टम prev/next नेविगेशन तीर (डेस्कटॉप)
#nestedNavigation{ media, activeIndex, count, onPrev, onNext }अंदरूनी हॉरिज़ॉन्टल स्लाइडर के कस्टम तीर
#nestedSlide{ item, media, index, size, isActive, isInnerActive, slideKey, defaultContent, onReady, onWaiting, onError }अंदरूनी हॉरिज़ॉन्टल स्लाइडर के अंदर कस्टम स्लाइड कंटेंट
#slide{ item, index, size, isActive, slideKey, defaultContent, onReady, onWaiting, onError }पूरी तरह कस्टम स्लाइड कंटेंट (न देने पर डिफ़ॉल्ट पर लौटता है)
#slideOverlay{ item, index, isActive }हर स्लाइड का overlay (लेखक की जानकारी, लाइक, विवरण वगैरह)
#timeline{ item, activeIndex, timelineState, defaultContent }कस्टम प्लेबैक टाइमलाइन बार। सिर्फ़ तब चलता है जब बिल्ट-इन नियम (timeline मोड + न्यूनतम अवधि) डिफ़ॉल्ट बार रेंडर करते; वही auto/always/never logic दोबारा इस्तेमाल करता है। बिल्ट-इन <TimelineBar /> को लपेटने के लिए defaultContent() इस्तेमाल करें।
vue
<script setup lang="ts">
import { ref } from 'vue';
import {
  ReelPlayerOverlay,
  CloseButton,
  SoundButton,
  type ContentItem,
  type SlideOverlaySlotScope,
  type ControlsSlotScope,
} from '@reelkit/vue-reel-player';

const isOpen = ref(false);
const content = ref<ContentItem[]>([/* ... */]);
</script>

<template>
  <ReelPlayerOverlay v-model:is-open="isOpen" :content="content">
    <!-- Custom per-slide overlay: branded caption -->
    <template #slideOverlay="{ item, isActive }: SlideOverlaySlotScope">
      <div v-if="isActive" style="position:absolute;bottom:80px;left:16px;color:#fff">
        <div style="display:flex;align-items:center;gap:8px">
          <img :src="item.author.avatar" style="width:40px;height:40px;border-radius:50%" />
          <span style="font-weight:600">{{ item.author.name }}</span>
        </div>
        <p style="margin-top:8px">{{ item.description }}</p>
      </div>
    </template>

    <!-- Custom global controls -->
    <template #controls="{ onClose }: ControlsSlotScope">
      <div style="position:absolute;top:16px;right:16px;display:flex;gap:8px">
        <SoundButton />
        <CloseButton :on-click="onClose" />
      </div>
    </template>

    <!-- Custom playback timeline -->
    <template #timeline="{ timelineState }: TimelineSlotScope">
      <CustomTimelineBar :state="timelineState" />
    </template>
  </ReelPlayerOverlay>
</template>

कस्टम टाइमलाइन

#timeline slot के ज़रिए बिल्ट-इन प्लेबैक बार की जगह अपना scrub UI लगाएँ। Slot सिर्फ़ तब चलता है जब overlay के नियम डिफ़ॉल्ट बार रेंडर करते (वही timeline मोड + timelineMinDurationSeconds), इसलिए आपको इसे दोबारा नहीं लिखना पड़ता। नीचे से सटी स्थिति, safe-area padding और टच डिवाइस पर दूरी पाने के लिए अपने root पर .rk-reel-timeline class दोबारा इस्तेमाल करें।

vue
<script setup lang="ts">
import { shallowRef, onMounted, onBeforeUnmount } from 'vue';
import {
  ReelPlayerOverlay,
  type TimelineSlotScope,
} from '@reelkit/vue-reel-player';
import { toVueRef, type TimelineController } from '@reelkit/vue';

const trackRef = shallowRef<HTMLDivElement | null>(null);
let dispose: (() => void) | null = null;
const bind = (state: TimelineController) => {
  if (trackRef.value) dispose = state.bindInteractions(trackRef.value);
};
onBeforeUnmount(() => dispose?.());
</script>

<template>
  <ReelPlayerOverlay :is-open="open" :content="items" timeline="always">
    <template #timeline="{ timelineState }: TimelineSlotScope">
      <div class="rk-reel-timeline" style="padding: 0 16px" @vue:mounted="bind(timelineState)">
        <div ref="trackRef" role="slider" style="height:6px;background:rgba(255,255,255,0.2)">
          <div :style="{
            width: (timelineState.progress.value * 100) + '%',
            height: '100%',
            background: 'linear-gradient(90deg, #6366f1, #ec4899)',
          }" />
        </div>
      </div>
    </template>
  </ReelPlayerOverlay>
</template>

कस्टम कंटेंट टाइप

ReelPlayerOverlay और ReelPlayerUrlOverlay React प्लेयर की तरह आपके कंटेंट आइटम के रूप पर generic हैं। कोई भी डेटा मॉडल इस्तेमाल करने के लिए BaseContentItem extend करें: आइटम का टाइप content से निकाला जाता है, इसलिए जो फ़ीड BaseContentItem से मेल नहीं खाता, वह टाइप जाँच में फ़ेल होता है। Vue में slots यह टाइप नहीं ले जा सकते, इसलिए जब तक आप टाइप न लिखें, उनके स्कोप बिना टाइप के रहते हैं। मेल खाता slot-scope टाइप import करें और destructure पर टाइप लिखें, ताकि टेम्पलेट के भीतर भी अतिरिक्त फ़ील्ड typed रहें। यह टाइप content से मिलाकर जाँचा नहीं जाता, इसलिए दोनों को एक जैसा रखें:

vue
<script setup lang="ts">
import { ref } from 'vue';
import {
  ReelPlayerOverlay,
  type BaseContentItem,
  type SlideOverlaySlotScope,
} from '@reelkit/vue-reel-player';

interface MyItem extends BaseContentItem {
  title: string;
  category: 'video' | 'photo';
}

const open = ref(false);
const items: MyItem[] = [/* ... */];
</script>

<template>
  <ReelPlayerOverlay v-model:is-open="open" :content="items">
    <template #slideOverlay="{ item }: SlideOverlaySlotScope<MyItem>">
      <div class="my-overlay">
        <h2>{{ item.title }}</h2>
        <span>{{ item.category }}</span>
      </div>
    </template>
  </ReelPlayerOverlay>
</template>

यही पैटर्न बाकी हर slot पर काम करता है। मेल खाता scope टाइप (SlideSlotScope, ControlsSlotScope, NavigationSlotScope, NestedSlideSlotScope, LoadingSlotScope) import करें और destructure पर टाइप लगाएँ।

URL स्टेट

लाइव डेमो देखें →

@reelkit/vue के useOverlayUrlState से कंट्रोलर बनाएँ और उसे ReelPlayerUrlOverlay को controller के रूप में दें: प्लेयर address bar के हाथ में रहता है, इसलिए parameter किसी स्लाइड का नाम ले तो खुलता है और parameter हटते ही बंद हो जाता है। खोलने पर history में एक entry जुड़ती है और हर स्लाइड बदलाव उसे बदल देता है, इसलिए फ़ीड में आगे बढ़ना कोई entry नहीं जोड़ता और back का एक कदम हमेशा बाहर ले जाता है। URL की गहराई कंट्रोलर की key पर निर्भर है: एक-अक्ष वाला urlIndexKey सिर्फ़ पोस्ट को पहचानता है (?reel=3), दो-अक्ष वाला urlIndexTwoAxisKey मल्टी-मीडिया पोस्ट का अंदरूनी मीडिया index भी रखता है (?reel=3.2); हर ऐप के लिए एक key चुनें, दोनों wire रूप एक-दूसरे में decode नहीं होते। यह ReelPlayerOverlay से अलग कंपोनेंट है, इसलिए हर एक में खुली स्टेट चलाने वाला ठीक एक स्रोत होता है — is-open model या url controller, कभी दोनों नहीं।

बिल्ट-इन keys

आप बिल्ट-इन key से स्लाइडों को पहचान सकते हैं — कंट्रोलर में urlIndexKey (स्थिति से) या urlStableIdKey (स्थिर id से) spread करें — दोनों @reelkit/vue से भी export होते हैं। URL स्टेट गाइड और Core API देखें।

routing वाले ऐप को router पर आधारित adapter पास करना चाहिए, ताकि नेविगेशन की सच्चाई का अकेला स्रोत router ही रहे — उसके पीछे history लिखने से उसकी location पुरानी पड़ जाती है और अगले नेविगेशन पर parameter गिर जाता है। @reelkit/vue/vue-router-url-adapter का useVueRouterUrlAdapter Vue Router के लिए तैयार adapter है।

vue
<script setup lang="ts">
import { ReelPlayerUrlOverlay, type ContentItem } from '@reelkit/vue-reel-player';
import { useOverlayUrlState, urlIndexKey, urlStableIdKey } from '@reelkit/vue';
import { useVueRouterUrlAdapter } from '@reelkit/vue/vue-router-url-adapter';
import '@reelkit/vue-reel-player/styles.css';

const props = defineProps<{ content: ContentItem[] }>();

const reel = useOverlayUrlState({
  param: 'reel',
  adapter: useVueRouterUrlAdapter(),
  ...urlIndexKey(() => props.content.length),
});
</script>

<template>
  <!-- Opening is a link — the overlay reads the URL and opens itself. -->
  <RouterLink v-for="(post, i) in props.content" :key="post.id" :to="`?reel=${i}`">
    <img :src="post.media[0].src" />
  </RouterLink>

  <ReelPlayerUrlOverlay :controller="reel" :content="props.content" />
</template>

useOverlayUrlState के सारे विकल्प Vue API रेफ़रेंस में हैं।

  • खोलने पर history में एक entry जुड़ती है। फ़ीड स्वाइप करना उसे बदल देता है, इसलिए N स्वाइप कोई entry नहीं जोड़ते और back का एक कदम हमेशा प्लेयर छोड़ देता है। Back बंद करता है; स्लाइड नहीं बदलता।
  • Back सिर्फ़ तब बंद करता है जब प्लेयर ऐप के अंदर से खोला गया हो — लिंक ने entry जोड़ी हो। नए tab में सीधे खोले गए शेयर किए गए लिंक के पीछे कोई history नहीं होती, इसलिए ब्राउज़र का back साइट छोड़ देता है; ✕ बटन या Escape parameter को उसी जगह हटाकर पेज पर बना रहता है।
  • सीधा लिंक ?reel=3 लोड होते ही प्लेयर को उसी स्लाइड पर खोलता है।
  • किसी स्लाइड का नाम न लेने वाला parameter — पुराना bookmark, हाथ से बदली value — URL से हटा दिया जाता है, ताकि address bar ऐसी स्लाइड का दावा न करता रहे जो खुल ही नहीं सकती।
  • URL की गहराई कंट्रोलर की key पर निर्भर है: सिर्फ़ पोस्ट के लिए एक-अक्ष, या मल्टी-मीडिया पोस्ट का अंदरूनी इमेज index भी रखने के लिए दो-अक्ष (urlIndexTwoAxisKey)। हर ऐप के लिए एक key चुनें; दोनों रूप एक-दूसरे में decode नहीं होते।

एक key या दो — URL की गहराई चुनें

वही ReelPlayerOverlay दोनों रूप चलाता है; यह चलते समय कंट्रोलर की स्थिति से फ़र्क पहचानता है, इसलिए कोई mode prop नहीं है। कंट्रोलर बनाते समय key चुनें:

KeyWireक्या रखता है
urlIndexKey(…)?reel=3सिर्फ़ वर्टिकल पोस्ट।
urlIndexTwoAxisKey(…)?reel=3.2पोस्ट और carousel का अंदरूनी मीडिया index।

दोनों wire जानबूझकर अलग हैं — दो-अक्ष वाली key सख़्ती से बिंदु वाली है (3.0, कभी अकेला 3 नहीं), इसलिए एक-अक्ष वाला साधारण लिंक दूसरे रूप में decode नहीं होता। इसलिए ऐप में key बदलने से पहले शेयर किए गए सारे लिंक बेकार हो जाते हैं। एक रूप चुनें और उसी पर टिके रहें।

typescript
import { useOverlayUrlState, urlIndexTwoAxisKey } from '@reelkit/vue';

const reel = useOverlayUrlState({
  param: 'reel',
  ...urlIndexTwoAxisKey({
    outerCount: () => content.value.length,
    innerCounts: () => content.value.map((post) => post.media.length),
  }),
});

// A link now names both axes: post 3, inner media 2 — ?reel=3.2

स्थिर लिंक। Index स्थिति पर आधारित है, इसलिए फ़ीड का क्रम बदलते ही bookmark किया गया ?reel=3 कोई दूसरी पोस्ट खोलता है — फ़ीड के लिए यह अपवाद नहीं, आम बात है। urlStableIdKey हर पोस्ट की स्थिर id से key बनाता है और मौजूदा फ़ीड में ढूँढता है — आम मामले के लिए एक कॉल काफ़ी है।

typescript
const reel = useOverlayUrlState({
  param: 'reel',
  ...urlStableIdKey({ items: () => content.value }),
});

URL में id को base64url में encode करने के लिए hashCodec: base64UrlCodec पास करें — वापस पढ़ा जा सकने वाला छिपाव, cryptographic hash नहीं।

किसी दूसरे field (slug) से key बनाएँ, या locateAsync से अनंत फ़ीड के पेज लोड करें, और codec/locator खुद बनाएँ। दो अलग काम: codec पहचान को URL में लिखता है, locator ढूँढता है कि वह पहचान कहाँ है।

typescript
const reel = useOverlayUrlState({
  param: 'reel',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => content.value.findIndex((x) => x.id === id),
    identify: (index) => content.value[index].id,
  },
});

अनंत फ़ीड। locate synchronous है, इसलिए यह सिर्फ़ लोड हो चुकी पोस्ट के लिए जवाब दे सकता है — जिस फ़ीड में 20 पोस्ट लोड हुई हैं, उसकी पोस्ट 400 का शेयर किया गया लिंक खाली लौटता है। locateAsync विकल्प है, जो सिर्फ़ locate के न मिलने पर कॉल होता है।

छोटा रास्ता

आइटम की id से key बना रहे हैं? अपना codec और locator लिखना छोड़ें — locateAsync सीधे urlStableIdKey({ items, locateAsync }) को पास करें (न मिलने पर यह fetch करता है, फिर index लौटाता है)। नीचे का पूरा रूप किसी दूसरे field से key बनाने या पूरे नियंत्रण के लिए है।

typescript
const reel = useOverlayUrlState({
  param: 'reel',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => content.value.findIndex((x) => x.id === id),
    identify: (index) => content.value[index].id,
    locateAsync: async (id) => {
      const loaded = await loadById(id); // or loadUntil(id) — fetch just that one, or page up to it
      if (!loaded) return null; // exhausted — link names no post
      content.value = loaded; // commit — the overlay renders from this state
      return loaded.findIndex((x) => x.id === id); // wherever it landed
    },
  },
});
  • locateAsync के pending रहने तक प्लेयर बंद रहता है और parameter को छुआ नहीं जाता, इसलिए सीधा लिंक fetch के बाद भी बना रहता है। null या rejection parameter हटा देता है।
  • URL आगे बढ़ जाने, बंद होने या unmount के बाद आया जवाब फेंक दिया जाता है — धीमा fetch ऐसी स्लाइड नहीं खोल सकता जो किसी ने माँगी ही नहीं।
  • pending रहने के दौरान कुछ भी रेंडर नहीं होता; लोडिंग की वह स्टेट पेज के पास पहले से है, इसलिए अपना skeleton रेंडर करें।
  • कोई timeout नहीं है — प्लेयर नहीं जान सकता कि फ़ीड कितनी लंबी है। पेज ख़त्म होने पर null से निपटाएँ, नहीं तो overlay हमेशा बंद रहेगा।

API रेफ़रेंस

ReelPlayerOverlay के props

ReelPlayerOverlayProps<T>

Propटाइपडिफ़ॉल्टविवरण
ariaLabelstring'Video player'dialog क्षेत्र का सुलभ लेबल; overlay खुलने पर स्क्रीन रीडर इसे बोलते हैं
aspectRationumber9 / 16डेस्कटॉप कंटेनर का चौड़ाई/ऊँचाई अनुपात। 768px या उससे कम चौड़ाई पर प्लेयर पूरा viewport लेता है। शून्य, ऋणात्मक या non-finite मान होने पर 9/16 इस्तेमाल होता है।
contentT[] (extends BaseContentItem)requiredप्लेयर में दिखाए जाने वाले कंटेंट आइटम का array
enableNavKeysbooleantrueकीबोर्ड की तीर वाली कुंजियों से नेविगेशन चालू करता है
enableWheelbooleantrueमाउस व्हील से नेविगेशन चालू करता है
initialIndexnumber0शुरू में दिखने वाले आइटम का शून्य से शुरू होने वाला index
initialInnerIndexnumber0अंदरूनी मीडिया index जिस पर खोलना है, सिर्फ़ शुरू में दिखने वाली पोस्ट के लिए — इससे दो-अक्ष वाला URL मल्टी-मीडिया पोस्ट की किसी खास इमेज पर सीधे ले जा सकता है। यूज़र के नेविगेट करने के बाद अनदेखा होता है।
isOpenbooleanrequiredoverlay का दिखना नियंत्रित करता है; false होने पर overlay DOM से हट जाता है
loopbooleanfalseस्लाइडों के बीच अनंत लूप चालू करता है
swipeDistanceFactornumber0.12स्लाइड बदलने के लिए स्वाइप दूरी का न्यूनतम हिस्सा
timeline'auto' | 'always' | 'never''auto'बिल्ट-इन प्लेबैक टाइमलाइन बार दिखाने का नियम। 'auto' सिर्फ़ timelineMinDurationSeconds से लंबे वीडियो पर रेंडर करता है; 'always' जब भी सक्रिय स्लाइड में वीडियो हो; 'never' बिल्ट-इन बार बंद करता है (पूरी तरह कस्टम बार के लिए #timeline slot इस्तेमाल करें)।
timelineMinDurationSecondsnumber30timeline='auto' में बिल्ट-इन बार दिखाने के लिए वीडियो की न्यूनतम अवधि (सेकंड)। इससे छोटी लूप होने वाली क्लिप पर बार नहीं दिखता।
transitionDurationnumber300ms में स्लाइड एनिमेशन की अवधि
wheelDebounceMsnumber200ms में व्हील इवेंट के debounce की अवधि

ReelPlayerUrlOverlay के props

ReelPlayerUrlOverlayProps<T>

ऊपर के सारे props लेता है, सिवाय is-open के, जिसकी जगह controller है। initial-index अनदेखा होता है — स्लाइड कंट्रोलर की स्थिति चुनती है, इसलिए साथ में दी गई value हर बार खुलने पर बदल दी जाती है।

Propटाइपडिफ़ॉल्टविवरण
controllerUrlStateControllerrequireduseOverlayUrlState से मिला कंट्रोलर। इसकी position तय करती है कि overlay खुला है या नहीं और कौन-सी स्लाइड दिखती है; स्लाइड बदलने और बंद होने पर overlay इसी के ज़रिए वापस लिखता है।

इवेंट

इवेंटPayloadविवरण
@api-readyReelPlayerApiस्लाइडर तैयार होते ही एक बार emit होता है और imperative API देता है
@closevoidप्लेयर बंद होने पर emit होता है
@slide-changenumberबदलाव के बाद नए सक्रिय स्लाइड index के साथ emit होता है
@inner-slide-changeouter: number, inner: numberसक्रिय पोस्ट का अंदरूनी मीडिया index बदलने पर emit होता है — अंदरूनी नेविगेशन पर और बाहरी सक्रियता पर (सक्रिय हुई पोस्ट का मौजूदा अंदरूनी index, एक मीडिया वाली के लिए 0)।
@update:is-openbooleanबंद होने पर emit होता है; `v-model:is-open` संभव बनाता है

v-model:is-open

एक ही binding से overlay चलाने के लिए v-model:is-open इस्तेमाल करें। अगर आपको साफ़ इवेंट चाहिए, तो पुराना :is-open + @close पैटर्न भी काम करता है।

vue
<template>
  <button @click="open = true">Open</button>
  <ReelPlayerOverlay v-model:is-open="open" :content="content" />
</template>

टाइप

ContentItem

Fieldटाइपविवरण
idstringअनोखी पहचान
mediaMediaItem[]एक या ज़्यादा मीडिया (इमेज या वीडियो)
author{ name: string; avatar?: string }डिफ़ॉल्ट स्लाइड overlay में दिखने वाला लेखक
descriptionstring?कैप्शन का टेक्स्ट
likesnumber?लाइक की गिनती

TimelineBarProps

typescript
interface TimelineBarProps {
  class?: string;
  style?: CSSProperties;
}

TimelineSlotScope<T>

typescript
interface TimelineSlotScope<T extends BaseContentItem> {
  item: T;
  activeIndex: number;
  timelineState: TimelineController;
  defaultContent: () => VNode | VNode[];
}

MediaItem

Fieldटाइपविवरण
idstringअनोखी पहचान
type'image' | 'video'मीडिया का प्रकार
srcstringमीडिया का URL
posterstring?वीडियो आइटम के poster थंबनेल का URL
aspectRationumberचौड़ाई/ऊँचाई अनुपात। < 1 = वर्टिकल (cover), ≥ 1 = हॉरिज़ॉन्टल (contain)।

Unions

टाइपमानविवरण
MediaType'image' | 'video'किसी MediaItem का प्रकार; तय करता है कि स्लाइड ImageSlide रेंडर करे या VideoSlide।
TimelineMode'auto' | 'always' | 'never'timeline prop का मान: 'auto' timelineMinDurationSeconds से लंबे वीडियो पर बार दिखाता है, 'always' हर वीडियो पर, 'never' उसे छिपाता है।

सब-कंपोनेंट

इन्हें अपने कस्टम #controls, #slide या #slideOverlay templates में डालें। slot scope से आकार और callbacks आगे पास करें, ताकि autoplay, poster कैप्चर और साउंड sync काम करते रहें।

CloseButton

reel प्लेयर की डिफ़ॉल्ट styling वाला अलग गोल बंद करने का बटन। #controls के अंदर इस्तेमाल करें।

vue
<script setup lang="ts">
import { CloseButton } from '@reelkit/vue-reel-player';
</script>

<template>
  <CloseButton :on-click="onClose" />
  <CloseButton :on-click="onClose" class-name="my-close-btn" :style="{ top: '24px', right: '24px' }" />
</template>

SoundButton

Mute/unmute टॉगल। इसे SoundProvider के अंदर रेंडर करें (ReelPlayerOverlay एक देता है)। सक्रिय स्लाइड में वीडियो न हो तो छिपा रहता है।

vue
<script setup lang="ts">
import { SoundButton } from '@reelkit/vue-reel-player';
</script>

<template>
  <SoundButton />
  <SoundButton disabled class-name="my-sound-btn" />
</template>

PlayerControls

#controls slot न दिए जाने पर overlay जो डिफ़ॉल्ट कंट्रोल रेंडर करता है: एक CloseButton और, show-sound के साथ, एक SoundButton। Props: showSound, soundDisabled (साउंड बटन को धुंधला करता है) और onClose। अतिरिक्त बटनों के साथ बिल्ट-इन रूप बनाए रखने के लिए इसे अपने #controls slot के अंदर रखें।

LoadingIndicator और ErrorIndicator

LoadingIndicator मीडिया buffer होते समय दिखने वाला पूरी स्लाइड का wave loader है; ErrorIndicator मीडिया लोड न होने पर दिखने वाला डिफ़ॉल्ट संदेश है। कोई भी props नहीं लेता। अतिरिक्त कंटेंट के आसपास बिल्ट-इन रूप बनाए रखने के लिए इन्हें अपने #loading या #error slot से रेंडर करें।

TimelineBar

डिफ़ॉल्ट प्लेबैक scrub bar। सबसे पास के TimelineProvider (जो ReelPlayerOverlay के अंदर अपने-आप mount होता है) से पढ़ता है और track, buffered ranges, प्रोग्रेस भराव और scrub pill रेंडर करता है। --rk-reel-timeline-* custom properties से थीम करें, या #timeline slot से बदल दें।

vue
<script setup lang="ts">
import type { TimelineSlotScope } from '@reelkit/vue-reel-player';
</script>

<template>
  <!-- Wrap or augment the default bar from #timeline: -->
  <ReelPlayerOverlay>
    <template #timeline="{ defaultContent }: TimelineSlotScope">
      <MyTimecode />
      <component :is="defaultContent" />
    </template>
  </ReelPlayerOverlay>
</template>

SlideOverlay

लेखक, विवरण और लाइक दिखाने वाला डिफ़ॉल्ट gradient overlay। कंटेंट में ये fields होने पर रेंडर होता है। #slideOverlay slot से इसे बदलें या छिपाएँ।

vue
<script setup lang="ts">
import { SlideOverlay } from '@reelkit/vue-reel-player';
</script>

<template>
  <SlideOverlay
    :author="{ name: 'John', avatar: '/avatar.jpg' }"
    description="Amazing content"
    :likes="12500"
  />
</template>

ImageSlide

lazy loading और डिफ़ॉल्ट object-fit: cover वाली इमेज स्लाइड। बिल्ट-इन व्यवहार बनाए रखते हुए इमेज रेंडरिंग कस्टमाइज़ करने के लिए इसे #slide slot के अंदर इस्तेमाल करें।

vue
<script setup lang="ts">
import { ImageSlide } from '@reelkit/vue-reel-player';
</script>

<template>
  <ImageSlide :src="media.src" :size="size" />

  <ImageSlide
    :src="media.src"
    :size="size"
    class-name="my-image-slide"
    :style="{ backgroundColor: '#1a1a1a', borderRadius: '12px' }"
    :img-style="{ objectFit: 'contain' }"
  />
</template>

VideoSlide

साझा <video> एलिमेंट पर चलने वाली वीडियो स्लाइड। iOS पर आवाज़ बनाए रखना, poster फ़्रेम और स्थिति याद रखना संभालती है। इसे SoundProvider के अंदर रेंडर करें (ReelPlayerOverlay एक देता है)।

vue
<script setup lang="ts">
import { VideoSlide } from '@reelkit/vue-reel-player';
</script>

<template>
  <VideoSlide
    :src="media.src"
    :poster="media.poster"
    :aspect-ratio="9 / 16"
    :size="size"
    :is-active="isActive"
    :slide-key="slideKey"
    :style="{ borderRadius: '12px' }"
  />
</template>
कस्टम स्लाइड बनाना

सारा बिल्ट-इन व्यवहार (autoplay, poster कैप्चर, साउंड sync) बनाए रखते हुए मीडिया रेंडरिंग कस्टमाइज़ करने के लिए ImageSlide / VideoSlide के साथ #slide इस्तेमाल करें।

vue
<script setup lang="ts">
import {
  ReelPlayerOverlay,
  ImageSlide,
  VideoSlide,
} from '@reelkit/vue-reel-player';
</script>

<template>
  <ReelPlayerOverlay v-model:is-open="isOpen" :content="content">
    <template #slide="{ item, size, isActive, slideKey }">
      <ImageSlide
        v-if="item.media[0].type === 'image'"
        :src="item.media[0].src"
        :size="size"
        :style="{ backgroundColor: '#111' }"
        :img-style="{ objectFit: 'contain' }"
      />
      <VideoSlide
        v-else
        :src="item.media[0].src"
        :poster="item.media[0].poster"
        :aspect-ratio="item.media[0].aspectRatio"
        :size="size"
        :is-active="isActive"
        :slide-key="slideKey"
        :style="{ borderRadius: '16px' }"
      />
    </template>
  </ReelPlayerOverlay>
</template>

कंटेंट लोडिंग और एरर हैंडलिंग

प्लेयर हर स्लाइड की लोडिंग और एरर स्टेट ट्रैक करता है। कंटेंट लोड होते समय wave loader दिखता है; टूटे मीडिया पर एरर आइकन दिखता है। प्लेयर फ़ेल हुए URL cache करता है, इसलिए टूटी स्लाइड दोबारा खोलने पर फिर से कोशिश नहीं होती।

Lifecycle callbacks

#slide slot इस्तेमाल करते समय लोडिंग इंडिकेटर चलाने के लिए slot scope से ये callbacks कॉल करें:

Callbackकब कॉल करें
onReadyइमेज लोड हुई या वीडियो चलने लगा। लोडिंग और एरर स्टेट साफ़ करता है।
onWaitingवीडियो चलते-चलते buffer हो रहा है। लोडिंग इंडिकेटर दिखाता है।
onErrorकंटेंट लोड नहीं हुआ। एरर overlay दिखाता है और URL को टूटा हुआ cache करता है।
vue
<!-- Inside #slide — wire callbacks to your custom media -->
<template #slide="{ item, size, isActive, onReady, onWaiting, onError }">
  <div :style="{ width: size[0] + 'px', height: size[1] + 'px' }">
    <img
      v-if="item.media[0].type === 'image'"
      :src="item.media[0].src"
      @load="onReady"
      @error="onError"
      style="width:100%;height:100%;object-fit:cover"
    />
    <video
      v-else
      :src="item.media[0].src"
      :autoplay="isActive"
      @canplay="onReady"
      @waiting="onWaiting"
      @error="onError"
      style="width:100%;height:100%;object-fit:cover"
    />
  </div>
</template>

कस्टम लोडिंग और एरर UI

#loading और #error slots से डिफ़ॉल्ट wave loader और एरर आइकन बदलें:

vue
<ReelPlayerOverlay v-model:is-open="isOpen" :content="content">
  <template #loading="{ activeIndex }">
    <div
      style="position:absolute;inset:0;z-index:10;display:flex;
             align-items:center;justify-content:center;color:#fff;font-size:14px"
    >
      Loading slide {{ activeIndex + 1 }}...
    </div>
  </template>

  <template #error="{ activeIndex }">
    <div
      style="position:absolute;inset:0;z-index:10;display:flex;
             flex-direction:column;align-items:center;justify-content:center;
             gap:12px;color:rgba(255,255,255,0.5)"
    >
      <span style="font-size:48px">!</span>
      <span>Slide {{ activeIndex + 1 }} failed to load</span>
    </div>
  </template>
</ReelPlayerOverlay>

टाइमलाइन

Overlay सक्रिय वीडियो के ऊपर एक बिल्ट-इन प्लेबैक टाइमलाइन बार रेंडर करता है। इसे timeline prop से नियंत्रित करें: 'auto' (डिफ़ॉल्ट) तब रेंडर करता है जब सक्रिय मीडिया timelineMinDurationSeconds (डिफ़ॉल्ट 30) से लंबा वीडियो हो, 'always' जब भी वीडियो सक्रिय हो, 'never' बंद करने के लिए। पूरी तरह कस्टम scrub bar के लिए #timeline slot इस्तेमाल करें; उसका scope अंदर के TimelineController पर आधारित timelineState देता है।

vue
<ReelPlayerOverlay
  :is-open="isOpen"
  :content="items"
  timeline="auto"
  :timeline-min-duration-seconds="30"
  @close="isOpen = false"
/>

--rk-reel-timeline-* CSS custom properties से थीम करें।

slot के बाहर वही कंट्रोलर एक composable से पढ़ें:

Exportविवरण
useTimelineState()सबसे पास वाला TimelineController लौटाता है; TimelineProvider (जिसे overlay लगाता है) के बाहर एरर देता है।
useTimelineStateOptional()useTimelineState जैसा ही, पर TimelineProvider के बाहर एरर की जगह null लौटाता है — उन कंपोनेंट के लिए जो overlay के बाहर भी रेंडर हो सकते हैं।
RK_TIMELINE_KEYinjection key, जिसे दोनों composables पढ़ते हैं; अपने कंट्रोलर से TimelineBar चलाने के लिए इसे ख़ुद provide करें।
useViewportSize(aspectRatio)reactive [width, height], जिससे overlay अपना साइज़ तय करता है: डेस्कटॉप पर दिया गया aspect ratio, 768px या उससे कम चौड़ाई पर पूरा viewport। mount होने तक [0, 0] लौटाता है, इसलिए SSR के दौरान सुरक्षित है।

साउंड context

ReelPlayerOverlay अपने root पर SoundProvider mount करता है, इसलिए अंदर रेंडर होने वाला कोई भी कंपोनेंट useSoundState से mute स्टेट पढ़ या टॉगल कर सकता है। यह composable @reelkit/vue-reel-player से भी export होता है, इसलिए अलग से @reelkit/vue import करने की ज़रूरत नहीं।

vue
<script setup lang="ts">
import { useSoundState, toVueRef } from '@reelkit/vue';

// Inside a custom control rendered from the #controls slot:
const soundState = useSoundState();
const muted = toVueRef(soundState.muted);
</script>

<template>
  <button @click="soundState.toggle()">
    {{ muted ? 'Unmute' : 'Mute' }}
  </button>
</template>

प्लेयर के अंदर #controls slot भी अपने scope में soundState देता है। अगर आपको यह सिर्फ़ कंट्रोल template के अंदर चाहिए, तो उसे प्राथमिकता दें।

CSS क्लास

CSS क्लास साधारण हैं (scoped नहीं)। @reelkit/vue-reel-player/styles.css के बाद लोड होने वाली stylesheet ज़्यादा specificity वाले selector से इनमें से किसी को भी override कर सकती है। रंग, आकार और z-index बदलने के लिए नीचे थीमिंग हिस्से की CSS custom properties इस्तेमाल करें।

क्लासकंपोनेंटविवरण
.rk-reel-overlayOverlayतय फ़ुलस्क्रीन backdrop (background, z-index)
.rk-reel-containerOverlayप्लेयर कंटेनर (position, overflow)
.rk-reel-loaderOverlaywave लोडिंग एनिमेशन का overlay
.rk-reel-media-errorOverlayएरर स्टेट का overlay (बीच में आइकन + टेक्स्ट)
.rk-reel-media-error-textOverlayएरर संदेश का टेक्स्ट
.rk-reel-buttonControlsसाझा गोल आइकन बटन (बंद, साउंड, नेविगेशन तीर)
.rk-reel-close-btnControlsबंद करने का बटन
.rk-reel-sound-btnControlsसाउंड टॉगल बटन
.rk-reel-nav-arrowsNavigationसिर्फ़ डेस्कटॉप वाला तीरों का कंटेनर (768px या उससे कम चौड़ाई पर छिपा)
.rk-reel-nav-buttonNavigationअलग prev/next नेविगेशन तीर
.rk-reel-slide-wrapperSlideमीडिया + overlay के चारों ओर का wrapper
.rk-reel-slide-overlaySlideOverlaygradient overlay का कंटेनर
.rk-reel-slide-overlay-authorSlideOverlayलेखक की पंक्ति (avatar + नाम)
.rk-reel-slide-overlay-avatarSlideOverlayलेखक की avatar इमेज
.rk-reel-slide-overlay-nameSlideOverlayलेखक के नाम का टेक्स्ट
.rk-reel-slide-overlay-descriptionSlideOverlayविवरण का टेक्स्ट
.rk-reel-slide-overlay-likesSlideOverlayलाइक की पंक्ति (दिल + गिनती)
.rk-reel-video-containerVideoSlideवीडियो wrapper (background, overflow)
.rk-reel-video-elementVideoSlide<video> एलिमेंट
.rk-reel-video-posterVideoSlideposter इमेज (चलने पर धुंधली होकर गायब)
.rk-reel-video-poster.rk-visibleVideoSlideवीडियो रुका/लोड होते समय poster पर लगने वाला स्टेट modifier
.rk-reel-nested-indicatorNestedSliderमल्टी-मीडिया स्लाइड के नीचे बिंदु वाला pagination (डेस्कटॉप और टच पर स्थिति अलग)
.rk-reel-nested-navNestedSliderहॉरिज़ॉन्टल carousel के तीर (768px या उससे कम चौड़ाई पर छिपे)
.rk-reel-nested-nav-nextNestedSlidernested अगले तीर की स्थिति
.rk-reel-nested-nav-prevNestedSlidernested पिछले तीर की स्थिति
.rk-reel-timelineTimelineBarscrub bar का wrapper। नीचे से सटी स्थिति, safe-area padding और टच डिवाइस पर स्लाइड overlay से दूरी पाने के लिए कस्टम `#timeline` slot roots पर दोबारा इस्तेमाल करें।
.rk-reel-timeline-trackTimelineBarTrack (न चला हुआ हिस्सा)
.rk-reel-timeline-bufferedTimelineBarbuffered हिस्सों की परत
.rk-reel-timeline-fillTimelineBarचले हुए हिस्से का भराव
.rk-reel-timeline-cursorTimelineBarscrub handle की pill (track के ऊपर तैरती)

थीमिंग

हर रंग, आकार, z-index और ट्रांज़िशन एक CSS custom property में है। कंपोनेंट का source छुए बिना थीम बदलने के लिए :root (या overlay के किसी भी ancestor) पर एक या कई override करें। Tokens @reelkit/react-reel-player से मेल खाते हैं, इसलिए overrides एक binding से दूसरी में ले जाए जा सकते हैं।

Tokenडिफ़ॉल्टक्या नियंत्रित करता है
--rk-reel-overlay-bg#000फ़ुलस्क्रीन backdrop का रंग
--rk-reel-overlay-z1000Overlay का z-index
--rk-reel-button-bgrgba(0, 0, 0, 0.5)डिफ़ॉल्ट गोल बटन का background
--rk-reel-button-bg-hoverrgba(255, 255, 255, 0.1)नेविगेशन तीर का background (और hover की मूल स्टेट)
--rk-reel-button-bg-hover-strongrgba(255, 255, 255, 0.2)नेविगेशन तीर का hover background
--rk-reel-button-fg#fffबटन आइकन का रंग
--rk-reel-button-size44pxबटन की चौड़ाई / ऊँचाई
--rk-reel-button-radius50%बटन का border-radius
--rk-reel-ui-z10बंद / साउंड / नेविगेशन का z-index
--rk-reel-edge-padding16pxबंद / साउंड / नेविगेशन तीरों की किनारे से दूरी
--rk-reel-nav-gap8pxएक के ऊपर एक रखे नेविगेशन तीरों के बीच की दूरी
--rk-reel-transition0.2shover ट्रांज़िशन की अवधि
--rk-reel-loader-colorrgba(255, 255, 255, 0.12)wave loader के gradient का रंग
--rk-reel-loader-duration1.8swave loader एनिमेशन की अवधि
--rk-reel-error-fgrgba(255, 255, 255, 0.4)एरर आइकन और टेक्स्ट का रंग
--rk-reel-slide-overlay-bglinear-gradient(transparent, rgba(0, 0, 0, 0.7))कैप्शन के पीछे का gradient
--rk-reel-slide-overlay-padding48px 16px 16pxकैप्शन की अंदरूनी padding
--rk-reel-slide-overlay-name-color#fffलेखक के नाम का रंग
--rk-reel-video-bg#000<video> के पीछे letterbox background
--rk-reel-nested-button-bgrgba(0, 0, 0, 0.5)nested तीर का background
--rk-reel-nested-button-size36pxnested तीर का आकार
--rk-reel-timeline-trackrgba(255, 255, 255, 0.22)Track का background (न चला हुआ हिस्सा)
--rk-reel-timeline-bufferedrgba(255, 255, 255, 0.4)buffered हिस्सों का रंग
--rk-reel-timeline-fill#fffचले हुए हिस्से के भराव का रंग
--rk-reel-timeline-cursor#fffscrub handle की pill का रंग
--rk-reel-timeline-height3pxआराम की हालत में track की ऊँचाई
--rk-reel-timeline-height-active6pxhover / फ़ोकस / scrub पर track की ऊँचाई
--rk-reel-timeline-cursor-width10pxआराम की हालत में scrub pill की चौड़ाई
--rk-reel-timeline-cursor-width-active14pxscrub करते समय scrub pill की चौड़ाई
--rk-reel-timeline-cursor-height24pxआराम की हालत में scrub pill की ऊँचाई
--rk-reel-timeline-cursor-height-active32pxscrub करते समय scrub pill की ऊँचाई
--rk-reel-timeline-transition0.15s ease-outtrack + pill के बढ़ने/घटने का एनिमेशन

नीचे का snippet @reelkit/vue-reel-player/styles.css के बाद लोड होने वाली stylesheet में डालें।

css
/* Brand the reel-player overlay */
:root {
  --rk-reel-overlay-bg: #0f172a;
  --rk-reel-button-bg: rgba(99, 102, 241, 0.65);
  --rk-reel-button-bg-hover-strong: rgba(168, 85, 247, 0.85);
  --rk-reel-edge-padding: 24px;
  --rk-reel-button-size: 52px;

  /* Timeline bar: brand-matched, beefier on desktop */
  --rk-reel-timeline-track: rgba(99, 102, 241, 0.25);
  --rk-reel-timeline-buffered: rgba(168, 85, 247, 0.45);
  --rk-reel-timeline-fill: #a855f7;
  --rk-reel-timeline-cursor: #a855f7;
  --rk-reel-timeline-height: 4px;
  --rk-reel-timeline-height-active: 8px;
  --rk-reel-timeline-cursor-width-active: 18px;
  --rk-reel-timeline-transition: 0.2s ease-out;
}

सुलभता

Overlay का root एक modal dialog है (role="dialog", aria-modal="true")। स्क्रीन रीडर की घोषणा बदलने के लिए aria-label prop सेट करें; डिफ़ॉल्ट "Video player" है। हर स्लाइड पर role="group", aria-roledescription="slide" और aria-label="Slide N of M" होते हैं।

Overlay खुलने पर फ़ोकस पकड़ता है और बंद होने पर उसे trigger पर लौटा देता है। Tab और Shift+Tab अंदर के फ़ोकस होने वाले एलिमेंट में घूमते हैं; बाहर निकला फ़ोकस (बाहर क्लिक, प्रोग्राम से फ़ोकस) वापस खींच लिया जाता है। यह @reelkit/core के captureFocusForReturn और createFocusTrap से बना है।

कीबोर्ड शॉर्टकट

कुंजीक्रिया
ArrowUpपिछली स्लाइड
ArrowDownअगली स्लाइड
ArrowLeftपिछला मीडिया (nested carousel)
ArrowRightअगला मीडिया (nested carousel)
Escapeप्लेयर बंद करें