12k
All articles

Cómo agregar reproducción de video a aplicaciones Vue

Añade reproducción de video a apps Vue con video HTML5 nativo, componentes reutilizables o Video.js para HLS, subtítulos, autoplay y limpieza.

OpenReplay Team
OpenReplay Team
Cómo agregar reproducción de video a aplicaciones Vue

Hay tres formas de agregar reproducción de video a una aplicación Vue, y la adecuada depende de cuánto control necesites: usa el elemento nativo HTML5 <video> para un clip incrustado simple, recurre a Video.js cuando necesites streaming adaptativo, subtítulos o soporte amplio de formatos, e incorpora un componente ya listo como @videojs-player/vue cuando quieras las funciones de Video.js sin cablear el reproductor tú mismo.

El clip suele reproducirse bien en la máquina donde lo desarrollaste. Luego un tester abre la página en un iPhone y salta a pantalla completa, o el autoplay simplemente no hace nada en Chrome y nadie sabe por qué. Por eso esta guía recorre los tres enfoques con código Vue 3 <script setup> listo para copiar y pegar, y cubre los fallos que solo aparecen en el dispositivo de otra persona.

Puntos clave

  • Para un clip incrustado simple con controles predeterminados, el elemento nativo HTML5 <video> enlazado con :src, controls y poster no necesita ninguna librería.
  • En Vue 3 con <script setup>, obtén el elemento con un ref() de plantilla y llama a la API nativa de medios sobre videoRef.value dentro de onMounted, nunca mediante document.querySelector.
  • Video.js se renderiza sin estilos a menos que importes video.js/dist/video-js.css, la razón más común por la que un reproductor Video.js en Vue se ve roto.
  • Siempre que inicialices Video.js, llama a player.dispose() en onBeforeUnmount (Vue 3). Omitirlo filtra el reproductor y su DOM en cada desmontaje.
  • Los navegadores bloquean el autoplay con sonido: para que el autoplay funcione de forma fiable debes establecer tanto muted como autoplay, y añadir playsinline para que iOS Safari reproduzca en línea en lugar de forzar la pantalla completa.

¿Cómo elegir un enfoque de reproductor de video en Vue?

Elige <video> nativo cuando no necesites UI personalizada ni streaming adaptativo; elige un wrapper personalizado cuando quieras controles a medida pero con plena propiedad del código; elige Video.js o su componente wrapper cuando necesites HLS, subtítulos o muchos formatos. Estos son los tres criterios que deciden: ¿necesitas UI personalizada?, ¿necesitas streaming adaptativo (HLS/DASH)? y ¿cuántas dependencias puedes tolerar?

EnfoqueDependencias extraEsfuerzo de UI personalizadaHLS / subtítulosElige esto cuando
<video> nativoNingunaConstruyes cada controlSolo HLS nativo (Safari)Clip simple, bundle mínimo
Componente wrapper personalizadoNingunaControl total mediante slotsSolo HLS nativoUI a medida, reutilizada en toda la app
Video.js / @videojs-player/vuevideo.js (+ wrapper)Aplicar skin o sobrescribirSí, integradoAmplitud de formatos, streaming, pistas

<video> nativo de HTML5 en Vue (empieza aquí)

La forma más rápida de agregar reproducción de video en Vue es un elemento nativo <video> con props enlazadas mediante :src, poster y preload, más un ref() de plantilla para llamar a la API HTMLMediaElement: play(), pause() y .muted. En Vue 3 <script setup>, enlaza eventos nativos de medios como timeupdate, loadedmetadata y ended directamente en la plantilla para que Vue agregue y elimine los listeners por ti.

<script setup>
import { ref } from 'vue'

const videoRef = ref(null)
const playing = ref(false)
const muted = ref(false)
const current = ref(0)
const duration = ref(0)

function togglePlay() {
  const el = videoRef.value
  el.paused ? el.play() : el.pause()
}

function toggleMute() {
  const el = videoRef.value
  el.muted = !el.muted
  muted.value = el.muted
}
</script>

<template>
  <video
    ref="videoRef"
    src="/media/clip.mp4"
    poster="/media/poster.jpg"
    preload="metadata"
    playsinline
    @play="playing = true"
    @pause="playing = false"
    @ended="playing = false"
    @loadedmetadata="duration = $event.target.duration"
    @timeupdate="current = $event.target.currentTime"
  />
  <div>
    <button @click="togglePlay">{{ playing ? 'Pause' : 'Play' }}</button>
    <button @click="toggleMute">{{ muted ? 'Unmute' : 'Mute' }}</button>
    <span>{{ current.toFixed(0) }}s / {{ duration.toFixed(0) }}s</span>
  </div>
