fix: 升级vue3

This commit is contained in:
“zhouliujun” 2026-05-23 21:29:44 +08:00
parent cdf70ab208
commit 15c784daef
44 changed files with 4553 additions and 3847 deletions

View File

@ -1,2 +0,0 @@
> 1%
last 2 versions

View File

@ -1,23 +0,0 @@
module.exports = {
root: true,
env: {
node: true,
browser: true,
es6: true
},
'extends': [
'plugin:vue/essential',
'eslint:recommended'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
},
parserOptions: {
parser: 'babel-eslint'
},
globals: {
'Aliplayer': false
},
}

268
GITHUB_ISSUES_TODO.md Normal file
View File

@ -0,0 +1,268 @@
# GitHub Issues Todo
Fetched from `langyuxiansheng/vue-aliplayer-v2` open issues on 2026-05-23.
Source: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues
## Summary
- Open issues recorded: 25
- Main themes: Vue 3 compatibility, SDK loading/lifecycle, FLV/HLS/RTMP playback, Aliplayer SDK version drift, skin/custom UI configuration, documentation gaps.
- Implementation pass on 2026-05-23: Vue 3 rewrite landed, SDK default updated to the new `imp-web-player` path, wrapper-level lifecycle/loading/playback helpers were added. Items marked "Code status" below are handled in this repository unless noted as upstream/documentation-only.
## Compatibility And Packaging
### #34 please support vue3.x
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/34
- Created: 2020-12-08
- Comments: 3
- Problem: users request Vue 3 support.
- Notes: comments mention a third-party Vue 3/Vite fork/package `vue-aliplayer-v3`; current project is Vue 2 based.
- Follow-up: decide whether to add Vue 3 support in this package, publish a separate Vue 3 package, or document Vue 2-only support.
- Code status: fixed in v2.0.0 rewrite. Vue 2 users must stay on 1.x.
### #35 安装之后引用就直接报这个问题
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/35
- Created: 2020-12-23
- Comments: 3
- Problem: Vue 3 usage throws `Cannot read property '_c' of undefined` from `vue-aliplayer-v2.umd.min.js`.
- Notes: maintainer identified likely cause as Vue version mismatch; overlaps with #34.
- Follow-up: treat as Vue 3 compatibility/package-entry issue.
- Code status: fixed by replacing the Vue 2 UMD build with Vue 3/Vite library output.
### #65 VueAliplayerV2在vue中使用报错
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/65
- Created: 2022-06-12
- Comments: 1
- Problem: user reports usage error with screenshot only.
- Notes: another user reported the same issue in 2023.
- Follow-up: inspect screenshot manually when triaging; likely packaging/import/runtime compatibility.
- Code status: likely fixed by Vue 3 package rewrite and typed exports; still needs reporter repro if screenshot covers another error.
### #45 关于浏览器兼容的问题
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/45
- Created: 2021-03-23
- Comments: 1
- Problem: works in Chrome 89, player component does not display in Chrome 59.
- Notes: current wrapper relies on upstream Aliplayer SDK compatibility.
- Follow-up: define supported browser matrix and test whether transpilation/polyfills or SDK version is the blocker.
- Code status: documented as upstream SDK/browser support. v2 build targets modern Vue 3/Vite environments.
### #46 请问我使用uniapp安装aliplayerV2的问题
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/46
- Created: 2021-03-25
- Comments: 1
- Problem: using npm install in uni-app, player buttons/components do not show.
- Notes: maintainer had not tested uni-app.
- Follow-up: decide whether uni-app is supported; if yes, add setup notes and a minimal repro.
- Code status: not claimed as fixed. Needs a uni-app repro because this package targets Vue 3 web apps.
### #70 建议升级阿里云播放器到最新版
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/70
- Created: 2023-04-12
- Comments: 0
- Problem: default SDK is outdated; user changed CSS/JS defaults to Aliplayer `2.15.2`.
- Notes: user also mentions missing source maps because Aliplayer is closed source.
- Follow-up: verify latest stable Aliplayer SDK URL, update defaults if safe, and document how to override SDK URLs.
- Code status: fixed. Default SDK now uses `apsara-media-box/imp-web-player/2.37.0`; `sdkVersion`, `cssLink`, and `scriptSrc` are configurable.
## SDK Loading And Lifecycle
### #71 优化建议
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/71
- Created: 2024-08-02
- Comments: 2
- Problem: each created player reportedly adds another CSS and JS resource.
- Notes: comment references newer `apsara-media-box/imp-web-player/2.16.3` resource URLs and a multi-player example.
- Follow-up: reproduce with multiple create/destroy cycles and verify whether duplicate resource tags appear under current component logic.
- Code status: fixed in shared SDK loader. It caches CSS/JS by URL and avoids duplicate tags for multiple players.
### #62 多屏播放后,只销毁最后一个播放实例
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/62
- Created: 2022-03-25
- Comments: 2
- Problem: in `v-for`, clearing the list only destroys the last player instance.
- Notes: reporter says FLV did not reproduce, HLS did reproduce.
- Follow-up: test multi-instance destroy with HLS and inspect player ID/ref/lifecycle handling.
- Code status: improved. Component now guards async init after unmount and disposes each instance independently; still needs real HLS multi-player regression test.
### #69 Uncaught TypeError: s._options is null
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/69
- Created: 2023-03-29
- Comments: 0
- Problem: runtime error `s._options is null`, screenshot only.
- Follow-up: inspect screenshot and try to reproduce around destroy/re-init or options mutation.
- Code status: improved. Init is token-guarded and avoids creating stale players after fast option/source changes.
## Playback Failures And Stream Behavior
### #11 rtmp html5模式下不能播放
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/11
- Created: 2020-06-10
- Comments: 3
- Problem: RTMP cannot play in HTML5 mode after Flash deprecation.
- Notes: comment says RTMP only works with Flash; another suggests FLV.js.
- Follow-up: document RTMP limitation clearly; avoid promising HTML5 RTMP support unless using another playback strategy.
- Code status: documented limitation. RTMP H5 support is not fixed in wrapper.
### #21 直播不能播放flv格式的视频
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/21
- Created: 2020-08-10
- Comments: 1
- Problem: FLV live stream fails both by `source` and dynamic `loadByUrl`.
- Notes: maintainer suggested validating stream in VLC and checking Aliplayer config.
- Follow-up: create verified FLV live test case and document required options.
- Code status: improved. Wrapper infers `format: 'flv'`; `lowLatency` adds FLV live buffer defaults. Needs real FLV source validation.
### #24 mp4格式视频加载错误4400
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/24
- Created: 2020-09-27
- Comments: 4
- Problem: large or special MP4 URL reports error 4400; user says autoplay is false but play still appears to trigger.
- Notes: maintainer suspected URL permissions/cross-origin/path encoding; reporter later said the URL contains Chinese characters.
- Follow-up: test encoded vs unencoded URLs, autoplay false behavior, and error handling for cross-origin/private MP4.
- Code status: improved. Wrapper now `encodeURI`s source URLs by default to handle Chinese filenames. Cross-origin/private URL failures remain source/server issues.
### #37 发现m3u8视频播放时的问题
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/37
- Created: 2021-01-07
- Comments: 1
- Problem: M3U8 quality switching is delayed; after switching and refreshing, playback fails.
- Follow-up: reproduce with multi-quality M3U8 source and inspect whether wrapper re-init/load flow loses format or quality state.
- Code status: improved. Wrapper infers source format and rebuilds player on cross-format changes instead of always calling `loadByUrl`.
### #52 flv直播刷新页面视频卡住
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/52
- Created: 2021-06-16
- Comments: 3
- Problem: FLV live video gets stuck after page refresh.
- Notes: reporter confirmed issue was still unresolved in 2021.
- Follow-up: include in FLV live repro matrix with #21 and #68.
- Code status: improved by FLV format inference, async init guards, and `lowLatency`; still needs real stream repro.
### #68 播放Flv视频直播时时间长了后会延迟几分钟
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/68
- Created: 2022-12-17
- Comments: 0
- Problem: FLV live stream accumulates several minutes of latency over time.
- Follow-up: check Aliplayer FLV buffer options such as stash buffer settings and live latency handling.
- Code status: improved. `lowLatency` applies `enableStashBufferForFlv: false` and `stashInitialSizeForFlv: 128` unless explicitly overridden.
## Playback APIs And Source Modes
### #43 vid+palyauth播放问题
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/43
- Created: 2021-03-06
- Comments: 5
- Problem: `replayByVidAndPlayAuth` appears undefined from both `getPlayer()` and component ref.
- Notes: later comment says methods like `replay()` and `pause()` were also undefined; maintainer notes multiple refs become arrays; user later resolved.
- Follow-up: improve docs for `$refs` in single vs multiple-player usage and when methods are available.
- Code status: fixed for Vue 3 docs. README now shows typed `ref<VueAliplayerV2Expose | null>`.
### #49 这个不支持vid+playauth播放吗
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/49
- Created: 2021-05-18
- Comments: 3
- Problem: question about `vid + playauth` playback support.
- Notes: comment says it works when adding `encryptType: 1`; later user asks what else is required.
- Follow-up: add working `vid + playauth` example and option notes.
- Code status: documented. `vid + playauth` is supported through `options`; ref method remains exposed.
## UI, Skin, And Extension Features
### #53 播放按钮
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/53
- Created: 2021-06-21
- Comments: 14
- Problem: users ask how to center play button, configure quality/subtitle options, change cover, and adjust progress/button colors.
- Notes: maintainer answered that `skinLayout` must be placed inside `options`; example uses `skinLayout: [{ name: 'bigPlayButton', align: 'cc' }]`.
- Follow-up: add skin customization examples to README.
- Code status: partially documented through options passthrough. More visual skin examples can be added later.
### #55 请问我要把清晰度切换单独放到设置外面要怎么弄呢?
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/55
- Created: 2021-06-22
- Comments: 1
- Problem: user wants quality switch outside the settings menu.
- Notes: maintainer pointed to Aliplayer skin settings docs.
- Follow-up: document whether this is possible through `skinLayout` or requires custom components.
- Code status: documented as Aliplayer skin/custom component responsibility.
### #57 自定义组件
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/57
- Created: 2021-07-06
- Comments: 4
- Problem: user asks whether Aliplayer custom component API is supported for a playlist.
- Notes: maintainer says SDK should support it, but another user says adding `components` in `options` does not work unless custom component JS is separately loaded.
- Follow-up: verify custom component loading order and provide an example if supported.
- Code status: improved. Added `componentScripts` to load custom component scripts before player init; README includes example.
### #61 播放失败页面自定义
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/61
- Created: 2021-09-24
- Comments: 1
- Problem: clicking refresh on the player error page refreshes the whole page; user wants local refresh or custom refresh handler.
- Notes: maintainer says the error page comes from Aliplayer.
- Follow-up: check whether SDK exposes error UI customization or whether wrapper can expose a local retry pattern.
- Code status: improved. Added `reload()` / `retry()` exposed methods for local retry; SDK error UI itself remains upstream.
### #67 跑马灯功能可以用吗
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/67
- Created: 2022-10-08
- Comments: 0
- Problem: user asks whether marquee/watermark scrolling text is available.
- Follow-up: verify Aliplayer support and document unsupported/supported approach.
- Code status: documented via `componentScripts` + `options.components`; availability depends on the loaded Aliplayer custom component.
### #59 如何关闭track
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/59
- Created: 2021-08-20
- Comments: 1
- Problem: player sends tracking POST requests to `https://videocloud.cn-hangzhou.log.aliyuncs.com/logstores/newplayer/track`; user wants to disable.
- Notes: a comment suggests deleting `postWithHeader`, which is not a clean wrapper-level solution.
- Follow-up: find official SDK option, if any; otherwise document limitation.
- Code status: opt-in mitigation added. `disableTracking` can block known Aliplayer tracking URLs at wrapper level.
## Format And Capability Questions
### #60 能播vr视频或者vr直播吗
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/60
- Created: 2021-09-16
- Comments: 1
- Problem: asks whether VR video or VR live can be played.
- Notes: maintainer had not tested.
- Follow-up: verify upstream Aliplayer capability and document scope.
- Code status: documentation-only. Wrapper passes options through but does not implement VR rendering itself.
### #66 支持AVI 格式的视频吗?
- Link: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/66
- Created: 2022-09-07
- Comments: 0
- Problem: asks whether AVI format is supported.
- Follow-up: document supported formats according to upstream Aliplayer/browser support.
- Code status: documented format scope. AVI is not claimed as supported by this wrapper.

708
README.md
View File

@ -1,512 +1,296 @@
# vue-alipayer-v2
# vue-aliplayer-v2
## 感谢每一位支持开源的朋友. 这是一个基于 Alipayer 开发并封装成 vue 组件的播放器.
基于阿里云 Aliplayer SDK 的 Vue 3 播放器组件。可通过 Aliplayer 官方能力播放 mp4、m3u8、flv、直播流、加密点播、清晰度切换和直播时移等场景。
### vue 中使用 Alipayer,播放 rtmp,m3u8,mp4 视频
> v2 从 Vue 3 + Vite 重构开始,不再兼容 Vue 2。Vue 2 项目请继续安装 `vue-aliplayer-v2@1.x`
#### [本项目在线演示](https://langyuxiansheng.github.io/vue-aliplayer-v2/)
## 在线演示
#### [阿里云播放器在线演示](https://player.alicdn.com/aliplayer/index.html)
- 项目演示https://langyuxiansheng.github.io/vue-aliplayer-v2/
- 阿里云播放器演示https://player.alicdn.com/aliplayer/index.html
> 假如此轮子对你有帮助,请顺手 star 一下吧.o(_ ̄︶ ̄_)o
## 1.安装使用! 下载安装 npm 包
## 安装
```bash
npm i vue-aliplayer-v2
```
```bash
npm i vue-aliplayer-v2 --save
or
yarn add vue-aliplayer-v2
```
#### 全局注册 main.js
## 全局注册
```javascript
import VueAliplayerV2 from "vue-aliplayer-v2";
```ts
import { createApp } from 'vue';
import App from './App.vue';
import VueAliplayerV2 from 'vue-aliplayer-v2';
Vue.use(VueAliplayerV2);
const app = createApp(App);
//可选全局配置
//Vue.use(VueAliplayerV2,{
// cssLink: 'https://g.alicdn.com/de/prismplayer/2.8.2/skins/default/aliplayer-min.css',
// scriptSrc: 'https://g.alicdn.com/de/prismplayer/2.8.2/aliplayer-min.js'
//});
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'
});
app.mount('#app');
```
#### 局部注册 App.vue
## 局部注册
```javascript
//推荐第一种(仅v1.2.3)及以上的版本可用
import VueAliplayerV2 from "vue-aliplayer-v2";
components: {
VueAliplayerV2;
}
//或者
components: {
VueAliplayerV2: VueAliplayerV2.Player;
}
```
## 2.组件中使用
### 组件模板使用,下面的视频连接仅供演示测试.
```html
```vue
<template>
<div id="app">
<template v-if="!isShowMultiple && show">
<vue-aliplayer-v2
<VueAliplayerV2
ref="playerRef"
:source="source"
ref="VueAliplayerV2"
:options="options"
/>
</template>
<div v-if="isShowMultiple && show" class="show-multiple">
<template v-for="x in 5">
<vue-aliplayer-v2
class="multiple-player"
:key="x"
:source="source"
ref="VueAliplayerV2"
:options="options"
/>
</template>
</div>
<p class="remove-text" v-if="!show">播放器已销毁!</p>
<div class="player-btns">
<template v-if="!isShowMultiple && show">
<span @click="play()">播放</span>
<span @click="pause()">暂停</span>
<span @click="replay()">重播</span>
<span @click="getCurrentTime()">播放时刻</span>
<span @click="getStatus()">获取播放器状态</span>
</template>
<span @click="show = !show">{{ show ? '销毁' : '重载' }}</span>
<span @click="options.isLive = !options.isLive"
>{{ options.isLive ? '切换普通模式' : '切换直播模式' }}</span
>
<span @click="showMultiple()"
>{{isShowMultiple ? '显示1个播放器' : '显示多个播放器'}}</span
>
</div>
<div class="source-box">
<span class="source-label">选择播放源(支持动态切换):</span>
<select v-model="source" name="source" id="source">
<option value="//player.alicdn.com/video/aliyunmedia.mp4"
>播放源1</option
>
<option value="//yunqivedio.alicdn.com/user-upload/nXPDX8AASx.mp4"
>播放源2</option
>
<option
value="//tbm-auth.alicdn.com/e7qHgLdugbzzKh2eW0J/kXTgBkjvNXcERYxh2PA@@hd_hq.mp4?auth_key=1584519814-0-0-fc98b2738f331ff015f7bf5c62394888"
>播放源3</option
>
<option value="//ivi.bupt.edu.cn/hls/cctv1.m3u8">直播播放源4</option>
</select>
</div>
<div class="source-box">
<span class="source-label">输入播放源(支持动态切换):</span>
<input class="source-input" type="text" v-model="source" />
</div>
</div>
:license="license"
low-latency
@ready="handleReady"
@error="handleError"
/>
</template>
<script>
export default {
data() {
return {
options: {
// source:'//player.alicdn.com/video/aliyunmedia.mp4',
isLive: true, //切换为直播流的时候必填
// format: 'm3u8' //切换为直播流的时候必填
},
source: "//player.alicdn.com/video/aliyunmedia.mp4",
// source: '//ivi.bupt.edu.cn/hls/cctv1.m3u8',
show: true,
isShowMultiple: false,
};
},
methods: {
play() {
this.$refs.VueAliplayerV2.play();
},
<script setup lang="ts">
import { reactive, ref } from 'vue';
import VueAliplayerV2, {
type AliplayerLicense,
type AliplayerOptions,
type VueAliplayerV2Expose
} from 'vue-aliplayer-v2';
pause() {
this.$refs.VueAliplayerV2.pause();
},
const playerRef = ref<VueAliplayerV2Expose | null>(null);
const source = ref('//player.alicdn.com/video/aliyunmedia.mp4');
const license = ref<AliplayerLicense | null>(null);
const options = reactive<AliplayerOptions>({
autoplay: true,
isLive: false,
useH5Prism: true
});
replay() {
this.$refs.VueAliplayerV2.replay();
},
function handleReady() {
playerRef.value?.play();
}
getCurrentTime() {
// this.$refs.VueAliplayerV2.getCurrentTime();
this.source = "http://ivi.bupt.edu.cn/hls/cctv1.m3u8";
},
getStatus() {
const status = this.$refs.VueAliplayerV2.getStatus();
console.log(`getStatus:`, status);
alert(`getStatus:${status}`);
},
showMultiple() {
this.isShowMultiple = !this.isShowMultiple;
},
},
};
function handleError(error: unknown) {
console.log('player error', error);
}
</script>
<style lang="less">
* {
margin: 0;
padding: 0;
}
.remove-text {
text-align: center;
padding: 20px;
font-size: 24px;
}
.show-multiple {
display: flex;
.multiple-player {
width: calc(100% / 4);
margin: 20px;
}
}
.player-btns {
width: 100%;
display: flex;
justify-content: center;
flex-wrap: wrap;
span {
margin: 0 auto;
display: inline-block;
padding: 5px 10px;
width: 150px;
height: 40px;
line-height: 40px;
border: 1px solid #eee;
background: #e1e1e1;
border-radius: 10px;
text-align: center;
margin: 5px;
cursor: pointer;
}
}
.source-box {
padding: 5px 10px;
margin-bottom: 20px;
.source-label {
margin-right: 20px;
font-size: 16px;
display: block;
}
#source {
margin-top: 10px;
}
.source-input {
margin-top: 10px;
padding: 5px 10px;
width: 80%;
border: 1px solid #ccc;
}
}
</style>
```
## 3.功能与配置
## Props
```javascript
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 片段或正则。 |
forbidFastForward: { //禁止拖拽快进
required: false,
type: [Boolean],
default: false
},
options: { //配置项 (options.source 不支持动态切换,需要动态切换请直接使用source)
required: false,
type: [Object],
default: () => null
},
## Events
source: { //播放源(此属性存在则优先于options.source) 支持动态切换,目前只支持同种格式mp4/flv/m3u8之间切换。暂不支持直播rtmp流切换。
required: false,
type: [Object],
default: () => null
},
组件会透传常用 Aliplayer 事件:
cssLink:{ //css版本源
required: false,
type: [String],
default: `https://g.alicdn.com/de/prismplayer/2.9.7/skins/default/aliplayer-min.css`
},
scriptSrc:{ //js版本源
required: false,
type: [String],
default: `https://g.alicdn.com/de/prismplayer/2.9.7/aliplayer-min.js`
}
```ts
ready
play
pause
canplay
playing
ended
liveStreamStop
onM3u8Retry
hideBar
showBar
waiting
timeupdate
snapshoted
requestFullScreen
cancelFullScreen
error
startSeek
completeSeek
sdk-error
```
`sdk-error` 是组件额外事件,在 Aliplayer SDK 脚本加载失败时触发。
## Ref 方法
```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();
```
完整暴露方法包括:
```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
```
## 多播放器
```vue
<template>
<VueAliplayerV2
v-for="item in sources"
:key="item"
:source="item"
:options="options"
/>
</template>
```
v2 的 SDK 加载器会复用同一份 CSS/JS 资源,多个播放器同时挂载时不会重复插入相同的 SDK 标签。
## License
阿里云新版 Web 播放器 SDK 要求配置网站域名和 License Key。可以通过 `license` prop 或 `options.license` 传入:
```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']"
:options="{
components: [
{
name: 'PlaylistComponent',
type: window.PlaylistComponent,
args: { list: playlist }
}
]
}"
/>
```
跑马灯、水印、播放列表等能力是否可用取决于所加载的 Aliplayer 自定义组件脚本。
## 失败重试
播放器错误页属于 Aliplayer SDK 内部 UI。业务侧可以监听 `error` 后使用组件暴露的 `retry()``reload()` 做局部重试:
```ts
function handleError() {
playerRef.value?.retry();
}
```
### 3.1 配置项 options 属性
可以参考 [属性和接口说明](https://help.aliyun.com/document_detail/125572.html?spm=a2c4g.11186623.6.1085.36fc6fc57WKZ5P)
| 名称 | 类型 | 说明 |
| :----------------------- | :-----------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| source | String | 视频播放地址 url单独 url。默认状态表示使用 vid+playauth。source 播放方式优先级最高。source 支持多清晰度设置source:{“HD”:”address1”,”SD”:”address2”详情参见多清晰度播放。 |
| vid | String | 媒体转码服务的媒体 Id。 |
| playauth | String | 播放权证,如何得到参见获取 playauth。 |
| height | String | 播放器高度可形如100%或者100px,chrome 浏览器下 flash 播放器分别不能小于 397x297。 |
| width | String | 播放器宽度可形如100%或者100px,chrome 浏览器下 flash 播放器分别不能小于 397x297。 |
| videoWidth | String | 视频宽度,仅 h5 支持。详情参见旋转和镜像。 |
| videoHeight | String | 视频高度,仅 h5 支持。详情参见旋转和镜像。 |
| preload | Boolean | 播放器自动加载,目前仅 h5 可用。 |
| cover | String | 播放器默认封面图片,请填写正确的图片 url 地址。需要 autoplay 为false才生效。Flash 播放器封面也需要开启允许跨域访问。 |
| isLive | Boolean | 播放内容是否为直播,直播时会禁止用户拖动进度条。 |
| autoplay | Boolean | 播放器是否自动播放,在移动端 autoplay 属性会失效。Safari11 不会自动开启自动播放如何开启。 |
| rePlay | Boolean | 播放器自动循环播放。 |
| useH5Prism | Boolean | 指定使用 H5 播放器。 |
| useFlashPrism | Boolean | 指定使用 Flash 播放器。 |
| playsinline | Boolean | H5 是否内置播放,有的 Android 浏览器不起作用。 |
| showBuffer | Boolean | 显示播放时缓冲图标,默认 true。 |
| skinRes | Url | 说明:皮肤图片,不建议随意修改该字段,如要修改,请参照皮肤定制。 |
| skinLayout | Array Boolean | 说明:功能组件布局配置,不传该字段使用默认布局。传 false 隐藏所有功能组件,请参照皮肤定制。 |
| controlBarVisibility | String | 控制面板的实现默认为hover。可选的值为clickhoveralways。 |
| showBarTime | String | 控制栏自动隐藏时间ms。 |
| extraInfo | String | 说明JSON 串用于定制性接口参数。 |
| > | > | 1.“fullTitle”“测试页面”全屏时显示视频标题仅 flash 支持)。 |
| > | > | 2. “m3u8BufferLength”“30”播放 m3u8 时加载缓存 ts 文件长度单位(秒)(仅 flash 支持)。 |
| > | > | 3. “liveStartTime”“2016/08/17 12:00:00”,直播开始时间,用于提示直播未开始(仅 flash 支持)。 |
| > | > | 4. “liveOverTime”“2016/08/17 14:00:00”,直播结束时间,用于提示直播结束(仅 flash 支持)。 |
| enableSystemMenu | Boolean | 是否允许系统右键菜单显示,默认为 false。 |
| format | String | 指定播放地址格式,只有使用 vid 的播放方式时支持,可选值为mp4m3u8flvmp3默认为空仅 H5 支持。 |
| mediaType | String | 指定返回音频还是视频,只有使用 vid 的播放方式时支持。可选值为videoaudio默认为video,audio主要是针对只包含音频的视频格式比如音频的 mp4仅 H5 支持。 |
| qualitySort | String | 指定排序方式,只有使用 vid + plauth 播放方式时支持。desc表示按倒序排序从大到小排序,asc表示按正序排序从小到大排序,默认值asc仅 H5 支持。 |
| definition | String | 显示视频清晰度多个用逗号分隔比如FD,LD此值是 vid 对应流清晰度的一个子集取值范围FD流畅LD标清SD高清HD超清OD原画2K2K4K4K仅 H5 支持。 |
| defaultDefinition | String | 默认视频清晰度,此值是 vid 对应流的一个清晰度取值范围FD流畅LD标清SD高清HD超清OD原画2K2K4K4K,仅 H5 支持。 |
| x5_type | String | 声明启用同层 H5 播放器启用时设置的值为h5,详情参见同层播放。 |
| x5_fullscreen | Boolean | 声明视频播放时是否进入到 TBS 的全屏模式,默认为 false。当需要把视频做为背景时设置为 true,详情参见同层播放。 |
| x5_video_position | String | 声明视频播在界面上的位置默认为“center”。可选值为“top”“center”,详情参见同层播放。 |
| x5_orientation | String | 声明 TBS 播放器支持的方向可选值landscape横屏,portraint竖屏,详情参见同层播放。 |
| x5LandscapeAsFullScreen | String | 声明 TBS 全屏播放是否横屏,默认值为 true。 |
| autoPlayDelay | Number | 延迟播放时间,单位为秒。详情参见延迟播放 |
| autoPlayDelayDisplayText | String | 延迟播放提示文本,详情参见延迟播放。 |
| language | String | 国际化默认为zh-cn。如果未设置则采用浏览器语言。可选值为zh-cnen-us或其它值。 |
| languageTexts | JSON | 自定义国际化文本 json 结构key 的值需要和 language 属性值对应起来。例子:{jp:{Play:”Play”}},自定义值参见 Json 结构。 |
| snapshot | Boolean | flash 启用截图功能。 |
| snapshotWatermark | Object | H5 设置截图水印。 |
| useHlsPluginForSafari | Boolean | Safari 浏览器可以启用 Hls 插件播放Safari 11 除外。 |
| enableStashBufferForFlv | Boolean | H5 播放 flv 时,设置是否启用播放缓存,只在直播下起作用。 |
| stashInitialSizeForFlv | Number | H5 播放 flv 时,初始缓存大小,只在直播下起作用。 |
| loadDataTimeout | Number | 缓冲多长时间后提示用户切换低清晰度默认20 秒。 |
| waitingTimeout | Number | 最大缓冲超时时间超过这个时间会有错误提示默认60 秒。 |
| liveStartTime | String | 直播开始时间直播时移功能使用格式为“2018/01/04 12:00:00”。 |
| liveOverTime | String | 直播结束时间直播时移功能使用格式为“2018/01/04 12:00:00”。 |
| liveTimeShiftUrl | String | 直播可用时移查询地址,详情参见直播时移。 |
| liveShiftSource | String | flv 直播地址播放时hls 的流地址,详情参见直播时移。 |
| recreatePlayer | Function | flv 直播和 hls 时移切换是,重新创建播放器方法,详情参见直播时移。 |
| diagnosisButtonVisible | Boolean | 是否显示检测按钮,默认为 true。 |
| disableSeek | Boolean | 禁用进度条的 Seek默认为 false仅 Flash 支持。 |
| encryptType | int | 加密类型,播放点播私有加密视频时,设置值为 1默认值为 0。 |
| progressMarkers | Array | 进度条打点内容数组,详情参见进度条打点。 |
| vodRetry | int | 点播失败重试次数,默认 3 次 |
| liveRetry | int | 直播播放失败重试次数,默认 5 次 |
### 3.2 播放器方法
```javascript
// 暂停播放
this.$refs.VueAliplayerV2.pause();
```
可以参考 [播放器接口方法](https://help.aliyun.com/document_detail/125572.html?spm=a2c4g.11186623.6.1085.36fc6fc57WKZ5P#h2-u64ADu653Eu5668u63A5u53E32)
| 名称 | 参数 | 说明 |
| :--------------------- | :------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| play | - | 播放视频。 |
| pause | - | 暂停视频。 |
| replay | - | 重播视频。 |
| seek | time | 跳转到某个时刻进行播放time 的单位为秒。 |
| getCurrentTime | - | 获取当前的播放时刻,返回的单位为秒。 |
| getDuration | - | 获取视频总时长,返回的单位为秒,这个需要在视频加载完成以后才可以获取到,可以在 play 事件后获取。 |
| getVolume | - | 获取当前的音量,返回值为 0-1 的实数。ios 和部分 android 会失效。 |
| setVolume | - | 设置音量vol 为 0-1 的实数ios 和部分 android 会失效。 |
| loadByUrl | urltime | 直接播放视频 urltime 为可选值单位秒。目前只支持同种格式mp4/flv/m3u8之间切换。暂不支持直播 rtmp 流切换。 |
| replayByVidAndPlayAuth | vid视频 id,playauth播放凭证 | 目前只支持 H5 播放器。暂不支持不同格式视频间的之间切换。暂不支持直播 rtmp 流切换。 |
| replayByVidAndAuthInfo | 仅 MPS 用户时使用,参数顺序为vid、accId、accSecret、stsToken、authInfo、domainRegion | 目前只支持 H5 播放器。暂不支持不同格式视频间的之间切换。暂不支持直播 rtmp 流切换。 |
| setPlayerSize | wh | 设置播放器大小 wh 可分别为 400px 像素或 60%百分比。chrome 浏览器下 flash 播放器分别不能小于 397x297。 |
| setSpeed | speed | 手动设置播放的倍速,倍速播放仅 H5 支持。移动端可能会失效,比如 android 微信。倍速播放 UI 默认是开启的。如果自定义过 skinLaout 属性,需要添加 speedButton 项到数组里: |
| > | > | {name“speedButton”align“tr”x10y23} |
| setSanpshotProperties | width宽度,height高度,rate截图质量 | 设置截图参数。 |
| requestFullScreen | - | 播放器全屏,仅 H5 支持。 |
| cancelFullScreen | - | 播放器退出全屏iOS 调用无效,仅 H5 支持。 |
| getIsFullScreen | - | 获取播放器全屏状态,仅 H5 支持。 |
| getStatus | - | 获取播放器状态包含的值init,ready,loading,play,pause,playing,waiting,error,ended |
| setLiveTimeRange | 开始时间,结束时间 | 设置直播的开始结束时间开启直播时移功能时使用。例子player.liveShiftSerivce.setLiveTimeRange(“”2018/01/04 20:00:00) |
| setRotate | rotate 旋转角度 | 参数为旋转角度, 正数为正时针旋转, 负数为逆时针旋转。例如: setRotate(90)。详情参见旋转和镜像。 |
| getRotate | - | 获取旋转角度。详情参见旋转和镜像。 |
| setImage | image镜像类型,可选值为horizon,vertical | 设置镜像,例如: setImage(horizon)。详情参见旋转和镜像。 |
| dispose | - | 播放器销毁 |
| setCover | cover 封面地址 | 设置封面 |
| setProgressMarkers | markers 打点数据集合 | 设置打点数据 |
| setPreviewTime | time 试看时间 | 设置试看时间,单位为秒,详情参见试看 |
| getPreviewTime | - | 获取试看时间 |
| isPreview | - | 是否试看 |
| off | ev:事件名[String],handle,事件回调方法[Function] | 通过播放器实例的 off 方法取消绑定的方法 |
## 4.播放器事件
```html
<template>
<vue-aliplayer-v2 @ready="handleReady" />
</template>
<script>
export default {
methods: {
/**
* 播放器事件回调
*/
handleReady(e) {
console.log(`ready`, e);
},
},
};
</script>
```
可以参考 [播放器事件](https://help.aliyun.com/document_detail/125572.html?spm=a2c4g.11186623.6.1085.36fc6fc57WKZ5P#h2-u64ADu653Eu5668u4E8Bu4EF63)
| 名称 | 说明 |
| :---------------- | :--------------------------------------------------------------------------------------------------------------------------------------- |
| ready | 播放器视频初始化按钮渲染完毕。播放器 UI 初始设置需要此事件后触发,避免 UI 被初始化所覆盖。播放器提供的方法需要在此事件发生后才可以调用。 |
| play | 视频由暂停恢复为播放时触发。 |
| pause | 视频暂停时触发。 |
| canplay | 能够开始播放音频/视频时发生,会多次触发,仅 H5 播放器。 |
| playing | 播放中,会触发多次。 |
| ended | 当前视频播放完毕时触发。 |
| liveStreamStop | 直播流中断时触发。m3u8/flv/rtmp 在重试 5 次未成功后触发。提示上层流中断或需要重新加载视频。PSm3u8 一直自动重试,不需要上层添加重试。 |
| onM3u8Retry | m3u8 直播流中断后重试事件,每次断流只触发一次。 |
| hideBar | 控制栏自动隐藏事件。 |
| showBar | 控制栏自动显示事件。 |
| waiting | 数据缓冲事件。 |
| timeupdate | 播放位置发生改变时触发,仅 H5 播放器。可通过 getCurrentTime 方法,得到当前播放时间。 |
| snapshoted | 截图完成事件。 |
| requestFullScreen | 全屏事件,仅 H5 支持。 |
| cancelFullScreen | 取消全屏事件iOS 下不会触发,仅 H5 支持。 |
| error | 错误事件。 |
| startSeek | 开始拖拽,参数返回拖拽点的时间。 |
| completeSeek | 完成拖拽,参数返回拖拽点的时间。 |
---
## 5. 图片展示
![图片1](https://github.com/langyuxiansheng/vue-aliplayer-v2/blob/master/images/p1.png)
![图片2](https://github.com/langyuxiansheng/vue-aliplayer-v2/blob/master/images/p2.png)
---
## 6.二次开发 下载项目
## 开发
```bash
git clone https://github.com/langyuxiansheng/vue-aliplayer-v2.git
```
## Project setup
```bash
cd vue-aliplayer-v2
npm install
```
### Compiles and hot-reloads for development
```bash
npm run dev
```
### Compiles and minifies for production
构建 demo
```bash
npm run build
```
### Lints and fixes files
构建组件库:
```bash
npm run lint
npm run type-check
npm run lib
```
## 7.缺陷 & 后期计划
## 迁移说明
> 2019 年 12 月 27 日 更新优化局部引用方式
> 您有功能建议,或者 bug 反馈请留言.
## 8.感谢
---
- vue-aliplayer 的作者,项目地址:https://github.com/slacrey/vue-aliplayer
- Alipayer 阿里云的开源播放器 https://help.aliyun.com/document_detail/125572.html?spm=a2c4g.11186623.6.1085.36fc6fc57WKZ5P#h2-u64ADu653Eu5668u4E8Bu4EF63
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
## 更新日志
> v1.3.0 修复部分已知bug,增加forbidFastForward 属性,感谢 "william-xue"网友的功能提交.
- sdk 版本更新,默认 SDK 版本由 2.9.3 更新为 2.9.20
- 默认注释更新
- 增加禁止用户拖拽快进的属性选项 forbidFastForward [Boolean] 默认 false
> v1.2.9 修正部分默认属性, 感谢"Schean17"网友的反馈与建议.
> v1.2.8 更换底层默认 sdk 版本为 2.9.3 修复 options 遇到 update loop 错误 感谢"litmonw"网友的反馈与建议.
> v1.2.7 更换底层默认 sdk 版本为 2.9.1 的版本. 更新线上演示 demo 的选项
> v1.2.6 优化 beforeDestroy() 部分的代码.
> v1.2.5 更换默认的播放器 SDK 版本 2.8.2 => 2.9.0,2.8.2 的版本存在多个播放器同时播放直播流异常的 bug,增加了全局 SDK 版本配置,可以在 Vue.use()的时候进行配置.
> v1.2.4 修复多个播放器加载,只初始化一个播放器的 bug.文档部分更新,增加了问题栏. 感谢"沙洲 ad"的反馈与建议.
> v1.2.3 优化播放器的初始化代码,调整包内结构,优化局部组件的注册方式,也兼容老版本的引用方式.文档部分更新,增加了问题栏. 感谢"liangzhiyuan2015"和"fancheur"两位网友的反馈与建议.
> v1.2.2 修复指定 id 情况下,播放器报错"没有为播放器指定容器",目前移除外部指定 id 的方式,所有的播放器 id 都由内部生成,不再由外部指定容器(外部指定的意义并不大),其它的说明:更新 1.2.1 后报错 Uncaught TypeError: 没有为播放器指定容器,因为源码中变更了部分代码,以及最大限度的简化代码,组件内部的根容器就只有一个 div 容器,导致以前外部指定 id 的时候,容器 id 与外部的不一致,导致抛出异常了,现在已经紧急修复了,若在使用,请更新到 v1.2.2 的版本;如果使用了外部指定 id 的方式请移除外部的 id.否则 id 会出现重复的情况., 感谢"liyoro"的反馈和建议.
> v1.2.1 修复直播播放的情况下,播放器已经销毁,而后台还在继续下载资源造成卡顿的 bug,修复多个播放器只渲染 1 个的 bug, 感谢"Jonauil"和"guangming95"两位网友的反馈和建议.
> v1.2.0 修复播放源(MP4/m3u8)之间切换无法正常播放的 bug,增加 options 配置项动态响应功能,优化部分播放器的逻辑, 感谢"liyoro"网友的反馈和建议.
> v1.1.9 修复播放源(修复 prop:source 类型验证报错), 感谢"hugo2017"和“nullF”网友的反馈.
> v1.1.8 修复播放源(增加 source 属性类型错误),修复获取播放器状态 getStatus()方法报错, 感谢"kongjigu"网友的反馈.
> v1.1.7 增加动态切换播放源(增加 source 属性)功能 感谢"wikimo"和"jieruian"两位网友的反馈建议.
> v1.1.6 修复部分已知 bug 和优化局部的引用方式
---
## 其它问题
1. IOS 或者其它设备无法全屏播放,或者点击全屏按钮的时候也只是显示竖屏?
> 方案与问题所在:
一般情况下可能是开启了强制竖屏(也就是屏幕锁定)打开后就会竖屏而不会全屏了!如下关闭就可以了:
参考 issues: https://github.com/langyuxiansheng/vue-aliplayer-v2/issues/25
-开启了屏幕锁定,只要上拉控制中心,点击屏幕锁定关闭就可以了!
-也可能是播放器或者浏览器兼容性问题.
- v2 仅支持 Vue 3。
- 入口从 Vue 2 插件模式迁移为 Vue 3 `app.use(...)`
- 组件 ref 需要通过 `ref<VueAliplayerV2Expose | null>` 使用。
- 旧版本用户请安装 `vue-aliplayer-v2@1.x`

View File

@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/app'
]
}

1
dist/assets/index-BwNqcy-4.css vendored Normal file

File diff suppressed because one or more lines are too long

6
dist/assets/index-Cx06WFub.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
[data-v-9b14a31e]{margin:0;padding:0}.remove-text[data-v-9b14a31e]{text-align:center;padding:20px;font-size:24px}.player-btns[data-v-9b14a31e]{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-wrap:wrap;flex-wrap:wrap}.player-btns span[data-v-9b14a31e]{margin:0 auto;display:inline-block;padding:5px 10px;width:150px;height:40px;line-height:40px;border:1px solid #eee;background:#e1e1e1;border-radius:10px;text-align:center;margin:20px auto;cursor:pointer}

15
dist/index.html vendored
View File

@ -1 +1,14 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/vue-aliplayer-v2/favicon.ico><title>vue-aliplayer-v2</title><link href=/vue-aliplayer-v2/css/index.06951331.css rel=preload as=style><link href=/vue-aliplayer-v2/js/chunk-vendors.69bad5f4.js rel=preload as=script><link href=/vue-aliplayer-v2/js/index.b7f22f1a.js rel=preload as=script><link href=/vue-aliplayer-v2/css/index.06951331.css rel=stylesheet></head><body><noscript><strong>We're sorry but vue-aliplayer-v2 doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/vue-aliplayer-v2/js/chunk-vendors.69bad5f4.js></script><script src=/vue-aliplayer-v2/js/index.b7f22f1a.js></script></body></html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-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">
</head>
<body>
<div id="app"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

8
env.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, unknown>;
export default component;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +0,0 @@
import Vue from 'vue';
import App from './App.vue';
import VueAliplayerV2 from '../packages';
Vue.use(VueAliplayerV2,{
// cssLink: 'https://g.alicdn.com/de/prismplayer/2.8.2/skins/default/aliplayer-min.css',
// scriptSrc: 'https://g.alicdn.com/de/prismplayer/2.8.2/aliplayer-min.js'
});
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
}).$mount('#app');

4
examples/main.ts Normal file
View File

@ -0,0 +1,4 @@
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/favicon.ico">
<title>vue-aliplayer-v2</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/examples/main.ts"></script>
</body>
</html>

View File

@ -1,10 +0,0 @@
<meta charset="utf-8">
<title>vue-aliplayer-v2 demo</title>
<script src="./vue-aliplayer-v2.umd.js"></script>
<link rel="stylesheet" href="./vue-aliplayer-v2.css">
<script>
console.log(vue-aliplayer-v2)
</script>

59
lib/types/AliplayerV2/index.vue.d.ts vendored Normal file
View File

@ -0,0 +1,59 @@
import type { AliplayerOptions, AliplayerV2Props, VueAliplayerV2Expose } from './types';
declare const __VLS_export: import("vue").DefineComponent<AliplayerV2Props, VueAliplayerV2Expose, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
canplay: (payload?: unknown) => any;
ended: (payload?: unknown) => any;
error: (payload?: unknown) => any;
pause: (payload?: unknown) => any;
play: (payload?: unknown) => any;
playing: (payload?: unknown) => any;
timeupdate: (payload?: unknown) => any;
waiting: (payload?: unknown) => any;
ready: (payload?: unknown) => any;
liveStreamStop: (payload?: unknown) => any;
onM3u8Retry: (payload?: unknown) => any;
hideBar: (payload?: unknown) => any;
showBar: (payload?: unknown) => any;
snapshoted: (payload?: unknown) => any;
requestFullScreen: (payload?: unknown) => any;
cancelFullScreen: (payload?: unknown) => any;
startSeek: (payload?: unknown) => any;
completeSeek: (payload?: unknown) => any;
"sdk-error": (error: Error) => any;
}, string, import("vue").PublicProps, Readonly<AliplayerV2Props> & Readonly<{
onCanplay?: ((payload?: unknown) => any) | undefined;
onEnded?: ((payload?: unknown) => any) | undefined;
onError?: ((payload?: unknown) => any) | undefined;
onPause?: ((payload?: unknown) => any) | undefined;
onPlay?: ((payload?: unknown) => any) | undefined;
onPlaying?: ((payload?: unknown) => any) | undefined;
onTimeupdate?: ((payload?: unknown) => any) | undefined;
onWaiting?: ((payload?: unknown) => any) | undefined;
onReady?: ((payload?: unknown) => any) | undefined;
onLiveStreamStop?: ((payload?: unknown) => any) | undefined;
onOnM3u8Retry?: ((payload?: unknown) => any) | undefined;
onHideBar?: ((payload?: unknown) => any) | undefined;
onShowBar?: ((payload?: unknown) => any) | undefined;
onSnapshoted?: ((payload?: unknown) => any) | undefined;
onRequestFullScreen?: ((payload?: unknown) => any) | undefined;
onCancelFullScreen?: ((payload?: unknown) => any) | undefined;
onStartSeek?: ((payload?: unknown) => any) | undefined;
onCompleteSeek?: ((payload?: unknown) => any) | undefined;
"onSdk-error"?: ((error: Error) => any) | undefined;
}>, {
source: string | null;
autoFormat: boolean;
forbidFastForward: boolean;
license: import("./types").AliplayerLicense | null;
lowLatency: boolean;
normalizeSourceUrl: boolean;
options: AliplayerOptions | null;
sdkVersion: string;
cssLink: string;
scriptSrc: string;
componentScripts: string[];
disableTracking: boolean;
trackingUrlPatterns: Array<string | RegExp>;
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
declare const _default: typeof __VLS_export;
export default _default;
//# sourceMappingURL=index.vue.d.ts.map

12
lib/types/AliplayerV2/sdkLoader.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
export declare const DEFAULT_SDK_VERSION = "2.37.0";
export declare const DEFAULT_CSS_LINK = "https://g.alicdn.com/apsara-media-box/imp-web-player/2.37.0/skins/default/aliplayer-min.css";
export declare const DEFAULT_SCRIPT_SRC = "https://g.alicdn.com/apsara-media-box/imp-web-player/2.37.0/aliplayer-min.js";
export declare function getCssLinkByVersion(version: string): string;
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.
*/
export declare function loadAliplayerSdk(cssLink: string, scriptSrc: string): Promise<void>;
export declare function loadExtraScripts(scriptUrls?: string[]): Promise<void>;
//# sourceMappingURL=sdkLoader.d.ts.map

4
lib/types/AliplayerV2/source.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
export type SourceFormat = 'mp4' | 'm3u8' | 'flv' | 'rtmp' | 'mp3' | null;
export declare function inferSourceFormat(source?: string | null): SourceFormat;
export declare function normalizeSource(source?: string | null): string | null;
//# sourceMappingURL=source.d.ts.map

7
lib/types/AliplayerV2/tracking.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
export declare function installTrackingBlocker(patterns?: Array<string | RegExp>): void;
declare global {
interface XMLHttpRequest {
__vueAliplayerV2Blocked?: boolean;
}
}
//# sourceMappingURL=tracking.d.ts.map

128
lib/types/AliplayerV2/types.d.ts vendored Normal file
View File

@ -0,0 +1,128 @@
import type { Plugin } from 'vue';
export interface VueAliplayerV2Options {
sdkVersion?: string;
cssLink?: string;
scriptSrc?: string;
componentScripts?: string[];
disableTracking?: boolean;
trackingUrlPatterns?: Array<string | RegExp>;
}
export interface AliplayerV2Props extends VueAliplayerV2Options {
autoFormat?: boolean;
forbidFastForward?: boolean;
license?: AliplayerLicense | null;
lowLatency?: boolean;
normalizeSourceUrl?: boolean;
options?: AliplayerOptions | null;
source?: string | null;
}
export interface AliplayerLicense {
domain: string;
key: string;
}
export type AliplayerEventName = 'ready' | 'play' | 'pause' | 'canplay' | 'playing' | 'ended' | 'liveStreamStop' | 'onM3u8Retry' | 'hideBar' | 'showBar' | 'waiting' | 'timeupdate' | 'snapshoted' | 'requestFullScreen' | 'cancelFullScreen' | 'error' | 'startSeek' | 'completeSeek';
export interface AliplayerOptions {
id?: string;
source?: string;
width?: string;
autoplay?: boolean;
isLive?: boolean;
format?: string;
license?: AliplayerLicense;
vid?: string;
playauth?: string;
accessKeyId?: string;
securityToken?: string;
accessKeySecret?: string;
region?: string;
authTimeout?: number;
components?: unknown[];
enableStashBufferForFlv?: boolean;
stashInitialSizeForFlv?: number;
rtsVersion?: string;
[key: string]: unknown;
}
export interface AliplayerFullscreenService {
requestFullScreen: () => void;
cancelFullScreen: () => void;
getIsFullScreen: () => boolean;
}
export interface AliplayerLiveShiftService {
setLiveTimeRange: (beginTime: string, endTime: string) => void;
}
export interface AliplayerInstance {
on: (eventName: string, handler: (event?: unknown) => void) => void;
off?: (eventName: string, handler: (event?: unknown) => void) => void;
play: () => void;
pause: () => void;
replay: () => void;
seek: (time: number) => void;
getCurrentTime: () => number;
getDuration: () => number;
getVolume: () => number;
setVolume: (volume: number) => void;
loadByUrl: (url: string, time?: number) => void;
replayByVidAndPlayAuth: (vid: string, playauth: string) => void;
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;
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;
}
export interface VueAliplayerV2Expose {
getPlayer: () => AliplayerInstance | null;
init: () => Promise<void>;
initPlayer: () => void;
reload: (nextSource?: string) => Promise<void>;
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;
loadByUrl: (url: string, time?: number) => void;
replayByVidAndPlayAuth: (vid: string, playauth: string) => void;
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;
}
export type VueAliplayerV2Plugin = Plugin & {
Player?: unknown;
};
declare global {
interface Window {
Aliplayer?: (options: AliplayerOptions) => AliplayerInstance;
[key: string]: unknown;
}
}
//# sourceMappingURL=types.d.ts.map

32
lib/types/index.d.ts vendored Normal file
View File

@ -0,0 +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';
type InstallableVueAliplayerV2 = typeof VueAliplayerV2 & {
install: (app: App, options?: VueAliplayerV2Options) => void;
Player: typeof VueAliplayerV2;
props: {
sdkVersion: {
default: string | (() => string);
};
cssLink: {
default: string | (() => string);
};
scriptSrc: {
default: string | (() => string);
};
componentScripts: {
default: string[] | (() => string[]);
};
disableTracking: {
default: boolean | (() => boolean);
};
trackingUrlPatterns: {
default: Array<string | RegExp> | (() => Array<string | RegExp>);
};
};
};
declare const installable: InstallableVueAliplayerV2;
export { installable as VueAliplayerV2 };
export default installable;
//# sourceMappingURL=index.d.ts.map

File diff suppressed because it is too large Load Diff

2
lib/vue-aliplayer-v2.css Normal file
View File

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

395
lib/vue-aliplayer-v2.js Normal file
View File

@ -0,0 +1,395 @@
import { computed as e, createElementBlock as t, defineComponent as n, nextTick as r, onBeforeUnmount as i, onMounted as a, openBlock as o, ref as s, watch as c } from "vue";
//#region packages/AliplayerV2/sdkLoader.ts
var l = "2.37.0", u = `https://g.alicdn.com/apsara-media-box/imp-web-player/${l}/skins/default/aliplayer-min.css`, d = `https://g.alicdn.com/apsara-media-box/imp-web-player/${l}/aliplayer-min.js`, f = /* @__PURE__ */ new Map();
function p(e) {
return `https://g.alicdn.com/apsara-media-box/imp-web-player/${e}/skins/default/aliplayer-min.css`;
}
function m(e) {
return `https://g.alicdn.com/apsara-media-box/imp-web-player/${e}/aliplayer-min.js`;
}
function h(e) {
if (!e || document.querySelector(`link[data-vue-aliplayer-v2-css="${e}"]`)) return;
let t = document.createElement("link");
t.type = "text/css", t.rel = "stylesheet", t.href = e, t.dataset.vueAliplayerV2Css = e, document.head.appendChild(t);
}
function g(e, t) {
if (!e || t && window[t]) return Promise.resolve();
if (f.has(e)) return f.get(e);
let n = document.querySelector(`script[data-vue-aliplayer-v2-js="${e}"]`);
if (n) {
let t = new Promise((t, r) => {
if (n.dataset.vueAliplayerV2Loaded === "true") {
t();
return;
}
n.addEventListener("load", () => {
n.dataset.vueAliplayerV2Loaded = "true", t();
}, { once: !0 }), n.addEventListener("error", () => r(/* @__PURE__ */ Error(`Failed to load Aliplayer SDK: ${e}`)), { once: !0 });
});
return f.set(e, t), t;
}
let r = new Promise((t, n) => {
let r = document.createElement("script");
r.type = "text/javascript", r.src = e, r.dataset.vueAliplayerV2Js = e, r.addEventListener("load", () => {
r.dataset.vueAliplayerV2Loaded = "true", t();
}, { once: !0 }), r.addEventListener("error", () => n(/* @__PURE__ */ Error(`Failed to load Aliplayer SDK: ${e}`)), { once: !0 }), document.documentElement.appendChild(r);
});
return f.set(e, r), r;
}
async function _(e, t) {
h(e), await g(t, "Aliplayer");
}
async function v(e = []) {
for (let t of e) await g(t);
}
//#endregion
//#region packages/AliplayerV2/source.ts
var y = [
"m3u8",
"flv",
"mp4",
"rtmp",
"mp3"
];
function b(e) {
if (!e) return null;
if (/^rtmps?:\/\//i.test(e)) return "rtmp";
let t = e.split("?")[0].split("#")[0].toLowerCase();
return y.find((e) => e && t.endsWith(`.${e}`)) || null;
}
function x(e) {
if (!e) return e || null;
if (/^(data|blob):/i.test(e)) return e;
try {
return encodeURI(e);
} catch {
return e;
}
}
//#endregion
//#region packages/AliplayerV2/tracking.ts
var S = ["videocloud.cn-hangzhou.log.aliyuncs.com/logstores/newplayer/track"], C = !1;
function w(e, t) {
return t.some((t) => typeof t == "string" ? e.includes(t) : t.test(e));
}
function T(e = S) {
if (C) return;
C = !0;
let t = window.fetch?.bind(window);
t && (window.fetch = ((n, r) => w(typeof n == "string" || n instanceof URL ? String(n) : n.url, e) ? Promise.resolve(new Response(null, {
status: 204,
statusText: "No Content"
})) : t(n, r)));
let n = XMLHttpRequest.prototype.open, r = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(t, r, i, a, o) {
this.__vueAliplayerV2Blocked = w(String(r), e), n.call(this, t, r, i ?? !0, a ?? void 0, o ?? void 0);
}, XMLHttpRequest.prototype.send = function(e) {
this.__vueAliplayerV2Blocked || r.call(this, e);
};
}
//#endregion
//#region packages/AliplayerV2/index.vue
var E = /* @__PURE__ */ ((e, t) => {
let n = e.__vccOpts || e;
for (let [e, r] of t) n[e] = r;
return n;
})(/* @__PURE__ */ n({
name: "VueAliplayerV2",
__name: "index",
props: {
autoFormat: {
type: Boolean,
default: !0
},
forbidFastForward: {
type: Boolean,
default: !1
},
license: { default: null },
lowLatency: {
type: Boolean,
default: !1
},
normalizeSourceUrl: {
type: Boolean,
default: !0
},
options: { default: null },
source: { default: null },
sdkVersion: { default: l },
cssLink: { default: "" },
scriptSrc: { default: "" },
componentScripts: { default: () => [] },
disableTracking: {
type: Boolean,
default: !1
},
trackingUrlPatterns: { default: () => [] }
},
emits: [
"ready",
"play",
"pause",
"canplay",
"playing",
"ended",
"liveStreamStop",
"onM3u8Retry",
"hideBar",
"showBar",
"waiting",
"timeupdate",
"snapshoted",
"requestFullScreen",
"cancelFullScreen",
"error",
"startSeek",
"completeSeek",
"sdk-error"
],
setup(n, { expose: l, emit: f }) {
let h = n, g = f, y = s(null), S = s(null), C = s(!1), w = s(null), E = 0, D = `player-${Math.random().toString(36).slice(2).toUpperCase()}`, O = [
"ready",
"play",
"pause",
"canplay",
"playing",
"ended",
"liveStreamStop",
"onM3u8Retry",
"hideBar",
"showBar",
"waiting",
"timeupdate",
"snapshoted",
"requestFullScreen",
"cancelFullScreen",
"error",
"startSeek",
"completeSeek"
], k = e(() => h.cssLink || (h.sdkVersion ? p(h.sdkVersion) : u)), A = e(() => h.scriptSrc || (h.sdkVersion ? m(h.sdkVersion) : d)), j = e(() => h.normalizeSourceUrl ? x(h.source) : h.source), M = e(() => b(j.value)), N = e(() => {
let e = {
width: "100%",
autoplay: !0,
...h.options || {},
...h.license ? { license: h.license } : {},
...j.value ? { source: j.value } : {},
id: D
};
return h.autoFormat && M.value && !e.format && (e.format = M.value), h.lowLatency && e.isLive && M.value === "flv" && (e.enableStashBufferForFlv = e.enableStashBufferForFlv ?? !1, e.stashInitialSizeForFlv = e.stashInitialSizeForFlv ?? 128), e;
});
function P() {
return y.value;
}
function F() {
y.value && (y.value.dispose(), y.value = null, w.value = null);
}
function I(e) {
O.forEach((t) => {
e.on(t, (e) => {
g(t, e);
});
});
}
function L(e) {
if (!h.forbidFastForward) return;
let t = 0, n = 0;
e.on("timeupdate", () => {
let r = e.getCurrentTime();
if (r - t > 2) {
e.seek(t);
return;
}
t = r, t >= n && (n = t);
});
}
function R() {
if (C.value || !window.Aliplayer || !S.value) return;
F();
let e = window.Aliplayer({ ...N.value });
y.value = e, w.value = M.value, I(e), L(e);
}
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;
R();
} catch (e) {
g("sdk-error", e instanceof Error ? e : Error(String(e)));
}
}
async function B(e) {
if (e && y.value) {
y.value.loadByUrl(h.normalizeSourceUrl && x(e) || e);
return;
}
await z();
}
async function V(e) {
await B(e);
}
function H() {
y.value?.play();
}
function U() {
y.value?.pause();
}
function W() {
y.value?.replay();
}
function G(e) {
y.value?.seek(e);
}
function K() {
return y.value?.getCurrentTime();
}
function ee() {
return y.value?.getDuration();
}
function q() {
return y.value?.getVolume();
}
function J(e) {
y.value?.setVolume(e);
}
function Y(e, t) {
y.value?.loadByUrl(e, t);
}
function X(e, t) {
y.value?.replayByVidAndPlayAuth(e, t);
}
function Z(e, t, n, r, i, a) {
y.value?.replayByVidAndAuthInfo(e, t, n, r, i, a);
}
function Q(e, t) {
y.value?.setPlayerSize(e, t);
}
function $(e) {
y.value?.setSpeed(e);
}
function te(e, t, n) {
y.value?.setSanpshotProperties(e, t, n);
}
function ne() {
y.value?.fullscreenService?.requestFullScreen();
}
function re() {
y.value?.fullscreenService?.cancelFullScreen();
}
function ie() {
return y.value?.fullscreenService?.getIsFullScreen();
}
function ae() {
return y.value?.getStatus();
}
function oe(e, t) {
y.value?.liveShiftSerivce?.setLiveTimeRange(e, t);
}
function se(e) {
y.value?.setRotate(e);
}
function ce() {
return y.value?.getRotate();
}
function le(e) {
y.value?.setImage(e);
}
function ue(e) {
y.value?.setCover(e);
}
function de(e) {
y.value?.setProgressMarkers(e);
}
function fe(e) {
y.value?.setPreviewTime(e);
}
function pe() {
return y.value?.getPreviewTime();
}
function me() {
return y.value?.isPreview();
}
function he(e, t) {
y.value?.off?.(e, t);
}
return a(() => {
z();
}), c(() => j.value, (e) => {
if (!e) return;
let t = b(e);
if (y.value && t && t === w.value) {
y.value.loadByUrl(e);
return;
}
z();
}), c(() => [
h.options,
h.forbidFastForward,
h.license,
h.lowLatency,
h.autoFormat,
h.sdkVersion,
h.cssLink,
h.scriptSrc,
h.componentScripts,
h.disableTracking,
h.trackingUrlPatterns
], () => {
z();
}, { deep: !0 }), i(() => {
C.value = !0, E += 1, F();
}), l({
getPlayer: P,
init: z,
initPlayer: R,
reload: B,
retry: V,
play: H,
pause: U,
replay: W,
seek: G,
getCurrentTime: K,
getDuration: ee,
getVolume: q,
setVolume: J,
loadByUrl: Y,
replayByVidAndPlayAuth: X,
replayByVidAndAuthInfo: Z,
setPlayerSize: Q,
setSpeed: $,
setSanpshotProperties: te,
requestFullScreen: ne,
cancelFullScreen: re,
getIsFullScreen: ie,
getStatus: ae,
setLiveTimeRange: oe,
setRotate: se,
getRotate: ce,
setImage: le,
dispose: F,
setCover: ue,
setProgressMarkers: de,
setPreviewTime: fe,
getPreviewTime: pe,
isPreview: me,
off: he
}), (e, n) => (o(), t("div", {
id: D,
ref_key: "containerRef",
ref: S,
class: "vue-aliplayer-v2"
}, null, 512));
}
}), [["__scopeId", "data-v-cf852d0d"]]), D = {
sdkVersion: l,
cssLink: u,
scriptSrc: d,
componentScripts: [],
disableTracking: !1,
trackingUrlPatterns: []
}, O = E;
O.install = (e, t) => {
Object.assign(D, t || {}), e.component("VueAliplayerV2", O);
}, O.props.cssLink.default = () => D.cssLink, O.props.scriptSrc.default = () => D.scriptSrc, O.props.sdkVersion.default = () => D.sdkVersion, O.props.componentScripts.default = () => D.componentScripts, O.props.disableTracking.default = () => D.disableTracking, O.props.trackingUrlPatterns.default = () => D.trackingUrlPatterns, O.Player = O;
//#endregion
export { O as VueAliplayerV2, O as default };

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

1250
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +1,44 @@
{
"name": "vue-aliplayer-v2",
"version": "1.3.1",
"version": "2.0.0",
"type": "module",
"author": "yxs",
"description": "感谢每一位支持开源的朋友. 这是一个基于Alipayer 开发并封装成vue组件的集成播放器.可播放rtmp,m3u8,mp4....视频.除支持直播流与点播的基础功能外,也支持视频的加密播放、清晰度切换、直播时移等业务场景",
"main": "lib/vue-aliplayer-v2.umd.min.js",
"description": "Vue 3 wrapper for Aliyun Aliplayer. Supports mp4, m3u8, flv, live streams, encrypted playback, quality switching, and live time shift through the official Aliplayer SDK.",
"main": "lib/vue-aliplayer-v2.umd.cjs",
"module": "lib/vue-aliplayer-v2.js",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/types/index.d.ts",
"import": "./lib/vue-aliplayer-v2.js",
"require": "./lib/vue-aliplayer-v2.umd.cjs"
}
},
"files": [
"lib",
"README.md"
],
"private": false,
"license": "MIT",
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"lib": "vue-cli-service build --target lib --name vue-aliplayer-v2 --dest lib packages/index.js"
"dev": "vite --host 0.0.0.0",
"build": "vite build --mode demo",
"type-check": "vue-tsc --noEmit",
"lib": "vue-tsc -p tsconfig.build.json --declaration --emitDeclarationOnly --outDir lib/types && vite build --mode lib",
"lint": "vue-tsc --noEmit"
},
"dependencies": {
"core-js": "^2.6.5",
"vue": "^2.6.10"
"vue": "^3.5.34"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.8.0",
"@vue/cli-plugin-eslint": "^3.8.0",
"@vue/cli-service": "^3.8.0",
"babel-eslint": "^10.0.1",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"less": "^3.9.0",
"less-loader": "^5.0.0",
"vue-template-compiler": "^2.6.10"
"@types/node": "^24.10.0",
"@vitejs/plugin-vue": "^6.0.7",
"typescript": "^5.9.3",
"vite": "^8.0.14",
"vue-tsc": "^3.3.1"
},
"repository": {
"type": "git",
"url": "git@github.com:langyuxiansheng/vue-aliplayer-v2.git"
}
}
}

