Reel Player для Angular

Повноекранний вертикальний медіаплеєр у стилі Instagram чи TikTok для Angular на основі @reelkit/angular-reel-player.

Подивитися демо наживо →

Features

Вертикальний свайп
Дотик, перетягування, клавіатура, колесо
Автовідтворення відео
Грає, коли слайд видимий
Перемикач звуку
Безперервність на iOS
Multi-Media
Вкладені горизонтальні каруселі
Пам’ять позиції
Продовжує з місця, де зупинилися
Знімок кадру
Плавний перехід від постера до відео
Віртуалізований
Лише 3 слайди в DOM
Співвідношення сторін
9:16 на десктопі, на весь екран на мобільних
Навігація на десктопі
Кнопки-стрілки
Узагальнені типи
Власні моделі даних вмісту
Customizable
Шаблонні слоти для всього
Обробка помилок
Виявлення зіпсованого медіа з LRU-кешем
Стан в URL
Посилання, якими можна ділитися, і закриття кнопкою «назад»

Встановлення

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"]
Icons

Стандартні елементи керування використовують lucide-angular для іконок (закриття, звук, стрілки навігації). Якщо ви віддаєте перевагу іншій бібліотеці іконок, скористайтеся слотами rkPlayerControls та rkPlayerNavigation щоб передати власні.

Швидкий старт

Імпортуйте таблицю стилів і автономний RkReelPlayerOverlayComponent у imports array.

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: такий предок стає блоком-контейнером оверлея, тож плеєр отримує розмір предка замість вікна й обрізається його overflow. Предок, що створює власний контекст накладання, також обмежує, наскільки високо --rk-reel-overlay-z може підняти плеєр. Натомість токени --rk-reel-*, задані на будь-якому предку, доходять до плеєра.

Шаблонні слоти

Шість директив шаблонних слотів дають налаштувати будь-яку частину інтерфейсу плеєра. Кожна отримує строго типізований об’єкт контексту. Задавайте лише ті слоти, які хочете перекрити — для решти лишиться стандартний вигляд.

DirectiveТип контекстуОпис
[rkPlayerControls]PlayerControlsContext<T>Власна загальна смуга керування (закриття, перемикач звуку тощо); клас директиви — RkPlayerControlsDirective
[rkPlayerError]PlayerErrorContextШаблонний слот власного індикатора помилки; клас директиви — RkPlayerErrorDirective
[rkPlayerLoading]PlayerLoadingContextШаблонний слот власного індикатора завантаження; клас директиви — RkPlayerLoadingDirective
[rkPlayerNavigation]PlayerNavigationContextВласні стрілки навігації вперед і назад; клас директиви — RkPlayerNavigationDirective
[rkPlayerNestedNavigation]PlayerNestedNavigationContextВласні стрілки навігації для внутрішнього горизонтального слайдера; клас директиви — RkPlayerNestedNavigationDirective
[rkPlayerNestedSlide]PlayerNestedSlideContextВласний вміст кожного слайда всередині внутрішнього горизонтального слайдера; клас директиви — RkPlayerNestedSlideDirective
[rkPlayerSlide]PlayerSlideContext<T>Повністю власний вміст слайда, що замінює стандартний медіаслайд; клас директиви — RkPlayerSlideDirective
[rkPlayerSlideOverlay]PlayerSlideOverlayContext<T>Оверлей для кожного слайда (дані автора, лайки, опис тощо); клас директиви — RkPlayerSlideOverlayDirective
[rkPlayerTimeline]PlayerTimelineContext<T>Власна смуга таймлайну відтворення. Показується лише тоді, коли за правилом (режим timeline плюс мінімальна тривалість) вивелася б стандартна смуга — логіка auto/always/never та сама. Клас директиви — RkPlayerTimelineDirective

PLAYER_TEMPLATE_SLOT_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);
  }
}

Стандартні компоненти слайда

Частини, з яких складено стандартний слайд. Кожна — самостійний компонент; використайте його у власному шаблоні, щоб зберегти частину стандартного вигляду.

