Angular Reel Player

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

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

फ़ीचर

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

इंस्टॉलेशन

bash
npm install @reelkit/angular-reel-player @reelkit/angular lucide-angular

स्टाइलशीट को एक बार रजिस्टर करें:

typescript
// Anywhere that runs at startup — main.ts, app.config.ts, a component file.
import '@reelkit/angular-reel-player/styles.css';

// Or, if you keep CSS out of TypeScript, from your global stylesheet:
//   @import '@reelkit/angular-reel-player/styles.css';
// Or register it in angular.json:
//   "styles": ["node_modules/@reelkit/angular-reel-player/styles.css"]
आइकन

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

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

stylesheet और standalone RkReelPlayerOverlayComponent को अपने कंपोनेंट के imports array में import करें।

reel-feed.component.ts
import { Component, signal } from '@angular/core';
import {
  RkReelPlayerOverlayComponent,
  type ContentItem,
} from '@reelkit/angular-reel-player';
import '@reelkit/angular-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 content',
  },
  {
    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',
  },
];

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RkReelPlayerOverlayComponent],
  template: `
    <!-- Grid thumbnail view -->
    <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:6px">
      @for (item of content; track item.id; let i = $index) {
        <button
          (click)="openAt(i)"
          style="aspect-ratio:9/16;cursor:pointer;overflow:hidden"
        >
          <img
            [src]="item.media[0].poster || item.media[0].src"
            style="width:100%;height:100%;object-fit:cover"
          />
        </button>
      }
    </div>

    <rk-reel-player-overlay
      [isOpen]="isOpen()"
      [content]="content"
      [initialIndex]="startIndex()"
      (closed)="isOpen.set(false)"
    />
  `,
})
export class AppComponent {
  readonly content = content;
  readonly isOpen = signal(false);
  readonly startIndex = signal(0);

  openAt(index: number): void {
    this.startIndex.set(index);
    this.isOpen.set(true);
  }
}
ओवरले कहाँ रेंडर होता है

React और Vue प्लेयर अपना ओवरले document.body के अंत में ले जाते हैं, पर rk-reel-player-overlay वहीं रेंडर होता है जहाँ आप उसे रखते हैं और position: fixed से पूरी स्क्रीन ढकता है। उसे ऐसे किसी पूर्वज के भीतर न रखें जिस पर transform, perspective, filter या will-change: transform हो: वह पूर्वज ओवरले का containing block बन जाता है, इसलिए प्लेयर व्यूपोर्ट की जगह उसी के आकार का हो जाता है और उसके overflow से कट जाता है। अपना स्टैकिंग कॉन्टेक्स्ट बनाने वाला पूर्वज भी सीमित करता है कि --rk-reel-overlay-z प्लेयर को कितना ऊपर उठा सकता है। बदले में, किसी भी पूर्वज पर सेट किए गए --rk-reel-* टोकन प्लेयर तक पहुँचते हैं।

टेम्पलेट स्लॉट

छह template slot directives से आप प्लेयर UI का हर पहलू कस्टमाइज़ कर सकते हैं। हर एक को strongly-typed context object मिलता है। सिर्फ़ वही slots दें जिन्हें बदलना है — बाकी के लिए डिफ़ॉल्ट इस्तेमाल होते हैं।

