Angular Reel Player

Player vertical de mídia em tela cheia no estilo Instagram/TikTok para Angular, construído sobre @reelkit/angular-reel-player.

Ver demonstração ao vivo →

Recursos

Deslize vertical
Toque, arrasto, teclado, roda
Vídeo automático
Toca quando fica visível
Botão de som
Continuidade no iOS
Várias mídias
Carrosséis horizontais aninhados
Memória de posição
Retoma de onde parou
Captura de quadro
Transição suave do pôster ao vídeo
Virtualizado
Apenas 3 slides no DOM
Proporção
9:16 no desktop, tela inteira no celular
Navegação no desktop
Botões de seta
Tipos genéricos
Modelos de dados próprios
Personalizável
Slots de template para tudo
Tratamento de erros
Detecta mídia quebrada, com cache LRU
Estado na URL
Links para compartilhar, fechamento no botão voltar

Instalação

bash
Ícones

Os controles padrão usam lucide-angular para os ícones (fechar, som, setas de navegação). Se você prefere outra biblioteca de ícones, use os slots de template rkPlayerControls e rkPlayerNavigation para fornecer os seus.

Uso básico

Importe a folha de estilo e o componente standalone RkReelPlayerOverlayComponent no array imports do seu componente.

reel-feed.component.ts

Slots de template

Seis diretivas de slot de template permitem personalizar cada parte da interface do player. Cada uma recebe um objeto de contexto fortemente tipado. Informe apenas os slots que quiser trocar — nos demais valem os padrões.

DiretivaTipo do contextoDescrição
[rkPlayerControls]PlayerControlsContext<T>Barra de controles global própria (fechar, botão de som e afins)
[rkPlayerError]{ $implicit: activeIndex, item, innerActiveIndex }Slot de template do indicador de erro próprio
[rkPlayerLoading]{ $implicit: activeIndex, item, innerActiveIndex }Slot de template do indicador de carregamento próprio
[rkPlayerNavigation]PlayerNavigationContextSetas próprias de avançar e voltar
[rkPlayerNestedNavigation]PlayerNestedNavigationContextSetas próprias do slider horizontal interno
[rkPlayerNestedSlide]PlayerNestedSlideContextConteúdo próprio de cada slide dentro do slider horizontal interno
[rkPlayerSlide]PlayerSlideContext<T>Conteúdo de slide totalmente seu, no lugar do slide de mídia padrão
[rkPlayerSlideOverlay]PlayerSlideOverlayContext<T>Camada de cada slide (dados do autor, curtidas, descrição e afins)
[rkPlayerTimeline]PlayerTimelineContext<T>Barra de reprodução própria. Só é renderizada quando a regra (modo da linha do tempo e duração mínima) renderizaria a barra padrão (a mesma lógica de auto/always/never).
typescript

Linha do tempo própria

O slot de template rkPlayerTimeline só é chamado quando as regras do overlay renderizariam a barra padrão (o mesmo modo timeline e o mesmo timelineMinDurationSeconds), então você não precisa reimplementá-las. Reaproveite a classe .rk-reel-timeline na sua raiz para herdar o posicionamento rente à base, o respiro da área segura e o espaço reservado em dispositivos de toque. Chame state.bindInteractions(el) na sua trilha de arrasto para ligar o arrasto por ponteiro e por teclado.

Slider aninhado (itens com várias mídias)

Quando um ContentItem traz várias entradas em media, o player as exibe em um slider horizontal aninhado (no estilo carrossel do Instagram). Use o slot rkPlayerNestedSlide para personalizar o conteúdo dos slides internos.

typescript

Carregamento de conteúdo e tratamento de erros

O player acompanha o carregamento e os erros de cada slide. Uma animação de onda aparece enquanto o conteúdo carrega; um ícone de erro aparece para mídias quebradas. As URLs com erro ficam em cache, então voltar a elas mostra o erro na hora, sem nova tentativa.

Callbacks de ciclo de vida

Ao usar o slot de template rkPlayerSlide, use os callbacks do contexto para comandar o indicador de carregamento:

CallbackQuando chamar
onReadyA imagem carregou ou o vídeo começou a tocar. Limpa os estados de carregamento e erro.
onWaitingO vídeo está carregando no meio da reprodução. Mostra o indicador de carregamento.
onErrorO conteúdo não carregou. Mostra a camada de erro e registra a URL como quebrada.
html

Interface própria de carregamento e erro

Troque a animação de onda e o ícone de erro padrão por templates seus:

