记录下接入融云视频通话的 好日子
集成融云RongCloud视频通话功能PC端,vue2.0接入融云视频通话和语音通话
vue2.0和vue3.0接入的方式几乎一样,改变下写法就好
js引入,在index.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>
连接融云,需要调用官方API接口,接口请求request封装进行拦截,记得引入import sha1 from ‘./SHA1.js’,
if (config.url.includes('RongYunsAPI')) {
let NonceV = Math.floor(Math.random() * 10)
let TimestampV = +new Date()
let SignatureV = 你的融云密钥 + NonceV + TimestampV
config.headers["App-Key"] = '你的融云key'
config.headers["Nonce"] = NonceV
config.headers["Timestamp"] = TimestampV
config.headers["Signature"] = sha1(SignatureV)
}
// 每个项目的封装请求方式不同,请求的方式随意贴一个作为参考
export const rongyunToken = (params) => {
return request({
url: `https://api-cn.ronghub.com/user/getToken.json`,
method: "post",
data: params
});
};
// 在需要连接的界面进行方法请求
//phone 为需要登录的用户ID,电话号码最好
rongyunToken (`userId=${phone}&name=${name}`).then((res) => {
console.log(res);
在这里再进行融云的连接方法
this.tokens = res.data.token;
connectIM()
});
vue页面代码,这里是自动连接,如果不需要自动连接融云就去掉this.GetuserInfo();方法
<template>
<div id="RongYun">
<div class="rong-container">
<div id="rongUser" class="rong-user" >
<span>用户 ID:</span>
<span id="rongUserId"></span>
</div>
<div class="rong-im" id="rongIM" >
<p>请先进行 IM 连接</p>
<!--appkey-->
<div class="im-item">
<label>App Key</label>
<!-- value="pwe86ga5pjno6" -->
<input
type="text"
id="appkey"
:value="AppKey"
placeholder="请输入 App Key"
/>
<p>
必填;可通过
<a target="_blank" href="https://developer.rongcloud.cn/"
>[开发者后台] -> [服务管理] -> [基本信息]</a
>
获取
</p>
</div>
<!--token-->
<div class="im-item">
<label>Token</label>
<input
type="text"
id="token"
:value="tokens"
placeholder="请输入 Token"
/>
<p>
必填;可通过
<a target="_blank" href="https://developer.rongcloud.cn/"
>[开发者后台] -> [服务管理] -> [API 调用] -> [用户服务] -> [获取
token]</a
>
获取
</p>
</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" >
<label>通话类型</label>
<select name="" id="callType" @change="callTypeChange()">
<option value="1">单聊</option>
<option value="3">群聊</option>
</select>
<p>必选</p>
</div>
<!--mediaType-->
<div class="param-item" >
<label>媒体类型</label>
<select name="" id="callMediaType" @change="callMediaTypeChange()">
<option value="1">音频</option>
<option value="2">音视频</option>
</select>
<p>必选</p>
</div>
<!--targetId-->
<div id="paramPrivate" 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" 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" >
<!-- <div id="">通话已结束,点击可再次呼起</div> -->
<div class="btn-call" @click="call()"></div>
</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 "@/api/paishuiSys/patrol/patrolMange/wayManage";
import { getUserInfo } from "@/api/system/user";
/**
* call 主要相关逻辑
*/
const { ConversationType } = RongIMLib;
const { RCCallMediaType, RCCallErrorCode } = RCCall;
// CallSession 实例
let callSession;
// Call 呼叫类型
let callType = ConversationType.PRIVATE;
// Call 媒体类型
let mediaType = RCCallMediaType.AUDIO;
// RTC 实例
let rtcClient;
// CallLib 实例
// let callClient;
let 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;");
},
};
let RCCallView = {
connectedIM: () => {
// RCDom.show("rongUser");
},
readyToCall: () => {
RCDom.hide("rongIM");
RCDom.show("rongCall");
},
outgoing: () => {
RCDom.hide("callParam");
RCDom.hide("callBtn");
RCDom.show("hungupBtn");
},
incomming: () => {
RCDom.hide("callParam");
RCDom.hide("callBtn");
RCDom.show("acceptBtn");
RCDom.show("hungupBtn");
},
inTheCall: () => {
RCDom.hide("acceptBtn");
RCDom.hide("callParam");
RCDom.show("hungupBtn");
},
end: () => {
RCDom.show("callParam");
RCDom.show("callBtn");
RCDom.hide("acceptBtn");
RCDom.hide("hungupBtn");
},
};
export default {
components: {},
props: {
Iphone: { //呼叫号码
type: String,
default: () => "",
},
getmediaType: { //类型
type: Number,
default: () => 2,
},
},
data() {
return {
imClient: null,
callClient: null,
targetIdvalue: "123",
AppKey: "你的融云key",
UserList: "11,22",
tokens: "",
DengLuBTN: null,
};
},
watch: {
Iphone: {
handler(val) {
console.log(val);
if (val) {
this.targetIdvalue = val;
}
},
// immediate: true
},
},
computed: {},
created() {},
mounted() {
// this.GetuserInfo();
},
methods: {
// GetuserInfo 获取用户信息
GetuserInfo() {
getUserInfo().then((res) => {
console.log(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();
});
}
});
},
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);
},
// im
/**
* 初始化、链接 IM 相关逻辑
*/
// IM 实例
connectIM() {
const appkey = RCDom.get("appkey").value;
const token = RCDom.get("token").value;
const navi = 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) => {
RCCallView.connectedIM();
RCCallView.readyToCall();
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 链接失败,请检查网络后再试");
});
},
// call
/**
* RTC 初始化
* 在 IM 初始化后进行初始化 (具体位置:im.js)
*/
initRTC() {
const mediaServer = RCDom.get("mediaServer").value;
rtcClient = this.imClient.install(window.RCRTC.installer, {
mediaServer: mediaServer || undefined,
timeout: 30 * 1000,
logLevel: window.RCEngine.LogLevel.DEBUG,
});
},
/**
* CallLib 初始化
* 在 IM 初始化后进行初始化 (具体位置:im.js)
*/
initCall() {
this.callClient = this.imClient.install(window.RCCall.installer, {
rtcClient: rtcClient,
onSession: (session) => {
callSession = session;
mediaType = session.getMediaType();
this.registerCallSessionEvent(callSession);
this.RCToast(`收到 ${session.getCallerId()} 的通话邀请`);
RCCallView.incomming();
},
onSessionClose: (session, summary) => {
this.RCToast("通话已结束");
RCCallView.end();
this.removeVideoEl();
},
});
console.log(this.callClient);
},
/**
* 通话类型监听
*/
callTypeChange() {
const callTypeDom = RCDom.get("callType");
callType = Number(callTypeDom.value);
console.log(callType);
if (callType === ConversationType.GROUP) {
RCDom.showBlock("paramGroupId");
RCDom.showBlock("paramInvitedIds");
RCDom.hide("paramPrivate");
} else {
RCDom.hide("paramGroupId");
RCDom.hide("paramInvitedIds");
// RCDom.showBlock("paramPrivate");
}
},
/**
* 媒体类型监听
*/
callMediaTypeChange() {
const mediaTypeDom = RCDom.get("callMediaType");
mediaType = Number(mediaTypeDom.value);
console.log(mediaType);
},
/**
* CallSession 事件
*/
getCallSessionEvent() {
return {
onRinging: (sender) => {
this.RCToast(`收到 ${sender.userId} 振铃`);
},
onAccept: (sender) => {
this.RCToast(`${sender.userId} 已接听`);
},
onHungup: (sender) => {
this.RCToast(`${sender.userId} 已挂断`);
// 群组中移除相应节点
const videoViewDom = RCDom.get("videoView");
const videoDom = RCDom.get(`video-${sender.userId}`);
videoDom && videoViewDom.removeChild(videoDom);
},
onTrackReady: (track) => {
this.appendVideoEl(track);
if (!track.isLocalTrack()) {
this.RCToast("通话已建立");
RCCallView.inTheCall();
}
},
onMemberModify: (sender) => {
console.log(sender);
},
onMediaModify: (sender) => {
console.log(sender);
},
onAudioMuteChange: (muteUser) => {
console.log(muteUser);
},
onVideoMuteChange: (muteUser) => {
console.log(muteUser);
},
};
},
/**
* callSession 事件注册
*/
registerCallSessionEvent(session) {
const events = this.getCallSessionEvent();
session.registerSessionListener(events);
},
/**
* callSession 呼叫
*/
call() {
mediaType = this.getmediaType;
const events = this.getCallSessionEvent();
const isPrivateCall = callType === ConversationType.PRIVATE;
const params = {
targetId: RCDom.get(`${isPrivateCall ? "targetId" : "groupId"}`).value,
mediaType: mediaType,
listener: events,
};
if (isPrivateCall) {
if (!RCDom.get("targetId").value) {
this.RCToast("请输入对方 ID");
return;
}
this.privateCall(params);
} else {
if (!RCDom.get("groupId").value) {
this.RCToast("请输入群组 ID");
return;
}
if (!RCDom.get("userIds").value) {
this.RCToast("请输入被邀请者 ID");
return;
}
this.groupCall(params);
}
},
/**
* 单呼
*/
privateCall(params) {
this.callClient.call(params).then(({ code, session }) => {
if (code === RCCallErrorCode.SUCCESS) {
this.registerCallSessionEvent(session);
callSession = session;
RCCallView.outgoing();
} else {
this.RCToast(`呼叫失败,错误原因:${code}`);
}
});
},
/**
* 群呼
*/
groupCall(params) {
params.userIds = (RCDom.get("userIds").value || []).split(",");
this.callClient.callInGroup(params).then(({ code, session }) => {
if (code === RCCallErrorCode.SUCCESS) {
this.registerCallSessionEvent(session);
callSession = session;
RCCallView.outgoing();
} else {
const reason =
code === RCCallErrorCode.NOT_IN_GROUP ? "当前用户未加入群组" : code;
this.RCToast(`呼叫失败,错误原因:${reason}`);
this.removeVideoEl();
}
});
},
/**
* 接听当前 callSession
*/
accept() {
callSession.accept().then(({ code }) => {
if (code === RCCallErrorCode.SUCCESS) {
this.RCToast("接听成功");
} else {
this.RCToast(`接听失败,错误原因:${code}`);
}
});
},
/**
* 挂断当前 callSession
*/
hungup() {
if (callSession) {
callSession.hungup().then(({ code }) => {
if (code === RCCall.RCCallErrorCode.SUCCESS) {
this.RCToast("挂断成功");
} else {
this.RCToast(`挂断失败,错误原因:${code}`);
}
});
}
},
/**
* video 视图渲染
*/
appendVideoEl(track) {
console.log(track);
const container = 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">ID: ${uid}</span>
<span class="video-media-type">${mediaType === 1 ? "音频" : ""}</span>
<video id="${uid}" controls
preload="auto"></video>`;
node.innerHTML = videoTpl;
node.classList = "video-item";
const rongCall = document.getElementById("rongCall");
const btndiv = document.getElementById("btn-div");
container.appendChild(node);
track.play();
} else {
const videoEl = RCDom.get(track.getUserId());
track.play(videoEl);
}
},
/**
* 通话结束后,清除所有 video 标签
*/
removeVideoEl() {
RCDom.get("videoView").innerHTML = "";
},
},
beforeDestroy() {
this.hungup();
},
};
</script>
<style lang="scss" scoped>
@import "./rongyun.css";
#RongYun {
width: 100%;
height: 100%;
}
.rong-call input {
color: #000 !important;
}
input:-internal-autofill-selected {
-webkit-text-fill-color: #000 !important;
}
</style>
SHA1.js 加密
//SHA1 加密
function encodeUTF8(s) {
var i, r = [],
c, x;
for (i = 0; i < s.length; i++)
if ((c = s.charCodeAt(i)) < 0x80) r.push(c);
else if (c < 0x800) r.push(0xC0 + (c >> 6 & 0x1F), 0x80 + (c & 0x3F));
else {
if ((x = c ^ 0xD800) >> 10 == 0) //对四字节UTF-16转换为Unicode
c = (x << 10) + (s.charCodeAt(++i) ^ 0xDC00) + 0x10000,
r.push(0xF0 + (c >> 18 & 0x7), 0x80 + (c >> 12 & 0x3F));
else r.push(0xE0 + (c >> 12 & 0xF));
r.push(0x80 + (c >> 6 & 0x3F), 0x80 + (c & 0x3F));
};
return r;
};
// 字符串加密成 hex 字符串
function sha1(s) {
var data = new Uint8Array(encodeUTF8(s))
var i, j, t;
var l = ((data.length + 8) >>> 6 << 4) + 16,
s = new Uint8Array(l << 2);
s.set(new Uint8Array(data.buffer)), s = new Uint32Array(s.buffer);
for (t = new DataView(s.buffer), i = 0; i < l; i++) s[i] = t.getUint32(i << 2);
s[data.length >> 2] |= 0x80 << (24 - (data.length & 3) * 8);
s[l - 1] = data.length << 3;
var w = [],
f = [
function () {
return m[1] & m[2] | ~m[1] & m[3];
},
function () {
return m[1] ^ m[2] ^ m[3];
},
function () {
return m[1] & m[2] | m[1] & m[3] | m[2] & m[3];
},
function () {
return m[1] ^ m[2] ^ m[3];
}
],
rol = function (n, c) {
return n << c | n >>> (32 - c);
},
k = [1518500249, 1859775393, -1894007588, -899497514],
m = [1732584193, -271733879, null, null, -1009589776];
m[2] = ~m[0], m[3] = ~m[1];
for (i = 0; i < s.length; i += 16) {
var o = m.slice(0);
for (j = 0; j < 80; j++)
w[j] = j < 16 ? s[i + j] : rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1),
t = rol(m[0], 5) + f[j / 20 | 0]() + m[4] + w[j] + k[j / 20 | 0] | 0,
m[1] = rol(m[1], 30), m.pop(), m.unshift(t);
for (j = 0; j < 5; j++) m[j] = m[j] + o[j] | 0;
};
t = new DataView(new Uint32Array(m).buffer);
for (var i = 0; i < 5; i++) m[i] = t.getUint32(i << 2);
var hex = Array.prototype.map.call(new Uint8Array(new Uint32Array(m).buffer), function (e) {
return (e < 16 ? "0" : "") + e.toString(16);
}).join("");
return hex;
};
export default sha1