КомпонентСелекторОпис
RkMediaSlideComponentrk-media-slideСтандартний слайд. Рендерить rk-image-slide, rk-video-slide або rk-nested-slider залежно від media елемента вмісту; використайте його в шаблоні rkPlayerSlide, щоб зберегти стандартне медіа й додати поверх власний шар.
RkImageSlideComponentrk-image-slideОдне зображення з object-fit: cover: показує свій poster, доки завантажується, і викликає onReady, onWaiting або onError.
RkVideoSlideComponentrk-video-slideОдне відео на спільному елементі <video>. Потребує SoundStateService, який надає оверлей, тож рендерте його лише всередині плеєра.
RkNestedSliderComponentrk-nested-sliderГоризонтальний слайдер з точками-індикаторами для елемента вмісту з кількома записами media.
RkSlideOverlayComponentrk-slide-overlayСтандартний нижній оверлей: аватар та ім’я автора, опис і лайки з його входів author, description і likes. Використайте його в шаблоні rkPlayerSlideOverlay, щоб зберегти стандартний вигляд.

Власний таймлайн

The rkPlayerTimeline викликається лише тоді, коли за правилами показу оверлей вивів би стандартну смугу (та сама логіка timeline плюс timelineMinDurationSeconds), so you don't re-implement it. Reuse the .rk-reel-timeline на своєму корені, щоб успадкувати притиснення до низу, відступи безпечної зони та проміжок на дотикових пристроях. Викличте state.bindInteractions(el) на своїй доріжці перемотування, щоб підключити перемотування вказівником і клавіатурою.

Вкладений слайдер (мультимедійні елементи)

Коли ContentItem містить кілька записів media , плеєр показує їх у вкладеному горизонтальному слайдері (як карусель Instagram). Через слот rkPlayerNestedSlide можна налаштувати вміст внутрішнього слайда.

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',
};

Завантаження вмісту та обробка помилок

Плеєр стежить за станом завантаження й помилок кожного слайда. Поки вміст вантажиться, показується хвильовий індикатор; для зіпсованого медіа — значок помилки. URL з помилками кешуються, тож повторний перехід одразу показує помилку без нової спроби.

Колбеки життєвого циклу

Коли використовуєте слот rkPlayerSlide , керуйте індикатором завантаження через колбеки контексту:

КолбекКоли викликати
onReadyЗображення завантажилося або відео почало грати. Скидає стани завантаження й помилки.
onWaitingВідео буферизується посеред відтворення. Показує індикатор завантаження.
onErrorВміст не завантажився. Показує оверлей помилки й кешує URL як зіпсований.
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>

Власний інтерфейс завантаження та помилок

Замініть стандартний хвильовий індикатор і значок помилки власними шаблонами:

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>

Таймлайн

Оверлей показує вбудовану смугу таймлайну відтворення над активним відео. Керуйте показом через пропс timeline : 'auto' (типово) показує її, коли активне медіа — відео, довше за timelineMinDurationSeconds (default 30), 'always' щойно активне відео, 'never' вимикає її. Для повністю власної смуги перемотування беріть слот rkPlayerTimeline ; його контекст відкриває timelineState на основі TimelineController.

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

Тему задавайте через власні властивості CSS --rk-reel-timeline-* власними властивостями CSS. Для прямого керування у власних компонентах-споживачах впроваджуйте TimelineStateService.

RkTimelineBarComponent

Стандартний компонент смуги перемотування. Використовує TimelineStateService (його надає RkReelPlayerOverlayComponent) and renders the track, buffered ranges, progress fill, and scrub pill. Selector: rk-timeline-bar. Входи: class?: string, style?: Record<string, string>. Використовуйте всередині шаблону rkPlayerTimeline щоб загорнути або доповнити стандартну смугу; окремо — лише всередині споживача, який надає цей сервіс.

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 . Впроваджується стандартною кнопкою звуку й відкривається в контексті шаблонного слота елементів керування. Можна впровадити у власні елементи керування, що є children оверлея, для прямого доступу.

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>Чи вимкнено звук просто зараз
disabled()Signal<boolean>True, коли на активному слайді немає відео або триває перехід
toggle()() => voidПеремикає стан звуку

