Reel Player

Um player de vídeo em tela cheia no estilo Instagram Reels/TikTok, com @reelkit/react-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
Render props para tudo
Camada sobre o slide
Autor, curtidas, descrição
Estado na URL
Links para compartilhar e favoritar

Instalação

bash

Não esqueça de importar os estilos:

typescript
Ícones

Os controles padrão usam lucide-react para os ícones. Se você prefere outra biblioteca de ícones, use renderControls e renderNavigation para fornecer os seus.

Início rápido

O componente ReelPlayerOverlay renderiza um player em tela cheia. Passe um array de objetos ContentItem e controle a visibilidade com isOpen.

tsx

Demonstração ao vivo

ReelPlayerPage.tsx

Clique em uma miniatura para abrir o player em tela cheia. Pressione Escape ou o botão de fechar para voltar.

Personalização

Tipo de conteúdo genérico

Use tipos de dados próprios estendendo BaseContentItem:

tsx

Camada própria sobre o slide

Troque a camada padrão por um conteúdo seu em cada slide:

tsx

Slides que não são mídia

Use renderSlide para inserir conteúdo próprio (cartões de chamada, por exemplo). Devolva null para cair no padrão:

tsx

Controles próprios

Componha os subcomponentes reutilizáveis com os seus acréscimos:

tsx

Linha do tempo própria

