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-angularIconos
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.
Uso básico
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);
}
}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.) |
| [rkPlayerError] | { $implicit: activeIndex, item, innerActiveIndex } | Slot de plantilla para un indicador de error propio |
| [rkPlayerLoading] | { $implicit: activeIndex, item, innerActiveIndex } | Slot de plantilla para un indicador de carga propio |
| [rkPlayerNavigation] | PlayerNavigationContext | Flechas de navegación de anterior y siguiente propias |
| [rkPlayerNestedNavigation] | PlayerNestedNavigationContext | Flechas de navegación propias para el slider horizontal interior |
| [rkPlayerNestedSlide] | PlayerNestedSlideContext | Contenido propio de cada slide dentro del slider horizontal interior |
| [rkPlayerSlide] | PlayerSlideContext<T> | Contenido del slide totalmente propio que sustituye el slide de medios por defecto |
| [rkPlayerSlideOverlay] | PlayerSlideOverlayContext<T> | Overlay por slide (información del autor, me gusta, descripción, etc.) |
| [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). |
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);
}
}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. En móvil 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 | '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 | 'image' | 'video' | Tipo de medio |
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) } |
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 por debajo de 768px) |
.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 por debajo de 768px) |
.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 |