html

Linha do tempo

O overlay desenha uma barra de reprodução embutida sobre o vídeo ativo. Libere-a pelo input timeline: 'auto' (padrão) aparece sempre que a mídia ativa for um vídeo mais longo que timelineMinDurationSeconds (30 por padrão), 'always' sempre que houver um vídeo ativo, 'never' para desligar. Para uma barra totalmente sua, use a diretiva de template rkPlayerTimeline; o contexto dela expõe um timelineState apoiado no TimelineController por baixo.

html

Personalize pelas propriedades CSS --rk-reel-timeline-*. Para controle direto em componentes seus, injete o TimelineStateService.

RkTimelineBarComponent

Componente da barra de reprodução padrão. Consome o TimelineStateService (fornecido pelo RkReelPlayerOverlayComponent) e desenha a trilha, os trechos em buffer, o preenchimento do progresso e a pílula de arrasto. Seletor: rk-timeline-bar. Inputs: class?: string, style?: Record<string, string>. Use dentro de um template rkPlayerTimeline para envolver ou complementar a barra padrão; use isolado apenas dentro de um componente que forneça o serviço.

typescript

SoundStateService

Fornecido no nível do RkReelPlayerOverlayComponent. Injetado pelo botão de som padrão e exposto no contexto do slot de controles. Pode ser injetado em controles próprios que sejam filhos do overlay, para acesso direto.

typescript
MembroTipoDescrição
muted()Signal<boolean>Se o player está sem som no momento
disabled()Signal<boolean>True quando o slide ativo não tem vídeo ou está em transição
toggle()() => voidAlterna o estado de mudo

Estado na URL

Ver demonstração ao vivo →

RkReelPlayerUrlOverlayComponent é um componente à parte cujo estado aberto vive na barra de endereços. Monte um controlador com createOverlayUrlState em um contexto de injeção e passe-o como [controller]: o player abre quando o parâmetro nomeia um slide e fecha quando ele some. Os links dão para compartilhar, e o botão voltar fecha o player. O RkReelPlayerOverlayComponent continua comandado por [isOpen], então cada componente carrega exatamente um comando do estado aberto.

Chaves prontas

Você pode endereçar os slides com uma chave pronta — espalhe urlIndexKey (por posição) ou urlStableIdKey (por um id estável) no controlador — as duas reexportadas por @reelkit/angular. Veja o guia de estado na URL e a API do core.

Um app com rotas passa um adaptador ligado ao Router, para que ele siga sendo a única fonte de verdade da navegação — escrever no histórico por trás dele deixa sua localização desatualizada e derruba o parâmetro na navegação seguinte. createRouterUrlAdapter, de @reelkit/angular/ng-router-url-adapter, é o adaptador pronto.

typescript
  • Abrir empilha uma entrada no histórico. Percorrer o feed substitui essa entrada, então N deslizes não acrescentam nenhuma e um único passo para trás sempre sai do player. Voltar fecha; não troca de slide.
  • Voltar só fecha quando o player foi aberto de dentro do app — foi o link que empilhou a entrada. Um link compartilhado aberto direto em uma aba nova não tem histórico atrás de si, então o voltar do navegador sai do site; o botão ✕ ou o Escape removem o parâmetro ali mesmo e você continua na página.
  • Um link direto ?reel=3 abre o player naquele slide já no carregamento.
  • Um parâmetro que não nomeia slide nenhum — um favorito velho, um valor editado à mão — é retirado da URL, em vez de deixar a barra de endereços afirmando um slide que não abre.
  • A profundidade da URL acompanha a chave do controlador: eixo único para o post apenas, ou dois eixos (urlIndexTwoAxisKey) para carregar também o índice da imagem interna de um post com várias mídias. Escolha uma chave por app; os formatos não são decodificados um pelo outro.

As opções completas de createOverlayUrlState estão na referência da API para Angular.

Uma chave ou duas — escolha a profundidade da URL

O mesmo RkReelPlayerUrlOverlayComponent conduz os dois formatos; ele distingue em tempo de execução pela posição do controlador, então não existe input de modo. Escolha a chave ao montar o controlador:

ChaveFioO que carrega
urlIndexKey(…)?reel=3Apenas o post vertical.
urlIndexTwoAxisKey(…)?reel=3.2O post e o índice da mídia interna do carrossel.

