在今年也就是第48次发布的《中国互联网络发展状况统计报告》有这样一个数据,21年的上半年以来,我国我国网民规模达10.11亿,其中短视频用户达8.88亿。碎片化的生活场景下,短视频成为人们获取信息的重要渠道之一。可以看到这10亿互联网用户中,有接近九成都是短视频APP的用户。同时,截止2021年3月,中国短视频人均单日使用时长超过了两小时。在这一数据支撑下,国民对于短视频的粘性还在进一步上升。而5G时代的逐步铺开也是另一个短视频利好的因素。

面对短视频的风口,不少人想要成为内容提供者,也有不少人跃跃欲试想要推出自己的短视频APP程序成为下一个抖音,今天就以uniapp框架下的短视频插件实例分享如何快速实现基础功能的仿抖音短视频插件。


技术实现


效果预览


项目实践

首先需要开发者提前准备好“技术实现”部分的环境并登录到DCloud中。此时打开智密原生仿抖音上下滑动插件的详情页(仿抖音短视频插件-原生控制视频上下滑动-智密科技 - DCloud 插件市场

点击导入之后,系统将会自动打开hbx,并且提示新建导入项目,导入成功之后开发者将会看到这样的一个目录结构,这我们就创建完成基础项目。

试用插件

创建完成项目之后,根据uniapp的官方要求,我们并不能直接使用插件,我们还需要先申请试用,然后打包自定义基座才可以使用。

点击确认申请试用之后,我们还需要回到hbx中选择云端插件并且打包自定义基座,运行的时候我们也需要选择自定义基座,具体操作如下:

至此我们完成了打包自定义基座以及以自定义基座运行的方式了,接下来我们开始进入代码实战阶段。

代码实战

首先我们先看一下demo,demo中提供的/pages/ui/index.nvue是ui展示界面,这里我们可以来分析一下代码情况,笔者划分几个部分给大家分析一下。

界面控件

<view>
<asv_list_player ref="listPlayer" class="player"></asv_list_player>
<bottom-popover v-if="showBottomPopover" ref="popover" @close="onClosePopover">
<text>此处可以展示评论内容</text>
</bottom-popover>
</view>

在这里asv_list_player是展示短视频的主体,用于接受界面控件配置数据以及承载视频播放,上下滑动视频的控件,而bottom-popover是demo内部自带的底部弹出覆盖层,用于展示弹窗业务逻辑。

初始化控件

mounted() {
uni.$on('pause-video', () => {
this.asvListPlayer.pause()
this.showBottomPopover = false
})
this.$nextTick(e => {
// 创建jssdk示例
this.asvListPlayer = new asvListPlayer(this.$refs.listPlayer)
this.$refs.listPlayer.setScaleMode(0) let screenWidth = uni.getSystemInfoSync().screenWidth
// 这里开始是初始化界面布局信息
let views = [
asvListPlayer.getView('rightBox').isLayer().position(['right', 'bottom']).width(60).height('auto').marginRight(15).marginBottom(15)
.children([
asvListPlayer.getView('head').isImage().position(['right', 'bottom']).width(50).height(50).marginBottom(245).radius(30).toJSON(),
asvListPlayer.getView('like').isImage().position(['right', 'bottom']).width(50).height(45).marginBottom(185).radius(0).toJSON(),
asvListPlayer.getView('likeText').isText().position(['right', 'bottom']).width(50).height(20).marginBottom(165).textAlign('center').fontSize(14).toJSON(),
asvListPlayer.getView('commit').isImage().position(['right', 'bottom']).width(50).height(50).marginBottom(111).radius(0).toJSON(),
asvListPlayer.getView('commitText').isText().position(['right', 'bottom']).width(50).height(20).marginBottom(90).textAlign('center').fontSize(14).toJSON(),
asvListPlayer.getView('share').isImage().position(['right', 'bottom']).width(50).height(50).marginBottom(38).radius(0).toJSON(),
asvListPlayer.getView('shareText').isText().position(['right', 'bottom']).width(50).height(20).marginBottom(15).textAlign('center').fontSize(14).toJSON(),
])
.toJSON(),
asvListPlayer.getView('titleBox').isLayer().position(['left', 'bottom']).width(screenWidth * 0.6).height(100).bgc('#55000000').marginLeft(15).marginBottom(15).radius(10)
.children([
asvListPlayer.getView('userBox').isLayer().position(['left']).width('100%').height('auto').marginLeft(10).marginTop(10)
.children([
asvListPlayer.getView('userIcon').isImage().position('left').width(15).height(15).marginTop(3).radius(10).toJSON(),
asvListPlayer.getView('userName').isText().position('left').width('100%').height(20).lines(2).color('#ffffff').marginLeft(20).toJSON(),
])
.toJSON(),
asvListPlayer.getView('title').isText().position('left').width('100%').height('auto').color('#ffffff').marginLeft(10).marginTop(35).marginBottom(10).fontSize(14).marginRight(10).toJSON(),
])
.toJSON(),
]
this.asvListPlayer.setViewConfig({ views })
// 这是初始化视频数据
this.onRefresh();
// 这是初始化控件的监听器
this.asvListPlayer.on('onClick', this.onClick)
this.asvListPlayer.on('onLoadMore', this.onLoadMore)
this.asvListPlayer.on('onRefresh', this.onRefresh)
})
}

在demo中提供的mounted函数中,主要划分为3个阶段,对于3个阶段的代码注释我已经写在其中了。主要的流程是先使用用new asvListPlayer(this.$refs.listPlayer)的方式初始化仿抖音控件,然后通过提供的asvListPlayer.getView的方法构建界面布局对象,this.asvListPlayer.setViewConfig将布局信息绑定到控件中,然后通过初始化视频数据和初始化控件监听器完成业务逻辑。

初始化视频数据

上面我们提到demo用的是this.onRefresh()初始化视频数据,现在我们上俩段代码解析一下控件初始化视频数据都需要执行那些操作。

onRefresh() {
this.list = []
this.presetCur = 0
var datas = []
this.genData().forEach(item => {
let data = asvListPlayer.getItem(item.i)
.video(item.v)
.cover(item.c)
.bindImage('head', 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3533321036,2623280788&fm=15&gp=0.jpg', true)
.bindImage('like', item.like ? 'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/c94c1a75-094a-482a-9acf-f1eefbd24792.png':'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/5a17a78c-f923-41f3-8916-271b4d5d528f.png')
.bindText('likeText', parseInt((item.i * 1 + 1) * (new Date().getTime()) / 1000000000000) + '.4w')
.bindImage('commit', 'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/003910f2-92cf-40c5-9d8a-870401be6e41.png')
.bindText('commitText', parseInt((item.i * 1 + 1) * (new Date().getTime()) / 10000000000) + '')
.bindImage('share', 'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/353edf1a-c2f1-481a-87fc-b9d5df98fb9e.png')
.bindText('shareText', '分享')
.bindText('userName', `UserName`)
.bindImage('userIcon', 'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/89898f94-c3b8-4e14-9d06-7ccb1f27d3ab.png')
.bindText('title', `这是第${item.i * 1}个视频,悄悄是离别的笙箫,沉默是今晚的康桥,再别康桥,再见我终将逝去的青春,愿一切安好,愿你永远都在。`)
.toJSON()
datas.push(data)
})
this.asvListPlayer.loadDatas(datas)
}
点击并拖拽以移动
genData () {
let len = this.list.length
let presetDatas = [
{ v: 'http://txfile-30121.sz.gfp.tencent-cloud.com/1603991410685_8240447_29b1d0770d6cdf43a1dd1fd3a992f96f.mp4', c: 'http://txfile-30121.sz.gfp.tencent-cloud.com/1604043258739_635757_8fd725d85d2b42ad1a8d878ef286d0bf.png' },
{ v: 'http://txfile-30121.sz.gfp.tencent-cloud.com/1611758544058_4702548_5047449b104091e5dd3acfa00ed7eb99.mp4', c: 'http://txfile-30121.sz.gfp.tencent-cloud.com/1611758623279_1481920_89d5f27064f39fee56e663b050d28d8c.png' },
{ v: 'http://txfile-30121.sz.gfp.tencent-cloud.com/1604048716240_10046019_6566a337a503919c68f36a9fad9537b0.mp4', c: 'http://txfile-30121.sz.gfp.tencent-cloud.com/1604048732088_557815_c24e7f6276e650174494aa805fd7e45f.jpg' },
{ v: 'http://txfile-30121.sz.gfp.tencent-cloud.com/1604048722437_2185711_6da27ea482ecb28c549250d09c5abdf1.mp4', c: 'http://txfile-30121.sz.gfp.tencent-cloud.com/1604048734024_824230_198eb706d2052ddea6c2814adfe8d798.jpg' },
]
let newDatas = []
for (let i = len; i < len + 10; i++) {
let item = JSON.parse(JSON.stringify(presetDatas[this.presetCur]))
item.i = i + ''
item.like = item.i % 3 === 0
newDatas.push(item)
this.presetCur = this.presetCur + 1
if (this.presetCur >= presetDatas.length) {
this.presetCur = 0
}
}
this.list = this.list.concat(newDatas)
return newDatas
},

结合俩段代码我们可以看到,onRefresh函数通过getItem的方法,将获取到的ajax数据构建成为视频对象,然后通过loadDatas方法传入给控件,使得控件可以正常渲染视频,这里我们主要就来看看genData方法。

genData方法采用一个非常简单的方式构造假的ajax数据,这样我们改造起来方便很多,我们只需要把他改成promise的形式,然后通过axios异步获取数据之后重新构造即可,废话不多说,我们直接po出来俩个函数的改造结果。

async onRefresh() {
uni.showLoading()
this.list = []
this.presetCur = 0
var datas = []
// 这里注意我没有用try catch,因为我直接用我本地服务器测试的
let ajaxDatas = await this.genData()
ajaxDatas.forEach(item => {
let data = asvListPlayer.getItem(item.i)
.video(item.v)
.cover(item.c)
.bindImage('head', 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3533321036,2623280788&fm=15&gp=0.jpg', true)
.bindImage('like', item.like ? 'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/c94c1a75-094a-482a-9acf-f1eefbd24792.png':'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/5a17a78c-f923-41f3-8916-271b4d5d528f.png')
.bindText('likeText', parseInt((item.i * 1 + 1) * (new Date().getTime()) / 1000000000000) + '.4w')
.bindImage('commit', 'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/003910f2-92cf-40c5-9d8a-870401be6e41.png')
.bindText('commitText', parseInt((item.i * 1 + 1) * (new Date().getTime()) / 10000000000) + '')
.bindImage('share', 'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/353edf1a-c2f1-481a-87fc-b9d5df98fb9e.png')
.bindText('shareText', '分享')
.bindText('userName', `UserName`)
.bindImage('userIcon', 'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/89898f94-c3b8-4e14-9d06-7ccb1f27d3ab.png')
.bindText('title', `这是第${item.i * 1}个视频,悄悄是离别的笙箫,沉默是今晚的康桥,再别康桥,再见我终将逝去的青春,愿一切安好,愿你永远都在。`)
.toJSON()
datas.push(data)
})
this.asvListPlayer.loadDatas(datas)
uni.hideLoading()
}
点击并拖拽以移动
async genData () {
return new Promise(resolve => {
let list = await new axios({
url: 'http://192.168.0.25:8080/api/getVideoList',
type: 'post'
})
// 这里axios会包一层data,所以需要这样处理
list = list.data
let retList = []
list.forEach(item => {
retList.push({
v: item.videoUrl,
c: item.coverUrl,
i: item.id,
like: item.likes
})
})
resolve(retList)
})
},

这里通过给俩个方法加上async/await,以及给genData加上Promise返回,这样我们就可以无痛的改造让demo支持ajax获取视频数据,对于分页获取,也就是onLoadMore的改造也是如此。

监听控件点击

mounted () {
this.asvListPlayer.on('onClick', this.onClick)
}
点击并拖拽以移动
onClick({ type, data }) {
uni.showToast({
icon: 'none',
position: 'bottom',
title: '您点击了第' + (data.position + 1) + '个视频的控件,控件名为:' + data.id
})
let index = data.position
let [item] = this.list.filter((R,I) => I === index)
switch (data.id) {
case 'like':
item.like = !item.like
this.asvListPlayer.setItemData(index,asvListPlayer.getItem(item.i)
.video(item.v)
.cover(item.c)
.bindImage('head', 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3533321036,2623280788&fm=15&gp=0.jpg', true)
.bindImage('like', item.like ? 'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/c94c1a75-094a-482a-9acf-f1eefbd24792.png':'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/5a17a78c-f923-41f3-8916-271b4d5d528f.png')
.bindText('likeText', parseInt((item.i * 1 + 1) * (new Date().getTime()) / 1000000000000) + '.4w')
.bindImage('commit', 'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/003910f2-92cf-40c5-9d8a-870401be6e41.png')
.bindText('commitText', parseInt((item.i * 1 + 1) * (new Date().getTime()) / 10000000000) + '')
.bindImage('share', 'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/353edf1a-c2f1-481a-87fc-b9d5df98fb9e.png')
.bindText('shareText', '分享66')
.bindText('userName', `UserName`)
.bindImage('userIcon', 'https://files.qiadoo.com/CareBoBo/Common/2021/05/17/89898f94-c3b8-4e14-9d06-7ccb1f27d3ab.png')
.bindText('title', `这是第${item.i * 1}个视频,悄悄是离别的笙箫,沉默是今晚的康桥,再别康桥,再见我终将逝去的青春,愿一切安好,愿你永远都在。`)
.toJSON())
break
case 'commit':
this.showBottomPopover = true
break
case 'share':
uni.showActionSheet({
itemList: ['分享到微信']
})
break
}
}

在这里的话,控件用的是事件回调的方式,通过onClick返回事件信息,data.id就是我们setViewConfig的时候传入的asvListPlayer.getView('head')这里的字符串,也就是唯一id,我们可以通过id判断用户点击了什么,从而响应对应的事件,甚至是通过setItemData刷新视频的控件布局视频,比如点赞成功之类的。

为了方便我这里本地接口还是复制黏贴官方demo提供的链接地址,大家有需要的可以自己用实际数据测试,测试下感觉流畅度还是蛮ok的,但是要注意以下几点。

  1. 因为这用了自定义原生控件,因此必须使用nvue界面布局
  2. 在使用.bindText('commitText', '666')绑定文字的时候,传入的必须是String类型,一开始我传入Number就直接渲染不出来了
  3. 视频不能使用m3u8这种,最好是使用mp4,毕竟mp4才是标准的移动视频格式。

10分钟快速上车短视频风口:基于uniapp框架创建自己的仿抖音短视APP的更多相关文章

  1. Vue3.0短视频+直播|vue3+vite2+vant3仿抖音界面|vue3.x小视频实例

    基于vue3.0构建移动端仿抖音/快手短视频+直播实战项目Vue3-DouYin. 5G时代已来,短视频也越来越成为新一代年轻人的娱乐方式,在这个特殊之年,又将再一次成为新年俗! 基于vue3.x+v ...

  2. 5分钟快速打造WebRTC视频聊天<转>

    原文地址: 5分钟快速打造WebRTC视频聊天 百度一下WebRTC,我想也是一堆.本以为用这位朋友( 搭建WebRtc环境 )的SkyRTC-demo 就可以一马平川的实现聊天,结果折腾了半天,文本 ...

  3. Vite2+Electron仿抖音|vite2.x+electron12+vant3短视频|直播|聊天

    整合vite2+electron12跨平台仿抖音电脑版实战Vite2-ElectronDouYin. 基于vite2.0+electron12+vant3+swiper6+v3popup等技术跨端仿制 ...

  4. 基于vue+uniapp直播项目|uni-app仿抖音/陌陌直播室

    一.项目简介 uni-liveShow是一个基于vue+uni-app技术开发的集小视频/IM聊天/直播等功能于一体的微直播项目.界面仿制抖音|火山小视频/陌陌直播,支持编译到多端(H5.小程序.Ap ...

  5. iOS多种刷新样式、音乐播放器、仿抖音视频、旅游App等源码

    iOS精选源码 企业级开源项目,模仿艺龙旅行App 3D立体相册,可以旋转的立方体 横竖屏切换工具,使用陀螺仪检测手机设备方向,锁屏状... Swift版Refresh(可以自定义多种样式)架构方面有 ...

  6. rtvue-lowcode:一款基于uniapp框架和uview组件库的开源低代码开发平台

    rtvue-lowcode低代码开发平台 rtvue-lowcode一款基于uniapp框架和uview组件库的低代码开发平台,项目提供可视化拖拽编辑器,采用MIT开源协议,适用于app.小程序等项目 ...

  7. 【Istio实际操作篇】Istio入门,10分钟快速安装

    @ 目录 前言 本文说明 请大家务必查看 环境准备 详细版 入门:搭建步骤 Istio软件包下载 下载Istio 卸载 简洁版 安装 卸载 学习不走弯路,gz号「yeTechLog」 前言 上一篇讲了 ...

  8. iOS 快速集成ijkplayer视频直播与录播框架

    最近由于需求的变动,项目内把最初最简单的原生直播框架变成了B站开源的ijkplayer框架,下面把具体的过程总结一下整个过程都比较简单,重要的是理解的过程,集成完毕之后,视频的用户体验比苹果原生好了很 ...

  9. uni-app仿抖音APP短视频+直播+聊天实例|uniapp全屏滑动小视频+直播

    基于uniapp+uView-ui跨端H5+小程序+APP短视频|直播项目uni-ttLive. uni-ttLive一款全新基于uni-app技术开发的仿制抖音/快手短视频直播项目.支持全屏丝滑般上 ...

随机推荐

  1. 第41篇-JNIEnv与JavaVM的初始化

    JavaVM和JNIEnv的初始化和JVM各模块的初始化都是在JNI_CreateJavaVM()函数中完成.这一篇将详细介绍JavaVM和JNIEnv的初始化过程. 1.初始化JavaVM Java ...

  2. CF1592F2 Alice and Recoloring 2

    目前在看贪心/构造/DP 杂题选做,发现一道非常不错的结论题,具有启发意义. 先说明如下结论 结论一:如何怎么样都不会使用二和三操作 证明: 二三操作显然可以通过两次一操作达到,而其操作费用大于两次一 ...

  3. Dirichlet 前缀和的几种版本

    [模板]Dirichlet 前缀和 求 \[B[i] = \sum_{d|i} A[d] \] $ n \le 2\times 10^{7} $ 看代码: for( int i = 1 ; i < ...

  4. Codeforces 1304F1/F2 Animal Observation(单调队列优化 dp)

    easy 题目链接 & hard 题目链接 给出一张 \(n \times m\) 的矩阵,每个格子上面有一个数,你要在每行选出一个点 \((i,t)\),并覆盖左上角为 \((i,t)\), ...

  5. STM32驱动直流电机的程序与电路设计(IR2110S自举电路+H桥+高级定时器和死区PWM)

    https://blog.csdn.net/geek_monkey/article/details/82079435

  6. 谈谈AI

    由AI大作业想到的 近几年<人工智能导论>的大作业是编写一个博弈程序,这类程序的典型框架就是α-β剪枝算法,像著名的打败了国际象棋大师卡斯帕罗的深蓝,就是这么干的,一些中国象棋程序也是这样 ...

  7. 10 — springboot整合mybatis — 更新完毕

    1.xml版 -- 复杂sql使用xml,简单sql使用注解 1).导入依赖 <!-- mybatis-spring-boot-starter是第三方( mybatis )jar包,不是spri ...

  8. 巩固javaweb的第二十二天

    巩固内容: 使用表单数据 : 要对用户输入的信息进行验证,需要先获取输入信息.每个表单元素都属于一个 form 表单,要获取信息,需要先获取 form,然后访问表单元素的值. 有两种方式可以获取 fo ...

  9. Ubuntu 14.04 升级到 Ubuntu16.04

    Ubuntu 14.04 升级到 Ubuntu16.04 1). 更改source.list 源 (24条消息) Ubuntu16.04 source.list更改源_dylan的博客-CSDN博客_ ...

  10. Hadoop【Hadoop-HA搭建(HDFS、YARN)】

    目录 0.HDFS-HA的工作机制 1. HDFS-HA集群配置 1.1 环境准备 1.2 规划集群 1.3 配置Zookeeper集群 2. 配置HDFS-HA集群 3. 启动HDFS-HA集群 4 ...