12k
All articles

如何为 Vue 应用添加视频播放功能

为Vue应用添加视频播放:原生HTML5 video、可复用组件或Video.js,支持HLS、字幕、自动播放和清理。

OpenReplay Team
OpenReplay Team
如何为 Vue 应用添加视频播放功能

为 Vue 应用添加视频播放有三种方式,选哪一种取决于你需要多大程度的控制力:简单嵌入一段视频用原生 HTML5 <video> 元素即可;需要自适应流媒体、字幕或广泛的格式支持时选择 Video.js;如果想要 Video.js 的功能但不愿自己动手接线播放器,就直接引入 @videojs-player/vue 这类现成组件。

视频在你自己开发的机器上通常播得好好的。然后测试人员用 iPhone 打开页面,它直接跳到全屏;或者自动播放在 Chrome 里悄无声息地失效,却没人说得清原因。因此,本文会用可直接复制粘贴的 Vue 3 <script setup> 代码逐一讲解这三种方案,并覆盖那些只在别人设备上才暴露出来的故障。

核心要点

  • 如果只是简单嵌入一段带默认控件的视频,用 :srccontrolsposter 绑定的原生 HTML5 <video> 元素就够了,完全不需要任何库。
  • 在使用 <script setup> 的 Vue 3 中,通过模板 ref() 获取元素,并在 onMounted 内对 videoRef.value 调用原生媒体 API,绝不要用 document.querySelector
  • 除非你导入了 video.js/dist/video-js.css,否则 Video.js 渲染出来将毫无样式——这是 Vue 中 Video.js 播放器看起来”坏掉了”的最常见原因。
  • 只要初始化了 Video.js,就必须在 onBeforeUnmount(Vue 3)中调用 player.dispose()。跳过这一步会导致每次卸载都泄漏播放器及其 DOM。
  • 浏览器会阻止带声音的自动播放:要让自动播放可靠生效,必须同时设置 mutedautoplay,并加上 playsinline,这样 iOS Safari 才会内联播放而非强制全屏。

如何选择 Vue 视频播放器方案?

不需要自定义 UI 或自适应流媒体时,选原生 <video>;想要定制控件但保有完全掌控权时,选自定义封装组件;需要 HLS、字幕或多种格式支持时,选 Video.js 或其组件封装。决策依据就这三条:是否需要自定义 UI,是否需要自适应流媒体(HLS/DASH),以及你能容忍多少依赖。

方案额外依赖自定义 UI 工作量HLS / 字幕适用场景
原生 <video>每个控件都要自己写仅原生 HLS(Safari)简单视频,最小打包体积
自定义封装组件通过插槽完全掌控仅原生 HLS定制 UI,在应用中复用
Video.js / @videojs-player/vuevideo.js(+ 封装层)换肤或覆写是,内置支持格式广度、流媒体、轨道

Vue 中的原生 HTML5 <video>(从这里开始)

在 Vue 中添加视频播放最快的方式,是使用原生 <video> 元素,通过 :srcposterpreload 绑定属性,再加上一个模板 ref() 来调用 HTMLMediaElement APIplay()pause().muted。在 Vue 3 <script setup> 中,直接在模板里绑定 timeupdateloadedmetadataended 等原生媒体事件,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>

模板中的 ref="videoRef" 会解析为 videoRef.value 上的 DOM 元素。请在事件处理函数或 onMounted 中访问它,不要在组件挂载之前访问。在 Options API 中,对应的写法是 this.$refs.videoRefmounted 钩子。如果你用 addEventListener 手动挂载监听器,就要在 onUnmounted 中移除它们;而在模板中通过 @event 绑定则可以彻底避免这类泄漏。

可复用的自定义播放器组件

要在整个应用中复用播放器逻辑,可以把原生 <video> 封装进一个组件,通过作用域插槽暴露其控件和状态,这样每处使用都能组合出自己的按钮和进度条,而无需重复编写播放逻辑。

<!-- 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>

使用方从插槽 props 中取出 playingtogglePlay,然后渲染任何自己需要的 UI。同一个基础播放器既可以支撑一个只有播放按钮的极简嵌入,也可以支撑一个带进度条的完整播放器,而媒体逻辑始终集中在一处。

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

什么时候该用 Video.js?

当你需要 HLS/自适应流媒体、字幕与文本轨道、统一的皮肤,或者超出原生 <video> 保证范围的格式广度时,就该使用 Video.js。安装 video.js,渲染一个 <video class="video-js">,在 onMounted 中初始化 videojs(ref, options),同时记得导入样式表并在卸载时销毁播放器。

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