Стан в URL

Подивитися демо наживо →

RkReelPlayerUrlOverlayComponent — окремий компонент, стан відкриття якого живе в адресному рядку. Побудуйте контролер через createOverlayUrlState у контексті впровадження й передайте його як [controller]: плеєр відкривається, коли параметр називає слайд, і закривається, коли параметр зникає. Посиланнями можна ділитися, а кнопка «назад» закриває плеєр. RkReelPlayerOverlayComponent stays [isOpen], тож кожен компонент має рівно один драйвер стану відкриття.

Вбудовані клавіші

Слайди можна адресувати вбудованим ключем — розгорніть urlIndexKey (за позицією) або urlStableIdKey (за стабільним id) into the controller — both re-exported from @reelkit/angular. See the посібник зі стану в URL та API ядра.

Застосунок із роутером передає адаптер на базі Router, щоб той лишався єдиним джерелом правди про навігацію: запис в історію повз нього лишає його місцеположення застарілим і втрачає параметр на наступній навігації. createRouterUrlAdapter from @reelkit/angular/ng-router-url-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),
  });
}
  • Відкриття додає один запис в історію. Свайп стрічки замінює його, тож N свайпів не додають записів, і один крок назад завжди виходить із плеєра. «Назад» закриває, а не гортає слайди.
  • «Назад» закриває лише тоді, коли плеєр відкрили всередині застосунку — посилання додало запис. За надісланим посиланням у новій вкладці історії позаду немає, тож кнопка «назад» виведе із сайту; кнопка ✕ або Escape прибирає параметр на місці й лишає вас на сторінці.
  • Пряме посилання ?reel=3 відкриває плеєр одразу на цьому слайді.
  • Параметр, який не називає жодного слайда — застаріла закладка, змінене вручну значення, — прибирається з URL, а не лишає в адресному рядку слайд, який не відкриється.
  • Глибина URL залежить від ключа контролера: одна вісь для самого допису або дві (urlIndexTwoAxisKey) to also carry a multi-media post’s inner image index. Pick one key per app; the shapes do not cross-decode.

Full createOverlayUrlState опції — у довіднику API для Angular.

Один ключ чи два — оберіть глибину URL

Той самий RkReelPlayerUrlOverlayComponent працює з обома формами; він розрізняє їх під час виконання за position контролера, тож жодного входу режиму немає. Ключ обирайте, коли будуєте контролер:

KeyWireCarries
urlIndexKey(…)?reel=3Лише вертикальний допис.
urlIndexTwoAxisKey(…)?reel=3.2Допис та та індекс внутрішнього медіа каруселі.

Дві форми запису навмисно різні — двовісний ключ строго з крапкою (3.0, never a bare 3), so a bare one-axis link does not cross-decode. Switching an app between keys therefore invalidates any previously shared links. Pick one shape and keep it.

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

Стабільні посилання. Індекс адресує за позицією, тож збережений у закладках ?reel=3 відкриє інший допис, щойно стрічку перевпорядкують — а для стрічки це радше правило. urlStableIdKey адресує за стабільним id, scanning the live feed — one call covers the common case.

typescript
protected readonly reel = createOverlayUrlState({
  param: 'reel',
  ...urlStableIdKey({ items: () => this.loaded() }),
});

Pass hashCodec: base64UrlCodec щоб закодувати id в URL у base64url — оборотне маскування, а не криптографічний хеш.

Адресуєте за іншим полем ( slug), or page an infinite feed with locateAsync, and build the 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 синхронний, тож відповідає лише за вже завантажені дописи — надіслане посилання на допис 400 у стрічці, де завантажено 20, нічого не знайде. locateAsync — запасний варіант, що викликається лише коли locate не знаходить: завантажте потрібні сторінки й поверніть індекс, який ця ідентичність отримала.

Shortcut