DirectiveContext टाइपविवरण
[rkPlayerControls]PlayerControlsContext<T>कस्टम global कंट्रोल बार (बंद, साउंड टॉगल वगैरह); directive class RkPlayerControlsDirective है
[rkPlayerError]PlayerErrorContextकस्टम एरर इंडिकेटर का template slot; directive class RkPlayerErrorDirective है
[rkPlayerLoading]PlayerLoadingContextकस्टम लोडिंग इंडिकेटर का template slot; directive class RkPlayerLoadingDirective है
[rkPlayerNavigation]PlayerNavigationContextकस्टम prev/next नेविगेशन तीर; directive class RkPlayerNavigationDirective है
[rkPlayerNestedNavigation]PlayerNestedNavigationContextअंदरूनी हॉरिज़ॉन्टल स्लाइडर के कस्टम नेविगेशन तीर; directive class RkPlayerNestedNavigationDirective है
[rkPlayerNestedSlide]PlayerNestedSlideContextअंदरूनी हॉरिज़ॉन्टल स्लाइडर की हर स्लाइड के लिए कस्टम कंटेंट; directive class RkPlayerNestedSlideDirective है
[rkPlayerSlide]PlayerSlideContext<T>डिफ़ॉल्ट मीडिया स्लाइड की जगह पूरी तरह कस्टम स्लाइड कंटेंट; directive class RkPlayerSlideDirective है
[rkPlayerSlideOverlay]PlayerSlideOverlayContext<T>हर स्लाइड का overlay (लेखक की जानकारी, लाइक, विवरण वगैरह); directive class RkPlayerSlideOverlayDirective है
[rkPlayerTimeline]PlayerTimelineContext<T>कस्टम प्लेबैक टाइमलाइन बार। सिर्फ़ तब रेंडर होता है जब नियम (timeline मोड + न्यूनतम अवधि) डिफ़ॉल्ट बार रेंडर करते (वही auto/always/never logic)। directive class RkPlayerTimelineDirective है

PLAYER_TEMPLATE_SLOT_DIRECTIVES इन सभी directives को एक साथ रखता है, ताकि कोई कंपोनेंट इन्हें एक बार में अपने imports में फैला सके।

typescript
import {
  RkReelPlayerOverlayComponent,
  RkPlayerSlideOverlayDirective,
  RkPlayerControlsDirective,
  RkPlayerNavigationDirective,
  RkCloseButtonComponent,
  RkSoundButtonComponent,
  type ContentItem,
  type PlayerSlideOverlayContext,
  type PlayerControlsContext,
  type PlayerNavigationContext,
} from '@reelkit/angular-reel-player';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    RkReelPlayerOverlayComponent,
    RkPlayerSlideOverlayDirective,
    RkPlayerControlsDirective,
    RkPlayerNavigationDirective,
    RkCloseButtonComponent,
    RkSoundButtonComponent,
  ],
  template: `
    <rk-reel-player-overlay
      [isOpen]="isOpen"
      [content]="content"
      (closed)="isOpen = false"
    >
      <!-- Custom per-slide overlay: author + likes -->
      <ng-template rkPlayerSlideOverlay let-item let-isActive="isActive">
        @if (isActive) {
          <div 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>
        }
      </ng-template>

      <!-- Custom global controls -->
      <ng-template rkPlayerControls
                   let-onClose
                   let-soundState="soundState">
        <div style="position:absolute;top:16px;right:16px;display:flex;gap:8px">
          <rk-sound-button [soundState]="soundState" />
          <rk-close-button (click)="onClose()" />
        </div>
      </ng-template>

      <!-- Custom navigation -->
      <ng-template rkPlayerNavigation
                   let-onPrev
                   let-onNext="onNext"
                   let-activeIndex="activeIndex"
                   let-count="count">
        <div style="position:absolute;right:16px;top:50%;transform:translateY(-50%)">
          <button (click)="onPrev()" [disabled]="activeIndex === 0">&#9650;</button>
          <button (click)="onNext()" [disabled]="activeIndex === count - 1">&#9660;</button>
        </div>
      </ng-template>

      <!-- Custom playback timeline -->
      <ng-template rkPlayerTimeline let-state="timelineState">
        <div class="rk-reel-timeline" style="padding:0 16px"
             (pointerdown)="bindTrack(track, state); track.focus()">
          <div #track
               role="slider"
               [attr.aria-valuenow]="state.currentTime()"
               style="height:6px;background:rgba(255,255,255,0.2);border-radius:999px">
            <div [style.width.%]="state.progress() * 100"
                 style="height:100%;background:linear-gradient(90deg,#6366f1,#ec4899);border-radius:999px"></div>
          </div>
        </div>
      </ng-template>
    </rk-reel-player-overlay>
  `,
})
export class AppComponent {
  isOpen = false;
  content: ContentItem[] = [];