</template>

El ref="videoRef" de la plantilla se resuelve al elemento del DOM en videoRef.value. Accede a él dentro de los manejadores o en onMounted, no antes de que el componente se monte. En la Options API los equivalentes son this.$refs.videoRef y el hook mounted. Si agregas listeners manualmente con addEventListener, elimínalos en onUnmounted; enlazar mediante @event en la plantilla evita por completo ese tipo de fuga.

Componente reproductor personalizado y reutilizable

Para reutilizar la lógica del reproductor en toda una aplicación, envuelve el <video> nativo en un componente que exponga sus controles y estado mediante un scoped slot, de modo que cada uso componga sus propios botones y barra de progreso sin duplicar el cableado de reproducción.

<!-- VideoPlayer.vue -->
<script setup>
import { ref } from 'vue'
defineProps({ src: String, poster: String })
const emit = defineEmits(['timeupdate', 'ended'])

const videoRef = ref(null)
const playing = ref(false)

function togglePlay() {
  const el = videoRef.value
  el.paused ? el.play() : el.pause()
}
</script>

<template>
  <video
    ref="videoRef"
    :src="src"
    :poster="poster"
    playsinline
    @play="playing = true"
    @pause="playing = false"
    @timeupdate="emit('timeupdate', $event.target.currentTime)"
    @ended="emit('ended')"
  />
  <slot name="controls" :playing="playing" :toggle-play="togglePlay" />
</template>

Los consumidores extraen playing y togglePlay de las props del slot y renderizan la UI que necesiten. Un único reproductor base puede respaldar tanto una incrustación mínima con solo reproducción como un reproductor completo con barra de progreso, manteniendo la lógica de medios en un solo lugar.

<VideoPlayer src="/media/clip.mp4" @ended="onEnded">
  <template #controls="{ playing, togglePlay }">
    <button @click="togglePlay">{{ playing ? 'Pause' : 'Play' }}</button>
  </template>
</VideoPlayer>

¿Cuándo deberías usar Video.js?

Usa Video.js cuando necesites HLS/streaming adaptativo, subtítulos y pistas de texto, un skin consistente, o amplitud de formatos más allá de lo que el <video> nativo garantiza. Instala video.js, renderiza un <video class="video-js">, inicializa videojs(ref, options) en onMounted, e importa la hoja de estilos y destruye el reproductor al desmontar.

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import videojs from 'video.js'
import 'video.js/dist/video-js.css' // required, or the player renders unstyled

const videoRef = ref(null)
let player = null

const options = {
  autoplay: false,
  controls: true,
  preload: 'auto',
  fluid: true,
  sources: [{ src: '/media/clip.mp4', type: 'video/mp4' }]
}

onMounted(() => {
  player = videojs(videoRef.value, options)
})

onBeforeUnmount(() => {
  if (player) player.dispose()
})
</script>

<template>
  <video ref="videoRef" class="video-js" playsinline />
</template>

Dos cosas son fáciles de equivocar aquí: el nombre del ref de la plantilla debe coincidir con el que lees en onMounted (un par videoJsPlayer/videoPlayer desalineado lanza un error), y dispose() corresponde a onBeforeUnmount en Vue 3. beforeDestroy es un hook de Vue 2, y Vue 2 llegó al fin de su vida útil el 31 de diciembre de 2023. La línea estable actual es Video.js 8.x, con la versión 8.24.0 publicada en agosto de 2026. Una versión modular, Video.js 10, está en beta y aún no está disponible de forma general: el cronograma del repositorio v10 todavía lista la disponibilidad general como trabajo en curso, con la paridad del core de Video.js y contrib prevista para finales de 2026. Mantente en 8.x para producción y deja la instalación sin fijar versión: npm install video.js.

Componente listo para usar: @videojs-player/vue

La ruta con menos código hacia Video.js en Vue 3 es @videojs-player/vue, un <video-player> listo para incorporar con props reactivas (src, sources, poster, controls, loop, volume, fluid, playsinline, tracks) y un scoped slot { player, state } para controles personalizados. Gestiona la inicialización y la destrucción internamente, así que un clip HLS es un cambio de fuente de una sola línea.

<script setup>
import { VideoPlayer } from '@videojs-player/vue'
import 'video.js/dist/video-js.css'
</script>

<template>
  <video-player
    src="https://example.com/stream/playlist.m3u8"
    poster="/media/poster.jpg"
    controls
    fluid
    playsinline
    :volume="0.6"
    @ready="(payload) => console.log(payload.state)"
  >
    <template #default="{ player, state }">
      <button @click="state.playing ? player.pause() : player.play()">
        {{ state.playing ? 'Pause' : 'Play' }}
      </button>
    </template>
  </video-player>