View File

@ -1,555 +1,386 @@
<template>
<div :id="playerId"></div>
<div :id="playerId" ref="containerRef" class="vue-aliplayer-v2"></div>
</template>
<script>
export default {
name: 'VueAliplayerV2',
props: {
forbidFastForward: { //
required: false,
type: [Boolean],
default: false
},
options: { //
required: false,
type: [Object],
default: () => null
},
source: { //(options.source) ,mp4/flv/m3u8rtmp
required: false,
type: [String],
default: null
},
cssLink: { //css
required: false,
type: [String],
default: `https://g.alicdn.com/de/prismplayer/2.9.20/skins/default/aliplayer-min.css`
},
scriptSrc: { //js
required: false,
type: [String],
default: `https://g.alicdn.com/de/prismplayer/2.9.20/aliplayer-min.js`
}
},
data () {
return {
player: null, //
playerId: `player-${Math.random().toString(36).substr(2).toLocaleUpperCase()}`,
config: {
id: null, //ID
width: '100%',
autoplay: true,
// isLive: true,
//,
// source: 'rtmp://182.145.195.238:1935/hls/1194076936807170050',
},
events: [
/**
* 播放器视频初始化按钮渲染完毕
* 播放器UI初始设置需要此事件后触发避免UI被初始化所覆盖
* 播放器提供的方法需要在此事件发生后才可以调用
*/
'ready',
/**
* 视频由暂停恢复为播放时触发
*/
'play',
/**
* 视频暂停时触发
*/
'pause',
/**
* 能够开始播放音频/视频时发生会多次触发仅H5播放器
*/
'canplay',
/**
* 播放中会触发多次
*/
'playing',
/**
* 当前视频播放完毕时触发
*/
'ended',
/**
* 直播流中断时触发
* m3u8/flv/rtmp在重试5次未成功后触发
* 提示上层流中断或需要重新加载视频
* PSm3u8一直自动重试不需要上层添加重试
*/
'liveStreamStop',
/**
* m3u8直播流中断后重试事件每次断流只触发一次
*/
'onM3u8Retry',
/**
* 控制栏自动隐藏事件
*/
'hideBar',
/**
* 控制栏自动显示事件
*/
'showBar',
/**
* 数据缓冲事件
*/
'waiting',
/**
* 播放位置发生改变时触发仅H5播放器
* 可通过getCurrentTime方法得到当前播放时间
*/
'timeupdate',
/**
* 截图完成
*/
'snapshoted',
/**
* 全屏事件仅H5支持
*/
'requestFullScreen',
/**
* 取消全屏事件iOS下不会触发仅H5支持
*/
'cancelFullScreen',
/**
* 错误事件
*/
'error',
/**
* 开始拖拽参数返回拖拽点的时间
*/
'startSeek',
/**
* 完成拖拽参数返回拖拽点的时间
*/
'completeSeek'
],
};
},
watch: {
source () { //
this.init();
},
forbidFastForward () {
this.init();
},
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import {
DEFAULT_CSS_LINK,
DEFAULT_SCRIPT_SRC,
DEFAULT_SDK_VERSION,
getCssLinkByVersion,
getScriptSrcByVersion,
loadAliplayerSdk,
loadExtraScripts
} from './sdkLoader';
import { inferSourceFormat, normalizeSource, type SourceFormat } from './source';
import { installTrackingBlocker } from './tracking';
import type {
AliplayerEventName,
AliplayerInstance,
AliplayerOptions,
AliplayerV2Props,
VueAliplayerV2Expose
} from './types';
options: { //,
handler () {
this.init();
},
deep: true
}
},
mounted () {
this.$nextTick(() => {
this.init();
});
},
updated () {
//
this.$nextTick(() => {
this.init();
});
},
methods: {
handlerFastForward () {
defineOptions({
name: 'VueAliplayerV2'
});
},
/**
* 创建script和css
* 加载Alipayer的SDK
*/
init () {
const linkID = 'app__aliplayer-min-css';
const scriptID = 'app__aliplayer-min-js';
const head = document.getElementsByTagName('head');
const html = document.getElementsByTagName('html');
let scriptTag = document.getElementById(scriptID);
let linkIDTag = document.getElementById(linkID);
if (!linkIDTag) {
// console.log('linkIDTag');
const link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = this.cssLink;
link.id = linkID;
// link.className = linkID;
head[0].appendChild(link);
}
if (!scriptTag) {
// console.log('scriptTag');
scriptTag = document.createElement('script');
scriptTag.type = "text/javascript";
scriptTag.id = scriptID;
// scriptTag.className = scriptID;
scriptTag.src = this.scriptSrc;
html[0].appendChild(scriptTag);
} else {
this.initPlayer(); //
}
//
scriptTag.addEventListener("load", () => {
this.initPlayer();
});
},
const props = withDefaults(defineProps<AliplayerV2Props>(), {
autoFormat: true,
componentScripts: () => [],
disableTracking: false,
forbidFastForward: false,
license: null,
lowLatency: false,
normalizeSourceUrl: true,
options: null,
sdkVersion: DEFAULT_SDK_VERSION,
source: null,
cssLink: '',
scriptSrc: '',
trackingUrlPatterns: () => []
});
/**
* 创建播放器
* @description SDK文档地址:https://help.aliyun.com/document_detail/125572.html?spm=a2c4g.11186623.6.1084.131d1c4cJT7o5Z
*/
initPlayer () {
if (typeof window.Aliplayer != 'undefined') {
const options = this.deepCloneObject(this.options);
if (options) {
for (const key in options) {
this.config[key] = options[key];
}
}
if (this.source) this.config.source = this.source; //
this.config.id = this.playerId; //id
this.player && this.player.dispose(); //
this.player = Aliplayer(this.config);
for (const ev in this.events) {
this.player && this.player.on(this.events[ev], (e) => {
// console.log(`object ${this.events[ev]}`,e);
this.$emit(this.events[ev], e);
});
}
const emit = defineEmits<{
(event: AliplayerEventName, payload?: unknown): void;
(event: 'sdk-error', error: Error): void;
}>();
const player = ref<AliplayerInstance | null>(null);
const containerRef = ref<HTMLDivElement | null>(null);
const isUnmounted = ref(false);
const currentFormat = ref<SourceFormat>(null);
let initToken = 0;
const playerId = `player-${Math.random().toString(36).slice(2).toUpperCase()}`;
const eventNames: AliplayerEventName[] = [
'ready',
'play',
'pause',
'canplay',
'playing',
'ended',
'liveStreamStop',
'onM3u8Retry',
'hideBar',
'showBar',
'waiting',
'timeupdate',
'snapshoted',
'requestFullScreen',
'cancelFullScreen',
'error',
'startSeek',
'completeSeek'
];
//
if (this.forbidFastForward) {
let last = 0, max_time = 0;
this.player.on('timeupdate', function () {
let current = this.getCurrentTime();
if (current - last > 2) {
this.seek(last);
} else {
last = current;
if (last >= max_time) {
max_time = last;
}
}
});
}
const resolvedCssLink = computed(() => props.cssLink || (props.sdkVersion ? getCssLinkByVersion(props.sdkVersion) : DEFAULT_CSS_LINK));
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));
const mergedOptions = computed<AliplayerOptions>(() => {
const options: AliplayerOptions = {
width: '100%',
autoplay: true,
...(props.options || {}),
...(props.license ? { license: props.license } : {}),
...(normalizedSource.value ? { source: normalizedSource.value } : {}),
id: playerId
};
//off
//player.off('ready',handleReady);
}
},
/**
* @return player 实例
*/
getPlayer () {
return this.player;
},
/**
* 播放视频
*/
play () {
// console.log(``);
this.player && this.player.play();
},
/**
* 暂停视频
*/
pause () {
// console.log(``);
this.player && this.player.pause();
},
/**
* 重播视频
*/
replay () {
// console.log(``);
this.player && this.player.replay();
},
/**
* 跳转到某个时刻进行播放time的单位为秒
* @param time
* @return player
*/
seek (time) {
// console.log(`time${time}`);
this.player && this.player.seek(time);
},
/**
* 获取当前的播放时刻返回的单位为秒
* @return player
*/
getCurrentTime () {
// console.log(`${this.player && this.player.getCurrentTime()}`);
return this.player && this.player.getCurrentTime();
},
/**
* 获取视频总时长返回的单位为秒这个需要在视频加载完成以后才可以获取到可以在play事件后获取
* @return player
*/
getDuration () {
// console.log(`${this.player && this.player.getDuration()}`);
return this.player && this.player.getDuration();
},
/**
* 获取当前的音量返回值为0-1的实数ios和部分android会失效
* @return player
*/
getVolume () {
// console.log(`${this.player && this.player.getVolume()}`);
return this.player && this.player.getVolume();
},
/**
* 设置音量vol为0-1的实数ios和部分android会失效
* @return player
*/
setVolume (v) {
// console.log(`vol${v}`);
this.player && this.player.setVolume(v);
},
/**
* 直接播放视频urltime为可选值单位秒目前只支持同种格式mp4/flv/m3u8之间切换
* 暂不支持直播rtmp流切换
* @return player
*/
loadByUrl (url, time) {
// console.log(`url${url}time${time}`);
this.player && this.player.loadByUrl(url, time);
},
/**
* 目前只支持H5播放器暂不支持不同格式视频间的之间切换暂不支持直播rtmp流切换
* @param vid 视频id
* @param 播放凭证
*/
replayByVidAndPlayAuth (vid, playauth) {
// console.log(`replayByVidAndPlayAuth vid${vid}playauth${playauth}`);
this.player && this.player.replayByVidAndPlayAuth(vid, playauth);
},
/**
* 目前只支持H5播放器暂不支持不同格式视频间的之间切换暂不支持直播rtmp流切换
* @param vid 视频id
* @param 播放凭证
* @description 仅MPS用户时使用 仅MPS用户时使用 参数顺序为vidaccIdaccSecretstsTokenauthInfodomainRegion
*/
replayByVidAndAuthInfo (vid, accId, accSecret, stsToken, authInfo, domainRegion) {
// console.log(`replayByVidAndAuthInfo vidaccIdaccSecretstsTokenauthInfodomainRegion`,vid, accId, accSecret, stsToken, authInfo, domainRegion);
this.player && this.player.replayByVidAndAuthInfo(vid, accId, accSecret, stsToken, authInfo, domainRegion);
},
/**
* 设置播放器大小wh可分别为400px像素或60%百分比
* @param w 宽度
* @param h 宽度
* @description chrome浏览器下flash播放器分别不能小于397x297
*/
setPlayerSize (w, h) {
// console.log(` :${w},:${h}`);
this.player && this.player.setPlayerSize(w, h);
},
/**
* 手动设置播放的倍速倍速播放仅H5支持移动端可能会失效比如android微信
* 倍速播放UI默认是开启的
* 如果自定义过skinLaout属性需要添加speedButton项到数组里
* @param h 宽度
* @description {namespeedButtonaligntrx10y23}
*/
setSpeed (speed) {
// console.log(`:${speed}`);
this.player && this.player.setSpeed(speed);
},
/**
* 设置截图参数
* @param width 宽度
* @param height 高度
* @param rate 截图质量
*/
setSanpshotProperties (width, height, rate) {
// console.log(`:`,width, height, rate);
this.player && this.player.setSanpshotProperties(width, height, rate);
},
/**
* 播放器全屏仅H5支持
*/
requestFullScreen () {
// console.log(`H5`);
this.player && this.player.fullscreenService && this.player.fullscreenService.requestFullScreen();
},
/**
* 播放器退出全屏iOS调用无效仅H5支持
*/
cancelFullScreen () {
// console.log(`H5`);
this.player && this.player.fullscreenService && this.player.fullscreenService.cancelFullScreen();
},
/**
* 获取播放器全屏状态仅H5支持
*/
getIsFullScreen () {
// console.log(`H5`,this.player && this.player.fullscreenService && this.player && this.player.fullscreenService.getIsFullScreen());
return this.player && this.player.fullscreenService && this.player.fullscreenService.getIsFullScreen();
},
/**
* 获取播放器状态包含的值,
* @returns init ready loading play pause playing waiting error ended
*/
getStatus () {
// console.log(``,this.player && this.player.fullscreenService && this.player && this.player.fullscreenService.getStatus());
return this.player && this.player.getStatus();
},
/**
* 设置直播的开始结束时间开启直播时移功能时使用
* @param beginTime 开始时间
* @param endTime 结束时间
* @description 例子player.liveShiftSerivce.setLiveTimeRange(2018/01/04 20:00:00)
*/
setLiveTimeRange (beginTime, endTime) {
// console.log(`:${beginTime},:${endTime}使`);
this.player && this.player.liveShiftSerivce && this.player.liveShiftSerivce.setLiveTimeRange(beginTime, endTime);
},
/**
* 参数为旋转角度 正数为正时针旋转 负数为逆时针旋转
* @param rotate 旋转角度
* @description 例如: setRotate(90)详情参见旋转和镜像
*/
setRotate (rotate) {
// console.log(`:${rotate}`);
this.player && this.player.setRotate(rotate);
},
/**
* 获取旋转角度详情参见旋转和镜像
* @return rotate 旋转角度
*/
getRotate () {
// console.log(`:${this.player && this.player.getRotate()}`);
return this.player && this.player.getRotate();
},
/**
* 设置镜像
* @param image 镜像类型 可选值为horizon,vertical
* @description 例如: setImage(horizon)详情参见旋转和镜像
*/
setImage (image) {
// console.log(`:${image}`);
this.player && this.player.setImage(image);
},
/**
* 播放器销毁
*/
dispose () {
// console.log(``);
this.player && this.player.dispose();
},
/**
* 设置封面
* @param cover 封面地址
*/
setCover (cover) {
// console.log(`:${cover}`);
this.player && this.player.setCover(cover);
},
/**
* 设置封面
* @param markers 设置打点数据
*/
setProgressMarkers (markers) {
// console.log(`markers:${markers}`);
this.player && this.player.setProgressMarkers(markers);
},
/**
* 设置试看时间单位为秒详情参见
* @param time 试看时间
*/
setPreviewTime (time) {
// console.log(`:${time}`);
this.player && this.player.setPreviewTime(time);
},
/**
* 获取试看时间
* @return rotate 旋转角度
*/
getPreviewTime () {
// console.log(`:${this.player && this.player.getPreviewTime()}`);
return this.player && this.player.getPreviewTime();
},
/**
* 是否试看
*/
isPreview () {
// console.log(``);
this.player && this.player.isPreview();
},
/**
* @param ev 事件名
* @param handle 回调方法
*/
off (ev, handle) {
this.player && this.player.off(ev, handle);
},
/**
* 深度拷贝
* @param {*} obj
*/
deepCloneObject (obj) {
let objClone = Array.isArray(obj) ? [] : {};
if (obj && typeof obj === 'object') {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
//ojb
if (obj[key] && typeof obj[key] === 'object') {
objClone[key] = this.deepCloneObject(obj[key]);
} else {
//
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
},
beforeDestroy () { //
this.dispose(); //(,,bug)
// const head = document.querySelector('head');
// const cssNodes = document.querySelectorAll(`link.app__aliplayer-min-css`);
// (html && cssNodes.length > 1) && cssNodes.forEach((item, index)=>{
// if(index != 0) head.removeChild(item);
// });
// const html = document.querySelector('html');
// const jsNodes = document.querySelectorAll(`script.app__aliplayer-min-js`);
// (html && jsNodes.length > 1) && jsNodes.forEach((item, index)=>{
// if(index != 0) html.removeChild(item);
// });
if (props.autoFormat && sourceFormat.value && !options.format) {
options.format = sourceFormat.value;
}
};
if (props.lowLatency && options.isLive && sourceFormat.value === 'flv') {
options.enableStashBufferForFlv = options.enableStashBufferForFlv ?? false;
options.stashInitialSizeForFlv = options.stashInitialSizeForFlv ?? 128;
}
return options;
});
function getPlayer(): AliplayerInstance | null {
return player.value;
}
function dispose(): void {
if (!player.value) return;
player.value.dispose();
player.value = null;
currentFormat.value = null;
}
function bindEvents(target: AliplayerInstance): void {
eventNames.forEach((eventName) => {
target.on(eventName, (event?: unknown) => {
emit(eventName, event);
});
});
}
function bindForbidFastForward(target: AliplayerInstance): void {
if (!props.forbidFastForward) return;
let last = 0;
let maxTime = 0;
target.on('timeupdate', () => {
const current = target.getCurrentTime();
if (current - last > 2) {
target.seek(last);
return;
}
last = current;
if (last >= maxTime) {
maxTime = last;
}
});
}
function initPlayer(): void {
if (isUnmounted.value || !window.Aliplayer || !containerRef.value) return;
dispose();
const nextPlayer = window.Aliplayer({ ...mergedOptions.value });
player.value = nextPlayer;
currentFormat.value = sourceFormat.value;
bindEvents(nextPlayer);
bindForbidFastForward(nextPlayer);
}
async function init(): Promise<void> {
const token = ++initToken;
try {
if (props.disableTracking) {
installTrackingBlocker(props.trackingUrlPatterns);
}
await loadAliplayerSdk(resolvedCssLink.value, resolvedScriptSrc.value);
await loadExtraScripts(props.componentScripts);
await nextTick();
if (isUnmounted.value || token !== initToken) return;
initPlayer();
} catch (error) {
emit('sdk-error', error instanceof Error ? error : new Error(String(error)));
}
}
async function reload(nextSource?: string): Promise<void> {
if (nextSource && player.value) {
player.value.loadByUrl(props.normalizeSourceUrl ? normalizeSource(nextSource) || nextSource : nextSource);
return;
}
await init();
}
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();
}
function seek(time: number): void {
player.value?.seek(time);
}
function getCurrentTime(): number | undefined {
return player.value?.getCurrentTime();
}
function getDuration(): number | undefined {
return player.value?.getDuration();
}
function getVolume(): number | undefined {
return player.value?.getVolume();
}
function setVolume(volume: number): void {
player.value?.setVolume(volume);
}
function loadByUrl(url: string, time?: number): void {
player.value?.loadByUrl(url, time);
}
function replayByVidAndPlayAuth(vid: string, playauth: string): void {
player.value?.replayByVidAndPlayAuth(vid, playauth);
}
function replayByVidAndAuthInfo(
vid: string,
accId: string,
accSecret: string,
stsToken: string,
authInfo: string,
domainRegion: string
): void {
player.value?.replayByVidAndAuthInfo(vid, accId, accSecret, stsToken, authInfo, domainRegion);
}
function setPlayerSize(width: string, height: string): void {
player.value?.setPlayerSize(width, height);
}
function setSpeed(speed: number): void {
player.value?.setSpeed(speed);
}
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();
}
function getIsFullScreen(): boolean | undefined {
return player.value?.fullscreenService?.getIsFullScreen();
}
function getStatus(): string | undefined {
return player.value?.getStatus();
}
function setLiveTimeRange(beginTime: string, endTime: string): void {
player.value?.liveShiftSerivce?.setLiveTimeRange(beginTime, endTime);
}
function setRotate(rotate: number): void {
player.value?.setRotate(rotate);
}
function getRotate(): number | undefined {
return player.value?.getRotate();
}
function setImage(image: string): void {
player.value?.setImage(image);
}
function setCover(cover: string): void {
player.value?.setCover(cover);
}
function setProgressMarkers(markers: unknown[]): void {
player.value?.setProgressMarkers(markers);
}
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();
}
function off(eventName: string, handler: (event?: unknown) => void): void {
player.value?.off?.(eventName, handler);
}
onMounted(() => {
void init();
});
watch(
() => normalizedSource.value,
(source) => {
if (!source) return;
const nextFormat = inferSourceFormat(source);
if (player.value && nextFormat && nextFormat === currentFormat.value) {
player.value.loadByUrl(source);
return;
}
void init();
}
);
watch(
() => [
props.options,
props.forbidFastForward,
props.license,
props.lowLatency,
props.autoFormat,
props.sdkVersion,
props.cssLink,
props.scriptSrc,
props.componentScripts,
props.disableTracking,
props.trackingUrlPatterns
],
() => {
void init();
},
{ deep: true }
);
onBeforeUnmount(() => {
isUnmounted.value = true;
initToken += 1;
dispose();
});
defineExpose<VueAliplayerV2Expose>({
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
});
</script>
<style scoped>
.vue-aliplayer-v2 {
width: 100%;
}
</style>

View File

@ -0,0 +1,81 @@
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`;
const scriptLoadPromises = new Map<string, Promise<void>>();
export function getCssLinkByVersion(version: string): string {
return `https://g.alicdn.com/apsara-media-box/imp-web-player/${version}/skins/default/aliplayer-min.css`;
}
export function getScriptSrcByVersion(version: string): string {
return `https://g.alicdn.com/apsara-media-box/imp-web-player/${version}/aliplayer-min.js`;
}
function ensureStylesheet(cssLink: string): void {
if (!cssLink) return;
const existed = document.querySelector<HTMLLinkElement>(`link[data-vue-aliplayer-v2-css="${cssLink}"]`);
if (existed) return;
const link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = cssLink;
link.dataset.vueAliplayerV2Css = cssLink;
document.head.appendChild(link);
}
function ensureScript(scriptSrc: string, globalName?: string): Promise<void> {
if (!scriptSrc) return Promise.resolve();
if (globalName && window[globalName]) return Promise.resolve();
if (scriptLoadPromises.has(scriptSrc)) return scriptLoadPromises.get(scriptSrc) as Promise<void>;
const existed = document.querySelector<HTMLScriptElement>(`script[data-vue-aliplayer-v2-js="${scriptSrc}"]`);
if (existed) {
const loadPromise = new Promise<void>((resolve, reject) => {
if (existed.dataset.vueAliplayerV2Loaded === 'true') {
resolve();
return;
}
existed.addEventListener('load', () => {
existed.dataset.vueAliplayerV2Loaded = 'true';
resolve();
}, { once: true });
existed.addEventListener('error', () => reject(new Error(`Failed to load Aliplayer SDK: ${scriptSrc}`)), { once: true });
});
scriptLoadPromises.set(scriptSrc, loadPromise);
return loadPromise;
}
const loadPromise = new Promise<void>((resolve, reject) => {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = scriptSrc;
script.dataset.vueAliplayerV2Js = scriptSrc;
script.addEventListener('load', () => {
script.dataset.vueAliplayerV2Loaded = 'true';
resolve();
}, { once: true });
script.addEventListener('error', () => reject(new Error(`Failed to load Aliplayer SDK: ${scriptSrc}`)), { once: true });
document.documentElement.appendChild(script);
});
scriptLoadPromises.set(scriptSrc, loadPromise);
return loadPromise;
}
/**
* Shared SDK loader. It prevents duplicate CSS/JS tags when several player
* instances mount at the same time or are recreated by route changes.
*/
export async function loadAliplayerSdk(cssLink: string, scriptSrc: string): Promise<void> {
ensureStylesheet(cssLink);
await ensureScript(scriptSrc, 'Aliplayer');
}
export async function loadExtraScripts(scriptUrls: string[] = []): Promise<void> {
for (const scriptUrl of scriptUrls) {
await ensureScript(scriptUrl);
}
}

