简单实现了一下音视频会议功能,代码写得比较随意。
.main{
margin: 0 auto;
margin-top:80px;
width: 800px;
height: 800px;
}
.video-box{
display:flex;
justify-content: space-between;
width: 100%;
height: 450px;
border: 5px solid #ccc;
}
.vbox{
margin: 12px;
width:200px;
height:200px;
border: 1px solid #000;
overflow: hidden;
}
.video{
position: relative;
margin: 12px;
width:200px;
height:200px;
border: 1px solid #000;
background-color: rgba(11, 10, 10, 0.893);
overflow: hidden;
}
.video .avatar{
position: absolute;
top: 50%;
left: 50%;
margin-left: -50px;
margin-top: -50px;
background-image: url('../../../public/imgaes/Avatar.jpg');
background-size: 100px 100px;
width: 100px;
height: 100px;
border-radius: 50px;
border: 1px solid #ccc;
overflow: hidden;
}
.video .rong-video{
width: 100%;
height: 100%;
background-color: #000;
}
input{
margin-right: 12px;
width: 160px;
height: 30px;
border: 1px solid #ccc;
border-radius: 2px;
}
button{
margin-right: 12px;
height: 30px;
background-color: rgb(0, 119, 255);
border: none;
border: 1px solid rgb(0, 98, 255);
border-radius: 2px;
color: #fff;
}
.operation{
display: flex;
width: 100%;
height: 50px;
border: 1px solid #000;
}
.icon{
margin-left: 16px;
cursor: pointer;
text-align: center;
padding-top: 10px;
width: 100px;
height:calc(100% - 10px);
font-size: 12px;
background-color: rgba(174, 178, 181, 0.307);
}
import { useCallback, useEffect, useRef, useState } from "react"
import * as RongIMLib from '@rongcloud/imlib-v4-adapter'
import { installer, RCRTCCode } from '@rongcloud/plugin-rtc'
import {
CameraOutlined,
AudioOutlined,
LaptopOutlined,
} from '@ant-design/icons';
import "./index.css"
import "../../index.css"
export default function Meeting() {
const [meethingId, setMeethingId] = useState("")//会议号
const [remoteTracks, setRemoteTracks] = useState([])//tracks初始化
const[track,setTrack] = useState(null)
const [crtRoom,setCtrRoom] =useState(null)//room实例
const [rtcClient, setRtcClient] = useState()//初始化rtc
const [token, setToken] = useState('ccOTgRXdriqNuRTHlZlHUYWGJFGTBY7L@62zf.cn.rongnav.com;62zf.cn.rongcfg.com')
const [openCamer, setOpenCamer] = useState(true)
const videoRef = useRef(null)
useEffect(() => {
// 获取IMLIb实例,IMLib 是即时通讯能力库,封装了通信能力和会话、消息等对象
const im = RongIMLib.init({ appkey: '8luwapkv8dmsl' })
// 监听IMLib连接的状态变化
im.watch({
status(status) {
// IM 连接状态变更通知
console.log("连接status", status)
}
})
//初始化RCRTCClient
setRtcClient(im.install(installer, {
timeout: 30*1000,
}))
// 建立IM连接
try {
const user = im.connect({ token: token })
console.log('链接成功, 链接用户 id 为: ', user.id);
} catch (error) {
console.log('链接失败: ', error.code, error.msg);
}
}, [])
async function joinRoom() {
setOpenCamer(false)
const { code, room, userIds, tracks } = await rtcClient.joinRTCRoom(meethingId)
if (code !== RCRTCCode.SUCCESS) {
console.log('加入房间失败:', code)
return
}else{
console.log('加入房间成功:', code)
}
setCtrRoom(room)//创建的房间实例
tracks.length && setRemoteTracks(tracks)
room.registerRoomEventListener({
onKickOff (byServer) {
console.log('onKickedByServer', byServer);
// 当本地已获取资源后,需要调用 track.destroy() 销毁已获取的资源, track 为 RCMicphoneAudioTrack 或 RCCameraVideoTrack 类型实例
},
//房间其他用户发布新资源时触发
onTrackPublish(tracks) {
console.log('onTrackPublish:', JSON.stringify(tracks));
// 按业务需求选择需要订阅资源,通过 room.subscribe 接口进行订阅
const { code } = room.subscribe(tracks)
if (code == RCRTCCode.SUCCESS) {
console.log('资源订阅成功 ->', code)
}
appendVideoEl(tracks);//添加节点
},
// 订阅音视频流通道建立,track可以进行播放
onTrackReady (track) {
console.log('onTrackReady')
if (track.isAudioTrack()) {
// 音轨不需要传递播放控件
track.play()
} else {
const node = document.getElementById('rc-video-' + track.getTrackId())
track.play(node);
}
},
// 人员加入
onUserJoin(userIds) {
console.log('onUserJoined', JSON.stringify(userIds));
},
// 人员退出
onUserLeave (userIds) {
console.log('onUserLeft', JSON.stringify(userIds));
// userIds.forEach((userId) => {
// document.querySelectorAll('.video-wrap-'+userId).forEach((el) => {
// el.remove();
// });
// });
}
})
// 进入房间打开摄像头
const videoEl = await rtcClient.createMicrophoneAndCameraTracks()
if (videoEl.code === RCRTCCode.SUCCESS) {
console.log("打开摄像头")
setTrack(videoEl.tracks)
const [ audioTrack, videoTrack ] = videoEl.tracks
await room.publish([audioTrack, videoTrack])
videoEl.tracks.map((track) => {
if (track.isVideoTrack()) {
var node = document.createElement("div");
node.classList.add("vbox")
const tempHtml = `
<video class="rong-video" id="rc-video-${track.getTrackId()}"></video>
`;
node.innerHTML = tempHtml;
videoRef.current.appendChild(node)
// 播放视频
if (track.isVideoTrack()) {
const node = document.getElementById('rc-video-' + track.getTrackId())
track.play(node);
return;
}
// 播放音频
if (false) {
track.play();
}
}
});
}
}
function appendVideoEl(tracks) {
tracks.map((track) => {
if (track.isVideoTrack()) {
var node = document.createElement("div");
node.classList.add("vbox")
const tempHtml = `
<video class="rong-video" id="rc-video-${track.getTrackId()}"></video>
`;
node.innerHTML = tempHtml;
videoRef.current.appendChild(node)
}
});
}
// 订阅资源
async function subscribe(tracks){
const { code } = await crtRoom.subscribe(tracks)
console.log("订阅成功",code)
if (code !== RCRTCCode.SUCCESS) {
console.log('资源订阅失败 ->', code)
}
}
// 获取摄像头资源 麦克风资源
async function publishMicrophoneCamera() {
setOpenCamer(false)
const { code, tracks } = await rtcClient.createMicrophoneAndCameraTracks()
setTrack(tracks)
if (code === RCRTCCode.SUCCESS) {
// tracks 包含一个 RCMicphoneAudioTrack 实例和一个 RCCameraVideoTrack 实例
const [ audioTrack, videoTrack ] = tracks
publish(tracks);
} else {
console.log("获取资源失败",code)
}
}
// 发布
async function publish(tracks){
const pubRes = await crtRoom.publish(tracks);
// 若资源发布失败
if (pubRes.code === RCRTCCode.SUCCESS) {
console.log('播放资源 发布成功')
appendVideoEl(tracks);
tracks.forEach((track) => {
playTrack(track, false);
});
}else{
console.log('资源发布失败:', pubRes)
}
}
function playTrack(track, playAudio){
// 播放视频
if (track.isVideoTrack()) {
const node = document.getElementById('rc-video-' + track.getTrackId())
track.play(node);
return;
}
// 播放音频
if (playAudio) {
track.play();
}
}
// 退出房间
async function leaveRoom() {
setOpenCamer(true)
const { code } = await rtcClient.leaveRoom(crtRoom)
if (code !== RCRTCCode.SUCCESS) {
console.log('退出房间失败:', code)
} else {
console.log("已退出房间")
removeVideoEl();
}
await crtRoom.unpublish(track)
const [ audioTrack, videoTrack ] = track
audioTrack.destroy()
videoTrack.destroy()
}
function removeVideoEl(){
setOpenCamer(true)
}
// 禁用音视频
function disable() {
setOpenCamer(true)
const [ audioTrack, videoTrack ] = track
videoTrack.mute()
audioTrack.mute()
audioTrack.destroy()
videoTrack.destroy()
}
return (
<>
<div className="main">
<div>
<input onChange={function (e) {
setMeethingId(e.target.value)
console.log(meethingId)
}} placeholder="会议号"></input>
<button onClick={joinRoom}>进入会议</button>
<button onClick={leaveRoom}>离开房间</button>
<button onClick={publishMicrophoneCamera}>获取摄像头麦克风</button>
</div>
<div ref={videoRef} id="video-box" className="video-box">
{openCamer ?
<div style={{ marginTop: 10 }} className="video" id="rong-video-box">
<div
className="avatar">
</div>
</div>:""
}
</div>
<div className="operation">
<div className="icon">
<CameraOutlined style={{
width: 50,
}} />
<div onClick={disable}>关闭摄像头</div>
</div>
<div className="icon"><AudioOutlined
style={{
width: 50,
}}/> <div>解除静音</div></div>
<div className="icon"><LaptopOutlined
style={{
width: 50,
}}/> <div>共享屏幕</div></div>
</div>
</div>
</>
)
}