</template>

Una advertencia que conviene conocer antes de adoptarlo: este es el wrapper estándar para Vue 3, pero su publicación en npm se ha estancado en la v1.0.0, publicada en 2022, y los escáneres de dependencias lo marcan como de mantenimiento bajo. Su rango de peer dependencies publicado todavía pide video.js 7.x, por lo que combinarlo con Video.js 8 genera una advertencia de peer dependency al instalar. Si estás en Vue 2, usa la build heredada vue-video-player enlazada desde la sección legacy del repositorio. Las versiones actuales de @videojs-player/vue apuntan únicamente a Vue 3.

Lista de verificación de trampas específicas de Vue

La mayoría de los bugs de “el video no se reproduce” en Vue se remontan a un puñado de reglas de plataforma que pasan la revisión de código pero fallan en dispositivos reales. Los fallos de autoplay y playsinline en particular son invisibles hasta que un usuario los encuentra, que es exactamente el tipo de problema específico de dispositivo que el session replay expone a partir de sesiones reales.

  • Limpia al desmontar. Llama a player.dispose() para Video.js y elimina cualquier manejador manual de addEventListener en onUnmounted, o cada ciclo de montaje/desmontaje generará fugas.
  • Importa el CSS. import 'video.js/dist/video-js.css' o el reproductor quedará sin estilos.
  • El autoplay silenciado es obligatorio. Los navegadores bloquean el autoplay con sonido; establece tanto muted como autoplay, según la política de autoplay de Chrome.
  • playsinline para iOS. Sin el atributo playsinline, iOS Safari en iPhone normalmente abrirá el video en pantalla completa; el iPad se comporta de forma distinta.
  • Usa template refs, no consultas al DOM. Accede al elemento mediante ref(), nunca con document.querySelector.

Empieza con lo nativo: un <video> enlazado te da reproducción en minutos con cero dependencias. Pasa a Video.js o @videojs-player/vue en cuanto necesites HLS, subtítulos o una UI consistente entre navegadores, y cablea la limpieza con dispose() desde el primer commit para que nunca se convierta en una fuga que tengas que rastrear más adelante.

Preguntas frecuentes

¿Cómo reproduzco un stream HLS (.m3u8) en una aplicación Vue?

El video HTML5 nativo reproduce HLS solo en Safari, así que para HLS multinavegador usa Video.js o su wrapper @videojs-player/vue, que incluye soporte HLS mediante el motor videojs-http-streaming. Con @videojs-player/vue estableces el src a la URL de tu playlist .m3u8 y el componente gestiona el motor de streaming internamente. En Chrome o Firefox nativos, el elemento video no tiene HLS integrado, que es la razón principal para recurrir a Video.js.

¿Por qué no funciona el autoplay en mi reproductor de video en Vue?

El autoplay silenciado generalmente está permitido, pero el autoplay con sonido requiere que el usuario haya interactuado antes con el sitio, y Chrome también lo permite cuando el sitio tiene una puntuación alta en el Media Engagement Index. Safari aplica su propia política similar. Para que el autoplay funcione de forma fiable debes establecer tanto muted como autoplay en el elemento o en las opciones de Video.js. Este fallo es invisible en la revisión de código porque depende de la política del navegador y del dispositivo, no de tu marcado, así que solo aparece cuando un usuario real carga la página.

¿Cuál es la diferencia entre @videojs-player/vue y usar Video.js directamente?

@videojs-player/vue es un componente listo para incorporar en Vue 3 que envuelve Video.js, gestionando la inicialización y la destrucción internamente y exponiendo props reactivas más un scoped slot con player y state, mientras que usar Video.js directamente significa que renderizas un elemento video, llamas a videojs() en onMounted y lo destruyes tú mismo en onBeforeUnmount. El wrapper requiere menos código, pero su publicación en npm se ha estancado en la v1.0.0 de 2022, así que usar Video.js directamente te da más control sobre versiones y ciclo de vida.

¿Puedo seguir usando @videojs-player/vue con Vue 2?

No. Las versiones actuales de @videojs-player/vue apuntan únicamente a Vue 3. El paquete adoptó su nombre actual cuando se añadió soporte para React, y ese cambio fue una ruptura de compatibilidad para los usuarios de Vue. Para Vue 2 necesitas la build más antigua vue-video-player 5.x, que el repositorio todavía enlaza desde su sección legacy. Vue 2 en sí llegó al fin de su vida útil el 31 de diciembre de 2023, así que los proyectos nuevos deberían apuntar a Vue 3.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.