View File

@ -0,0 +1,25 @@
export type SourceFormat = 'mp4' | 'm3u8' | 'flv' | 'rtmp' | 'mp3' | null;
const KNOWN_FORMATS: SourceFormat[] = ['m3u8', 'flv', 'mp4', 'rtmp', 'mp3'];
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;
}
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;
}
}

View File

@ -0,0 +1,49 @@
const DEFAULT_TRACKING_PATTERNS: Array<string | RegExp> = [
'videocloud.cn-hangzhou.log.aliyuncs.com/logstores/newplayer/track'
];
let trackingBlockerInstalled = false;
function matchesTrackingUrl(url: string, patterns: Array<string | RegExp>): boolean {
return patterns.some((pattern) => {
if (typeof pattern === 'string') return url.includes(pattern);
return pattern.test(url);
});
}
export function installTrackingBlocker(patterns: Array<string | RegExp> = DEFAULT_TRACKING_PATTERNS): void {
if (trackingBlockerInstalled) return;
trackingBlockerInstalled = true;
const originalFetch = window.fetch?.bind(window);
if (originalFetch) {
window.fetch = ((input: RequestInfo | URL, init?: RequestInit) => {
const url = typeof input === 'string' || input instanceof URL ? String(input) : input.url;
if (matchesTrackingUrl(url, patterns)) {
return Promise.resolve(new Response(null, { status: 204, statusText: 'No Content' }));
}
return originalFetch(input, init);
}) as typeof window.fetch;
}
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function open(method: string, url: string | URL, async?: boolean, username?: string | null, password?: string | null): void {
this.__vueAliplayerV2Blocked = matchesTrackingUrl(String(url), patterns);
originalOpen.call(this, method, url, async ?? true, username ?? undefined, password ?? undefined);
};
XMLHttpRequest.prototype.send = function send(body?: Document | XMLHttpRequestBodyInit | null): void {
if (this.__vueAliplayerV2Blocked) {
return;
}
originalSend.call(this, body);
};
}
declare global {
interface XMLHttpRequest {
__vueAliplayerV2Blocked?: boolean;
}
}