Troque a barra de reprodução padrão pela sua, com renderTimeline. O callback só é chamado quando as regras do overlay renderizariam a barra padrão (a mesma lógica de modo timeline e de 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 para a camada do slide em dispositivos de toque.

tsx

Navegação própria

tsx

Navegação aninhada própria

Troque as setas de esquerda e direita dentro dos slides com várias mídias (carrossel horizontal) por uma navegação sua:

tsx

Slides aninhados próprios

Personalize cada slide dentro dos carrosséis de várias mídias com renderNestedSlide. Use props.defaultContent para envolver o ImageSlide/VideoSlide padrão, ou substitua-o por completo:

tsx

Estado na URL

Ver demonstração ao vivo →

ReelPlayerUrlOverlay é um componente à parte cujo estado aberto vive na barra de endereços. Monte um controlador com useOverlayUrlState, de @reelkit/react, e entregue-o como controller: o player se abre quando o parâmetro nomeia um slide e se fecha quando ele some. Os links dão para compartilhar, e o botão voltar fecha o player em vez de sair da página.

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/react. Veja o guia de estado na URL e a API do core.

tsx

As opções completas de useOverlayUrlStateparam, adapter, codec, locator — estão na referência da API para React. O passo a passo está no guia para React.

  • 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.
  • Por padrão o parâmetro endereça apenas o post vertical (?reel=3). Adote uma chave de dois eixos para carregar também o índice da mídia interna de um carrossel — veja abaixo.

Uma chave ou duas — escolha a profundidade da URL

O mesmo ReelPlayerUrlOverlay conduz os dois formatos; ele distingue em tempo de execução pela posição do controlador, então não existe prop 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.

tsx

App com rotas — passe um adaptador. Escrever history.pushState por trás de um roteador deixa a localização dele desatualizada, e a navegação seguinte derruba o parâmetro:

tsx

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, não a exceção. urlStableIdKey usa o id estável de cada post, varrendo o feed atual — uma chamada resolve o caso comum.

tsx

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á.

tsx

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.

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.

tsx
  • 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.

Referência da API

Props do ReelPlayerOverlayProps

ReelPlayerOverlayProps<T>

PropTipoPadrãoDescrição
apiRefMutableRefObject<ReelApi>-Ref para alcançar a API do Reel
ariaLabelstring'Video player'Rótulo acessível da região do diálogo; anunciado pelos leitores de tela quando o overlay abre
aspectRationumber9/16 (0.5625)Proporção entre largura e altura do contêiner do player no desktop. No celular o player sempre ocupa a tela inteira.
contentT[]obrigatórioArray de itens de conteúdo (genérico, ContentItem por padrão)
initialIndexnumber0Índice do slide inicial
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 player abre e o leitor navega.
isOpenbooleanobrigatórioControla a visibilidade do overlay. Para um estado aberto guiado pela URL, use o componente à parte ReelPlayerUrlOverlay — veja Estado na URL, abaixo.
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 renderTimeline 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.
renderControls(props: ControlsRenderProps) => ReactNode-Controles próprios, substituem os botões padrão de fechar e som
renderError(props: { item: T; activeIndex: number }) => ReactNode-Indicador de erro próprio, substitui o ícone padrão de erro
renderLoading(props: { item: T; activeIndex: number }) => ReactNode-Indicador de carregamento próprio, substitui a animação padrão de onda
renderNavigation(props: NavigationRenderProps) => ReactNode-Navegação própria, substitui as setas verticais padrão
renderNestedNavigation(props: NavigationRenderProps) => ReactNode-Navegação própria do slider horizontal aninhado (posts com várias mídias), substitui as setas padrão de esquerda e direita
renderNestedSlide(props: NestedSlideRenderProps) => ReactNode-Renderizador próprio dos itens do slider horizontal aninhado. Use props.defaultContent para envolver ou embutir o ImageSlide/VideoSlide padrão. Diferente de renderSlide, aqui null não significa cair no padrão.
renderSlide(props: SlideRenderProps) => ReactNode | null-Renderização própria do slide. Devolva null para cair no padrão. Use props.defaultContent para envolver ou embutir o slide padrão.
renderSlideOverlay(item, index, isActive) => ReactNode-Camada própria em cada slide, substitui o SlideOverlay padrão. Devolva null para esconder.
renderTimeline(props: TimelineRenderProps) => ReactNode-Barra de reprodução própria. Chamada apenas quando as regras liberariam a barra padrão (a mesma lógica de auto/always/never e de timelineMinDurationSeconds). Use props.defaultContent para envolver a <TimelineBar /> embutida; devolva null para esconder.

Props do ReelPlayerUrlOverlay

ReelPlayerUrlOverlayProps<T>

Aceita todas as props visuais e de comportamento acima, menos isOpen, substituída por controller. initialIndex é ignorada — quem escolhe o slide é a posição do controlador, então um valor passado ao lado dela é sobrescrito a cada abertura.

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

Callbacks

PropTipoDescrição
onClose() => voidChamado quando o player fecha. Obrigatório no ReelPlayerOverlay (o estado aberto é seu, então o fechamento também); opcional no ReelPlayerUrlOverlay, onde quem fecha é a URL — passe apenas se quiser reagir depois do fechamento.
onSlideChange(index: number) => voidChamado depois da troca de slide
onInnerSlideChange(outerIndex: number, innerIndex: number) => voidChamado quando o índice da mídia interna do post ativo muda — na navegação interna de um post com várias mídias e na ativação externa, informando o índice interno atual do post ativado (0 para um post de mídia única).

Props do Reel (repassadas)

Estas props são repassadas ao componente Reel por baixo.

PropTipoPadrãoDescrição
enableNavKeysbooleantrueLiga a navegação por teclado
enableWheelbooleantrueLiga a navegação pela roda do mouse
loopbooleanfalseLiga o ciclo infinito
swipeDistanceFactornumber0.12Limiar do deslize (0-1)
transitionDurationnumber300Duração da animação de transição (ms)
wheelDebounceMsnumber200Debounce da roda do mouse (ms)

Tipos

BaseContentItem

O tipo que serve de restrição genérica. Estenda-o para usar tipos de dados próprios com o ReelPlayerOverlay.

typescript

ContentItem

typescript

MediaItem

typescript

MediaType

typescript

ControlsRenderProps<T>

typescript
typescript

SlideRenderProps<T>

typescript

NestedSlideRenderProps

typescript

SlideOverlayProps

typescript

ImageSlideProps

typescript

VideoSlideProps

typescript

CloseButtonProps

typescript

SoundButtonProps

typescript

TimelineBarProps

typescript

TimelineRenderProps<T>

typescript

Subcomponentes

Blocos reutilizáveis exportados para você compor dentro das suas render props:

CloseButton

Botão de fechar isolado, já com o estilo do reel player. Use dentro de renderControls.

tsx

SoundButton

Botão de som isolado. Precisa estar dentro de um SoundProvider (que o ReelPlayerOverlay fornece sozinho).

tsx

TimelineBar

Barra de reprodução padrão. Lê o TimelineProvider mais próximo (montado automaticamente dentro do ReelPlayerOverlay) e desenha a trilha, os trechos em buffer, o preenchimento do progresso e a pílula de arrasto. Personalize pelas propriedades --rk-reel-timeline-*, ou substitua por renderTimeline.

tsx

SlideOverlay

A camada em gradiente padrão, que mostra autor, descrição e curtidas. Aparece sozinha quando o conteúdo tem os campos necessários. Use renderSlideOverlay para substituí-la ou escondê-la.

tsx

ImageSlide

Slide de imagem com carregamento tardio e object-fit: cover por padrão. Use dentro de renderSlide para compor slides de imagem com os seus estilos.

tsx

VideoSlide

Slide de vídeo com elemento <video> compartilhado para a continuidade do som no iOS, quadros de pôster, memória de posição e indicador de carregamento. Precisa estar dentro de um SoundProvider (que o ReelPlayerOverlay fornece sozinho).

tsx
Compondo slides próprios

Use renderSlide com ImageSlide / VideoSlide para personalizar a exibição da mídia mantendo todo o comportamento embutido (reprodução automática, captura de pôster, sincronia do som).

tsx

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 renderSlide, chame estes callbacks 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.
tsx

Interface própria de carregamento e erro

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

tsx

Linha do tempo

O overlay desenha uma barra de reprodução embutida sobre o vídeo ativo. A prop timeline libera ou não essa exibição:

  • 'auto' (padrão): aparece quando a mídia ativa é um vídeo mais longo que timelineMinDurationSeconds (30 por padrão). Vale para slides de vídeo único e para carrosséis de várias mídias; a barra segue o item aninhado ativo e some nas imagens.
  • 'always': aparece sempre que o slide ativo tiver vídeo.
  • 'never': nunca aparece. Monte uma barra sua com renderTimeline.
tsx

Personalize pelas propriedades CSS --rk-reel-timeline-* (altura, cores, tamanho do cursor). Para uma barra, um cronômetro ou um indicador de progresso totalmente próprios, use renderTimeline; o callback recebe um timelineState apoiado no TimelineController por baixo.

Contexto de som

Em implementações próprias, você pode alcançar o estado do som:

tsx

Classes CSS

Todas as classes CSS são simples (não são CSS modules), então dá para alcançá-las com seletores de especificidade maior em uma folha de estilo carregada depois de @reelkit/react-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-buttonNavigationCada 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-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-timelineTimelineBarInvólucro da barra de arrasto. Reaproveite nas raízes de um `renderTimeline` próprio 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.

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-error-text-size13pxTamanho da fonte da mensagem 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-slide-overlay-description-colorrgba(255, 255, 255, 0.9)Cor do texto da descrição
--rk-reel-slide-overlay-likes-colorrgba(255, 255, 255, 0.8)Cor do texto da linha de curtidas
--rk-reel-video-bg#000Cor das faixas em volta do <video>
--rk-reel-nested-button-bgrgba(0, 0, 0, 0.5)Fundo das setas aninhadas
--rk-reel-nested-button-bg-hoverrgba(255, 255, 255, 0.2)Fundo das setas aninhadas ao passar o mouse
--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-hitbox16pxÁrea extra de toque acima da trilha
--rk-reel-timeline-transition0.15s ease-outAnimação de crescer e encolher da trilha e da pílula
--rk-reel-timeline-z11z-index da linha do tempo (acima da camada padrão da interface)

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

css

Acessibilidade

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

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