  private _trackDispose: (() => void) | null = null;
  /** Wire pointer + keyboard scrub onto your custom track element. */
  bindTrack(el: HTMLElement, state: PlayerTimelineState) {
    this._trackDispose?.();
    this._trackDispose = state.bindInteractions(el);
  }
}

डिफ़ॉल्ट स्लाइड कंपोनेंट

वे हिस्से जिनसे डिफ़ॉल्ट स्लाइड बनती है। हर एक standalone कंपोनेंट है; डिफ़ॉल्ट रूप का कोई हिस्सा बनाए रखने के लिए अपने template के अंदर इनमें से किसी का इस्तेमाल करें।

कंपोनेंटSelectorविवरण
RkMediaSlideComponentrk-media-slideडिफ़ॉल्ट स्लाइड। कंटेंट आइटम के media से rk-image-slide, rk-video-slide या rk-nested-slider रेंडर करती है; डिफ़ॉल्ट मीडिया रखते हुए ऊपर अपनी परत जोड़ने के लिए इसे rkPlayerSlide template में इस्तेमाल करें।
RkImageSlideComponentrk-image-slideobject-fit: cover वाली एक इमेज, जो लोड होने तक अपना poster दिखाती है और onReady, onWaiting या onError कॉल करती है।
RkVideoSlideComponentrk-video-slideसाझा <video> एलिमेंट पर एक वीडियो। इसे overlay का दिया SoundStateService चाहिए, इसलिए इसे सिर्फ़ प्लेयर के अंदर रेंडर करें।
RkNestedSliderComponentrk-nested-sliderकई media एंट्री वाले कंटेंट आइटम के लिए, indicator बिंदुओं के साथ हॉरिज़ॉन्टल स्लाइडर।
RkSlideOverlayComponentrk-slide-overlayडिफ़ॉल्ट निचला overlay: लेखक का avatar और नाम, विवरण और लाइक, इसके author, description और likes inputs से। डिफ़ॉल्ट रूप बनाए रखने के लिए इसे rkPlayerSlideOverlay template में इस्तेमाल करें।

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

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

नेस्टेड स्लाइडर (कई मीडिया वाले आइटम)

जब किसी ContentItem में कई media entries होती हैं, तो प्लेयर उन्हें हॉरिज़ॉन्टल nested स्लाइडर में रेंडर करता है (Instagram carousel की तरह)। अंदरूनी स्लाइड का कंटेंट कस्टमाइज़ करने के लिए rkPlayerNestedSlide slot इस्तेमाल करें।

typescript
const carouselItem: ContentItem = {
  id: '3',
  media: [
    { id: 'img-a', type: 'image', src: '/photo-a.jpg', aspectRatio: 2 / 3 },
    { id: 'img-b', type: 'image', src: '/photo-b.jpg', aspectRatio: 3 / 4 },
    { id: 'img-c', type: 'image', src: '/photo-c.jpg', aspectRatio: 1 },
  ],
  author: { name: 'Emma Davis', avatar: '/avatar3.jpg' },
  likes: 8901,
  description: 'Travel moments',
};

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

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

Lifecycle callbacks

rkPlayerSlide template slot इस्तेमाल करते समय लोडिंग इंडिकेटर नियंत्रित करने के लिए context के callbacks इस्तेमाल करें:

Callbackकब कॉल करें
onReadyइमेज लोड हुई या वीडियो चलने लगा। लोडिंग और एरर स्टेट साफ़ करता है।
onWaitingवीडियो चलते-चलते buffer हो रहा है। लोडिंग इंडिकेटर दिखाता है।
onErrorकंटेंट लोड नहीं हुआ। एरर overlay दिखाता है और URL को टूटा हुआ cache करता है।
html
<!-- Wire lifecycle callbacks in a custom slide template -->
<rk-reel-player-overlay [isOpen]="isOpen" [content]="items" (closed)="isOpen = false">
  <ng-template rkPlayerSlide
    let-item
    let-size="size"
    let-isActive="isActive"
    let-onReady="onReady"
    let-onWaiting="onWaiting"
    let-onError="onError"
  >
    @if (item.media[0].type === 'image') {
      <img
        [src]="item.media[0].src"
        (load)="onReady()"
        (error)="onError()"
        [style.width.px]="size[0]"
        [style.height.px]="size[1]"
        style="object-fit: cover"
      />
    } @else {
      <video
        [src]="item.media[0].src"
        [autoplay]="isActive"
        (canplay)="onReady()"
        (waiting)="onWaiting()"
        (error)="onError()"
        [style.width.px]="size[0]"
        [style.height.px]="size[1]"
        style="object-fit: cover"
      />
    }
  </ng-template>
</rk-reel-player-overlay>

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

डिफ़ॉल्ट wave loader और एरर आइकन की जगह अपने templates लगाएँ:

html
<rk-reel-player-overlay [isOpen]="isOpen" [content]="items" (closed)="isOpen = false">
  <ng-template rkPlayerLoading let-index let-item="item">
    <div style="
      position: absolute; inset: 0; z-index: 10;
      display: flex; align-items: center; justify-content: center;
      color: #fff; font-size: 14px;
    ">
      Loading slide {{ index + 1 }}...
    </div>
  </ng-template>

  <ng-template rkPlayerError let-index let-item="item">
    <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>Failed to load media</span>
    </div>
  </ng-template>
</rk-reel-player-overlay>

टाइमलाइन

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

html
<rk-reel-player-overlay
  [isOpen]="isOpen()"
  [content]="items"
  timeline="auto"
  [timelineMinDurationSeconds]="30"
  (closed)="isOpen.set(false)"
/>

--rk-reel-timeline-* CSS custom properties से थीम करें। कस्टम consumer कंपोनेंट में सीधे नियंत्रण के लिए TimelineStateService inject करें।

RkTimelineBarComponent

डिफ़ॉल्ट प्लेबैक scrub bar कंपोनेंट। TimelineStateService (जो RkReelPlayerOverlayComponent देता है) इस्तेमाल करता है और track, buffered ranges, प्रोग्रेस भराव और scrub pill रेंडर करता है। Selector: rk-timeline-bar। Inputs: class?: string, style?: Record<string, string>। डिफ़ॉल्ट बार को लपेटने या बढ़ाने के लिए rkPlayerTimeline template के अंदर इस्तेमाल करें; अकेले सिर्फ़ ऐसे consumer के अंदर इस्तेमाल करें जो service देता हो।

typescript
import { RkTimelineBarComponent } from '@reelkit/angular-reel-player';

@Component({
  standalone: true,
  imports: [RkReelPlayerOverlayComponent, RkTimelineBarComponent],
  template: `
    <rk-reel-player-overlay [isOpen]="isOpen()" [content]="items">
      <!-- Wrap or augment the default bar: -->
      <ng-template rkPlayerTimeline>
        <my-timecode />
        <rk-timeline-bar />
      </ng-template>
    </rk-reel-player-overlay>
  `,
})
export class AppComponent {}

SoundStateService

RkReelPlayerOverlayComponent के स्तर पर दिया जाता है। डिफ़ॉल्ट साउंड बटन इसे inject करता है और यह controls template slot के context में भी मिलता है। सीधी पहुँच के लिए इसे overlay के children वाले कस्टम कंट्रोल में inject किया जा सकता है।

typescript
import { inject } from '@angular/core';
import { SoundStateService } from '@reelkit/angular-reel-player';

@Component({ ... })
export class AppComponent {
  readonly soundState = inject(SoundStateService);

  // Use in template:
  // [class.muted]="soundState.muted()"
  // [disabled]="soundState.disabled()"
  // (click)="soundState.toggle()"
}
सदस्यटाइपविवरण
muted()Signal<boolean>प्लेयर अभी muted है या नहीं
disabled()Signal<boolean>सक्रिय स्लाइड में वीडियो न हो या ट्रांज़िशन चल रहा हो, तब true
toggle()() => voidmuted स्टेट टॉगल करता है

