10分钟快速上车短视频风口:基于uniapp框架创建自己的仿抖音短视APP
在今年也就是第48次发布的《中国互联网络发展状况统计报告》有这样一个数据,21年的上半年以来,我国我国网民规模达10.11亿,其中短视频用户达8.88亿。碎片化的生活场景下,短视频成为人们获取信息的重要渠道之一。可以看到这10亿互联网用户中,有接近九成都是短视频APP的用户。同时,截止2021年3月,中国短视频人均单日使用时长超过了两小时。在这一数据支撑下,国民对于短视频的粘性还在进一步上升。而5G时代的逐步铺开也是另一个短视频利好的因素。
面对短视频的风口,不少人想要成为内容提供者,也有不少人跃跃欲试想要推出自己的短视频APP程序成为下一个抖音,今天就以uniapp框架下的短视频插件实例分享如何快速实现基础功能的仿抖音短视频插件。
技术实现
- 开发环境:HbuilderX + nodejs
- 技术框架:uniapp + vue2.x
- 测试环境:App端(Android + IOS)
- 代码:开源
- SDK: 智密原生仿抖音上下滑动插件仿抖音短视频插件-原生控制视频上下滑动-智密科技 - DCloud 插件市场
效果预览
项目实践
首先需要开发者提前准备好“技术实现”部分的环境并登录到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的,但是要注意以下几点。
- 因为这用了自定义原生控件,因此必须使用nvue界面布局
- 在使用.bindText('commitText', '666')绑定文字的时候,传入的必须是String类型,一开始我传入Number就直接渲染不出来了
- 视频不能使用m3u8这种,最好是使用mp4,毕竟mp4才是标准的移动视频格式。
10分钟快速上车短视频风口:基于uniapp框架创建自己的仿抖音短视APP的更多相关文章
- Vue3.0短视频+直播|vue3+vite2+vant3仿抖音界面|vue3.x小视频实例
基于vue3.0构建移动端仿抖音/快手短视频+直播实战项目Vue3-DouYin. 5G时代已来,短视频也越来越成为新一代年轻人的娱乐方式,在这个特殊之年,又将再一次成为新年俗! 基于vue3.x+v ...
- 5分钟快速打造WebRTC视频聊天<转>
原文地址: 5分钟快速打造WebRTC视频聊天 百度一下WebRTC,我想也是一堆.本以为用这位朋友( 搭建WebRtc环境 )的SkyRTC-demo 就可以一马平川的实现聊天,结果折腾了半天,文本 ...
- Vite2+Electron仿抖音|vite2.x+electron12+vant3短视频|直播|聊天
整合vite2+electron12跨平台仿抖音电脑版实战Vite2-ElectronDouYin. 基于vite2.0+electron12+vant3+swiper6+v3popup等技术跨端仿制 ...
- 基于vue+uniapp直播项目|uni-app仿抖音/陌陌直播室
一.项目简介 uni-liveShow是一个基于vue+uni-app技术开发的集小视频/IM聊天/直播等功能于一体的微直播项目.界面仿制抖音|火山小视频/陌陌直播,支持编译到多端(H5.小程序.Ap ...
- iOS多种刷新样式、音乐播放器、仿抖音视频、旅游App等源码
iOS精选源码 企业级开源项目,模仿艺龙旅行App 3D立体相册,可以旋转的立方体 横竖屏切换工具,使用陀螺仪检测手机设备方向,锁屏状... Swift版Refresh(可以自定义多种样式)架构方面有 ...
- rtvue-lowcode:一款基于uniapp框架和uview组件库的开源低代码开发平台
rtvue-lowcode低代码开发平台 rtvue-lowcode一款基于uniapp框架和uview组件库的低代码开发平台,项目提供可视化拖拽编辑器,采用MIT开源协议,适用于app.小程序等项目 ...
- 【Istio实际操作篇】Istio入门,10分钟快速安装
@ 目录 前言 本文说明 请大家务必查看 环境准备 详细版 入门:搭建步骤 Istio软件包下载 下载Istio 卸载 简洁版 安装 卸载 学习不走弯路,gz号「yeTechLog」 前言 上一篇讲了 ...
- iOS 快速集成ijkplayer视频直播与录播框架
最近由于需求的变动,项目内把最初最简单的原生直播框架变成了B站开源的ijkplayer框架,下面把具体的过程总结一下整个过程都比较简单,重要的是理解的过程,集成完毕之后,视频的用户体验比苹果原生好了很 ...
- uni-app仿抖音APP短视频+直播+聊天实例|uniapp全屏滑动小视频+直播
基于uniapp+uView-ui跨端H5+小程序+APP短视频|直播项目uni-ttLive. uni-ttLive一款全新基于uni-app技术开发的仿制抖音/快手短视频直播项目.支持全屏丝滑般上 ...
随机推荐
- Codeforces 587D - Duff in Mafia(2-SAT+前后缀优化建图)
Codeforces 题面传送门 & 洛谷题面传送门 2-SAT hot tea. 首先一眼二分答案,我们二分答案 \(mid\),那么问题转化为,是否存在一个所有边权都 \(\le mid\ ...
- P5599【XR-4】文本编辑器
题目传送门. 题意简述:给定长度为 \(n\) 的文本串 \(a\) 和有 \(m\) 个单词的字典 \(s_i\).\(q\) 次操作,每次求出字典内所有单词在 \(a[l,r]\) 的出现次数,或 ...
- R语言与医学统计图形-【9】过渡函数qplot
ggplot2绘图系统 基础绘图包向ggplot2过渡--qplot 绘图理念的不同: 基础绘图包是先铺好画布,再在这张画布上作图(常规思维): ggplot2打破常规,采用图层叠加的方法. qplo ...
- Python爬虫3大解析库使用导航
1. Xpath解析库 2. BeautifulSoup解析库 3. PyQuery解析库
- Linux— 查看系统的位数
[root@zf-test-web01-4 ~]# file /bin/ls #"/bin/ls" is a binary file /bin/ls: ELF ...
- HDFS01 概述
HDFS 概述 目录 HDFS 概述 HDFS的产生背景和定义 HDFS产生背景 HDFS定义 优缺点 优点 缺点 组成 NameNode DataNode Secondary NameNode(2n ...
- 数仓:解读 NameNode 的 edits 和 fsimage 文件内容
一.edits 文件 一)文件组成 一个edits文件记录了一次写文件的过程,该过程被分解成多个部分进行记录:(每条记录在hdfs中有一个编号) 每一个部分为: '<RECORD>...& ...
- Spark相关知识点(一)
spark工作机制,哪些角色,作用. spark yarn模式下的cluster模式和client模式有什么区别.
- 【转载】HBase基本数据操作详解【完整版,绝对精品】
转载自: http://blog.csdn.net/u010967382/article/details/37878701 概述 对于建表,和RDBMS类似,HBase也有namespace的概念,可 ...
- 文件读写以及NMEA码中GPS信息的提取
首先先了解下什么是NMEA码,这里有很好的解释,就不直接搬运了 http://www.gpsbaby.com/wz/nmea.html 首先要找到包含GPS信息的文本行,即字符串GPGGA所在行 $G ...