前端视频播放方案选型:主流 Web 播放器对比 + Vue3 实战


基于 Spring Boot3、Vue3、Naive UI 构建,助力应用快速开发、发布、运维的低代码平台,旨在帮助使用者(包含但不限于开发人员、业务人员)快速响应业务需求

项目地址1:https://gitcode.com/app-meta/app-meta

项目地址2:https://github.com/app-meta/app-meta


需求背景

客户希望增加一个在线播放视频的功能(类似于 Markdown 文档页),同时可以查看哪些用户查看该视频。

思路

用现有的快应用就能快速解决该需求,视频文件以附件形式上传(后端已经有处理附件的接口)。

常见播放器

GitHub 收藏数据截止到 2026-02-10

序号 播放器 GitHub ⭐Star 最新版本 & 发布情况 社区/维护活跃度 包体积 / 说明
1 mui-player ⭐ ~550+ 最新 v1.8.1(发布 ~3 年前)([NPM][1]) 🟡 维护较弱(Issues 不够活跃) 中等体积 HTML5 UI 播放器(~35 KB 核心)([jsDelivr][2])
2 oplayer ⭐ ~180+ 最新 UI 组件 v1.3.x(2024 发布) 🟡 中等(Release & PR 不算频繁) 未官方明确说明体积(轻量级实现)
3 Video.js ⭐ ~39.5k v8.x 系列持续更新 🟢 非常活跃(成熟生态 & 大贡献者网络) 功能丰富但体积相对较大
4 ArtPlayer.js ⭐ ~3.5k v5.x 系列活跃发布 🟢 中高(插件生态 & 文档较完善) ~25 KB minified + gzipped(轻量)

ArtPlayer.js

Video.js

Video.js 明显领先,是最成熟、最受欢迎的视频播放器库,适合大型业务级应用,并提供非常全面的能力:多格式支持、插件体系、主题、事件机制等,但体积相对大。

oplayer

OPlayer 有不错的功能覆盖,但 Star 数相对较少,社区参与度一般。

mui-player

MuiPlayer 在国内生态相对有人关注,但整体社区活跃度偏弱。

集成到 Vue3

最终,选择了 ArtPlayer 播放器。

安装依赖

shell 复制代码
pnpm i artplayer

渲染器

html 复制代码
<template>
    <n-card :title="title">
        <n-grid :gutter="10">
            <n-grid-item :span>
                <div ref="playerDiv" :style="style"></div>
            </n-grid-item>
            <n-grid-item :span="24-span">
                <n-card title="视频信息" size="small" :style="`height: `+height+`px`" content-style="overflow:auto">
                    <n-table size="small" :bordered="true" :single-line="false">
                        <tbody>
                            <tr>
                                <td class="text-center" width="120">大小</td>
                                <td>{{filesize(video.size)}}</td>
                            </tr>
                            <tr>
                                <td class="text-center">上传者</td>
                                <td>{{ video.user }}</td>
                            </tr>
                            <tr>
                                <td class="text-center">上传时间</td>
                                <td>{{ video.time? toDate(video.time) : "" }}</td>
                            </tr>
                        </tbody>
                    </n-table>

                    <div class="mt-4" v-if="video.summary">
                        <n-tag :bordered="false">描述信息</n-tag>
                        <div class="mt-1">{{ video.summary }}</div>
                    </div>
                </n-card>
            </n-grid-item>
        </n-grid>
    </n-card>
</template>

<script setup>
    import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
    import ArtPlayer from 'artplayer'

    import { renderProps, loadContent } from "../"

    const props = defineProps(renderProps)

    const height = window.innerHeight - 160
    let player = null

    let title = ref(props.page.name)
    const video = JSON.parse(props.data)

    const filesize  = r=> H.filesize(r)
    const toDate    = d=>H.date.datetime(d)
    
    let span = ref(24)
    const playerDiv = ref()
    const style = reactive({
        margin: "5px auto",
        height: `${height}px`,
        overflow: "hidden"
    })

    onMounted(() => {
        let { ratio = 16/9, highlight = [], path, showInfo } = video
        //计算宽度
        style.width = `${Math.floor(height * ratio)}px`
        span.value = showInfo ? 16 : 24

        let url = path ? window.SERVER+"/"+path : null

        //处理高亮点
        let highlights = []
        if(highlight){
            highlight.split("\n").forEach(line=>{
                let [time, text] = line.trim().split(/\s+/)
                if(time && text && !isNaN(time))
                    highlights.push({ time: Number(time), text })
            })
        }

        player = new ArtPlayer({
            //播放器的唯一标识,目前只用于记忆播放 autoplayback
            id: `video-${props.page.id}`,
            container: playerDiv.value,
            url, //'https://muiplayer.js.org/media/media.mp4',
            volume: 1.0,                //播放器的默认音量
            muted: false,               //是否默认静音
            autoSize: true,             //播放器的尺寸默认会填充整个 container 容器尺寸,所以经常出现黑边,该值能自动调整播放器尺寸以隐藏黑边
            autoPlay: false,
            loop: false,                //是否循环播放
            playbackRate: true,         //是否显示视频播放速度功能,会出现在 设置面板 和 右键菜单 里
            setting: true,              //是否在底部控制栏里显示 设置面板 的开关按钮
            screenshot: true,           //是否在底部控制栏里显示播放器 视频截图 功能
            fullscreen: true,           //是否在底部控制栏里显示播放器 窗口全屏 按钮
            fullscreenWeb: true,        //是否在底部控制栏里显示播放器 网页全屏 按钮
            pip: true,                  //是否在底部控制栏里显示 画中画 的开关按钮
            theme: window.color,        //播放器主题颜色,目前用于 进度条 和 高亮元素 上
            miniProgressBar: true,      //迷你进度条,只在播放器失去焦点后且正在播放时出现
            highlight: highlights,      //在进度条上显示高亮信息,格式为:[ { time:60, text:`前方高能` }]
            autoPlayback: true,         //是否使用自动 回放功能
            lock: true,                 //是否在移动端显示一个 锁定按钮 ,用于隐藏底部 控制栏
            aspectRatio: true,          //是否显示视频长宽比功能,会出现在 设置面板 和 右键菜单 里
        })
    })

    onUnmounted(() => player?.destroy(false))
</script>

编辑页面

html 复制代码
<!-- 视频页编辑 -->
<template>
    <n-card title="视频设置" segmented size="small">
        <n-form :show-feedback="false" label-placement="left" label-width="100px">
            <n-space vertical>
                <n-form-item label="画面宽高比">
                    <n-select v-model:value="bean.ratio" :options="ratios" />
                </n-form-item>
                <n-form-item label="显示基本信息">
                    <n-switch v-model:value="bean.showInfo" />
                    <n-text class="ml-2" depth="3">勾选后将显示视频的大小、上传者、日期等信息</n-text>
                </n-form-item>
                <n-form-item label="描述信息">
                    <n-input v-model:value="bean.summary" type="textarea" />
                </n-form-item>
                <n-form-item label="更新视频">
                    <Uploader action="/page/document-upload" :data accept=".mp4" showFileList :noticeOnOk="false" @ok="uploadDone">
                        <n-button type="primary" secondary>上传视频文件</n-button>
                        <n-text class="ml-2" depth="3">仅支持 MP4 格式,重新生成将覆盖原视频</n-text>
                        <div v-if="bean.id || bean.size" class="mt-1">
                            <n-tag size="small" :bordered="false">{{ bean.addOn? toDate(bean.addOn) : "" }}</n-tag>
                            已上传视频 
                            <n-tag size="small" :bordered="false">{{ bean.file}}</n-tag> ,大小为 
                            <n-tag size="small" :bordered="false">{{ filesize(bean.size) }}</n-tag>
                        </div>
                    </Uploader>
                </n-form-item>
                <n-form-item label="高亮时间点">
                    <n-space vertical class="w-full">
                        <n-input v-model:value="bean.highlight" type="textarea" placeholder="在进度条上显示高亮信息"/>
                        <n-text class="ml-2" depth="3">
                            一行一个高亮,格式为
                            <n-tag size="small" :bordered="false">秒数(空格)文本</n-tag>,示例:
                            10 前方高能
                        </n-text>
                    </n-space>
                </n-form-item>
                
                <div class="text-center">
                    <n-button type="primary" @click="toSave" size="large">更新视频配置信息</n-button>
                </div>
            </n-space>
        </n-form>
    </n-card>
</template>

<script setup>
    import { ref, reactive, h, onMounted, computed } from 'vue'
    import { useRoute } from 'vue-router'

    import Uploader from "@C/uploader.vue"

    import { pageEditor } from "../"

    const r16_9 = 1.78
    let ratios = [
        { label:"16:9", value: r16_9 },
        { label:"16:10", value: 1.6 },
        { label:"4:3", value: 1.33 },
        { label:"1:1", value: 1 }
    ]
    
    let { id, bean, loading , updateContent } = pageEditor(
        { ratio: r16_9, summary: "", path: null, highlight: '' }, 
        d=> JSON.parse(d), 
        { padding: false }
    )

    const filesize  = r=> H.filesize(r)
    const toDate    = d=>H.date.datetime(d)
    const data = computed(()=> bean.value.id ? { pid: id, id: bean.value.id }:{ pid: id })

    let uploadDone = ({ data:{ path, addOn, uid, id } }, { file:{ size, name }})=>{
        bean.value.path = path? path.replace("\\","/") : null
        bean.value.size = size
        bean.value.user = uid
        bean.value.addOn= Date.now()
        bean.value.file = name
        bean.value.id   = id

        M.ok(`⌈${name}⌋已上传`)
    }

    let toSave = () => updateContent(JSON.stringify(bean.value))
</script>

效果展示

新增视频模块

视频配置

播放页面

相关推荐
前端 贾公子1 小时前
Vue3 业务组件库按需加载的实现原理(中)
前端·javascript·vue.js
温轻舟1 小时前
前端可视化大屏【附源码】
前端·javascript·css·html·可视化·可视化大屏·温轻舟
北极象1 小时前
Flying-Saucer HTML到PDF渲染引擎核心流程分析
前端·pdf·html
weixin199701080161 小时前
Tume商品详情页前端性能优化实战
大数据·前端·java-rabbitmq
梦里寻码1 小时前
深入解析 SmartChat 的 RAG 架构设计 — 如何用 pgvector + 本地嵌入打造企业级智能客服
前端·agent
克里斯蒂亚诺·罗纳尔达1 小时前
vue页面加载时间过长优化
vue
edisao1 小时前
第一章:L-704 的 0.00% 偏差
前端·数据库·人工智能
CappuccinoRose2 小时前
HTML语法学习文档(一)
前端·学习·html
Cache技术分享2 小时前
322. Java Stream API - 使用 Finisher 对 Collector 结果进行后处理
前端·后端