first , Tell me about the usage . Because of the need , Need to make a chat room page , Because it's not a peer-to-peer chat , It's similar to live broadcasting , But it's a live text platform . Now the general classroom , This may be needed . Divided into 2 End , One is the lecturer side , One is the client . The instructor side may be separate APP. The client's page may be embedded into a dedicated APP, Or wechat public platform . What I'm doing this time is a client . The lecturer wrote by the original student , Because of the need H5 The page can be compatible with wechat and can be used on the mobile phone .

  then , Explain , Yes vue Of UI The framework is vux( Maybe not next time , among Scroller They didn't maintain it !), Used webpack pack .

  Everything is ready , Drawing pages , It is similar to the live broadcast platform . use vux Draw quickly .




Confirm the requirements first ,1. Need the teacher to withdraw , The client then simultaneously removes the data from the array .2. Can play voice .3. The instructor area is drop-down to load more , The audience area is pull up to load more .4. because ios There is a column of associative words , So in order to be able to input text normally , Finally, it was decided to use a pop-up , It's in the bullet frame textarea Enter text .5. Pictures sent by lecturer . Need client to be able to click open , Turn it into a big picture .( Easy version , There is no double finger zoom or double click zoom ).6. Lecturers can be forbidden . The client needs to change the style accordingly , Input forbidden . That's almost what they need .

Get ready :

1. Go to rongyun official website to register account . In fact, for our front end , Just need one appkey.



2. Get it appkey, It's like getting the key to the door . This is the time to start writing js The file is connected .po Contents of the previous document .



Mainly in the views Inside index.vue Written documents . Because there is only one page , So my route is written in main.js( Don't say I didn't write the route , Ha ha ha ha ).

okay , Next we need to connect . So I wrote in utils.js It's from the official website js file , By the way, package it , In order to be able to index.vue Medium value , A callback function is added here .
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); // Voice playback initialization RongIMLib.RongIMVoice.init(); var instance =
RongIMClient.getInstance(); // Connection status monitor
RongIMClient.setConnectionStatusListener({ onChanged: function(status) { //
console.log(status); switch (status) { case
RongIMLib.ConnectionStatus["CONNECTED"]: case 0: console.log(" The connection was successful ");
callbacks.getInstance && callbacks.getInstance(instance); break; case
RongIMLib.ConnectionStatus["CONNECTING"]: case 1: console.log(" Connecting "); break;
case RongIMLib.ConnectionStatus["DISCONNECTED"]: case 2:
console.log(" Active disconnection of current user "); break; case
RongIMLib.ConnectionStatus["NETWORK_UNAVAILABLE"]: case 3:
console.log(" Network not available "); break; case
RongIMLib.ConnectionStatus["CONNECTION_CLOSED"]: case 4:
console.log(" Unknown reason , Connection closed "); break; case
RongIMLib.ConnectionStatus["KICKED_OFFLINE_BY_OTHER_CLIENT"]: case 6:
alert(" User account login on other devices , This opportunity was kicked off the line "); break; case
RongIMLib.ConnectionStatus["DOMAIN_INCORRECT"]: case 12:
console.log(" Current running domain name error , Please check the security domain name configuration "); break; } } }); // Start link RongIMClient.connect(
token, { onSuccess: function(userId) { callbacks.getCurrentUser &&
callbacks.getCurrentUser({ userId: userId }); console.log(" Link successful , user id:" +
userId); }, onTokenIncorrect: function() { console.log("token invalid "); }, onError:
function(errorCode) { console.log(errorCode); } }, params.userId ); /*
file :http://www.rongcloud.cn/docs/web.html#3, Setting up a message listener matters needing attention :
1: In order to see the reception effect , Another user is required to send a message to this user 2: Judging session uniqueness :conversationType + targetId
3: Show message in front of page , You need to determine whether it belongs to the current session , Avoid clutter .
4: The description of message body properties can be referred to :http://rongcloud.cn/docs/api/js/index.html */
RongIMClient.setOnReceiveMessageListener({ // Message received onReceived:
function(message) { // Judge message type // console.log(" New news : " + message.targetId); //
console.log(message); // Judge message type switch (message.messageType) { case
RongIMClient.MessageType.TextMessage: // message.content.content => Message content break;
case RongIMClient.MessageType.VoiceMessage: // message.content.content The format is AMR
Formal base64 code break; case RongIMClient.MessageType.ImageMessage: //
message.content.content => Picture thumbnail base64. // message.content.imageUri => Original picture URL.
break; case RongIMClient.MessageType.DiscussionNotificationMessage: //
message.content.extension => People in the discussion group . break; case
RongIMClient.MessageType.RichContentMessage: // message.content.content =>
Text message content . // message.content.imageUri => picture base64. // message.content.url => Original picture
URL. break; } callbacks.receiveNewMessage &&
callbacks.receiveNewMessage(message); } }); } };
This will warm up the engine , This is the time , Go to us index.vue Driving .


