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.
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,controlsyposterno necesita ninguna librería. - En Vue 3 con
<script setup>, obtén el elemento con unref()de plantilla y llama a la API nativa de medios sobrevideoRef.valuedentro deonMounted, nunca mediantedocument.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()enonBeforeUnmount(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
mutedcomoautoplay, y añadirplaysinlinepara 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?
| Enfoque | Dependencias extra | Esfuerzo de UI personalizada | HLS / subtítulos | Elige esto cuando |
|---|---|---|---|---|
<video> nativo | Ninguna | Construyes cada control | Solo HLS nativo (Safari) | Clip simple, bundle mínimo |
| Componente wrapper personalizado | Ninguna | Control total mediante slots | Solo HLS nativo | UI a medida, reutilizada en toda la app |
Video.js / @videojs-player/vue | video.js (+ wrapper) | Aplicar skin o sobrescribir | Sí, integrado | Amplitud de formatos, streaming, pistas |
Discover how at OpenReplay.com.
<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 deaddEventListenerenonUnmounted, 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
mutedcomoautoplay, según la política de autoplay de Chrome. playsinlinepara iOS. Sin el atributoplaysinline, 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 condocument.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.
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