|
|
@@ -3,6 +3,17 @@ |
|
|
|
<DeviceSelect v-show="false" v-model:value="cameraId" device-type="camera" /> |
|
|
|
<DeviceSelect v-show="false" v-model:value="microphoneId" device-type="microphone" /> |
|
|
|
|
|
|
|
<!-- 远端 --> |
|
|
|
<div class="remote-container"> |
|
|
|
<div |
|
|
|
v-for="(item) in remoteStreamList" |
|
|
|
:id="item.getUserId()" |
|
|
|
:key="item.getUserId()" |
|
|
|
class="remote-stream-container" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 本地 --> |
|
|
|
<div v-if="localStream" class="local-stream-container"> |
|
|
|
<!-- 本地流播放区域 --> |
|
|
|
<div id="localStream" class="local-stream-content" /> |
|
|
@@ -23,7 +34,9 @@ |
|
|
|
<n-icon v-if="isMutedAudio" size="40" @click="handleAudioUnMute"> |
|
|
|
<AudioMutedOutlined /> |
|
|
|
</n-icon> |
|
|
|
|
|
|
|
</div> |
|
|
|
<div class="audio-control control"> |
|
|
|
<n-button type="error" ghost @click="leaveMeetingRoom">退出会议</n-button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@@ -34,9 +47,10 @@ |
|
|
|
import TRTC from 'trtc-js-sdk' |
|
|
|
import { useRoute } from 'vue-router' |
|
|
|
import DeviceSelect from './components/Device.vue' |
|
|
|
import { reactive, toRefs, onMounted, watch } from 'vue' |
|
|
|
import { reactive, toRefs, onMounted, watch, nextTick } from 'vue' |
|
|
|
import { VideocamOutline, VideocamOffOutline } from '@vicons/ionicons5' |
|
|
|
import { AudioOutlined, AudioMutedOutlined } from '@vicons/antd' |
|
|
|
import { isUndef } from '@/utils/is.js' |
|
|
|
// import { Screen, ScreenOff } from '@vicons/carbon' |
|
|
|
export default { |
|
|
|
name: 'HomePage', |
|
|
@@ -55,14 +69,11 @@ export default { |
|
|
|
sdkSecret: '9b5fc557f286d7e4d6eafd8023026da59f0674000f319754aa1ec4beefddcdd6', |
|
|
|
userId: '', |
|
|
|
roomId: null, |
|
|
|
isJoining: false, |
|
|
|
isJoined: false, |
|
|
|
isPublishing: false, |
|
|
|
isPublished: false, |
|
|
|
secret: { |
|
|
|
'haoran': 'eJwtzEELgjAYxvHvsqsh29ymCB28hIIElZR1G2y1ty2VJSVE3z1Tj8-vgf8HVeUhfGmPUkRDjFbTBqWbHq4wsZGtl83yPJWVXQcKpYRhHHMqGJkfPXTg9eicc4oxnrWHx9*EEFHECGVLBW5jWMn6HCRBW7njJd*WNgNrNrnbFXcZG356V94Vg7W12ydr9P0BbGAyOQ__', |
|
|
|
'wanghaoran': 'eJwtzEELgjAcBfDvsnO4ubnFhA6ihwIhUqEOXhZO*6dNUalF9N0z9fh*7-E*KItT56l75CPqELSZMxTajFDCzC9lqptqe2XWdihq1XVQIN-1CNlyKjx3abTtoNeTc84pIWTRER5-E0IwVwq5bgeopnPPRDluaVPGTb1P3nd7CcPMHgJ2zbFJ5HCKrDkHxzLHMmX1Dn1-G3A0Zg__' |
|
|
|
} |
|
|
|
}, |
|
|
|
remoteStreamList: [] |
|
|
|
}) |
|
|
|
|
|
|
|
const settings = reactive({ |
|
|
@@ -74,6 +85,15 @@ export default { |
|
|
|
isMutedAudio: false // 是否关闭音频 |
|
|
|
}) |
|
|
|
|
|
|
|
const status = reactive({ |
|
|
|
isJoining: false, |
|
|
|
isJoined: false, |
|
|
|
isPublishing: false, |
|
|
|
isUnPublishing: false, |
|
|
|
isPublished: false, |
|
|
|
isLeaving: false |
|
|
|
}) |
|
|
|
|
|
|
|
const createMeetingRoom = async() => { |
|
|
|
data.client = TRTC.createClient({ |
|
|
|
sdkAppId: data.sdkAppId, // 填写您申请的 sdkAppId |
|
|
@@ -85,18 +105,18 @@ export default { |
|
|
|
} |
|
|
|
|
|
|
|
const joinMeetingRoom = async() => { |
|
|
|
if (data.isJoining || data.isJoined) { |
|
|
|
if (status.isJoining || status.isJoined) { |
|
|
|
return |
|
|
|
} |
|
|
|
data.isJoining = true |
|
|
|
status.isJoining = true |
|
|
|
!data.client && await createMeetingRoom() |
|
|
|
try { |
|
|
|
await data.client.join({ roomId: data.roomId }) |
|
|
|
data.isJoining = false |
|
|
|
data.isJoined = true |
|
|
|
status.isJoining = false |
|
|
|
status.isJoined = true |
|
|
|
startGetAudioLevel() |
|
|
|
} catch (error) { |
|
|
|
data.isJoining = false |
|
|
|
status.isJoining = false |
|
|
|
console.error('join room failed', error) |
|
|
|
throw error |
|
|
|
} |
|
|
@@ -140,15 +160,14 @@ export default { |
|
|
|
console.log(`peer-leave ${userId}`, event) |
|
|
|
}) |
|
|
|
/* 远端添加流 */ |
|
|
|
data.client.on('stream-added', (event) => { |
|
|
|
data.client.on('stream-added', async(event) => { |
|
|
|
const { stream: remoteStream } = event |
|
|
|
const remoteUserId = remoteStream.getUserId() |
|
|
|
if (remoteUserId === `share_${this.userId}`) { |
|
|
|
this.unSubscribe(remoteStream) |
|
|
|
if (remoteUserId === `share_${data.userId}`) { |
|
|
|
await unsubscribeStream(remoteStream) |
|
|
|
} else { |
|
|
|
console.log(`remote stream added: [${remoteUserId}] type: ${remoteStream.getType()}`) |
|
|
|
this.subscribe(remoteStream) |
|
|
|
this.addSuccessLog(`RemoteStream added: [${remoteUserId}].`) |
|
|
|
await subscribeStream(remoteStream) |
|
|
|
} |
|
|
|
}) |
|
|
|
/* 远端流订阅成功 */ |
|
|
@@ -156,19 +175,18 @@ export default { |
|
|
|
const { stream: remoteStream } = event |
|
|
|
const remoteUserId = remoteStream.getUserId() |
|
|
|
console.log('stream-subscribed userId: ', remoteUserId) |
|
|
|
this.addSuccessLog(`RemoteStream subscribed: [${remoteUserId}].`) |
|
|
|
this.remoteStreamList.push(remoteStream) |
|
|
|
this.$nextTick(() => { |
|
|
|
this.playRemoteStream(remoteStream, remoteUserId) |
|
|
|
data.remoteStreamList.push(remoteStream) |
|
|
|
nextTick(() => { |
|
|
|
playRemoteStream(remoteStream, remoteUserId) |
|
|
|
}) |
|
|
|
}) |
|
|
|
/* 远端流移除 */ |
|
|
|
data.client.on('stream-removed', (event) => { |
|
|
|
const { stream: remoteStream } = event |
|
|
|
remoteStream.stop() |
|
|
|
const index = this.remoteStreamList.indexOf(remoteStream) |
|
|
|
const index = data.remoteStreamList.indexOf(remoteStream) |
|
|
|
if (index >= 0) { |
|
|
|
this.remoteStreamList.splice(index, 1) |
|
|
|
data.remoteStreamList.splice(index, 1) |
|
|
|
} |
|
|
|
console.log(`stream-removed userId: ${remoteStream.getUserId()} type: ${remoteStream.getType()}`) |
|
|
|
}) |
|
|
@@ -176,7 +194,6 @@ export default { |
|
|
|
data.client.on('stream-updated', (event) => { |
|
|
|
const { stream: remoteStream } = event |
|
|
|
console.log(`type: ${remoteStream.getType()} stream-updated hasAudio: ${remoteStream.hasAudio()} hasVideo: ${remoteStream.hasVideo()}`) |
|
|
|
this.addSuccessLog(`RemoteStream updated: [${remoteStream.getUserId()}] audio:${remoteStream.hasAudio()}, video:${remoteStream.hasVideo()}.`) |
|
|
|
}) |
|
|
|
/* 远端流禁用音频 */ |
|
|
|
data.client.on('mute-audio', (event) => { |
|
|
@@ -211,6 +228,47 @@ export default { |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @description: 订阅远端流 |
|
|
|
* @return {*} |
|
|
|
*/ |
|
|
|
const subscribeStream = async(remoteStream, config = { audio: true, video: true }) => { |
|
|
|
try { |
|
|
|
await data.client.subscribe(remoteStream, { |
|
|
|
audio: isUndef(config.audio) ? true : config.audio, |
|
|
|
video: isUndef(config.video) ? true : config.video |
|
|
|
}) |
|
|
|
} catch (error) { |
|
|
|
console.error(`subscribe ${remoteStream.getUserId()} with audio: ${config.audio} video: ${config.video} error`, error) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @description: 取消订阅远端流 |
|
|
|
* @return {*} |
|
|
|
*/ |
|
|
|
const unsubscribeStream = async(remoteStream) => { |
|
|
|
try { |
|
|
|
await data.client.unsubscribe(remoteStream) |
|
|
|
} catch (error) { |
|
|
|
console.error(`unsubscribe ${remoteStream.getUserId()} error`, error) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @description: 播放远端流 |
|
|
|
* @param {*} remoteStream |
|
|
|
* @param {*} element |
|
|
|
* @return {*} |
|
|
|
*/ |
|
|
|
const playRemoteStream = (remoteStream, element) => { |
|
|
|
if (remoteStream.getType() === 'main' && remoteStream.getUserId().indexOf('share') >= 0) { |
|
|
|
remoteStream.play(element, { objectFit: 'contain' }).catch() |
|
|
|
} else { |
|
|
|
remoteStream.play(element).catch() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @description: 初始化本地流 |
|
|
|
* @return {*} |
|
|
@@ -249,21 +307,41 @@ export default { |
|
|
|
* @return {*} |
|
|
|
*/ |
|
|
|
const publishStream = async() => { |
|
|
|
if (!data.isJoined || data.isPublishing || data.isPublished) { |
|
|
|
if (!status.isJoined || status.isPublishing || status.isPublished) { |
|
|
|
return |
|
|
|
} |
|
|
|
data.isPublishing = true |
|
|
|
status.isPublishing = true |
|
|
|
try { |
|
|
|
await data.client.publish(settings.localStream) |
|
|
|
data.isPublishing = false |
|
|
|
data.isPublished = true |
|
|
|
status.isPublishing = false |
|
|
|
status.isPublished = true |
|
|
|
} catch (error) { |
|
|
|
data.isPublishing = false |
|
|
|
status.isPublishing = false |
|
|
|
console.error('publish localStream failed', error) |
|
|
|
throw error |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @description: 终止推流 |
|
|
|
* @return {*} |
|
|
|
*/ |
|
|
|
const unPublishStream = async() => { |
|
|
|
if (!status.isPublished || status.isUnPublishing) { |
|
|
|
return |
|
|
|
} |
|
|
|
status.isUnPublishing = true |
|
|
|
try { |
|
|
|
await data.client.unpublish(settings.localStream) |
|
|
|
status.isUnPublishing = false |
|
|
|
status.isPublished = false |
|
|
|
} catch (error) { |
|
|
|
status.isUnPublishing = false |
|
|
|
console.error('unpublish localStream failed', error) |
|
|
|
throw error |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @description: 打开摄像头 |
|
|
|
* @return {*} |
|
|
@@ -308,18 +386,53 @@ export default { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @description: 销毁房间 |
|
|
|
* @return {*} |
|
|
|
*/ |
|
|
|
const destroyLocalStream = () => { |
|
|
|
settings.localStream && settings.localStream.stop() |
|
|
|
settings.localStream && settings.localStream.close() |
|
|
|
settings.localStream = null |
|
|
|
settings.isPlayingLocalStream = false |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @description: 禁用音频 |
|
|
|
* @return {*} |
|
|
|
*/ |
|
|
|
const stopGetAudioLevel = () => { |
|
|
|
data.client && data.client.enableAudioVolumeEvaluation(-1) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @description: 退出直播间 |
|
|
|
* @return {*} |
|
|
|
*/ |
|
|
|
const leaveMeetingRoom = async() => { |
|
|
|
await data.client.leave() |
|
|
|
if (!status.isJoined || status.isLeaving) { |
|
|
|
return |
|
|
|
} |
|
|
|
status.isLeaving = true |
|
|
|
stopGetAudioLevel() |
|
|
|
status.isPublished && await unPublishStream() |
|
|
|
settings.localStream && destroyLocalStream() |
|
|
|
|
|
|
|
try { |
|
|
|
await data.client.leave() |
|
|
|
status.isLeaving = false |
|
|
|
status.isJoined = false |
|
|
|
} catch (error) { |
|
|
|
status.isLeaving = false |
|
|
|
console.error('leave room error', error) |
|
|
|
throw error |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
onMounted(() => { |
|
|
|
const { userId, roomId } = route.query |
|
|
|
data.userId = userId |
|
|
|
data.roomId = Number(roomId) |
|
|
|
// nextTick(async() => { |
|
|
|
// await createMeetingRoom() |
|
|
|
// await initLocalStream() |
|
|
|
// }) |
|
|
|
}) |
|
|
|
watch(() => [settings.cameraId, settings.microphoneId], async([cameraId, microphoneId]) => { |
|
|
|
if (cameraId && microphoneId) { |
|
|
@@ -334,10 +447,12 @@ export default { |
|
|
|
return { |
|
|
|
...toRefs(data), |
|
|
|
...toRefs(settings), |
|
|
|
...toRefs(status), |
|
|
|
handleVideoMute, |
|
|
|
handleVideoUnMute, |
|
|
|
handleAudioMute, |
|
|
|
handleAudioUnMute |
|
|
|
handleAudioUnMute, |
|
|
|
leaveMeetingRoom |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@@ -347,10 +462,12 @@ export default { |
|
|
|
.room{ |
|
|
|
width: 100vw; |
|
|
|
height: 100vh; |
|
|
|
position: relative; |
|
|
|
.local-stream-container{ |
|
|
|
width: 100%; |
|
|
|
height: 100%; |
|
|
|
position: relative; |
|
|
|
position: absolute; |
|
|
|
top: 0; |
|
|
|
.local-stream-content { |
|
|
|
width: 100%; |
|
|
|
height: 100%; |
|
|
@@ -358,9 +475,9 @@ export default { |
|
|
|
.local-stream-control { |
|
|
|
width: 100%; |
|
|
|
height: 100%; |
|
|
|
height: 40px; |
|
|
|
height: 50px; |
|
|
|
position: absolute; |
|
|
|
bottom: 5px; |
|
|
|
bottom: 0; |
|
|
|
z-index: 99; |
|
|
|
display: flex; |
|
|
|
justify-content: flex-end; |
|
|
@@ -372,5 +489,20 @@ export default { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
.remote-container{ |
|
|
|
position: absolute; |
|
|
|
top: 50px; |
|
|
|
width: 150px; |
|
|
|
max-height: calc(100% - 100px); |
|
|
|
overflow-y: scroll; |
|
|
|
z-index: 999; |
|
|
|
right: 10px; |
|
|
|
.remote-stream-container{ |
|
|
|
width: 100%; |
|
|
|
height: 200px; |
|
|
|
background: red; |
|
|
|
margin-bottom: 10px; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
</style> |