效果展示
实现分析
总体思路就是通过添加点击事件,给流播放节点动态添加样式,来实现视角切换。拿web 端 Demo来举例就是,通过点击,修改flex来实现两个dom的切换。
Demo代码位置修改
1. 添加变量
代码语言:javascript复制 data() {
return {
isRowReverse: false
};
},
2. 给播放div添加点击事件
代码语言:javascript复制 <div
v-for="userId in meetingUserIdList"
:key="`video-${userId}`"
:id="`video-${userId}`"
:class="{
'user-video-container': true,
'is-me': userId === loginUserInfo.userId,
}"
@click="isRowReverse = !isRowReverse"
>
3. 给 video-conference-list 添加动态样式
代码语言:javascript复制 <div
:class="{
'video-conference-list': true,
'row-rev': isRowReverse,
}"
>
4. 添加row-rev样式并给 video-conference-list 样式添加 justify-content 属性
代码语言:javascript复制.video-conference-list {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 10px;
}
.row-rev {
flex-direction: row-reverse;
}
通过上述步骤就可以切换视角了!
总结
实现视图切换最好是通过动态修改样式来实现,不需要调佣TRTCCalling的相关api,这样可以比较快速便捷无感切换。
全文件代码
代码语言:javascript复制<template>
<div class="video-call-section">
<div class="video-call-section-header">
Welcome
{{ loginUserInfo && (loginUserInfo.name || loginUserInfo.userId) }}
</div>
<div class="video-call-section-title">视频通话</div>
<search-user
:callFlag="callFlag"
:cancelFlag="cancelFlag"
@callUser="handleCallUser"
@cancelCallUser="handleCancelCallUser"
></search-user>
<div :class="{ 'video-conference': true, 'is-show': isShowVideoCall }">
<div class="video-conference-header">视频通话区域</div>
<div
:class="{
'video-conference-list': true,
'row-rev': isRowReverse,
}"
>
<div
v-for="userId in meetingUserIdList"
:key="`video-${userId}`"
:id="`video-${userId}`"
:class="{
'user-video-container': true,
'is-me': userId === loginUserInfo.userId,
}"
@click="isRowReverse = !isRowReverse"
>
<div class="user-status">
<div
:class="{
'user-video-status': true,
'is-mute': isUserMute(muteVideoUserIdList, userId),
}"
></div>
<div
:class="{
'user-audio-status': true,
'is-mute': isUserMute(muteAudioUserIdList, userId),
}"
></div>
</div>
<div class="video-item-username">
{{ userId2Name[userId] || userId }}
</div>
</div>
</div>
<div class="video-conference-action">
<el-button class="action-btn" type="success" @click="toggleVideo">{{
isVideoOn ? "关闭摄像头" : "打开摄像头"
}}</el-button>
<el-button class="action-btn" type="success" @click="toggleAudio">{{
isAudioOn ? "关闭麦克风" : "打开麦克风"
}}</el-button>
<el-button class="action-btn" type="danger" @click="handleHangup"
>挂断</el-button
>
</div>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
import SearchUser from "../search-user";
import { getUsernameByUserid } from "../../service";
export default {
name: "VideoCall",
components: {
SearchUser,
},
computed: {
...mapState({
loginUserInfo: (state) => state.loginUserInfo,
callStatus: (state) => state.callStatus,
isInviter: (state) => state.isInviter,
meetingUserIdList: (state) => state.meetingUserIdList,
muteVideoUserIdList: (state) => state.muteVideoUserIdList,
muteAudioUserIdList: (state) => state.muteAudioUserIdList,
}),
},
data() {
return {
isShowVideoCall: false,
isVideoOn: true,
isAudioOn: true,
userId2Name: {},
callFlag: false,
cancelFlag: false,
isRowReverse: false,
};
},
mounted() {
if (this.callStatus === "connected" && !this.isInviter) {
this.startMeeting();
this.updateUserId2Name(this.meetingUserIdList);
}
},
destroyed() {
this.$store.commit("updateMuteVideoUserIdList", []);
this.$store.commit("updateMuteAudioUserIdList", []);
if (this.callStatus === "connected") {
this.$trtcCalling.hangup();
this.$store.commit("updateCallStatus", "idle");
}
},
watch: {
callStatus: function (newStatus, oldStatus) {
// 作为被邀请者, 建立通话连接
if (newStatus !== oldStatus && newStatus === "connected") {
this.startMeeting();
this.updateUserId2Name(this.meetingUserIdList);
}
},
meetingUserIdList: function (newList, oldList) {
if (newList !== oldList || newList.length !== oldList) {
this.updateUserId2Name(newList);
}
},
},
methods: {
handleCallUser: function ({ param }) {
this.callFlag = true;
this.$trtcCalling
.call({
userID: param,
type: this.TrtcCalling.CALL_TYPE.VIDEO_CALL,
})
.then(() => {
this.callFlag = false;
this.$store.commit("userJoinMeeting", this.loginUserInfo.userId);
this.$store.commit("updateCallStatus", "calling");
this.$store.commit("updateIsInviter", true);
});
},
handleCancelCallUser: function () {
this.cancelFlag = true;
this.$trtcCalling.hangup().then(() => {
this.cancelFlag = false;
this.$store.commit("dissolveMeeting");
this.$store.commit("updateCallStatus", "idle");
});
},
startMeeting: function () {
if (this.meetingUserIdList.length >= 3) {
// 多人通话
const lastJoinUser =
this.meetingUserIdList[this.meetingUserIdList.length - 1];
this.$trtcCalling.startRemoteView({
userID: lastJoinUser,
videoViewDomID: `video-${lastJoinUser}`,
});
return;
}
this.isShowVideoCall = true;
this.$trtcCalling.startLocalView({
userID: this.loginUserInfo.userId,
videoViewDomID: `video-${this.loginUserInfo.userId}`,
});
const otherParticipants = this.meetingUserIdList.filter(
(userId) => userId !== this.loginUserInfo.userId
);
otherParticipants.forEach((userId) => {
this.$trtcCalling.startRemoteView({
userID: userId,
videoViewDomID: `video-${userId}`,
});
});
},
handleHangup: function () {
this.$trtcCalling.hangup();
this.isShowVideoCall = false;
this.$store.commit("updateCallStatus", "idle");
this.$router.push("/");
},
toggleVideo: function () {
this.isVideoOn = !this.isVideoOn;
if (this.isVideoOn) {
this.$trtcCalling.openCamera();
const muteUserList = this.muteVideoUserIdList.filter(
(userId) => userId !== this.loginUserInfo.userId
);
this.$store.commit("updateMuteVideoUserIdList", muteUserList);
} else {
this.$trtcCalling.closeCamera();
const muteUserList = this.muteVideoUserIdList.concat(
this.loginUserInfo.userId
);
this.$store.commit("updateMuteVideoUserIdList", muteUserList);
}
},
toggleAudio: function () {
this.isAudioOn = !this.isAudioOn;
this.$trtcCalling.setMicMute(!this.isAudioOn);
if (this.isAudioOn) {
const muteUserList = this.muteAudioUserIdList.filter(
(userId) => userId !== this.loginUserInfo.userId
);
this.$store.commit("updateMuteAudioUserIdList", muteUserList);
} else {
const muteUserList = this.muteAudioUserIdList.concat(
this.loginUserInfo.userId
);
this.$store.commit("updateMuteAudioUserIdList", muteUserList);
}
},
isUserMute: function (muteUserList, userId) {
return muteUserList.indexOf(userId) !== -1;
},
updateUserId2Name: async function (userIdList) {
let userId2Name = {};
let loginUserId = this.loginUserInfo.userId;
for (let i = 0; i < userIdList.length; i ) {
const userId = userIdList[i];
if (!this.userId2Name[userId]) {
const userName = await getUsernameByUserid(userId);
userId2Name[userId] = userName;
if (loginUserId === userId) {
userId2Name[userId] = "(me)";
}
}
}
this.userId2Name = {
...this.userId2Name,
...userId2Name,
};
},
goto: function (path) {
this.$router.push(path);
},
},
};
</script>
<style scoped>
.video-call-section {
padding-top: 50px;
width: 800px;
margin: 0 auto;
}
.video-call-section-header {
font-size: 24px;
}
.video-call-section-title {
margin-top: 30px;
font-size: 20px;
}
.video-conference {
display: none;
margin-top: 20px;
}
.video-conference.is-show {
display: block;
}
.video-conference-list {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 10px;
}
.row-rev {
flex-direction: row-reverse;
}
.user-video-container {
position: relative;
text-align: left;
width: 360px;
height: 240px;
margin: 10px;
}
.user-video-status {
position: absolute;
right: 50px;
bottom: 20px;
width: 24px;
height: 27px;
z-index: 10;
background-image: url("../../assets/camera-on.png");
background-size: cover;
}
.user-video-status.is-mute {
background-image: url("../../assets/camera-off.png");
}
.user-audio-status {
position: absolute;
right: 20px;
bottom: 20px;
width: 22px;
height: 27px;
z-index: 10;
background-image: url("../../assets/mic-on.png");
background-size: cover;
}
.user-audio-status.is-mute {
background-image: url("../../assets/mic-off.png");
}
.video-conference-action {
margin-top: 10px;
}
.video-item-username {
position: absolute;
top: 20px;
left: 20px;
z-index: 10;
color: #ffffff;
}
@media screen and (max-width: 767px) {
.video-call-section {
width: 100%;
}
.video-conference-list {
margin: 0;
padding: 10px;
}
.user-video-container {
margin: 5px;
}
}
</style>