1. 程式人生 > >融雲集成一個聊天室頁面(vue版本)

融雲集成一個聊天室頁面(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)