12k
All articles

Как добавить воспроизведение видео в приложения на Vue

Добавьте воспроизведение видео в Vue-приложения с нативным HTML5 video, переиспользуемыми компонентами или Video.js для HLS, субтитров, autoplay и очистки.

OpenReplay Team
OpenReplay Team
Как добавить воспроизведение видео в приложения на Vue

Есть три способа добавить воспроизведение видео в приложение на 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> получайте элемент через template ref() и вызывайте нативный 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) и сколько зависимостей вы готовы терпеть.

ПодходДоп. зависимостиТрудозатраты на кастомный UIHLS / субтитрыКогда выбирать
Нативный <video>НетВсе элементы управления пишете самиТолько нативный HLS (Safari)Простой ролик, минимальный бандл
Собственный компонент-обёрткаНетПолный контроль через слотыТолько нативный HLSИндивидуальный UI, переиспользуемый по всему приложению
Video.js / @videojs-player/vuevideo.js (+ обёртка)Скин или переопределение стилейДа, из коробкиШирота форматов, стриминг, текстовые дорожки

Нативный 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. Без атрибута playsinline iOS 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.

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.