Vue 3.0 项目中集成腾讯云TRTCCalling
2021年1月1日,祝大家新年快乐!大吉大利!2020年疫情原因,在线直播、实时音视频技术也更加火热起来。公司项目业务中也有需要音视频的地方。技术选型使用了腾讯云TRTCCalling,以下展示在Vue 3.0 项目中如何配置使用。在线demo地址:https://wkl007.github.io/trtc-calling-webGitHub地址:https://github.com/wkl007
·
2021年1月1日,祝大家新年快乐!大吉大利!2020年疫情原因,在线直播、实时音视频技术也更加火热起来。公司项目业务中也有需要音视频的地方。技术选型使用了腾讯云TRTCCalling
,以下展示在Vue 3.0
项目中如何配置使用。
在线demo
地址:https://wkl007.github.io/trtc-calling-web
GitHub
地址:https://github.com/wkl007/trtc-calling-web
1. 安装依赖
yarn add tim-js-sdk trtc-calling-js trtc-js-sdk tsignaling
2. Typescript项目中配置
trtc-calling-js
目前暂无TypeScript类型声明文件,在shimes-vue.d.ts
中添加:
2021年01月15日,本人已PR@types/trtc-calling-js
类型声明文件。
yarn add --dev @types/trtc-calling-js
3. Vuex封装
有关TRTC相关的实例,通话状态等信息保存在Vuex中。
state.ts
中
const state: State = {
...
trtcCalling: undefined, // trtc实例
trtcInfo: {
callStatus: 'idle', // 通话状态, idle, calling, connected
isInviter: false, // 邀请者
meetingUserIdList: [], // 会话用户ID列表
muteVideoUserIdList: [], // 关闭摄像头用户ID列表
muteAudioUserIdList: [] // 关闭麦克风用户ID列表
},
...
}
4. useTRTC
hooks
封装
新建hooks/index.ts
文件
import TRTCCalling, { InvitedInfo, UserAudioInfo, UserInfo, UserVideoInfo } from 'trtc-calling-js'
// @ts-ignore
import * as LibGenerateTestUserSig from '../../public/js/lib-generate-test-usersig.min'
import { message, Modal } from 'ant-design-vue'
import store from '@/store'
import router from '@/router'
interface UseTRTC {
getUserSig: () => { userSig: string },
initTRTC: () => TRTCCalling,
initListener: (trtcCalling: TRTCCalling) => void,
removeListener: (trtcCalling: TRTCCalling) => void,
handleLogin: (trtcCalling: TRTCCalling) => void
}
export function useTRTC (): UseTRTC {
// 前端生成签名
function getUserSig () {
const EXPIRE_TIME = 604800 // 签名过期时间
const { sdkInfo: { sdkAppId, secretKey }, userInfo: { username } } = store.getters
// eslint-disable-next-line new-cap
const generator = new LibGenerateTestUserSig.default(Number(sdkAppId), secretKey, EXPIRE_TIME)
return generator.genTestUserSig(username)
}
// 初始化TRTC实例
function initTRTC () {
const { sdkInfo: { sdkAppId } } = store.getters
const options = {
SDKAppID: sdkAppId
}
return new TRTCCalling(options)
}
// 初始化监听
function initListener (trtcCalling: TRTCCalling) {
if (!trtcCalling) return
trtcCalling.on(TRTCCalling.EVENT.ERROR, handleError)
trtcCalling.on(TRTCCalling.EVENT.INVITED, (e: InvitedInfo) => handleInvited(trtcCalling, e))
trtcCalling.on(TRTCCalling.EVENT.USER_ENTER, (e: UserInfo) => handleUserEnter(trtcCalling, e))
trtcCalling.on(TRTCCalling.EVENT.USER_LEAVE, handleUserLeave)
trtcCalling.on(TRTCCalling.EVENT.REJECT, handleEject)
trtcCalling.on(TRTCCalling.EVENT.LINE_BUSY, handleLineBusy)
trtcCalling.on(TRTCCalling.EVENT.CALLING_CANCEL, handleCallingCancel)
trtcCalling.on(TRTCCalling.EVENT.KICKED_OUT, () => handleKickedOut(trtcCalling))
trtcCalling.on(TRTCCalling.EVENT.CALLING_TIMEOUT, handleCallingTimeout)
trtcCalling.on(TRTCCalling.EVENT.NO_RESP, handleNoResp)
trtcCalling.on(TRTCCalling.EVENT.CALL_END, () => handleCallEnd(trtcCalling))
trtcCalling.on(TRTCCalling.EVENT.USER_VIDEO_AVAILABLE, handleUserVideoChange)
trtcCalling.on(TRTCCalling.EVENT.USER_AUDIO_AVAILABLE, handleUserAudioChange)
}
// 移除监听
function removeListener (trtcCalling: TRTCCalling) {
if (!trtcCalling) return
trtcCalling.off(TRTCCalling.EVENT.ERROR, handleError)
trtcCalling.off(TRTCCalling.EVENT.INVITED, (e: InvitedInfo) => handleInvited(trtcCalling, e))
trtcCalling.off(TRTCCalling.EVENT.USER_ENTER, (e: UserInfo) => handleUserEnter(trtcCalling, e))
trtcCalling.off(TRTCCalling.EVENT.USER_LEAVE, handleUserLeave)
trtcCalling.off(TRTCCalling.EVENT.REJECT, handleEject)
trtcCalling.off(TRTCCalling.EVENT.LINE_BUSY, handleLineBusy)
trtcCalling.off(TRTCCalling.EVENT.CALLING_CANCEL, handleCallingCancel)
trtcCalling.off(TRTCCalling.EVENT.KICKED_OUT, () => handleKickedOut(trtcCalling))
trtcCalling.off(TRTCCalling.EVENT.CALLING_TIMEOUT, handleCallingTimeout)
trtcCalling.off(TRTCCalling.EVENT.NO_RESP, handleNoResp)
trtcCalling.off(TRTCCalling.EVENT.CALL_END, () => handleCallEnd(trtcCalling))
trtcCalling.off(TRTCCalling.EVENT.USER_VIDEO_AVAILABLE, handleUserVideoChange)
trtcCalling.off(TRTCCalling.EVENT.USER_AUDIO_AVAILABLE, handleUserAudioChange)
}
// 登录
async function handleLogin (trtcCalling: TRTCCalling) {
try {
const { userInfo: { username } } = store.getters
await trtcCalling.login({
userID: username,
userSig: getUserSig()
})
} catch (e) {}
}
// 退出登录
async function handleLogout (trtcCalling: TRTCCalling) {
try {
await trtcCalling.logout()
await store.dispatch('setLoginStatus', 0)
await store.dispatch('setUserInfo', { username: '' })
await router.push({ path: '/login' })
} catch (e) {}
}
function handleError (err: any) {
console.log(err)
}
// 被邀用户收到了邀请通知
async function handleInvited (trtcCalling: TRTCCalling, {
sponsor,
userIDList,
isFromGroup,
inviteData,
inviteID
}: InvitedInfo) {
try {
console.log('被邀用户收到了邀请通知')
const { trtcInfo, userInfo: { username } } = store.getters
// 最后一个人发送 invite 进行挂断
if (inviteData.callEnd) {
trtcInfo.callStatus = 'idle'
await store.dispatch('setTrtcInfo', trtcInfo)
return
}
// 邀请人是自己, 同一个账号有可能在多端登录
if (sponsor === username) return
// 考虑忙线的情况
if (trtcInfo.callStatus === 'calling' || trtcInfo.callStatus === 'connected') {
await trtcCalling.reject({ inviteID, isBusy: true, callType: inviteData.callType })
return
}
// 接通会话
const { callType, roomID } = inviteData // 1:语音通话,2:视频通话
trtcInfo.callStatus = 'calling'
trtcInfo.isInviter = false
const callTypeDisplayName = callType === TRTCCalling.CALL_TYPE.AUDIO_CALL ? '语音通话' : '视频通话'
await store.dispatch('setTrtcInfo', trtcInfo)
Modal.confirm({
content: `来自${sponsor}的${callTypeDisplayName}`,
okText: '接听',
cancelText: '拒绝',
onOk: async () => {
if (trtcInfo.meetingUserIdList.indexOf(username) < 0) trtcInfo.meetingUserIdList.push(username)
await store.dispatch('setTrtcInfo', trtcInfo)
if (roomID) {
await trtcCalling.accept({
inviteID,
roomID,
callType
})
}
if (callType === TRTCCalling.CALL_TYPE.AUDIO_CALL) {
await router.push({ path: '/audioCall' })
}
if (callType === TRTCCalling.CALL_TYPE.VIDEO_CALL) {
await router.push({ path: '/videoCall' })
}
},
onCancel: async () => {
trtcCalling.reject({
inviteID,
isBusy: false,
callType
})
await dissolveMeeting()
}
})
} catch (e) {}
}
// 用户进入通话
async function handleUserEnter (trtcCalling: TRTCCalling, { userID }: UserInfo) {
console.log('用户进入通话')
const { trtcInfo } = store.getters
if (trtcInfo.meetingUserIdList.indexOf(userID) < 0) trtcInfo.meetingUserIdList.push(userID)
if (trtcInfo.callStatus === 'calling') {
// 如果是邀请者, 则建立连接
// TODO
trtcInfo.callStatus = 'connected'
} else {
// 第n (n >= 3)个人被邀请入会, 并且他不是第 n 个人的邀请人
// 需要先等远程用户 id 的节点渲染到 dom 上
// TODO
await trtcCalling.startRemoteView({
userID,
videoViewDomID: `video-${userID}`
})
}
await store.dispatch('setTrtcInfo', trtcInfo)
}
// 用户离开会话
async function handleUserLeave ({ userID }: UserInfo) {
console.log('用户离开会话')
const { trtcInfo } = store.getters
if (trtcInfo.meetingUserIdList.length === 2) trtcInfo.callStatus = 'idle'
const index = trtcInfo.meetingUserIdList.findIndex((item: string) => item === userID)
if (index >= 0) trtcInfo.meetingUserIdList.splice(index, 1)
await store.dispatch('setTrtcInfo', trtcInfo)
}
// 被邀用户拒绝通话
async function handleEject ({ userID }: UserInfo) {
console.log('被邀用户拒绝通话')
message.info(`${userID}拒绝通话`)
await dissolveMeeting()
}
// 被邀用户正在通话中,忙线
async function handleLineBusy ({ userID }: UserInfo) {
console.log('被邀用户正在通话中,忙线')
message.info(`${userID}忙线`)
await dissolveMeeting()
}
// 本次通话被取消了
async function handleCallingCancel () {
console.log('本次通话被取消了')
message.info('通话已取消')
await dissolveMeeting()
}
// 重复登录,被踢出
async function handleKickedOut (trtcCalling: TRTCCalling) {
console.log('重复登录,被踢出')
message.info('重复登录,被踢出')
// await handleLogout(trtcCalling)
}
// 本次通话超时未应答
async function handleCallingTimeout () {
console.log('本次通话超时未应答')
message.info('通话超时未应答')
await dissolveMeeting()
}
// 被邀用户超时无应答
async function handleNoResp ({ userID }: UserInfo) {
console.log('被邀用户超时无应答')
message.info(`${userID || '被邀用户'}无应答`)
await dissolveMeeting()
}
// 本次通话结束
async function handleCallEnd (trtcCalling: TRTCCalling) {
console.log('通话已结束')
message.info('通话已结束')
trtcCalling.hangup()
await dissolveMeeting()
}
// 远端用户开启/关闭了摄像头
async function handleUserVideoChange ({ userID, isVideoAvailable }: UserVideoInfo) {
console.log('远端用户开启/关闭了摄像头')
const { trtcInfo } = store.getters
if (isVideoAvailable) {
trtcInfo.muteVideoUserIdList = trtcInfo.muteVideoUserIdList.filter((item: string) => item !== userID)
} else {
trtcInfo.muteVideoUserIdList = [...trtcInfo.muteVideoUserIdList, userID]
}
await store.dispatch('setTrtcInfo', trtcInfo)
}
// 远端用户开启/关闭了麦克风
async function handleUserAudioChange ({ userID, isAudioAvailable }: UserAudioInfo) {
console.log('远端用户开启/关闭了麦克风')
const { trtcInfo } = store.getters
if (isAudioAvailable) {
trtcInfo.muteAudioUserIdList = trtcInfo.muteAudioUserIdList.filter((item: string) => item !== userID)
} else {
trtcInfo.muteAudioUserIdList = [...trtcInfo.muteAudioUserIdList, userID]
}
await store.dispatch('setTrtcInfo', trtcInfo)
}
// 解散会议
async function dissolveMeeting () {
const { trtcInfo } = store.getters
trtcInfo.callStatus = 'idle'
if (trtcInfo.meetingUserIdList.length < 2) {
trtcInfo.meetingUserIdList = []
trtcInfo.muteVideoUserIdList = []
trtcInfo.muteAudioUserIdList = []
}
await store.dispatch('setTrtcInfo', trtcInfo)
}
return {
getUserSig,
initTRTC,
initListener,
removeListener,
handleLogin
}
}
5. 绑定监听事件
大家可以结合实际的业务场景,在页面中或者全局绑定监听事件。
App.vue
中添加:
setup () {
const { initListener, removeListener } = useTRTC()
const store = useStore()
const loginStatus = computed(() => store.getters.loginStatus)
const trtcCalling = computed(() => store.getters.trtcCalling)
watch(loginStatus, (val, oldVal) => {
if (val) {
// 登录成功监听
initListener(toRaw(trtcCalling.value))
} else {
// 取消登录移除监听
removeListener(toRaw(trtcCalling.value))
}
})
....
}
6. 视频呼叫模块
VideoCall.vue
中:
setup () {
const store = useStore()
const images = inject('images')
const trtcInfo = computed(() => store.getters.trtcInfo)
const userInfo = computed(() => store.getters.userInfo)
const trtcCalling = computed(() => store.getters.trtcCalling)
const showVideoCall = ref(false) // 显示视频区域
const isVideoOn = ref(true) // 视频状态
const isAudioOn = ref(true) // 麦克风状态
// 呼叫用户
async function handleCallUser (values: { username: string }) {
const trtcInfoData = trtcInfo.value
const { username } = userInfo.value
await toRaw(trtcCalling.value).call({
userID: values.username,
type: TRTCCalling.CALL_TYPE.VIDEO_CALL, // 视频通话
timeout: 30 // 超时30s
})
trtcInfoData.callStatus = 'calling'
trtcInfoData.isInviter = true
if (trtcInfoData.meetingUserIdList.indexOf(username) < 0) trtcInfoData.meetingUserIdList.push(username)
await store.dispatch('setTrtcInfo', trtcInfoData)
}
// 取消呼叫
async function handleCancelCallUser () {
const trtcInfoData = trtcInfo.value
await toRaw(trtcCalling.value).hangup()
trtcInfoData.callStatus = 'idle'
trtcInfoData.meetingUserIdList = []
trtcInfoData.muteVideoUserIdList = []
trtcInfoData.muteAudioUserIdList = []
await store.dispatch('setTrtcInfo', trtcInfoData)
}
// 开始会议
function startMeeting () {
const trtcInfoData = trtcInfo.value
const { username } = userInfo.value
// 多人通话
if (trtcInfoData.meetingUserIdList >= 3) {
const lastJoinUser = trtcInfoData.meetingUserIdList[trtcInfoData.meetingUserIdList.length - 1]
nextTick(() => {
toRaw(trtcCalling.value).startRemoteView({
userID: lastJoinUser,
videoViewDomID: `video-${lastJoinUser}`
})
})
return
}
showVideoCall.value = true
nextTick(() => {
toRaw(trtcCalling.value).startLocalView({
userID: username,
videoViewDomID: `video-${username}`
})
const otherParticipants = trtcInfoData.meetingUserIdList.filter((item: string) => item !== username)
otherParticipants.forEach((userID: string) => {
toRaw(trtcCalling.value).startRemoteView({
userID,
videoViewDomID: `video-${userID}`
})
})
})
}
// 挂断会议
async function handleHangup () {
const trtcInfoData = trtcInfo.value
await toRaw(trtcCalling.value).hangup()
showVideoCall.value = false
trtcInfoData.callStatus = 'idle'
await store.dispatch('setTrtcInfo', trtcInfoData)
}
// 打开/关闭摄像头
async function toggleVideoStatus () {
const trtcInfoData = trtcInfo.value
const { username } = userInfo.value
isVideoOn.value = !isVideoOn.value
if (isVideoOn.value) {
await toRaw(trtcCalling.value).openCamera()
trtcInfoData.muteVideoUserIdList = trtcInfoData.muteVideoUserIdList.filter((item: string) => item !== username)
} else {
await toRaw(trtcCalling.value).closeCamera()
trtcInfoData.muteVideoUserIdList.push(username)
}
await store.dispatch('setTrtcInfo', trtcInfoData)
}
// 打开/关闭麦克风
async function toggleAudioStatus () {
const trtcInfoData = trtcInfo.value
const { username } = userInfo.value
isAudioOn.value = !isAudioOn.value
toRaw(trtcCalling.value).setMicMute(!isAudioOn.value)
if (isAudioOn.value) {
trtcInfoData.muteAudioUserIdList = trtcInfoData.muteAudioUserIdList.filter((item: string) => item !== username)
} else {
trtcInfoData.muteAudioUserIdList.push(username)
}
await store.dispatch('setTrtcInfo', trtcInfoData)
}
// 判断是否关闭媒体
function isUserMute (muteUserList: Array<string>, userId: string): boolean {
return muteUserList.indexOf(userId) !== -1
}
watch(() => trtcInfo.value.callStatus, (val, oldVal) => {
if (val !== oldVal && val === 'connected') {
startMeeting()
}
})
onMounted(() => {
const trtcInfoData = trtcInfo.value
if (trtcInfoData.callStatus === 'connected' && !trtcInfoData.isInviter) {
startMeeting()
}
})
onBeforeUnmount(() => {
const trtcInfoData = trtcInfo.value
trtcInfoData.muteVideoUserIdList = []
trtcInfoData.muteAudioUserIdList = []
if (trtcInfoData.callStatus === 'connected') {
toRaw(trtcCalling.value).hangup()
trtcInfoData.callStatus = 'idle'
}
store.dispatch('setTrtcInfo', trtcInfoData)
})
return {
images,
trtcInfo,
userInfo,
showVideoCall,
isVideoOn,
isAudioOn,
handleCallUser,
handleCancelCallUser,
toggleVideoStatus,
toggleAudioStatus,
handleHangup,
isUserMute
}
}
7. 效果展示
7.1 登录页
7.2 视频通话
7.3 语音通话
更多推荐
已为社区贡献19条内容
所有评论(0)