Как добавить воспроизведение видео в приложения на Vue
Добавьте воспроизведение видео в Vue-приложения с нативным HTML5 video, переиспользуемыми компонентами или Video.js для HLS, субтитров, autoplay и очистки.
Есть три способа добавить воспроизведение видео в приложение на Vue, и выбор зависит от того, насколько тонкий контроль вам нужен: используйте нативный HTML5-элемент <video> для простого встроенного ролика, обратитесь к Video.js, когда нужны адаптивный стриминг, субтитры или широкая поддержка форматов, и подключите готовый компонент вроде @videojs-player/vue, если хотите получить возможности Video.js, не занимаясь настройкой плеера вручную.
Обычно ролик прекрасно воспроизводится на той машине, где вы его собирали. А потом тестировщик открывает страницу на iPhone — и видео разворачивается на весь экран, или автовоспроизведение тихо не срабатывает в Chrome, и никто не может понять почему. Поэтому в этом руководстве разбираются все три подхода с готовым к копированию кодом для Vue 3 на <script setup>, а также рассматриваются сбои, которые проявляются только на чужих устройствах.
Ключевые выводы
- Для простого встроенного ролика со стандартными элементами управления достаточно нативного HTML5-элемента
<video>с привязками:src,controlsиposter— никакая библиотека не нужна. - Во Vue 3 с
<script setup>получайте элемент через templateref()и вызывайте нативный media API наvideoRef.valueвнутриonMounted, а не черезdocument.querySelector. - Video.js отображается без стилей, если не импортировать
video.js/dist/video-js.css— это самая частая причина «сломанного» вида плеера Video.js во Vue. - Каждый раз, когда вы инициализируете Video.js, вызывайте
player.dispose()вonBeforeUnmount(Vue 3). Пропустив это, вы будете утекать плеер и его DOM при каждом размонтировании. - Браузеры блокируют автовоспроизведение со звуком: чтобы автозапуск работал надёжно, нужно задать одновременно
mutedиautoplay, а также добавитьplaysinline, чтобы iOS Safari воспроизводил видео встроенно, а не переключался в полноэкранный режим.
Как выбрать подход к видеоплееру во Vue?
Выбирайте нативный <video>, если вам не нужны кастомный UI или адаптивный стриминг; выбирайте собственную обёртку, если хотите индивидуальные элементы управления с полным контролем над кодом; выбирайте Video.js или его компонент-обёртку, если нужны HLS, субтитры или множество форматов. Решение определяют три критерия: нужен ли кастомный UI, нужен ли адаптивный стриминг (HLS/DASH) и сколько зависимостей вы готовы терпеть.
| Подход | Доп. зависимости | Трудозатраты на кастомный UI | HLS / субтитры | Когда выбирать |
|---|---|---|---|---|
Нативный <video> | Нет | Все элементы управления пишете сами | Только нативный HLS (Safari) | Простой ролик, минимальный бандл |
| Собственный компонент-обёртка | Нет | Полный контроль через слоты | Только нативный HLS | Индивидуальный UI, переиспользуемый по всему приложению |
Video.js / @videojs-player/vue | video.js (+ обёртка) | Скин или переопределение стилей | Да, из коробки | Широта форматов, стриминг, текстовые дорожки |
Discover how at OpenReplay.com.
Нативный HTML5 <video> во Vue (начните отсюда)
Самый быстрый способ добавить воспроизведение видео во Vue — нативный элемент <video> со свойствами, привязанными через :src, poster и preload, плюс template ref() для вызова HTMLMediaElement API: play(), pause() и .muted. Во Vue 3 с <script setup> привязывайте нативные media-события вроде timeupdate, loadedmetadata и ended прямо в шаблоне — тогда Vue сам добавит и удалит обработчики.
<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>
Template-ссылка ref="videoRef" разрешается в DOM-элемент, доступный через videoRef.value. Обращайтесь к нему внутри обработчиков или в onMounted, но не до монтирования компонента. В Options API эквивалентами будут this.$refs.videoRef и хук mounted. Если вы навешиваете обработчики вручную через addEventListener, удаляйте их в onUnmounted; привязка через @event в шаблоне полностью избавляет от утечек такого рода.
Переиспользуемый компонент собственного плеера
Чтобы переиспользовать логику плеера во всём приложении, оберните нативный <video> в компонент, который отдаёт свои элементы управления и состояние через scoped slot — тогда в каждом месте использования можно собрать собственные кнопки и полосу прогресса, не дублируя логику воспроизведения.
<!-- 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>
Потребители достают playing и togglePlay из props слота и рендерят любой нужный им UI. Один базовый плеер может обслуживать и минималистичную вставку с одной кнопкой воспроизведения, и полноценный плеер с полосой прогресса, сохраняя всю медиалогику в одном месте.
<VideoPlayer src="/media/clip.mp4" @ended="onEnded">
<template #controls="{ playing, togglePlay }">
<button @click="togglePlay">{{ playing ? 'Pause' : 'Play' }}</button>
</template>
</VideoPlayer>
Когда стоит использовать Video.js?
Используйте Video.js, когда нужны HLS/адаптивный стриминг, субтитры и текстовые дорожки, единообразное оформление или поддержка форматов шире того, что гарантирует нативный <video>. Установите video.js, отрендерите <video class="video-js">, инициализируйте videojs(ref, options) в onMounted, а также импортируйте таблицу стилей и уничтожайте плеер при размонтировании.
<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>
Здесь легко ошибиться в двух местах: имя template-ссылки ref должно совпадать с тем, которое вы читаете в onMounted (несоответствие пары videoJsPlayer/videoPlayer приведёт к исключению), а dispose() во Vue 3 должен вызываться в onBeforeUnmount. beforeDestroy — это хук Vue 2, а срок поддержки Vue 2 истёк 31 декабря 2023 года. Текущая стабильная линейка — Video.js 8.x, при этом версия 8.24.0 вышла в августе 2026 года. Модульная Video.js 10 находится в бете и пока не доступна в общем релизе: таймлайн репозитория v10 всё ещё указывает общую доступность как работу в процессе, а паритет ядра Video.js и contrib-плагинов запланирован на конец 2026 года. Для продакшена оставайтесь на 8.x и не фиксируйте версию при установке: npm install video.js.
Готовый компонент: @videojs-player/vue
Путь с минимумом кода к Video.js во Vue 3 — @videojs-player/vue, готовый к подключению <video-player> с реактивными props (src, sources, poster, controls, loop, volume, fluid, playsinline, tracks) и scoped-слотом { player, state } для кастомных элементов управления. Инициализацию и очистку он берёт на себя, поэтому переход на HLS-ролик сводится к замене одной строки с источником.
<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>
Одна оговорка, о которой стоит знать до внедрения: это стандартная обёртка для Vue 3, но её релизы в npm застыли на v1.0.0, опубликованной в 2022 году, и сканеры зависимостей помечают её как слабо поддерживаемую. Опубликованный диапазон peer-зависимостей до сих пор требует video.js 7.x, поэтому связка с Video.js 8 при установке выдаёт предупреждение о peer-зависимости. Если вы на Vue 2, используйте устаревшую сборку vue-video-player, ссылка на которую есть в legacy-разделе репозитория. Актуальные релизы @videojs-player/vue рассчитаны только на Vue 3.
Чек-лист специфичных для Vue подводных камней
Большинство багов вида «видео не воспроизводится» во Vue сводится к нескольким платформенным правилам, которые проходят код-ревью, но отказывают на реальных устройствах. Особенно это касается сбоев автовоспроизведения и playsinline: они невидимы, пока с ними не столкнётся пользователь, — а это ровно тот класс проблем, специфичных для устройств, который session replay выявляет по реальным сессиям.
- Очищайте ресурсы при размонтировании. Вызывайте
player.dispose()для Video.js и удаляйте все обработчики, добавленные вручную черезaddEventListener, вonUnmounted— иначе каждый цикл монтирования/размонтирования будет давать утечку. - Импортируйте CSS.
import 'video.js/dist/video-js.css', иначе плеер останется без стилей. - Автозапуск обязательно без звука. Браузеры блокируют автовоспроизведение со звуком; задавайте и
muted, иautoplay— в соответствии с политикой автовоспроизведения Chrome. playsinlineдля iOS. Без атрибутаplaysinlineiOS Safari на iPhone обычно открывает видео в полноэкранном режиме; на iPad поведение отличается.- Используйте template-ссылки, а не запросы к DOM. Обращайтесь к элементу через
ref(), никогда не черезdocument.querySelector.
Начинайте с нативного варианта: привязанный <video> даёт воспроизведение за считанные минуты и без единой зависимости. Переходите на Video.js или @videojs-player/vue в тот момент, когда понадобятся HLS, субтитры или единообразный кроссбраузерный интерфейс, и закладывайте очистку через dispose() с первого коммита, чтобы она никогда не превратилась в утечку, которую придётся выискивать позже.
Часто задаваемые вопросы
Как воспроизвести HLS-поток (.m3u8) в приложении на Vue?
Нативное HTML5-видео воспроизводит HLS только в Safari, поэтому для кроссбраузерного HLS используйте Video.js или его обёртку @videojs-player/vue, которая включает поддержку HLS через движок videojs-http-streaming. С @videojs-player/vue вы указываете в src URL вашего .m3u8-плейлиста, а компонент внутри себя работает со стриминговым движком. В обычных Chrome или Firefox у нативного video нет встроенной поддержки HLS — это и есть главная причина обратиться к Video.js.
Почему не работает автовоспроизведение в моём видеоплеере на Vue?
Автозапуск без звука, как правило, разрешён, но автовоспроизведение со звуком требует, чтобы пользователь предварительно взаимодействовал с сайтом; Chrome также допускает его, если у сайта высокий показатель Media Engagement Index. Safari применяет собственную аналогичную политику. Чтобы автозапуск работал надёжно, нужно задать и muted, и autoplay — либо на элементе, либо в опциях Video.js. Этот сбой не виден при код-ревью, потому что зависит от политики браузера и устройства, а не от вашей разметки, и проявляется только когда страницу открывает реальный пользователь.
Чем @videojs-player/vue отличается от прямого использования Video.js?
@videojs-player/vue — это готовый к подключению компонент Vue 3, оборачивающий Video.js: он берёт на себя инициализацию и очистку, предоставляя реактивные props и scoped-слот с player и state. При прямом использовании Video.js вы сами рендерите элемент video, вызываете videojs() в onMounted и уничтожаете плеер в onBeforeUnmount. Обёртка требует меньше кода, но её релизы в npm застыли на v1.0.0 от 2022 года, поэтому прямая работа с Video.js даёт больше контроля над версиями и жизненным циклом.
Можно ли использовать @videojs-player/vue с Vue 2?
Нет. Актуальные релизы @videojs-player/vue рассчитаны только на Vue 3. Пакет получил своё нынешнее имя, когда в него добавили поддержку React, и этот переход стал ломающим изменением для пользователей Vue. Для Vue 2 нужна более старая сборка vue-video-player 5.x, ссылка на которую по-прежнему есть в legacy-разделе репозитория. Сам Vue 2 достиг конца жизненного цикла 31 декабря 2023 года, поэтому новые проекты стоит делать на 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