从 Vue 2 到 Vue 3:我把 vue-aliplayer-v2 重构成了一个更现代的阿里云播放器组件

前言

最近我把开源项目 vue-aliplayer-v2 做了一次比较彻底的重构:从原来的 Vue 2 组件升级到 Vue 3 + TypeScript + Vite,并围绕阿里云新版 Web 播放器 SDK 补齐了 License、低延迟直播、VID + PlayAuth、STS、自动格式推断、多实例、扩展组件脚本、失败重试、双语 README 和 demo 工作台。

这次重构没有选择继续兼容 Vue 2。原因很简单:如果一边照顾旧版本 API,一边接入新版 SDK 和 Vue 3 组件模型,项目会越来越难维护。旧项目如果仍然依赖 Vue 2,可以继续固定安装 vue-aliplayer-v2@1.x;从 v2 开始,目标就是把组件做好、类型补全、使用体验理顺。

项目地址:

text 复制代码
https://github.com/langyuxiansheng/vue-aliplayer-v2

在线 demo:

text 复制代码
https://langyuxiansheng.github.io/vue-aliplayer-v2/

为什么要重构

这个项目的核心职责是把阿里云 Aliplayer Web SDK 封装成一个 Vue 组件。早期版本能解决基本播放问题,但随着 Vue 生态和阿里云播放器 SDK 的变化,旧实现会遇到几个典型问题:

  1. Vue 2 插件写法已经不适合 Vue 3 项目。
  2. 新版阿里云 Web 播放器需要 License 配置。
  3. SDK 资源路径已经从旧版播放器资源迁移到 imp-web-player
  4. 直播、FLV、M3U8、RTMP、VID、STS 等场景需要更明确的初始化逻辑。
  5. 多播放器实例共存时,SDK 加载和 DOM 容器管理需要更稳。
  6. 原项目文档和 demo 比较弱,很多参数只能靠翻源码。
  7. TypeScript 类型不完整,业务侧使用 ref 调方法时不够清晰。

所以这次重构的目标不是"让旧代码能跑",而是重新梳理组件边界:组件负责 Vue 生命周期、SDK 资源加载、基础兼容和常用能力暴露;真正的播放能力仍然交给阿里云官方 SDK。

技术栈选择

重构后的项目使用:

text 复制代码
Vue 3
TypeScript
Vite
vue-tsc
Aliplayer Web SDK 2.37.0

构建产物分成两类:

text 复制代码
dist/  用于 GitHub Pages demo
lib/   用于 npm 包发布

Vite 配置里通过 mode 区分 demo 和 lib:

ts 复制代码
export default defineConfig(({ mode }) => {
    const isDemoBuild = mode === 'demo';

    return {
        base: isDemoBuild ? '/vue-aliplayer-v2/' : '/',
        plugins: [vue()],
        publicDir: isDemoBuild ? 'public' : false,
        build: isDemoBuild
            ? {
                outDir: 'dist',
                emptyOutDir: true
            }
            : {
                outDir: 'lib',
                emptyOutDir: false,
                lib: {
                    entry: resolve(__dirname, 'packages/index.ts'),
                    name: 'VueAliplayerV2',
                    fileName: 'vue-aliplayer-v2'
                },
                rollupOptions: {
                    external: ['vue'],
                    output: {
                        exports: 'named',
                        globals: {
                            vue: 'Vue'
                        }
                    }
                }
            }
    };
});

这样做的好处是:demo 和 npm 包互不污染。demo 可以带页面样式和演示逻辑,库包只输出组件代码、样式和声明文件。

组件 API 设计

v2 暴露的组件叫 VueAliplayerV2,支持全局注册和局部引入。

全局注册:

ts 复制代码
import { createApp } from 'vue';
import App from './App.vue';
import VueAliplayerV2 from 'vue-aliplayer-v2';

const app = createApp(App);

app.use(VueAliplayerV2, {
    sdkVersion: '2.37.0'
});

app.mount('#app');

局部使用:

vue 复制代码
<template>
    <VueAliplayerV2
        ref="playerRef"
        :source="source"
        :options="options"
        :license="license"
        low-latency
        @ready="handleReady"
        @error="handleError"
        @sdk-error="handleSdkError"
    />
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue';
import VueAliplayerV2, {
    type AliplayerLicense,
    type AliplayerOptions,
    type VueAliplayerV2Expose
} from 'vue-aliplayer-v2';

const playerRef = ref<VueAliplayerV2Expose | null>(null);

const source = ref('//player.alicdn.com/video/aliyunmedia.mp4');

const license = ref<AliplayerLicense | null>({
    domain: 'example.com',
    key: 'your-license-key'
});

const options = reactive<AliplayerOptions>({
    autoplay: true,
    useH5Prism: true,
    playsinline: true,
    width: '100%',
    height: '420px'
});

function handleReady() {
    playerRef.value?.play();
}

function handleError(error: unknown) {
    console.log('player error', error);
}

function handleSdkError(error: Error) {
    console.log('sdk load error', error.message);
}
</script>

这里我把播放器配置分成了两层:

  • options:原样透传给阿里云 Aliplayer SDK。
  • 组件 props:封装 wrapper 层更常用的能力,比如 sourcelicenseautoFormatlowLatencycomponentScripts

这样既不会限制用户使用官方 SDK 新参数,又能把常见问题在组件层解决掉。

为了让 API 更容易扫读,我把 v2 主要 props 整理成下面这张表。实际使用时可以把它当成接入 checklist。

Prop 类型 默认值 使用场景 说明
source `string null` null URL 播放、动态切源
options `AliplayerOptions null` null 透传官方 SDK 参数
license `AliplayerLicense null` null 新版 Web 播放器 License
autoFormat boolean true 自动识别播放格式 根据 URL 后缀推断 mp4m3u8flvmp3rtmp,用户显式传 options.format 时不覆盖。
lowLatency boolean false FLV 直播低延迟 isLive + flv 场景下默认关闭 stash buffer,并设置较小初始缓存。
normalizeSourceUrl boolean true 中文、空格 URL 默认对 URL 执行 encodeURI,处理常见未编码播放地址。
forbidFastForward boolean false 试看、防快进 监听进度变化,发现异常快进时回退到上一次播放位置。
sdkVersion string 2.37.0 切换阿里云 SDK 版本 未传 cssLinkscriptSrc 时用于生成官方资源地址。
cssLink string 官方 CSS 私有部署、固定资源 覆盖默认播放器样式地址。
scriptSrc string 官方 JS 私有部署、固定资源 覆盖默认播放器脚本地址。
componentScripts string[] [] 跑马灯、水印、播放列表 在播放器初始化前加载额外组件脚本。
disableTracking boolean false 可选拦截统计请求 开启后会安装 wrapper 层的 track 请求拦截器。
trackingUrlPatterns `Array<string RegExp>` 默认 track 规则 自定义统计拦截

事件也尽量保持和 Aliplayer 官方 SDK 一致。组件只是做一层透传,额外增加了 sdk-error,用于区分"SDK 资源加载失败"和"播放器运行时失败"。

事件名 参数 触发时机 常见用途
ready unknown 播放器实例创建完成 自动播放、读取播放器状态、初始化业务 UI。
play unknown 开始播放 统计播放开始、切换按钮状态。
pause unknown 暂停播放 统计暂停、保存播放进度。
canplay unknown 媒体可以播放 隐藏 loading、展示控制栏。
playing unknown 媒体正在播放 判断播放恢复、关闭缓冲提示。
waiting unknown 播放等待或缓冲 展示缓冲状态、排查网络问题。
timeupdate unknown 播放进度变化 试看、学习进度、业务进度条同步。
ended unknown 播放结束 自动播放下一集、记录完成状态。
error unknown 播放器运行时错误 触发 retry()、展示业务错误提示。
sdk-error Error SDK CSS、JS 或扩展脚本加载失败 检查 CDN、版本号、网络拦截、私有资源地址。
requestFullScreen unknown 进入全屏 同步页面布局或埋点。
cancelFullScreen unknown 退出全屏 恢复页面布局。
startSeek unknown 开始拖拽或跳转 记录用户 seek 行为。
completeSeek unknown seek 完成 刷新业务播放进度。
liveStreamStop unknown 直播流停止 提示直播结束或尝试重连。
onM3u8Retry unknown M3U8 重试 排查 HLS 网络和分片问题。
snapshoted unknown 截图完成 获取截图结果或提示用户保存。