View File

@ -0,0 +1,170 @@
import type { Plugin } from 'vue';
export interface VueAliplayerV2Options {
sdkVersion?: string;
cssLink?: string;
scriptSrc?: string;
componentScripts?: string[];
disableTracking?: boolean;
trackingUrlPatterns?: Array<string | RegExp>;
}
export interface AliplayerV2Props extends VueAliplayerV2Options {
autoFormat?: boolean;
forbidFastForward?: boolean;
license?: AliplayerLicense | null;
lowLatency?: boolean;
normalizeSourceUrl?: boolean;
options?: AliplayerOptions | null;
source?: string | null;
}
export interface AliplayerLicense {
domain: string;
key: string;
}
export type AliplayerEventName =
| 'ready'
| 'play'
| 'pause'
| 'canplay'
| 'playing'
| 'ended'
| 'liveStreamStop'
| 'onM3u8Retry'
| 'hideBar'
| 'showBar'
| 'waiting'
| 'timeupdate'
| 'snapshoted'
| 'requestFullScreen'
| 'cancelFullScreen'
| 'error'
| 'startSeek'
| 'completeSeek';
export interface AliplayerOptions {
id?: string;
source?: string;
width?: string;
autoplay?: boolean;
isLive?: boolean;
format?: string;
license?: AliplayerLicense;
vid?: string;
playauth?: string;
accessKeyId?: string;
securityToken?: string;
accessKeySecret?: string;
region?: string;
authTimeout?: number;
components?: unknown[];
enableStashBufferForFlv?: boolean;
stashInitialSizeForFlv?: number;
rtsVersion?: string;
[key: string]: unknown;
}
export interface AliplayerFullscreenService {
requestFullScreen: () => void;
cancelFullScreen: () => void;
getIsFullScreen: () => boolean;
}
export interface AliplayerLiveShiftService {
setLiveTimeRange: (beginTime: string, endTime: string) => void;
}
export interface AliplayerInstance {
on: (eventName: string, handler: (event?: unknown) => void) => void;
off?: (eventName: string, handler: (event?: unknown) => void) => void;
play: () => void;
pause: () => void;
replay: () => void;
seek: (time: number) => void;
getCurrentTime: () => number;
getDuration: () => number;
getVolume: () => number;
setVolume: (volume: number) => void;
loadByUrl: (url: string, time?: number) => void;
replayByVidAndPlayAuth: (vid: string, playauth: string) => void;
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;
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;
}
export interface VueAliplayerV2Expose {
getPlayer: () => AliplayerInstance | null;
init: () => Promise<void>;
initPlayer: () => void;
reload: (nextSource?: string) => Promise<void>;
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;
loadByUrl: (url: string, time?: number) => void;
replayByVidAndPlayAuth: (vid: string, playauth: string) => void;
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;
}
export type VueAliplayerV2Plugin = Plugin & {
Player?: unknown;
};
declare global {
interface Window {
Aliplayer?: (options: AliplayerOptions) => AliplayerInstance;
[key: string]: unknown;
}
}