Os dois fios são deliberadamente distintos — uma chave de dois eixos é estritamente pontuada (3.0, nunca um 3 solto), então um link de um eixo não é decodificado pelo outro. Trocar a chave de um app, portanto, invalida qualquer link já compartilhado. Escolha um formato e fique com ele.

typescript

Links estáveis. O índice é posicional, então um ?reel=3 favoritado abre outro post assim que o feed muda de ordem — e num feed isso é o normal. urlStableIdKey usa o id estável de cada post, varrendo o feed atual — uma chamada resolve o caso comum.

typescript

Passe hashCodec: base64UrlCodec para escrever o id em base64url na URL — é um disfarce reversível, não um hash criptográfico.

Quer endereçar por outro campo (um slug), ou paginar um feed infinito com locateAsync? Monte o codec e o locator você mesmo. São dois papéis distintos: o codec escreve a identidade na URL, o locator descobre onde ela está.

typescript

Feeds infinitos. locate é síncrono, então só responde pelos posts já carregados — um link compartilhado para o post 400 de um feed que carregou 20 volta vazio. locateAsync é o plano B, chamado somente quando locate falha: carregue as páginas necessárias e devolva o índice que a identidade acabou tendo.

Atalho

Vai endereçar pelo id do item? Dispense o codec e o locator escritos à mão — passe locateAsync direto para urlStableIdKey({ items, locateAsync }) (ele busca quando falha e então devolve o índice). A versão mais completa abaixo serve para endereçar por outro campo, ou para ter controle total.

typescript
  • Enquanto locateAsync está pendente, o player fica fechado e o parâmetro é deixado em paz, então o link direto sobrevive à busca. Um null ou uma rejeição derrubam o parâmetro.
  • Uma resposta que chega depois de a URL ter mudado, depois de um fechamento ou depois da desmontagem é descartada — uma busca lenta não pode abrir um slide que ninguém pediu.
  • Nada é renderizado enquanto a busca corre; esse estado de carregamento já pertence à página, então desenhe o seu próprio esqueleto.
  • Não existe tempo limite — o player não tem como saber o tamanho do feed. Termine com null quando a paginação se esgotar, ou o overlay fica fechado para sempre.

Tipos de dados próprios

Estenda BaseContentItem para usar o seu próprio modelo de domínio. O componente é genérico: RkReelPlayerOverlayComponent<T extends BaseContentItem>.

typescript

Inputs do RkReelPlayerOverlayComponent

InputTipoPadrãoDescrição
ariaLabelstring'Video player'Rótulo acessível da região do diálogo
aspectRationumber | undefinedundefinedProporção entre largura e altura do contêiner no desktop. O padrão é 9/16. No celular o player ocupa a tela inteira.
contentT[] (extends BaseContentItem)obrigatórioArray de itens de conteúdo a exibir no player
enableNavKeysbooleantrueLiga a navegação pelas setas do teclado
enableWheelbooleantrueLiga a navegação pela roda do mouse
initialIndexnumber0Índice, começando em zero, do item visível no início
initialInnerIndexnumber0Índice da mídia interna em que abrir, apenas para o post visível no início — permite que uma URL de dois eixos aponte direto para uma imagem específica de um post com várias mídias. Ignorado assim que o leitor navega.
isOpenbooleanobrigatórioControla a visibilidade do overlay; com false, o overlay sai do DOM
loopbooleanfalseLiga o ciclo infinito entre os slides
swipeDistanceFactornumber0.12Fração mínima da distância de deslize para trocar de slide
timeline'auto' | 'always' | 'never''auto'Estratégia que libera a barra de reprodução embutida. 'auto' só renderiza para vídeos mais longos que timelineMinDurationSeconds; 'always' renderiza sempre que o slide ativo tiver vídeo; 'never' desliga a barra embutida (use o slot de template rkPlayerTimeline para substituí-la inteiramente).
timelineMinDurationSecondsnumber30Duração mínima do vídeo, em segundos, para que timeline='auto' renderize a barra embutida. Clipes curtos em repetição abaixo desse limite ficam sem barra.
transitionDurationnumber300Duração da animação do slide em ms
wheelDebounceMsnumber200Duração do debounce dos eventos de roda em ms

Outputs do RkReelPlayerOverlayComponent

OutputTipoDescrição
apiReadyEventEmitter<ReelApi>Emitido assim que o slider fica pronto, expondo a API imperativa
closedEventEmitter<void>Emitido quando o player fecha
slideChangeEventEmitter<number>Emitido quando o índice do slide ativo muda
innerSlideChangeEventEmitter<{ outer: number; inner: number }>Emitido quando o índice da mídia interna do post ativo muda — na navegação interna e na ativação externa, informando o índice interno atual do post ativado (0 para um post de mídia única).

