加载中...

Uni-APP高仿抖音|uniapp直播+小视频+聊天实例

Uni-APP高仿抖音|uniapp直播+小视频+聊天实例

项目概况

uniapp-ttLive 一款使用uni-app+uview-ui开发的跨端短视频/直播聊天项目。

如下图:编译至小程序+h5+App端效果

New Image

采用swiper组件实现小视频上下滑动切换。支持播放/暂停。

New Image

运用技术

  • 编辑器/技术:HbuilderX3.1.21+Uniapp+Nvue+Vuex
  • UI组件库:uView-ui / uni-ui
  • 矢量图标库:iconfont字体图标
  • 弹窗组件:ua-popup 基于uni-app封装跨端弹窗组件
  • 自定义导航条+底部菜单栏
  • 编译:h5+小程序+APP端
New Image
New Image

配置main.js

代码语言:javascript
复制
import Vue from ''vue''
import App from ''./App''

import uView from ''uview-ui''
Vue.use(uView)

import API from ''@/common/request''
Vue.prototype.$api = API

// 引入状态管理
import Store from ''./store''
Vue.prototype.$store = Store

Vue.config.productionTip = false
App.mpType = ''app''

// #ifdef APP-PLUS
plus.navigator.closeSplashscreen()
// #endif

const app = new Vue({
    ...App
})
app.$mount()

项目结构目录

New Image
New Image
New Image
New Image
New Image
New Image
New Image
New Image
New Image
New Image
New Image
New Image
New Image
New Image
New Image
New Image
New Image
New Image
New Image
New Image

由于prototype不支持nvue页面,所以使用globalData全局设置状态栏高度。

代码语言:javascript
复制
<script>
	export default {
		globalData: {
			// 全局设置状态栏和导航栏高度
			statusBarH: 0,
			customBarH: 0,
		},
		onLaunch: function() {
			uni.getSystemInfo({
				success: (e) => {
					// 获取手机状态栏高度
					let statusBar = e.statusBarHeight
					let customBar
					
					// #ifndef MP
					customBar = statusBar + (e.platform == ''android'' ? 50 : 45)
					// #endif
					
					// #ifdef MP-WEIXIN
					// 获取胶囊按钮的布局位置信息
					let menu = wx.getMenuButtonBoundingClientRect()
					// 导航栏高度 = 胶囊下距离 + 胶囊上距离 - 状态栏高度
					customBar = menu.bottom + menu.top - statusBar
					// #endif
					
					// #ifdef MP-ALIPAY
					customBar = statusBar + e.titleBarHeight
					// #endif
					
					// 兼容nvue写法(H5/小程序/APP/APP-Nvue)
					this.globalData.statusBarH = statusBar
					this.globalData.customBarH = customBar
				}
			})
		},
		onShow: function() {
			console.log(''App Show'')
		},
		onHide: function() {
			console.log(''App Hide'')
		}
	}
</script>

uniapp自定义组件

项目中导航条ua-navbar及底部菜单栏ua-tabbar组件,均是自定义组件。

组件支持镂空在video组件上面,实现全屏沉浸式效果。

New Image
代码语言:javascript
复制
<template>
    <view class="ua__navbar">
        <view class="ua__navbar-wrap" :class="{''custom'': custom, ''fixed'': fixed || transparent}"
            :style="{''height'': customBarH + ''px'', ''padding-top'': (custom ? statusBarH : 0) + ''px'', ''background'': bgcolor, ''color'': color, ''z-index'': zIndex}">
            <!-- //左侧 (返回) -->
            <view class="action navbar-action__left" v-if="back && back!=''false''" @click="onBack">
                <template v-if="$slots.back">
                    <slot name="back" />
                </template>
                <template v-else><text class="iconfont nvuefont"
                        :style="{''color'': color}">{{''\ue84c''}}</text></template>
                <slot name="backText" />
            </view>
            <slot name="left" />

            <!-- //标题 -->
            <view v-if="!search" class="navbar-title" :class="{''center'': center}">
                <template v-if="$slots.title">
                    <slot name="title" />
                </template>
                <template v-else><text :style="{''color'': color}">{{title}}</text></template>
            </view>

            <!-- //搜索框 -->
            <view v-if="search" class="action navbar-action__search">
                <slot name="search" />
            </view>

            <!-- //右侧 -->
            <view class="action navbar-action__right">
                <slot name="right" />
            </view>
        </view>
    </view>
</template>
New Image

之前有过这方面的分享,如果感兴趣可以去看看,另外两个组件均上传至uniapp插件市场。

uniapp自定义组件uaNavbar+uaTabbar

uaPopup全新支持nvue弹窗组件

