app/components/vlog/videoComp.vue

995 lines
22 KiB
Vue
Raw Normal View History

2025-04-12 17:11:06 +08:00
<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="!isDragging"
>
<refresh
@pullingdown="onpullingdown"
@refresh="onrefresh"
:display="refreshing ? 'show' : 'hide'"
>
<text class="refresh-info-txt"></text>
<loading-indicator></loading-indicator>
</refresh>
<cell
:recycle="false"
:class="isIOS ? 'abs-box-ios' : ''"
v-for="(item, index) in playerList"
:key="index"
:data-index="index"
:style="{ height: screenHeight + 'px' }"
>
<video
ref="myVideo"
id="myVideo"
:object-fit="item.width >= item.height ? 'contain' : 'fill'"
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
: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
class="progress-bar"
@touchstart="startDrag"
@touchmove="onDragging"
@panend="endDrag"
v-if="progressFlag"
>
<view
v-if="progressFlag"
class="progress-background"
></view>
<view
v-if="progressFlag"
class="progress-foreground"
: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>
2025-04-21 17:35:54 +08:00
<!-- 右下角 -->
<!-- <view
2025-04-12 17:11:06 +08:00
class=""
style="flex-direction: row"
>
<image
src="/static/images/cd-play-4.gif"
style="width: 150rpx; height: 150rpx; opacity: 0.8"
></image>
2025-04-21 17:35:54 +08:00
</view> -->
2025-04-12 17:11:06 +08:00
</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>
2025-03-14 16:27:50 +08:00
<script>
2025-04-12 17:11:06 +08:00
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 {
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
// progressFlag: false
};
},
created() {
console.log(this.isIOS);
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('myVideo', this.$refs.myVideo);
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(this.isDragging);
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;
2025-03-14 16:27:50 +08:00
}
2025-04-12 17:11:06 +08:00
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) {
// console.log('开始播放');
// let timer = setTimeout(() => {
// this.progressFlag = true;
// clearTimeout(timer);
// }, 10);
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;
console.log(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 userId = '';
if (myUserInfo != null) {
userId = myUserInfo.id;
this.userId = userId;
}
const result = await vlogList(page, 10, userId).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 (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);
// }, 20);
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.5);
color: #fff;
text-align: center;
align-items: center;
justify-content: center;
line-height: 30px;
font-size: 12px;
border-radius: 4px;
z-index: 5;
}
.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;
}
.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>