前端视频播放方案选型:主流 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 年前)(NPM1) 🟡 维护较弱(Issues 不够活跃) 中等体积 HTML5 UI 播放器(~35 KB 核心)(jsDelivr2)
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>

效果展示

新增视频模块

视频配置

播放页面

相关推荐
mldong8 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排8 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60619 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角10 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!10 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰10 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi12 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen12 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马13 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒14 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端