Keying by the item’s id? Не пишіть кодек і локатор вручну — передайте locateAsync просто в urlStableIdKey({ items, locateAsync }) (він вантажить дані, якщо не знайшов, і повертає індекс). Розгорнутий варіант нижче — для адресації за іншим полем або для повного контролю.

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);
    },
  },
});
  • While locateAsync у процесі, плеєр лишається закритим, а параметр — недоторканим, тож пряме посилання переживає запит. null або відмова прибирає параметр.
  • Відповідь, що приходить після зміни URL, після закриття або після демонтажу, відкидається — повільний запит не відкриє слайд, якого ніхто не просив.
  • Поки триває очікування, нічого не рендериться: цей стан завантаження вже належить сторінці, тож малюйте власний скелетон.
  • Тайм-ауту немає — плеєр не може знати, яка стрічка завдовжки. Завершуйте значенням null коли сторінки скінчилися, інакше оверлей лишиться закритим назавжди.

Власні типи даних

Extend BaseContentItem щоб узяти власну доменну модель. Компонент узагальнений: 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

InputТипТипове значенняОпис
ariaLabelstring'Video player'Доступна назва області діалогу
aspectRationumber | undefinedundefinedСпіввідношення ширини до висоти контейнера на десктопі. Типово 9/16; це ж значення замінює нульове, від’ємне чи нескінченне. На ширині 768px і вужче плеєр займає всю область перегляду.
contentT[] (extends BaseContentItem)requiredМасив елементів вмісту для показу в плеєрі
enableNavKeysbooleantrueВмикає навігацію стрілками клавіатури
enableWheelbooleantrueВмикає навігацію колесом миші
initialIndexnumber0Індекс початково видимого елемента, від нуля
initialInnerIndexnumber0Індекс внутрішнього медіа, з якого відкриватися, лише для початково видимого допису — дає змогу двовісному URL вести просто до потрібного зображення мультимедійного допису. Ігнорується, щойно користувач починає гортати.
isOpenbooleanrequiredКерує видимістю оверлея; якщо false, оверлей прибирається з DOM
loopbooleanfalseВмикає нескінченний цикл між слайдами
swipeDistanceFactornumber0.12Мінімальна частка відстані свайпу, щоб змінити слайд
timelineTimelineMode ('auto' | 'always' | 'never')'auto'Правило показу вбудованої смуги таймлайну. 'auto' показує її лише для відео, довших за timelineMinDurationSeconds; 'always' — щойно на активному слайді є відео; 'never' вимикає вбудовану смугу (для повної заміни беріть шаблонний слот rkPlayerTimeline).
timelineMinDurationSecondsnumber30Мінімальна тривалість відео (у секундах), за якої timeline='auto' показує вбудовану смугу. Короткі зациклені кліпи нижче цього порогу не показують її.
transitionDurationnumber300Тривалість анімації слайда в мілісекундах
wheelDebounceMsnumber200Тривалість дебаунсу подій колеса в мілісекундах

Виходи RkReelPlayerOverlayComponent

OutputТипОпис
apiReadyEventEmitter<ReelApi>Видається, коли слайдер готовий, і відкриває імперативний API
closedEventEmitter<void>Видається, коли плеєр закрито
slideChangeEventEmitter<number>Видається, коли змінюється індекс активного слайда
innerSlideChangeEventEmitter<{ outer: number; inner: number }>Видається, коли змінюється індекс внутрішнього медіа активного допису — під час внутрішньої навігації та під час активації зовнішнього, повідомляючи поточний внутрішній індекс активованого допису (0 для допису з одним медіа).

Входи RkReelPlayerUrlOverlayComponent

Приймає всі входи вище, крім isOpen та initialIndex, replaced by a controller position якого обирає слайд. Виходи closed та slideChange.

InputТипТипове значенняОпис
controllerUrlStateControllerrequiredКонтролер із createOverlayUrlState. Its позицією вирішує, чи плеєр відкритий і який слайд показує; оверлей записує через нього назад на зміну слайда та на закриття.

Інтерфейс MediaItem

FieldТипОпис
idstringУнікальний ідентифікатор медіаелемента
typeMediaTypeТип медіа: 'image' | 'video'
srcstringURL медіафайлу
posterstring?URL мініатюри-постера для елементів-відео
aspectRationumberспіввідношення ширини до висоти. Значення < 1 означають вертикальне (cover), > 1 — горизонтальне (contain)

