Angular Reel Player
Reproductor vertical de medios a pantalla completa al estilo de Instagram y TikTok para Angular, con @reelkit/angular-reel-player.
Características
Instalación
npm install @reelkit/angular-reel-player @reelkit/angular lucide-angularRegistra la hoja de estilos una sola vez:
// 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"]Iconos
Los controles por defecto usan lucide-angular para los iconos (cerrar, sonido, flechas de navegación). Si prefieres otra librería de iconos, usa los slots de plantilla rkPlayerControls y rkPlayerNavigation para poner los tuyos.
Inicio rápido
Importa la hoja de estilos y el componente standalone RkReelPlayerOverlayComponent en el array imports de tu componente.
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);
}
}Dónde se renderiza el overlay
A diferencia de los reproductores de React y Vue, que mueven su overlay al final de document.body, rk-reel-player-overlay se renderiza donde lo colocas y cubre la pantalla con position: fixed. Mantenlo fuera de cualquier ancestro con transform, perspective, filter o will-change: transform: ese ancestro pasa a ser el bloque contenedor del overlay, así que el reproductor toma su tamaño en lugar del del viewport y queda recortado por su overflow. Un ancestro que crea su propio contexto de apilamiento también limita cuánto puede elevar --rk-reel-overlay-z al reproductor. A cambio, los tokens --rk-reel-* definidos en cualquier ancestro llegan al reproductor.
Slots de plantilla
Seis directivas de slots de plantilla te permiten personalizar cada aspecto de la interfaz del reproductor. Cada una recibe un objeto de contexto con tipos estrictos. Pasa solo los slots que quieras sobrescribir; para el resto se usan los valores por defecto.
| Directiva | Tipo del contexto | Descripción |
|---|---|---|
| [rkPlayerControls] | PlayerControlsContext<T> | Barra de controles globales propia (cerrar, botón de sonido, etc.); la clase de la directiva es RkPlayerControlsDirective |
| [rkPlayerError] | PlayerErrorContext | Slot de plantilla para un indicador de error propio; la clase de la directiva es RkPlayerErrorDirective |
| [rkPlayerLoading] | PlayerLoadingContext | Slot de plantilla para un indicador de carga propio; la clase de la directiva es RkPlayerLoadingDirective |
| [rkPlayerNavigation] | PlayerNavigationContext | Flechas de navegación de anterior y siguiente propias; la clase de la directiva es RkPlayerNavigationDirective |
| [rkPlayerNestedNavigation] | PlayerNestedNavigationContext | Flechas de navegación propias para el slider horizontal interior; la clase de la directiva es RkPlayerNestedNavigationDirective |
| [rkPlayerNestedSlide] | PlayerNestedSlideContext | Contenido propio de cada slide dentro del slider horizontal interior; la clase de la directiva es RkPlayerNestedSlideDirective |
| [rkPlayerSlide] | PlayerSlideContext<T> | Contenido del slide totalmente propio que sustituye el slide de medios por defecto; la clase de la directiva es RkPlayerSlideDirective |
| [rkPlayerSlideOverlay] | PlayerSlideOverlayContext<T> | Overlay por slide (información del autor, me gusta, descripción, etc.); la clase de la directiva es RkPlayerSlideOverlayDirective |
| [rkPlayerTimeline] | PlayerTimelineContext<T> | Barra de reproducción propia. Solo se renderiza cuando la regla (modo de timeline y duración mínima) renderizaría la barra por defecto (la misma lógica de auto/always/never). La clase de la directiva es RkPlayerTimelineDirective |
PLAYER_TEMPLATE_SLOT_DIRECTIVES reúne todas estas directivas, así que un componente puede expandirlas en sus imports de una sola vez.
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">▲</button>
<button (click)="onNext()" [disabled]="activeIndex === count - 1">▼</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);
}
}Componentes de slide por defecto
Las piezas con las que está hecho el slide por defecto. Cada una es un componente standalone; usa una dentro de tu propia plantilla para conservar parte del aspecto por defecto.
| Componente | Selector | Descripción |
|---|---|---|
RkMediaSlideComponent | rk-media-slide | El slide por defecto. Renderiza rk-image-slide, rk-video-slide o rk-nested-slider según el media del elemento de contenido; úsalo en una plantilla rkPlayerSlide para conservar los medios por defecto y añadir tu propia capa encima. |
RkImageSlideComponent | rk-image-slide | Una imagen con object-fit: cover que muestra su poster hasta que carga y llama a onReady, onWaiting o onError. |
RkVideoSlideComponent | rk-video-slide | Un vídeo en el elemento <video> compartido. Necesita el SoundStateService que proporciona el overlay, así que renderízalo solo dentro del reproductor. |
RkNestedSliderComponent | rk-nested-slider | El slider horizontal, con puntos indicadores, para un elemento de contenido que tiene varias entradas en media. |
RkSlideOverlayComponent | rk-slide-overlay | El overlay inferior por defecto: avatar y nombre del autor, descripción y me gusta, a partir de sus inputs author, description y likes. Úsalo en una plantilla rkPlayerSlideOverlay para conservar el aspecto por defecto. |
Línea de tiempo propia
El slot de plantilla rkPlayerTimeline solo se usa cuando las reglas del overlay renderizarían la barra por defecto (el mismo modo timeline y timelineMinDurationSeconds), así que no tienes que reimplementarlas. Reutiliza la clase .rk-reel-timeline en tu raíz para heredar la posición pegada al borde inferior, el relleno de la zona segura y el espacio que deja en dispositivos táctiles. Llama a state.bindInteractions(el) en tu pista de arrastre para conectar el arrastre con puntero y teclado.
Slider anidado (elementos con varios medios)
Cuando un ContentItem tiene varias entradas en media, el reproductor las muestra en un slider horizontal anidado (al estilo del carrusel de Instagram). Usa el slot rkPlayerNestedSlide para personalizar el contenido de los slides interiores.
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',
};Carga de contenido y gestión de errores
El reproductor sigue el estado de carga y de error de cada slide. Mientras el contenido se carga se muestra una animación de onda; con un medio roto aparece un icono de error. Las URLs con error se guardan en caché, así que al volver se muestra el error al instante sin reintentar.
Callbacks del ciclo de vida
Si usas el slot de plantilla rkPlayerSlide, usa los callbacks del contexto para controlar el indicador de carga:
| Callback | Cuándo llamarlo |
|---|---|
onReady | La imagen se ha cargado o el vídeo ha empezado a reproducirse. Limpia los estados de carga y de error. |
onWaiting | El vídeo está cargando a mitad de la reproducción. Muestra el indicador de carga. |
onError | El contenido no se ha podido cargar. Muestra el overlay de error y guarda la URL en caché como rota. |
<!-- 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>Interfaz de carga y de error propia
Sustituye la animación de onda y el icono de error por defecto por plantillas propias:
<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>Línea de tiempo
El overlay renderiza una barra de reproducción incluida sobre el vídeo activo. Controla cuándo con el input timeline: 'auto' (por defecto) la renderiza siempre que el medio activo sea un vídeo más largo que timelineMinDurationSeconds (30 por defecto), 'always' siempre que haya un vídeo activo y 'never' la desactiva. Para una barra de arrastre totalmente propia, usa la directiva de plantilla rkPlayerTimeline; su contexto expone un timelineState respaldado por el TimelineController que hay debajo.
<rk-reel-player-overlay
[isOpen]="isOpen()"
[content]="items"
timeline="auto"
[timelineMinDurationSeconds]="30"
(closed)="isOpen.set(false)"
/>Cambia su tema con las propiedades personalizadas de CSS --rk-reel-timeline-*. Para controlarla directamente desde componentes propios, inyecta TimelineStateService.
RkTimelineBarComponent
Componente de la barra de reproducción por defecto. Usa TimelineStateService (lo proporciona RkReelPlayerOverlayComponent) y renderiza la pista, los rangos cargados, el relleno del progreso y la píldora de arrastre. Selector: rk-timeline-bar. Inputs: class?: string, style?: Record<string, string>. Úsalo dentro de una plantilla rkPlayerTimeline para envolver o ampliar la barra por defecto; por separado, úsalo solo dentro de un componente que proporcione el servicio.
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
Se proporciona en el nivel de RkReelPlayerOverlayComponent. Lo inyecta el botón de sonido por defecto y se expone en el contexto del slot de plantilla de los controles. Puedes inyectarlo en controles propios que sean hijos del overlay para acceder a él directamente.
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()"
}| Miembro | Tipo | Descripción |
|---|---|---|
muted() | Signal<boolean> | Indica si el reproductor está en silencio |
disabled() | Signal<boolean> | Es true cuando el slide activo no tiene vídeo o está en transición |
toggle() | () => void | Alterna el estado de silencio |
Estado en la URL
Ver la demo en vivo →RkReelPlayerUrlOverlayComponent es un componente aparte cuyo estado abierto vive en la barra de direcciones. Crea un controlador con createOverlayUrlState en un contexto de inyección y pásalo como [controller]: el reproductor se abre cuando el parámetro nombra un slide y se cierra cuando desaparece. Los enlaces se pueden compartir y el botón de volver cierra el reproductor. RkReelPlayerOverlayComponent sigue controlándose con [isOpen], así que cada componente tiene una única forma de controlar el estado abierto.
Claves incluidas
Puedes apuntar a los slides con una clave incluida: pasa con spread urlIndexKey (por posición) o urlStableIdKey (por un id estable) al controlador; las dos se reexportan desde @reelkit/angular. Consulta la guía del estado en la URL y la API del core.
Una aplicación con router pasa un adaptador basado en el Router, para que este siga siendo la única fuente de verdad de la navegación: escribir en el historial a sus espaldas deja su ubicación desfasada y pierde el parámetro en la siguiente navegación. createRouterUrlAdapter de @reelkit/angular/ng-router-url-adapter es el adaptador listo para usar.
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),
});
}- Abrir añade una entrada al historial. Deslizar por el feed la sustituye, así que N deslizamientos no añaden entradas y volver una vez siempre sale del reproductor. Volver cierra; no cambia de slide.
- Volver solo cierra cuando el reproductor se abrió desde dentro de la aplicación, es decir, cuando el enlace añadió una entrada. Un enlace compartido abierto directamente en una pestaña nueva no tiene historial detrás, así que el botón de volver del navegador sale del sitio; el botón ✕ o Escape quitan el parámetro en el sitio y se quedan.
- Un enlace directo
?reel=3abre el reproductor en ese slide al cargar. - Un parámetro que no nombra ningún slide (un marcador desfasado, un valor editado a mano) se quita de la URL en lugar de dejar la barra de direcciones apuntando a un slide que no se puede abrir.
- La profundidad de la URL depende de la clave del controlador: un eje solo para la publicación, o dos ejes (
urlIndexTwoAxisKey) para llevar también el índice de la imagen interior de una publicación con varios medios. Elige una clave por aplicación; los formatos no se decodifican entre sí.
Las opciones completas de createOverlayUrlState están en la referencia de la API de Angular.
Una clave o dos: elige la profundidad de la URL
El mismo RkReelPlayerUrlOverlayComponent admite las dos formas; las distingue en tiempo de ejecución por la posición del controlador, así que no hay ningún input de modo. Elige la clave al crear el controlador:
| Clave | Formato | Lleva |
|---|---|---|
urlIndexKey(…) | ?reel=3 | Solo la publicación vertical. |
urlIndexTwoAxisKey(…) | ?reel=3.2 | La publicación y el índice del medio interior de un carrusel. |
Los dos formatos son distintos a propósito: una clave de dos ejes siempre lleva punto (3.0, nunca un 3 suelto), así que un enlace de un eje no se decodifica como de dos. Por eso cambiar una aplicación de una clave a otra invalida los enlaces compartidos antes. Elige una forma y mantenla.
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.2Enlaces estables. El índice es posicional, así que un ?reel=3 guardado abre otra publicación en cuanto el feed se reordena, y en un feed eso es lo normal. urlStableIdKey usa como clave el id estable de cada publicación y recorre el feed actual; una sola llamada cubre el caso habitual.
protected readonly reel = createOverlayUrlState({
param: 'reel',
...urlStableIdKey({ items: () => this.loaded() }),
});Pasa hashCodec: base64UrlCodec para codificar el id en base64url en la URL: es una ofuscación reversible, no un hash criptográfico.
Si usas otro campo como clave (un slug) o paginas un feed infinito con locateAsync, crea tú el codec y el locator. Son dos tareas distintas: codec escribe la identidad en la URL y locator encuentra dónde está esa identidad.
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,
},
});Feeds infinitos. locate es síncrono, así que solo puede responder por las publicaciones ya cargadas: un enlace compartido a la publicación 400 de un feed que ha cargado 20 no encuentra nada. locateAsync es la alternativa y solo se llama cuando locate falla: carga las páginas que necesites y devuelve el índice que resulte tener la identidad.
Atajo
¿Usas el id del elemento como clave? Sáltate el codec y el locator hechos a mano y pasa locateAsync directamente a urlStableIdKey({ items, locateAsync }) (carga cuando no lo encuentra y después devuelve el índice). La versión más completa de abajo es para usar otro campo como clave o para tener el control total.
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);
},
},
});- Mientras
locateAsyncestá pendiente, el reproductor sigue cerrado y el parámetro no se toca, así que el enlace directo sobrevive a la carga.nullo un rechazo quitan el parámetro. - Una respuesta que llega después de que la URL haya cambiado, después de cerrar o después de desmontar se descarta: una carga lenta no puede abrir un slide que nadie ha pedido.
- Mientras está pendiente no se renderiza nada; la página ya es dueña de ese estado de carga, así que renderiza tu propio esqueleto.
- No hay tiempo límite: el reproductor no puede saber lo largo que es el feed. Resuelve con
nullcuando se agote la paginación, o el overlay seguirá cerrado indefinidamente.
Tipos de datos personalizados
Extiende BaseContentItem para usar tu propio modelo de dominio. El componente es genérico: RkReelPlayerOverlayComponent<T extends BaseContentItem>.
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[] = [];
}Inputs de RkReelPlayerOverlayComponent
| Input | Tipo | Por defecto | Descripción |
|---|---|---|---|
ariaLabel | string | 'Video player' | Etiqueta accesible de la región del diálogo |
aspectRatio | number | undefined | undefined | Relación entre ancho y alto del contenedor en escritorio. Por defecto es 9/16, que también sustituye a un valor cero, negativo o no finito. Con 768px de ancho o menos el reproductor ocupa todo el viewport. |
content | T[] (extends BaseContentItem) | obligatorio | Array de elementos de contenido que mostrar en el reproductor |
enableNavKeys | boolean | true | Activa la navegación con las teclas de flecha |
enableWheel | boolean | true | Activa la navegación con la rueda del ratón |
initialIndex | number | 0 | Índice, empezando en cero, del elemento visible al principio |
initialInnerIndex | number | 0 | Índice del medio interior con el que abrir, solo para la publicación visible al principio: permite que una URL de dos ejes enlace a una imagen concreta de una publicación con varios medios. Se ignora cuando el usuario navega. |
isOpen | boolean | obligatorio | Controla la visibilidad del overlay; con false el overlay se quita del DOM |
loop | boolean | false | Activa el bucle infinito entre slides |
swipeDistanceFactor | number | 0.12 | Fracción mínima de la distancia de deslizamiento para cambiar de slide |
timeline | TimelineMode ('auto' | 'always' | 'never') | 'auto' | Cuándo se muestra la barra de reproducción incluida. 'auto' solo la renderiza con vídeos más largos que timelineMinDurationSeconds; 'always' la renderiza siempre que el slide activo tenga un vídeo; 'never' desactiva la barra incluida (usa el slot de plantilla rkPlayerTimeline para sustituirla por completo). |
timelineMinDurationSeconds | number | 30 | Duración mínima del vídeo (en segundos) para que timeline='auto' renderice la barra incluida. Los clips cortos en bucle por debajo de este umbral no la muestran. |
transitionDuration | number | 300 | Duración de la animación de los slides en ms |
wheelDebounceMs | number | 200 | Duración del debounce de los eventos de la rueda en ms |
Outputs de RkReelPlayerOverlayComponent
| Output | Tipo | Descripción |
|---|---|---|
apiReady | EventEmitter<ReelApi> | Se emite cuando el slider está listo y expone la API imperativa |
closed | EventEmitter<void> | Se emite al cerrar el reproductor |
slideChange | EventEmitter<number> | Se emite cuando cambia el índice del slide activo |
innerSlideChange | EventEmitter<{ outer: number; inner: number }> | Se emite cuando cambia el índice del medio interior de la publicación activa: al navegar por dentro y al activar una publicación, con su índice interior actual (0 en una publicación con un solo medio). |
Inputs de RkReelPlayerUrlOverlayComponent
Acepta todos los inputs de arriba salvo isOpen e initialIndex, que se sustituyen por un controller cuya posición elige el slide. Tiene los outputs closed y slideChange.
| Input | Tipo | Por defecto | Descripción |
|---|---|---|---|
controller | UrlStateController | obligatorio | Controlador de createOverlayUrlState. Su position decide si el reproductor está abierto y qué slide muestra; el overlay escribe a través de él al cambiar de slide y al cerrar. |
Interfaz MediaItem
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Identificador único del medio |
type | MediaType | Tipo de medio: 'image' | 'video' |
src | string | URL del medio |
poster | string? | URL de la miniatura del póster en los vídeos |
aspectRatio | number | Relación entre ancho y alto. Los valores < 1 indican vertical (cover) y > 1, horizontal (contain) |
Tipos de contexto de los slots de plantilla
| Tipo | Campos |
|---|---|
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 } en segundos: una entrada de bufferedRanges() |
PlayerLoadingContext | { $implicit: activeIndex, item, innerActiveIndex }: lo que recibe la plantilla rkPlayerLoading; innerActiveIndex es null fuera de un slider anidado |
PlayerErrorContext | { $implicit: activeIndex, item, innerActiveIndex }: lo que recibe la plantilla rkPlayerError |
Clases CSS
Todas las clases CSS son normales (no scoped), así que puedes apuntar a ellas con selectores más específicos en una hoja de estilos cargada después de @reelkit/angular-reel-player/styles.css. Para cambiar colores, tamaños y z-index, es mejor usar las propiedades personalizadas de CSS de la sección Temas de más abajo: están pensadas justo para eso.
| Clase | Componente | Descripción |
|---|---|---|
.rk-reel-overlay | Overlay | Fondo fijo a pantalla completa (fondo, z-index) |
.rk-reel-container | Overlay | Contenedor del reproductor (posición, desbordamiento) |
.rk-reel-loader | Overlay | Overlay con la animación de carga en onda |
.rk-reel-media-error | Overlay | Overlay del estado de error (icono y texto centrados) |
.rk-reel-media-error-text | Overlay | Texto del mensaje de error |
.rk-reel-button | Controls | Botón circular de icono compartido (cerrar, sonido, flechas de navegación) |
.rk-reel-close-btn | Controls | Botón de cerrar |
.rk-reel-sound-btn | Controls | Botón de sonido |
.rk-reel-nav-arrows | Navigation | Contenedor de flechas solo para escritorio (oculto con 768px de ancho o menos) |
.rk-reel-nav-btn | Navigation | Cada flecha de navegación de anterior o siguiente |
.rk-reel-slide-wrapper | Slide | Envoltorio alrededor del medio y el overlay |
.rk-reel-slide-overlay | SlideOverlay | Contenedor del overlay con degradado |
.rk-reel-slide-overlay-author | SlideOverlay | Fila del autor (avatar y nombre) |
.rk-reel-slide-overlay-avatar | SlideOverlay | Imagen del avatar del autor |
.rk-reel-slide-overlay-name | SlideOverlay | Texto con el nombre del autor |
.rk-reel-slide-overlay-description | SlideOverlay | Texto de la descripción |
.rk-reel-slide-overlay-likes | SlideOverlay | Fila de me gusta (corazón y recuento) |
.rk-reel-video-container | VideoSlide | Envoltorio del vídeo (fondo, desbordamiento) |
.rk-reel-video-element | VideoSlide | El elemento <video> |
.rk-reel-video-poster | VideoSlide | Imagen del póster (se desvanece al reproducir) |
.rk-reel-video-loader | VideoSlide | Animación de carga en onda |
.rk-reel-video-poster.rk-visible | VideoSlide | Modificador de estado que se aplica al póster mientras el vídeo está en pausa o cargando |
.rk-reel-nested-indicator | NestedSlider | Paginación con puntos bajo los slides con varios medios (su posición cambia entre escritorio y táctil) |
.rk-reel-nested-nav | NestedSlider | Flechas del carrusel horizontal (ocultas con 768px de ancho o menos) |
.rk-reel-nested-nav-next | NestedSlider | Posición de la flecha anidada de siguiente |
.rk-reel-nested-nav-prev | NestedSlider | Posición de la flecha anidada de anterior |
.rk-reel-nested-slider-inner | NestedSlider | Raíz del slider horizontal anidado |
.rk-reel-timeline | TimelineBar | Envoltorio de la barra de arrastre. Reutilízalo en las raíces de una plantilla `rkPlayerTimeline` propia para heredar la posición pegada al borde inferior, el relleno de la zona segura y el espacio que deja al overlay del slide en dispositivos táctiles. |
.rk-reel-timeline-track | TimelineBar | Pista (la zona sin reproducir) |
.rk-reel-timeline-buffered | TimelineBar | Capa de segmentos cargados |
.rk-reel-timeline-fill | TimelineBar | Relleno del progreso reproducido |
.rk-reel-timeline-cursor | TimelineBar | Píldora de arrastre (flota sobre la pista) |
Temas
Cada color, tamaño, z-index y transición vive en una propiedad personalizada de CSS. Sobrescribe una o varias en :root (o en cualquier ancestro del overlay) para cambiar el tema sin tocar el código de los componentes. Los tokens coinciden con los de los paquetes de React y Vue, así que los cambios sirven para todos los bindings.
| Token | Por defecto | Controla |
|---|---|---|
--rk-reel-overlay-bg | #000 | Color del fondo a pantalla completa |
--rk-reel-overlay-z | 1000 | z-index del overlay |
--rk-reel-button-bg | rgba(0, 0, 0, 0.5) | Fondo por defecto de los botones circulares |
--rk-reel-button-bg-hover | rgba(255, 255, 255, 0.1) | Fondo de las flechas de navegación (y estado hover base) |
--rk-reel-button-bg-hover-strong | rgba(255, 255, 255, 0.2) | Fondo de las flechas de navegación al pasar el ratón |
--rk-reel-button-fg | #fff | Color del icono de los botones |
--rk-reel-button-size | 44px | Ancho / alto de los botones |
--rk-reel-button-radius | 50% | border-radius de los botones |
--rk-reel-ui-z | 10 | z-index de cerrar / sonido / navegación |
--rk-reel-edge-padding | 16px | Separación del borde de cerrar / sonido / flechas |
--rk-reel-nav-gap | 8px | Espacio entre las flechas de navegación apiladas |
--rk-reel-transition | 0.2s | Duración de la transición al pasar el ratón |
--rk-reel-loader-color | rgba(255, 255, 255, 0.12) | Color del degradado de la animación de onda |
--rk-reel-loader-duration | 1.8s | Duración de la animación de onda |
--rk-reel-error-fg | rgba(255, 255, 255, 0.4) | Color del icono y el texto de error |
--rk-reel-slide-overlay-bg | linear-gradient(transparent, rgba(0, 0, 0, 0.7)) | Degradado del fondo del pie |
--rk-reel-slide-overlay-padding | 48px 16px 16px | Relleno interior del pie |
--rk-reel-slide-overlay-name-color | #fff | Color del nombre del autor |
--rk-reel-video-bg | #000 | Fondo de las bandas detrás del <video> |
--rk-reel-video-loader-color | rgba(255, 255, 255, 0.15) | Color del brillo mientras el vídeo carga |
--rk-reel-nested-button-bg | rgba(0, 0, 0, 0.5) | Fondo de las flechas anidadas |
--rk-reel-nested-button-size | 36px | Tamaño de las flechas anidadas |
--rk-reel-nested-edge-padding | 12px | Separación del borde de las flechas anidadas |
--rk-reel-timeline-track | rgba(255, 255, 255, 0.22) | Fondo de la pista (la zona sin reproducir) |
--rk-reel-timeline-buffered | rgba(255, 255, 255, 0.4) | Color de los segmentos cargados |
--rk-reel-timeline-fill | #fff | Color del relleno del progreso reproducido |
--rk-reel-timeline-cursor | #fff | Color de la píldora de arrastre |
--rk-reel-timeline-height | 3px | Altura de la pista en reposo |
--rk-reel-timeline-height-active | 6px | Altura de la pista al pasar el ratón, con foco o al arrastrar |
--rk-reel-timeline-cursor-width | 10px | Ancho de la píldora en reposo |
--rk-reel-timeline-cursor-width-active | 14px | Ancho de la píldora al arrastrar |
--rk-reel-timeline-cursor-height | 24px | Altura de la píldora en reposo |
--rk-reel-timeline-cursor-height-active | 32px | Altura de la píldora al arrastrar |
--rk-reel-timeline-transition | 0.15s ease-out | Animación al crecer y encoger la pista y la píldora |
Pega el fragmento de abajo en una hoja de estilos cargada después de @reelkit/angular-reel-player/styles.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;
}Accesibilidad
La raíz del overlay es un diálogo modal (role="dialog", aria-modal="true"). Usa el input ariaLabel para cambiar lo que anuncia el lector de pantalla; por defecto es "Video player". Cada slide lleva role="group", aria-roledescription="slide" y aria-label="Slide N of M".
El overlay captura el foco al abrirse y lo devuelve al disparador al cerrarse. Tab y Mayús+Tab recorren los elementos enfocables de dentro; el foco que sale (un clic fuera, un foco por código) vuelve dentro. Está implementado con captureFocusForReturn y createFocusTrap de @reelkit/core.
Atajos de teclado
| Tecla | Acción |
|---|---|
ArrowUp | Slide anterior |
ArrowDown | Slide siguiente |
ArrowLeft | Medio anterior (en el slider anidado) |
ArrowRight | Medio siguiente (en el slider anidado) |
Escape | Cierra el reproductor |