app/components/vlog/videoLocal.vue
2025-04-21 17:35:54 +08:00

1018 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view style="flex: 1">
<!-- <uni-list @change="onchange" :num="playerList.length"> -->
<list
ref="list"
:pagingEnabled="true"
:show-scrollbar="false"
@scroll="listScroll"
@scrollend="scroll"
:scrollable="true"
>
<refresh
@pullingdown="onpullingdown"
@refresh="onrefresh"
:display="refreshing ? 'show' : 'hide'"
>
<text class="refresh-info-txt"></text>
<loading-indicator></loading-indicator>
</refresh>
<cell
class="flbx"
:recycle="false"
v-if="playerList.length == 0"
:style="{ height: screenHeight + 'px' }"
>
<text class="warn-info">暂无数据</text>
</cell>
<cell
:recycle="false"
:class="isIOS ? 'abs-box-ios' : ''"
v-for="(item, index) in playerList"
:key="index"
:data-index="index"
:style="{ height: screenHeight + 'px' }"
>
<!-- <uni-video :src="item.url" :playStatus="playStatus" :screenHeight="screenHeight" v-if="playerCur === index" @play="onplay"></uni-video> -->
<video
ref="localVideo"
id="localVideo"
:object-fit="item.width >= item.height ? 'contain' : 'fill'"
play-btn-position="center"
:header="{ Range: 'bytes=0-' }"
preload="metadata"
:src="item.url"
:controls="false"
:enable-progress-gesture="false"
v-if="playerCur === index"
autoplay
loop
:http-cache="true"
style="width: 750rpx"
:style="{ height: screenHeight + 'px' }"
@click="playOrPause"
@play="onplay"
@error="onerror"
@timeupdate="onTimeUpdate"
></video>
<image
:lazy-load="true"
:fade-show="false"
v-if="!item.play"
:src="item.firstFrameImg"
:mode="item.width >= item.height ? 'aspectFit' : 'scaleToFill'"
style="width: 750rpx; filter: blur(10px)"
:style="{ height: screenHeight + 'px' }"
></image>
<!-- 播放按钮 -->
<view
v-if="!isIOS && !playStatus && item.play"
class="abs-box-ad"
:style="{ height: screenHeight + 'px' }"
>
<image
src="/static/images/playvd.png"
class="play-btn"
></image>
</view>
<image
v-if="isIOS && !playStatus && item.play"
src="/static/images/playvd.png"
class="play-btn"
></image>
<!-- 进度条 -->
<view
v-if="progressFlag"
class="progress-bar"
@touchstart="startDrag"
@touchmove="onDragging"
@panend="endDrag"
>
<view
class="progress-background"
v-if="progressFlag"
></view>
<view
class="progress-foreground"
v-if="progressFlag"
:class="isDragging ? '' : 'anm'"
:style="{ width: progressWidth + 'px' }"
></view>
</view>
<!-- 浮动时间提示 -->
<view
class="float-time"
v-if="isDragging"
:style="{ left: floatLeft + 'px' }"
>
<text style="color: #fff">{{ formatTime(floatTime) }}</text>
</view>
<view class="publish-info-box">
<view class="">
<text class="publish-info-vloger-name">@{{ item.vlogerName }}</text>
<text class="publish-info-content">{{ item.content }}</text>
<view class="publish-info-music-box">
<image
src="/static/images/icon-fire.png"
class="icon-fire"
></image>
<text class="muisc-words">{{ item.vlogerName }}的原声创作</text>
</view>
</view>
<!-- <view
class=""
style="flex-direction: row"
>
<image
src="/static/images/cd-play-4.gif"
style="width: 150rpx; height: 150rpx; opacity: 0.8"
></image>
</view> -->
</view>
<!-- 视频展示右侧的操作按钮,头像 - 点赞 - 评论 - 转发 -->
<view class="operation-box">
<view class="operation-face-box">
<image
:src="item.vlogerFace"
class="user-face"
@click="goUserInfoSeeSee(item.vlogerId)"
></image>
</view>
<image
v-if="!item.doIFollowVloger && userId != thisVlogerId"
src="/static/images/icon-follow.png"
@click="followMe(item.vlogerId)"
class="follow-me"
></image>
<view class="like-box">
<image
v-if="!item.doILikeThisVlog"
src="/static/images/icon-unlike.png"
@click="likeOrDislikeVlog(1)"
class="icon"
></image>
<image
v-if="item.doILikeThisVlog"
src="/static/images/icon-like.png"
@click="likeOrDislikeVlog(0)"
class="icon"
></image>
<text class="some-counts">
{{ getGraceNumber(item.likeCounts) }}
</text>
</view>
<view class="comment-and-share-box">
<image
src="/static/images/icon-comments.png"
@click="commentToggle"
class="icon"
></image>
<!-- <text class="some-counts">{{item.commentsCounts}}</text> -->
<text class="some-counts">
{{ getGraceNumber(thisVlogTotalComentCounts) }}
</text>
</view>
<view class="comment-and-share-box">
<image
src="/static/images/icon-share.png"
@click="shareToggle"
class="icon"
></image>
<text class="some-counts">分享</text>
</view>
</view>
</cell>
</list>
<!-- </uni-list> -->
<view>
<!-- 底部窗口popup -->
<uni-popup
ref="comment"
type="comment"
>
<uni-popup-comments
:thisVlogerId="thisVlogerId"
:thisVlogId="thisVlogId"
></uni-popup-comments>
</uni-popup>
<uni-popup
ref="share"
background-color="#fff"
type="share"
>
<uni-popup-share
:thisVlogerId="thisVlogerId"
:thisVlogId="thisVlogId"
:vlogUrl="thisVlog.url"
:isPrivate="thisVlog.isPrivate"
></uni-popup-share>
</uni-popup>
</view>
</view>
</template>
<script>
let system = uni.getSystemInfoSync();
import storage from '@/utils/storage.js'; //缓存
import { graceNumber } from '@/utils/tools.js';
import api from '@/config/api.js';
import { vlogList, vlogLike, vlogUnLike, vlogComment, vlogFollow, vlogTotalLikedCounts } from '@/api/vlog';
export default {
props: {
screenHeight: {
default: 0
},
src: {
default: false
},
playStatus: {
default: false
},
videoList: {
default: []
},
refreshList: {
default: []
},
pagingList: {
default: []
},
//
pid: {
type: [Number, String],
default: ''
},
parentId: {
type: String,
default: ''
},
progressFlag: {
default: false
}
},
data() {
return {
showMsg: false,
thisVlog: {}, // 当前的短视频对象
thisVlogId: '', // 当前的短视频主键id
thisVlogerId: '', // 当前的短视频博主的主键id
userId: '', // 当前用户id
refreshing: false,
showRefreshLoading: 'hide',
playerCur: 0,
page: 0,
totalPage: 0,
playerList: [],
// playerList: this.videoList,
thisVlogTotalComentCounts: 0,
videoContext: {},
currentIndex: 0,
contentOffsetY: 0,
times: new Date().getTime(),
objectFit: 'fill',
isIOS: uni.getSystemInfoSync().platform == 'ios',
duration: 0,
currentTime: 0,
progressWidth: 0,
floatTime: 0,
floatLeft: 0,
isDragging: false
};
},
created() {
if (!this.isIOS) {
this.objectFit = 'cover';
}
let myUserInfo = storage.getVlogUserInfo();
if (myUserInfo != null) {
this.userId = myUserInfo.id;
}
// 查询首页短视频列表
this.displayVideoPaging(this.page + 1, true);
this.videoContext = uni.createVideoContext('localVideo', this.$refs.localVideo);
console.log(this.videoContext);
},
onReady() {},
watch: {
refreshList(value) {
var me = this;
var newList = value;
if (newList != null && newList != undefined && newList.length > 0) {
me.playerList = newList;
}
// 重置
this.playerCur = 0;
this.currentIndex = 0;
this.contentOffsetY = 0;
},
playStatus(val) {
var me = this;
if (!val) {
me.videoContext.pause();
} else {
me.videoContext.play();
}
}
},
methods: {
//进度start
formatTime(sec) {
const m = Math.floor(sec / 60);
const s = Math.floor(sec % 60);
return `${m}:${s < 10 ? '0' : ''}${s}`;
},
onTimeUpdate(e) {
// console.log(e);
if (e.detail.currentTime > 0.2) {
this.doplay(e.detail.currentTime);
}
if (this.isDragging) return;
this.currentTime = e.detail.currentTime;
this.duration = e.detail.duration;
this.updateProgress();
},
updateProgress() {
const percent = this.currentTime / this.duration;
this.progressWidth = Math.floor(system.screenWidth * percent);
// console.log(this.progressWidth);
},
startDrag(e) {
console.log('触发开始拖动');
this.isDragging = true;
},
onDragging(e) {
console.log('拖动中');
const touchX = e.touches[0].pageX;
const max = system.screenWidth;
const clampedX = Math.max(0, Math.min(touchX, max));
this.progressWidth = clampedX;
const percent = clampedX / max;
this.floatTime = percent * this.duration;
// 浮动提示偏移,避免越界
const floatBoxWidth = 60;
this.floatLeft = Math.max(0, Math.min(clampedX - floatBoxWidth / 2, max - floatBoxWidth));
},
endDrag() {
console.log('拖动结束');
const percent = this.progressWidth / system.screenWidth;
const seekTime = percent * this.duration;
if (!this.duration || isNaN(seekTime)) {
console.warn('duration 未就绪或 seekTime 非法');
this.isDragging = false;
return;
}
// 避免重复 seek
if (Math.abs(seekTime - this.currentTime) > 0.2) {
this.videoContext.seek(seekTime);
}
this.isDragging = false;
},
//进度end
//------------addd
setScrollRef(height) {
if (this.$refs['list'].setSpecialEffects) {
this.$refs['list'].setSpecialEffects({
id: this.parentId,
headerHeight: height
});
}
},
loadData() {
// 首次激活时被调用
this.displayVideoPaging(this.page + 1, true);
},
clear() {
// 释放数据时被调用,参考 swiper-list 缓存配置
this.playerList.length = 0;
},
//---------------------------------
// 把超过1000或10000的数字调整比如1.3k/6.8w
getGraceNumber(num) {
return graceNumber(num);
},
async freshCommentCounts() {
var me = this;
var currentIndex = me.playerCur;
console.log('currentIndex', currentIndex);
var vlog = me.playerList[currentIndex];
var vlogId = vlog.vlogId;
var result = await vlogComment(vlogId);
if (result.data.status == 200) {
me.thisVlogTotalComentCounts = result.data.data;
} else {
me.thisVlogTotalComentCounts = 0;
}
},
async likeOrDislikeVlog(isLike) {
var me = this;
var myUserInfo = storage.getVlogUserInfo() || null;
var currentIndex = me.playerCur;
var vlog = me.playerList[currentIndex];
if (isLike == 1) {
// 喜欢/点赞视频
if (myUserInfo == null) {
uni.navigateTo({
url: '/pages/passport/login',
animationType: 'slide-in-bottom'
});
return;
}
var userId = myUserInfo.id;
var result = await vlogLike({
userId,
vlogerId: vlog.vlogerId,
vlogId: vlog.vlogId
});
console.log(result);
if (result.data.status == 200) {
me.reLikePlayList(vlog.vlogId);
me.refreshVlogCounts();
} else {
uni.showToast({
title: result.data.msg,
icon: 'none',
duration: 3000
});
}
} else if (isLike == 0) {
// 取消喜欢/点赞视频
var myUserInfo = storage.getVlogUserInfo() || null;
if (myUserInfo == null) {
uni.navigateTo({
url: '/pages/passport/login',
animationType: 'slide-in-bottom'
});
return;
}
var userId = myUserInfo.id;
var result = await vlogUnLike({
userId,
vlogerId: vlog.vlogerId,
vlogId: vlog.vlogId
});
if (result.data.status == 200) {
me.reDislikePlayList(vlog.vlogId);
me.refreshVlogCounts();
} else {
uni.showToast({
title: result.data.msg,
icon: 'none',
duration: 3000
});
}
}
},
// 喜欢/点赞的list重新设置
reLikePlayList(vlogId) {
var me = this;
var playerList = me.playerList;
// 关注以后循环当前playerList修改对应粉丝关系的doIFollowVloger改为true
for (var i = 0; i < playerList.length; i++) {
var vlog = playerList[i];
if (vlog.vlogId == vlogId) {
vlog.doILikeThisVlog = true;
playerList.splice(i, 1, vlog);
}
}
me.playerList = playerList;
},
reDislikePlayList(vlogId) {
var me = this;
var playerList = me.playerList;
// 关注以后循环当前playerList修改对应粉丝关系的doIFollowVloger改为true
for (var i = 0; i < playerList.length; i++) {
var vlog = playerList[i];
if (vlog.vlogId == vlogId) {
vlog.doILikeThisVlog = false;
playerList.splice(i, 1, vlog);
}
}
me.playerList = playerList;
},
// 关注后的list重新设置
reFollowPlayList(vlogerId) {
var me = this;
var playerList = me.playerList;
// 关注以后循环当前playerList修改对应粉丝关系的doIFollowVloger改为true
for (var i = 0; i < playerList.length; i++) {
var vlog = playerList[i];
if (vlog.vlogerId == vlogerId) {
vlog.doIFollowVloger = true;
playerList.splice(i, 1, vlog);
}
}
me.playerList = playerList;
},
// 取关后的list重新设置
reCancelPlayList(vlogerId) {
var me = this;
var playerList = me.playerList;
// 关注以后循环当前playerList修改对应粉丝关系的doIFollowVloger改为true
for (var i = 0; i < playerList.length; i++) {
var vlog = playerList[i];
if (vlog.vlogerId == vlogerId) {
vlog.doIFollowVloger = false;
playerList.splice(i, 1, vlog);
}
}
me.playerList = playerList;
},
// 关注博主
async followMe(vlogerId) {
var me = this;
var myUserInfo = storage.getVlogUserInfo() || null;
if (myUserInfo == null) {
uni.navigateTo({
url: '/pages/passport/login',
animationType: 'slide-in-bottom'
});
return;
}
var userId = myUserInfo.id;
var result = await vlogFollow(userId, vlogerId);
if (result.data.status == 200) {
me.reFollowPlayList(vlogerId);
} else {
uni.showToast({
title: result.data.msg,
icon: 'none',
duration: 3000
});
}
},
// 查看用户详情
goUserInfoSeeSee(userId) {
uni.setStorageSync('userPageId', userId);
const info = storage.getVlogUserInfo() || null;
if (info == null) {
uni.navigateTo({
url: '/pages/passport/login',
animationType: 'slide-in-bottom'
});
return;
}
let myUserId = info.id;
if (myUserId == userId) {
uni.switchTab({
url: '/pages/me/me'
});
} else {
uni.navigateTo({
url: '/pages/me/vlogerInfo?userPageId=' + userId
});
}
},
listScroll(e) {
// console.log(e.contentOffset.y)
this.progressFlag = false;
if (e.contentOffset.y > 0) {
this.$emit('showLoading');
}
},
downloadVlog() {
var me = this;
var serverUrl = app.globalData.serverUrl;
var currentIndex = me.playerCur;
var vlog = me.playerList[currentIndex];
var pendingLength = vlog.url;
},
copyLink() {
var me = me;
},
showQRCode() {
var me = me;
},
changeVlogToPrivate() {
var me = me;
},
// 下拉刷新的过程中改变头部tab显示的字样
onpullingdown(e) {
console.log('下拉中');
},
async onrefresh(e) {
// console.log('开始')
// this.refreshing = true;
// // 通过list组件的下拉刷新触发当前所在页面的下拉刷新
// uni.startPullDownRefresh();
// await this.displayVideoPaging(1,true)
// uni.stopPullDownRefresh()
// this.refreshing = false;
// this.$emit("hideLoading");
console.log('开始');
this.refreshing = true;
// 通过list组件的下拉刷新触发当前所在页面的下拉刷新
uni.startPullDownRefresh();
await this.displayVideoPaging(1, true);
uni.stopPullDownRefresh();
this.refreshing = false;
this.$emit('hideLoading');
},
onplay: function (e) {
// let timer = setTimeout(() => {
// this.progressFlag = true;
// clearTimeout(timer);
// }, 500);
this.progressFlag = true;
if (uni.getSystemInfoSync().platform == 'ios') {
this.doplay(0.1);
}
},
onerror: function (err) {},
// timeupdate: function (e) {
// if (e.detail.currentTime > 0.2) {
// this.doplay(e.detail.currentTime);
// }
// },
playOrPause() {
var me = this;
var playStatus = this.playStatus;
if (!playStatus) {
me.videoContext.pause();
} else {
me.videoContext.play();
}
this.playStatus = !playStatus;
},
scroll: function (e) {
let originalIndex = this.currentIndex;
let isNext = false;
if (e.contentOffset.y < this.contentOffsetY) {
isNext = true;
}
this.contentOffsetY = Number(e.contentOffset.y);
var num = this.playerList.length;
if (num > 0) {
var ht = Number(e.contentSize.height);
console.log(ht);
this.currentIndex = Math.round(Math.abs(this.contentOffsetY) / (ht / num));
} else {
this.currentIndex = 0;
}
this.onchange(this.currentIndex);
this.times = new Date().getTime();
// 判断如果视频列表总长度-当前下标少于3个则开始分页查询后续的视频并且追加到现有list中
if (this.playerList.length - this.currentIndex < 3) {
// 如果要分页的page和总数totalPage相等则没有更多
if (this.page == this.totalPage || this.totalPage == 0) {
this.$emit('hideLoading');
return;
}
this.displayVideoPaging(this.page + 1, false);
}
},
// 分页查询新的list, 并且追加到现有list中
async displayVideoPaging(page, needClearList) {
// 查询首页短视频列表
let me = this;
let myUserInfo = storage.getVlogUserInfo();
let cityCode = storage.getCityCode();
let userId = '';
if (myUserInfo != null) {
userId = myUserInfo.id;
this.userId = userId;
}
const result = await vlogList(page, 10, userId, cityCode).catch((err) => {
uni.stopPullDownRefresh();
});
uni.stopPullDownRefresh();
console.log(result);
if (result.data.status == 200) {
let vlogList = result.data.data.rows;
// 缺:该条视频是否被喜欢过
let totalPage = result.data.data.total;
// me.playerList = vlogList;
if (needClearList) {
me.playerList = [];
}
me.playerList = me.playerList.concat(vlogList);
me.page = page;
me.totalPage = totalPage;
if (me.page == 1 && me.playerList.length == 0) {
me.showMsg = true;
me.$emit('hanveData', true);
} else {
me.showMsg = false;
me.$emit('hanveData', false);
}
if (needClearList) {
me.setThisVlogInfo();
me.freshCommentCounts();
}
} else {
uni.showToast({
title: result.data.msg,
icon: 'none',
duration: 3000
});
}
},
doplay: function (time) {
if (time > 0) {
this.playerList[this.playerCur].play = true;
}
},
onchange: function (index) {
console.log(index);
console.log(this.playerCur);
if (index != this.playerCur) {
this.playerList[this.playerCur].play = false;
this.playerCur = index;
this.playStatus = true;
this.progressWidth = 0;
}
this.progressFlag = true;
// let timer = setTimeout(() => {
// this.progressFlag = true;
// clearTimeout(timer);
// }, 300);
this.refreshVlogCounts();
this.setThisVlogInfo();
this.freshCommentCounts();
},
// 设置当前vlog的信息
setThisVlogInfo() {
var me = this;
var currentIndex = me.playerCur;
var vlog = me.playerList[currentIndex];
this.thisVlog = vlog;
this.thisVlogId = vlog.vlogId;
this.thisVlogerId = vlog.vlogerId;
},
async refreshVlogCounts() {
// 查询当前点赞数,重新赋值给当前视频
var me = this;
var currentIndex = me.playerCur;
var vlog = me.playerList[currentIndex];
var result = await vlogTotalLikedCounts(vlog.vlogId);
if (result.data.status == 200) {
var counts = result.data.data;
me.reChangeVlogLikedCountsInPlayList(vlog.vlogId, counts);
}
},
reChangeVlogLikedCountsInPlayList(vlogId, counts) {
var me = this;
var playerList = me.playerList;
// 关注以后循环当前playerList修改对应粉丝关系的doIFollowVloger改为true
for (var i = 0; i < playerList.length; i++) {
var vlog = playerList[i];
if (vlog.vlogId == vlogId) {
vlog.likeCounts = counts;
playerList.splice(i, 1, vlog);
}
}
me.playerList = playerList;
},
commentToggle() {
this.$refs.comment.open();
uni.hideTabBar({
animation: true
});
},
shareToggle() {
this.$refs.share.open();
uni.hideTabBar({
animation: true
});
}
}
};
</script>
<style scoped>
.progress-bar {
height: 60rpx;
width: 750rpx;
display: flex;
align-items: center;
position: fixed;
bottom: 0;
z-index: 99999;
}
.progress-background {
position: absolute;
bottom: 0;
width: 750rpx;
height: 2px;
background-color: #ccc;
z-index: 3;
}
.progress-foreground {
z-index: 2;
height: 2px;
background-color: #fff;
position: absolute;
bottom: 0;
left: 0;
}
.anm {
transition: width 0.25s linear;
}
.float-time {
position: fixed;
bottom: 2px;
width: 60px;
height: 30px;
background-color: rgba(0, 0, 0, 0.3);
color: white;
text-align: center;
align-items: center;
justify-content: center;
line-height: 30px;
font-size: 12px;
border-radius: 4px;
z-index: 1;
}
.abs-box-ad {
width: 750rpx;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
}
.abs-box-ios {
position: relative;
width: 750rpx;
display: flex;
justify-content: center;
align-items: center;
}
.play-btn {
position: absolute;
width: 120rpx;
height: 120rpx;
}
.icon {
width: 80rpx;
height: 80rpx;
opacity: 0.9;
}
.user-face {
width: 100rpx;
height: 100rpx;
border-radius: 100rpx;
}
.play-cd {
width: 150rpx;
height: 150rpx;
opacity: 0.8;
}
.refresh-info-txt {
color: #f1f1f1;
text-align: center;
font-size: 12px;
}
.publish-info-box {
position: absolute;
bottom: 200rpx;
left: 0;
right: 0;
padding-left: 20rpx;
padding-right: 20rpx;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.publish-info-vloger-name {
color: #ffffff;
font-size: 40rpx;
font-weight: 600;
padding: 10rpx;
}
.publish-info-music-box {
flex-direction: row;
align-items: center;
}
.publish-info-content {
color: #ffffff;
font-size: 28rpx;
font-weight: 400;
padding: 10rpx;
lines: 5;
width: 520rpx;
text-overflow: ellipsis;
}
.icon-fire {
width: 36rpx;
height: 36rpx;
}
.muisc-words {
color: #ffffff;
font-size: 28rpx;
padding: 10rpx;
width: 400rpx;
}
.some-counts {
font-size: 24rpx;
font-weight: 500;
text-align: center;
color: #ffffff;
margin-top: 2rpx;
}
.operation-box {
position: absolute;
top: 0;
bottom: 0;
right: 0;
align-items: center;
justify-content: center;
padding-right: 20rpx;
}
.operation-face-box {
border-radius: 100rpx;
border-color: #ffffff;
border-width: 3rpx;
}
.flbx {
align-items: center;
justify-content: center;
display: flex;
}
.warn-info {
color: #ffffff;
font-size: 36rpx;
font-weight: 600;
}
.follow-me {
width: 40rpx;
height: 40rpx;
border-radius: 10px;
position: relative;
top: -20rpx;
}
.like-box {
flex-direction: column;
align-items: center;
margin-top: 30rpx;
}
.comment-and-share-box {
flex-direction: column;
align-items: center;
margin-top: 45rpx;
}
</style>