Типи контексту шаблонних слотів

Тип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; innerActiveIndex дорівнює null поза вкладеним слайдером
PlayerErrorContext{ $implicit: activeIndex, item, innerActiveIndex } — що отримує шаблон rkPlayerError

Класи CSS

Усі класи CSS звичайні (не scoped), тож їх можна перекрити селекторами вищої специфічності в таблиці стилів, підключеній після @reelkit/angular-reel-player/styles.css. Для змін кольору, розміру та z-index краще беріть власні властивості CSS, описані в розділі Theming нижче — вони саме для цього й зроблені.

ClassComponentОпис
.rk-reel-overlayOverlayФіксоване повноекранне тло (фон, z-index)
.rk-reel-containerOverlayКонтейнер плеєра (позиція, переповнення)
.rk-reel-loaderOverlayОверлей хвильової анімації завантаження
.rk-reel-media-errorOverlayОверлей стану помилки (значок і текст по центру)
.rk-reel-media-error-textOverlayТекст повідомлення про помилку
.rk-reel-buttonControlsСпільна кругла кнопка з іконкою (закриття, звук, стрілки)
.rk-reel-close-btnControlsКнопка закриття
.rk-reel-sound-btnControlsКнопка перемикання звуку
.rk-reel-nav-arrowsНавігаціяКонтейнер стрілок лише для десктопа (прихований на ширині 768px і вужче)
.rk-reel-nav-btnНавігаціяОкрема стрілка вперед або назад
.rk-reel-slide-wrapperSlideОбгортка навколо медіа та оверлея
.rk-reel-slide-overlaySlideOverlayКонтейнер градієнтного оверлея
.rk-reel-slide-overlay-authorSlideOverlayРядок автора (аватар та ім’я)
.rk-reel-slide-overlay-avatarSlideOverlayЗображення аватара автора
.rk-reel-slide-overlay-nameSlideOverlayТекст імені автора
.rk-reel-slide-overlay-descriptionSlideOverlayТекст опису
.rk-reel-slide-overlay-likesSlideOverlayРядок лайків (сердечко й лічильник)
.rk-reel-video-containerVideoSlideОбгортка відео (фон, переповнення)
.rk-reel-video-elementVideoSlideЕлемент <video>
.rk-reel-video-posterVideoSlideПостер (зникає з початком відтворення)
.rk-reel-video-loaderVideoSlideХвильова анімація завантаження
.rk-reel-video-poster.rk-visibleVideoSlideМодифікатор стану постера, поки відео на паузі або вантажиться
.rk-reel-nested-indicatorNestedSliderПагінація точками під мультимедійними слайдами (позиція різна на десктопі й на дотикових пристроях)
.rk-reel-nested-navNestedSliderСтрілки горизонтальної каруселі (приховані на ширині 768px і вужче)
.rk-reel-nested-nav-nextNestedSliderПозиція вкладеної стрілки вперед
.rk-reel-nested-nav-prevNestedSliderПозиція вкладеної стрілки назад
.rk-reel-nested-slider-innerNestedSliderКорінь вкладеного горизонтального слайдера
.rk-reel-timelineTimelineBarОбгортка смуги перемотування. Використовуйте її на корені власного шаблону `rkPlayerTimeline`, щоб успадкувати розташування впритул до низу, відступ безпечної зони та відступ від накладки слайда на сенсорних пристроях.
.rk-reel-timeline-trackTimelineBarДоріжка (невідтворена частина)
.rk-reel-timeline-bufferedTimelineBarШар буферизованих сегментів
.rk-reel-timeline-fillTimelineBarЗаповнення відтвореного прогресу
.rk-reel-timeline-cursorTimelineBarПовзунок перемотування (плаває над доріжкою)

Theming

Кожен колір, розмір, z-index і перехід живе у власній властивості CSS. Перевизначайте одну чи кілька на :root (або на будь-якому предку оверлея), щоб змінити тему, не чіпаючи код компонентів. Токени збігаються з пакетами для React і Vue, тож перевизначення переносяться між прив’язками.