View File

@ -1,11 +0,0 @@
// packages / index.js
// 导入单个组件
import VueAliplayerV2 from './AliplayerV2';
// 定义 install 方法
VueAliplayerV2.install = (Vue, options) =>{
if(options && options.cssLink) VueAliplayerV2.props.cssLink.default = options.cssLink;
if(options && options.scriptSrc) VueAliplayerV2.props.scriptSrc.default = options.scriptSrc;
Vue.component(VueAliplayerV2.name, VueAliplayerV2);
};
VueAliplayerV2.Player = VueAliplayerV2;
export default VueAliplayerV2;

54
packages/index.ts Normal file
View File

@ -0,0 +1,54 @@
import type { App } from 'vue';
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,
AliplayerLicense,
AliplayerOptions,
AliplayerV2Props,
VueAliplayerV2Expose,
VueAliplayerV2Options
} from './AliplayerV2/types';
type InstallableVueAliplayerV2 = typeof VueAliplayerV2 & {
install: (app: App, options?: VueAliplayerV2Options) => void;
Player: typeof VueAliplayerV2;
props: {
sdkVersion: { default: string | (() => string) };
cssLink: { default: string | (() => string) };
scriptSrc: { default: string | (() => string) };
componentScripts: { default: string[] | (() => string[]) };
disableTracking: { default: boolean | (() => boolean) };
trackingUrlPatterns: { default: Array<string | RegExp> | (() => Array<string | RegExp>) };
};
};
const globalOptions: Required<VueAliplayerV2Options> = {
sdkVersion: DEFAULT_SDK_VERSION,
cssLink: DEFAULT_CSS_LINK,
scriptSrc: DEFAULT_SCRIPT_SRC,
componentScripts: [],
disableTracking: false,
trackingUrlPatterns: []
};
const installable = VueAliplayerV2 as unknown as InstallableVueAliplayerV2;
installable.install = (app: App, options?: VueAliplayerV2Options): void => {
Object.assign(globalOptions, options || {});
app.component('VueAliplayerV2', installable);
};
installable.props.cssLink.default = (): string => globalOptions.cssLink;
installable.props.scriptSrc.default = (): string => globalOptions.scriptSrc;
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;
installable.Player = installable;
export { installable as VueAliplayerV2 };
export default installable;