URL स्टेट

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

RkReelPlayerUrlOverlayComponent एक अलग कंपोनेंट है जिसकी खुले होने की स्टेट address bar में रहती है। injection context में createOverlayUrlState से कंट्रोलर बनाएँ और उसे [controller] के रूप में पास करें: जब parameter किसी स्लाइड का नाम लेता है तो प्लेयर खुलता है, और उसके हटते ही बंद हो जाता है। लिंक शेयर किए जा सकते हैं, और back बटन प्लेयर बंद करता है। RkReelPlayerOverlayComponent [isOpen] से ही नियंत्रित रहता है, इसलिए हर कंपोनेंट में खुली स्टेट चलाने वाला ठीक एक स्रोत होता है।

बिल्ट-इन keys

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

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

typescript
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import {
  RkReelPlayerUrlOverlayComponent,
  type ContentItem,
} from '@reelkit/angular-reel-player';
import { createOverlayUrlState, urlIndexKey, urlStableIdKey } from '@reelkit/angular';
import { createRouterUrlAdapter } from '@reelkit/angular/ng-router-url-adapter';
import '@reelkit/angular-reel-player/styles.css';

@Component({
  standalone: true,
  imports: [RkReelPlayerUrlOverlayComponent, RouterLink],
  template: `
    @for (post of content; track post.id; let i = $index) {
      <a [routerLink]="[]" [queryParams]="{ reel: i }">{{ post.id }}</a>
    }
    <rk-reel-player-url-overlay [controller]="reel" [content]="content" />
  `,
})
export class FeedComponent {
  content: ContentItem[] = [/* ... */];
  protected readonly reel = createOverlayUrlState({
    param: 'reel',
    adapter: createRouterUrlAdapter(),
    ...urlIndexKey(() => this.content.length),
  });
}
  • खोलने पर 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 नहीं होते।

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

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

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

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

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

typescript
import { createOverlayUrlState, urlIndexTwoAxisKey } from '@reelkit/angular';

