@plasosdk/plaso-electron-sdk
v1.3.11
Published
伯索课堂Electron SDK
Readme
Plaso SDK for Electron
目录
环境支持
- MacOS:x86-64、arm64
- Windows:ia32、x64
- Electron:14.0.0~22.3.27
安装
安装 @electron/remote
npm install @electron/remote --global-style --legacy-peer-deps安装 plaso-electron-sdk
npm install @plasosdk/plaso-electron-sdk --global-style注意: 不同平台需要单独安装,尤其是MacOS,请分别在Intel芯片和Apple芯片的电脑上安装
electron-builder打包特别说明
extraResources配置
⚠️ 注意:该配置在 1.3.7 版本有所调整。如果你正在从 < 1.3.7 升级到 >= 1.3.7,请参照下列说明进行调整。
agora-electron-sdk-v4包是在安装plaso-electron-sdk时通过脚本动态安装的,不在dependencies依赖树中,electron-builder打包时不会包含它。为了保留它,需要配置extraResources来单独拷贝一份,把它包含进来。
{
"extraResources": [
{
"from": "node_modules/@plasosdk/plaso-electron-sdk/node_modules/agora-electron-sdk-v4",
"to": "app/node_modules/@plasosdk/plaso-electron-sdk/node_modules/agora-electron-sdk-v4",
"filter": [
"**/*"
]
},
{
"from": "node_modules/@plasosdk/plaso-electron-sdk/node_modules/agora-electron-sdk-v4/node_modules",
"to": "app/node_modules/@plasosdk/plaso-electron-sdk/node_modules/agora-electron-sdk-v4/node_modules",
"filter": [
"**/*"
]
}
]
}同时,由于这个包是通过extraResources进行拷贝的,需要在签名脚本中额外配置这个包的路径。
关闭asar
需要关闭asar,否则SDK会无法加载到node_modules。关闭方式详见electron-buider官方文档。
{
"asar": false, // 关闭asar
}MacOS赋予文件执行权限
打MacOS的包时有几个需要额外进行授权的可执行文件。建议通过额外的脚本来执行。
下面的可执行文件是在安装@plasosdk/plaso-electron-sdk时动态下载的,因此授权的脚本需要保证是在文件下载之后。
// 截图会用到
chmod +x node_modules/@plasosdk/plaso-electron-sdk/lib/flameshot.app
// 桌面共享会用到
chmod +x node_modules/@plasosdk/plaso-electron-sdk/lib/PlasoALD/PlasoALD.scpt验证
MacOS打包完毕后,建议安装打好的包,验证上述操作是否成功。核对清单:
- 检查包内容中node_modules里面的agora-electron-sdk-v4包中是否包含node_modules,包含则说明打包正确。
- 上课测试截图功能是否正常,截图正常说明授权正确。
- 上课测试桌面共享功能,使用屏幕共享,并在浏览器里面播放一段视频,结束课堂后检查回放中能否听到桌面共享那一段播放的视频的声音,能听到说明授权正确。
使用
在主进程中使用
需要在主进程加载 @plasosdk/plaso-electron-sdk 依赖包
// electron 版本>=14.0.0 时:需要在主进程里 初始化、启动 remote
const remoteMain = require('@electron/remote/main');
remoteMain.initialize();
remoteMain.enable(mainWindow.webContents); // mainWindow: 主进程通过loadURL加载的那个渲染进程窗口
// plaso-electron-sdk中依赖@electron/remote,需要通过 initRemoteMain方法 传入 remoteMain
const { initRemoteMain } = require('@plasosdk/plaso-electron-sdk');
initRemoteMain(remoteMain);在渲染进程中使用
打开实时课堂/备课课堂方法入参类型定义
interface CreateClassWindowPamras {
/**
* 属性说明详见下方classOptions,打开实时课堂时为ILiveClassOptions,备课课堂为IPrepareClassOptions
*/
classOptions: ILiveClassOptions | IPrepareClassOptions;
electronWinOptions?: Electron.BrowserWindowConstructorOptions;
onClassWindowReadyFn?: (winId: number) => void
onClassWindowLeaveFn?: (winId: number) => void
onClassFinishedFn?: (meetingId: string) => void;
onSaveBoardFn?: (
params: {
fileInfo: FileParams[];
fileName?: string;
},
callback: (result: boolean) => void,
) => void;
onOpenResourceCenterFn?: () => void;
onGetExtFileNameFn?: (info: any[], ...args: any[]) => Promise<string>;
onGetPreParseFileNameFn?: (info: any[], option: { suffix: string }) => Promise<string>;
}打开实时课堂
SDK打开一个新的BrowserWindow来加载实时课堂UI界面,示例代码如下:
const PlasoElectronSdk = window.require('@plasosdk/plaso-electron-sdk');
const createLiveClassWindowParams: CreateClassWindowPamras = { classOptions: { query } };
PlasoElectronSdk.createLiveClassWindow(createLiveClassWindowParams);createLiveClassWindow参数说明
classOptions
interface ILiveClassOptions {
/** 进入课堂必须的签名字符串 */
query: string;
/** 当前进入课堂用户的头像,取值为https全地址 */
displayAvatarUrl?: string;
/** 课堂中的成员,会在成员列表中呈现 */
classMembers?: UserInfo[];
/** 是否启用降噪 */
enableENC?: boolean;
/** 是否启用3A */
enableRTC3A?: boolean;
/** 是否启用新桌面共享 */
enableLiveNewShare?: boolean;
/** 是否启用触屏设备上的新桌面共享 */
enableLiveNewShareInTouch?: boolean;
/** 是否启用部分屏幕区域共享 */
enableLiveNewShareRegion?: boolean;
/** 是否启用签到 */
enableLiveSign?: boolean;
/** 是否启用资料中心(云盘) */
supportShowResourceCenter?: boolean;
/** 是否支持保存板书 */
supportSaveBoard?: boolean;
/** 是否启用摄像头常驻 */
residentCamera?: boolean;
}
interface UserInfo {
/** 用户名,唯一标识 */
loginName: string;
/** 用户昵称 */
name: string;
/** 用户角色 */
upimeRole: 'speaker' | 'assistant' | 'listener';
/** 用户头像 */
displayAvatarUrl?: string;
}| 参数名称 | 是否必填 | 类型 | 参数描述 |
| --- | --- | --- | --- |
| query | 是 | string | 带签名的字符串,具体拼接逻辑见下文query属性说明 |
| displayAvatarUrl | 否 | string | 用户头像地址。不传默认使用用户昵称作为头像。 |
| classMembers | 否 | UserInfo[] | 成员列表中显示的人员,最多支持 2000 人。人员超过2000人需要联系伯索平台进行额外申请。 |
| enableRTC3A | 否 | boolean | 是否启用3A:回音消除、噪声抑制、自动增益控制。默认启用。 |
| enableENC | 否 | boolean | 是否启用降噪控制,开启后设置界面可以设置基础降噪或增强降噪。默认启用。 |
| enableLiveNewShare | 否 | boolean | 是否启用新桌面共享。启用后共享屏幕/部分屏幕区域时会对课堂窗口做透明化处理,需要选中工具栏上的交互模式工具来交互桌面应用,可能会触发Electron长时间透传鼠标事件导致的透传异常问题,谨慎使用。默认不启用。 |
| enableLiveNewShareInTouch | 否 | boolean | 是否在触屏设备上启用新桌面共享,启用后通过切换演示模式和互动模式来交互桌面应用和课堂中的工具。默认不启用。 |
| enableLiveNewShareRegion | 否 | boolean | 是否启用部分屏幕区域共享。 |
| enableLiveSign | 否 | boolean | 是否启用签到,启用后工具箱中显示签到按钮。默认不启用。 |
| supportShowResourceCenter | 否 | boolean | 是否启用资料中心(云盘),启用后在工具栏显示资料中心(云盘)按钮,此按钮需要配合onOpenResourceCenterFn回调一起使用。默认不启用。 |
| supportSaveBoard | 否 | boolean | 是否启用保存板书,启用后工具箱中显示保存当页板书按钮,需要配合云盘使用。默认不启用。 |
| residentCamera | 否 | boolean | 是否启用常驻摄像头:只对学生或游客生效。启用后学生进入课堂后摄像头常驻开启,没有开启摄像头权限时也会开启。默认不启用。 |
query属性说明
query本质上是根据IQueryParams对象中的字段生成带签名的字符串,query示例如下:
const query = 'appId=plaso&appType=liveclassSDK&d_dimension=1280x720&enableNewClassExam=1&loginName=t_1&mediaType=video&meetingId=test_1742442362&meetingType=public&signature=A226198904A392579B98987FB4CD5478AB3F5587&userName=%E8%80%81%E5%B8%881&userType=speaker&validBegin=1742442364&validTime=99999'interface IQueryParams {
appId: string;
validBegin: number;
validTime: number;
mediaType: string;
meetingType: string;
meetingId: string;
userType: string;
loginName: string;
userName: string;
d_dimension: string;
topic?: string;
endTime?: number;
onlineMode?: number;
d_delayEndTimes?: number;
d_delayEnd?: number;
d_enableAvatarFreeScale?: number;
d_enableObjectEraser?: number;
d_vote?: number;
d_sharpness?: number;
isNewMT?: number;
enableNewClassExam?: number;
d_enableReRecording?: number;
d_restrictAssistantPerm?: number;
}| 字段名称 | 是否必填 | 类型 | 字段描述 |
| --- | --- | --- |--- |
| appId | 是 | string | 在申请接入时,伯索平台给予的 appId |
| validBegin | 是 | number | 签名query生效的起始时间,Unix Epoch 时间戳,单位为秒 |
| validTime | 是 | number | 签名query的有效期,从validBegin开始计算,单位为秒 |
| mediaType | 是 | string | 媒体类型,取值:audio: 音频课堂video: 视频课堂
| meetingType | 是 | string | 课堂类型,固定值为public |
| meetingId | 是 | string | 课堂ID,唯一标识该课堂;使用ASSIIC字符,不得包含/,,空格等;长度在40字节以内的字符串。 |
| userType | 是 | string | 用户角色类型,取值:speaker: 课堂的主讲者,有控制其他listener是否可板书/发言的权限。课堂中只能有一个主讲assistant:助教,辅助主讲授课的角色,在课堂中的权限与主讲基本一致。课堂中可以有多个助教listener:听众,可以理解为学生 |
| loginName | 是 | string | 唯一标识该用户的id,不能为空,相同的loginName进入课堂时,后面进入的会使前面进入的登出 |
| userName | 是 | string | 用户昵称,头像缺省时会显示 |
| d_dimension | 是 | string | 固定值为1280x720,定义界面尺寸为16:9界面 |
| topic | 否 | string | 课堂名称,在标题栏上显示 |
| endTime | 否 | number | 课堂结束时间,格式为 Unix Epoch 时间戳,单位为秒 |
| onlineMode | 否 | number | 当mediaType为video时生效,表示最大能开启的listener的摄像头的个数,取值:1612默认值为6 |
| d_delayEndTimes | 否 | number | 单节课最大延时下课次数,取值:1234默认没有延时 |
| d_delayEnd | 否 | number | 单次延时时间,单位为秒,取值:5 * 6010 * 6020 * 6030 * 60默认20分钟 |
| d_enableAvatarFreeScale | 否 | number | 是否开启头像任意比例缩放,仅上课中各端同步,录制头像时历史课堂不支持课堂调整的任意比例,取值:0:关闭1:开启默认关闭 |
| d_enableObjectEraser | 否 | number | 是否启用新版板书,新版板书的橡皮擦支持对象擦除,传入大于0的值启用新版板书。取值:0: 不启用,橡皮功能为点擦1: 支持对象擦除手写3: 支持对象擦除手写+文本5: 支持对象擦除手写+图形7: 支持对象擦除手写+文本+图形默认不启用 |
| d_vote | 否 | number | 是否启用投票工具。取值:0:关闭1:开启默认关闭 |
| d_sharpness | 否 | number | 当mediaType为video时生效,表示摄像头画面的清晰度。取值:10: 360p20: 720p30: 1080p默认值为10 |
| isNewMT | 否 | number | 是否支持移动授课模式,建议传1 |
| recordAvator | 否 | string | 传入老师或助教的loginName表示录制对应人的头像,传入recordScreen时SDK会忽略此参数 |
| recordScreen | 否 | string | 传入screen表示录制屏幕(当前仅支持录制老师屏幕)|
| enableNewClassExam | 否 | number | 是否启用新版随堂测,取值:0: 不启用1: 启用选择题2: 启用填空题3: 启用选择题+填空题默认不启用,建议传3 |
| d_enableReRecording | 否 | number | 是否允许老师/助教重新录制,取值:0: 不允许老师和助教重新录制1: 仅允许老师重新录制2: 仅允许助教重新录制3: 允许老师和助教重新录制默认值为3 |
| d_restrictAssistantPerm | 否 | number | 是否启用限制助教权限,取值:0: 不启用1: 启用默认不启用 |
根据 queryParams 对象生成签名字符串
注意:为了安全和各端签名统一,建议将签名的计算放在服务端,前端通过接口获取带签名的query
将queryParams传入签名函数生成签名,签名示例参考:签名示例
获取signature后将signature加到queryParams中作为一个字段。
queryParams.signature = signature;获取完整的queryParams后,遍历queryParams生成query字符串,每个字段的值用encodeURIComponent编码。完整流程示例如下:
function genSignature(params) {
return 'xxx';
}
function genQuery(params) {
const keys = Object.keys(params).sort();
const res = [];
for (const key of keys) {
res.push(key + '=' + encodeURIComponent(params[key]));
}
return res.join('&');
}
const queryParams = {
appId: 'xxx';
validBegin: 175645206;
validTime: 3600;
mediaType: 'video';
meetingType: 'public';
meetingId: 1234;
userType: 'speaker';
loginName: 'hello';
userName: 'world';
d_dimension: '1280x720';
};
const signature = genSignature(queryParams);
queryParams.signature = signature;
const query = genQuery(queryParams);打开备课课堂
SDK打开一个新的BrowserWindow来加载备课课堂UI界面,示例代码如下:
const PlasoElectronSdk = window.require('@plasosdk/plaso-electron-sdk');
const createPrepareClassWindowParams: CreateClassWindowPamras = { classOptions: { loginName: 'hello', userName: 'world' } };
PlasoElectronSdk.createPrepareClassWindow(createPrepareClassWindowParams);createPrepareClassWindow参数说明
classOptions
interface IPrepareClassOptions {
loginName: string;
userName: string;
displayAvatarUrl?: string;
topic?: string;
d_enableObjectEraser?: number;
}| 参数名称 | 是否必填 | 类型 | 参数描述 | | --- | --- | --- | --- | | loginName | 是 | string | 唯一标识该用户的id,不能为空,相同的loginName进入课堂时,后面进入的会使前面进入的登出 | | userName | 是 | string | 用户昵称,头像缺省时会显示 | | displayAvatarUrl | 否 | string | 用户头像地址。不传默认使用用户昵称作为头像。 | | d_enableObjectEraser | 否 | number | 是否启用新版板书,新版板书的橡皮擦支持对象擦除,传入大于0的值启用新版板书。取值:0: 不启用,橡皮功能为点擦1: 支持对象擦除手写3: 支持对象擦除手写+文本5: 支持对象擦除手写+图形7: 支持对象擦除手写+文本+图形默认不启用 |
打开实时课堂/备课课堂通用参数说明
electronWinOptions
即将弃用: 不推荐传入,SDK内部默认设置了一些窗口参数,为了保证最佳体验,不要传入此参数。
Electron的窗口参数,详情参考 Electron官方文档。
type electronWinOptions = Electron.BrowserWindowConstructiorOptions;onClassWindowReadyFn
// 课堂窗口打开渲染成功后的回调,回调参数为 窗口id
type onClassWindowReadyFn = (winId: number) => void;onClassWindowLeaveFn
// 课堂窗口关闭后的回调,回调参数为 窗口id
type onClassWindowLeaveFn = (winId: number) => void;onClassFinishedFn
// 课堂结束后的回调,回调参数为 课堂的meetingId
type onClassFinishedFn = (meetingId: string) => void;onSaveBoardFn
注意:
1、filePath 对应的文件资源,用户保存在自己的云端时,需要把 本次保存的备课文件的 相关资源放在同一特定目录下
2、每次保存生成的备课文件 都放在一个新的目录下,不同的备课文件不能共用一个目录
3、备课保存的资源文件名 不能更改,info.pb 是固定的文件名
// 保存板书方法,具体的保存逻辑由外部实现,取消保存板书时,callback传false, 不然传true
type FileParams = {
/** 备课相关资源文件的本地地址*/
filePath: string[];
/** 备课文件 类型*/
fileType: 'png' | 'pb';
};
type onSaveBoardFn = (
params: {
fileInfo: FileParams[];
fileName?: string;
},
callback: (result: boolean) => void,
) => void;onOpenResourceCenterFn
详见云盘接入
// 通知外部用户打开自己的资料中心,资料中心的具体ui和逻辑由外部用户自己实现
// 推荐:通过onClassWindowReadyFn回调的winId拿到课堂窗口实例,用户的云盘通过一个BrowserWindow
// 和课堂窗口组成父子窗口,云盘是子窗口,并且云盘窗口打开时保持置顶,这样来保证云盘打开时始终可见并且跟随课堂窗口。
type onOpenResourceCenterFn = () => void;onGetExtFileNameFn
详见云盘接入
type onGetExtFileNameFn = (info: any, ...args: any[]) => Promise<string>;onGetPreParseFileNameFn
详见云盘接入
type onGetPreParseFileNameFn = (info: any, option: { suffix: string }) => Promise<string>;API参考
PlasoElectronSdk.initLogConfig
该方法用于设置SDK日志位置,SDK崩溃时会在同级目录下生成reports文件夹存储 dump,在调用createLiveClassWindow前设置,不调用SDK会在默认位置写入日志,默认位置如下:
// 获取日志路径的代码如下
require('path').join(require('electron').app.getPath('userData'), 'P403FileTemp');
// Windows:C:\Users\${userName}\AppData\Roaming\${appName}\P403FileTemp
// Mac:/Users/${userName}/Application\ Support/${appName}/P403FileTemp参考示例:
// 代码示例
const PlasoElectronSdk = window.require('@plasosdk/plaso-electron-sdk');
const logDir = 'C:/Users/userName/Desktop/electronDemo/electron12.0.18_x32/resources/app';
PlasoElectronSdk.initLogConfig(logDir);重要:用户需要对日志目录做定期清理和及时上传日志到自己的服务器或OSS。推荐在退出SDK后做一次清理和上传,建议清理创建时间超过7天的日志即可。 相关代码示例如下:
import fs from 'fs';
import path from 'path';
/**
* 清理创建时间超过7天的日志
* @param logDir 日志目录
*/
function clearExpireLogs(logDir: string) {
try {
const logFile = fs.readdirSync(logDir).filter((fileName) => /\.(log|dmp|ips|diag)/.test(path.extname(fileName)));
logFile.forEach((fileName) => {
const fileInfo = fs.statSync(path.join(logDir, fileName));
const fileDate = new Date(fileInfo.birthtime).setHours(0, 0, 0, 0).valueOf();
const currentDate = new Date().setHours(0, 0, 0, 0);
if (currentDate - fileDate > 7 * 24 * 60 * 60 * 1000) {
fs.unlinkSync(path.join(logDir, fileName));
}
});
} catch (e) {
console.error('clear expire log error', e);
}
}
/**
* 收集崩溃日志
* @param logDir 日志目录
* @param pendingUploadDir 待上传目录
*/
function collectCrashLogs(logDir: string, pendingUploadDir: string) {
const platform = require('os').platform();
switch (platform) {
case 'darwin':
const newDumpFolder = path.join(logDir, '/new/');
const pendingDumpFolder = path.join(logDir, '/pending/');
const completedDumpFolder = path.join(logDir, '/completed/');
const diagnosticReports = path.normalize('/Library/Logs/DiagnosticReports');
collectDumpFiles(newDumpFolder);
collectDumpFiles(pendingDumpFolder);
collectDumpFiles(completedDumpFolder);
collectSystemLogs(diagnosticReports);
break;
case 'win32':
const reportsDumpFolder = path.join(logDir, '/reports/');
collectDumpFiles(reportsDumpFolder);
break;
default:
break;
}
function collectSystemLogs(folder: string) {
try {
const files = fs.readdirSync(folder);
const todayDate = new Date();
const formatDate = `${todayDate.getFullYear()}-${(todayDate.getMonth() + 1).toString().padStart(2, '0')}-${todayDate
.getDate()
.toString()
.padStart(2, '0')}`;
files.forEach((name) => {
const reg = new RegExp(`Electron Helper.*${formatDate}`);
if (name && name.match(reg)) {
fs.copyFileSync(path.join(folder, name), path.join(pendingUploadDir, name));
}
});
} catch (e) {
console.error(e);
}
}
function collectDumpFiles(folder: string) {
try {
const files = fs.readdirSync(folder);
files.forEach((name) => {
if (name) {
fs.copyFileSync(path.join(folder, name), path.join(pendingUploadDir, name));
fs.unlinkSync(path.join(folder, name));
}
});
} catch (e) {
console.error(e);
}
}
}
/**
* 收集业务日志
* @param logDir 日志目录
* @param pendingUploadDir 待上传目录
*/
function collectBussinessLogs(logDir: string, pendingUploadDir: string) {
const logFiles = fs.readdirSync(logDir).filter((fileName) => /\.(log|dmp|ips|diag)/.test(path.extname(fileName)));
logFiles.forEach((fileName) => {
fs.copyFileSync(path.join(logDir, fileName), path.join(pendingUploadDir, fileName));
});
}
/**
* 收集日志
* @param logDir 日志目录
* @param pendingUploadDir 待上传目录
*/
function collectLogs(logDir: string, pendingUploadDir: string) {
collectCrashLogs(logDir, pendingUploadDir);
collectBussinessLogs(logDir, pendingUploadDir);
}
/**
* 压缩日志
* @param pendingUploadDir 待上传目录
*/
function zipLogs(pendingUploadDir: string): Promise<string> {
return new Promise((resolve) => {
const zipLogPath = path.join(pendingUploadDir, 'xxx.zip'); // 这边的xxx.zip取名时建议将用户名以及上传时间拼接上去,方便查询
// TODO: 这边做压缩,压缩完毕后将zip文件路径返回
resolve(zipLogPath);
});
}
/**
* 上传日志到服务器
* @param filePath zip文件路径
*/
function uploadToServer(filePath: string): Promise<boolean> {
return new Promise((resolve) => {
// TODO: 这边做上传
resolve(true);
});
}
/**
* 上传日志
* @param logDir 日志目录
*/
export function uploadLogs(logDir: string): Promise<boolean> {
return new Promise(async (resolve) => {
try {
// 上传前先清理过期的日志,避免垃圾日志太多
clearExpireLogs(logDir);
// 创建待上传目录
const pendingUploadDir = path.join(logDir, 'pendingUpload');
if (fs.existsSync(pendingUploadDir)) {
fs.rmdirSync(pendingUploadDir, { recursive: true });
}
fs.mkdirSync(pendingUploadDir);
// 将日志复制到待上传目录
collectLogs(logDir, pendingUploadDir);
// 压缩待上传目录
const zipName = await zipLogs(pendingUploadDir);
// 将压缩后的日志上传到服务器
await uploadToServer(zipName);
// 上传完成后删除临时文件
fs.unlinkSync(zipName);
fs.rmdirSync(pendingUploadDir, { recursive: true });
resolve(true);
} catch (error) {
resolve(false);
}
});
}PlasoElectronSdk.getVersion
返回包的版本,格式:x.x.x
type getVersion = () => string;PlasoElectronSdk.createLiveClassWindow
创建实时课堂窗口
function createLiveClassWindow(params: CreateClassWindowPamras): void;PlasoElectronSdk.createPrepareClassWindow
创建备课课堂窗口
function createLiveClassWindow(params: CreateClassWindowPamras): void;PlasoElectronSdk.insertObject
用户从自己的资料中心往实时课堂/备课课堂插入WORD/EXCEL/PPT/PDF、音视频、备课文件等,详见云盘接入
资料中心
- 进课堂时,对象
classOptions.supportShowResourceCenter需要是 true - 资料中心的接入还涉及
onOpenResourceCenterFn、PlasoElectronSdk.insertObject、onGetExtFileNameFn、onGetPreParseFileNameFn几个函数,具体用法详见云盘接入的showResourceCenter、insertObject、getExtFileName、getPreParseFileName章节
播放历史课堂
1、参考文档: 播放器SDK-Web播放器
2、当课堂中insertObject使用了info属性时,SDK内部需要外部传入getExtFileName,因此历史课堂需要使用jssdk的接入方式, 详见:Web播放器-jssdk接入
3、当课堂中insertObject插入了预解析资源时,SDK内部需要外部传入getPreParseFileName,因此历史课堂需要使用jssdk的接入方式,同上。
注意点
(1)用户的课堂外主窗口销毁时需要销毁课堂窗口
(2)基于 此包封装新包时:注意 @electron/remote 这个包的位置需要 和新包处于同级目录,需要把 和该包同级的@electron/remote 移到新包的同级目录处
(3)确保 仅最后的 node_moudles 的顶层有 @electron/remote
Q&A
进课堂遇到闪退/崩溃怎么办?
- 检查应用程序路径中是否存在中文,路径中不应出现中文
可通过在主进程中,用
console.log(require.resolve('@plasosdk/plaso-electron-sdk'))来检查路径 - 如果是Mac,进课堂会需要麦克风权限。打开系统设置->隐私与安全性->麦克风,查看列表里你的应用程序是否被授权。
