更新记录
1.0.0(2024-10-25)
ml-swiper-x
仿抖音短视频 APP端超高性能、进度条、全屏等
平台兼容性
Vue2 | Vue3 |
---|---|
× | √ |
App | 快应用 | 微信小程序 | 支付宝小程序 | 百度小程序 | 字节小程序 | QQ小程序 |
---|---|---|---|---|---|---|
HBuilderX 4.25 | × | × | × | × | × | × |
钉钉小程序 | 快手小程序 | 飞书小程序 | 京东小程序 |
---|---|---|---|
× | × | × | × |
H5-Safari | Android Browser | 微信浏览器(Android) | QQ浏览器(Android) | Chrome | IE | Edge | Firefox | PC-Safari |
---|---|---|---|---|---|---|---|---|
× | × | × | × | × | × | × | × | × |
ml-swiper-x
本插件仅支持:uni-app-x,该插件使用 uts 开发,性能远高于 uni-app 。
uni-app 版提供了:ml-swiper 、ml-swiper-v2 、ml-swiper-v3 三个版本
组件其他版本
更多组件 请前往 作者主页查看 :https://ext.dcloud.net.cn/publisher?id=1784252
ml-swiper :该版本的插件 为 ml-swiper
的第一版 插件 适用于 案例测试、组件学习、抖音功能分析等;该版本的插件 仅支持 VUE3;
ml-swiper-v2 :该 插件 是在 ml-swiper
版本上做的升级,支持 VUE2、VUE3,并且支持了 m3u8 流媒体资源;此版本的插件 适用于 个人或者公司 的二次开发 定制专属插件;插件内 功能、方法 均有详细注释说明,并且 组件内详细说明了 抖音APP
的实现逻辑,以及抖音官方的说明;(通过该 版本的组件 可以实现一个 适用于自己或者公司 专属的 扩展组件)
ml-swiper-v3 :该 插件 是在 ml-swiper-v2
的基础上 做出的最终版,并且优化了 大资源数据下 导致的上下滑动卡顿的问题。该组件不需要特殊配置,容易上手,内置插槽 便于实现个性化UI页面,并且提供了 很多组件方法 便于自定义实现更多的业务逻辑;
特别说明
特别说明:
收费代码有版权问题,且发布后的插件会被自动加密,如非购买本插件出现的各种问题,均与此有关。
如正常购买了插件,出现了运行报错,或者案例代码运行报错,均可入群与作者联系,作者将提供帮助,并解决问题。
该组件将持续维护,有任何问题均可留言。
非购买出现的问题,非组件本身问题 作者将不予处理和解决,切勿恶意差评,创作不易 还请理解。
购买普通版权后,想要源码的可插件市场直接购买源码,如需要继续扩展 可以直接联系作者 索要底层源代码 以供扩展使用。
使用案例时特别注意,案例中的函数名会被转义为空,报错时 请先检查是否存在函数名为空的,为空时补全后重新运行即可。
功能演示
使用说明
案例报错说明
复制代码// @ondblclick
function (e) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释 被转义掉的函数名为:
console.log(e);
}
使用案例时特别注意,案例中的函数名会被转义为空,报错时 请先检查是否存在函数名为空的,为空时补全后重新运行即可。
组件其他版本
更多组件 请前往 作者主页查看 :https://ext.dcloud.net.cn/publisher?id=1784252
ml-swiper :该版本的插件 为 ml-swiper
的第一版 插件 适用于 案例测试、组件学习、抖音功能分析等;该版本的插件 仅支持 VUE3;
ml-swiper-v2 :该 插件 是在 ml-swiper
版本上做的升级,支持 VUE2、VUE3,并且支持了 m3u8 流媒体资源;此版本的插件 适用于 个人或者公司 的二次开发 定制专属插件;插件内 功能、方法 均有详细注释说明,并且 组件内详细说明了 抖音APP
的实现逻辑,以及抖音官方的说明;(通过该 版本的组件 可以实现一个 适用于自己或者公司 专属的 扩展组件)
ml-swiper-v3 :该 插件 是在 ml-swiper-v2
的基础上 做出的最终版,并且优化了 大资源数据下 导致的上下滑动卡顿的问题。该组件不需要特殊配置,容易上手,内置插槽 便于实现个性化UI页面,并且提供了 很多组件方法 便于自定义实现更多的业务逻辑;
prop 属性列表
属性名 | 类型 | 默认值 | 说明 | 是否必填 |
---|---|---|---|---|
list |
Array<ListOption> |
[] | 资源列表,资源数据最少不得少于 3 条数据 |
true |
width |
Number |
uni.getWindowInfo().windowWidth | 组件宽度,默认设备同宽 | false |
height |
Number |
uni.getWindowInfo().windowHeight | 组件高度,默认设备同高 | false |
progress |
Boolean |
true | 是否显示进度条 | false |
duration |
Boolean |
true | 是否显示时长 | false |
criticalVal |
Number |
2 | 临界值,当 list.length - index <= criticalVal 时触发loadmore() 事件 |
false |
ListOption类型
ListOption
类型详情
属性名 | 类型 | 说明 | 是否必填 |
---|---|---|---|
url |
string | 资源链接地址 | true |
id | string | 资源id | false |
title | string | 资源标题 | false |
poster | string | 视频资源封面图 | false |
initialTime | number | 初始化播放位置,从指定位置开始播放,可为空(null | 0 | 不写) | false |
percent | number | 当前播放进度,当前已播放进度,可为空(null | 0 | 不写) | false |
currentTime | string | 当前播放时长,当前播放进度的时间,可为空(null | "" | 不写) | false |
duration | string | 视频总时长,可为空(null | "" | 不写) | false |
userInfo | UserOption | 资源所属的用户信息,可为空(null | 不写) | false |
UserOption
类型详情
属性名 | 类型 | 说明 | 是否必填 |
---|---|---|---|
id | string | 当前资源所属用户的用户ID | false |
avatar | string | 当前资源所属用户的用户头像 | false |
username | string | 当前资源所属用户的用户名称 | false |
Event事件列表
事件名 | 参数 | 解释 |
---|---|---|
onchange(event: EventOptions) 上下滑动视频时触发 |
event = { index, playing, data, context } | index: Number 当前资源索引,可能为 null playing: Boolean 是否正在播放,可能为 null data: ListOption 当前资源数据,可能为 null context: VideoContext 视频上下文对象,可能为 null |
onclick(event: EventOptions) 点击事件,暂停 | 播放 |
event = { index, playing, data, context } | index: Number 当前资源索引,可能为 null playing: Boolean 是否正在播放,可能为 null data: ListOption 当前资源数据,可能为 null context: VideoContext 视频上下文对象,可能为 null |
dblclick(event: EventOptions) 双击事件,双击点赞 |
event = { index, playing, data, context } | index: Number 当前资源索引,可能为 null playing: Boolean 是否正在播放,可能为 null data: ListOption 当前资源数据,可能为 null context: VideoContext 视频上下文对象,可能为 null |
onplay(event: EventOptions) 视频开始播放时触发 |
event = { index, playing, data, context } | index: Number 当前资源索引,可能为 null playing: Boolean 是否正在播放,可能为 null data: ListOption 当前资源数据,可能为 null context: VideoContext 视频上下文对象,可能为 null |
onpause(event: EventOptions) 视频暂停播放时触发 |
event = { index, data, context } | index: Number 当前资源索引,可能为 null playing: Boolean 是否正在播放,可能为 null data: ListOption 当前资源数据,可能为 null context: VideoContext 视频上下文对象,可能为 null |
onend(event: EventOptions) 视频播放结束时触发 |
event = { index, playing, data, context } | index: Number 当前资源索引,可能为 null playing: Boolean 是否正在播放,可能为 null data: ListOption 当前资源数据,可能为 null context: VideoContext 视频上下文对象,可能为 null |
ontimeupdate(event: UniVideoTimeUpdateEvent) 进度发生变化时触发 |
UniVideoTimeUpdateEvent | 详见uni-app-x https://doc.dcloud.net.cn/uni-app-x/component/video.html |
onwaiting(event: UniEvent) 视频出现缓冲时触发 |
UniEvent | 详见uni-app-x https://doc.dcloud.net.cn/uni-app-x/component/video.html |
onerror(event: UniVideoErrorEvent) 视频播放出错时触发 |
UniVideoErrorEvent | 详见uni-app-x https://doc.dcloud.net.cn/uni-app-x/component/video.html |
onprogress(event: UniVideoProgressEvent) 加载进度变化时触发 |
UniVideoProgressEvent | 详见uni-app-x https://doc.dcloud.net.cn/uni-app-x/component/video.html |
onfullscreen(event: UniVideoFullScreenChangeEvent) 进入|退出全屏时触发 |
UniVideoFullScreenChangeEvent | 详见uni-app-x https://doc.dcloud.net.cn/uni-app-x/api/create-video-context.html |
onfullscreenclick(event: UniVideoFullScreenClickEvent) 全屏点击屏幕时触发 |
UniVideoFullScreenClickEvent | 详见uni-app-x https://doc.dcloud.net.cn/uni-app-x/api/create-video-context.html |
oncontrolstoggle(event: UniVideoControlsToggleEvent) 切换控制条时触发 |
UniVideoControlsToggleEvent | 详见uni-app-x https://doc.dcloud.net.cn/uni-app-x/api/create-video-context.html |
loadmore() 需要加载更多资源时触发 |
无 | 当 播放到 list.length - 2 时 触发 该事件 |
EventOptions类型
EventOptions
类型详情
属性名 | 类型 | 说明 |
---|---|---|
index | number | null | 当前资源的 索引,可能为 null |
data | ListOption | null | 当前资源的 数据,可能为 null |
playing | boolean | null | 是否正在播放,可能为 null |
context | VideoContext | null | 视频上下文对象,详见uni-app-x https://doc.dcloud.net.cn/uni-app-x/api/create-video-context.html |
组件方法methods
事件 | 参数 | 说明 |
---|---|---|
fullScreen() 进入全屏 |
- | 全屏播放 详见uni-app-x https://doc.dcloud.net.cn/uni-app-x/api/create-video-context.html |
exitFullScreen() 退出全屏 |
- | 退出全屏播放 详见uni-app-x https://doc.dcloud.net.cn/uni-app-x/api/create-video-context.html |
setRate(rate) 设置倍速 |
Number: 0.5 | 0.8 | 1.0 | 1.25 | 1.5 | 2.0 | 当前视频倍速,不同平台支持不一样 详见uni-app-x https://doc.dcloud.net.cn/uni-app-x/api/create-video-context.html |
setSeek(val) 指定播放时间 |
Number: 60 | 指定播放时间,单位秒,从 60秒处 播放 详见uni-app-x https://doc.dcloud.net.cn/uni-app-x/api/create-video-context.html |
示例代码
<template>
<view class="wh-full">
<!-- 使用 v-if="list.length > 0" 防止后台长时间不返回数据,导致组件首次加载后无法更新到最新数据 -->
<ml-swiper-x v-if="list.length > 0" :width="width" :height="height" :list="list" :duration="true" :progress="true"
@onchange="onchange" @loadmore="loadmore" @onplay="onplay" @onpause="onpause" @onend="onend"
@ontimeupdate="" @onwaiting="" @onerror="onerror" @onprogress=""
@onfullscreen="onfullscreen" @onfullscreenclick="onfullscreenclick" @oncontrolstoggle="oncontrolstoggle"
@dblclick="dblclick" @onclick="onclick">
<template v-slot:right>
<view class="right-view">
<image :src="userInfo?.avatar" class="avatar"></image>
<text style="text-align: center; font-size: 12; color: #fff">{{ currentData?.likes }}</text>
<text style="font-size: 12px; color: #fff">点赞</text>
<text style="text-align: center; font-size: 12; color: #fff">{{ currentData?.comments }}</text>
<text style="font-size: 12px; color: #fff">评论</text>
<text style="text-align: center; font-size: 12; color: #fff">{{ currentData?.stars }}</text>
<text style="font-size: 12px; color: #fff">收藏</text>
<text style="text-align: center; font-size: 12; color: #fff">{{ currentData?.forward }}</text>
<text style="font-size: 12px; color: #fff">转发</text>
</view>
</template>
<template v-slot:bottom="{ index }">
<view>
<view> <text class="text-left c-red">当前索引:{{ index }}</text> </view>
<view> <text class="text-left c-red">当前用户:{{ userInfo?.username }}</text> </view>
<view> <text class="text-left c-red">当前标题:{{ rowItem?.title }}</text> </view>
<view> <text class="text-left c-red">当前资源:{{ rowItem?.url }}</text> </view>
<view> <text class="text-left c-red">视频时长:{{ rowItem?.duration }}</text> </view>
</view>
</template>
</ml-swiper-x>
</view>
</template>
<script setup lang="uts">
// ml-swiper-x 组件符合 easycom 自动导入规范,不需要单独引入,但是需要引入数据类型
import { ListOption, EventOptions, UserOption } from '@/uni_modules/ml-swiper-x/components/ml-swiper-x/ml-swiper-x.vue';
// 这里是自定义数据类型,需要结合后台返回的数据来定义
type UserItem = {
id ?: string; // 用户ID
name ?: string; // 用户名称
avatar ?: string; // 用户头像
gender ?: string; // 用户性别
};
// 这里是自定义数据类型,需要结合后台返回的数据来定义
type DataItem = {
id ?: string; // 资源ID
url ?: string; // 资源链接
title ?: string; // 资源标题
image ?: string; // 封面图
likes ?: number; // 喜欢数
stars ?: number; // 点赞数
forward ?: number; // 转发数
comments ?: number; // 评论数
user ?: UserItem; // 用户信息
};
const win = uni.getWindowInfo();
const list = ref<ListOption[]>([]);
const currentIndex = ref<number>(0);
const dataList = ref<DataItem[]>([]);
const width = ref<number>(win.windowWidth);
const height = ref<number>(win.windowHeight);
const currentData = ref<DataItem | null>(null);
const rowItem = ref<ListOption | null>(null);
const userInfo = ref<UserOption | null>(null);
let urls : string[] = [
'https://web-ext-storage.dcloud.net.cn/uni-app-x/video/uts-5-16.mp4',
'https://web-ext-storage.dcloud.net.cn/uni-app-x/video/uni-ai-5-16.mp4',
'https://txmov2.a.yximgs.com/upic/2020/11/08/19/BMjAyMDExMDgxOTQxNTlfNTIzNDczMzQ0XzM4OTQ1MDk5MTI4XzFfMw==_b_Bc770a92f0cf153407d60a2eddffeae2a.mp4',
'https://txmov2.a.yximgs.com/upic/2020/10/02/09/BMjAyMDEwMDIwOTAwMDlfMTIyMjc0NTk0Ml8zNjk3Mjg0NjcxOF8xXzM=_b_B28a4518e86e2cf6155a6c1fc9cf79c6d.mp4',
'https://txmov6.a.yximgs.com/upic/2020/08/23/00/BMjAyMDA4MjMwMDMyNDRfMTYzMzY5MDA0XzM0ODI4MDcyMzQ5XzFfMw==_b_B9a1c9d4e3a090bb2815994d7f33a906a.mp4'
];
/**
* @getList 模拟后台请求,返回数据列表
*/
function getList() : DataItem[] {
let lists = [] as DataItem[];
for (var i = 0; i < 5; i++) {
let user = {
id: Math.ceil(Math.random() * 9999).toString(),
name: '哈哈',
avatar: 'https://img0.baidu.com/it/u=341193644,576104567&fm=253',
gender: '男'
} as UserItem;
let url = urls[i] as string;
let item = {
id: Math.ceil(Math.random() * 9999).toString(),
url: url,
image: '',
title: `【${i + 1}】uts 全称 uni type script,是一门跨平台的、高性能的、强类型的现代编程语言。它在不同平台,会被编译为不同平台的native语言`,
likes: Math.ceil(Math.random() * 9999),
stars: Math.ceil(Math.random() * 9999),
forward: Math.ceil(Math.random() * 9999),
comments: Math.ceil(Math.random() * 9999),
user: user
} as DataItem;
lists.add(item);
}
return lists;
}
/**
* @preDataList 处理后台返回的数据,将数据转化成指定格式
*/
function preDataList(list : DataItem[]) : ListOption[] {
let datas = [] as ListOption[];
list.forEach((row : DataItem) => {
let user = row.user as UserItem;
let userinfo = {
id: user.id,
avatar: user.avatar,
username: user.name
} as UserOption;
let video = {
id: row.id,
url: row.url,
poster: row.image,
title: row.title,
userInfo: userinfo,
initialTime: null, // 直接给 null 就行
percent: null, // 直接给 null 就行
currentTime: null, // 直接给 null 就行
duration: null // 直接给 null 就行
} as ListOption;
datas.add(video);
});
return datas;
}
/**
* @onchange
*/
function onchange(event : EventOptions) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
console.log("onchange:", event);
let info = event.data as ListOption | null;
rowItem.value = info;
if (info != null) {
let user = info.userInfo as UserOption | null;
userInfo.value = user;
}
currentIndex.value = event.index as number;
currentData.value = dataList.value[currentIndex.value] as DataItem;
}
/**
* @loadmore
*/
function loadmore() { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
console.log(" ======== 加载更多 ======== ")
let datas = getList() as DataItem[];
dataList.value = dataList.value.concat(datas) as DataItem[];
let lists = preDataList(datas) as ListOption[];
list.value = list.value.concat(lists) as ListOption[];
}
/**
* @onplay
*/
function onplay(event : EventOptions) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
// uni.showToast({ title: "开始播放", icon: "none", duration: 2000 });
console.log("onplay:", event);
}
/**
* @onpause
*/
function onpause(event : EventOptions) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
// uni.showToast({ title: "暂停播放", icon: "none", duration: 2000 });
console.log("onpause:", event);
}
/**
* @onend
*/
function onend(event : EventOptions) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
// uni.showToast({ title: "播放结束", icon: "none", duration: 2000 });
console.log("onend:", event);
}
/**
* @ontimeupdate
*/
function (event : UniVideoTimeUpdateEvent) {// 这里的函数名可能会被转义掉,如果函数名为空 请看注释
// console.log(":", event); // 每 250ms 执行一次
}
/**
* @onwaiting
*/
function (event : UniEvent) {// 这里的函数名可能会被转义掉,如果函数名为空 请看注释
console.log("出现缓冲 :", event);
}
/**
* @onerror
*/
function onerror(event : UniVideoErrorEvent) {// 这里的函数名可能会被转义掉,如果函数名为空 请看注释
console.error("播放出错 onerror:", event);
}
/**
* @onprogress
*/
function (event : UniVideoProgressEvent) {// 这里的函数名可能会被转义掉,如果函数名为空 请看注释
console.log(":", event);
}
/**
* @onfullscreen
*/
function onfullscreen(event : UniVideoFullScreenChangeEvent) {// 这里的函数名可能会被转义掉,如果函数名为空 请看注释
console.log("onfullscreen:", event);
}
/**
* @onfullscreenclick
*/
function onfullscreenclick(event : UniVideoFullScreenClickEvent) {// 这里的函数名可能会被转义掉,如果函数名为空 请看注释
console.log("onfullscreenclick:", event);
}
/**
* @oncontrolstoggle
*/
function oncontrolstoggle(event : UniVideoControlsToggleEvent) {// 这里的函数名可能会被转义掉,如果函数名为空 请看注释
console.log("oncontrolstoggle:", event);
}
/**
* @dblclick
*/
function dblclick(event : EventOptions) {// 这里的函数名可能会被转义掉,如果函数名为空 请看注释
uni.showToast({ title: '双击事件', icon: 'none', duration: 2000 });
console.log("dblclick:", event);
}
/**
* @onclick
*/
function onclick(event : EventOptions) {// 这里的函数名可能会被转义掉,如果函数名为空 请看注释
if (!event.playing) {
uni.showToast({ title: '播放', icon: 'none', duration: 2000 });
} else {
uni.showToast({ title: '暂停', icon: 'none', duration: 2000 });
}
console.log("onclick:", event);
}
onMounted(() => {
let datas = getList() as DataItem[];
dataList.value = datas as DataItem[];
let lists = preDataList(datas) as ListOption[];
list.value = lists;
});
</script>
<style>
.avatar {
width: 40px;
height: 40px;
border-radius: 100;
}
.right-view {
margin: 10px;
display: flex;
flex-wrap: wrap;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>