nodeplayer-addon 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +450 -5
- package/dist/index.mjs +450 -5
- package/dist/video-player.js +436 -0
- package/dist/video-player.mjs +429 -1
- package/dist/video-player.umd.js +429 -1
- package/docs/introduction.md +26 -0
- package/docs/quick-start.md +265 -0
- package/docs/react-frontend.md +328 -0
- package/docs/vue-frontend.md +330 -0
- package/package.json +7 -6
- package/SKILL.md +0 -684
package/dist/index.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
3
|
+
var require$$0 = require('events');
|
|
4
|
+
var require$$1 = require('fs');
|
|
5
|
+
var require$$2 = require('path');
|
|
6
|
+
var require$$3 = require('module');
|
|
7
7
|
|
|
8
8
|
function getDefaultExportFromCjs (x) {
|
|
9
9
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
@@ -13,7 +13,452 @@ function commonjsRequire(path) {
|
|
|
13
13
|
throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
var js
|
|
16
|
+
var js;
|
|
17
|
+
var hasRequiredJs;
|
|
18
|
+
|
|
19
|
+
function requireJs () {
|
|
20
|
+
if (hasRequiredJs) return js;
|
|
21
|
+
hasRequiredJs = 1;
|
|
22
|
+
const { EventEmitter } = require$$0;
|
|
23
|
+
const { existsSync, mkdirSync, writeFileSync } = require$$1;
|
|
24
|
+
const { join, dirname } = require$$2;
|
|
25
|
+
const { createRequire } = require$$3;
|
|
26
|
+
const { platform, arch } = process;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 获取原生 require,绕过 webpack/vite 的模块替换
|
|
30
|
+
* - webpack 环境使用 __non_webpack_require__
|
|
31
|
+
* - 其他环境使用 createRequire() 基于运行时目录创建
|
|
32
|
+
*/
|
|
33
|
+
let _nativeRequire = null;
|
|
34
|
+
function getNativeRequire() {
|
|
35
|
+
if (!_nativeRequire) {
|
|
36
|
+
if (typeof __non_webpack_require__ !== 'undefined') {
|
|
37
|
+
_nativeRequire = __non_webpack_require__;
|
|
38
|
+
} else {
|
|
39
|
+
try {
|
|
40
|
+
// 基于运行时工作目录创建 require,不受打包工具对 __dirname/__filename 的篡改
|
|
41
|
+
_nativeRequire = createRequire(process.cwd() + '/package.json');
|
|
42
|
+
} catch (e) {
|
|
43
|
+
_nativeRequire = commonjsRequire;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return _nativeRequire
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 解析包根目录(兼容打包后 __dirname 失效)
|
|
52
|
+
* - 优先通过 require.resolve 找到包在 node_modules 中的真实路径
|
|
53
|
+
* - __dirname 检查作为 fallback(未打包环境)
|
|
54
|
+
*/
|
|
55
|
+
function resolvePackageRoot() {
|
|
56
|
+
const req = getNativeRequire();
|
|
57
|
+
|
|
58
|
+
// Strategy 1: 通过 require.resolve 找到包的真实安装路径
|
|
59
|
+
try {
|
|
60
|
+
const pkgJsonPath = req.resolve('nodeplayer-addon/package.json');
|
|
61
|
+
return dirname(pkgJsonPath)
|
|
62
|
+
} catch (e) {
|
|
63
|
+
// ignore
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Strategy 2: __dirname 上级存在 package.json(未打包环境)
|
|
67
|
+
const fallback = join(__dirname, '..');
|
|
68
|
+
if (existsSync(join(fallback, 'package.json'))) {
|
|
69
|
+
return fallback
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return fallback
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 延迟加载 native 模块,优先从预编译目录加载
|
|
76
|
+
let native = null;
|
|
77
|
+
function getNative() {
|
|
78
|
+
if (!native) {
|
|
79
|
+
const req = getNativeRequire();
|
|
80
|
+
const basePath = resolvePackageRoot();
|
|
81
|
+
|
|
82
|
+
const prebuildPath = join(basePath, 'prebuilds', `${platform}-${arch}`, 'node_player.node');
|
|
83
|
+
const localPath = join(basePath, 'build', 'Release', 'node_player.node');
|
|
84
|
+
|
|
85
|
+
if (existsSync(prebuildPath)) {
|
|
86
|
+
native = req(prebuildPath);
|
|
87
|
+
} else if (existsSync(localPath)) {
|
|
88
|
+
native = req(localPath);
|
|
89
|
+
} else {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`[nodeplayer-addon] Cannot find native module.\n` +
|
|
92
|
+
` Searched:\n` +
|
|
93
|
+
` Prebuild: ${prebuildPath}\n` +
|
|
94
|
+
` Local: ${localPath}\n` +
|
|
95
|
+
` Resolved root: ${basePath}\n` +
|
|
96
|
+
` __dirname: ${__dirname}\n` +
|
|
97
|
+
` Platform: ${platform}-${arch}\n\n` +
|
|
98
|
+
`If using webpack/vite, add to externals:\n` +
|
|
99
|
+
` webpack: externals: { 'nodeplayer-addon': 'commonjs nodeplayer-addon' }\n` +
|
|
100
|
+
` vite: optimizeDeps: { exclude: ['nodeplayer-addon'] }`
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return native
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============ Electron IPC 管理 ============
|
|
108
|
+
const _ipcPlayers = new Map();
|
|
109
|
+
const _ipcChannels = [
|
|
110
|
+
'player:create',
|
|
111
|
+
'player:start',
|
|
112
|
+
'player:stop',
|
|
113
|
+
'player:destroy',
|
|
114
|
+
'player:startRecord',
|
|
115
|
+
'player:stopRecord',
|
|
116
|
+
'player:screenshot',
|
|
117
|
+
'player:getMediaInfo'
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* NodePlayer - RTSP/RTMP/KMP 流媒体播放器
|
|
122
|
+
*
|
|
123
|
+
* 支持的事件:
|
|
124
|
+
* - 'event': (code, msg) => {} - 管线事件
|
|
125
|
+
* - 'info': (info) => {} - 流信息
|
|
126
|
+
* - 'data': (buffer) => {} - fMP4 分片数据
|
|
127
|
+
*
|
|
128
|
+
* 使用示例:
|
|
129
|
+
* // 正式授权模式
|
|
130
|
+
* const player = new NodePlayer({ licensePath: '/path/to/license.dat' })
|
|
131
|
+
* player.on('info', (info) => console.log(info))
|
|
132
|
+
* player.on('data', (buffer) => console.log(buffer.length))
|
|
133
|
+
* player.start('rtsp://...')
|
|
134
|
+
* player.startRecord('./record.mp4')
|
|
135
|
+
* // ...
|
|
136
|
+
* player.stopRecord()
|
|
137
|
+
* player.stop()
|
|
138
|
+
*
|
|
139
|
+
* // 试用模式(无需许可证,累计 10 分钟)
|
|
140
|
+
* const player = new NodePlayer()
|
|
141
|
+
* player.start('rtsp://...')
|
|
142
|
+
*/
|
|
143
|
+
class NodePlayer extends EventEmitter {
|
|
144
|
+
/**
|
|
145
|
+
* @param {object} [options] - 配置选项
|
|
146
|
+
* @param {string} [options.licensePath] - 许可证文件路径。
|
|
147
|
+
* 传入有效路径 → 正式授权模式;
|
|
148
|
+
* 不传或空字符串 → 试用模式(累计 10 分钟)。
|
|
149
|
+
*/
|
|
150
|
+
constructor(options) {
|
|
151
|
+
super();
|
|
152
|
+
const opts = options || {};
|
|
153
|
+
this._player = new (getNative().NodePlayer)(opts);
|
|
154
|
+
this._started = false;
|
|
155
|
+
this._trialMode = !opts.licensePath;
|
|
156
|
+
|
|
157
|
+
// 转发原生事件
|
|
158
|
+
this._player.on('event', (code, msg) => {
|
|
159
|
+
this.emit('event', code, msg);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
this._player.on('info', (info) => {
|
|
163
|
+
this.emit('info', info);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
this._player.on('data', (buffer) => {
|
|
167
|
+
this.emit('data', buffer);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 是否处于试用模式
|
|
173
|
+
* @returns {boolean}
|
|
174
|
+
*/
|
|
175
|
+
get isTrialMode() {
|
|
176
|
+
return this._trialMode
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 启动管线(内部会先验证许可证)
|
|
181
|
+
* @param {string} url - 输入地址 (RTSP/RTMP/KMP)
|
|
182
|
+
* @returns {boolean} 是否成功启动
|
|
183
|
+
*/
|
|
184
|
+
start(url) {
|
|
185
|
+
if (this._started) {
|
|
186
|
+
throw new Error('Pipeline already started')
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
this._player.start(url);
|
|
190
|
+
this._started = true;
|
|
191
|
+
return true
|
|
192
|
+
} catch (err) {
|
|
193
|
+
this.emit('error', err);
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 停止管线
|
|
200
|
+
*/
|
|
201
|
+
stop() {
|
|
202
|
+
if (this._started) {
|
|
203
|
+
this._player.stop();
|
|
204
|
+
this._started = false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 开始录像
|
|
210
|
+
* @param {string} outputPath - 输出 MP4 文件路径
|
|
211
|
+
*/
|
|
212
|
+
startRecord(outputPath) {
|
|
213
|
+
if (!this._started) {
|
|
214
|
+
throw new Error('Pipeline not started')
|
|
215
|
+
}
|
|
216
|
+
this._player.startRecord(outputPath);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 停止录像(生成完整 MP4 文件)
|
|
221
|
+
*/
|
|
222
|
+
stopRecord() {
|
|
223
|
+
this._player.stopRecord();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ============ Electron IPC 静态方法 ============
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 注册 Electron ipcMain 处理器,将播放器操作桥接到渲染进程。
|
|
230
|
+
*
|
|
231
|
+
* 注册后,渲染进程可通过 preload 调用:
|
|
232
|
+
* - ipcRenderer.invoke('player:getMediaInfo', url) // 预探测(无需 id)
|
|
233
|
+
* - ipcRenderer.invoke('player:create', id, options)
|
|
234
|
+
* - ipcRenderer.invoke('player:start', id, url)
|
|
235
|
+
* - ipcRenderer.invoke('player:stop', id)
|
|
236
|
+
* - ipcRenderer.invoke('player:destroy', id)
|
|
237
|
+
* - ipcRenderer.invoke('player:startRecord', id, outputPath?)
|
|
238
|
+
* - ipcRenderer.invoke('player:stopRecord', id)
|
|
239
|
+
*
|
|
240
|
+
* 事件通过 mainWindow.webContents.send 推送到渲染进程:
|
|
241
|
+
* - player:event:${id} → { code, msg }
|
|
242
|
+
* - player:info:${id} → info
|
|
243
|
+
* - player:data:${id} → Buffer
|
|
244
|
+
*
|
|
245
|
+
* @param {object} ipcMain - Electron ipcMain
|
|
246
|
+
* @param {object} options
|
|
247
|
+
* @param {function} options.getWindow - 返回当前 BrowserWindow 的函数(窗口可重建)
|
|
248
|
+
* @param {string} [options.licensePath] - 许可证路径(不传则为试用模式)
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* // main.js
|
|
252
|
+
* const { ipcMain, app } = require('electron')
|
|
253
|
+
* const NodePlayer = require('nodeplayer-addon')
|
|
254
|
+
*
|
|
255
|
+
* NodePlayer.registerIpc(ipcMain, {
|
|
256
|
+
* getWindow: () => mainWindow,
|
|
257
|
+
* licensePath: app.isPackaged
|
|
258
|
+
* ? path.join(process.resourcesPath, 'license.dat')
|
|
259
|
+
* : path.join(__dirname, 'license.dat'),
|
|
260
|
+
* })
|
|
261
|
+
*/
|
|
262
|
+
static registerIpc(ipcMain, options = {}) {
|
|
263
|
+
const { getWindow, licensePath } = options;
|
|
264
|
+
|
|
265
|
+
const send = (channel, data) => {
|
|
266
|
+
const win = getWindow && getWindow();
|
|
267
|
+
if (win && !win.isDestroyed()) {
|
|
268
|
+
win.webContents.send(channel, data);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// 预探测:分析 URL 是否可连接 + 视频/音频参数 + 截图(与 player 实例无关)
|
|
273
|
+
// 返回 { success, info?: { video, audio, screenshot } }
|
|
274
|
+
ipcMain.handle('player:getMediaInfo', async (event, url) => {
|
|
275
|
+
try {
|
|
276
|
+
const info = await NodePlayer.getMediaInfo(url);
|
|
277
|
+
return { success: true, info }
|
|
278
|
+
} catch (e) {
|
|
279
|
+
return { success: false, error: e.message }
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
ipcMain.handle('player:create', (event, id, playerOptions) => {
|
|
284
|
+
if (_ipcPlayers.has(id)) {
|
|
285
|
+
return { success: false, error: 'Player already exists' }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const opts = playerOptions || {};
|
|
289
|
+
if (!opts.licensePath && licensePath) {
|
|
290
|
+
opts.licensePath = licensePath;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const player = new NodePlayer(opts);
|
|
294
|
+
|
|
295
|
+
player.on('event', (code, msg) => {
|
|
296
|
+
send(`player:event:${id}`, { code, msg });
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
player.on('info', (info) => {
|
|
300
|
+
send(`player:info:${id}`, info);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
player.on('data', (data) => {
|
|
304
|
+
send(`player:data:${id}`, data);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
_ipcPlayers.set(id, player);
|
|
308
|
+
return { success: true }
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
ipcMain.handle('player:start', (event, id, url) => {
|
|
312
|
+
const player = _ipcPlayers.get(id);
|
|
313
|
+
if (!player) {
|
|
314
|
+
return { success: false, error: 'Player not found' }
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
const result = player.start(url);
|
|
318
|
+
return { success: result }
|
|
319
|
+
} catch (e) {
|
|
320
|
+
return { success: false, error: e.message }
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
ipcMain.handle('player:stop', (event, id) => {
|
|
325
|
+
const player = _ipcPlayers.get(id);
|
|
326
|
+
if (!player) {
|
|
327
|
+
return { success: false, error: 'Player not found' }
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
player.stop();
|
|
331
|
+
return { success: true }
|
|
332
|
+
} catch (e) {
|
|
333
|
+
return { success: false, error: e.message }
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
ipcMain.handle('player:destroy', (event, id) => {
|
|
338
|
+
const player = _ipcPlayers.get(id);
|
|
339
|
+
if (!player) {
|
|
340
|
+
return { success: false, error: 'Player not found' }
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
player.stop();
|
|
344
|
+
_ipcPlayers.delete(id);
|
|
345
|
+
return { success: true }
|
|
346
|
+
} catch (e) {
|
|
347
|
+
return { success: false, error: e.message }
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
ipcMain.handle('player:startRecord', (event, id, outputPath) => {
|
|
352
|
+
const player = _ipcPlayers.get(id);
|
|
353
|
+
if (!player) {
|
|
354
|
+
return { success: false, error: 'Player not found' }
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
const savePath = outputPath || join(
|
|
358
|
+
process.cwd(),
|
|
359
|
+
`record_${id}_${Date.now()}.mp4`
|
|
360
|
+
);
|
|
361
|
+
const dir = dirname(savePath);
|
|
362
|
+
if (!existsSync(dir)) {
|
|
363
|
+
mkdirSync(dir, { recursive: true });
|
|
364
|
+
}
|
|
365
|
+
player.startRecord(savePath);
|
|
366
|
+
return { success: true, path: savePath }
|
|
367
|
+
} catch (e) {
|
|
368
|
+
return { success: false, error: e.message }
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
ipcMain.handle('player:stopRecord', (event, id) => {
|
|
373
|
+
const player = _ipcPlayers.get(id);
|
|
374
|
+
if (!player) {
|
|
375
|
+
return { success: false, error: 'Player not found' }
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
player.stopRecord();
|
|
379
|
+
return { success: true }
|
|
380
|
+
} catch (e) {
|
|
381
|
+
return { success: false, error: e.message }
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
ipcMain.handle('player:screenshot', (event, id, outputPath, base64Data) => {
|
|
386
|
+
try {
|
|
387
|
+
const savePath = outputPath || join(
|
|
388
|
+
process.cwd(),
|
|
389
|
+
`screenshot_${id}_${Date.now()}.jpg`
|
|
390
|
+
);
|
|
391
|
+
const dir = dirname(savePath);
|
|
392
|
+
if (!existsSync(dir)) {
|
|
393
|
+
mkdirSync(dir, { recursive: true });
|
|
394
|
+
}
|
|
395
|
+
writeFileSync(savePath, Buffer.from(base64Data, 'base64'));
|
|
396
|
+
return { success: true, path: savePath }
|
|
397
|
+
} catch (e) {
|
|
398
|
+
return { success: false, error: e.message }
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* 注销 Electron ipcMain 处理器,停止所有播放器并清理资源。
|
|
405
|
+
*
|
|
406
|
+
* @param {object} ipcMain - Electron ipcMain
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* mainWindow.on('closed', () => {
|
|
410
|
+
* NodePlayer.unregisterIpc(ipcMain)
|
|
411
|
+
* })
|
|
412
|
+
*/
|
|
413
|
+
static unregisterIpc(ipcMain) {
|
|
414
|
+
_ipcPlayers.forEach(player => {
|
|
415
|
+
try { player.stop(); } catch (e) { /* ignore */ }
|
|
416
|
+
});
|
|
417
|
+
_ipcPlayers.clear();
|
|
418
|
+
|
|
419
|
+
_ipcChannels.forEach(ch => ipcMain.removeHandler(ch));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ============ 流媒体探测(静态方法)============
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* 异步获取流媒体信息(不阻塞 Node 事件循环)。
|
|
426
|
+
*
|
|
427
|
+
* 用于在添加 URL 到播放器之前,先分析流:
|
|
428
|
+
* - 是否可连接
|
|
429
|
+
* - 视频/音频编码、分辨率、采样率
|
|
430
|
+
* - 截取首帧作为预览图(MJPEG Buffer)
|
|
431
|
+
*
|
|
432
|
+
* @param {string} url - 流地址 (RTSP/RTMP/KMP/HTTP...)
|
|
433
|
+
* @returns {Promise<{video: object, audio: object, screenshot: Buffer|null}>}
|
|
434
|
+
* - video: { codecId:number, width:number, height:number }
|
|
435
|
+
* codecId 为 AV_CODEC_ID_* 原始值(27=H264, 173=HEVC...)
|
|
436
|
+
* - audio: { codecId:number, sampleRate:number, channels:number }
|
|
437
|
+
* 无音频时三个字段均为 0
|
|
438
|
+
* - screenshot: MJPEG 二进制 Buffer;解码失败/无视频时为 null
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* const info = await NodePlayer.getMediaInfo('rtsp://...')
|
|
442
|
+
* console.log(info.video.width + 'x' + info.video.height)
|
|
443
|
+
* if (info.screenshot) {
|
|
444
|
+
* const base64 = info.screenshot.toString('base64')
|
|
445
|
+
* previewImg.src = 'data:image/jpeg;base64,' + base64
|
|
446
|
+
* }
|
|
447
|
+
*/
|
|
448
|
+
static getMediaInfo(url) {
|
|
449
|
+
if (typeof url !== 'string' || !url) {
|
|
450
|
+
// 同步抛出 TypeError,遵循 Node.js fs.promises.* 的惯例
|
|
451
|
+
throw new TypeError('url must be a non-empty string')
|
|
452
|
+
}
|
|
453
|
+
// 原生层返回 Promise;用 Promise.resolve 统一异常路径,避免被调用方漏 catch
|
|
454
|
+
return Promise.resolve(getNative().getMediaInfo(url))
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
js = NodePlayer;
|
|
460
|
+
return js;
|
|
461
|
+
}
|
|
17
462
|
|
|
18
463
|
var jsExports = requireJs();
|
|
19
464
|
var index = /*@__PURE__*/getDefaultExportFromCjs(jsExports);
|