SDK 加载器:避免重复加载和版本写死

播放器组件经常会遇到一个问题:页面里有多个播放器时,如果每个组件都插入一份 SDK 脚本,很容易造成重复请求、全局对象覆盖或者初始化顺序问题。

v2 做了一个独立的 SDK loader:

ts 复制代码
export const DEFAULT_SDK_VERSION = '2.37.0';
export const DEFAULT_CSS_LINK = `https://g.alicdn.com/apsara-media-box/imp-web-player/${DEFAULT_SDK_VERSION}/skins/default/aliplayer-min.css`;
export const DEFAULT_SCRIPT_SRC = `https://g.alicdn.com/apsara-media-box/imp-web-player/${DEFAULT_SDK_VERSION}/aliplayer-min.js`;

加载时按 URL 缓存 Promise:

ts 复制代码
const cssPromises = new Map<string, Promise<void>>();
const scriptPromises = new Map<string, Promise<void>>();

这样多个播放器同时挂载时,最终也只会加载同一份 CSS 和 JS。

同时,sdkVersioncssLinkscriptSrc 都可以通过全局插件配置或单组件 props 覆盖。如果后续阿里云 SDK 发新版本,用户不需要等组件库发版,也可以先自己指定资源地址。

播放源处理:自动识别格式,但不抢业务控制权

Aliplayer 在不同格式之间切换时,行为并不完全一致。比如 MP4 切 MP4 可以优先调用 loadByUrl,但 MP4 切 FLV、M3U8 切 FLV 这种跨格式场景,重建播放器更稳。

v2 里增加了一个简单明确的格式推断:

ts 复制代码
export type SourceFormat = 'mp4' | 'm3u8' | 'flv' | 'rtmp' | 'mp3' | null;

export function inferSourceFormat(source?: string | null): SourceFormat {
    if (!source) return null;

    if (/^rtmps?:\/\//i.test(source)) return 'rtmp';

    const cleanSource = source.split('?')[0].split('#')[0].toLowerCase();
    const matched = KNOWN_FORMATS.find((format) => format && cleanSource.endsWith(`.${format}`));
    return matched || null;
}

组件只在用户没有显式设置 options.format 时才自动补充格式。这一点很重要:自动化应该减少使用成本,但不能覆盖业务明确传入的配置。

另外还加了 URL 标准化:

ts 复制代码
export function normalizeSource(source?: string | null): string | null {
    if (!source) return source || null;
    if (/^(data|blob):/i.test(source)) return source;

    try {
        return encodeURI(source);
    } catch {
        return source;
    }
}

很多业务播放地址里会出现中文文件名或空格,直接给 SDK 可能播放失败。默认做 encodeURI 能解决一批实际问题;如果业务已经自己编码,也可以关闭 normalizeSourceUrl

License:适配新版阿里云 Web 播放器

新版阿里云 Web 播放器通常需要配置 License。v2 支持两种方式:

vue 复制代码
<VueAliplayerV2
    :source="source"
    :license="{ domain: 'example.com', key: 'your-license-key' }"
/>

或者直接放到 options

vue 复制代码
<VueAliplayerV2
    :source="source"
    :options="{
        license: {
            domain: 'example.com',
            key: 'your-license-key'
        }
    }"
/>

组件层的 license prop 优先级更高。这样做是为了方便全局或页面级统一管理 License,同时不破坏 Aliplayer 原始 options。

VID + PlayAuth 和 STS

除了普通 URL,很多阿里云点播场景会用 VID + PlayAuth 或 STS。

VID + PlayAuth:

vue 复制代码
<VueAliplayerV2
    :options="{
        vid: '<your-video-id>',
        playauth: '<your-playauth>',
        authTimeout: 7200
    }"
/>

STS:

vue 复制代码
<VueAliplayerV2
    :options="{
        vid: '<your-video-id>',
        region: 'cn-shanghai',
        accessKeyId: '<temporary-access-key-id>',
        accessKeySecret: '<temporary-access-key-secret>',
        securityToken: '<temporary-security-token>'
    }"
/>

这类初始化模式不需要传 source。如果播放器创建后要切换,可以通过 ref 调用:

ts 复制代码
playerRef.value?.replayByVidAndPlayAuth(vid, playauth);

FLV 直播低延迟

直播是这次重构重点补齐的场景之一。FLV 直播如果追求更低延迟,通常需要调整 stash buffer。

v2 里提供了 lowLatency

vue 复制代码
<VueAliplayerV2
    source="//example.com/live.flv"
    low-latency
    :options="{
        isLive: true,
        autoplay: true
    }"
/>

当满足 isLive + flv + lowLatency 时,组件会默认补充:

ts 复制代码
{
    enableStashBufferForFlv: false,
    stashInitialSizeForFlv: 128
}

但这里仍然保留业务覆盖权:如果你在 options 里显式传了 enableStashBufferForFlvstashInitialSizeForFlv,组件不会再强行覆盖。

多实例:每个播放器独立,但 SDK 资源共享

多播放器场景容易出两个问题:

  1. 多个组件争抢同一个 DOM id。
  2. 多个组件重复插入 SDK 标签。

v2 为每个组件实例生成独立容器 id:

text 复制代码
vue-aliplayer-v2-${seed}

SDK 资源则通过 loader 层共享 Promise。这样可以做到:

  • 每个播放器实例互不干扰。
  • 同一页面只加载一次相同 SDK。
  • 组件卸载时销毁自己的播放器实例。

多实例使用示例:

vue 复制代码
<template>
    <VueAliplayerV2
        v-for="item in sources"
        :key="item"
        :source="item"
        :options="options"
    />
</template>

Ref 方法:给业务侧足够的控制能力

播放器组件不能只靠 props,因为很多操作是命令式的:播放、暂停、seek、全屏、设置音量、切换源、设置试看时间等。

v2 暴露了 VueAliplayerV2Expose 类型:

ts 复制代码
const playerRef = ref<VueAliplayerV2Expose | null>(null);

playerRef.value?.play();
playerRef.value?.pause();
playerRef.value?.seek(30);
playerRef.value?.setVolume(0.8);
playerRef.value?.requestFullScreen();
playerRef.value?.retry();

如果只给一个方法名列表,读者需要来回翻类型定义。这里更适合放成表格,把参数、返回值和典型用途一起写清楚:

方法 参数 返回值 说明 典型场景
getPlayer() `AliplayerInstance null` 获取底层 Aliplayer 实例。
init() Promise<void> 加载 SDK、扩展脚本并初始化播放器。 手动重新初始化,或调试 SDK 加载流程。
initPlayer() void 用当前 props 和 options 直接创建播放器实例。 SDK 已加载完成后,内部初始化播放器。
reload(nextSource?) nextSource?: string Promise<void> 重载播放器,可传新的 URL。 切换清晰度、切换线路、播放器异常后重建。
retry(nextSource?) nextSource?: string Promise<void> 语义化的失败重试方法,内部逻辑接近 reload error 事件里给用户一个"重试"按钮。
play() void 开始播放。 自定义播放按钮、ready 后自动播放。
pause() void 暂停播放。 自定义暂停按钮、页面离开时暂停。
replay() void 从头重播当前视频。 播放结束后重新观看。
seek(time) time: number void 跳转到指定秒数。 恢复历史进度、章节跳转。
getCurrentTime() `number undefined` 获取当前播放时间,单位秒。
getDuration() `number undefined` 获取视频总时长,单位秒。
getVolume() `number undefined` 获取当前音量。
setVolume(volume) volume: number void 设置音量,通常范围为 01 静音、恢复音量、自定义音量控制。
loadByUrl(url, time?) url: string, time?: number void 通过 URL 切换播放源。 同格式播放源切换、指定起播时间。
replayByVidAndPlayAuth(vid, playauth) vid: string, playauth: string void 使用 VID + PlayAuth 重新播放。 点播鉴权信息刷新后继续播放。
replayByVidAndAuthInfo(...) vid, accId, accSecret, stsToken, authInfo, domainRegion void 使用 MPS 鉴权信息重新播放。 兼容阿里云旧鉴权或特殊业务接入。
setPlayerSize(width, height) width: string, height: string void 设置播放器尺寸。 弹窗、布局切换、横竖屏适配。
setSpeed(speed) speed: number void 设置播放倍速。 课程、长视频、用户自定义倍速。
setSanpshotProperties(width, height, rate) width: number, height: number, rate: number void 设置截图参数,方法名沿用 SDK 原始拼写。 业务截图、封面生成。
requestFullScreen() void 进入全屏。 自定义全屏按钮。
cancelFullScreen() void 退出全屏。 自定义退出全屏按钮。
getIsFullScreen() `boolean undefined` 获取当前是否全屏。
getStatus() `string undefined` 获取播放器内部状态。
setLiveTimeRange(beginTime, endTime) beginTime: string, endTime: string void 设置直播时移可播放范围。 直播回看、时移播放。
setRotate(rotate) rotate: number void 设置视频旋转角度。 处理拍摄方向不正确的视频。
getRotate() `number undefined` 获取当前旋转角度。
setImage(image) image: string void 设置视频镜像。 摄像头、镜像预览类场景。
dispose() void 销毁播放器实例。 手动释放资源、弹窗关闭。
setCover(cover) cover: string void 设置封面图。 动态替换视频封面。
setProgressMarkers(markers) markers: unknown[] void 设置进度条打点。 章节点、广告点、知识点标记。
setPreviewTime(time) time: number void 设置试看时间。 付费视频试看。
getPreviewTime() `number undefined` 获取试看时间。
isPreview() `boolean undefined` 判断是否处于试看状态。
off(eventName, handler) eventName: string, handler: Function void 取消底层播放器事件监听。 手动绑定 SDK 事件后清理监听。

这里尽量保持了阿里云 SDK 的原始方法名,包括 setSanpshotProperties 这种 SDK 里的拼写。这样用户查官方文档时不会因为 wrapper 改名而产生额外心智负担。

扩展组件脚本:跑马灯、水印、播放列表

Aliplayer 的自定义组件能力通常需要额外脚本,例如跑马灯、水印、播放列表等。

v2 提供 componentScripts

vue 复制代码
<VueAliplayerV2
    :source="source"
    :component-scripts="['/aliplayer-components/marquee.js']"
    :options="{
        components: [
            {
                name: 'MarqueeComponent',
                type: window.MarqueeComponent,
                args: {
                    text: 'vue-aliplayer-v2'
                }
            }
        ]
    }"
/>

组件会保证扩展脚本在播放器初始化之前加载完成。这样业务侧不需要在页面里手动管理脚本加载顺序。

可选的 track 上报拦截

部分用户反馈过播放器统计请求的问题,所以 v2 提供了一个可选兜底能力:

vue 复制代码
<VueAliplayerV2
    :source="source"
    disable-tracking
    :tracking-url-patterns="['newplayer/track', /\/logstores\//]"
/>

实现上会 patch fetchXMLHttpRequest,拦截命中规则的请求。

这个能力我没有默认开启,因为它会影响全局网络 API。生产环境里,优先建议使用阿里云官方配置、控制台设置或业务自己的合规上报策略。组件层的拦截只适合作为兜底方案。

Demo 也要认真写

这次我没有只写一个"能播放视频"的 demo,而是重新做了一个参数工作台。它包含:

  • SDK 版本切换
  • URL / VID / STS 三种播放源模式
  • License 输入
  • 自动格式推断
  • FLV 低延迟配置
  • 直播时移配置
  • 皮肤布局配置
  • 自定义 options JSON
  • 事件流
  • 配置预览和复制
  • 多实例验证

这样 demo 不只是展示页,也能作为调试工具。遇到用户反馈 issue 时,可以直接在 demo 中复现参数组合。

文档和类型

组件库想要好用,类型和文档必须跟上。

