Avalonia 實現跨平臺的IM即時通訊、語音視訊通話(原始碼,支援信創國產OS,統信、銀河麒麟)

2023-11-20 12:05:38

      在 Avalonia 如火如荼的現在,之前使用CPF實現的簡單IM,非常有必要基於 Avalonia 來實現了。Avalonia 在跨平臺上的表現非常出色,對信創國產作業系統(像銀河麒麟、統信UOS、Deepin等)也很不錯。

      現在,我們就來使用 Avalonia 實現一個跨平臺的簡單IM,除了文字聊天外,還可以語音視訊通話。廢話不多說,我們開始吧!

      下圖是這個簡單IM的Avalonia使用者端在國產統信UOS上的執行的截圖:

      

一. IM 即時通訊系統主要功能

      這個簡單的IM系統實現了以下功能:

1.基礎功能、文字聊天

(1)使用者端使用者上下線時,通知其他線上使用者。

(2)當用戶端與伺服器端網路斷開時,進行自動重連,當網路恢復後,重連成功。

(3)所有線上使用者之間可以進行文字聊天(支援表情,支援撤回訊息、刪除訊息)。

(4)檔案傳送。

2.語音視訊聊天、遠端桌面

(1)一方發起視訊對話請求,對方同意後,即可開始視訊對話。

(2)在對話的過程中,任何一方都可以結束通話,以終止對話。

(3)在對話的過程中,任何一方掉線,都會自動終止對話。

(4)雙擊視訊視窗,會全螢幕顯示視訊,按esc退出全螢幕。

(5)遠端桌面或遠端協助功能,也是跟視訊聊天同樣的流程,不再贅述。

二.開發環境

1.開發工具:

Visual Studio 2022

2. 開發框架:

.NET Core 3.1

3.開發語言:

C#

4.其它框架:

Avalonia UI 框架(版本:0.10.22)、ESFramework 通訊框架 (版本:7.2)

注:建議 Avalonia 使用0.10.*的版本,精簡而且很穩定,而最新的11.0的版本太龐大了。

三.具體實現

下面我們講一下Demo中核心的程式碼實現,大家從文末下載原始碼並對照著原始碼看,會更清楚些。

1.自定義訊息型別 InformationTypes

       若要實現上述功能列表中列出來的所有功能,我們先要定義相應的通訊訊息的訊息型別,如下所示:

    public static class InformationTypes
    {
        /// <summary>
        /// 文字(表情)聊天資訊
        /// </summary>
        public const int TextChat = 0;

        /// <summary>
        /// 文字(表情)聊天資訊 (由伺服器端轉發給訊息接收方)
        /// </summary>
        public const int TextChat4Transit = 1;

        /// <summary>
        /// 圖片聊天資訊
        /// </summary>
        public const int ImageChat = 2;

        /// <summary>
        /// 收到訊息傳送者 撤回訊息請求
        /// </summary>
        public const int RecallMsg = 3;

        /// <summary>
        /// 使用者端非同步呼叫伺服器端
        /// </summary>
        public const int ClientSyncCallServer = 4;

        /// <summary>
        /// 視訊請求 5
        /// </summary>
        public const int VideoRequest = 5;

        /// <summary>
        /// 回覆視訊請求的結果 6
        /// </summary>
        public const int VideoResult = 6;

        /// <summary>
        /// 通知對方 結束通話 視訊連線 7
        /// </summary>
        public const int CloseVideo = 7;

        /// <summary>
        /// 通知好友 網路原因,導致 視訊中斷 8
        /// </summary>
        public const int NetReasonCloseVideo = 8;

        /// <summary>
        /// 通知對方(忙線中) 結束通話 視訊連線 9
        /// </summary>
        public const int BusyLine = 9;

        /// <summary>
        /// 收到遠端協助請求
        /// </summary>
        public const int AssistReceive = 10;

        /// <summary>
        /// 協助方拒絕遠端協助
        /// </summary>
        public const int AssistGusetReject = 11;

        /// <summary>
        /// 協助方同意遠端協助
        /// </summary>
        public const int AssistGusetAgree = 12;

        /// <summary>
        /// 請求方關閉遠端協助
        /// </summary>
        public const int AssistOwnerClose = 13;

        /// <summary>
        /// 協助方關閉遠端協助
        /// </summary>
        public const int AssistGusetClose = 14;
    }

       在約定好訊息型別之後,我們就可以實現業務邏輯功能了。 

