其实重点不是单页面代码
注意点:本地调试的时候记得localhost调试哦 IP拨打电话会报错误
然后就是引入的html三个固定的版本js
<!-- 融云相关 -->
<script src="https://cdn.ronghub.com/RongIMLib-4.5.latest.js"></script>
<script src="https://cdn.ronghub.com/RCRTC-5.2.latest.js"></script>
<script src="https://cdn.ronghub.com/RCCall-5.0.latest.js"></script>
然后JS样式就自己写一下咯
融云服务提供的
AppKey: '提供的',
AppSecret: '提供的',
在vue.config里面配置一下转发
"/RongYunsAPI": {
target: "https://api-cn.ronghub.com",
changeOrigin: true,
pathRewrite: {
'^/RongYunsAPI': '' //需要rewrite重写的,
}
},
axios请求头配置
if (config.url.includes('RongYunsAPI')) {
let NonceV = Math.floor(Math.random() * 10)
let TimestampV = +new Date()
let SignatureV = 'AppSecret' + NonceV + TimestampV
config.headers["App-Key"] = 'AppKey'
config.headers["Nonce"] = NonceV
config.headers["Timestamp"] = TimestampV
config.headers["Signature"] = sha1(SignatureV)
}
这一步其实是在融云管网获取key 然后我们省略内置前端跨域去解决这个问题
其实这一步是需要后端去转发成接口比较好
自己封装一下获得token
export const userGetToken = (params) => {
return request({
url: `/RongYunsAPI/user/getToken.json`,
method: "post",
data: params
});
};
<!-- 融云弹框 -->
<div class="RYdia"
v-show="rongyuntype&&TitleIndex!=4&&TitleIndex!=5">
<div class="closeBtn"
@click="rongyuntype=false">X</div>
<RongYunPage v-if="rongyuntype"
:Getphone='GetFXphone'
:getmediaType='getmediaType'
:RYtype='RYtype'></RongYunPage>
</div>
.RYdia {
position: absolute;
padding-top: 20px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 4;
background: #0b0c0b;
border-radius: 5px;
}
.closeBtn {
position: absolute;
cursor: pointer;
width: 40px;
height: 40px;
line-height: 40px;
right: 0px;
top: 0px;
color: #fff;
text-align: center;
}
<template>
<div id="RongYun">
<div class="rong-container">
<div id="rongUser"
class="rong-user"
v-show="false">
<span>用户 ID:</span>
<span id="rongUserId"></span>
</div>
<div class="rong-im"
v-show="false"
id="rongIM">
<p>请先进行 IM 连接</p>
<!--appkey-->
<div class="im-item">
<label>App Key</label>
<input type="text"
id="appkey"
:value="AppKey"
placeholder="请输入 App Key" />
</div>
<!--token-->
<div class="im-item">
<label>Token</label>
<input type="text"
id="token"
:value="tokens"
placeholder="请输入 Token" />
</div>
<!--navi-->
<div class="im-item">
<label>Navi</label>
<input type="text"
id="navi"
placeholder="请输入 Navi 地址" />
<p>非必填;私有云环境必填</p>
</div>
<!--mediaServer-->
<div class="im-item">
<label>MediaServer</label>
<input type="text"
id="mediaServer"
placeholder="请输入 MediaServer 地址" />
<p>非必填;音视频服务地址</p>
</div>
<!--连接-->
<div class="im-item">
<button id="DengLuBTN"
ref="DengLuBTN"
@click="connectIM()">
连接
</button>
</div>
</div>
<div class="rong-call"
id="rongCall"
style="display: none">
<!--呼叫选项-->
<div id="callParam"
class="call-param">
<!--选择通话类型-->
<div class="param-item"
v-show="false">
<label>通话类型</label>
<select name=""
id="callType"
@change="callTypeChange()">
<option value="1">单聊</option>
<option value="3">群聊</option>
</select>
<p>必选</p>
</div>
<!--this.mediaType-->
<div class="param-item"
v-show="false">
<label>媒体类型</label>
<select name=""
id="callMediaType"
@change="callMediaTypeChange()">
<option value="1">音频</option>
<option value="2">音视频</option>
</select>
<p>必选</p>
</div>
<!--targetId-->
<div id="paramPrivate"
v-show="false"
class="param-item">
<label>对方 ID</label>
<input id="targetId"
type="text"
placeholder="对方 userId"
v-model="targetIdvalue" />
<p>
必填;对方的 userId,可通过<a target="_blank"
href="https://developer.rongcloud.cn/">[开发者后台] -> [服务管理] -> [API 调用] -> [用户服务] -> [获取
token]</a>获取,且登录成功
</p>
</div>
<!--targetId-->
<div id="paramGroupId"
class="param-item"
style="display: none">
<label>群组 ID</label>
<input id="groupId"
type="text"
value="q1"
placeholder="群组 ID" />
<p>
必填;可通过
<a target="_blank"
href="https://developer.rongcloud.cn/">[开发者后台] -> [服务管理] -> [API 调用] -> [群组服务] ->
[加入群组]</a>
加入群组后获取
</p>
</div>
<!--userIds 只有群显示-->
<div id="paramInvitedIds"
class="param-item"
style="display: none">
<label>被邀请者 ID</label>
<input id="userIds"
type="text"
placeholder="多个 userId 用英文半角逗号分开"
:value="UserList" />
<p>
必填;需加入群后,方可收到邀请。多个 userId 用英文半角逗号分开
</p>
</div>
</div>
<!--通话视图展示-->
<div id="videoView"
class="video-view"></div>
<!--通话操作按钮-->
<div class="opt-btn"
id="btn-div">
<!-- <button id="callBtn"
class="btn-call"
@click="call()">呼叫</button> -->
<div id="callBtn"
class="btn-call"
@click="call()"></div>
<!-- <button id="acceptBtn"
class="btn-accept"
@click="accept()">
接听 -->
<div id="acceptBtn"
class="btn-accept"
@click="accept()"></div>
<div id="hungupBtn"
class="btn-hungup"
@click="hungup()">
</div>
</div>
</div>
</div>
<div class="toast-wrap">
<span class="toast-msg"></span>
</div>
</div>
</template>
<script>
// import { userGetToken } from "@/services";
// import configs from "@/config.js";
import {
userGetToken
} from "@/api/floodControlDrainage/floodControlDrainage.js";
import { mapGetters } from "vuex";
import { getUserInfo } from "@/api/system/user";
export default {
name: "RongYun",
props: {
Getphone: {
type: String,
default: () => ""
},
RYtype: {
type: String,
default: () => ""
},
getmediaType: {
type: Number,
default: () => 1
}
},
data () {
return {
targetIdvalue: '',
userinfo: {},
names: '姓名',
numbs: 'XXX',
AppKey: 'AppKey',
tokens: "",
DengLuBTN: null,
value: "",
UserList: "11,22",
imClient: '',
callSession: '',
callType: RongIMLib.ConversationType.PRIVATE,
mediaType: RCCall.RCCallMediaType.AUDIO,
rtcClient: undefined,
callClient: undefined,
RCDom: {
get: (id) => {
return document.getElementById(id);
},
show: (id) => {
var rongIMDom = document.getElementById(id);
rongIMDom.setAttribute("style", "display:block;");
},
showBlock: (id) => {
var rongIMDom = document.getElementById(id);
rongIMDom.setAttribute("style", "display:block;");
},
hide: (id) => {
var rongIMDom = document.getElementById(id);
rongIMDom.setAttribute("style", "display:none;");
},
},
RCToast: (msg) => {
setTimeout(function () {
document
.getElementsByClassName("toast-wrap")[0]
.getElementsByClassName("toast-msg")[0].innerHTML = msg;
var toastTag = document.getElementsByClassName("toast-wrap")[0];
toastTag.className = toastTag.className.replace("toastAnimate", "");
setTimeout(function () {
toastTag.className = toastTag.className + " toastAnimate";
}, 10);
}, 10);
},
RCCallView: {
connectedIM: () => {
// this.RCDom.show("rongUser");
},
readyToCall: () => {
this.RCDom.hide("rongIM");
this.RCDom.show("rongCall");
},
outgoing: () => {
this.RCDom.hide("callParam");
this.RCDom.hide("callBtn");
this.RCDom.show("hungupBtn");
},
incomming: () => {
this.RCDom.hide("callParam");
this.RCDom.hide("callBtn");
this.RCDom.show("acceptBtn");
this.RCDom.show("hungupBtn");
},
inTheCall: () => {
this.RCDom.hide("acceptBtn");
this.RCDom.hide("callParam");
this.RCDom.show("hungupBtn");
},
end: () => {
this.RCDom.show("callParam");
this.RCDom.show("callBtn");
this.RCDom.hide("acceptBtn");
this.RCDom.hide("hungupBtn");
},
}
}
},
computed: {},
methods: {
connectIM () {
const appkey = this.RCDom.get("appkey").value;
const token = this.RCDom.get("token").value;
const navi = this.RCDom.get("navi").value;
if (!appkey) {
this.RCToast("请输入 App Key");
return;
}
if (!token) {
this.RCToast("请输入 Token");
return;
}
// IM 客户端初始化
this.imClient = RongIMLib.init({
appkey,
navigators: navi ? [navi] : undefined,
logLevel: 1,
});
// 初始化 RTC CallLib
this.initRTC();
this.initCall();
this.imClient.watch({
// 监听 IM 连接状态变化
status (evt) {
console.log("connection status change:", evt.status);
},
});
// this.RCToast("正在链接 IM ... ☕️");
this.imClient
.connect({ token })
.then((user) => {
this.RCCallView.connectedIM();
this.RCCallView.readyToCall();
this.RCDom.get("rongUserId").innerText = user.id;
// this.RCToast(`用户 ${user.id} IM 链接成功 ✌🏻`);
this.callTypeChange();
this.callMediaTypeChange();
setTimeout(() => {
this.call()
}, 0);
})
.catch((error) => {
console.log(error);
this.RCToast("IM 链接失败,请检查网络后再试");
});
},
/**
* RTC 初始化
* 在 IM 初始化后进行初始化 (具体位置:im.js)
*/
initRTC () {
const mediaServer = this.RCDom.get("mediaServer").value;
this.rtcClient = this.imClient.install(RCRTC.installer, {
mediaServer: mediaServer || undefined,
timeout: 30 * 1000,
logLevel: window.RCEngine.LogLevel.DEBUG,
});
console.log('this.rtcClient', this.rtcClient);
},
/**
* CallLib 初始化
* 在 IM 初始化后进行初始化 (具体位置:im.js)
*/
initCall () {
this.callClient = this.imClient.install(window.RCCall.installer, {
rtcClient: this.rtcClient,
onSession: (session) => {
this.callSession = session;
this.mediaType = session.getMediaType();
this.registerCallSessionEvent(this.callSession);
this.RCToast(`收到 ${session.getCallerId()} 的通话邀请`);
// this.RCCallView.incomming();
},
onSessionClose: (session, summary) => {
this.RCToast("通话已结束");
this.RCCallView.end();
this.removeVideoEl();
},
});
console.log('this.callClient', this.callClient);
},
/**
* 通话类型监听
*/
callTypeChange () {
// debugger;
const callTypeDom = this.RCDom.get("callType");
this.callType = Number(callTypeDom.value);
if (this.callType === RongIMLib.ConversationType.GROUP) {
this.RCDom.showBlock("paramGroupId");
this.RCDom.showBlock("paramInvitedIds");
this.RCDom.hide("paramPrivate");
} else {
this.RCDom.hide("paramGroupId");
this.RCDom.hide("paramInvitedIds");
// this.RCDom.showBlock("paramPrivate");
}
// // 默认多人通话
// this.RCDom.showBlock("paramGroupId");
// this.RCDom.showBlock("paramInvitedIds");
// this.RCDom.hide("paramPrivate");
},
/**
* 媒体类型监听
*/
callMediaTypeChange () {
// debugger;
const mediaTypeDom = this.RCDom.get("callMediaType");
this.mediaType = Number(mediaTypeDom.value);
// this.mediaType = 2;
},
/**
* CallSession 事件
*/
getCallSessionEvent () {
return {
onRinging: (sender) => {
// 当远端用户已开始响铃, 该函数有 2 个参数: sender 是发送者,session 是通话实例。这时用户可以在业务层做响铃的 UI 展示。
this.RCToast(`收到 ${sender.userId} 振铃`);
message.info(`收到 ${sender.userId} 振铃`);
},
onAccept: (sender) => {
// 当远端用户已同意接听, 该函数有 2 个参数: sender 是发送者,session 是通话实例。这时用户可以把 UI 的‘响铃’变成‘通话中’。
this.RCToast(`${sender.userId} 已接听`);
},
onHungup: (sender) => {
// 当有远端用户挂断, 该函数有 3 个参数: sender 是发送者,reason 是挂断原因,session 是通话实例。这时用户可以在 UI 层提示‘xxx已挂断’。
this.RCToast(`${sender.userId} 已挂断`);
// 群组中移除相应节点
const videoViewDom = this.RCDom.get("videoView");
const videoDom = this.RCDom.get(`video-${sender.userId}`);
videoDom && videoViewDom.removeChild(videoDom);
},
onTrackReady: (track) => {
// 当本端资源或远端资源已获取, 该函数有 2 个参数:track 是本端资源或远端资源, session 是通话实例。这时用户可以用拿到的 track 播放音频、视频。
this.appendVideoEl(track);
if (!track.isLocalTrack()) {
this.RCToast("通话已建立");
this.RCCallView.inTheCall();
}
},
onMemberModify: (sender, invitedUsers) => {
// 群组通话中有其他人被邀请加入, 该函数有 3 个参数: sender 是发送者,invitedUsers 是被邀请的用户列表, session 是通话实例。这时用户可以在 UI 层提示‘xxx加入通话’。
},
onMediaModify: (sender) => {
// 通话类型改变时触发, 该函数有3个参数: sender 是发送者,mediaType 通话类型, session 是通话实例。这时用户可以在 UI 层提示‘已降级成音频通话’。
},
onAudioMuteChange: (muteUser) => {
// 对方静音后触发, 该函数有2个参数: muteUser 是已静音的用户, session 是通话实例。这时用户可以在 UI 层提示‘xxx已静音’。
},
onVideoMuteChange: (muteUser) => {
// 对方禁用视频后触发, 该函数有2个参数: muteUser 是已禁用视频的用户, session 是通话实例。这时用户可以在 UI 层提示‘xxx已禁用视频’。
},
};
},
/**
* callSession 事件注册
*/
registerCallSessionEvent (session) {
const events = this.getCallSessionEvent();
session.registerSessionListener(events);
},
/**
* callSession 呼叫
*/
call () {
this.mediaType = this.getmediaType
console.log('this.getmediaType', this.getmediaType);
const events = this.getCallSessionEvent();
const isPrivateCall = this.callType === RongIMLib.ConversationType.PRIVATE;
const params = {
targetId: this.RCDom.get(`${isPrivateCall ? "targetId" : "groupId"}`).value,
mediaType: this.mediaType,
listener: events,
};
if (isPrivateCall) {
if (!this.RCDom.get("targetId").value) {
this.RCToast("请输入对方 ID");
return;
}
this.privateCall(params);
} else {
if (!this.RCDom.get("groupId").value) {
this.RCToast("请输入群组 ID");
return;
}
if (!this.RCDom.get("userIds").value) {
this.RCToast("请输入被邀请者 ID");
return;
}
this.groupCall(params);
}
},
/**
* 单呼
*/
privateCall (params) {
console.log(this.callClient, params, "this.callClient");
this.callClient.call(params).then(({ code, session }) => {
console.log(code, session);
if (code === RCCall.RCCallErrorCode.SUCCESS) {
this.registerCallSessionEvent(session);
this.callSession = session;
this.RCCallView.outgoing();
} else {
this.RCToast(`呼叫失败,错误原因:${code}`);
}
});
},
/**
* 群呼
*/
groupCall (params) {
params.userIds = (this.RCDom.get("userIds").value || []).split(",");
this.callClient.callInGroup(params).then(({ code, session }) => {
if (code === RCCall.RCCallErrorCode.SUCCESS) {
this.registerCallSessionEvent(session);
this.callSession = session;
this.RCCallView.outgoing();
} else {
const reason =
code === RCCall.RCCallErrorCode.NOT_IN_GROUP ? "当前用户未加入群组" : code;
this.RCToast(`呼叫失败,错误原因:${reason}`);
removeVideoEl();
}
});
},
/**
* 接听当前 callSession
*/
accept () {
this.callSession.accept().then(({ code }) => {
if (code === RCCall.RCCallErrorCode.SUCCESS) {
this.RCToast("接听成功");
} else {
this.RCToast(`接听失败,错误原因:${code}`);
}
});
},
/**
* 挂断当前 callSession
*/
hungup () {
if (this.callSession) {
this.callSession.hungup().then(({ code }) => {
if (code === RCCall.RCCallErrorCode.SUCCESS) {
this.RCToast("挂断成功");
if (this.RYtype == 'FX') {
this.bus.$emit('closeFX')
}
} else {
this.RCToast(`挂断失败,错误原因:${code}`);
}
});
}
},
/**
* video 视图渲染
*/
appendVideoEl (track) {
const container = this.RCDom.get("videoView");
if (track.isAudioTrack()) {
const uid = track.getUserId();
const node = document.createElement("div");
node.setAttribute("id", `video-${uid}`);
const videoTpl = `<span class="video-user-id">${uid}</span>
<video id="${uid}"></video>`;
// const videoTpl = `<span class="video-user-id">${uid}</span>
// <span class="video-media-type">${this.mediaType === 1 ? "音频" : ""}</span>
// <video id="${uid}"></video>`;
node.innerHTML = videoTpl;
node.classList = "video-item";
const rongCall = document.getElementById('rongCall')
const btndiv = document.getElementById('btn-div')
container.appendChild(node);
// rongCall.insertBefore(node, btndiv);
track.play();
} else {
const videoEl = this.RCDom.get(track.getUserId());
track.play(videoEl);
}
},
/**
* 通话结束后,清除所有 video 标签
*/
removeVideoEl () {
this.RCDom.get("videoView").innerHTML = "";
},
// GetuserInfo 获取用户信息
GetuserInfo () {
getUserInfo().then(res => {
this.userinfo = res.data.data;
console.log(this.userinfo);
this.numbs = res.data.data.phone
this.names = res.data.data.name
this.$nextTick(() => {
this.Dengl()
})
});
},
// 默认登录,获取当前电脑用户PCUser的通话token
Dengl () {
userGetToken(`userId=${this.numbs}&name=${this.names}`).then(res => {
console.log(res);
if (res.data.code == 200) {
console.log('res.data.token', res.data.token);
this.tokens = res.data.token;
this.$nextTick(() => {
this.connectIM()
})
}
})
}
},
// computed: {
// ...mapGetters([
// "userInfo",
// ]),
// },
watch: {
Getphone: {
handler (val) {
if (val) {
this.targetIdvalue = val
}
},
immediate: true
}
},
mounted () {
this.GetuserInfo()
},
beforeDestroy () {
this.hungup()
}
};
</script>
<style lang="less" scoped>
@import "./css/style.css";
#RongYun {
width: 100%;
height: 100%;
}
.rong-call input {
color: #000 !important;
}
input:-internal-autofill-selected {
-webkit-text-fill-color: #000 !important;
}
</style>
下面是基本的CSS样式 可以自己写
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
a:link {color: #2da2ea} /* 未被访问的链接 蓝色 */
a:visited {color: #2da2ea} /* 已被访问过的链接 蓝色 */
a:hover {color: #2da2ea} /* 鼠标悬浮在上的链接 蓝色 */
a:active {color: #2da2ea} /* 鼠标点中激活链接 蓝色 */
#RongYun {
border-radius: 5px;
}
.rong-container {
background: #0b0c0b;
min-height: 100%;
color: #FFF;
font-size: 18px;
width: 350px;
}
.rong-container .rong-title {
margin: 0;
padding: 25px 0 10px;
text-align: center;
color: #fff;
font-size: 30px;
}
.rong-container .rong-user {
display: none;
text-align: center;
}
.rong-container button {
width: 150px;
height: 30px;
border-radius: 2px;
background: #2da2ea;
border: none;
color: #fff;
box-shadow: 1px 1px 6px #00000069;
cursor: pointer;
}
.rong-container button:active {
width: 150px;
height: 30px;
border-radius: 2px;
background: #0f93e4;
border: none;
color: #fff;
box-shadow: 1px 1px 6px #00000069;
cursor: pointer;
/* opacity: .8; */
}
.rong-container .rong-im {
background: #0b0c0b;
width: 850px;
border-radius: 2px;
display: inline-block;
}
.rong-container .rong-im .im-item {
text-align: center;
margin-bottom: 10px;
height: 50px;
position: relative;
}
.rong-container .rong-im .im-tips {
width: 100px;
}
.rong-container .rong-im .im-item label {
display: inline-block;
width: 150px;
text-align: right;
}
.rong-container .rong-im .im-item input {
border: 1px solid #ccc;
border-radius: 2px;
height: 25px;
width: 600px;
}
.rong-container .rong-im .im-item p {
margin: 0px;
display: inline-block;
position: absolute;
left: 200px;
top: 30px;
color: #fff;
font-size: 13px;
}
.rong-container .rong-call {
text-align: center;
margin-bottom: 10px;
}
.rong-container .rong-call .call-param {
/* padding: 10px; */
background: #0b0c0b;
width: 650px;
border-radius: 2px;
display: inline-block;
}
.rong-container .rong-call select {
background: #4285F4;
width: 450px;
height: 30px;
border-radius: 2px;
border: none;
color: #fff;
text-align: center;
}
.rong-container .rong-call label {
display: inline-block;
width: 150px;
text-align: right;
}
.rong-container .rong-call input {
border: 1px solid #ccc;
border-radius: 2px;
height: 25px;
width: 450px;
}
.rong-container .rong-call .call-item,
.rong-container .rong-call .call-param,
.rong-container .rong-call .opt-btn {
background: #0b0c0b;
margin-bottom: 10px;
}
.rong-container .rong-call .opt-btn {
margin-bottom: 10px;
display: flex;
justify-content: center;
}
.rong-container .rong-call .call-param .param-item {
margin-bottom: 15px;
}
.rong-container .rong-call .call-param .param-item {
height: 50px;
position: relative;
}
.rong-container .rong-call .call-param .param-item p {
margin: 0px;
display: inline-block;
position: absolute;
left: 180px;
top: 30px;
color: #fff;
font-size: 13px;
text-align: left;
}
.rong-container .rong-call .opt-btn .btn-accept, .rong-container .rong-call .opt-btn .btn-hungup {
display: none;
}
.rong-container .rong-call .video-view .video-item {
width: 320px;
height: 240px;
/* background: #1a7cb9; */
position: relative;
border-radius: 2px;
display: inline-block;
margin: 8px;
}
.rong-container .rong-call .video-view {
/* display: flex; */
}
.rong-container .rong-call .video-view .video-item .video-user-id {
/* background: #a8d8ea9c; */
position: absolute;
left: 2px;
top: -20px;
/* left: 2px; */
/* left: 50%; */
/* top: 50%; */
/* transform: translate(-50%,-50%); */
display: inline-block;
padding: 0 5px;
border-radius: 2px;
}
.rong-container .rong-call .video-view .video-item .video-media-type {
position: absolute;
top: 41%;
right: 42%;
}
.rong-container .rong-call .video-view video {
width: 320px;
height: 240px;
border-radius: 2px;
}
/* toast */
.toast-wrap {
display: inline-block;
opacity: 0;
position: fixed;
top: -30px;
color: #fff;
width: 100%;
text-align: center;
}
.toast-msg {
background-color: rgba(89, 148, 236, 0.918);
border-radius: 2px;
padding: 10px 15px;
}
.toastAnimate {
animation: toastKF 2s;
}
@keyframes toastKF {
0% {opacity: 0;}
25% {opacity: 1; z-index: 9999}
50% {opacity: 1; z-index: 9999}
75% {opacity: 1; z-index: 9999}
100% {opacity: 0; z-index: 0}
}
.btn-hungup {
cursor: pointer;
width: 32px;
height: 32px;
background: url("~@/assets/images/siping-fangxun/call-off.png") no-repeat;;
background-size: 100% 100%;
}
.btn-call,
.btn-accept {
cursor: pointer;
width: 32px;
height: 32px;
background: url("~@/assets/images/siping-fangxun/call.png") no-repeat;;
background-size: 100% 100%;
}
.btn-hungup:hover,
.btn-call:hover,
.btn-accept{
opacity: 0.7;
}
#videoView {
border-radius: 5px;
}