更新记录

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-swiperml-swiper-v2ml-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页面,并且提供了 很多组件方法 便于自定义实现更多的业务逻辑;

特别说明

特别说明:
  • 收费代码有版权问题,且发布后的插件会被自动加密,如非购买本插件出现的各种问题,均与此有关。
  • 如正常购买了插件,出现了运行报错,或者案例代码运行报错,均可入群与作者联系,作者将提供帮助,并解决问题。
  • 该组件将持续维护,有任何问题均可留言。
  • 非购买出现的问题,非组件本身问题 作者将不予处理和解决,切勿恶意差评,创作不易 还请理解。
  • 购买普通版权后,想要源码的可插件市场直接购买源码,如需要继续扩展 可以直接联系作者 索要底层源代码 以供扩展使用。
  • 使用案例时特别注意,案例中的函数名会被转义为空,报错时 请先检查是否存在函数名为空的,为空时补全后重新运行即可。

功能演示

img

img

img

img

使用说明

img

案例报错说明

img

复制代码// @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>

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

使用中有什么不明白的地方,就向插件作者提问吧~ 我要提问