这次做了三件事:

  1. 所有公开类型补了详细 JSDoc。
  2. README 写成中英文双语版本,并互相链接。
  3. npm 包发布文件里包含 README.mdREADME.en-US.md

类型导出如下:

ts 复制代码
import type {
    AliplayerEventName,
    AliplayerInstance,
    AliplayerLicense,
    AliplayerOptions,
    AliplayerV2Props,
    VueAliplayerV2Expose,
    VueAliplayerV2Options
} from 'vue-aliplayer-v2';

业务侧不需要进入内部目录找类型,直接从包入口导入即可。

构建和发布

本地开发:

bash 复制代码
npm install
npm run dev

类型检查:

bash 复制代码
npm run type-check

构建 demo:

bash 复制代码
npm run build

构建组件库:

bash 复制代码
npm run lib

发布前可以先 dry-run 看包内容:

bash 复制代码
npm pack --dry-run

如果本机 npm cache 权限异常,可以临时指定 cache:

bash 复制代码
npm --cache /private/tmp/npm-cache-vue-aliplayer-v2 pack --dry-run

这次重构修掉了什么

从功能角度看,这次 v2 主要解决了这些问题:

  • Vue 3 项目可以直接使用。
  • 新版 Aliplayer SDK 资源路径可配置。
  • License 有明确入口。
  • URL、VID、STS 三种初始化方式更清晰。
  • MP4、M3U8、FLV、MP3、RTMP 格式能自动推断。
  • 同格式切源和跨格式切源有不同处理策略。
  • 多播放器不会重复加载 SDK。
  • 直播 FLV 有低延迟预设。
  • 播放失败可以通过 retry() / reload() 处理。
  • 扩展组件脚本有统一加载入口。
  • TypeScript 用户可以获得完整的 ref 方法提示。
  • README 和 demo 能覆盖大部分常见使用方式。

一点重构经验

这次重构最大的感受是:组件库升级不要只做语法迁移。

如果只是把 Vue 2 代码改成 Vue 3 写法,短期看起来成本低,但实际价值有限。真正应该做的是重新确认边界:

  • 哪些能力应该由 wrapper 负责?
  • 哪些能力应该透传给官方 SDK?
  • 哪些默认行为是安全的?
  • 哪些配置必须保留业务覆盖权?
  • demo 能不能帮助用户复现问题?
  • 类型和 README 能不能让用户少翻源码?

对播放器组件来说,最重要的是稳定。组件层不应该试图重写播放器能力,而是应该把 SDK 接入、生命周期、资源加载、常见兼容和工程体验处理好。

结语

vue-aliplayer-v2 的 v2 版本现在已经从 Vue 2 时代的封装,升级成了一个面向 Vue 3 的播放器组件库。它不是一个重写播放器内核的项目,而是一个更现代、更清晰、更容易维护的 Aliplayer Vue wrapper。

如果你的项目正在使用 Vue 3,并且需要接入阿里云点播、直播或 Aliplayer Web SDK,可以试试这个版本。

项目地址:

text 复制代码
https://github.com/langyuxiansheng/vue-aliplayer-v2

在线 demo:

text 复制代码
https://langyuxiansheng.github.io/vue-aliplayer-v2/
相关推荐
蓝银草同学7 小时前
新手指南:快速理清独立仓库 Java 8 多模块项目依赖并运行
前端·后端
蓝银草同学7 小时前
前端转 Java,第一篇看懂 pom.xml:Maven 依赖管理从入门到不懵
前端·后端
彦为君7 小时前
JavaSE-11-网络编程(详细版)
java·前端·网络·ai·ai编程
HjhIron7 小时前
从三件套到模块化:前端开发的底层思维
前端·后端
yingyima7 小时前
Kubernetes CronJob 速查手册:核心语法与实战示例
前端
麻雀飞吧7 小时前
TqWebHelper 本地监控:图表不刷新与端口冲突排查
前端·python
用户52438855928877 小时前
拆解Vue2源码-01 reactive
前端
ZC跨境爬虫7 小时前
跟着 MDN 学CSS day_10:(博客页面样式修复实战挑战)
前端·css
宋浮檀s7 小时前
DVWA通关教程2
运维·服务器·前端·javascript