New Image

uapopup针对uniapp短视频直播项目开发的一款全端弹窗组件。

支持函数调用及组件写法调用。

代码语言:javascript
复制
<ua-popup v-model="isVisibleConfirm" shadeClose="false" title="标题" xclose z-index="1001"
    content="<div style=''color:#ff557f;padding:20px 40px;''>预测未来的最好办法是自己亲手创造未来!</div>"
    :btns="[
        {text: ''取消'', click: handleCancel},
        {text: ''确定'', style: ''color:#00aa00;'', click: handleOk},
    ]"
/>
代码语言:javascript
复制
<script>
export default {
    methods: {
        handleOk() {
            let $ua = this.$refs.uapopup
            $ua.open({
                content: ''人生漫漫,且行且珍惜'',
                customStyle: {''background-color'': ''rgba(170, 0, 127, 0.6)'', ''color'': ''#fff''},
                time: 3,
                onClose() {
                    $ua.open({
                        type: ''android'',
                        content: ''<div >不要等待机会,而要创造机会</div>'',
                        customStyle: {''width'': ''210px''},
                        btns: [
                            {
                                text: ''关闭'',
                                click() {
                                    $ua.close()
                                }
                            },
                            {
                                text: ''确定'',
                                style: ''color:#00aa00;'',
                                click() {
                                    // ...
                                }
                            }
                        ]
                    })
                }
            })
        }
    }
}
</script>

调用非常简单,支持20+参数混合搭配使用。

uni-app跨平台自定义弹窗组件

uni-app直播/短视频

项目中直播/短视频页面整体分为顶部导航区域、视频区域、底部菜单栏区域三个部分。

New Image
代码语言:javascript
复制
<view v-if="currentTab == 2" class="ua__tabcnt-recommend">
    <swiper class="ua__vdplayer-swiper flex1" :current="currentVideo" vertical @change="handleSwipeVertical">
        <swiper-item v-for="(item, index) in videoList" :key="index">
            <!-- 视频模块 -->
            <view class="ua__vdplayer-video flex1">
                <video class="vdplayer" :id="''vdplayer'' + index" :ref="''vdplayer'' + index" 
                    :src="item.src"
                    :controls="false" :loop="true" :show-center-play-btn="false" object-fit="fill"
                    :autoplay="index == currentVideo"
                    @play="isPlaying=true" @timeupdate="handleTimeUpdate"
                    :style="{''width'': winWidth, ''height'': winHeight}"
                >
                </video>
                <view class="ua__vdplayer-playwrap" @click="handleVideoClicked"><view v-if="!isPlaying" class="ua__vdplayer-playbtn"><text class="iconfont">{{`\ue607`}}</text></view></view>
            </view>
            <!-- 信息模块 -->
            <view class="ua__vdplayer-info flexbox flex-col">
                <view class="flexbox flex-row flex-alignb">
                    <!-- //左侧信息 -->
                    <view class="vdinfo__left flex1">
                        <view class="ltitem uavatar flexbox flex-row">
                            <navigator url="#" class="flexbox flex-alignc flex-row"><image class="uimg" :src="item.avatar" /><text class="uname">{{item.author}}</text></navigator>
                            <view class="flexbox btn" :class="{''actived'': item.isFollow}" @click="handleFollow(index)"><text class="btn-text">{{item.isFollow ? ''已关注'' : ''关注''}}</text></view>
                        </view>
                        <view v-if="item.topic" class="ltitem flexbox flex-row">
                            <view class="kw" v-for="(kw, index2) in item.topic" :key="index2"><text class="lbl">#{{kw}}</text></view>
                        </view>
                        <view class="ltitem"><text class="desc">{{item.desc}}</text></view>
                    </view>
                    <!-- //右侧按钮 -->
                    <view class="vdinfo__right flexbox flex-col">
                        <view class="rtitem ball" v-if="item.goods&&item.goods.length > 0" @click="handleShowGoodsPopup(item.goods)"><text class="icon iconfont">{{`\ue734`}}</text></view>
                        <view class="rtitem" :class="{''isliked'': item.isLike}" @click="handleLiked(index)"><text class="icon iconfont">{{`\ue635`}}</text><text class="num">{{item.likeNum+(item.isLike ? 1 : 0)}}</text></view>
                        <view class="rtitem" @click="showReplyPopup = true"><text class="icon iconfont">{{`\ue632`}}</text><text class="num">{{item.replyNum}}</text></view>
                        <view class="rtitem" @click="showSharePopup = true"><text class="icon iconfont">{{`\ue63b`}}</text><text class="num">{{item.shareNum}}</text></view>
                    </view>
                </view>
            </view>
        </swiper-item>
    </swiper>
    <!-- 底部播放进度条 -->
    <view class="ua__vdplayer-progress"><view class="bar" :style="{''width'': progressBar+''px''}"></view></view>
</view>
代码语言:javascript
复制
<script>
    const app = getApp()
    import videoJSON from ''@/mock/videolist.js''
    
    export default {
        data() {
            return {
                // 导航栏高度
                customBarHeight: app.globalData.customBarH,
                navbarBgcolor: ''#21252b'',
                tabbarBgcolor: ''#21252b'',
                
                tabNavLs: [
                    {label: ''附近动态'', badge: 5, lists: []},
                    {label: ''关注'', lists: []},
                    {label: ''推荐'', dot: true, lists: []},
                ],
                // 当前选项卡
                currentTab: 0,
                
                // 当前视频索引
                currentVideo: 0,
                // 视频数据
                videoList: videoJSON,
                // 视频是否播放中
                isPlaying: false,
                // 点击次数
                clickNum: 0,
                // 视频播放进度条
                progressBar: 0,
                clickTimer: null,
                
                // 屏幕宽高
                winWidth: '''',
                winHeight: '''',
                
                popupGoodsList: [],
                showGoodsPopup: false,
                showReplyPopup: false,
                showSharePopup: false,
            }
        },
        watch: {
            currentTab(val) {
                this.changeTabPanel(val)
            }
        },
        computed:{
            customBarMargin() {
                return `margin-top: ${this.customBarHeight}px`
            }
        },
        created() {
            // 引入iconfont字体
            // #ifdef APP-NVUE
            const domModule = weex.requireModule(''dom'')
            domModule.addRule(''fontFace'', {
                fontFamily: "nvueIcon",
                ''src'': "url(''/static/fonts/iconfont.ttf'')"
            });
            // #endif
            
            let wW = uni.getSystemInfoSync().windowWidth
            let wH = uni.getSystemInfoSync().windowHeight
            this.winWidth = `${wW}px`
            this.winHeight = `${wH}px`
        },
        methods: {
            
            // 长按动态
            handleDynamicMenu(e) {
                let points
                // #ifndef APP-NVUE
                points = [e.touches[0].clientX, e.touches[0].clientY]
                // #endif
                // #ifdef APP-NVUE
                points = [e.touches[0].screenX, e.touches[0].screenY]
                // #endif
                
                this.$refs.uapopup.open({
                    type: ''contextmenu'',
                    follow: points,
                    btns: [
                        {text: ''不感兴趣''},
                        {text: ''复制''},
                        {
                            text: ''举报'',
                            style: ''color:#f00;'',
                            click: () => {
                                this.$refs.uapopup.close()
                            }
                        },
                    ],
                })
            },
            
            /* ++++++++++ { 视频播放模块 } ++++++++++ */
            getVideoCtx() {
                // return this.$refs[''vdplayer'' + this.currentVideo][0]
                return uni.createVideoContext(''vdplayer''+ this.currentVideo, this)
            },
            
            // 垂直滑动视频
            handleSwipeVertical(e) {
                let index = e.detail.current
                this.progressBar = 0
                this.isPlaying = false
                let video = this.getVideoCtx()
                if(!video) return
                video.pause()
                // 重新开始
                video.seek(0)
                
                this.currentVideo = index
                
                // 自动播放
                this.handlePlay()
            },
            
            handlePlay() {
                let video = this.getVideoCtx()
                if(!video) return
                video.play()
                this.isPlaying = true
            },
            
            handlePause() {
                let video = this.getVideoCtx()
                if(!video) return
                video.pause()
                this.isPlaying = false
            },
            
            // 点击视频(单击/双击)
            handleVideoClicked() {
                this.clickTimer && clearTimeout(this.clickTimer)
                this.clickNum++
                this.clickTimer = setTimeout(() => {
                    if(this.clickNum >= 2) {
                        console.log(''你双击了'')
                    }else {
                        console.log(''你单击了'')
                        if(this.isPlaying) {
                            this.handlePause()
                        }else {
                            this.handlePlay()
                        }
                    }
                    this.clickNum = 0
                }, 250)
            },
            
            ...
        }
    }
</script>

另外,短视频底部有一条时间刻度进度条。

New Image
代码语言:javascript
复制
// 播放进度变化时触发
handleTimeUpdate(e) {
    let { currentTime, duration } = e.detail
    
    this.progressBar = parseInt((currentTime / duration).toFixed(2) * parseInt(this.winWidth))
},

yeah,基于uniapp开发抖音短视频/直播聊天项目就分享到这里了。

New Image