2.定義協定類

      資訊型別定義好後,我們接下來定義資訊協定。

      對於聊天訊息(InformationTypes.EmotionTextChat),專門定義了一個協定類:ChatMessageRecord。     

    public class ChatMessageRecord
    {
        public string Guid { get; set; }

        public DateTime MessageTime { get; set; }

        public string SpeakerID { get; set; }

        public string ListenerID { get; set; }

        public ChatMessageType ChatMessageType { get; set; }

        public string ContentStr { get; set; }

        public byte[] ImgData { get; set; }

        public string FilePath { get; set; }
    }

      對於同步呼叫(InformationTypes.ClientSyncCallServer),我們範例的是向伺服器請求加法運算的結果,協定類用的是MathModel。

3.實現自定義資訊處理器

      使用者端的MainForm實現了ICustomizeHandler介面,其主要實現HandleInformation方法,來處理收到的聊天資訊和振動提醒。

  void HandleInformation(string sourceUserID, int informationType, byte[] info);

      伺服器端的CustomizeHandler實現了伺服器端的ICustomizeHandler介面,其主要實現HandleQuery方法來處理來自使用者端的同步呼叫(InformationTypes.ClientCallServer)。

  byte[] HandleQuery(string sourceUserID, int informationType, byte[] info);

4.伺服器端驗證使用者登入的帳號

       伺服器端的BasicHandler類實現IBasicHandler介面,以驗證登入使用者的賬號密碼。

    public class BasicHandler : IBasicHandler
    { 
        /// <summary>
        /// 此處驗證使用者的賬號和密碼。返回true表示通過驗證。
        /// </summary>  
        public bool VerifyUser(ClientType clientType, string systemToken, string userID, string password, out string failureCause)
        {
            failureCause = "";
            return true;
        }

        public string HandleQueryBeforeLogin(AgileIPE clientAddr, int queryType, string query)
        {
            return "";
        }
    }

       本demo中,假設所有的驗證都通過,所以驗證方法直接返回true。

5.使用者端實現文字聊天功能

     通過IRapidPassiveEngine的 CustomizeOutter 的 Send 方法來傳送文字表情聊天訊息。 

     在傳送文字聊天訊息時,有兩個傳送按鈕,「傳送1」和「傳送2」,分別演示了兩種傳送訊息給對方的方式:

(1)直接發給對方。(若P2P通道存在,則經由P2P通道傳送)

        internal static void SendTextMsgToClient(ChatMessageRecord record)
        {
            try
            {
                string cont = JsonConvert.SerializeObject(record);
                byte[] recordInfo = Encoding.UTF8.GetBytes(cont);
                //使用Tag攜帶 接收者的ID
                App.PassiveEngine.CustomizeOutter.Send(record.ListenerID, InformationTypes.TextChat4Transit, recordInfo);
            }
            catch (Exception e)
            {
                logger.Log(e, "GlobalHelper.SendTextMsgToClient", ErrorLevel.Standard);
            }
        }

      聊天訊息 ChatMessageRecord 物件先由JSON序列化成字串,然後在使用UTF-8轉成位元組陣列,然後通過通訊引擎的CustomizeOutter傳送出去。

(2)先發給伺服器,再由伺服器轉發給對方。     

      具體實現,大家去參看原始碼,這裡就不再贅述了。

6.使用者端實現語音視訊通話功能

         語音視訊通話實際執行起來後的效果如下所示:

 

         我們先簡單描述一下實現視訊對話流程的要點,更詳細的細節請查閱原始碼。

(1)發起方傳送InformationTypes.VideoRequest型別的資訊給對方,以請求視訊對話。

     程式中是在 VideoChatWindow 視窗顯示的時候,來做這件事的:

        protected override void OnInitialized()
        {
            base.OnInitialized(); 
            this.SetWindowStats();
            if (!this.IsWorking)
            {
                VideoController.Singleton.SendMessage(this.DestID, InformationTypes.VideoRequest, null);
                CommonHelper.AddSystemMsg(this.DestID, "向對方發起視訊通話邀請");
            }
        }

(2)接收方收到請求後,介面提示使用者是同意還是拒絕,使用者選擇後,將傳送InformationTypes.VideoResult型別的資訊給請求方,資訊的內容是一個bool值,true表示同意,false表示拒絕。

(3)發起方收到回覆,如果回覆為拒絕,則介面給出對應的提示;如果回覆為同意,則進入(4)。

(4)先說接收方,如果同意視訊,則傳送回復後,立即呼叫DynamicCameraConnector和MicrophoneConnector的Connect方法,連線到對方的攝像頭、麥克風。

        internal void BeginConnect()
        {
            UiSafeInvoker.ActionOnUI(() =>
            {
                string tip = this.IsWorking ? "已同意對方的視訊通話" : "對方同意了你的視訊通話請求";
                CommonHelper.AddSystemMsg(this.DestID, tip);
                this.IsWorking = true;
                this.NotifyOther = true;
                this.Title = this.title.Text = this.RepeatedCallTip(false);
                this.startTime = DateTime.Now;
                this.timer.Start();
                this.otherCamera.Core.DisplayVideoParameters = true;
                this.otherCamera.Core.VideoDrawMode = VideoDrawMode.ScaleToFill;
                this.otherCamera.Core.ConnectEnded += DynamicCameraConnector_ConnectEnded;
                this.otherCamera.Core.Disconnected += DynamicCameraConnector_Disconnected;
                this.microphoneConnector.ConnectEnded += MicrophoneConnector_ConnectEnded;
                this.microphoneConnector.Disconnected += MicrophoneConnector_Disconnected;
                this.otherCamera.BeginConnect(this.DestID);
                this.microphoneConnector.BeginConnect(this.DestID);
            });
        }

(5)對於發起方,當收到對方同意的回覆後,也立即呼叫DynamicCameraConnector和MicrophoneConnector的Connect方法,連線到接收方的攝像頭、麥克風。

(6)當一方點選結束通話的按鈕時,就會傳送InformationTypes.CloseVideo型別的資訊給對方,並呼叫DynamicCameraConnector和MicrophoneConnector的Disconnect方法斷開到對方裝置的連線。

(7)另一方接收到InformationTypes.CloseVideo型別的資訊時,也會呼叫DynamicCameraConnector和MicrophoneConnector的Disconnect方法以斷開連線。

        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);
            this.otherCamera?.Disconnect();
            this.otherCamera?.Dispose();

            this.microphoneConnector?.Disconnect();
            this.microphoneConnector?.Dispose();

            this.selfCamera?.Disconnect();
            this.selfCamera?.Dispose();
        }

(8)如果接收到自己掉線的事件或好友掉線的事件,也採用類似結束通話對話的處理。 

四.下載

    Avalonia 版本即時通訊原始碼: IM_VideoChat.Avalonia.rar

該原始碼中包括如下專案:

(1)Oraycn.Demos.VideoChat.LinuxServer :     該Demo的Linux伺服器端(基於.NetCore)。

(2)Oraycn.Demos.VideoChat.ClientAvalonia :   該Demo的 Avalonia 使用者端。

          注: Linux使用者端內建的是x86/x64非託管so庫,若需要其它架構的so,請聯絡我們免費獲取。