View File

@ -1,5 +0,0 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>vue-aliplayer-v2</title>
</head>
<body>
<noscript>
<strong>We're sorry but vue-aliplayer-v2 doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

5
tsconfig.build.json Normal file
View File

@ -0,0 +1,5 @@
{
"extends": "./tsconfig.json",
"include": ["packages/**/*.ts", "packages/**/*.vue", "env.d.ts"],
"exclude": ["examples/**", "dist/**", "lib/**", "node_modules/**"]
}

27
tsconfig.json Normal file
View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"types": ["node"]
},
"include": [
"env.d.ts",
"examples/**/*.ts",
"examples/**/*.vue",
"packages/**/*.ts",
"packages/**/*.vue",
"vite.config.ts"
]
}

36
vite.config.ts Normal file
View File

@ -0,0 +1,36 @@
import { resolve } from 'node:path';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
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'
}
}
}
}
};
});

View File

@ -1,15 +0,0 @@
// vue.config.js
module.exports = {
// 将 examples 目录添加为新的页面
pages: {
index: {
// page 的入口
entry: 'examples/main.js',
// 模板来源
template: 'public/index.html',
// 输出文件名
filename: 'index.html'
}
},
publicPath: process.env.NODE_ENV === "production" ? "/vue-aliplayer-v2" : "/"
}