fix: 更新文档和说明

This commit is contained in:
“zhouliujun” 2026-05-23 22:05:52 +08:00
parent 15c784daef
commit 01ede92ed6
25 changed files with 2570 additions and 164 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
.DS_Store
node_modules/
*.map
docs/
# local env files
.env.local
.env.*.local

551
README.en-US.md Normal file
View File

@ -0,0 +1,551 @@
# vue-aliplayer-v2
English | [简体中文](./README.md)
A Vue 3 wrapper for the Aliyun Web Player Aliplayer SDK. v2 is rebuilt with Vue 3, TypeScript, and Vite. It supports URL playback, VID + PlayAuth, STS credentials, live streams, low-latency FLV, automatic format inference, License injection, extension component scripts, multiple player instances, and common player instance methods.
> v2 no longer supports Vue 2. For Vue 2 projects, keep using `vue-aliplayer-v2@1.x`.
## Demo
- Project demo: https://langyuxiansheng.github.io/vue-aliplayer-v2/
- Official Aliplayer demo: https://player.alicdn.com/aliplayer/index.html
- Official integration guide: https://help.aliyun.com/zh/vod/developer-reference/integration
## Features
- Vue 3 component and plugin installation via `app.use(VueAliplayerV2, options)`.
- TypeScript exports for props, options, events, player instances, and exposed ref methods.
- Loads Aliyun `imp-web-player` SDK 2.37.0 by default, with custom CSS/JS URL support.
- Supports `source` URLs, `options.source`, VID + PlayAuth, and STS credential playback.
- Infers `mp4`, `m3u8`, `flv`, `mp3`, and `rtmp` formats from source URLs.
- Adds an optional low-latency preset for FLV live streams.
- Supports License config for newer Aliplayer Web SDK versions.
- Supports multiple player instances while loading the SDK assets only once.
- Loads extra component scripts before player initialization for playlists, watermarks, marquees, and other Aliplayer extensions.
- Provides an optional blocker for known tracking requests.
## Installation
```bash
npm install vue-aliplayer-v2
```
```bash
yarn add vue-aliplayer-v2
```
```bash
pnpm add vue-aliplayer-v2
```
## Quick Start
### Global Registration
```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
source="//player.alicdn.com/video/aliyunmedia.mp4"
:options="{ autoplay: true, useH5Prism: true }"
/>
</template>
```
### Local Import
```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,
isLive: false,
useH5Prism: true,
playsinline: true,
width: '100%',
height: '420px',
controlBarVisibility: 'hover'
});
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>
```
## Plugin Options
`app.use(VueAliplayerV2, options)` sets global defaults. Component props with the same names override these defaults per instance.
```ts
app.use(VueAliplayerV2, {
sdkVersion: '2.37.0',
cssLink: 'https://g.alicdn.com/apsara-media-box/imp-web-player/2.37.0/skins/default/aliplayer-min.css',
scriptSrc: 'https://g.alicdn.com/apsara-media-box/imp-web-player/2.37.0/aliplayer-min.js',
componentScripts: ['/aliplayer-components/watermark.js'],
disableTracking: false,
trackingUrlPatterns: ['newplayer/track']
});
```
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `sdkVersion` | `string` | `2.37.0` | Aliyun Web Player SDK version. Used to generate official asset URLs when `cssLink` or `scriptSrc` is not provided. |
| `cssLink` | `string` | Official 2.37.0 CSS | Custom Aliplayer CSS URL. |
| `scriptSrc` | `string` | Official 2.37.0 JS | Custom Aliplayer JS URL. |
| `componentScripts` | `string[]` | `[]` | Extra component or business extension script URLs. |
| `disableTracking` | `boolean` | `false` | Enables the known Aliplayer tracking request blocker. |
| `trackingUrlPatterns` | `Array<string \| RegExp>` | `['newplayer/track']` | URL fragments or regular expressions to block. |
## Props
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `source` | `string \| null` | `null` | Playback source URL. It has priority over `options.source`. Same-format changes prefer `loadByUrl`; cross-format changes recreate the player. |
| `options` | `AliplayerOptions \| null` | `null` | Raw Aliplayer initialization options. The component adds `id`, `source`, `license`, `format`, and low-latency defaults when needed. |
| `license` | `AliplayerLicense \| null` | `null` | Aliplayer License config. It has priority over `options.license`. |
| `autoFormat` | `boolean` | `true` | Infers `format` from `source`. |
| `lowLatency` | `boolean` | `false` | Enables the low-latency FLV live preset. |
| `normalizeSourceUrl` | `boolean` | `true` | Runs `encodeURI` on source URLs to handle Chinese characters, spaces, and other unencoded characters. |
| `forbidFastForward` | `boolean` | `false` | Prevents drag-to-fast-forward behavior. |
| `sdkVersion` | `string` | Global option | Overrides the SDK version for the current component. |
| `cssLink` | `string` | Global option | Overrides the CSS URL for the current component. |
| `scriptSrc` | `string` | Global option | Overrides the JS URL for the current component. |
| `componentScripts` | `string[]` | Global option | Extra scripts to load before initializing the current component. |
| `disableTracking` | `boolean` | Global option | Enables tracking request blocking for the current component. |
| `trackingUrlPatterns` | `Array<string \| RegExp>` | Global option | Tracking URL rules for the current component. |
## Common AliplayerOptions Fields
`options` is passed through to the Aliplayer SDK, so official SDK fields can still be used. The package declares the common fields below and keeps an index signature for additional official or business fields.
| Name | Type | Description |
| --- | --- | --- |
| `source` | `string` | Playback source URL. |
| `width` | `string` | Player width, for example `100%`. |
| `height` | `string` | Player height, for example `420px`. |
| `autoplay` | `boolean` | Whether to autoplay. |
| `isLive` | `boolean` | Whether the stream is live. |
| `format` | `string` | Source format, such as `mp4`, `m3u8`, or `flv`. |
| `license` | `AliplayerLicense` | Aliplayer License config. |
| `vid` | `string` | VOD video ID. |
| `playauth` | `string` | VOD PlayAuth token. |
| `authTimeout` | `number` | Playback URL validity duration in seconds. |
| `region` | `string` | STS media region, for example `cn-shanghai`. |
| `accessKeyId` | `string` | Temporary STS AccessKey ID. |
| `accessKeySecret` | `string` | Temporary STS AccessKey Secret. |
| `securityToken` | `string` | STS security token. |
| `components` | `unknown[]` | Aliplayer custom component config. |
| `enableStashBufferForFlv` | `boolean` | Whether FLV stash buffer is enabled. |
| `stashInitialSizeForFlv` | `number` | FLV initial stash size. |
| `rtsVersion` | `string` | RTS SDK version. |
## Events
The component forwards common Aliplayer events and adds `sdk-error`.
| Event | Payload | Description |
| --- | --- | --- |
| `ready` | `unknown` | Player initialization completed. |
| `play` | `unknown` | Playback started. |
| `pause` | `unknown` | Playback paused. |
| `canplay` | `unknown` | Media can play. |
| `playing` | `unknown` | Media is playing. |
| `ended` | `unknown` | Playback ended. |
| `liveStreamStop` | `unknown` | Live stream stopped. |
| `onM3u8Retry` | `unknown` | M3U8 retry event. |
| `hideBar` | `unknown` | Control bar hidden. |
| `showBar` | `unknown` | Control bar shown. |
| `waiting` | `unknown` | Playback is waiting. |
| `timeupdate` | `unknown` | Playback time updated. |
| `snapshoted` | `unknown` | Snapshot completed. |
| `requestFullScreen` | `unknown` | Fullscreen requested. |
| `cancelFullScreen` | `unknown` | Fullscreen canceled. |
| `error` | `unknown` | Runtime player error. |
| `startSeek` | `unknown` | Seek started. |
| `completeSeek` | `unknown` | Seek completed. |
| `sdk-error` | `Error` | SDK CSS, JS, or extension script failed to load. |
## Ref Methods
Use `ref<VueAliplayerV2Expose | null>` to access exposed methods.
```ts
playerRef.value?.play();
playerRef.value?.pause();
playerRef.value?.seek(30);
playerRef.value?.setVolume(0.8);
playerRef.value?.loadByUrl('//player.alicdn.com/video/aliyunmedia.mp4');
playerRef.value?.requestFullScreen();
playerRef.value?.retry();
```
| Method | Description |
| --- | --- |
| `getPlayer()` | Returns the underlying Aliplayer instance. |
| `init()` | Loads the SDK and initializes the player. |
| `initPlayer()` | Creates a player instance with current props/options. |
| `reload(nextSource?)` | Reloads the player, optionally with a new source. |
| `retry(nextSource?)` | Business-friendly retry method after playback failure. |
| `play()` | Starts playback. |
| `pause()` | Pauses playback. |
| `replay()` | Replays from the beginning. |
| `seek(time)` | Seeks to a time in seconds. |
| `getCurrentTime()` | Returns current playback time. |
| `getDuration()` | Returns media duration. |
| `getVolume()` | Returns current volume. |
| `setVolume(volume)` | Sets volume. |
| `loadByUrl(url, time?)` | Switches source by URL. |
| `replayByVidAndPlayAuth(vid, playauth)` | Replays with VID + PlayAuth. |
| `replayByVidAndAuthInfo(...)` | Replays with MPS auth info. |
| `setPlayerSize(width, height)` | Sets player size. |
| `setSpeed(speed)` | Sets playback speed. |
| `setSanpshotProperties(width, height, rate)` | Sets snapshot options. The method name follows the SDK spelling. |
| `requestFullScreen()` | Enters fullscreen. |
| `cancelFullScreen()` | Exits fullscreen. |
| `getIsFullScreen()` | Returns fullscreen state. |
| `getStatus()` | Returns player status. |
| `setLiveTimeRange(beginTime, endTime)` | Sets the playable live time-shift range. |
| `setRotate(rotate)` | Sets video rotation. |
| `getRotate()` | Returns video rotation. |
| `setImage(image)` | Sets video mirror mode. |
| `dispose()` | Disposes the player. |
| `setCover(cover)` | Sets cover image. |
| `setProgressMarkers(markers)` | Sets progress markers. |
| `setPreviewTime(time)` | Sets preview duration. |
| `getPreviewTime()` | Returns preview duration. |
| `isPreview()` | Returns whether the player is in preview mode. |
| `off(eventName, handler)` | Removes a listener from the underlying player. |
## Usage Examples
### URL Playback
```vue
<VueAliplayerV2
source="//player.alicdn.com/video/aliyunmedia.mp4"
:options="{
autoplay: true,
useH5Prism: true,
width: '100%',
height: '420px'
}"
/>
```
### VID + PlayAuth
```vue
<VueAliplayerV2
:options="{
vid: '<your-video-id>',
playauth: '<your-playauth>',
authTimeout: 7200,
width: '100%',
height: '420px'
}"
/>
```
`VID + PlayAuth` is an initialization mode, so `source` is not required. To switch after the player has been created, call:
```ts
playerRef.value?.replayByVidAndPlayAuth(vid, playauth);
```
### 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>',
width: '100%',
height: '420px'
}"
/>
```
### FLV Live and Low Latency
```vue
<VueAliplayerV2
source="//example.com/live.flv"
low-latency
:options="{
isLive: true,
autoplay: true,
width: '100%',
height: '420px'
}"
/>
```
When `low-latency` is enabled and the current source is FLV with `options.isLive = true`, the component adds:
```ts
{
enableStashBufferForFlv: false,
stashInitialSizeForFlv: 128
}
```
You can override these values directly in `options`.
### Live Time Shift
```ts
playerRef.value?.setLiveTimeRange('2026/05/23 10:00:00', '2026/05/23 12:00:00');
```
You can also pass official time-shift options during initialization:
```vue
<VueAliplayerV2
source="//example.com/live.m3u8"
:options="{
isLive: true,
liveStartTime: '2026/05/23 10:00:00',
liveOverTime: '2026/05/23 12:00:00'
}"
/>
```
### Multiple Players
```vue
<template>
<VueAliplayerV2
v-for="item in sources"
:key="item"
:source="item"
:options="options"
/>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
const sources = [
'//player.alicdn.com/video/aliyunmedia.mp4',
'//yunqivedio.alicdn.com/user-upload/nXPDX8AASx.mp4'
];
const options = reactive({
autoplay: false,
width: '100%',
height: '300px'
});
</script>
```
v2 reuses SDK CSS/JS loading promises, so multiple players do not insert duplicate SDK tags for the same assets.
### Custom Components and Marquees
Aliplayer custom components often require extra scripts. Use `componentScripts` to load extension scripts before player initialization.
```vue
<VueAliplayerV2
:source="source"
:component-scripts="['/aliplayer-components/marquee.js']"
:options="{
components: [
{
name: 'MarqueeComponent',
type: window.MarqueeComponent,
args: {
text: 'vue-aliplayer-v2'
}
}
]
}"
/>
```
Playlist, watermark, marquee, and similar features depend on the Aliplayer custom component scripts you provide.
### Forbid Fast Forward
```vue
<VueAliplayerV2
:source="source"
forbid-fast-forward
/>
```
This is implemented by watching playback progress and seeking back when a forward jump is detected. It is useful as a lightweight business restriction. Stronger anti-bypass requirements should be combined with server-side authorization, preview limits, or encrypted playback.
### Retry After Failure
Aliplayer error pages are rendered by the SDK itself. Business code can listen to `error` and call `retry()` or `reload()` for local recovery.
```ts
function handleError() {
playerRef.value?.retry();
}
```
### Optional Tracking Request Blocking
```vue
<VueAliplayerV2
:source="source"
disable-tracking
:tracking-url-patterns="['newplayer/track', /\/logstores\//]"
/>
```
This only blocks URLs matching the configured rules and is intended as a wrapper-level fallback. In production, prefer official Aliyun settings, console-side configuration, or your own compliant analytics strategy.
## SSR and Nuxt
The Aliplayer SDK depends on browser `window` and DOM APIs. In SSR projects, render the player only on the client. For Nuxt:
```vue
<ClientOnly>
<VueAliplayerV2 :source="source" />
</ClientOnly>
```
## TypeScript
The package entry exports these types:
```ts
import type {
AliplayerEventName,
AliplayerInstance,
AliplayerLicense,
AliplayerOptions,
AliplayerV2Props,
VueAliplayerV2Expose,
VueAliplayerV2Options
} from 'vue-aliplayer-v2';
```
## Migration
### From 1.x to 2.x
- v2 supports Vue 3 only and no longer keeps Vue 2 compatibility.
- The build toolchain is Vite, with ESM and UMD library outputs.
- Global registration now uses Vue 3 `app.use(VueAliplayerV2, options)`.
- Component refs should use `ref<VueAliplayerV2Expose | null>`.
- The default SDK path now uses Aliyun `imp-web-player` 2.37.0.
- New configs include `license`, `lowLatency`, `normalizeSourceUrl`, `componentScripts`, and `disableTracking`.
- For legacy Vue 2 projects, pin `vue-aliplayer-v2@1.x`.
## FAQ
### The player is not visible
Make sure the player is rendered only in the browser and check the `sdk-error` event. Common causes include blocked SDK assets, missing License config, source CORS issues, and a container height of `0`.
### The new SDK reports a License error
Apply for a Web Player License in the Aliyun console and pass it through the `license` prop or `options.license`:
```vue
<VueAliplayerV2
:source="source"
:license="{ domain: 'example.com', key: 'your-license-key' }"
/>
```
### URLs with Chinese characters or spaces fail to play
`normalizeSourceUrl = true` runs `encodeURI` on source URLs by default. If your business code already encodes URLs, disable it explicitly:
```vue
<VueAliplayerV2
:source="source"
:normalize-source-url="false"
/>
```
### Should I update `source` or call `loadByUrl` when switching sources?
For normal Vue usage, update `source`. The component handles same-format and cross-format changes. For imperative workflows, call `playerRef.value?.loadByUrl(url)`.
## Local Development
```bash
npm install
npm run dev
```
Type check:
```bash
npm run type-check
```
Build the demo:
```bash
npm run build
```
Build the library:
```bash
npm run lib
```
## License
[MIT](./LICENSE)

551
README.md
View File

@ -1,25 +1,47 @@
# vue-aliplayer-v2
基于阿里云 Aliplayer SDK 的 Vue 3 播放器组件。可通过 Aliplayer 官方能力播放 mp4、m3u8、flv、直播流、加密点播、清晰度切换和直播时移等场景。
[English](./README.en-US.md) | 简体中文
> v2 从 Vue 3 + Vite 重构开始,不再兼容 Vue 2。Vue 2 项目请继续安装 `vue-aliplayer-v2@1.x`
基于阿里云 Web 播放器 Aliplayer SDK 的 Vue 3 播放器组件。v2 使用 Vue 3、TypeScript 和 Vite 重构,支持 URL 播放、VID + PlayAuth、STS、直播、低延迟 FLV、自动格式推断、License 注入、扩展组件脚本、多实例和常用播放器实例方法。
> v2 不再兼容 Vue 2。Vue 2 项目请继续安装 `vue-aliplayer-v2@1.x`
## 在线演示
- 项目演示https://langyuxiansheng.github.io/vue-aliplayer-v2/
- 阿里云播放器演示https://player.alicdn.com/aliplayer/index.html
- 项目 demohttps://langyuxiansheng.github.io/vue-aliplayer-v2/
- 阿里云播放器官方演示https://player.alicdn.com/aliplayer/index.html
- 阿里云播放器接入文档https://help.aliyun.com/zh/vod/developer-reference/integration
## 特性
- Vue 3 组件和插件安装方式,支持 `app.use(VueAliplayerV2, options)`
- TypeScript 类型导出,包含 props、options、事件、播放器实例和 ref 暴露方法。
- 默认加载阿里云 `imp-web-player` SDK 2.37.0,也可以自定义 CSS/JS 地址。
- 支持 `source` URL、`options.source`、VID + PlayAuth、STS 鉴权配置。
- 自动识别 `mp4``m3u8``flv``mp3``rtmp` 播放格式。
- FLV 直播低延迟预设,可按业务需要覆盖 stash buffer 配置。
- 支持 License 配置,适配新版 Aliplayer Web 播放器要求。
- 支持多播放器共存SDK 资源只加载一次。
- 支持额外加载自定义组件脚本,便于接入播放列表、水印、跑马灯等 Aliplayer 扩展。
- 提供可选的已知 track 上报拦截能力。
## 安装
```bash
npm i vue-aliplayer-v2
npm install vue-aliplayer-v2
```
```bash
yarn add vue-aliplayer-v2
```
## 全局注册
```bash
pnpm add vue-aliplayer-v2
```
## 快速开始
### 全局注册
```ts
import { createApp } from 'vue';
@ -29,17 +51,22 @@ import VueAliplayerV2 from 'vue-aliplayer-v2';
const app = createApp(App);
app.use(VueAliplayerV2, {
// 默认使用阿里云 Web 播放器 SDK 2.37.0,新版 SDK 走 imp-web-player 资源路径
sdkVersion: '2.37.0',
// 可选:覆盖完整 SDK 地址
cssLink: 'https://g.alicdn.com/apsara-media-box/imp-web-player/2.37.0/skins/default/aliplayer-min.css',
scriptSrc: 'https://g.alicdn.com/apsara-media-box/imp-web-player/2.37.0/aliplayer-min.js'
sdkVersion: '2.37.0'
});
app.mount('#app');
```
## 局部注册
```vue
<template>
<VueAliplayerV2
source="//player.alicdn.com/video/aliyunmedia.mp4"
:options="{ autoplay: true, useH5Prism: true }"
/>
</template>
```
### 局部引入
```vue
<template>
@ -51,6 +78,7 @@ app.mount('#app');
low-latency
@ready="handleReady"
@error="handleError"
@sdk-error="handleSdkError"
/>
</template>
@ -63,12 +91,22 @@ import VueAliplayerV2, {
} from 'vue-aliplayer-v2';
const playerRef = ref<VueAliplayerV2Expose | null>(null);
const source = ref('//player.alicdn.com/video/aliyunmedia.mp4');
const license = ref<AliplayerLicense | null>(null);
const license = ref<AliplayerLicense | null>({
domain: 'example.com',
key: 'your-license-key'
});
const options = reactive<AliplayerOptions>({
autoplay: true,
isLive: false,
useH5Prism: true
useH5Prism: true,
playsinline: true,
width: '100%',
height: '420px',
controlBarVisibility: 'hover'
});
function handleReady() {
@ -78,114 +116,255 @@ function handleReady() {
function handleError(error: unknown) {
console.log('player error', error);
}
function handleSdkError(error: Error) {
console.log('sdk load error', error.message);
}
</script>
```
## Vue 插件配置
`app.use(VueAliplayerV2, options)` 会设置全局默认值。单个组件传入同名 prop 时,会覆盖全局默认值。
```ts
app.use(VueAliplayerV2, {
sdkVersion: '2.37.0',
cssLink: 'https://g.alicdn.com/apsara-media-box/imp-web-player/2.37.0/skins/default/aliplayer-min.css',
scriptSrc: 'https://g.alicdn.com/apsara-media-box/imp-web-player/2.37.0/aliplayer-min.js',
componentScripts: ['/aliplayer-components/watermark.js'],
disableTracking: false,
trackingUrlPatterns: ['newplayer/track']
});
```
| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `sdkVersion` | `string` | `2.37.0` | 阿里云 Web 播放器 SDK 版本。未传 `cssLink``scriptSrc` 时用于生成官方资源地址。 |
| `cssLink` | `string` | 2.37.0 官方 CSS | 自定义 Aliplayer CSS 地址。 |
| `scriptSrc` | `string` | 2.37.0 官方 JS | 自定义 Aliplayer JS 地址。 |
| `componentScripts` | `string[]` | `[]` | 自定义组件或业务扩展脚本地址列表。 |
| `disableTracking` | `boolean` | `false` | 是否启用已知 Aliplayer 统计上报拦截。 |
| `trackingUrlPatterns` | `Array<string \| RegExp>` | `['newplayer/track']` | 需要拦截的统计上报 URL 片段或正则。 |
## Props
| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `source` | `string \| null` | `null` | 播放源。存在时优先于 `options.source`,变更后会调用底层 `loadByUrl` 动态切换。 |
| `options` | `AliplayerOptions \| null` | `null` | 透传给 Aliplayer 的配置项。 |
| `license` | `AliplayerLicense \| null` | `null` | Aliplayer License 配置,等价于 `options.license`,适配新版 SDK 要求。 |
| `autoFormat` | `boolean` | `true` | 根据 `source` 后缀自动补充 `format`,支持 `mp4/m3u8/flv/mp3/rtmp`。 |
| `lowLatency` | `boolean` | `false` | FLV 直播低延迟预设,会在 `isLive + flv` 时默认关闭 stash buffer。 |
| `normalizeSourceUrl` | `boolean` | `true` | 自动对非 ASCII 播放地址执行 `encodeURI`,处理中文文件名等 URL。 |
| `forbidFastForward` | `boolean` | `false` | 禁止拖拽快进。通过监听 `timeupdate` 回退到上次播放位置实现。 |
| `sdkVersion` | `string` | `2.37.0` | 生成阿里云 `imp-web-player` SDK 资源地址。 |
| `cssLink` | `string` | Aliplayer 2.37.0 CSS | 覆盖播放器样式地址。 |
| `scriptSrc` | `string` | Aliplayer 2.37.0 JS | 覆盖播放器脚本地址。 |
| `componentScripts` | `string[]` | `[]` | 额外加载自定义组件脚本,需在播放器初始化前加载。 |
| `disableTracking` | `boolean` | `false` | 可选拦截 Aliplayer 已知 track 上报请求。 |
| `trackingUrlPatterns` | `Array<string \| RegExp>` | `[]` | 自定义需要拦截的 track URL 片段或正则。 |
| `source` | `string \| null` | `null` | 播放源 URL。存在时优先于 `options.source`。同格式变更优先调用 `loadByUrl`,跨格式变更会重建播放器。 |
| `options` | `AliplayerOptions \| null` | `null` | 透传给 Aliplayer 的初始化配置。组件会补充 `id``source``license``format` 和低延迟默认值。 |
| `license` | `AliplayerLicense \| null` | `null` | Aliplayer License 配置,优先级高于 `options.license`。 |
| `autoFormat` | `boolean` | `true` | 是否根据 `source` 自动推断 `format`。 |
| `lowLatency` | `boolean` | `false` | 是否启用 FLV 直播低延迟预设。 |
| `normalizeSourceUrl` | `boolean` | `true` | 是否对播放源执行 `encodeURI`,用于处理中文、空格等未编码地址。 |
| `forbidFastForward` | `boolean` | `false` | 是否禁止拖拽快进。 |
| `sdkVersion` | `string` | 全局配置 | 覆盖当前组件使用的 SDK 版本。 |
| `cssLink` | `string` | 全局配置 | 覆盖当前组件使用的 CSS 地址。 |
| `scriptSrc` | `string` | 全局配置 | 覆盖当前组件使用的 JS 地址。 |
| `componentScripts` | `string[]` | 全局配置 | 当前组件初始化前需要加载的扩展脚本。 |
| `disableTracking` | `boolean` | 全局配置 | 当前组件是否启用 track 拦截。 |
| `trackingUrlPatterns` | `Array<string \| RegExp>` | 全局配置 | 当前组件的 track 拦截规则。 |
## Events
## AliplayerOptions 常用字段
组件会透传常用 Aliplayer 事件:
`options` 会完整透传给 Aliplayer SDK因此可以继续使用官方文档中的配置项。组件类型中内置了以下常用字段并保留索引签名支持官方新增字段。
```ts
ready
play
pause
canplay
playing
ended
liveStreamStop
onM3u8Retry
hideBar
showBar
waiting
timeupdate
snapshoted
requestFullScreen
cancelFullScreen
error
startSeek
completeSeek
sdk-error
```
| 名称 | 类型 | 说明 |
| --- | --- | --- |
| `source` | `string` | 播放源 URL。 |
| `width` | `string` | 播放器宽度,例如 `100%`。 |
| `height` | `string` | 播放器高度,例如 `420px`。 |
| `autoplay` | `boolean` | 是否自动播放。 |
| `isLive` | `boolean` | 是否为直播。 |
| `format` | `string` | 播放格式,例如 `mp4``m3u8``flv`。 |
| `license` | `AliplayerLicense` | Aliplayer License 配置。 |
| `vid` | `string` | 点播视频 ID。 |
| `playauth` | `string` | 点播播放凭证。 |
| `authTimeout` | `number` | 播放地址有效时长,单位秒。 |
| `region` | `string` | STS 媒资地域,例如 `cn-shanghai`。 |
| `accessKeyId` | `string` | STS 临时 AccessKey ID。 |
| `accessKeySecret` | `string` | STS 临时 AccessKey Secret。 |
| `securityToken` | `string` | STS 安全令牌。 |
| `components` | `unknown[]` | Aliplayer 自定义组件配置。 |
| `enableStashBufferForFlv` | `boolean` | FLV 是否启用 stash buffer。 |
| `stashInitialSizeForFlv` | `number` | FLV 初始缓存大小。 |
| `rtsVersion` | `string` | RTS SDK 版本。 |
`sdk-error` 是组件额外事件,在 Aliplayer SDK 脚本加载失败时触发。
## 事件
组件会透传常用 Aliplayer 事件,并额外提供 `sdk-error`
| 事件名 | 参数 | 说明 |
| --- | --- | --- |
| `ready` | `unknown` | 播放器初始化完成。 |
| `play` | `unknown` | 开始播放。 |
| `pause` | `unknown` | 暂停播放。 |
| `canplay` | `unknown` | 可以播放。 |
| `playing` | `unknown` | 正在播放。 |
| `ended` | `unknown` | 播放结束。 |
| `liveStreamStop` | `unknown` | 直播流停止。 |
| `onM3u8Retry` | `unknown` | M3U8 重试事件。 |
| `hideBar` | `unknown` | 控制栏隐藏。 |
| `showBar` | `unknown` | 控制栏显示。 |
| `waiting` | `unknown` | 播放等待。 |
| `timeupdate` | `unknown` | 播放进度更新。 |
| `snapshoted` | `unknown` | 截图完成。 |
| `requestFullScreen` | `unknown` | 进入全屏。 |
| `cancelFullScreen` | `unknown` | 退出全屏。 |
| `error` | `unknown` | 播放器运行时错误。 |
| `startSeek` | `unknown` | 开始 seek。 |
| `completeSeek` | `unknown` | seek 完成。 |
| `sdk-error` | `Error` | SDK CSS、JS 或扩展脚本加载失败。 |
## Ref 方法
通过 `ref<VueAliplayerV2Expose | null>` 可以访问组件暴露的方法。
```ts
playerRef.value?.play();
playerRef.value?.pause();
playerRef.value?.replay();
playerRef.value?.seek(30);
playerRef.value?.getCurrentTime();
playerRef.value?.getDuration();
playerRef.value?.getVolume();
playerRef.value?.setVolume(0.8);
playerRef.value?.loadByUrl('//player.alicdn.com/video/aliyunmedia.mp4');
playerRef.value?.getStatus();
playerRef.value?.requestFullScreen();
playerRef.value?.cancelFullScreen();
playerRef.value?.dispose();
playerRef.value?.getPlayer();
playerRef.value?.retry();
```
完整暴露方法包括:
| 方法 | 说明 |
| --- | --- |
| `getPlayer()` | 获取底层 Aliplayer 实例。 |
| `init()` | 加载 SDK 并初始化播放器。 |
| `initPlayer()` | 使用当前 props/options 创建播放器实例。 |
| `reload(nextSource?)` | 重载播放器,可传入新的播放源。 |
| `retry(nextSource?)` | 播放失败后的业务重试入口。 |
| `play()` | 播放。 |
| `pause()` | 暂停。 |
| `replay()` | 从头重播。 |
| `seek(time)` | 跳转到指定秒数。 |
| `getCurrentTime()` | 获取当前播放时间。 |
| `getDuration()` | 获取视频总时长。 |
| `getVolume()` | 获取音量。 |
| `setVolume(volume)` | 设置音量。 |
| `loadByUrl(url, time?)` | 使用 URL 切换播放源。 |
| `replayByVidAndPlayAuth(vid, playauth)` | 使用 VID + PlayAuth 重新播放。 |
| `replayByVidAndAuthInfo(...)` | 使用 MPS 鉴权信息重新播放。 |
| `setPlayerSize(width, height)` | 设置播放器尺寸。 |
| `setSpeed(speed)` | 设置播放倍速。 |
| `setSanpshotProperties(width, height, rate)` | 设置截图参数。方法名沿用阿里云 SDK 拼写。 |
| `requestFullScreen()` | 进入全屏。 |
| `cancelFullScreen()` | 退出全屏。 |
| `getIsFullScreen()` | 获取是否处于全屏。 |
| `getStatus()` | 获取播放器状态。 |
| `setLiveTimeRange(beginTime, endTime)` | 设置直播时移可播放范围。 |
| `setRotate(rotate)` | 设置视频旋转角度。 |
| `getRotate()` | 获取视频旋转角度。 |
| `setImage(image)` | 设置视频镜像。 |
| `dispose()` | 销毁播放器。 |
| `setCover(cover)` | 设置封面图。 |
| `setProgressMarkers(markers)` | 设置进度条打点。 |
| `setPreviewTime(time)` | 设置试看时间。 |
| `getPreviewTime()` | 获取试看时间。 |
| `isPreview()` | 判断是否处于试看状态。 |
| `off(eventName, handler)` | 取消监听底层播放器事件。 |
## 使用示例
### URL 播放
```vue
<VueAliplayerV2
source="//player.alicdn.com/video/aliyunmedia.mp4"
:options="{
autoplay: true,
useH5Prism: true,
width: '100%',
height: '420px'
}"
/>
```
### VID + PlayAuth
```vue
<VueAliplayerV2
:options="{
vid: '<your-video-id>',
playauth: '<your-playauth>',
authTimeout: 7200,
width: '100%',
height: '420px'
}"
/>
```
`VID + PlayAuth` 是初始化配置,不需要再传 `source`。如果要在播放器创建后切换,可以调用:
```ts
getPlayer
init
initPlayer
reload
retry
play
pause
replay
seek
getCurrentTime
getDuration
getVolume
setVolume
loadByUrl
replayByVidAndPlayAuth
replayByVidAndAuthInfo
setPlayerSize
setSpeed
setSanpshotProperties
requestFullScreen
cancelFullScreen
getIsFullScreen
getStatus
setLiveTimeRange
setRotate
getRotate
setImage
dispose
setCover
setProgressMarkers
setPreviewTime
getPreviewTime
isPreview
off
playerRef.value?.replayByVidAndPlayAuth(vid, playauth);
```
## 多播放器
### 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>',
width: '100%',
height: '420px'
}"
/>
```
### FLV 直播和低延迟
```vue
<VueAliplayerV2
source="//example.com/live.flv"
low-latency
:options="{
isLive: true,
autoplay: true,
width: '100%',
height: '420px'
}"
/>
```
开启 `low-latency` 后,如果当前播放源格式为 `flv``options.isLive = true`,组件会默认补充:
```ts
{
enableStashBufferForFlv: false,
stashInitialSizeForFlv: 128
}
```
如果业务需要其他缓存策略,可以直接在 `options` 中覆盖。
### 直播时移
```ts
playerRef.value?.setLiveTimeRange('2026/05/23 10:00:00', '2026/05/23 12:00:00');
```
也可以在初始化时传入官方支持的直播时移配置:
```vue
<VueAliplayerV2
source="//example.com/live.m3u8"
:options="{
isLive: true,
liveStartTime: '2026/05/23 10:00:00',
liveOverTime: '2026/05/23 12:00:00'
}"
/>
```
### 多播放器
```vue
<template>
@ -196,71 +375,63 @@ off
:options="options"
/>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
const sources = [
'//player.alicdn.com/video/aliyunmedia.mp4',
'//yunqivedio.alicdn.com/user-upload/nXPDX8AASx.mp4'
];
const options = reactive({
autoplay: false,
width: '100%',
height: '300px'
});
</script>
```
v2 的 SDK 加载器会复用同一份 CSS/JS 资源,多个播放器同时挂载时不会重复插入相同的 SDK 标签。
v2 会复用相同的 SDK CSS/JS 加载 Promise多实例挂载时不会重复插入同一份 SDK 标签。
## License
### 自定义组件和跑马灯
阿里云新版 Web 播放器 SDK 要求配置网站域名和 License Key。可以通过 `license` prop 或 `options.license` 传入:
Aliplayer 的自定义组件通常需要额外脚本。可以通过 `componentScripts` 保证扩展脚本在播放器初始化前加载。
```vue
<VueAliplayerV2
:source="source"
:license="{ domain: 'example.com', key: 'example-key' }"
/>
```
## VID + PlayAuth
```vue
<VueAliplayerV2
:options="{
vid: '<your video ID>',
playauth: '<your PlayAuth>',
authTimeout: 7200
}"
/>
```
`vid + playauth` 是初始化配置,不需要再传 `source`。如果要在播放器创建后切换,可以调用:
```ts
playerRef.value?.replayByVidAndPlayAuth(vid, playauth);
```
## FLV/HLS/RTMP 说明
- FLV 直播请设置 `options.isLive = true`,组件会自动推断 `format: 'flv'`
- 开启 `low-latency`FLV 直播默认使用 `enableStashBufferForFlv: false``stashInitialSizeForFlv: 128`,可在 `options` 中覆盖。
- m3u8、flv、mp4 跨格式切换时v2 会自动重建播放器;同格式切换优先调用 `loadByUrl`
- RTMP 在现代浏览器 H5 模式下不再可靠,建议服务端转 HLS/FLV/RTS。
## 自定义组件和跑马灯
Aliplayer 自定义组件需要额外加载组件脚本,然后通过 `options.components` 注入。组件提供 `componentScripts` 保证脚本在播放器初始化前加载:
```vue
<VueAliplayerV2
:source="source"
:component-scripts="['/aliplayer-components/playlist.js']"
:component-scripts="['/aliplayer-components/marquee.js']"
:options="{
components: [
{
name: 'PlaylistComponent',
type: window.PlaylistComponent,
args: { list: playlist }
name: 'MarqueeComponent',
type: window.MarqueeComponent,
args: {
text: 'vue-aliplayer-v2'
}
}
]
}"
/>
```
跑马灯、水印、播放列表等能力是否可用取决于所加载的 Aliplayer 自定义组件脚本。
播放列表、水印、跑马灯等能力是否可用取决于你加载的 Aliplayer 自定义组件脚本。
## 失败重试
### 禁止快进
播放器错误页属于 Aliplayer SDK 内部 UI。业务侧可以监听 `error` 后使用组件暴露的 `retry()``reload()` 做局部重试:
```vue
<VueAliplayerV2
:source="source"
forbid-fast-forward
/>
```
该能力通过监听播放进度并回退异常快进行为实现,适合作为轻量业务限制。更强的防绕过需求应结合服务端鉴权、试看、加密播放等方案。
### 失败重试
播放器错误页属于 Aliplayer SDK 内部 UI。业务侧可以监听 `error` 后调用 `retry()``reload()` 做局部重试。
```ts
function handleError() {
@ -268,13 +439,101 @@ function handleError() {
}
```
## 开发
### 可选拦截 track 上报
```vue
<VueAliplayerV2
:source="source"
disable-tracking
:tracking-url-patterns="['newplayer/track', /\/logstores\//]"
/>
```
该能力只拦截已知 URL 规则,属于 wrapper 层兜底方案。生产项目优先使用阿里云官方配置、控制台设置或合规的业务上报策略。
## SSR 和 Nuxt 说明
Aliplayer SDK 依赖浏览器环境中的 `window` 和 DOM。SSR 项目中请只在客户端渲染播放器,例如 Nuxt 中使用 `<ClientOnly>`
```vue
<ClientOnly>
<VueAliplayerV2 :source="source" />
</ClientOnly>
```
## TypeScript
包入口导出以下类型:
```ts
import type {
AliplayerEventName,
AliplayerInstance,
AliplayerLicense,
AliplayerOptions,
AliplayerV2Props,
VueAliplayerV2Expose,
VueAliplayerV2Options
} from 'vue-aliplayer-v2';
```
## 版本迁移
### 从 1.x 升级到 2.x
- v2 仅支持 Vue 3不再考虑 Vue 2 向下兼容。
- 构建工具迁移为 Vite库产物输出为 ESM 和 UMD。
- 全局注册改为 Vue 3 `app.use(VueAliplayerV2, options)`
- 组件 ref 建议使用 `ref<VueAliplayerV2Expose | null>`
- 默认 SDK 升级为阿里云 `imp-web-player` 2.37.0 路径。
- 新增 `license``lowLatency``normalizeSourceUrl``componentScripts``disableTracking` 等配置。
- 旧项目如需继续使用 Vue 2 版本,请固定安装 `vue-aliplayer-v2@1.x`
## 常见问题
### 播放器没有显示
先确认页面只在浏览器端渲染,并检查 `sdk-error` 事件。常见原因包括 SDK 地址被拦截、License 未配置、播放源跨域、容器高度为 0。
### 新版 SDK 提示 License 问题
请到阿里云控制台申请 Web 播放器 License并通过 `license` prop 或 `options.license` 传入:
```vue
<VueAliplayerV2
:source="source"
:license="{ domain: 'example.com', key: 'your-license-key' }"
/>
```
### URL 中有中文或空格播放失败
默认 `normalizeSourceUrl = true` 会对播放源执行 `encodeURI`。如果你的业务已经完成 URL 编码,可以显式关闭:
```vue
<VueAliplayerV2
:source="source"
:normalize-source-url="false"
/>
```
### 切换播放源时应该用 `source` 还是 `loadByUrl`
普通 Vue 场景优先更新 `source`。组件会自动判断同格式和跨格式切换。命令式场景可以调用 `playerRef.value?.loadByUrl(url)`
## 本地开发
```bash
npm install
npm run dev
```
类型检查:
```bash
npm run type-check
```
构建 demo
```bash
@ -284,13 +543,9 @@ npm run build
构建组件库:
```bash
npm run type-check
npm run lib
```
## 迁移说明
## License
- v2 仅支持 Vue 3。
- 入口从 Vue 2 插件模式迁移为 Vue 3 `app.use(...)`
- 组件 ref 需要通过 `ref<VueAliplayerV2Expose | null>` 使用。
- 旧版本用户请安装 `vue-aliplayer-v2@1.x`
[MIT](./LICENSE)

File diff suppressed because one or more lines are too long

1
dist/assets/index-D3stZ_L4.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/index.html vendored
View File

@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/vue-aliplayer-v2/favicon.ico">
<title>vue-aliplayer-v2</title>
<script type="module" crossorigin src="/vue-aliplayer-v2/assets/index-Cx06WFub.js"></script>
<link rel="stylesheet" crossorigin href="/vue-aliplayer-v2/assets/index-BwNqcy-4.css">
<script type="module" crossorigin src="/vue-aliplayer-v2/assets/index-DqwC15gS.js"></script>
<link rel="stylesheet" crossorigin href="/vue-aliplayer-v2/assets/index-D3stZ_L4.css">
</head>
<body>
<div id="app"></div>

View File

@ -417,43 +417,198 @@ import { computed, reactive, ref } from 'vue';
import VueAliplayerV2, { type AliplayerLicense, type AliplayerOptions, type VueAliplayerV2Expose } from '../packages';
import { inferSourceFormat } from '../packages/AliplayerV2/source';
/**
* Demo 支持的播放源输入方式
*
* - `url`直接传入 URL覆盖 MP4M3U8FLVRTMP 等普通场景
* - `vid`使用阿里云 VOD VID + PlayAuth 初始化
* - `sts`使用阿里云 VOD STS 临时凭证初始化
*/
type SourceMode = 'url' | 'vid' | 'sts';
/**
* 右侧参数面板的分组值
*
* 该类型让 tab 切换配置区渲染和默认状态保持同步新增面板时需要同步扩展
*/
type TabValue = 'source' | 'basic' | 'live' | 'sdk' | 'skin';
/**
* 播放器事件流中的单条日志
*
* 日志只用于 demo 展示不参与播放器状态机`id` 保证 Vue 列表渲染稳定
*/
type LogItem = {
id: string;
time: string;
message: string;
};
/**
* 单实例播放器引用
*
* 多实例模式下不绑定 ref避免示例按钮对多个播放器同时发出指令造成歧义
*/
const playerRef = ref<VueAliplayerV2Expose | null>(null);
/**
* 是否挂载播放器组件
*
* 用于验证组件反复创建销毁时 SDK 实例和 DOM 容器能否正确释放
*/
const show = ref(true);
/**
* 是否进入多实例验证模式
*
* 主要用于复现历史 issue 中多个播放器共存时容器 ID事件绑定和实例销毁的问题
*/
const isMultiple = ref(false);
/**
* 当前打开的右侧参数面板
*/
const activeTab = ref<TabValue>('source');
/**
* 当前播放源模式
*/
const sourceMode = ref<SourceMode>('url');
/**
* URL 模式下选择的预设播放源
*/
const selectedPreset = ref('//player.alicdn.com/video/aliyunmedia.mp4');
/**
* URL 模式下输入的自定义播放源
*
* 有值时优先级高于 `selectedPreset`便于快速粘贴用户自己的直播或点播地址
*/
const customSource = ref('');
/**
* demo 顶部展示的播放器状态
*/
const playerStatus = ref('init');
/**
* 最近播放器事件列表
*/
const logs = ref<LogItem[]>([]);
/**
* 当前加载的 Aliplayer SDK 版本
*
* 默认跟随阿里云官方文档当前推荐的 2.37.0用户可在 demo 中验证旧版或新版 SDK
*/
const sdkVersion = ref('2.37.0');
/**
* 是否自动根据 source URL 后缀推断 `format`
*/
const autoFormat = ref(true);
/**
* 是否启用低延迟直播相关默认项
*/
const lowLatency = ref(true);
/**
* 是否在传给 SDK 前对播放源做安全编码
*/
const normalizeSourceUrl = ref(true);
/**
* 是否禁止快进
*/
const forbidFastForward = ref(false);
/**
* 是否拦截 SDK 的埋点请求
*/
const disableTracking = ref(false);
/**
* 每行一个自定义组件脚本地址
*
* 组件会在 SDK 主脚本加载完成后顺序加载这些扩展脚本
*/
const componentScriptsText = ref('');
/**
* 手动指定的播放格式
*
* 留空时交给组件根据 `autoFormat` URL 自动推断
*/
const manualFormat = ref('');
/**
* FLV stash buffer 开关
*
* `auto` 表示不覆盖 SDK 默认值`on` `off` 分别写入布尔值
*/
const flvStash = ref<'auto' | 'on' | 'off'>('auto');
/**
* FLV 首包缓冲大小单位沿用 Aliplayer SDK
*/
const stashInitialSizeForFlv = ref(128);
/**
* RTS 插件版本号
*
* 仅在项目需要阿里云超低延迟 RTS 直播时填写
*/
const rtsVersion = ref('');
/**
* 直播时移开始时间
*/
const liveStartTime = ref('');
/**
* 直播时移结束时间
*/
const liveOverTime = ref('');
/**
* 皮肤布局预设
*/
const skinPreset = ref('center');
/**
* 试看时长单位为秒
*/
const previewTime = ref(0);
/**
* 进度条打点输入使用逗号分隔秒数
*/
const progressMarkersText = ref('10,30,60');
/**
* 透传给 Aliplayer 的自定义 JSON 配置
*
* 这里保留文本输入是为了让 demo 能覆盖未显式建表单的 SDK 参数
*/
const customOptionsJson = ref('{\n "diagnosisButtonVisible": true\n}');
/**
* 自定义 JSON 解析失败时的错误文案
*/
const customOptionsError = ref('');
/**
* 日志递增种子
*
* 与时间戳拼接后生成列表 key避免同一毫秒内多条事件冲突
*/
let logSeed = 0;
/**
* 右侧参数面板定义
*/
const tabs: Array<{ label: string; value: TabValue }> = [
{ label: '源', value: 'source' },
{ label: '基础', value: 'basic' },
@ -462,18 +617,29 @@ const tabs: Array<{ label: string; value: TabValue }> = [
{ label: '皮肤', value: 'skin' }
];
/**
* 播放源模式切换项
*/
const sourceModes: Array<{ label: string; value: SourceMode }> = [
{ label: 'URL', value: 'url' },
{ label: 'VID', value: 'vid' },
{ label: 'STS', value: 'sts' }
];
/**
* URL 模式下的演示源
*
* 保留多个源用于验证 URL 规范化格式推断和 reload 行为
*/
const sourcePresets = [
{ label: 'MP4 演示源', value: '//player.alicdn.com/video/aliyunmedia.mp4' },
{ label: 'MP4 备用源', value: '//yunqivedio.alicdn.com/user-upload/nXPDX8AASx.mp4' },
{ label: 'M3U8 直播源', value: '//ivi.bupt.edu.cn/hls/cctv1.m3u8' }
];
/**
* 多实例模式下使用的播放源列表
*/
const multipleSources = [
'//player.alicdn.com/video/aliyunmedia.mp4',
'//yunqivedio.alicdn.com/user-upload/nXPDX8AASx.mp4',
@ -481,6 +647,11 @@ const multipleSources = [
'//yunqivedio.alicdn.com/user-upload/nXPDX8AASx.mp4'
];
/**
* demo 可视化表单中的基础 Aliplayer 配置
*
* 该对象会被合并进最终 `playerOptions` SDK 参数名保持一致便于直接复制
*/
const baseOptions = reactive<AliplayerOptions>({
width: '100%',
height: '420px',
@ -499,17 +670,28 @@ const baseOptions = reactive<AliplayerOptions>({
cover: ''
});
/**
* 阿里云 Web 播放器 License 表单
*
* `domain` `key` 都填写后才会注入到组件避免空字符串覆盖全局配置
*/
const licenseForm = reactive<AliplayerLicense>({
domain: '',
key: ''
});
/**
* VID + PlayAuth 初始化表单
*/
const vidConfig = reactive({
vid: '',
playauth: '',
authTimeout: 7200
});
/**
* STS 初始化表单
*/
const stsConfig = reactive({
vid: '',
region: 'cn-shanghai',
@ -518,13 +700,34 @@ const stsConfig = reactive({
securityToken: ''
});
/**
* 当前播放源模式的展示文案
*/
const sourceModeLabel = computed(() => sourceModes.find((item) => item.value === sourceMode.value)?.label || 'URL');
/**
* 当前 URL 播放源
*
* URL 模式下返回 `null`这样组件不会同时收到 `source` VID/STS 参数
*/
const currentSource = computed(() => {
if (sourceMode.value !== 'url') return null;
return customSource.value.trim() || selectedPreset.value;
});
/**
* 当前 URL 播放源的自动格式推断结果
*/
const inferredFormat = computed(() => inferSourceFormat(currentSource.value));
/**
* 自定义组件脚本列表
*/
const componentScriptList = computed(() => componentScriptsText.value.split('\n').map((item) => item.trim()).filter(Boolean));
/**
* 组件最终接收的 License 配置
*/
const resolvedLicense = computed(() => {
if (!licenseForm.domain.trim() || !licenseForm.key.trim()) return null;
return {
@ -533,6 +736,11 @@ const resolvedLicense = computed(() => {
};
});
/**
* 根据皮肤预设生成 SDK `skinLayout`
*
* `undefined` 表示不覆盖 SDK 默认布局`false` 表示显式隐藏默认皮肤
*/
const skinLayout = computed(() => {
if (skinPreset.value === 'default') return undefined;
if (skinPreset.value === 'hidden') return false;
@ -556,6 +764,9 @@ const skinLayout = computed(() => {
return [{ name: 'bigPlayButton', align: 'cc' }];
});
/**
* 将逗号分隔的秒数转换成 Aliplayer 进度条打点配置
*/
const progressMarkers = computed(() => progressMarkersText.value
.split(',')
.map((item) => Number(item.trim()))
@ -565,6 +776,11 @@ const progressMarkers = computed(() => progressMarkersText.value
text: `Marker ${index + 1}`
})));
/**
* 解析用户输入的自定义 JSON 配置
*
* 解析失败时返回空对象同时把错误信息展示在 demo 表单下方避免无效 JSON 中断渲染
*/
const parsedCustomOptions = computed<Record<string, unknown>>(() => {
customOptionsError.value = '';
if (!customOptionsJson.value.trim()) return {};
@ -577,6 +793,11 @@ const parsedCustomOptions = computed<Record<string, unknown>>(() => {
}
});
/**
* 汇总组件当前使用的 Aliplayer options
*
* 这里有意只写入有效值避免空字符串或自动态字段覆盖 SDK 默认行为
*/
const playerOptions = computed<AliplayerOptions>(() => {
const options: AliplayerOptions = {
...baseOptions,
@ -611,6 +832,9 @@ const playerOptions = computed<AliplayerOptions>(() => {
return options;
});
/**
* 可复制的完整 demo 配置快照
*/
const formattedOptions = computed(() => JSON.stringify({
source: currentSource.value,
options: playerOptions.value,
@ -623,6 +847,11 @@ const formattedOptions = computed(() => JSON.stringify({
componentScripts: componentScriptList.value
}, null, 2));
/**
* 追加一条事件日志
*
* @param message - 要展示在事件流中的简短描述
*/
function pushLog(message: string): void {
const item = {
id: `${Date.now()}-${logSeed++}`,
@ -632,11 +861,19 @@ function pushLog(message: string): void {
logs.value = [item, ...logs.value].slice(0, 10);
}
/**
* 处理播放器 ready 事件
*/
function handleReady(): void {
playerStatus.value = 'ready';
pushLog('ready');
}
/**
* 处理播放进度更新
*
* demo 只在整 15 秒附近刷新状态减少 timeupdate 高频事件对界面的干扰
*/
function handleTimeUpdate(): void {
const current = playerRef.value?.getCurrentTime();
if (typeof current === 'number' && Math.floor(current) % 15 === 0) {
@ -644,36 +881,63 @@ function handleTimeUpdate(): void {
}
}
/**
* 处理播放器运行时错误
*
* @param event - SDK 透传的错误事件或错误对象
*/
function handleError(event?: unknown): void {
playerStatus.value = 'error';
pushLog(`error ${JSON.stringify(event || {})}`);
}
/**
* 处理 SDK 加载失败
*
* @param error - 组件在加载 CSSJS 或扩展脚本时抛出的错误
*/
function handleSdkError(error: Error): void {
playerStatus.value = 'sdk-error';
pushLog(`sdk-error ${error.message}`);
}
/**
* 跳转到固定时间点
*
* demo 使用 15 秒作为确定性测试值方便观察禁快进和 seek 行为
*/
function seekTo(): void {
playerRef.value?.seek(15);
pushLog('seek 15s');
}
/**
* 请求播放器全屏
*/
function requestFullScreen(): void {
playerRef.value?.requestFullScreen();
pushLog('fullscreen');
}
/**
* 读取播放器当前状态并写入事件流
*/
function snapshotStatus(): void {
playerStatus.value = playerRef.value?.getStatus() || 'unknown';
pushLog(`status ${playerStatus.value}`);
}
/**
* 复制当前配置快照到剪贴板
*/
function copyConfig(): void {
void navigator.clipboard?.writeText(formattedOptions.value);
pushLog('config copied');
}
/**
* 重置 demo 中所有可编辑参数
*/
function resetAll(): void {
sourceMode.value = 'url';
selectedPreset.value = sourcePresets[0].value;

View File

@ -1,4 +1,9 @@
import { createApp } from 'vue';
import App from './App.vue';
/**
* demo
*
* demo 便 GitHub Pages
*/
createApp(App).mount('#app');

View File

@ -1,12 +1,51 @@
/**
* Web SDK
*
* SDK SDK
*/
export declare const DEFAULT_SDK_VERSION = "2.37.0";
/**
* Aliplayer CSS
*
* 2.16.3 `de/prismplayer`
* `apsara-media-box/imp-web-player`使
*/
export declare const DEFAULT_CSS_LINK = "https://g.alicdn.com/apsara-media-box/imp-web-player/2.37.0/skins/default/aliplayer-min.css";
/**
* Aliplayer JS
*/
export declare const DEFAULT_SCRIPT_SRC = "https://g.alicdn.com/apsara-media-box/imp-web-player/2.37.0/aliplayer-min.js";
/**
* SDK CSS
*
* @param version Web SDK
* @returns `imp-web-player` CSS
*/
export declare function getCssLinkByVersion(version: string): string;
/**
* SDK JS
*
* @param version Web SDK
* @returns `imp-web-player` JS
*/
export declare function getScriptSrcByVersion(version: string): string;
/**
* Shared SDK loader. It prevents duplicate CSS/JS tags when several player
* instances mount at the same time or are recreated by route changes.
* Aliplayer SDK CSS JS
*
* CSS JS Promise
*
*
* @param cssLink Aliplayer
* @param scriptSrc Aliplayer
*/
export declare function loadAliplayerSdk(cssLink: string, scriptSrc: string): Promise<void>;
/**
*
*
* Aliplayer
*
*
* @param scriptUrls
*/
export declare function loadExtraScripts(scriptUrls?: string[]): Promise<void>;
//# sourceMappingURL=sdkLoader.d.ts.map

View File

@ -1,4 +1,27 @@
/**
*
*
* `null` Aliplayer SDK
*/
export type SourceFormat = 'mp4' | 'm3u8' | 'flv' | 'rtmp' | 'mp3' | null;
/**
*
*
* query hashRTMP
*
*
* @param source
* @returns `null`
*/
export declare function inferSourceFormat(source?: string | null): SourceFormat;
/**
* URL
*
* `encodeURI``data:` `blob:`
* URL 退
*
* @param source
* @returns
*/
export declare function normalizeSource(source?: string | null): string | null;
//# sourceMappingURL=source.d.ts.map

View File

@ -1,6 +1,20 @@
/**
* Aliplayer
*
* `fetch` `XMLHttpRequest`
* wrapper SDK
* API `disableTracking`
*
* @param patterns URL 使
*/
export declare function installTrackingBlocker(patterns?: Array<string | RegExp>): void;
declare global {
interface XMLHttpRequest {
/**
* XHR vue-aliplayer-v2
*
* `open` `send` patch
*/
__vueAliplayerV2Blocked?: boolean;
}
}

View File

@ -1,122 +1,519 @@
import type { Plugin } from 'vue';
/**
*
*
* props 使 `app.use(VueAliplayerV2, options)`
* SDK props
*/
export interface VueAliplayerV2Options {
/**
* Web SDK
*
* `cssLink` `scriptSrc`
* `apsara-media-box/imp-web-player`
*/
sdkVersion?: string;
/**
* Aliplayer CSS
*
* `sdkVersion`
*/
cssLink?: string;
/**
* Aliplayer JS
*
* `sdkVersion`
*/
scriptSrc?: string;
/**
*
*
*
* Aliplayer
*/
componentScripts?: string[];
/**
* Aliplayer
*
* wrapper 使
*/
disableTracking?: boolean;
/**
* URL
*
* `newplayer/track`
*/
trackingUrlPatterns?: Array<string | RegExp>;
}
/**
* `<VueAliplayerV2 />` props
*
* 使 SDK
*
*/
export interface AliplayerV2Props extends VueAliplayerV2Options {
/**
* `source` `format`
*
* `mp4``m3u8``flv``mp3``rtmp` `options.format`
*
*/
autoFormat?: boolean;
/**
*
*
* `timeupdate`退
*/
forbidFastForward?: boolean;
/**
* Aliplayer License
*
* Web License
* Aliplayer options `options.license`
*/
license?: AliplayerLicense | null;
/**
* FLV
*
* `options.isLive` `true` `flv` FLV stash buffer
* `options`
*/
lowLatency?: boolean;
/**
* URL `encodeURI`
*
* URL`data:` `blob:`
*/
normalizeSourceUrl?: boolean;
/**
* Aliplayer
*
* `id``source``license``format`
*
*/
options?: AliplayerOptions | null;
/**
* URL
*
* `options.source` `loadByUrl`
*/
source?: string | null;
}
/**
* Web License
*/
export interface AliplayerLicense {
/**
* License
*/
domain: string;
/**
* License Key
*/
key: string;
}
/**
* Aliplayer
*/
export type AliplayerEventName = 'ready' | 'play' | 'pause' | 'canplay' | 'playing' | 'ended' | 'liveStreamStop' | 'onM3u8Retry' | 'hideBar' | 'showBar' | 'waiting' | 'timeupdate' | 'snapshoted' | 'requestFullScreen' | 'cancelFullScreen' | 'error' | 'startSeek' | 'completeSeek';
/**
* Aliplayer
*
* wrapper SDK
*
*/
export interface AliplayerOptions {
/**
* DOM id
*/
id?: string;
/**
* URL
*/
source?: string;
/**
* `100%` `640px`
*/
width?: string;
/**
*
*/
autoplay?: boolean;
/**
*
*/
isLive?: boolean;
/**
* `mp4``m3u8``flv`
*/
format?: string;
/**
* Aliplayer License
*/
license?: AliplayerLicense;
/**
* ID VID + PlayAuth STS
*/
vid?: string;
/**
*
*/
playauth?: string;
/**
* STS AccessKey ID
*/
accessKeyId?: string;
/**
* STS
*/
securityToken?: string;
/**
* STS AccessKey Secret
*/
accessKeySecret?: string;
/**
* `cn-shanghai`
*/
region?: string;
/**
*
*/
authTimeout?: number;
/**
* Aliplayer
*/
components?: unknown[];
/**
* FLV stash buffer
*/
enableStashBufferForFlv?: boolean;
/**
* FLV
*/
stashInitialSizeForFlv?: number;
/**
* RTS SDK
*/
rtsVersion?: string;
/**
*
*/
[key: string]: unknown;
}
/**
* Aliplayer
*/
export interface AliplayerFullscreenService {
/**
*
*/
requestFullScreen: () => void;
/**
* 退
*/
cancelFullScreen: () => void;
/**
*
*/
getIsFullScreen: () => boolean;
}
/**
* Aliplayer
*/
export interface AliplayerLiveShiftService {
/**
*
*
* @param beginTime `YYYY/MM/DD HH:mm:ss`
* @param endTime `YYYY/MM/DD HH:mm:ss`
*/
setLiveTimeRange: (beginTime: string, endTime: string) => void;
}
/**
* Aliplayer
*
* SDK
*/
export interface AliplayerInstance {
/**
*
*
* @param eventName
* @param handler
*/
on: (eventName: string, handler: (event?: unknown) => void) => void;
/**
*
*
* SDK
*/
off?: (eventName: string, handler: (event?: unknown) => void) => void;
/**
*
*/
play: () => void;
/**
*
*/
pause: () => void;
/**
*
*/
replay: () => void;
/**
*
*
* @param time
*/
seek: (time: number) => void;
/**
*
*/
getCurrentTime: () => number;
/**
*
*/
getDuration: () => number;
/**
* 0 1
*/
getVolume: () => number;
/**
*
*
* @param volume 0 1
*/
setVolume: (volume: number) => void;
/**
* URL
*
* @param url
* @param time
*/
loadByUrl: (url: string, time?: number) => void;
/**
* 使 VID + PlayAuth
*/
replayByVidAndPlayAuth: (vid: string, playauth: string) => void;
/**
* 使 MPS
*/
replayByVidAndAuthInfo: (vid: string, accId: string, accSecret: string, stsToken: string, authInfo: string, domainRegion: string) => void;
/**
*
*/
setPlayerSize: (width: string, height: string) => void;
/**
*
*/
setSpeed: (speed: number) => void;
/**
*
*/
setSanpshotProperties: (width: number, height: number, rate: number) => void;
/**
*
*/
fullscreenService?: AliplayerFullscreenService;
/**
*
*/
getStatus: () => string;
/**
* SDK
*/
liveShiftSerivce?: AliplayerLiveShiftService;
/**
*
*/
setRotate: (rotate: number) => void;
/**
*
*/
getRotate: () => number;
/**
*
*/
setImage: (image: string) => void;
/**
*
*/
dispose: () => void;
/**
*
*/
setCover: (cover: string) => void;
/**
*
*/
setProgressMarkers: (markers: unknown[]) => void;
/**
*
*/
setPreviewTime: (time: number) => void;
/**
*
*/
getPreviewTime: () => number;
/**
*
*/
isPreview: () => boolean;
}
/**
* Vue `ref`
*
* 1.x SDK 便
*/
export interface VueAliplayerV2Expose {
/**
* Aliplayer
*/
getPlayer: () => AliplayerInstance | null;
/**
* SDK
*/
init: () => Promise<void>;
/**
* 使 props/options
*/
initPlayer: () => void;
/**
*
*
* @param nextSource `loadByUrl`
*/
reload: (nextSource?: string) => Promise<void>;
/**
*
*
* `reload` `error` 使
*/
retry: (nextSource?: string) => Promise<void>;
/**
*
*/
play: () => void;
/**
*
*/
pause: () => void;
/**
*
*/
replay: () => void;
/**
*
*/
seek: (time: number) => void;
/**
*
*/
getCurrentTime: () => number | undefined;
/**
*
*/
getDuration: () => number | undefined;
/**
*
*/
getVolume: () => number | undefined;
/**
*
*/
setVolume: (volume: number) => void;
/**
* URL
*/
loadByUrl: (url: string, time?: number) => void;
/**
* 使 VID + PlayAuth
*/
replayByVidAndPlayAuth: (vid: string, playauth: string) => void;
/**
* 使 MPS
*/
replayByVidAndAuthInfo: (vid: string, accId: string, accSecret: string, stsToken: string, authInfo: string, domainRegion: string) => void;
/**
*
*/
setPlayerSize: (width: string, height: string) => void;
/**
*
*/
setSpeed: (speed: number) => void;
/**
*
*/
setSanpshotProperties: (width: number, height: number, rate: number) => void;
/**
*
*/
requestFullScreen: () => void;
/**
* 退
*/
cancelFullScreen: () => void;
/**
*
*/
getIsFullScreen: () => boolean | undefined;
/**
*
*/
getStatus: () => string | undefined;
/**
*
*/
setLiveTimeRange: (beginTime: string, endTime: string) => void;
/**
*
*/
setRotate: (rotate: number) => void;
/**
*
*/
getRotate: () => number | undefined;
/**
*
*/
setImage: (image: string) => void;
/**
*
*/
dispose: () => void;
/**
*
*/
setCover: (cover: string) => void;
/**
*
*/
setProgressMarkers: (markers: unknown[]) => void;
/**
*
*/
setPreviewTime: (time: number) => void;
/**
*
*/
getPreviewTime: () => number | undefined;
/**
*
*/
isPreview: () => boolean | undefined;
/**
*
*/
off: (eventName: string, handler: (event?: unknown) => void) => void;
}
/**
* Vue
*/
export type VueAliplayerV2Plugin = Plugin & {
/**
* 1.x
*/
Player?: unknown;
};
declare global {

25
lib/types/index.d.ts vendored
View File

@ -1,10 +1,32 @@
import type { App } from 'vue';
import VueAliplayerV2 from './AliplayerV2/index.vue';
import type { VueAliplayerV2Options } from './AliplayerV2/types';
/**
*
*
* 使
*/
export type { AliplayerEventName, AliplayerInstance, AliplayerLicense, AliplayerOptions, AliplayerV2Props, VueAliplayerV2Expose, VueAliplayerV2Options } from './AliplayerV2/types';
/**
* Vue
*
* Vue SFC `install``Player` props
* TypeScript
*/
type InstallableVueAliplayerV2 = typeof VueAliplayerV2 & {
/**
* Vue `app.use(VueAliplayerV2, options)`
*/
install: (app: App, options?: VueAliplayerV2Options) => void;
/**
* `VueAliplayerV2.Player`
*/
Player: typeof VueAliplayerV2;
/**
* props
*
* props
*/
props: {
sdkVersion: {
default: string | (() => string);
@ -26,6 +48,9 @@ type InstallableVueAliplayerV2 = typeof VueAliplayerV2 & {
};
};
};
/**
*
*/
declare const installable: InstallableVueAliplayerV2;
export { installable as VueAliplayerV2 };
export default installable;

View File

@ -1,2 +1,2 @@
.vue-aliplayer-v2[data-v-cf852d0d]{width:100%}
.vue-aliplayer-v2[data-v-eff69876]{width:100%}
/*$vite$:1*/

View File

@ -212,7 +212,7 @@ var E = /* @__PURE__ */ ((e, t) => {
async function z() {
let e = ++E;
try {
if (h.disableTracking && T(h.trackingUrlPatterns), await _(k.value, A.value), await v(h.componentScripts), await r(), C.value || e !== E) return;
if (h.disableTracking && T(h.trackingUrlPatterns.length ? h.trackingUrlPatterns : void 0), await _(k.value, A.value), await v(h.componentScripts), await r(), C.value || e !== E) return;
R();
} catch (e) {
g("sdk-error", e instanceof Error ? e : Error(String(e)));
@ -380,7 +380,7 @@ var E = /* @__PURE__ */ ((e, t) => {
class: "vue-aliplayer-v2"
}, null, 512));
}
}), [["__scopeId", "data-v-cf852d0d"]]), D = {
}), [["__scopeId", "data-v-eff69876"]]), D = {
sdkVersion: l,
cssLink: u,
scriptSrc: d,

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,8 @@
},
"files": [
"lib",
"README.md"
"README.md",
"README.en-US.md"
],
"private": false,
"license": "MIT",

View File

@ -48,12 +48,40 @@ const emit = defineEmits<{
(event: 'sdk-error', error: Error): void;
}>();
/**
* 当前底层 Aliplayer 实例
*/
const player = ref<AliplayerInstance | null>(null);
/**
* 播放器挂载容器
*/
const containerRef = ref<HTMLDivElement | null>(null);
/**
* 组件是否已经卸载
*
* SDK 和扩展脚本是异步加载的卸载标记用于避免脚本加载完成后继续创建播放器
*/
const isUnmounted = ref(false);
/**
* 当前播放器实例对应的媒体格式
*
* 用于判断 source 变化时是走 `loadByUrl` 还是重建播放器
*/
const currentFormat = ref<SourceFormat>(null);
/**
* 初始化批次令牌
*
* 每次 `init` 都会递增旧批次异步返回时如果令牌不一致就不会继续创建播放器
* 避免快速切换参数造成 stale player
*/
let initToken = 0;
/**
* 内部自动生成的播放器容器 id
*/
const playerId = `player-${Math.random().toString(36).slice(2).toUpperCase()}`;
/**
* 需要从 Aliplayer 透传到 Vue 组件的事件列表
*/
const eventNames: AliplayerEventName[] = [
'ready',
'play',
@ -75,10 +103,28 @@ const eventNames: AliplayerEventName[] = [
'completeSeek'
];
/**
* 实际使用的 CSS 地址
*/
const resolvedCssLink = computed(() => props.cssLink || (props.sdkVersion ? getCssLinkByVersion(props.sdkVersion) : DEFAULT_CSS_LINK));
/**
* 实际使用的 JS 地址
*/
const resolvedScriptSrc = computed(() => props.scriptSrc || (props.sdkVersion ? getScriptSrcByVersion(props.sdkVersion) : DEFAULT_SCRIPT_SRC));
/**
* 标准化后的播放源
*/
const normalizedSource = computed(() => props.normalizeSourceUrl ? normalizeSource(props.source) : props.source);
/**
* 从播放源推断出的格式
*/
const sourceFormat = computed(() => inferSourceFormat(normalizedSource.value));
/**
* 最终传给 Aliplayer 的初始化配置
*
* 合并顺序为组件默认值 -> `props.options` -> `license` prop -> `source` prop -> 内部 id
* 这样可以保证容器 id 和显式 source 始终由组件控制同时仍然保留大部分官方配置项的透传能力
*/
const mergedOptions = computed<AliplayerOptions>(() => {
const options: AliplayerOptions = {
width: '100%',
@ -101,10 +147,20 @@ const mergedOptions = computed<AliplayerOptions>(() => {
return options;
});
/**
* 获取底层 Aliplayer 实例
*
* @returns 当前播放器实例尚未初始化或已销毁时返回 `null`
*/
function getPlayer(): AliplayerInstance | null {
return player.value;
}
/**
* 销毁当前播放器实例
*
* 销毁时同步清空当前格式记录避免后续 source 切换误判为同格式切换
*/
function dispose(): void {
if (!player.value) return;
player.value.dispose();
@ -112,6 +168,11 @@ function dispose(): void {
currentFormat.value = null;
}
/**
* Aliplayer 事件转发为 Vue 组件事件
*
* @param target 底层播放器实例
*/
function bindEvents(target: AliplayerInstance): void {
eventNames.forEach((eventName) => {
target.on(eventName, (event?: unknown) => {
@ -120,6 +181,14 @@ function bindEvents(target: AliplayerInstance): void {
});
}
/**
* 绑定禁止快进逻辑
*
* 该能力通过监听 `timeupdate` 实现如果当前播放时间比上次记录时间大幅跳跃
* 视为用户拖拽快进并回退到上次位置
*
* @param target 底层播放器实例
*/
function bindForbidFastForward(target: AliplayerInstance): void {
if (!props.forbidFastForward) return;
@ -139,6 +208,11 @@ function bindForbidFastForward(target: AliplayerInstance): void {
});
}
/**
* 使用当前合并配置创建 Aliplayer 实例
*
* 如果组件已卸载SDK 未加载或容器不存在则直接跳过
*/
function initPlayer(): void {
if (isUnmounted.value || !window.Aliplayer || !containerRef.value) return;
@ -150,11 +224,17 @@ function initPlayer(): void {
bindForbidFastForward(nextPlayer);
}
/**
* 加载 SDK扩展脚本并初始化播放器
*
* 该方法会处理统计拦截SDK 资源加载额外组件脚本加载和 Vue DOM 更新等待
* 如果任一步失败会通过 `sdk-error` 事件抛给业务层处理
*/
async function init(): Promise<void> {
const token = ++initToken;
try {
if (props.disableTracking) {
installTrackingBlocker(props.trackingUrlPatterns);
installTrackingBlocker(props.trackingUrlPatterns.length ? props.trackingUrlPatterns : undefined);
}
await loadAliplayerSdk(resolvedCssLink.value, resolvedScriptSrc.value);
await loadExtraScripts(props.componentScripts);
@ -166,6 +246,11 @@ async function init(): Promise<void> {
}
}
/**
* 局部重载播放器
*
* @param nextSource 可选的新播放源传入且播放器已存在时直接调用 `loadByUrl`
*/
async function reload(nextSource?: string): Promise<void> {
if (nextSource && player.value) {
player.value.loadByUrl(props.normalizeSourceUrl ? normalizeSource(nextSource) || nextSource : nextSource);
@ -175,50 +260,113 @@ async function reload(nextSource?: string): Promise<void> {
await init();
}
/**
* 播放失败时的业务重试入口
*
* 当前行为等价于 `reload`但命名更贴合 `error` 回调中的使用语义
*
* @param nextSource 可选的新播放源
*/
async function retry(nextSource?: string): Promise<void> {
await reload(nextSource);
}
/**
* 播放视频
*/
function play(): void {
player.value?.play();
}
/**
* 暂停视频
*/
function pause(): void {
player.value?.pause();
}
/**
* 从头重播视频
*/
function replay(): void {
player.value?.replay();
}
/**
* 跳转到指定时间
*
* @param time 秒数
*/
function seek(time: number): void {
player.value?.seek(time);
}
/**
* 获取当前播放时间
*
* @returns 当前播放时间单位秒
*/
function getCurrentTime(): number | undefined {
return player.value?.getCurrentTime();
}
/**
* 获取视频总时长
*
* @returns 视频总时长单位秒
*/
function getDuration(): number | undefined {
return player.value?.getDuration();
}
/**
* 获取当前音量
*
* @returns 音量值通常为 0 1
*/
function getVolume(): number | undefined {
return player.value?.getVolume();
}
/**
* 设置当前音量
*
* @param volume 音量值通常为 0 1
*/
function setVolume(volume: number): void {
player.value?.setVolume(volume);
}
/**
* 使用 URL 切换播放源
*
* @param url 播放源地址
* @param time 可选起播时间单位秒
*/
function loadByUrl(url: string, time?: number): void {
player.value?.loadByUrl(url, time);
}
/**
* 使用 VID + PlayAuth 重新播放
*
* @param vid 视频 ID
* @param playauth 播放凭证
*/
function replayByVidAndPlayAuth(vid: string, playauth: string): void {
player.value?.replayByVidAndPlayAuth(vid, playauth);
}
/**
* 使用 MPS 鉴权信息重新播放
*
* @param vid 视频 ID
* @param accId 访问密钥 ID
* @param accSecret 访问密钥 Secret
* @param stsToken STS Token
* @param authInfo 鉴权信息
* @param domainRegion 域名地域
*/
function replayByVidAndAuthInfo(
vid: string,
accId: string,
@ -230,70 +378,152 @@ function replayByVidAndAuthInfo(
player.value?.replayByVidAndAuthInfo(vid, accId, accSecret, stsToken, authInfo, domainRegion);
}
/**
* 设置播放器尺寸
*
* @param width 宽度例如 `100%` `640px`
* @param height 高度例如 `360px`
*/
function setPlayerSize(width: string, height: string): void {
player.value?.setPlayerSize(width, height);
}
/**
* 设置播放倍速
*
* @param speed 倍速值例如 `1.25``1.5``2`
*/
function setSpeed(speed: number): void {
player.value?.setSpeed(speed);
}
/**
* 设置截图参数
*
* 方法名保留阿里云 SDK 原始拼写 `setSanpshotProperties`避免破坏旧版本 API
*
* @param width 截图宽度
* @param height 截图高度
* @param rate 截图质量
*/
function setSanpshotProperties(width: number, height: number, rate: number): void {
player.value?.setSanpshotProperties(width, height, rate);
}
/**
* 请求进入全屏
*/
function requestFullScreen(): void {
player.value?.fullscreenService?.requestFullScreen();
}
/**
* 退出全屏
*/
function cancelFullScreen(): void {
player.value?.fullscreenService?.cancelFullScreen();
}
/**
* 获取当前是否处于全屏
*
* @returns 是否全屏当前 SDK 不支持或播放器未初始化时返回 `undefined`
*/
function getIsFullScreen(): boolean | undefined {
return player.value?.fullscreenService?.getIsFullScreen();
}
/**
* 获取播放器状态
*
* @returns Aliplayer 状态字符串
*/
function getStatus(): string | undefined {
return player.value?.getStatus();
}
/**
* 设置直播时移范围
*
* @param beginTime 起始时间
* @param endTime 结束时间
*/
function setLiveTimeRange(beginTime: string, endTime: string): void {
player.value?.liveShiftSerivce?.setLiveTimeRange(beginTime, endTime);
}
/**
* 设置视频旋转角度
*
* @param rotate 旋转角度正数为顺时针
*/
function setRotate(rotate: number): void {
player.value?.setRotate(rotate);
}
/**
* 获取视频旋转角度
*/
function getRotate(): number | undefined {
return player.value?.getRotate();
}
/**
* 设置视频镜像方向
*
* @param image 镜像类型例如 `horizon` `vertical`
*/
function setImage(image: string): void {
player.value?.setImage(image);
}
/**
* 设置播放器封面图
*
* @param cover 封面图地址
*/
function setCover(cover: string): void {
player.value?.setCover(cover);
}
/**
* 设置进度条打点
*
* @param markers 打点配置数组
*/
function setProgressMarkers(markers: unknown[]): void {
player.value?.setProgressMarkers(markers);
}
/**
* 设置试看时间
*
* @param time 试看时间单位秒
*/
function setPreviewTime(time: number): void {
player.value?.setPreviewTime(time);
}
/**
* 获取试看时间
*/
function getPreviewTime(): number | undefined {
return player.value?.getPreviewTime();
}
/**
* 判断当前是否为试看状态
*/
function isPreview(): boolean | undefined {
return player.value?.isPreview();
}
/**
* 取消监听底层播放器事件
*
* @param eventName 事件名
* @param handler 需要取消的回调函数
*/
function off(eventName: string, handler: (event?: unknown) => void): void {
player.value?.off?.(eventName, handler);
}

View File

@ -1,17 +1,56 @@
/**
* Web SDK
*
* SDK SDK
*/
export const DEFAULT_SDK_VERSION = '2.37.0';
/**
* Aliplayer CSS
*
* 2.16.3 `de/prismplayer`
* `apsara-media-box/imp-web-player`使
*/
export const DEFAULT_CSS_LINK = `https://g.alicdn.com/apsara-media-box/imp-web-player/${DEFAULT_SDK_VERSION}/skins/default/aliplayer-min.css`;
/**
* Aliplayer JS
*/
export const DEFAULT_SCRIPT_SRC = `https://g.alicdn.com/apsara-media-box/imp-web-player/${DEFAULT_SDK_VERSION}/aliplayer-min.js`;
/**
* Promise
*
*
* `<script>`
*/
const scriptLoadPromises = new Map<string, Promise<void>>();
/**
* SDK CSS
*
* @param version Web SDK
* @returns `imp-web-player` CSS
*/
export function getCssLinkByVersion(version: string): string {
return `https://g.alicdn.com/apsara-media-box/imp-web-player/${version}/skins/default/aliplayer-min.css`;
}
/**
* SDK JS
*
* @param version Web SDK
* @returns `imp-web-player` JS
*/
export function getScriptSrcByVersion(version: string): string {
return `https://g.alicdn.com/apsara-media-box/imp-web-player/${version}/aliplayer-min.js`;
}
/**
* Aliplayer CSS
*
* @param cssLink CSS
*/
function ensureStylesheet(cssLink: string): void {
if (!cssLink) return;
@ -26,6 +65,13 @@ function ensureStylesheet(cssLink: string): void {
document.head.appendChild(link);
}
/**
*
*
* @param scriptSrc
* @param globalName
* @returns Promise
*/
function ensureScript(scriptSrc: string, globalName?: string): Promise<void> {
if (!scriptSrc) return Promise.resolve();
if (globalName && window[globalName]) return Promise.resolve();
@ -66,14 +112,27 @@ function ensureScript(scriptSrc: string, globalName?: string): Promise<void> {
}
/**
* Shared SDK loader. It prevents duplicate CSS/JS tags when several player
* instances mount at the same time or are recreated by route changes.
* Aliplayer SDK CSS JS
*
* CSS JS Promise
*
*
* @param cssLink Aliplayer
* @param scriptSrc Aliplayer
*/
export async function loadAliplayerSdk(cssLink: string, scriptSrc: string): Promise<void> {
ensureStylesheet(cssLink);
await ensureScript(scriptSrc, 'Aliplayer');
}
/**
*
*
* Aliplayer
*
*
* @param scriptUrls
*/
export async function loadExtraScripts(scriptUrls: string[] = []): Promise<void> {
for (const scriptUrl of scriptUrls) {
await ensureScript(scriptUrl);

View File

@ -1,7 +1,24 @@
/**
*
*
* `null` Aliplayer SDK
*/
export type SourceFormat = 'mp4' | 'm3u8' | 'flv' | 'rtmp' | 'mp3' | null;
/**
*
*/
const KNOWN_FORMATS: SourceFormat[] = ['m3u8', 'flv', 'mp4', 'rtmp', 'mp3'];
/**
*
*
* query hashRTMP
*
*
* @param source
* @returns `null`
*/
export function inferSourceFormat(source?: string | null): SourceFormat {
if (!source) return null;
@ -12,6 +29,15 @@ export function inferSourceFormat(source?: string | null): SourceFormat {
return matched || null;
}
/**
* URL
*
* `encodeURI``data:` `blob:`
* URL 退
*
* @param source
* @returns
*/
export function normalizeSource(source?: string | null): string | null {
if (!source) return source || null;
if (/^(data|blob):/i.test(source)) return source;
@ -22,4 +48,3 @@ export function normalizeSource(source?: string | null): string | null {
return source;
}
}

View File

@ -1,9 +1,26 @@
/**
*
*
* issue `newplayer/track`
*/
const DEFAULT_TRACKING_PATTERNS: Array<string | RegExp> = [
'videocloud.cn-hangzhou.log.aliyuncs.com/logstores/newplayer/track'
];
/**
*
*
* monkey patch `fetch` `XMLHttpRequest`
*/
let trackingBlockerInstalled = false;
/**
* URL
*
* @param url
* @param patterns
* @returns `true`
*/
function matchesTrackingUrl(url: string, patterns: Array<string | RegExp>): boolean {
return patterns.some((pattern) => {
if (typeof pattern === 'string') return url.includes(pattern);
@ -11,6 +28,15 @@ function matchesTrackingUrl(url: string, patterns: Array<string | RegExp>): bool
});
}
/**
* Aliplayer
*
* `fetch` `XMLHttpRequest`
* wrapper SDK
* API `disableTracking`
*
* @param patterns URL 使
*/
export function installTrackingBlocker(patterns: Array<string | RegExp> = DEFAULT_TRACKING_PATTERNS): void {
if (trackingBlockerInstalled) return;
trackingBlockerInstalled = true;
@ -44,6 +70,11 @@ export function installTrackingBlocker(patterns: Array<string | RegExp> = DEFAUL
declare global {
interface XMLHttpRequest {
/**
* XHR vue-aliplayer-v2
*
* `open` `send` patch
*/
__vueAliplayerV2Blocked?: boolean;
}
}

View File

@ -1,29 +1,124 @@
import type { Plugin } from 'vue';
/**
*
*
* props 使 `app.use(VueAliplayerV2, options)`
* SDK props
*/
export interface VueAliplayerV2Options {
/**
* Web SDK
*
* `cssLink` `scriptSrc`
* `apsara-media-box/imp-web-player`
*/
sdkVersion?: string;
/**
* Aliplayer CSS
*
* `sdkVersion`
*/
cssLink?: string;
/**
* Aliplayer JS
*
* `sdkVersion`
*/
scriptSrc?: string;
/**
*
*
*
* Aliplayer
*/
componentScripts?: string[];
/**
* Aliplayer
*
* wrapper 使
*/
disableTracking?: boolean;
/**
* URL
*
* `newplayer/track`
*/
trackingUrlPatterns?: Array<string | RegExp>;
}
/**
* `<VueAliplayerV2 />` props
*
* 使 SDK
*
*/
export interface AliplayerV2Props extends VueAliplayerV2Options {
/**
* `source` `format`
*
* `mp4``m3u8``flv``mp3``rtmp` `options.format`
*
*/
autoFormat?: boolean;
/**
*
*
* `timeupdate`退
*/
forbidFastForward?: boolean;
/**
* Aliplayer License
*
* Web License
* Aliplayer options `options.license`
*/
license?: AliplayerLicense | null;
/**
* FLV
*
* `options.isLive` `true` `flv` FLV stash buffer
* `options`
*/
lowLatency?: boolean;
/**
* URL `encodeURI`
*
* URL`data:` `blob:`
*/
normalizeSourceUrl?: boolean;
/**
* Aliplayer
*
* `id``source``license``format`
*
*/
options?: AliplayerOptions | null;
/**
* URL
*
* `options.source` `loadByUrl`
*/
source?: string | null;
}
/**
* Web License
*/
export interface AliplayerLicense {
/**
* License
*/
domain: string;
/**
* License Key
*/
key: string;
}
/**
* Aliplayer
*/
export type AliplayerEventName =
| 'ready'
| 'play'
@ -44,51 +139,191 @@ export type AliplayerEventName =
| 'startSeek'
| 'completeSeek';
/**
* Aliplayer
*
* wrapper SDK
*
*/
export interface AliplayerOptions {
/**
* DOM id
*/
id?: string;
/**
* URL
*/
source?: string;
/**
* `100%` `640px`
*/
width?: string;
/**
*
*/
autoplay?: boolean;
/**
*
*/
isLive?: boolean;
/**
* `mp4``m3u8``flv`
*/
format?: string;
/**
* Aliplayer License
*/
license?: AliplayerLicense;
/**
* ID VID + PlayAuth STS
*/
vid?: string;
/**
*
*/
playauth?: string;
/**
* STS AccessKey ID
*/
accessKeyId?: string;
/**
* STS
*/
securityToken?: string;
/**
* STS AccessKey Secret
*/
accessKeySecret?: string;
/**
* `cn-shanghai`
*/
region?: string;
/**
*
*/
authTimeout?: number;
/**
* Aliplayer
*/
components?: unknown[];
/**
* FLV stash buffer
*/
enableStashBufferForFlv?: boolean;
/**
* FLV
*/
stashInitialSizeForFlv?: number;
/**
* RTS SDK
*/
rtsVersion?: string;
/**
*
*/
[key: string]: unknown;
}
/**
* Aliplayer
*/
export interface AliplayerFullscreenService {
/**
*
*/
requestFullScreen: () => void;
/**
* 退
*/
cancelFullScreen: () => void;
/**
*
*/
getIsFullScreen: () => boolean;
}
/**
* Aliplayer
*/
export interface AliplayerLiveShiftService {
/**
*
*
* @param beginTime `YYYY/MM/DD HH:mm:ss`
* @param endTime `YYYY/MM/DD HH:mm:ss`
*/
setLiveTimeRange: (beginTime: string, endTime: string) => void;
}
/**
* Aliplayer
*
* SDK
*/
export interface AliplayerInstance {
/**
*
*
* @param eventName
* @param handler
*/
on: (eventName: string, handler: (event?: unknown) => void) => void;
/**
*
*
* SDK
*/
off?: (eventName: string, handler: (event?: unknown) => void) => void;
/**
*
*/
play: () => void;
/**
*
*/
pause: () => void;
/**
*
*/
replay: () => void;
/**
*
*
* @param time
*/
seek: (time: number) => void;
/**
*
*/
getCurrentTime: () => number;
/**
*
*/
getDuration: () => number;
/**
* 0 1
*/
getVolume: () => number;
/**
*
*
* @param volume 0 1
*/
setVolume: (volume: number) => void;
/**
* URL
*
* @param url
* @param time
*/
loadByUrl: (url: string, time?: number) => void;
/**
* 使 VID + PlayAuth
*/
replayByVidAndPlayAuth: (vid: string, playauth: string) => void;
/**
* 使 MPS
*/
replayByVidAndAuthInfo: (
vid: string,
accId: string,
@ -97,39 +332,141 @@ export interface AliplayerInstance {
authInfo: string,
domainRegion: string
) => void;
/**
*
*/
setPlayerSize: (width: string, height: string) => void;
/**
*
*/
setSpeed: (speed: number) => void;
/**
*
*/
setSanpshotProperties: (width: number, height: number, rate: number) => void;
/**
*
*/
fullscreenService?: AliplayerFullscreenService;
/**
*
*/
getStatus: () => string;
/**
* SDK
*/
liveShiftSerivce?: AliplayerLiveShiftService;
/**
*
*/
setRotate: (rotate: number) => void;
/**
*
*/
getRotate: () => number;
/**
*
*/
setImage: (image: string) => void;
/**
*
*/
dispose: () => void;
/**
*
*/
setCover: (cover: string) => void;
/**
*
*/
setProgressMarkers: (markers: unknown[]) => void;
/**
*
*/
setPreviewTime: (time: number) => void;
/**
*
*/
getPreviewTime: () => number;
/**
*
*/
isPreview: () => boolean;
}
/**
* Vue `ref`
*
* 1.x SDK 便
*/
export interface VueAliplayerV2Expose {
/**
* Aliplayer
*/
getPlayer: () => AliplayerInstance | null;
/**
* SDK
*/
init: () => Promise<void>;
/**
* 使 props/options
*/
initPlayer: () => void;
/**
*
*
* @param nextSource `loadByUrl`
*/
reload: (nextSource?: string) => Promise<void>;
/**
*
*
* `reload` `error` 使
*/
retry: (nextSource?: string) => Promise<void>;
/**
*
*/
play: () => void;
/**
*
*/
pause: () => void;
/**
*
*/
replay: () => void;
/**
*
*/
seek: (time: number) => void;
/**
*
*/
getCurrentTime: () => number | undefined;
/**
*
*/
getDuration: () => number | undefined;
/**
*
*/
getVolume: () => number | undefined;
/**
*
*/
setVolume: (volume: number) => void;
/**
* URL
*/
loadByUrl: (url: string, time?: number) => void;
/**
* 使 VID + PlayAuth
*/
replayByVidAndPlayAuth: (vid: string, playauth: string) => void;
/**
* 使 MPS
*/
replayByVidAndAuthInfo: (
vid: string,
accId: string,
@ -138,27 +475,87 @@ export interface VueAliplayerV2Expose {
authInfo: string,
domainRegion: string
) => void;
/**
*
*/
setPlayerSize: (width: string, height: string) => void;
/**
*
*/
setSpeed: (speed: number) => void;
/**
*
*/
setSanpshotProperties: (width: number, height: number, rate: number) => void;
/**
*
*/
requestFullScreen: () => void;
/**
* 退
*/
cancelFullScreen: () => void;
/**
*
*/
getIsFullScreen: () => boolean | undefined;
/**
*
*/
getStatus: () => string | undefined;
/**
*
*/
setLiveTimeRange: (beginTime: string, endTime: string) => void;
/**
*
*/
setRotate: (rotate: number) => void;
/**
*
*/
getRotate: () => number | undefined;
/**
*
*/
setImage: (image: string) => void;
/**
*
*/
dispose: () => void;
/**
*
*/
setCover: (cover: string) => void;
/**
*
*/
setProgressMarkers: (markers: unknown[]) => void;
/**
*
*/
setPreviewTime: (time: number) => void;
/**
*
*/
getPreviewTime: () => number | undefined;
/**
*
*/
isPreview: () => boolean | undefined;
/**
*
*/
off: (eventName: string, handler: (event?: unknown) => void) => void;
}
/**
* Vue
*/
export type VueAliplayerV2Plugin = Plugin & {
/**
* 1.x
*/
Player?: unknown;
};

View File

@ -3,6 +3,11 @@ import VueAliplayerV2 from './AliplayerV2/index.vue';
import { DEFAULT_CSS_LINK, DEFAULT_SCRIPT_SRC, DEFAULT_SDK_VERSION } from './AliplayerV2/sdkLoader';
import type { VueAliplayerV2Options } from './AliplayerV2/types';
/**
*
*
* 使
*/
export type {
AliplayerEventName,
AliplayerInstance,
@ -13,9 +18,26 @@ export type {
VueAliplayerV2Options
} from './AliplayerV2/types';
/**
* Vue
*
* Vue SFC `install``Player` props
* TypeScript
*/
type InstallableVueAliplayerV2 = typeof VueAliplayerV2 & {
/**
* Vue `app.use(VueAliplayerV2, options)`
*/
install: (app: App, options?: VueAliplayerV2Options) => void;
/**
* `VueAliplayerV2.Player`
*/
Player: typeof VueAliplayerV2;
/**
* props
*
* props
*/
props: {
sdkVersion: { default: string | (() => string) };
cssLink: { default: string | (() => string) };
@ -26,6 +48,12 @@ type InstallableVueAliplayerV2 = typeof VueAliplayerV2 & {
};
};
/**
*
*
* props default `app.use`
* props
*/
const globalOptions: Required<VueAliplayerV2Options> = {
sdkVersion: DEFAULT_SDK_VERSION,
cssLink: DEFAULT_CSS_LINK,
@ -35,19 +63,49 @@ const globalOptions: Required<VueAliplayerV2Options> = {
trackingUrlPatterns: []
};
/**
*
*/
const installable = VueAliplayerV2 as unknown as InstallableVueAliplayerV2;
/**
* Vue
*
* @param app Vue
* @param options `globalOptions`
*/
installable.install = (app: App, options?: VueAliplayerV2Options): void => {
Object.assign(globalOptions, options || {});
app.component('VueAliplayerV2', installable);
};
/**
* CSS
*/
installable.props.cssLink.default = (): string => globalOptions.cssLink;
/**
* JS
*/
installable.props.scriptSrc.default = (): string => globalOptions.scriptSrc;
/**
* SDK
*/
installable.props.sdkVersion.default = (): string => globalOptions.sdkVersion;
/**
*
*/
installable.props.componentScripts.default = (): string[] => globalOptions.componentScripts;
/**
*
*/
installable.props.disableTracking.default = (): boolean => globalOptions.disableTracking;
/**
*
*/
installable.props.trackingUrlPatterns.default = (): Array<string | RegExp> => globalOptions.trackingUrlPatterns;
/**
* `VueAliplayerV2.Player`
*/
installable.Player = installable;
export { installable as VueAliplayerV2 };

View File

@ -2,26 +2,57 @@ import { resolve } from 'node:path';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
/**
* Vite
*
*
*
* - demo `dist` GitHub Pages
* - lib `lib` npm
*
* `--mode demo` demo
*/
export default defineConfig(({ mode }) => {
/**
* GitHub Pages demo
*/
const isDemoBuild = mode === 'demo';
return {
/**
* GitHub Pages 使
*/
base: isDemoBuild ? '/vue-aliplayer-v2/' : '/',
plugins: [vue()],
/**
* demo `public` publicDir
*/
publicDir: isDemoBuild ? 'public' : false,
build: isDemoBuild
? {
/**
* demo
*/
outDir: 'dist',
emptyOutDir: true
}
: {
/**
* npm
*/
outDir: 'lib',
emptyOutDir: false,
/**
* Vue
*/
lib: {
entry: resolve(__dirname, 'packages/index.ts'),
name: 'VueAliplayerV2',
fileName: 'vue-aliplayer-v2'
},
/**
* Vue 宿 Vue bundle
*/
rollupOptions: {
external: ['vue'],
output: {