Compare commits
28 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
7874ebd377 | |
|
|
285c149943 | |
|
|
bc2b7dfb1e | |
|
|
d8546abbaf | |
|
|
ac94990547 | |
|
|
c3d3e737b7 | |
|
|
ca616cfb08 | |
|
|
69356dd714 | |
|
|
95bfae8356 | |
|
|
7825e02ef5 | |
|
|
f2b4cc7091 | |
|
|
c3346a2f1f | |
|
|
8682fa26db | |
|
|
b18105f02d | |
|
|
a91424d995 | |
|
|
71cb7da4d4 | |
|
|
fd4cc250f9 | |
|
|
26ccf348e5 | |
|
|
51af040686 | |
|
|
10ad7cdeb2 | |
|
|
8bcb39045c | |
|
|
83a9ed380e | |
|
|
29e1ccad73 | |
|
|
b696b192ed | |
|
|
d911a20419 | |
|
|
377c3e7856 | |
|
|
2698ba7007 | |
|
|
54f189b4c4 |
2
.env
2
.env
|
|
@ -1,5 +1,5 @@
|
|||
# title
|
||||
VITE_APP_TITLE = 'h5'
|
||||
VITE_APP_TITLE = '视频会议'
|
||||
|
||||
# 端口号
|
||||
VITE_PORT = 3000
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
<link rel="icon" href="/favicon.ico" />
|
||||
<title><%= title %></title>
|
||||
|
||||
<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
db957f82a208d53cdaeff928dd554022
|
||||
|
|
@ -37,8 +37,6 @@ body {
|
|||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ html {
|
|||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
body,#app{
|
||||
width: 100%;
|
||||
min-width: 1440px;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background-color: #f2f2f2;
|
||||
font-family: 'Encode Sans Condensed', sans-serif;
|
||||
|
|
|
|||
|
|
@ -1,75 +1,71 @@
|
|||
<template>
|
||||
<div class="select-container">
|
||||
<n-select
|
||||
v-model:value="activeDeviceId"
|
||||
class="select"
|
||||
:placeholder="deviceType"
|
||||
:options="deviceList"
|
||||
value-field="deviceId"
|
||||
@update:value="handleChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="select-container" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TRTC from 'trtc-js-sdk'
|
||||
import { reactive, toRefs, onMounted, defineComponent } from 'vue'
|
||||
import { defineComponent, reactive, toRefs, onMounted, onBeforeUnmount } from 'vue'
|
||||
export default defineComponent({
|
||||
name: 'DeviceSelect',
|
||||
props: {
|
||||
deviceType: {
|
||||
type: String,
|
||||
default: () => {}
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: () => {}
|
||||
}
|
||||
|
||||
},
|
||||
emits: ['update:value'],
|
||||
emits: ['init', 'switch'],
|
||||
setup(props, { emit }) {
|
||||
const data = reactive({
|
||||
deviceList: [],
|
||||
activeDeviceId: ''
|
||||
videoList: [],
|
||||
audioList: [],
|
||||
videoDeviceId: '',
|
||||
audioDeviceId: ''
|
||||
})
|
||||
|
||||
const getDeviceList = async() => {
|
||||
switch (props.deviceType) {
|
||||
case 'camera':
|
||||
data.deviceList = await TRTC.getCameras()
|
||||
break
|
||||
case 'microphone':
|
||||
data.deviceList = await TRTC.getMicrophones()
|
||||
break
|
||||
case 'speaker':
|
||||
data.deviceList = await TRTC.getSpeakers()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
data.activeDeviceId = data.deviceList[0].deviceId
|
||||
emit('update:value', data.activeDeviceId)
|
||||
data.videoList = await TRTC.getCameras()
|
||||
data.audioList = await TRTC.getMicrophones()
|
||||
data.videoDeviceId = data.videoList[0]?.deviceId || ''
|
||||
data.audioDeviceId = data.audioList[0]?.deviceId || ''
|
||||
emit('init', {
|
||||
cameraId: data.videoDeviceId,
|
||||
microphoneId: data.audioDeviceId
|
||||
})
|
||||
}
|
||||
|
||||
const handleChange = (value) => {
|
||||
data.activeDeviceId = value
|
||||
emit('update:value', data.activeDeviceId)
|
||||
const handleCameraReverse = () => {
|
||||
const index = data.videoList.findIndex((item) => item.deviceId === data.videoDeviceId)
|
||||
const len = data.videoList.length
|
||||
if (len <= 1) {
|
||||
return
|
||||
} else if (index === 0) {
|
||||
data.videoDeviceId = data.videoList[len - 1].deviceId
|
||||
emit('init', {
|
||||
cameraId: data.videoDeviceId,
|
||||
microphoneId: data.audioDeviceId
|
||||
})
|
||||
emit('switch', { type: 'video', cameraId: data.videoDeviceId, isMirror: false })
|
||||
} else {
|
||||
data.videoDeviceId = data.videoList[0].deviceId
|
||||
emit('init', {
|
||||
cameraId: data.videoDeviceId,
|
||||
microphoneId: data.audioDeviceId
|
||||
})
|
||||
emit('switch', { type: 'video', cameraId: data.videoDeviceId, isMirror: true })
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
navigator.mediaDevices.getUserMedia({ audio: true, video: true })
|
||||
.then(() => {
|
||||
.then((res) => {
|
||||
getDeviceList()
|
||||
})
|
||||
// navigator.mediaDevices.addEventListener('devicechange', this.getDeviceList)
|
||||
navigator.mediaDevices.addEventListener('devicechange', getDeviceList)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
navigator.mediaDevices.removeEventListener('devicechange', getDeviceList)
|
||||
})
|
||||
// beforeUnmount() {
|
||||
// navigator.mediaDevices.removeEventListener('devicechange', this.getDeviceList)
|
||||
// }
|
||||
|
||||
return {
|
||||
...toRefs(data),
|
||||
handleChange
|
||||
handleCameraReverse
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
<template>
|
||||
<div class="select-container">
|
||||
<n-select
|
||||
v-model:value="activeDeviceId"
|
||||
class="select"
|
||||
:placeholder="deviceType"
|
||||
:options="deviceList"
|
||||
value-field="deviceId"
|
||||
@update:value="handleChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TRTC from 'trtc-js-sdk'
|
||||
import { defineComponent, reactive, toRefs, onMounted, onBeforeUnmount } from 'vue'
|
||||
export default defineComponent({
|
||||
name: 'DeviceSelect',
|
||||
props: {
|
||||
deviceType: {
|
||||
type: String,
|
||||
default: () => {}
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
emits: ['update:value', 'switch'],
|
||||
setup(props, { emit }) {
|
||||
const data = reactive({
|
||||
deviceList: [],
|
||||
activeDeviceId: ''
|
||||
})
|
||||
|
||||
const getDeviceList = async() => {
|
||||
switch (props.deviceType) {
|
||||
case 'video':
|
||||
data.deviceList = await TRTC.getCameras()
|
||||
break
|
||||
case 'audio':
|
||||
data.deviceList = await TRTC.getMicrophones()
|
||||
break
|
||||
case 'speaker':
|
||||
data.deviceList = await TRTC.getSpeakers()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
data.activeDeviceId = data.deviceList[0].deviceId
|
||||
emit('update:value', data.activeDeviceId)
|
||||
}
|
||||
|
||||
const handleChange = (value) => {
|
||||
data.activeDeviceId = value
|
||||
emit('update:value', data.activeDeviceId)
|
||||
emit('switch', { type: props.deviceType, deviceId: data.activeDeviceId })
|
||||
}
|
||||
|
||||
const handleReverse = () => {
|
||||
const index = data.deviceList.findIndex((item) => item.deviceId === data.activeDeviceId)
|
||||
if (data.deviceList.length === 1) {
|
||||
return
|
||||
} else if (index === 0) {
|
||||
data.activeDeviceId = data.deviceList[1].deviceId
|
||||
emit('update:value', data.activeDeviceId)
|
||||
emit('switch', { type: props.deviceType, deviceId: data.activeDeviceId })
|
||||
} else {
|
||||
data.activeDeviceId = data.deviceList[0].deviceId
|
||||
emit('update:value', data.activeDeviceId)
|
||||
emit('switch', { type: props.deviceType, deviceId: data.activeDeviceId })
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
navigator.mediaDevices.getUserMedia({ audio: true, video: true })
|
||||
.then((res) => {
|
||||
console.log(res)
|
||||
getDeviceList()
|
||||
})
|
||||
navigator.mediaDevices.addEventListener('devicechange', getDeviceList)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
navigator.mediaDevices.removeEventListener('devicechange', getDeviceList)
|
||||
})
|
||||
|
||||
return {
|
||||
...toRefs(data),
|
||||
handleChange,
|
||||
handleReverse
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select-container {
|
||||
display: flex;
|
||||
.label {
|
||||
display: inline-block;
|
||||
padding: 0 20px;
|
||||
width: 120px;
|
||||
height: 40px;
|
||||
text-align: left;
|
||||
line-height: 40px;
|
||||
border-top: 1px solid #DCDFE6;
|
||||
border-left: 1px solid #DCDFE6;
|
||||
border-bottom: 1px solid #DCDFE6;
|
||||
border-radius: 4px 0 0 4px;
|
||||
color: #909399;
|
||||
background-color: #F5F7FA;
|
||||
font-weight: bold;
|
||||
}
|
||||
.select {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.select {
|
||||
input {
|
||||
border-radius: 0 4px 4px 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<div class="room">
|
||||
<DeviceSelect v-show="false" v-model:value="cameraId" device-type="camera" />
|
||||
<DeviceSelect v-show="false" v-model:value="microphoneId" device-type="microphone" />
|
||||
<!-- <DeviceSelect v-show="false" ref="cameraRef" v-model:value="cameraId" device-type="video" @switch="handleCamareSwitch" /> -->
|
||||
<!-- <DeviceSelect v-show="false" v-model:value="microphoneId" device-type="audio" /> -->
|
||||
<Device ref="cameraRef" @init="deviceInit" @switch="handleCamareSwitch" />
|
||||
|
||||
<!-- 远端 -->
|
||||
<div class="remote-container">
|
||||
|
|
@ -19,6 +20,11 @@
|
|||
<div id="localStream" class="local-stream-content" />
|
||||
<!-- 本地流操作栏 -->
|
||||
<div v-if="isPlayingLocalStream" class="local-stream-control">
|
||||
<div class="video-control control">
|
||||
<n-icon size="40" @click="handelCameraReverse">
|
||||
<CameraReverseOutline />
|
||||
</n-icon>
|
||||
</div>
|
||||
<div class="video-control control">
|
||||
<n-icon v-if="!isMutedVideo" size="40" @click="handleVideoMute">
|
||||
<VideocamOutline />
|
||||
|
|
@ -46,16 +52,21 @@
|
|||
<script>
|
||||
import TRTC from 'trtc-js-sdk'
|
||||
import { useRoute } from 'vue-router'
|
||||
import DeviceSelect from './components/Device.vue'
|
||||
import { reactive, toRefs, onMounted, watch, nextTick } from 'vue'
|
||||
import { VideocamOutline, VideocamOffOutline } from '@vicons/ionicons5'
|
||||
import Device from './components/Device.vue'
|
||||
// import DeviceSelect from './components/DeviceSelect.vue'
|
||||
import { ref, reactive, toRefs, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||
import { CameraReverseOutline, VideocamOutline, VideocamOffOutline } from '@vicons/ionicons5'
|
||||
import { AudioOutlined, AudioMutedOutlined } from '@vicons/antd'
|
||||
import { isUndef } from '@/utils/is.js'
|
||||
import { formatDateTime } from '@/utils/index.js'
|
||||
import axios from 'axios'
|
||||
// import { Screen, ScreenOff } from '@vicons/carbon'
|
||||
export default {
|
||||
name: 'HomePage',
|
||||
components: {
|
||||
DeviceSelect,
|
||||
Device,
|
||||
// DeviceSelect,
|
||||
CameraReverseOutline,
|
||||
VideocamOutline,
|
||||
VideocamOffOutline,
|
||||
AudioOutlined,
|
||||
|
|
@ -63,17 +74,19 @@ export default {
|
|||
},
|
||||
setup() {
|
||||
const route = useRoute()
|
||||
const cameraRef = ref()
|
||||
const data = reactive({
|
||||
hasInit: false,
|
||||
client: null,
|
||||
timer: null,
|
||||
sdkAppId: 1400752641,
|
||||
sdkSecret: '9b5fc557f286d7e4d6eafd8023026da59f0674000f319754aa1ec4beefddcdd6',
|
||||
userId: '',
|
||||
userId: null,
|
||||
userSig: null,
|
||||
roomId: null,
|
||||
secret: {
|
||||
'haoran': 'eJwtzEELgjAYxvHvsqsh29ymCB28hIIElZR1G2y1ty2VJSVE3z1Tj8-vgf8HVeUhfGmPUkRDjFbTBqWbHq4wsZGtl83yPJWVXQcKpYRhHHMqGJkfPXTg9eicc4oxnrWHx9*EEFHECGVLBW5jWMn6HCRBW7njJd*WNgNrNrnbFXcZG356V94Vg7W12ydr9P0BbGAyOQ__',
|
||||
'wanghaoran': 'eJwtzEELgjAcBfDvsnO4ubnFhA6ihwIhUqEOXhZO*6dNUalF9N0z9fh*7-E*KItT56l75CPqELSZMxTajFDCzC9lqptqe2XWdihq1XVQIN-1CNlyKjx3abTtoNeTc84pIWTRER5-E0IwVwq5bgeopnPPRDluaVPGTb1P3nd7CcPMHgJ2zbFJ5HCKrDkHxzLHMmX1Dn1-G3A0Zg__'
|
||||
},
|
||||
remoteStreamList: []
|
||||
taskId: null,
|
||||
remoteStreamList: [],
|
||||
isMirror: true
|
||||
})
|
||||
|
||||
const settings = reactive({
|
||||
|
|
@ -98,7 +111,7 @@ export default {
|
|||
data.client = TRTC.createClient({
|
||||
sdkAppId: data.sdkAppId, // 填写您申请的 sdkAppId
|
||||
userId: data.userId, // 填写您业务对应的 userId
|
||||
userSig: data.secret[data.userId], // 填写服务器或本地计算的 userSig
|
||||
userSig: data.userSig, // 填写服务器或本地计算的 userSig
|
||||
mode: 'rtc'
|
||||
})
|
||||
handleClientEvents()
|
||||
|
|
@ -115,6 +128,10 @@ export default {
|
|||
status.isJoining = false
|
||||
status.isJoined = true
|
||||
startGetAudioLevel()
|
||||
updateUsers(2)
|
||||
data.timer = setInterval(async() => {
|
||||
await heartbeat()
|
||||
}, 5000)
|
||||
} catch (error) {
|
||||
status.isJoining = false
|
||||
console.error('join room failed', error)
|
||||
|
|
@ -293,7 +310,10 @@ export default {
|
|||
* @return {*}
|
||||
*/
|
||||
const playLocalStream = async() => {
|
||||
settings.localStream.play('localStream')
|
||||
if (settings.localStream && settings.isPlayingLocalStream) {
|
||||
settings.isPlayingLocalStream = false
|
||||
}
|
||||
settings.localStream.play('localStream', { mirror: data.isMirror })
|
||||
.then(() => {
|
||||
settings.isPlayingLocalStream = true
|
||||
})
|
||||
|
|
@ -405,6 +425,24 @@ export default {
|
|||
data.client && data.client.enableAudioVolumeEvaluation(-1)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 切换音频
|
||||
* @param {*} type
|
||||
* @param {*} cameraId
|
||||
* @return {*}
|
||||
*/
|
||||
const handleCamareSwitch = ({ type, cameraId, isMirror = true }) => {
|
||||
try {
|
||||
if (settings.localStream) {
|
||||
data.isMirror = isMirror
|
||||
settings.localStream.switchDevice(type, cameraId)
|
||||
playLocalStream()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('switchDevice failed', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 退出直播间
|
||||
* @return {*}
|
||||
|
|
@ -422,6 +460,7 @@ export default {
|
|||
await data.client.leave()
|
||||
status.isLeaving = false
|
||||
status.isJoined = false
|
||||
wx.miniProgram.switchTab({ url: '/pages/tool/tool' })
|
||||
} catch (error) {
|
||||
status.isLeaving = false
|
||||
console.error('leave room error', error)
|
||||
|
|
@ -429,13 +468,78 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const { userId, roomId } = route.query
|
||||
data.userId = userId
|
||||
/**
|
||||
* @description: 用户签名
|
||||
* @return {*}
|
||||
*/
|
||||
const handleSig = async() => {
|
||||
const res = await axios.get(`/hhz/api/tencentCloudRtc/genUserSig/${data.userId}`)
|
||||
if (res.data.code === 0) {
|
||||
data.userSig = res.data.data.userSig
|
||||
}
|
||||
}
|
||||
|
||||
const updateUsers = async(status) => {
|
||||
const params = {
|
||||
roomUserId: data.taskId,
|
||||
status
|
||||
}
|
||||
await axios.put(`/hhz/api/meeting/updatePeopleStatus`, params)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 心跳,定时调用
|
||||
* @param {*} status
|
||||
* @return {*}
|
||||
*/
|
||||
const heartbeat = async() => {
|
||||
const time = new Date().getTime()
|
||||
const params = {
|
||||
roomUserId: data.taskId,
|
||||
heartbeatTime: formatDateTime(time)
|
||||
}
|
||||
await axios.put(`/hhz/api/meeting/heartbeatTime`, params)
|
||||
}
|
||||
/**
|
||||
* @description: 翻转摄像头
|
||||
* @return {*}
|
||||
*/
|
||||
const handelCameraReverse = () => {
|
||||
if (!status.isMutedVideo) {
|
||||
cameraRef.value.handleCameraReverse()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async() => {
|
||||
const { userId, roomId, taskId } = route.query
|
||||
data.userId = String(userId)
|
||||
data.roomId = Number(roomId)
|
||||
data.taskId = Number(taskId)
|
||||
await handleSig()
|
||||
})
|
||||
watch(() => [settings.cameraId, settings.microphoneId], async([cameraId, microphoneId]) => {
|
||||
if (cameraId && microphoneId) {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
updateUsers(3)
|
||||
clearInterval(data.timer)
|
||||
data.timer = null
|
||||
})
|
||||
|
||||
// watch(() => [settings.cameraId, settings.microphoneId], async([cameraId, microphoneId]) => {
|
||||
// console.log('cameraId:' + cameraId, 'microphoneId:' + microphoneId)
|
||||
// if (cameraId && microphoneId) {
|
||||
// data.hasInit = true
|
||||
// }
|
||||
// })
|
||||
|
||||
const deviceInit = (device) => {
|
||||
const { cameraId, microphoneId } = device
|
||||
settings.cameraId = cameraId
|
||||
settings.microphoneId = microphoneId
|
||||
data.hasInit = true
|
||||
}
|
||||
|
||||
watch(() => [data.hasInit, data.userSig], async([init, sig]) => {
|
||||
if (init && sig) {
|
||||
await createMeetingRoom()
|
||||
await joinMeetingRoom()
|
||||
await initLocalStream()
|
||||
|
|
@ -448,10 +552,14 @@ export default {
|
|||
...toRefs(data),
|
||||
...toRefs(settings),
|
||||
...toRefs(status),
|
||||
cameraRef,
|
||||
deviceInit,
|
||||
handleVideoMute,
|
||||
handleVideoUnMute,
|
||||
handleAudioMute,
|
||||
handleAudioUnMute,
|
||||
handelCameraReverse,
|
||||
handleCamareSwitch,
|
||||
leaveMeetingRoom
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export default {
|
|||
const data = reactive({
|
||||
form: {
|
||||
userId: 'wanghaoran',
|
||||
roomId: 111
|
||||
roomId: '111'
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue