首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >鸿蒙Electron应用如何集成腾讯会议SDK

鸿蒙Electron应用如何集成腾讯会议SDK

原创
作者头像
千星织梦
修改于 2026-10-22 15:25:09
修改于 2026-10-22 15:25:09
230
举报
文章被收录于专栏:腾讯会议腾讯会议

【问题现象】

腾讯会议 SDK 暂未提供 Electron 版的 HarmonyOS 适配。运行在 HarmonyOS 上的 Electron 应用,应当如何集成腾讯会议功能?

【背景知识】

  • 腾讯会议HarmonyOS SDK接入指南介绍了集成腾讯会议HarmonyOS SDK的流程。 【解决方案】 参考Electron调用ets指导文档,封装一套腾讯会议接口的addon插件给electron调用,这套接口底层调用腾讯会议HarmonyOS SDK的ArkTS接口。以InitWemeetSDK接口为例,其工作流程如:
  • addon侧封装
  • adapter层实现
  • ArkTS层实现
  • Electron Js层调用 addon侧封装和adapter层实现完整代码见附件,编译流程参考Electron调用ets指导文档,以下将说明各部分的关键实现。
  • addon侧封装: 参考腾讯会议Electron接入手册中的接口说明封装如下常用接口:
  • AddJsCallback(call_back)
  • IsInitialized
  • IsAuthorized
  • GetSDKVersion()
  • RefreshSDKToken(token)
  • InitWemeetSDK(sdkId, sdkToken, logPath, appName, iconPath, 'zh-cn')
  • UninitWemeetSDK({ force: true })
  • EnableRingInvitationView(bFlag)
  • SetNeedShareCallback(boolean, boolean)
  • IsInitialized()
  • IsAuthorized()
  • Login(ssoUrl)
  • EnableInviteUsersCallback(boolean, boolean)
  • JoinMeeting(meetingCode, userName, password, inviteUrl, defaultAudio, defaultVideo, defaultSpeaker, defaultBeauty, APP_NAME)
  • AddUsersWithParam({ users,userType })
  • LeaveMeeting(boolean)
  • Logout()
  • HandleRingInvitation(isAccept, inviteId)
  • CollectLogFiles(beginTime, endTime) 关键代码片段如下:
代码语言:cpp
复制
static Napi::Value AddJsCallback(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     if (info.Length() != 1) { // 注册回调函数只有一个参数
       Napi::TypeError::New(env, "参数数目错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     if (!info[0].IsFunction()) {
       Napi::TypeError::New(env, "参数必须是函数").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     Napi::Function regJsCb = info[0].As<Napi::Function>();
  
     WemeetSdkAdapter::CallBack addCb = NapiFunctionToStdFunc(regJsCb, env);
     WemeetSdkAdapter::AddCallBack(addCb);
  
     return env.Undefined();
   }
  
   static Napi::String GetSDKVersion(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     std::string version = WemeetSdkAdapter::GetSDKVersion();
     return Napi::String::New(env, version);
   }
  
    /** InitParam:
    *   std::string sdkId{};
    *   std::string sdkToken{};
    *   std::string dataPath{};
    *   std::string appName{};
    *   std::string appIcon{};
    *   std::string preferLanguage{};
   */
   static Napi::Value InitWemeetSDK(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     size_t len = info.Length();
     if (len != 6) {
       Napi::TypeError::New(env, "参数数目错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
     for (size_t i = 0; i < len; i++) {
       if (!info[i].IsString()) {      
         Napi::TypeError::New(env, "参数类型错误").ThrowAsJavaScriptException();
         return env.Undefined();
       }
     }
  
     InitParam initParam;
     initParam.sdkId = info[0].As<Napi::String>().Utf8Value();  
     initParam.sdkToken = info[1].As<Napi::String>().Utf8Value();  
     initParam.dataPath = info[2].As<Napi::String>().Utf8Value();  
     initParam.appName = info[3].As<Napi::String>().Utf8Value();  
     initParam.appIcon = info[4].As<Napi::String>().Utf8Value();  
     initParam.preferLanguage = info[5].As<Napi::String>().Utf8Value();
  
     InitWemeetSDKWorker* worker = new InitWemeetSDKWorker(env, initParam);
     worker->Queue();
     return worker->Promise();
   }
  
   static Napi::Value UninitWemeetSDK(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     // params:{ force: true }
     if (info.Length() != 1) {
       Napi::TypeError::New(env, "参数数目错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     if (!info[0].IsString()) {      
       Napi::TypeError::New(env, "参数类型错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     std::string param = info[0].As<Napi::String>().Utf8Value(); 
  
     UninitWemeetSDKWorker* worker = new UninitWemeetSDKWorker(env, param);
     worker->Queue();
     return worker->Promise();
   }
  
   static Napi::Value IsInitialized(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     IsInitializedWorker* worker = new IsInitializedWorker(env);
     worker->Queue();
     return worker->Promise();
   }
  
   static Napi::Value RefreshSDKToken(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     if (info.Length() != 1) {
       Napi::TypeError::New(env, "参数数目错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     if (!info[0].IsString()) {
       Napi::TypeError::New(env, "参数必须是函数").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     std::string param = info[0].As<Napi::String>().Utf8Value(); 
     int code = WemeetSdkAdapter::RefreshSDKToken(param);
  
     return Napi::Number::New(env, code);
   }
  
   static Napi::Value IsAuthorized(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     IsAuthorizedWorker* worker = new IsAuthorizedWorker(env);
     worker->Queue();
     return worker->Promise();
   }
  
   static Napi::Value Login(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     // params: ssoUrl
     if (info.Length() != 1) {
       Napi::TypeError::New(env, "参数数目错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     if (!info[0].IsString()) {      
       Napi::TypeError::New(env, "参数类型错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     std::string param = info[0].As<Napi::String>().Utf8Value(); 
  
     LoginWorker* worker = new LoginWorker(env, param);
     worker->Queue();
     return worker->Promise();
   }
  
   static Napi::Value Logout(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     LogoutWorker* worker = new LogoutWorker(env);
     worker->Queue();
     return worker->Promise();
   }
  
   /** JoinParam:
    *   std::string meetingCode{};
    *   std::string userDisplayName{};
    *   std::string password{};
    *   std::string inviteUrl{};
    *   bool micOn{false};
    *   bool cameraOn{false};
    *   bool speakerOn{false};
    *   bool faceBeautyOn{false};
    *   std::string meetingWindowTitle{};
   */
   static Napi::Value JoinMeeting(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     if (info.Length() != 9) {
       Napi::TypeError::New(env, "参数数目错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
     for (size_t i = 0; i < 4; i++) {
       if (!info[i].IsString()) {      
         Napi::TypeError::New(env, "参数类型错误").ThrowAsJavaScriptException();
         return env.Undefined();
       }
     }
  
     for (size_t i = 4; i < 8; i++) {
       if (!info[i].IsBoolean()) {      
         Napi::TypeError::New(env, "参数类型错误").ThrowAsJavaScriptException();
         return env.Undefined();
       }
     }
  
     if (!info[8].IsString()) {      
       Napi::TypeError::New(env, "参数类型错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     JoinParam joinParam;
     joinParam.meetingCode = info[0].As<Napi::String>().Utf8Value();
     joinParam.userDisplayName = info[1].As<Napi::String>().Utf8Value();
     joinParam.password = info[2].As<Napi::String>().Utf8Value();
     joinParam.inviteUrl = info[3].As<Napi::String>().Utf8Value();
     joinParam.micOn = info[4].As<Napi::Boolean>().Value();
     joinParam.cameraOn = info[5].As<Napi::Boolean>().Value();
     joinParam.speakerOn = info[6].As<Napi::Boolean>().Value();
     joinParam.faceBeautyOn = info[7].As<Napi::Boolean>().Value();
     joinParam.meetingWindowTitle = info[8].As<Napi::String>().Utf8Value();
  
     JoinMeetingWorker* worker = new JoinMeetingWorker(env, joinParam);
     worker->Queue();
     return worker->Promise();
   }
  
   /** int leave_meeting_type: 
   * 0、所有设备离开会议;
   * 1、结束会议(结束会议,仅当前账户是会议主持人时,该参数才有效。当非主持人时,调用结束会议等同于所有设备离开会议);
   * 2、仅当前设备离开会议 (当非多端入会场景时,调用仅当前设备离开会议等同于离开会议
   */
   static Napi::Value LeaveMeeting(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     if (info.Length() != 1) {
       Napi::TypeError::New(env, "参数数目错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     if (!info[0].IsNumber()) {      
       Napi::TypeError::New(env, "参数类型错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     int type = info[0].As<Napi::Number>().Int32Value(); 
  
     LeaveMeetingWorker* worker = new LeaveMeetingWorker(env, type);
     worker->Queue();
     return worker->Promise();
   }
  
   static Napi::String CollectLogFiles(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     size_t len = info.Length();
     if (len != 2) {
       Napi::TypeError::New(env, "参数数目错误").ThrowAsJavaScriptException();
       return Napi::String::New(env, "");
     }
     for (size_t i = 0; i < len; i++) {
       if (!info[i].IsString()) {      
         Napi::TypeError::New(env, "参数类型错误").ThrowAsJavaScriptException();
         return Napi::String::New(env, "");
       }
     }
  
     std::string beginTime = info[0].As<Napi::String>().Utf8Value();
     std::string endTime   = info[1].As<Napi::String>().Utf8Value();
  
     std::string filePaths = WemeetSdkAdapter::CollectLogFiles(beginTime, endTime);
  
     return Napi::String::New(env, filePaths);
   }
  
   static Napi::Value EnableRingInvitationView(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     if (info.Length() != 1) { 
       Napi::TypeError::New(env, "参数数目错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     if (!info[0].IsBoolean()) {
       Napi::TypeError::New(env, "参数必须是函数").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     bool enable = info[0].As<Napi::Boolean>().Value();
  
     WemeetSdkAdapter::EnableRingInvitationView(enable);
  
     return env.Undefined();
   }
  
   static Napi::Value SetNeedShareCallback(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     if (info.Length() != 2) { 
       Napi::TypeError::New(env, "参数数目错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     if (!info[0].IsBoolean() || !info[1].IsBoolean()) {
       Napi::TypeError::New(env, "参数必须是函数").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     bool enable = info[0].As<Napi::Boolean>().Value();
     bool show   = info[1].As<Napi::Boolean>().Value();
  
     WemeetSdkAdapter::EnableInviteCallback(enable, show);
  
     return env.Undefined();
   }
  
   // 响应邀请的处理(入会、挂断)
   static Napi::Value HandleRingInvitation(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     if (info.Length() != 2) { 
       Napi::TypeError::New(env, "参数数目错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     if (!info[0].IsBoolean() || !info[1].IsString()) {
       Napi::TypeError::New(env, "参数必须是函数").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     bool accept = info[0].As<Napi::Boolean>().Value();
     std::string inviteId = info[1].As<Napi::String>().Utf8Value(); 
  
     WemeetSdkAdapter::HandleRingInvitation(accept, inviteId);
  
     return env.Undefined();
   }
  
   // 设置是否使用添加成员的回调, HarmonyOS暂不支持
   static Napi::Value EnableInviteUsersCallback(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     if (info.Length() != 2) { 
       Napi::TypeError::New(env, "参数数目错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     if (!info[0].IsBoolean() || !info[1].IsBoolean()) {
       Napi::TypeError::New(env, "参数必须是函数").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     bool enable = info[0].As<Napi::Boolean>().Value();
     bool show   = info[1].As<Napi::Boolean>().Value();
  
     WemeetSdkAdapter::EnableInviteUsersCallback(enable, show);
  
     return env.Undefined();
   }
  
   /** 异步执行
    *  添加人员操作,接入方可以邀请人加入预定会议、或邀请人员加入会议, HarmonyOS暂不支持
    * string json_param格式:
       {
       "users": ["user1_id","user2_id","user3_id","user4_id"],
       "user_type":2
       }
    */
   static Napi::Value AddUsersWithParam(const Napi::CallbackInfo& info) {
     Napi::Env env = info.Env();
  
     // params: json_param
     if (info.Length() != 1) {
       Napi::TypeError::New(env, "参数数目错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     if (!info[0].IsString()) {      
       Napi::TypeError::New(env, "参数类型错误").ThrowAsJavaScriptException();
       return env.Undefined();
     }
  
     std::string param = info[0].As<Napi::String>().Utf8Value(); 
  
     AddUsersWithParamWorker* worker = new AddUsersWithParamWorker(env, param);
     worker->Queue();
     return worker->Promise();
   }
  
   static Napi::Object Init(Napi::Env env, Napi::Object exports) {  
     exports.Set(Napi::String::New(env, "AddJsCallback"),
                 Napi::Function::New(env, AddJsCallback));
  
     exports.Set(Napi::String::New(env, "TestCallFunc"),
                 Napi::Function::New(env, TestCallFunc));
  
     exports.Set(Napi::String::New(env, "GetSDKVersion"),
                 Napi::Function::New(env, GetSDKVersion));
  
     exports.Set(Napi::String::New(env, "InitWemeetSDK"),
                 Napi::Function::New(env, InitWemeetSDK));
  
     exports.Set(Napi::String::New(env, "UninitWemeetSDK"),
                 Napi::Function::New(env, UninitWemeetSDK));
  
     exports.Set(Napi::String::New(env, "IsInitialized"),
                 Napi::Function::New(env, IsInitialized));
  
     exports.Set(Napi::String::New(env, "RefreshSDKToken"),
                 Napi::Function::New(env, RefreshSDKToken));
  
     exports.Set(Napi::String::New(env, "Login"),
                 Napi::Function::New(env, Login));
  
     exports.Set(Napi::String::New(env, "IsAuthorized"),
                 Napi::Function::New(env, IsAuthorized));
  
     exports.Set(Napi::String::New(env, "Logout"),
                 Napi::Function::New(env, Logout));
  
     exports.Set(Napi::String::New(env, "JoinMeeting"),
                 Napi::Function::New(env, JoinMeeting));
  
     exports.Set(Napi::String::New(env, "LeaveMeeting"),
                 Napi::Function::New(env, LeaveMeeting));
  
     exports.Set(Napi::String::New(env, "CollectLogFiles"),
                 Napi::Function::New(env, CollectLogFiles));
  
     exports.Set(Napi::String::New(env, "EnableRingInvitationView"),
                 Napi::Function::New(env, EnableRingInvitationView));
  
     exports.Set(Napi::String::New(env, "SetNeedShareCallback"),
                 Napi::Function::New(env, SetNeedShareCallback));
  
     exports.Set(Napi::String::New(env, "HandleRingInvitation"),
                 Napi::Function::New(env, HandleRingInvitation));
  
     exports.Set(Napi::String::New(env, "EnableInviteUsersCallback"),
                 Napi::Function::New(env, EnableInviteUsersCallback));
  
     exports.Set(Napi::String::New(env, "AddUsersWithParam"),
                 Napi::Function::New(env, AddUsersWithParam));
  
     return exports;
   }
  
   NODE_API_MODULE(wemeet_sdk, Init);
  1. adapter层实现:
代码语言:cpp
复制
#include "wemeetSdkAdapter.h"
   
   #include <functional>
   #include "aki/jsbind.h"
   void WemeetSdkAdapter::AddCallBack(CallBack& addCb)
   {
     if (auto jsFunc = aki::JSBind::GetJSFunction("addCallBack")) {
       jsFunc->Invoke<void>(addCb);
     }
   }
   
   std::string WemeetSdkAdapter::GetSDKVersion()
   {
     if (auto getSdkVerFunc = aki::JSBind::GetJSFunction("getSDKVersion")) {
       auto version = getSdkVerFunc->Invoke<std::string>();
       return version;
     }
   
     return "";
   }
   
   // SDK初始化 - 异步接口
   int WemeetSdkAdapter::InitWemeetSDK(InitParam initParam)
   {
     std::promise<int> promise;
     std::function<void(int)> callback = 
       [&promise](int code) {
         promise.set_value(code);
     };
   
     if (auto jsFunc = aki::JSBind::GetJSFunction("initWemeetSDK")) {
       jsFunc->Invoke<void>(initParam, callback);
     } else {
       int errcode(0);
       promise.set_value(errcode);
     }
     auto future = promise.get_future();
     return future.get();
   }
   
   // SDK反初始化 - 异步接口
   int WemeetSdkAdapter::UninitWemeetSDK(std::string param)
   {
     std::promise<int> promise;
     std::function<void(int)> callback = 
       [&promise](int code) {
         promise.set_value(code);
     };
   
     if (auto jsFunc = aki::JSBind::GetJSFunction("uninitWemeetSDK")) {
       jsFunc->Invoke<void>(param, callback);
     } else {
       int errcode(0);
       promise.set_value(errcode);
     }
     auto future = promise.get_future();
     return future.get();
   }
   
   // SDK是否初始化 - 异步接口
   int WemeetSdkAdapter::IsInitialized()
   {
     std::promise<int> promise;
     std::function<void(int)> callback = 
       [&promise](int code) {
         promise.set_value(code);
     };
   
     if (auto jsFunc = aki::JSBind::GetJSFunction("isInitialized")) {
       jsFunc->Invoke<void>(callback);
     } else {
       int errcode(0);
       promise.set_value(errcode);
     }
     auto future = promise.get_future();
     return future.get();
   }
   
   // 刷新SDK token信息 - 同步接口
   int WemeetSdkAdapter::RefreshSDKToken(std::string param)
   {
     if (auto refreshSDKTokenFunc = aki::JSBind::GetJSFunction("refreshSDKToken")) {
       auto code = refreshSDKTokenFunc->Invoke<int>(param);
       return code;
     }
   
     return -1;
   }
   
   // 异步登录
   int WemeetSdkAdapter::Login(std::string param)
   {
     std::promise<int> promise;
     std::function<void(int)> callback = 
       [&promise](int code) {
         promise.set_value(code);
     };
   
     if (auto jsFunc = aki::JSBind::GetJSFunction("login")) {
       jsFunc->Invoke<void>(param, callback);
     } else {
       int errcode(0);
       promise.set_value(errcode);
     }
     auto future = promise.get_future();
     return future.get();
   }
   
   // 异步退出
   int WemeetSdkAdapter::Logout()
   {
     std::promise<int> promise;
     std::function<void(int)> callback = 
       [&promise](int code) {
         promise.set_value(code);
     };
   
     if (auto jsFunc = aki::JSBind::GetJSFunction("logout")) {
       jsFunc->Invoke<void>(callback);
     } else {
       int errcode(0);
       promise.set_value(errcode);
     }
     auto future = promise.get_future();
     return future.get();
   }
   
   // 异步获取是否已登录(已授权)
   int WemeetSdkAdapter::IsAuthorized()
   {
     std::promise<int> promise;
     std::function<void(int)> callback = 
       [&promise](int code) {
         promise.set_value(code);
     };
   
     if (auto jsFunc = aki::JSBind::GetJSFunction("isAuthorized")) {
       jsFunc->Invoke<void>(callback);
     } else {
       int errcode(0);
       promise.set_value(errcode);
     }
     auto future = promise.get_future();
     return future.get();
   }
   
   // 异步加入会议
   int WemeetSdkAdapter::JoinMeeting(JoinParam joinParam)
   {
     std::promise<int> promise;
     std::function<void(int)> callback = 
       [&promise](int code) {
         promise.set_value(code);
     };
   
     if (auto jsFunc = aki::JSBind::GetJSFunction("joinMeeting")) {
       jsFunc->Invoke<void>(joinParam, callback);
     } else {
       int errcode(0);
       promise.set_value(errcode);
     }
     auto future = promise.get_future();
     return future.get();
   }
   
   // 异步离开会议
   int WemeetSdkAdapter::LeaveMeeting(int type)
   {
     std::promise<int> promise;
     std::function<void(int)> callback = 
       [&promise](int code) {
         promise.set_value(code);
     };
   
     if (auto jsFunc = aki::JSBind::GetJSFunction("leaveMeeting")) {
       jsFunc->Invoke<void>(type, callback);
     } else {
       int errcode(0);
       promise.set_value(errcode);
     }
     auto future = promise.get_future();
     return future.get();
   }
   
   // 同步获取会议日志文件路径的列表
   std::string WemeetSdkAdapter::CollectLogFiles(std::string beginTime, std::string endTime)
   {
     if (auto jsFunc = aki::JSBind::GetJSFunction("collectLogFiles")) {
       auto filePath = jsFunc->Invoke<std::string>(beginTime, endTime);
       return filePath;
     }
   
     return "";
   }
   
   // 同步设置是否显示SDK的响铃邀请界面
   void WemeetSdkAdapter::EnableRingInvitationView(bool enable)
   {
     if (auto jsFunc = aki::JSBind::GetJSFunction("enableRingInvitationView")) {
       jsFunc->Invoke<void>(enable);
     }
   }
   
   // 同步设置是否使用邀请回调,如果使用,点击会议中界面下方工具栏上的邀请按钮,会触发`InMeetingCallback.onInviteMeeting`回调,并回调会议信息
   void WemeetSdkAdapter::EnableInviteCallback(bool enable, bool show)
   {
     if (auto jsFunc = aki::JSBind::GetJSFunction("enableInviteCallback")) {
       jsFunc->Invoke<void>(enable, show);
     }
   }
   
   // 同步处理响铃邀请
   void WemeetSdkAdapter::HandleRingInvitation(bool accept, std::string inviteId)
   {
     if (auto jsFunc = aki::JSBind::GetJSFunction("handleRingInvitation")) {
       jsFunc->Invoke<void>(accept, inviteId);
     }
   }
   
   // 同步设置是否使用添加成员的回调
   void WemeetSdkAdapter::EnableInviteUsersCallback(bool enable, bool show)
   {
     if (auto jsFunc = aki::JSBind::GetJSFunction("enableInviteUsersCallback")) {
       jsFunc->Invoke<void>(enable, show);
     }
   }
   
   // 异步添加人员操作,接入方可以邀请人加入预定会议
   int WemeetSdkAdapter::AddUsersWithParam(std::string param)
   {
     std::promise<int> promise;
     std::function<void(int)> callback = 
       [&promise](int code) {
         promise.set_value(code);
     };
   
     if (auto jsFunc = aki::JSBind::GetJSFunction("addUsersWithParam")) {
       jsFunc->Invoke<void>(param, callback);
     } else {
       int errcode(0);
       promise.set_value(errcode);
     }
     auto future = promise.get_future();
     return future.get();
   }

InitWemeetSDK接口参数使用了InitParam结构体,JoinMeeting接口参数使用了JoinParam结构体,需要声明InitParam和JoinMeeting结构体并使用aki绑定类/结构体并绑定类成员属性。代码片段如下:

代码语言:cpp
复制
JSBIND_CLASS(InitParam) {
     JSBIND_PROPERTY(sdkId);
     JSBIND_PROPERTY(sdkToken);
     JSBIND_PROPERTY(dataPath);
     JSBIND_PROPERTY(appName);
     JSBIND_PROPERTY(appIcon);
     JSBIND_PROPERTY(preferLanguage);
   }
   
   JSBIND_CLASS(JoinParam) {
     JSBIND_PROPERTY(meetingCode);
     JSBIND_PROPERTY(userDisplayName);
     JSBIND_PROPERTY(password);
     JSBIND_PROPERTY(inviteUrl);
     JSBIND_PROPERTY(micOn);
     JSBIND_PROPERTY(cameraOn);
     JSBIND_PROPERTY(speakerOn);
     JSBIND_PROPERTY(faceBeautyOn);
     JSBIND_PROPERTY(meetingWindowTitle);
   }
  1. ArkTS层实现: 将adapter中方法和ArkTS中方法绑定,关键代码如下:
代码语言:ts
复制
static bind() {
       hilog.info(0x0000, TAG, `-----------JSBind start--------------`);
       if (WeMeetSdkJsBindingEts.currentContext === undefined) {
         WeMeetSdkJsBindingEts.currentContext = wemeet_sdk_adapter.getNativeContext();
       }
   
       // 绑定相关函数
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("addCallBack", WeMeetSdkJsBindingEts.addCallBack);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("getSDKVersion", WeMeetSdkJsBindingEts.getSDKVersion);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("initWemeetSDK", WeMeetSdkJsBindingEts.initWemeetSDK);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("uninitWemeetSDK", WeMeetSdkJsBindingEts.uninitWemeetSDK);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("isInitialized", WeMeetSdkJsBindingEts.isInitialized);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("refreshSDKToken", WeMeetSdkJsBindingEts.refreshSDKToken);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("login", WeMeetSdkJsBindingEts.login);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("logout", WeMeetSdkJsBindingEts.logout);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("isAuthorized", WeMeetSdkJsBindingEts.isAuthorized);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("joinMeeting", WeMeetSdkJsBindingEts.joinMeeting);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("leaveMeeting", WeMeetSdkJsBindingEts.leaveMeeting);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("collectLogFiles", WeMeetSdkJsBindingEts.collectLogFiles);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("enableRingInvitationView", WeMeetSdkJsBindingEts.enableRingInvitationView);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("enableInviteCallback", WeMeetSdkJsBindingEts.enableInviteCallback);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("handleRingInvitation", WeMeetSdkJsBindingEts.handleRingInvitation);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("enableInviteUsersCallback", WeMeetSdkJsBindingEts.enableInviteUsersCallback);
       WeMeetSdkJsBindingEts.currentContext.JSBind.bindFunction("addUsersWithParam", WeMeetSdkJsBindingEts.addUsersWithParam);
     }

addCallBack接口实现中需要保存onCallback参数,在其他接口如initWemeetSDK完成后通过onCallback通知调用方:

代码语言:ts
复制
   // addCallBack 添加注册函数
     static addCallBack(onCallback: JsonCallback): void {
       hilog.info(0x0000, TAG, `-----------addCallBack start--------------`);
       TMMeetingSDK.g_callBack = onCallback;
     }  

onCallback中回调参数为json字符串,格式如下 :

通过如下方法封装回调参数并回调到Electron Js侧:

代码语言:TS
复制
// msg should be normal string, param should be json string
static processCallbackMsg(
func: string,
service: string,
code: number,
json: boolean,
msg: string,
param: string
): void {
// 1. 创建基础JSON对象
const jsonObj: CallbackMsg = {
func: func,
service: service
};

// 2. 处理code和msg(仅当code非0或msg非空时添加)
if (code !== 0 || msg !== '') {
jsonObj.code = code.toString(); // 转为字符串

// 判断msg是否为JSON格式,决定是否带引号
if (TMMeetingSDK.judgeMsgJson(msg)) {
// 如果是JSON字符串,先解析为对象再赋值(避免JSON.stringify二次转义)
jsonObj.msg = JSON.parse(msg) as object;
} else {
jsonObj.msg = msg;
}
}

// 3. 处理param(非空时添加)
if (param !== '') {
// param直接作为JSON值(不添加引号)
jsonObj.param = JSON.parse(param) as object;
}

// 4. 转换为JSON字符串
const jsonStr = JSON.stringify(jsonObj);
TMMeetingSDK.triggerCallback(jsonStr);

}

以initWemeetSDK接口回调为例(其他接口代码可在),按如下方式回调初始话结果到js侧:

代码语言:ts
复制
// SDK初始化
   static initialize(initParam: InitParamIn): void {
     let uiContext: common.UIAbilityContext | undefined = TMMeetingSDK.getUIContext();
     TMSDK.initialize(uiContext, initParam, new MyCallback());
   }

onSDKInitializeResult(code: number, msg: string): object| void {
       hilog.info(0x0000, TAG, `SDKInitialize result code: ${code}, msg : ${msg}`);
       TMMeetingSDK.processCallbackMsg("OnSDKInitializeResult", "CommonService", code, false, msg, "");
     }

Navigation改造:

腾讯会议HarmonyOS SDK接入指导路由使用说明推荐使用Navigation实现路由跳转。当前HarmonyOS生态提供的Electron构建包模板工程中暂未支持Navigation跳转,需要对模板工程进行改造。在electron/src/main/ets 下新建EntryPage.ets页面:

代码语言:ts
复制
      import { Index } from "./Index";
      import { NodeHandleWindow } from "./NodeHandleWindow";
      let storage = LocalStorage.getShared();
      @Entry(storage)
      @Component
      export struct EntryPage {
        @State isSupportNodeHandleFeature:boolean = false;
        aboutToAppear(): void {
          let isTMWindow:boolean = AppStorage.get('isTMWindow') as boolean
          if (isTMWindow) {
            AppStorage.setOrCreate('PathStack', this.pathStack);
            AppStorage.setOrCreate('isTMWindow',false);
          }
          this.isSupportNodeHandleFeature = AppStorage.get('isSupportNodeHandleFeature') as boolean;
        }

        private  pathStack: NavPathStack = new NavPathStack()
        build() {
          Navigation(this.pathStack) {
            if(this.isSupportNodeHandleFeature) {
              NodeHandleWindow()
            } else {
              Index()
            }
          }
          .width('100%')
          .height('100%')
          .hideToolBar(true)
          .hideTitleBar(true)
          .mode(NavigationMode.Stack)
        }
      }

在electron\src\main\resources\base\profile\main_pages.json中添加

代码语言:txt
复制
      "pages/EntryPage",

修改web_engine\src\main\ets\ability\WebBaseAbility.ets中protected getContentPath(): string { ... }整体替换成

代码语言:ts
复制
protected getContentPath(): string {
AppStorage.setOrCreate('isSupportNodeHandleFeature', this.nativeContext.IsSupportNodeHandleFeature());
return 'pages/EntryPage';
}
  1. Electron层(JS): 引入编译的'wemeet_sdk.node后,通过addon.AddJsCallback()注册回调,通过addon.InitWemeetSDK()接口初始化sdk,在回调方法中解析JSON,通过func字段判断初始化回调方法OnSDKInitializeResult,其他接口调用和回调与初始化接口类似。
代码语言:js
复制
const addon = require('wemeet_sdk.node');
   addon.AddJsCallback((msg)=>{
     const obj = JSON.parse(msg);
     console.log(Tag, "收到回调:", msg);
     //初始化
     if(obj.func === 'OnSDKInitializeResult') {
       console.log(Tag,"OnSDKInitializeResult,code:",obj.code,"msg:",obj.msg);
   
       return;
     }
   });
   const addon = require('wemeet_sdk.node');
   addon.AddJsCallback((msg)=>{
     const obj = JSON.parse(msg);
     console.log(Tag, "收到回调:", msg);
     //初始化
     if(obj.func === 'OnSDKInitializeResult') {
       console.log(Tag,"OnSDKInitializeResult,code:",obj.code,"msg:",obj.msg);
   
       return;
     }
   });
   addon.InitWemeetSDK("sdkId","sdkToken", "logPath", "测试应用", "iconPath", 'zh-cn')x

关键问题

1.问题现象:集成中遇到CPPCrash,Signal:SIGSEGV(SEGV_MAPERR)@0x0000000000000590 probably caused by NULL pointer dereference

问题原因:libelectron.so依赖的libc++_shared.so和腾讯会议libwemeet_platform.so依赖的libc++_shared.so是不同LLVM版本工具编译的,存在冲突。

解决方案: 构建libelectron.so时采用静态链接libc++_static.a的方式,解耦libelectron.so对libc++_shared.so的动态依赖,可使用electron 34.8.4及以上版本,libelectron.so是采用静态链接libc++_static.a的。在编译libwemeet_sdk_adapter.so时修改为静态链接libc++_static.a,在/adapter/CMakeLists.txt中增加如下配置:

代码语言:txt
复制
# 静态链接libc++_static.a
target_link_options(${TARGETFILENAME} PRIVATE -nostdlib++)
target_link_options(${TARGETFILENAME} PRIVATE ${OHOS_SDK_PATH}/openharmony/native/llvm/lib/aarch64-unknown-linux-ohos/libc++_static.a)

代码附件

electron集成腾讯会议sdkdemo

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档