protected readonly reel = createOverlayUrlState({
  param: 'reel',
  ...urlIndexTwoAxisKey({
    outerCount: () => this.content.length,
    innerCounts: () => this.content.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
protected readonly reel = createOverlayUrlState({
  param: 'reel',
  ...urlStableIdKey({ items: () => this.loaded() }),
});

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

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

typescript
protected readonly reel = createOverlayUrlState({
  param: 'reel',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => this.loaded().findIndex((x) => x.id === id),
    identify: (index) => this.loaded()[index].id,
  },
});

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

छोटा रास्ता

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

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

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

अपना domain मॉडल इस्तेमाल करने के लिए BaseContentItem extend करें। कंपोनेंट generic है: RkReelPlayerOverlayComponent<T extends BaseContentItem>।

typescript
import type { BaseContentItem } from '@reelkit/angular-reel-player';

interface MyPost extends BaseContentItem {
  // id: string  — from BaseContentItem
  // media: MediaItem[]  — from BaseContentItem
  title: string;
  tags: string[];
  publishedAt: Date;
}

@Component({
  imports: [RkReelPlayerOverlayComponent],
  template: `
    <rk-reel-player-overlay [isOpen]="isOpen" [content]="posts" (closed)="isOpen = false">
      <ng-template rkPlayerSlideOverlay let-post let-isActive="isActive">
        @if (isActive) {
          <div style="position:absolute;bottom:80px;left:16px;color:#fff">
            <h3>{{ post.title }}</h3>
            @for (tag of post.tags; track tag) {
              <span>#{{ tag }} </span>
            }
          </div>
        }
      </ng-template>
    </rk-reel-player-overlay>
  `,
})
export class AppComponent {
  isOpen = false;
  posts: MyPost[] = [];
}

RkReelPlayerOverlayComponent के inputs

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

RkReelPlayerOverlayComponent के outputs

Outputटाइपविवरण
apiReadyEventEmitter<ReelApi>स्लाइडर तैयार होते ही एक बार emit होता है और imperative API देता है
closedEventEmitter<void>प्लेयर बंद होने पर emit होता है
slideChangeEventEmitter<number>सक्रिय स्लाइड index बदलने पर emit होता है
innerSlideChangeEventEmitter<{ outer: number; inner: number }>सक्रिय पोस्ट का अंदरूनी मीडिया index बदलने पर emit होता है — अंदरूनी नेविगेशन पर और बाहरी सक्रियता पर, सक्रिय हुई पोस्ट का मौजूदा अंदरूनी index बताते हुए (एक मीडिया वाली पोस्ट के लिए 0)।

RkReelPlayerUrlOverlayComponent के inputs

ऊपर के सारे inputs लेता है, सिवाय isOpen और initialIndex के, जिनकी जगह controller है और उसकी स्थिति स्लाइड चुनती है। Outputs closed और slideChange।

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

MediaItem इंटरफ़ेस

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

टेम्पलेट स्लॉट context के टाइप

टाइपFields
PlayerControlsContext<T>{ $implicit: onClose, activeIndex, content: T[], soundState: PlayerSoundState }
PlayerNavigationContext{ $implicit: onPrev, onNext, activeIndex, count }
PlayerNestedNavigationContext{ $implicit: onPrev, onNext, activeIndex, count }
PlayerNestedSlideContext{ $implicit: MediaItem, index, size, isActive, isInnerActive, slideKey }
PlayerSlideContext<T>{ $implicit: T, index, size: [number,number], isActive, slideKey, onReady, onWaiting, onError }
PlayerSlideOverlayContext<T>{ $implicit: T, index, isActive }
PlayerTimelineContext<T>{ $implicit: T, activeIndex, timelineState: PlayerTimelineState }
PlayerTimelineState{ duration(), currentTime(), progress(), bufferedRanges(), isScrubbing(), seek(t), bindInteractions(el) }
PlayerBufferedRangeसेकंड में { start, end } — bufferedRanges() की एक एंट्री
PlayerLoadingContext{ $implicit: activeIndex, item, innerActiveIndex } — rkPlayerLoading template को जो मिलता है; nested स्लाइडर के बाहर innerActiveIndex null होता है
PlayerErrorContext{ $implicit: activeIndex, item, innerActiveIndex } — rkPlayerError template को जो मिलता है

CSS क्लास

सारी CSS क्लास साधारण हैं (scoped नहीं), इसलिए @reelkit/angular-reel-player/styles.css के बाद लोड होने वाली stylesheet में ज़्यादा specificity वाले selectors से इन्हें निशाना बनाया जा सकता है। रंग, आकार और 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-btnNavigationअलग 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-loaderVideoSlidewave लोडिंग एनिमेशन
.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-nested-slider-innerNestedSlidernested हॉरिज़ॉन्टल स्लाइडर का root
.rk-reel-timelineTimelineBarscrub bar का wrapper। नीचे से सटी स्थिति, safe-area padding और टच डिवाइस पर स्लाइड overlay से दूरी पाने के लिए कस्टम `rkPlayerTimeline` template 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 React और Vue पैकेज से मेल खाते हैं, इसलिए 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-video-loader-colorrgba(255, 255, 255, 0.15)वीडियो buffer होने की चमक का रंग
--rk-reel-nested-button-bgrgba(0, 0, 0, 0.5)nested तीर का background
--rk-reel-nested-button-size36pxnested तीर का आकार
--rk-reel-nested-edge-padding12pxnested तीर की किनारे से दूरी
--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/angular-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")। स्क्रीन रीडर की घोषणा बदलने के लिए ariaLabel input सेट करें; डिफ़ॉल्ट "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 स्लाइडर में)
ArrowRightअगला मीडिया (nested स्लाइडर में)
Escapeप्लेयर बंद करें