融雲集成一個聊天室頁面(vue版本)
首先,說一下使用情況。因為需求,需要做一個聊天室頁面,因為不是專門的點對點聊天,是類似直播,但是是文字直播平臺的那種。現在一般的課堂,可能會需要這種。分為2個端,一個是講師端,一個是使用者端。講師端可能是單獨的APP。使用者端的頁面可能是內嵌到專門的APP,或者是微信公眾平臺。我這次做的就是一個使用者端。講師端由原生來寫,因為需要H5頁面能相容微信還有在手機端都能用。
然後,說明一下,用了vue的UI框架是vux(下次可能就不會用這個了,其中Scroller竟然不維護了!),用的webpack打包。
一切就緒,畫頁面,就是類似直播平臺。用vux快速畫好。
先確認需求,1.需要老師能撤回,然後客戶端同時將資料從陣列刪除。2.能播放語音。3.講師區是下拉載入更多,觀眾區是上拉載入更多。4.因為ios的輸入法有聯想詞一欄,所以為了能正常輸入文字,最後決定使用一個彈框,彈框裡面有textarea進行輸入文字。5.講師發的圖片。需要客戶端能點開,變成大圖。(簡易版本,暫時無雙指放大和雙擊放大)。6.講師可以禁言。而客戶端需要相應的改樣式,禁止輸入。差不多就是這些需求。
開始準備:
1.去融雲官網註冊賬號。其實對我們前端來說,只是需要一個appkey。
2.拿到appkey,就相當於拿到大門鑰匙。這個時候可以開始寫js檔案進行連線了。po上一張檔案的內容。
主要是在views裡面的index.vue寫的檔案。因為頁面只有一個,所以我的路由寫在了main.js(別說我沒寫路由,哈哈哈哈)。
好了,接下來我們要先進行連線。那麼我寫在utils.js就是從官網上扣下來的js檔案,順便封裝一下,為了能在index.vue中取值,這邊添加了一個回撥函式。
export default { init(params, callbacks, modules) { var appKey = params.appKey; var token = params.token; // var navi = params.navi || ""; modules = modules || {}; var RongIMLib = modules.RongIMLib || window.RongIMLib; var RongIMClient = RongIMLib.RongIMClient; // var protobuf = modules.protobuf || null; var config = {}; var dataProvider = null; var imClient = params.imClient; if (imClient) { dataProvider = new RongIMLib.VCDataProvider(imClient); } RongIMLib.RongIMClient.init(appKey, dataProvider, config); //語音播放初始化 RongIMLib.RongIMVoice.init(); var instance = RongIMClient.getInstance(); // 連線狀態監聽器 RongIMClient.setConnectionStatusListener({ onChanged: function(status) { // console.log(status); switch (status) { case RongIMLib.ConnectionStatus["CONNECTED"]: case 0: console.log("連線成功"); callbacks.getInstance && callbacks.getInstance(instance); break; case RongIMLib.ConnectionStatus["CONNECTING"]: case 1: console.log("連線中"); break; case RongIMLib.ConnectionStatus["DISCONNECTED"]: case 2: console.log("當前使用者主動斷開連結"); break; case RongIMLib.ConnectionStatus["NETWORK_UNAVAILABLE"]: case 3: console.log("網路不可用"); break; case RongIMLib.ConnectionStatus["CONNECTION_CLOSED"]: case 4: console.log("未知原因,連線關閉"); break; case RongIMLib.ConnectionStatus["KICKED_OFFLINE_BY_OTHER_CLIENT"]: case 6: alert("使用者賬戶在其他裝置登入,本機會被踢掉線"); break; case RongIMLib.ConnectionStatus["DOMAIN_INCORRECT"]: case 12: console.log("當前執行域名錯誤,請檢查安全域名配置"); break; } } }); //開始連結 RongIMClient.connect( token, { onSuccess: function(userId) { callbacks.getCurrentUser && callbacks.getCurrentUser({ userId: userId }); console.log("連結成功,使用者id:" + userId); }, onTokenIncorrect: function() { console.log("token無效"); }, onError: function(errorCode) { console.log(errorCode); } }, params.userId ); /* 文件:http://www.rongcloud.cn/docs/web.html#3、設定訊息監聽器 注意事項: 1:為了看到接收效果,需要另外一個使用者向本使用者發訊息 2:判斷會話唯一性 :conversationType + targetId 3:顯示訊息在頁面前,需要判斷是否屬於當前會話,避免訊息錯亂。 4:訊息體屬性說明可參考:http://rongcloud.cn/docs/api/js/index.html */ RongIMClient.setOnReceiveMessageListener({ // 接收到的訊息 onReceived: function(message) { // 判斷訊息型別 // console.log("新訊息: " + message.targetId); // console.log(message); // 判斷訊息型別 switch (message.messageType) { case RongIMClient.MessageType.TextMessage: // message.content.content => 訊息內容 break; case RongIMClient.MessageType.VoiceMessage: // message.content.content 格式為 AMR 格式的 base64 碼 break; case RongIMClient.MessageType.ImageMessage: // message.content.content => 圖片縮圖 base64。 // message.content.imageUri => 原圖 URL。 break; case RongIMClient.MessageType.DiscussionNotificationMessage: // message.content.extension => 討論組中的人員。 break; case RongIMClient.MessageType.RichContentMessage: // message.content.content => 文字訊息內容。 // message.content.imageUri => 圖片 base64。 // message.content.url => 原圖 URL。 break; } callbacks.receiveNewMessage && callbacks.receiveNewMessage(message); } }); } };
這樣就算把發動機預熱好了,這個時候,去我們的index.vue開車了。
在inde.vue裡面首先初始化。寫個startInit函式,順手掛載到mounted上。因為初始化,融雲是需要我們傳token的,這個token是融雲的token,那理所當然是需要後臺給我們傳。我們公司是這樣寫的,在進入頁面的時候,先發2個請求,一個是使用者個人資訊請求,一個是聊天室詳情請求。使用者個人資訊分為:這個人登入了,我把驗證訊息給後臺,後臺給我一個token,我去請求融雲。第二種是這個人沒有登入,那我這個時候的驗證訊息是空的,需要後臺去判斷,然後也給我傳一個token,順便需要傳一些其他判斷的依據,告訴我這個人沒有登入,那我就要讓他沒辦法發言。(扯遠了_(:з」∠)_)。拿到token,我就開始初始化。
同時我們要加入聊天室:
startInit(){
let token = getCookie('tk1')
var params = {
appKey: this.appKey,
token: token,
};
const that = this;
var userId = "";
var callbacks = {
getInstance: function (instance) {
//註冊 PersonMessage
var propertys = ["name", "age", "gender"]; // 訊息類中的屬性名。
that.registerMessage("PersonMessage", propertys);
//註冊 ProductMessage
var propertys = ["price", "title", "desc", "images"]; // 訊息類中的屬性名。
that.registerMessage("ProductMessage", propertys);
},
getCurrentUser(userInfo) {
const userId = getCookie('uId')
document.titie = "連結成功;userid=" + userInfo.userId;
//加入聊天室
that.joinChatRoom();
},
// 收到融雲的最新訊息
receiveNewMessage(message) {
// 禁言
if (message.objectName == 'ChatRoom:state') {
if (message.content.message.content.roomState == "BANNED_TO_POST") {
$('.weui-input').attr('placeholder', '全員禁言中,不能發言')
} else {
let rightControl = getCookie('rightControl')
if (rightControl == '1020') {
$('.weui-input').attr('placeholder', '登入之後才能向講師提問')
} else {
$('.weui-input').attr('placeholder', '輸入您的問題')
}
}
}
// 自定義訊息,判斷是否為主持人
let tag
if (message.content.extra) {
tag = JSON.parse(message.content.extra).tag
}
let messageBig1 = {};
// 判斷訊息型別
if (!tag) { //沒有值是觀眾
if (message.objectName == 'RC:TxtMsg') { // 文字資訊
messageBig1.sendTimeStamp = message.sentTime;
messageBig1.sendUserPortrait = JSON.parse(message.content.extra).portrait;
messageBig1.sendUserName = JSON.parse(message.content.extra).userName;
messageBig1.content = message.content.content;
messageBig1.messageId = JSON.parse(message.content.extra).messageId;
that.listBox.unshift(messageBig1)
}
that.totalCount = parseInt(getCookie('totalCount')) + 1;
if (that.totalCount >= 999) {
that.totalCount = "999+"
}
let expireDays = 1000 * 60 * 60;
// 存好討論區的歷史資訊數
setCookie('totalCount', that.totalCount, expireDays)
// 發言之後會滾到上方(vux方法)
that.$nextTick(() => {
let initTop;
if (
document.getElementById("conversationListH").scrollHeight > 300
) {
initTop = -(document.getElementById("conversationListH").scrollHeight - 300);
} else {
initTop = 0;
}
that.$refs.scrollerBottom.reset({
bottom: initTop
});
});
if (!that.chatDiscussion) {
that.hasMsg = true
}
}
else { //有值是主持人
let messageBig = {
messageType: {
code: null
}
};
messageBig.duration = message.content.duration;
messageBig.sendTimeStamp = message.sentTime;
messageBig.sendUserPortrait = JSON.parse(message.content.extra).portrait;
messageBig.sendUserName = JSON.parse(message.content.extra).userName;
messageBig.messageId = JSON.parse(message.content.extra).messageId;
messageBig.content = message.content.content;
if (message.objectName == 'RC:TxtMsg') {
messageBig.messageType.code = 1010;
that.listBox1.push(messageBig)
} else if (message.objectName == 'RC:VcMsg') {
messageBig.messageType.code = 1020;
messageBig.hasRead = false;
that.listBox1.push(messageBig)
} else if (message.objectName == 'RC:ImgMsg') {
messageBig.messageType.code = 1030;
messageBig.attachmentUrl = message.content.imageUri;
that.listBox1.push(messageBig)
}
that.withdraw();
}
// 訊息撤回
if (message.objectName == 'message:recall') {
that.hasMsg = false
let messageIds = message.content.message.content.messageIds;
messageIds.map((item) => {
that.listBox1 = that.listBox1.filter(e => e.messageId !== item)
that.withdraw();
})
}
},
};
utils.init(params, callbacks)
},
這個裡面要注意一點,就是獲取token會是非同步,所以,我最後決定,將這個函式,丟在獲取個人資訊函式裡面,這樣就不會出現非同步了。(大家可以試著寫中介軟體,或者是用其他處理非同步方法。)。因為vue是雙向繫結,所以只需要我改變陣列的值,就會自動出現撤回以及新增資料丟到頁面。對了,這個裡面有個很重要的方法,我還沒說。從案列上扒下來的,老實說,我並不知道為什麼要寫這個函式。這個函式在初始化裡面呼叫了。
registerMessage(type, propertys) {
var messageName = type; // 訊息名稱。
var objectName = "s:" + type; // 訊息內建名稱,請按照此格式命名 *:* 。
var mesasgeTag = new RongIMLib.MessageTag(true, true); //true true 儲存且計數,false false 不儲存不計數。
RongIMClient.registerMessageType(
messageName,
objectName,
mesasgeTag,
propertys
);
},
上面差不多就是發言和收到訊息了。
最後說一下語音。因為融雲語音是用base64位,正常的辦法沒有辦法解讀。這個時候要用融雲自己的檔案。但是融雲目前官網上的語音版本,在安卓微信端不能播放語音,但是新版本的還沒有更新到官網,我先把這個檔案複製上來。
var RongIMLib;
(function(RongIMLib) {
var RongIMVoice = (function() {
function RongIMVoice() {}
RongIMVoice.init = function() {
if (this.notSupportH5) {
var div = document.createElement("div");
div.setAttribute("id", "flashContent");
document.body.appendChild(div);
var script = document.createElement("script");
script.src = (RongIMLib.RongIMClient && RongIMLib.RongIMClient._memoryStore && RongIMLib.RongIMClient._memoryStore.depend && RongIMLib.RongIMClient._memoryStore.depend.voiceSwfobjct) || "//cdn.ronghub.com/swfobject-2.0.0.min.js";
var header = document.getElementsByTagName("head")[0];
header.appendChild(script);
var browser = navigator.appName;
var b_version = navigator.appVersion;
var version = b_version.split(";");
var trim_Version = version[1].replace(/[ ]/g, "");
script.onload = function() {
var swfVersionStr = "11.4.0";
var flashvars = {};
var params = {};
params.quality = "high";
params.bgcolor = "#ffffff";
params.allowscriptaccess = "always";
params.allowScriptAccess = "always";
params.allowfullscreen = "true";
var attributes = {};
attributes.id = "player";
attributes.name = "player";
attributes.align = "middle";
swfobject.embedSWF((RongIMLib.RongIMClient && RongIMLib.RongIMClient._memoryStore && RongIMLib.RongIMClient._memoryStore.depend && RongIMLib.RongIMClient._memoryStore.depend.voicePlaySwf) || "//cdn.ronghub.com/player-2.0.2.swf", "flashContent", "1", "1", swfVersionStr, null, flashvars, params, attributes)
var f_version = swfobject.getFlashPlayerVersion();
if (f_version['major'] <= 0) {
console.error("You haven't installed the flash Player yet.");
}
}
if (!(browser == "Microsoft Internet Explorer" && trim_Version == "MSIE9.0" || trim_Version == "MSIE10.0")) {
script.onreadystatechange = script.onload;
}
}
this.isInit = true
};
RongIMVoice.play = function(data, duration) {
this.checkInit("play");
var me = this;
if (me.notSupportH5) {
if (me.thisMovie().doAction) {
me.thisMovie().doAction("init", data);
} else {
setTimeout(function() { me.play(data, duration); }, 500);
}
} else {
var key = data.substr(-10);
if (this.element[key]) {
this.element[key].play();
}
me.onCompleted(duration)
}
};
RongIMVoice.stop = function(base64Data) {
this.checkInit("stop");
var me = this;
if (me.notSupportH5) {
me.thisMovie().doAction("stop")
} else {
if (base64Data) {
var key = base64Data.substr(-10);
if (me.element[key]) {
me.element[key].pause();
me.element[key].currentTime = 0
}
} else {
for (var key_1 in me.element) {
me.element[key_1].pause();
me.element[key_1].currentTime = 0
}
}
}
};
RongIMVoice.preLoaded = function(base64Data, callback) {
var str = base64Data.substr(-10),
me = this;
if (me.element[str]) {
callback && callback();
return
}
// if (/android/i.test(navigator.userAgent) && /MicroMessenger/i.test(navigator.userAgent)) {
if (false) {
var audio = new Audio();
audio.src = "data:audio/amr;base64," + base64Data;
me.element[str] = audio;
callback && callback()
} else {
if (!me.notSupportH5) {
if (str in me.element) {
return
}
var audio = new Audio();
audio.src = "";
var nopromise = {
catch: new Function()
};
(audio.play() || nopromise).catch(function() {}); //解決瀏覽器報錯 The play() request was interrupted by a new load request
var blob = me.base64ToBlob(base64Data, "audio/amr");
var reader = new FileReader();
reader.onload = function(e) {
var data = new Uint8Array(e.target.result);
var samples = AMR.decode(data);
var pcm = PCMData.encode({
sampleRate: 8000,
channelCount: 1,
bytesPerSample: 2,
data: samples
});
audio.src = "data:audio/wav;base64," + btoa(pcm);
me.element[str] = audio;
callback && callback()
};
reader.readAsArrayBuffer(blob)
} else {
callback && callback()
}
}
};
RongIMVoice.onprogress = function() {};
RongIMVoice.checkInit = function(position) {
if (!this.isInit) {
throw new Error("RongIMVoice is not init,position:" + position)
}
};
RongIMVoice.thisMovie = function() {
return eval("window['player']")
};
RongIMVoice.onCompleted = function(duration) {
var me = this;
var count = 0;
var timer = setInterval(function() {
count++;
me.onprogress();
if (count >= duration) {
clearInterval(timer)
}
},
1000);
if (me.notSupportH5) {
me.thisMovie().doAction("play")
}
};
RongIMVoice.base64ToBlob = function(base64Data, type) {
var mimeType;
if (type) {
mimeType = {
type: type
}
}
base64Data = base64Data.replace(/^(.*)[,]/, "");
var sliceSize = 1024;
var byteCharacters = atob(base64Data);
var bytesLength = byteCharacters.length;
var slicesCount = Math.ceil(bytesLength / sliceSize);
var byteArrays = new Array(slicesCount);
for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
var begin = sliceIndex * sliceSize;
var end = Math.min(begin + sliceSize, bytesLength);
var bytes = new Array(end - begin);
for (var offset = begin,
i = 0; offset < end; ++i, ++offset) {
bytes[i] = byteCharacters[offset].charCodeAt(0)
}
byteArrays[sliceIndex] = new Uint8Array(bytes)
}
return new Blob(byteArrays, mimeType)
};
RongIMVoice.notSupportH5 = /Trident/.test(navigator.userAgent);
RongIMVoice.element = {};
RongIMVoice.isInit = false;
return RongIMVoice
}());
RongIMLib.RongIMVoice = RongIMVoice;
if ("function" === typeof require && "object" === typeof module && module && module.id && "object" === typeof exports && exports) {
module.exports = RongIMVoice
} else {
if ("function" === typeof define && define.amd) {
define("RongIMVoice", [],
function() {
return RongIMVoice
})
}
}
})(RongIMLib || (RongIMLib = {}));
在index.vue中呼叫play方法。友情提醒,我已經在util.js中初始化語音了,你們注意一下,不然會報錯沒有初始化哦。
play(voice) {
RongIMLib.RongIMVoice.stop();
if (voice) {
var duration = voice.length / 1024; // 音訊持續大概時間(秒)
RongIMLib.RongIMVoice.preLoaded(voice, function () {
RongIMLib.RongIMVoice.play(voice, duration);
});
} else {
console.error('請傳入 amr 格式的 base64 音訊檔案');
}
},
好像要記錄的就這些了。有不清楚的,可以告訴我,知無不言哦~(僅限聊天室,我實力也有限QAQ)