Inputs do RkReelPlayerUrlOverlayComponent

Aceita todos os inputs acima, menos isOpen e initialIndex, substituídos por um controller cuja posição escolhe o slide. Emite closed e slideChange.

InputTipoPadrãoDescrição
controllerUrlStateControllerobrigatórioControlador vindo de createOverlayUrlState. A position dele decide se o player está aberto e qual slide aparece; o overlay escreve de volta por ele na troca de slide e no fechamento.

Interface MediaItem

CampoTipoDescrição
idstringIdentificador único do item de mídia
type'image' | 'video'Tipo da mídia
srcstringURL do arquivo de mídia
posterstring?URL da miniatura de pôster, para itens de vídeo
aspectRationumberProporção entre largura e altura. Valores < 1 indicam vertical (cover), > 1 horizontal (contain)

Tipos de contexto dos slots de template

TipoCampos
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) }

Classes CSS

Todas as classes CSS são simples (sem escopo), então dá para alcançá-las com seletores de especificidade maior em uma folha de estilo carregada depois de @reelkit/angular-reel-player/styles.css. Para mudar cor, tamanho e z-index, prefira as propriedades CSS documentadas na seção Temas, abaixo — elas existem exatamente para isso.

ClasseComponenteDescrição
.rk-reel-overlayOverlayFundo fixo em tela cheia (cor de fundo, z-index)
.rk-reel-containerOverlayContêiner do player (posição, transbordo)
.rk-reel-loaderOverlayCamada da animação de onda do carregamento
.rk-reel-media-errorOverlayCamada do estado de erro (ícone e texto centralizados)
.rk-reel-media-error-textOverlayTexto da mensagem de erro
.rk-reel-buttonControlsBotão circular de ícone compartilhado (fechar, som, setas)
.rk-reel-close-btnControlsBotão de fechar
.rk-reel-sound-btnControlsBotão de alternar o som
.rk-reel-nav-arrowsNavigationContêiner das setas, só no desktop (escondido abaixo de 768px)
.rk-reel-nav-btnNavigationCada seta de avançar e voltar
.rk-reel-slide-wrapperSlideInvólucro em volta da mídia e da camada sobreposta
.rk-reel-slide-overlaySlideOverlayContêiner da camada em gradiente
.rk-reel-slide-overlay-authorSlideOverlayLinha do autor (avatar e nome)
.rk-reel-slide-overlay-avatarSlideOverlayImagem do avatar do autor
.rk-reel-slide-overlay-nameSlideOverlayTexto do nome do autor
.rk-reel-slide-overlay-descriptionSlideOverlayTexto da descrição
.rk-reel-slide-overlay-likesSlideOverlayLinha das curtidas (coração e contagem)
.rk-reel-video-containerVideoSlideInvólucro do vídeo (cor de fundo, transbordo)
.rk-reel-video-elementVideoSlideO elemento <video>
.rk-reel-video-posterVideoSlideImagem de pôster (some ao dar play)
.rk-reel-video-loaderVideoSlideAnimação de onda do carregamento
.rk-reel-video-poster.rk-visibleVideoSlideModificador de estado aplicado ao pôster enquanto o vídeo está pausado ou carregando
.rk-reel-nested-indicatorNestedSliderPontinhos de paginação sob os slides com várias mídias (a posição muda entre desktop e toque)
.rk-reel-nested-navNestedSliderSetas do carrossel horizontal (escondidas abaixo de 768px)
.rk-reel-nested-nav-nextNestedSliderPosição da seta de avançar aninhada
.rk-reel-nested-nav-prevNestedSliderPosição da seta de voltar aninhada
.rk-reel-nested-slider-innerNestedSliderRaiz do slider horizontal aninhado
.rk-reel-timelineTimelineBarInvólucro da barra de arrasto. Reaproveite nas raízes do seu template `rkPlayerTimeline` para herdar o posicionamento rente à base, o respiro da área segura e o espaço reservado para a camada do slide em dispositivos de toque.
.rk-reel-timeline-trackTimelineBarTrilha (trecho ainda não reproduzido)
.rk-reel-timeline-bufferedTimelineBarCamada dos trechos em buffer
.rk-reel-timeline-fillTimelineBarPreenchimento do que já foi reproduzido
.rk-reel-timeline-cursorTimelineBarPílula da alça de arrasto (flutua acima da trilha)