stay inde.vue It is initialized first . Write a startInit function , Mount to mounted upper . Because of initialization , We need to pass on the cloud token Of , this token It's cloud melting token, That, of course, needs to be passed on backstage . This is what our company wrote , When entering the page , First mover 2 Requests , One is the user's personal information request , One is the chat room details request . User personal information is divided into : This person is logged in , I send the verification message to the background , Give me one backstage token, I'm going to ask rongyun . The second is that the person is not logged in , Then my verification message at this time is empty , It needs to be judged by the backstage , And then give me one token, By the way, we need to pass on some other basis for judgment , Tell me this person is not logged in , Then I'll make it impossible for him to speak .( Far away _(:з」∠)_). Get it token, I started to initialize .

At the same time, we're going to join the chat room :
startInit(){ let token = getCookie('tk1') var params = { appKey: this.appKey,
token: token, }; const that = this; var userId = ""; var callbacks = {
getInstance: function (instance) { // register PersonMessage var propertys = ["name",
"age", "gender"]; // Property name in message class . that.registerMessage("PersonMessage",
propertys); // register ProductMessage var propertys = ["price", "title", "desc",
"images"]; // Property name in message class . that.registerMessage("ProductMessage", propertys); },
getCurrentUser(userInfo) { const userId = getCookie('uId') document.titie =
" Link successful ;userid=" + userInfo.userId; // Join the chat room that.joinChatRoom(); }, // Receive the latest news from rongyun
receiveNewMessage(message) { // Forbidden words if (message.objectName == 'ChatRoom:state')
{ if (message.content.message.content.roomState == "BANNED_TO_POST") {
$('.weui-input').attr('placeholder', ' All members are forbidden , Can't speak ') } else { let rightControl =
getCookie('rightControl') if (rightControl == '1020') {
$('.weui-input').attr('placeholder', ' Only after logging in can you ask questions to the instructor ') } else {
$('.weui-input').attr('placeholder', ' Enter your question ') } } } // Custom message , Judge whether it is the host let tag
if (message.content.extra) { tag = JSON.parse(message.content.extra).tag } let
messageBig1 = {}; // Judge message type if (!tag) { // No value is the audience if (message.objectName ==
'RC:TxtMsg') { // Text message 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; //
The number of historical information stored in the discussion area setCookie('totalCount', that.totalCount, expireDays) //
After you speak, you roll up (vux method ) 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 { // It's worth being the host 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(); }
// Message withdrawal 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) },

Pay attention to this one , It's acquisition token It would be asynchronous , therefore , I finally decided , This function , In the function of getting personal information , So there's no asynchrony .( You can try to write middleware , Or some other way to deal with asynchrony .). because vue It's a two-way binding , So I just need to change the value of the array , It will automatically appear to withdraw and add data to the page . by the way , There is a very important method in this , I haven't said that yet . They were picked off the list , honestly , I don't know why I wrote this function . This function is called in initialization .
registerMessage(type, propertys) { var messageName = type; // Message name . var
objectName = "s:" + type; // Message built in name , Please name it in this format *:* . var mesasgeTag = new
RongIMLib.MessageTag(true, true); //true true Save and count ,false false No saving, no counting .
RongIMClient.registerMessageType( messageName, objectName, mesasgeTag,
propertys ); },
It's about speaking and receiving messages .


Finally, voice . Because rongyun voice is base64 position , There is no way to interpret the normal way . At this time, you should use rongyun's own files . However, the current voice version of rongyun's official website , Voice cannot be played on Android wechat , But the new version has not been updated to the official website , I'll copy this file first .
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() {});
// Solve browser error 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 = {}));
stay index.vue Call in play method . Friendly Reminder , I'm already here util.js Voice has been initialized in , Pay attention , Otherwise, there will be an error and there is no initialization .
play(voice) { RongIMLib.RongIMVoice.stop(); if (voice) { var duration =
voice.length / 1024; // Audio duration ( second ) RongIMLib.RongIMVoice.preLoaded(voice,
function () { RongIMLib.RongIMVoice.play(voice, duration); }); } else {
console.error(' Please pass in amr Formal base64 Audio files '); } },
It seems that's all we need to record . It's not clear , Can tell me , Say what you know ~( Chat room only , My strength is limited QAQ)