TokenТипове значенняControls
--rk-reel-overlay-bg#000Колір повноекранного фону
--rk-reel-overlay-z1000z-index оверлею
--rk-reel-button-bgrgba(0, 0, 0, 0.5)Типовий фон круглої кнопки
--rk-reel-button-bg-hoverrgba(255, 255, 255, 0.1)Фон стрілки навігації (і базовий стан наведення)
--rk-reel-button-bg-hover-strongrgba(255, 255, 255, 0.2)Фон стрілки навігації під час наведення
--rk-reel-button-fg#fffКолір іконки кнопки
--rk-reel-button-size44pxШирина й висота кнопки
--rk-reel-button-radius50%Радіус заокруглення кнопки
--rk-reel-ui-z10z-index кнопок закриття, звуку й навігації
--rk-reel-edge-padding16pxВідступ від краю для кнопок закриття, звуку та стрілок навігації
--rk-reel-nav-gap8pxВідстань між розташованими одна над одною стрілками навігації
--rk-reel-transition0.2sТривалість переходу під час наведення
--rk-reel-loader-colorrgba(255, 255, 255, 0.12)Колір градієнта хвильового завантажувача
--rk-reel-loader-duration1.8sТривалість анімації хвильового завантажувача
--rk-reel-error-fgrgba(255, 255, 255, 0.4)Колір іконки й тексту помилки
--rk-reel-slide-overlay-bglinear-gradient(transparent, rgba(0, 0, 0, 0.7))Градієнт затемнення під підписом
--rk-reel-slide-overlay-padding48px 16px 16pxВнутрішній відступ підпису
--rk-reel-slide-overlay-name-color#fffКолір імені автора
--rk-reel-video-bg#000Фон смуг навколо <video>
--rk-reel-video-loader-colorrgba(255, 255, 255, 0.15)Колір мерехтіння під час буферизації відео
--rk-reel-nested-button-bgrgba(0, 0, 0, 0.5)Фон вкладеної стрілки
--rk-reel-nested-button-size36pxРозмір вкладеної стрілки
--rk-reel-nested-edge-padding12pxВідступ вкладеної стрілки від краю
--rk-reel-timeline-trackrgba(255, 255, 255, 0.22)Фон доріжки (невідтворена частина)
--rk-reel-timeline-bufferedrgba(255, 255, 255, 0.4)Колір буферизованих відрізків
--rk-reel-timeline-fill#fffКолір заповнення відтвореної частини
--rk-reel-timeline-cursor#fffКолір капсули повзунка перемотування
--rk-reel-timeline-height3pxВисота доріжки у спокої
--rk-reel-timeline-height-active6pxВисота доріжки під час наведення, фокусу й перемотування
--rk-reel-timeline-cursor-width10pxШирина капсули перемотування у спокої
--rk-reel-timeline-cursor-width-active14pxШирина капсули під час перемотування
--rk-reel-timeline-cursor-height24pxВисота капсули перемотування у спокої
--rk-reel-timeline-cursor-height-active32pxВисота капсули під час перемотування
--rk-reel-timeline-transition0.15s ease-outАнімація збільшення й зменшення доріжки та капсули

Вставте фрагмент нижче в таблицю стилів, підключену після @reelkit/angular-reel-player/styles.css.

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;
}

Accessibility

Корінь оверлея — модальний діалог (role="dialog", aria-modal="true"). Set the ariaLabel щоб змінити оголошення для екранного читача; типове значення — «Video player». Кожен слайд несе role="group", aria-roledescription="slide", та aria-label="Слайд N з M".

Оверлей захоплює фокус під час відкриття й повертає його на елемент-тригер після закриття. Tab і Shift+Tab циклічно проходять фокусовані елементи всередині; фокус, що вислизнув (клік поза оверлеєм, програмна установка), повертається назад. Реалізовано через captureFocusForReturn та createFocusTrap from @reelkit/core.

Клавіатурні скорочення

KeyAction
ArrowUpПопередній слайд
ArrowDownНаступний слайд
ArrowLeftПопереднє медіа (у вкладеному слайдері)
ArrowRightНаступне медіа (у вкладеному слайдері)
EscapeЗакрити плеєр