Temas

Cada cor, tamanho, z-index e transição vive em uma propriedade CSS. Sobrescreva uma ou várias em :root (ou em qualquer ancestral do overlay) para trocar o tema sem mexer no código dos componentes. Os tokens são os mesmos dos pacotes de React e de Vue, então as sobrescritas valem para todos os bindings.

TokenPadrãoO que controla
--rk-reel-overlay-bg#000Cor do fundo em tela cheia
--rk-reel-overlay-z1000z-index do overlay
--rk-reel-button-bgrgba(0, 0, 0, 0.5)Fundo padrão dos botões circulares
--rk-reel-button-bg-hoverrgba(255, 255, 255, 0.1)Fundo das setas de navegação (e estado básico ao passar o mouse)
--rk-reel-button-bg-hover-strongrgba(255, 255, 255, 0.2)Fundo das setas ao passar o mouse
--rk-reel-button-fg#fffCor do ícone do botão
--rk-reel-button-size44pxLargura e altura do botão
--rk-reel-button-radius50%Arredondamento do botão
--rk-reel-ui-z10z-index de fechar, som e navegação
--rk-reel-edge-padding16pxRecuo da borda para fechar, som e setas
--rk-reel-nav-gap8pxEspaço entre as setas empilhadas
--rk-reel-transition0.2sDuração da transição ao passar o mouse
--rk-reel-loader-colorrgba(255, 255, 255, 0.12)Cor do gradiente da animação de onda
--rk-reel-loader-duration1.8sDuração da animação de onda
--rk-reel-error-fgrgba(255, 255, 255, 0.4)Cor do ícone e do texto de erro
--rk-reel-slide-overlay-bglinear-gradient(transparent, rgba(0, 0, 0, 0.7))Gradiente que escurece o fundo da legenda
--rk-reel-slide-overlay-padding48px 16px 16pxRespiro interno da legenda
--rk-reel-slide-overlay-name-color#fffCor do nome do autor
--rk-reel-video-bg#000Cor das faixas em volta do <video>
--rk-reel-video-loader-colorrgba(255, 255, 255, 0.15)Cor do brilho que passa enquanto o vídeo carrega
--rk-reel-nested-button-bgrgba(0, 0, 0, 0.5)Fundo das setas aninhadas
--rk-reel-nested-button-size36pxTamanho das setas aninhadas
--rk-reel-nested-edge-padding12pxRecuo das setas aninhadas em relação à borda
--rk-reel-timeline-trackrgba(255, 255, 255, 0.22)Fundo da trilha (trecho ainda não reproduzido)
--rk-reel-timeline-bufferedrgba(255, 255, 255, 0.4)Cor dos trechos em buffer
--rk-reel-timeline-fill#fffCor do preenchimento já reproduzido
--rk-reel-timeline-cursor#fffCor da pílula da alça de arrasto
--rk-reel-timeline-height3pxAltura da trilha em repouso
--rk-reel-timeline-height-active6pxAltura da trilha ao passar o mouse, no foco ou durante o arrasto
--rk-reel-timeline-cursor-width10pxLargura da pílula em repouso
--rk-reel-timeline-cursor-width-active14pxLargura da pílula durante o arrasto
--rk-reel-timeline-cursor-height24pxAltura da pílula em repouso
--rk-reel-timeline-cursor-height-active32pxAltura da pílula durante o arrasto
--rk-reel-timeline-transition0.15s ease-outAnimação de crescer e encolher da trilha e da pílula

Cole o trecho abaixo em uma folha de estilo carregada depois de @reelkit/angular-reel-player/styles.css.

css

Acessibilidade

A raiz do overlay é um diálogo modal (role="dialog", aria-modal="true"). Defina o input ariaLabel para mudar o que o leitor de tela anuncia; o padrão é "Video player". Cada slide carrega role="group", aria-roledescription="slide" e aria-label="Slide N of M".

O overlay captura o foco ao abrir e o devolve ao gatilho ao fechar. Tab e Shift+Tab percorrem os elementos focáveis lá dentro; um foco que escapa (clique fora, foco por código) é trazido de volta. Implementado com captureFocusForReturn e createFocusTrap, de @reelkit/core.

Atalhos de teclado

TeclaAção
ArrowUpSlide anterior
ArrowDownPróximo slide
ArrowLeftMídia anterior (no slider aninhado)
ArrowRightPróxima mídia (no slider aninhado)
EscapeFecha o player