这里有两点很容易出错:模板中的 ref 名称必须与你在 onMounted 中读取的名称一致(videoJsPlayer/videoPlayer 这类不匹配会抛错);以及在 Vue 3 中,dispose() 应该放在 onBeforeUnmount 里。beforeDestroy 是 Vue 2 的钩子,而 Vue 2 已于 2023 年 12 月 31 日停止维护。当前的稳定版本线是 Video.js 8.x,8.24.0 于 2026 年 8 月发布。模块化的 Video.js 10 尚处于 beta 阶段,还未正式发布:v10 仓库的时间线仍将正式可用标注为进行中,Video.js 核心与 contrib 的功能对齐目标定在 2026 年底。生产环境请继续使用 8.x,并且安装时不要锁定版本:npm install video.js

现成组件:@videojs-player/vue

在 Vue 3 中接入 Video.js 代码量最少的路径是 @videojs-player/vue,它提供一个开箱即用的 <video-player>,带有响应式 props(srcsourcespostercontrolsloopvolumefluidplaysinlinetracks)以及一个 { player, state } 作用域插槽用于自定义控件。它在内部处理了初始化与销毁,因此播放 HLS 视频只需替换一行 source。

<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 发布已停滞在 2022 年发布的 v1.0.0,依赖扫描工具会将其标记为低维护度。它发布的 peer dependency 范围仍要求 video.js 7.x,因此与 Video.js 8 搭配使用会在安装时产生 peer dependency 警告。如果你用的是 Vue 2,请使用仓库 legacy 章节中链接的旧版 vue-video-player 构建。@videojs-player/vue 的当前版本仅面向 Vue 3。

Vue 专属陷阱清单

Vue 中大多数”视频播不了”的 bug,都可以追溯到少数几条平台规则——它们能通过代码评审,却在真实设备上失效。尤其是自动播放和 playsinline 的失败,在用户真正遇到之前根本无从察觉,而这正是会话回放能从真实会话中揭示出来的那类设备相关问题。

  • 卸载时清理。 对 Video.js 调用 player.dispose(),并在 onUnmounted 中移除所有手动的 addEventListener 处理器,否则每一轮挂载/卸载都会造成泄漏。
  • 导入 CSS。 必须 import 'video.js/dist/video-js.css',否则播放器毫无样式。
  • 静音自动播放是强制要求。 浏览器会阻止带声音的自动播放;根据 Chrome 自动播放策略,需同时设置 mutedautoplay
  • iOS 需要 playsinline 缺少 playsinline 属性时,iPhone 上的 iOS Safari 通常会全屏打开视频;iPad 的行为则有所不同。
  • 使用模板 ref,而非 DOM 查询。 通过 ref() 获取元素,绝不要用 document.querySelector

从原生方案开始:一个绑定好的 <video> 能在几分钟内实现零依赖的播放功能。一旦你需要 HLS、字幕或跨浏览器一致的 UI,就升级到 Video.js 或 @videojs-player/vue,并从第一次提交起就把 dispose() 清理接上,这样它就永远不会变成日后需要你去排查的泄漏。

常见问题

如何在 Vue 应用中播放 HLS(.m3u8)流?

原生 HTML5 video 仅在 Safari 中支持 HLS,因此要实现跨浏览器的 HLS,请使用 Video.js 或其封装 @videojs-player/vue,后者通过 videojs-http-streaming 引擎内置了 HLS 支持。使用 @videojs-player/vue 时,你只需把 src 设为 .m3u8 播放列表 URL,组件会在内部处理流媒体引擎。在纯 Chrome 或 Firefox 中,原生 video 并不内置 HLS 支持,这正是选择 Video.js 的主要原因。

为什么我的 Vue 视频播放器无法自动播放?

静音自动播放通常是被允许的,但带声音的自动播放需要用户先与站点产生过交互;此外当站点拥有较高的 Media Engagement Index 分数时,Chrome 也会予以放行。Safari 也有类似的自有策略。要让自动播放可靠生效,你必须在元素上或 Video.js 选项中同时设置 muted 和 autoplay。这类失败在代码评审中是看不出来的,因为它取决于浏览器策略和设备,而非你的标记,所以只有真实用户加载页面时才会暴露。

@videojs-player/vue 与直接使用 Video.js 有什么区别?

@videojs-player/vue 是一个开箱即用的 Vue 3 组件,封装了 Video.js,在内部处理初始化与销毁,并暴露响应式 props 以及包含 player 和 state 的作用域插槽;而直接使用 Video.js 则意味着你要自己渲染 video 元素、在 onMounted 中调用 videojs(),并在 onBeforeUnmount 中自行销毁。封装组件代码量更少,但其 npm 发布自 2022 年的 v1.0.0 起就停滞了,因此直接使用 Video.js 能让你对版本和生命周期拥有更强的掌控力。

我还能在 Vue 2 中使用 @videojs-player/vue 吗?

不能。@videojs-player/vue 的当前版本仅面向 Vue 3。该包是在加入 React 支持时改成现在这个名称的,这一变更对 Vue 用户而言是破坏性的。对于 Vue 2,你需要使用较旧的 vue-video-player 5.x 构建,仓库的 legacy 章节仍然提供了链接。Vue 2 本身已于 2023 年 12 月 31 日停止维护,因此新的开发工作应面向 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.