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.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import _0x6a4a06 from 'events';
2
- import _0x3738e1 from 'fs';
3
- import _0x520165 from 'path';
4
- import _0x47bf14 from 'module';
1
+ import require$$0 from 'events';
2
+ import require$$1 from 'fs';
3
+ import require$$2 from 'path';
4
+ import require$$3 from 'module';
5
5
 
6
6
  function getDefaultExportFromCjs (x) {
7
7
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
@@ -11,7 +11,452 @@ function commonjsRequire(path) {
11
11
  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.');
12
12
  }
13
13
 
14
- var js,hasRequiredJs;function requireJs(){if(hasRequiredJs)return js;hasRequiredJs=0x1;const {EventEmitter:_0xb54e1}=_0x6a4a06,{existsSync:_0x171849,mkdirSync:_0x2bd77c,writeFileSync:_0x472a2f}=_0x3738e1,{join:_0x285bde,dirname:_0x54fa14}=_0x520165,{createRequire:_0x121c89}=_0x47bf14,{platform:_0x562abc,arch:_0x2ee7bb}=process;let _0x1174fb=null;function _0xb87b82(){if(!_0x1174fb){if(typeof __non_webpack_require__!=='undef'+'ined')_0x1174fb=__non_webpack_require__;else try{_0x1174fb=_0x121c89(process['cwd']()+('/pack'+'age.j'+'son'));}catch(_0x2ea631){_0x1174fb=commonjsRequire;}}return _0x1174fb;}function _0x4802ac(){const _0x16c7fb=_0xb87b82();try{const _0xd37f93=_0x16c7fb['resol'+'ve']('nodep'+'layer'+'-addo'+'n/pac'+'kage.'+'json');return _0x54fa14(_0xd37f93);}catch(_0x27e982){}const _0x2b092b=_0x285bde(__dirname,'..');if(_0x171849(_0x285bde(_0x2b092b,'packa'+'ge.js'+'on')))return _0x2b092b;return _0x2b092b;}let _0x2904e7=null;function _0x57d682(){if(!_0x2904e7){const _0x44d9b0=_0xb87b82(),_0x18311b=_0x4802ac(),_0x2e47db=_0x285bde(_0x18311b,'prebu'+'ilds',_0x562abc+'-'+_0x2ee7bb,'node_'+'playe'+'r.nod'+'e'),_0x374931=_0x285bde(_0x18311b,'build','Relea'+'se','node_'+'playe'+'r.nod'+'e');if(_0x171849(_0x2e47db))_0x2904e7=_0x44d9b0(_0x2e47db);else {if(_0x171849(_0x374931))_0x2904e7=_0x44d9b0(_0x374931);else throw new Error('[node'+'playe'+'r-add'+'on]\x20C'+'annot'+'\x20find'+'\x20nati'+'ve\x20mo'+'dule.'+'\x0a'+('\x20\x20Sea'+'rched'+':\x0a')+('\x20\x20\x20\x20P'+'rebui'+'ld:\x20'+_0x2e47db+'\x0a')+('\x20\x20\x20\x20L'+'ocal:'+'\x20\x20\x20\x20'+_0x374931+'\x0a')+('\x20\x20Res'+'olved'+'\x20root'+':\x20'+_0x18311b+'\x0a')+('\x20\x20__d'+'irnam'+'e:\x20\x20\x20'+'\x20\x20'+__dirname+'\x0a')+('\x20\x20Pla'+'tform'+':\x20\x20\x20\x20'+'\x20\x20'+_0x562abc+'-'+_0x2ee7bb+'\x0a\x0a')+('If\x20us'+'ing\x20w'+'ebpac'+'k/vit'+'e,\x20ad'+'d\x20to\x20'+'exter'+'nals:'+'\x0a')+('\x20\x20web'+'pack:'+'\x20exte'+'rnals'+':\x20{\x20\x27'+'nodep'+'layer'+'-addo'+'n\x27:\x20\x27'+'commo'+'njs\x20n'+'odepl'+'ayer-'+'addon'+'\x27\x20}\x0a')+('\x20\x20vit'+'e:\x20\x20\x20'+'\x20opti'+'mizeD'+'eps:\x20'+'{\x20exc'+'lude:'+'\x20[\x27no'+'depla'+'yer-a'+'ddon\x27'+']\x20}'));}}return _0x2904e7;}const _0x35c0fb=new Map(),_0x3660aa=['playe'+'r:cre'+'ate','playe'+'r:sta'+'rt','playe'+'r:sto'+'p','playe'+'r:des'+'troy','playe'+'r:sta'+'rtRec'+'ord','playe'+'r:sto'+'pReco'+'rd','playe'+'r:scr'+'eensh'+'ot'];class _0x34b869 extends _0xb54e1{constructor(_0xbff28){super();const _0x515f2f=_0xbff28||{};this['_play'+'er']=new(_0x57d682())['NodeP'+'layer'](_0x515f2f),this['_star'+'ted']=![],this['_tria'+'lMode']=!_0x515f2f['licen'+'sePat'+'h'],this['_play'+'er']['on']('event',(_0x306db6,_0x1b824a)=>{this['emit']('event',_0x306db6,_0x1b824a);}),this['_play'+'er']['on']('info',_0x5ccfdf=>{this['emit']('info',_0x5ccfdf);}),this['_play'+'er']['on']('data',_0x4768d7=>{this['emit']('data',_0x4768d7);});}get['isTri'+'alMod'+'e'](){return this['_tria'+'lMode'];}['start'](_0x5274ac){if(this['_star'+'ted'])throw new Error('Pipel'+'ine\x20a'+'lread'+'y\x20sta'+'rted');try{return this['_play'+'er']['start'](_0x5274ac),this['_star'+'ted']=!![],!![];}catch(_0x21258d){return this['emit']('error',_0x21258d),![];}}['stop'](){this['_star'+'ted']&&(this['_play'+'er']['stop'](),this['_star'+'ted']=![]);}['start'+'Recor'+'d'](_0x259748){if(!this['_star'+'ted'])throw new Error('Pipel'+'ine\x20n'+'ot\x20st'+'arted');this['_play'+'er']['start'+'Recor'+'d'](_0x259748);}['stopR'+'ecord'](){this['_play'+'er']['stopR'+'ecord']();}static['regis'+'terIp'+'c'](_0x27b1db,_0x46dc1d={}){const {getWindow:_0x5b6efd,licensePath:_0x207790}=_0x46dc1d,_0x2284cc=(_0xb509b4,_0x22e935)=>{const _0x947555=_0x5b6efd&&_0x5b6efd();_0x947555&&!_0x947555['isDes'+'troye'+'d']()&&_0x947555['webCo'+'ntent'+'s']['send'](_0xb509b4,_0x22e935);};_0x27b1db['handl'+'e']('playe'+'r:cre'+'ate',(_0x18fefe,_0x565b6f,_0x5d7124)=>{if(_0x35c0fb['has'](_0x565b6f))return {'success':![],'error':'Playe'+'r\x20alr'+'eady\x20'+'exist'+'s'};const _0x18b1d1=_0x5d7124||{};!_0x18b1d1['licen'+'sePat'+'h']&&_0x207790&&(_0x18b1d1['licen'+'sePat'+'h']=_0x207790);const _0x1d8bea=new _0x34b869(_0x18b1d1);return _0x1d8bea['on']('event',(_0x251301,_0x4b6570)=>{_0x2284cc('playe'+'r:eve'+'nt:'+_0x565b6f,{'code':_0x251301,'msg':_0x4b6570});}),_0x1d8bea['on']('info',_0x1e4fb1=>{_0x2284cc('playe'+'r:inf'+'o:'+_0x565b6f,_0x1e4fb1);}),_0x1d8bea['on']('data',_0x5ea188=>{_0x2284cc('playe'+'r:dat'+'a:'+_0x565b6f,_0x5ea188);}),_0x35c0fb['set'](_0x565b6f,_0x1d8bea),{'success':!![]};}),_0x27b1db['handl'+'e']('playe'+'r:sta'+'rt',(_0x2f55b4,_0xd4bc78,_0x55d356)=>{const _0x2e41cb=_0x35c0fb['get'](_0xd4bc78);if(!_0x2e41cb)return {'success':![],'error':'Playe'+'r\x20not'+'\x20foun'+'d'};try{const _0x1e2f68=_0x2e41cb['start'](_0x55d356);return {'success':_0x1e2f68};}catch(_0x5f44a0){return {'success':![],'error':_0x5f44a0['messa'+'ge']};}}),_0x27b1db['handl'+'e']('playe'+'r:sto'+'p',(_0xcb3de8,_0x5f0db3)=>{const _0x4bda28=_0x35c0fb['get'](_0x5f0db3);if(!_0x4bda28)return {'success':![],'error':'Playe'+'r\x20not'+'\x20foun'+'d'};try{return _0x4bda28['stop'](),{'success':!![]};}catch(_0x4d78bd){return {'success':![],'error':_0x4d78bd['messa'+'ge']};}}),_0x27b1db['handl'+'e']('playe'+'r:des'+'troy',(_0x37314b,_0x293e1a)=>{const _0x20cbde=_0x35c0fb['get'](_0x293e1a);if(!_0x20cbde)return {'success':![],'error':'Playe'+'r\x20not'+'\x20foun'+'d'};try{return _0x20cbde['stop'](),_0x35c0fb['delet'+'e'](_0x293e1a),{'success':!![]};}catch(_0x33faf9){return {'success':![],'error':_0x33faf9['messa'+'ge']};}}),_0x27b1db['handl'+'e']('playe'+'r:sta'+'rtRec'+'ord',(_0x62d551,_0x108593,_0x2d48ae)=>{const _0x50408c=_0x35c0fb['get'](_0x108593);if(!_0x50408c)return {'success':![],'error':'Playe'+'r\x20not'+'\x20foun'+'d'};try{const _0x402527=_0x2d48ae||_0x285bde(process['cwd'](),'recor'+'d_'+_0x108593+'_'+Date['now']()+'.mp4'),_0x4c251e=_0x54fa14(_0x402527);return !_0x171849(_0x4c251e)&&_0x2bd77c(_0x4c251e,{'recursive':!![]}),_0x50408c['start'+'Recor'+'d'](_0x402527),{'success':!![],'path':_0x402527};}catch(_0x517b39){return {'success':![],'error':_0x517b39['messa'+'ge']};}}),_0x27b1db['handl'+'e']('playe'+'r:sto'+'pReco'+'rd',(_0x177c55,_0x1ddc3a)=>{const _0x16057f=_0x35c0fb['get'](_0x1ddc3a);if(!_0x16057f)return {'success':![],'error':'Playe'+'r\x20not'+'\x20foun'+'d'};try{return _0x16057f['stopR'+'ecord'](),{'success':!![]};}catch(_0x59d3a4){return {'success':![],'error':_0x59d3a4['messa'+'ge']};}}),_0x27b1db['handl'+'e']('playe'+'r:scr'+'eensh'+'ot',(_0x123a58,_0x2450ee,_0xbda25f,_0x395f07)=>{try{const _0x59ce79=_0xbda25f||_0x285bde(process['cwd'](),'scree'+'nshot'+'_'+_0x2450ee+'_'+Date['now']()+'.jpg'),_0x1de75b=_0x54fa14(_0x59ce79);return !_0x171849(_0x1de75b)&&_0x2bd77c(_0x1de75b,{'recursive':!![]}),_0x472a2f(_0x59ce79,Buffer['from'](_0x395f07,'base6'+'4')),{'success':!![],'path':_0x59ce79};}catch(_0x5c81d4){return {'success':![],'error':_0x5c81d4['messa'+'ge']};}});}static['unreg'+'ister'+'Ipc'](_0xdf56d){_0x35c0fb['forEa'+'ch'](_0x455e1d=>{try{_0x455e1d['stop']();}catch(_0x4ec340){}}),_0x35c0fb['clear'](),_0x3660aa['forEa'+'ch'](_0x4f7a40=>_0xdf56d['remov'+'eHand'+'ler'](_0x4f7a40));}}return js=_0x34b869,js;}
14
+ var js;
15
+ var hasRequiredJs;
16
+
17
+ function requireJs () {
18
+ if (hasRequiredJs) return js;
19
+ hasRequiredJs = 1;
20
+ const { EventEmitter } = require$$0;
21
+ const { existsSync, mkdirSync, writeFileSync } = require$$1;
22
+ const { join, dirname } = require$$2;
23
+ const { createRequire } = require$$3;
24
+ const { platform, arch } = process;
25
+
26
+ /**
27
+ * 获取原生 require,绕过 webpack/vite 的模块替换
28
+ * - webpack 环境使用 __non_webpack_require__
29
+ * - 其他环境使用 createRequire() 基于运行时目录创建
30
+ */
31
+ let _nativeRequire = null;
32
+ function getNativeRequire() {
33
+ if (!_nativeRequire) {
34
+ if (typeof __non_webpack_require__ !== 'undefined') {
35
+ _nativeRequire = __non_webpack_require__;
36
+ } else {
37
+ try {
38
+ // 基于运行时工作目录创建 require,不受打包工具对 __dirname/__filename 的篡改
39
+ _nativeRequire = createRequire(process.cwd() + '/package.json');
40
+ } catch (e) {
41
+ _nativeRequire = commonjsRequire;
42
+ }
43
+ }
44
+ }
45
+ return _nativeRequire
46
+ }
47
+
48
+ /**
49
+ * 解析包根目录(兼容打包后 __dirname 失效)
50
+ * - 优先通过 require.resolve 找到包在 node_modules 中的真实路径
51
+ * - __dirname 检查作为 fallback(未打包环境)
52
+ */
53
+ function resolvePackageRoot() {
54
+ const req = getNativeRequire();
55
+
56
+ // Strategy 1: 通过 require.resolve 找到包的真实安装路径
57
+ try {
58
+ const pkgJsonPath = req.resolve('nodeplayer-addon/package.json');
59
+ return dirname(pkgJsonPath)
60
+ } catch (e) {
61
+ // ignore
62
+ }
63
+
64
+ // Strategy 2: __dirname 上级存在 package.json(未打包环境)
65
+ const fallback = join(__dirname, '..');
66
+ if (existsSync(join(fallback, 'package.json'))) {
67
+ return fallback
68
+ }
69
+
70
+ return fallback
71
+ }
72
+
73
+ // 延迟加载 native 模块,优先从预编译目录加载
74
+ let native = null;
75
+ function getNative() {
76
+ if (!native) {
77
+ const req = getNativeRequire();
78
+ const basePath = resolvePackageRoot();
79
+
80
+ const prebuildPath = join(basePath, 'prebuilds', `${platform}-${arch}`, 'node_player.node');
81
+ const localPath = join(basePath, 'build', 'Release', 'node_player.node');
82
+
83
+ if (existsSync(prebuildPath)) {
84
+ native = req(prebuildPath);
85
+ } else if (existsSync(localPath)) {
86
+ native = req(localPath);
87
+ } else {
88
+ throw new Error(
89
+ `[nodeplayer-addon] Cannot find native module.\n` +
90
+ ` Searched:\n` +
91
+ ` Prebuild: ${prebuildPath}\n` +
92
+ ` Local: ${localPath}\n` +
93
+ ` Resolved root: ${basePath}\n` +
94
+ ` __dirname: ${__dirname}\n` +
95
+ ` Platform: ${platform}-${arch}\n\n` +
96
+ `If using webpack/vite, add to externals:\n` +
97
+ ` webpack: externals: { 'nodeplayer-addon': 'commonjs nodeplayer-addon' }\n` +
98
+ ` vite: optimizeDeps: { exclude: ['nodeplayer-addon'] }`
99
+ )
100
+ }
101
+ }
102
+ return native
103
+ }
104
+
105
+ // ============ Electron IPC 管理 ============
106
+ const _ipcPlayers = new Map();
107
+ const _ipcChannels = [
108
+ 'player:create',
109
+ 'player:start',
110
+ 'player:stop',
111
+ 'player:destroy',
112
+ 'player:startRecord',
113
+ 'player:stopRecord',
114
+ 'player:screenshot',
115
+ 'player:getMediaInfo'
116
+ ];
117
+
118
+ /**
119
+ * NodePlayer - RTSP/RTMP/KMP 流媒体播放器
120
+ *
121
+ * 支持的事件:
122
+ * - 'event': (code, msg) => {} - 管线事件
123
+ * - 'info': (info) => {} - 流信息
124
+ * - 'data': (buffer) => {} - fMP4 分片数据
125
+ *
126
+ * 使用示例:
127
+ * // 正式授权模式
128
+ * const player = new NodePlayer({ licensePath: '/path/to/license.dat' })
129
+ * player.on('info', (info) => console.log(info))
130
+ * player.on('data', (buffer) => console.log(buffer.length))
131
+ * player.start('rtsp://...')
132
+ * player.startRecord('./record.mp4')
133
+ * // ...
134
+ * player.stopRecord()
135
+ * player.stop()
136
+ *
137
+ * // 试用模式(无需许可证,累计 10 分钟)
138
+ * const player = new NodePlayer()
139
+ * player.start('rtsp://...')
140
+ */
141
+ class NodePlayer extends EventEmitter {
142
+ /**
143
+ * @param {object} [options] - 配置选项
144
+ * @param {string} [options.licensePath] - 许可证文件路径。
145
+ * 传入有效路径 → 正式授权模式;
146
+ * 不传或空字符串 → 试用模式(累计 10 分钟)。
147
+ */
148
+ constructor(options) {
149
+ super();
150
+ const opts = options || {};
151
+ this._player = new (getNative().NodePlayer)(opts);
152
+ this._started = false;
153
+ this._trialMode = !opts.licensePath;
154
+
155
+ // 转发原生事件
156
+ this._player.on('event', (code, msg) => {
157
+ this.emit('event', code, msg);
158
+ });
159
+
160
+ this._player.on('info', (info) => {
161
+ this.emit('info', info);
162
+ });
163
+
164
+ this._player.on('data', (buffer) => {
165
+ this.emit('data', buffer);
166
+ });
167
+ }
168
+
169
+ /**
170
+ * 是否处于试用模式
171
+ * @returns {boolean}
172
+ */
173
+ get isTrialMode() {
174
+ return this._trialMode
175
+ }
176
+
177
+ /**
178
+ * 启动管线(内部会先验证许可证)
179
+ * @param {string} url - 输入地址 (RTSP/RTMP/KMP)
180
+ * @returns {boolean} 是否成功启动
181
+ */
182
+ start(url) {
183
+ if (this._started) {
184
+ throw new Error('Pipeline already started')
185
+ }
186
+ try {
187
+ this._player.start(url);
188
+ this._started = true;
189
+ return true
190
+ } catch (err) {
191
+ this.emit('error', err);
192
+ return false
193
+ }
194
+ }
195
+
196
+ /**
197
+ * 停止管线
198
+ */
199
+ stop() {
200
+ if (this._started) {
201
+ this._player.stop();
202
+ this._started = false;
203
+ }
204
+ }
205
+
206
+ /**
207
+ * 开始录像
208
+ * @param {string} outputPath - 输出 MP4 文件路径
209
+ */
210
+ startRecord(outputPath) {
211
+ if (!this._started) {
212
+ throw new Error('Pipeline not started')
213
+ }
214
+ this._player.startRecord(outputPath);
215
+ }
216
+
217
+ /**
218
+ * 停止录像(生成完整 MP4 文件)
219
+ */
220
+ stopRecord() {
221
+ this._player.stopRecord();
222
+ }
223
+
224
+ // ============ Electron IPC 静态方法 ============
225
+
226
+ /**
227
+ * 注册 Electron ipcMain 处理器,将播放器操作桥接到渲染进程。
228
+ *
229
+ * 注册后,渲染进程可通过 preload 调用:
230
+ * - ipcRenderer.invoke('player:getMediaInfo', url) // 预探测(无需 id)
231
+ * - ipcRenderer.invoke('player:create', id, options)
232
+ * - ipcRenderer.invoke('player:start', id, url)
233
+ * - ipcRenderer.invoke('player:stop', id)
234
+ * - ipcRenderer.invoke('player:destroy', id)
235
+ * - ipcRenderer.invoke('player:startRecord', id, outputPath?)
236
+ * - ipcRenderer.invoke('player:stopRecord', id)
237
+ *
238
+ * 事件通过 mainWindow.webContents.send 推送到渲染进程:
239
+ * - player:event:${id} → { code, msg }
240
+ * - player:info:${id} → info
241
+ * - player:data:${id} → Buffer
242
+ *
243
+ * @param {object} ipcMain - Electron ipcMain
244
+ * @param {object} options
245
+ * @param {function} options.getWindow - 返回当前 BrowserWindow 的函数(窗口可重建)
246
+ * @param {string} [options.licensePath] - 许可证路径(不传则为试用模式)
247
+ *
248
+ * @example
249
+ * // main.js
250
+ * const { ipcMain, app } = require('electron')
251
+ * const NodePlayer = require('nodeplayer-addon')
252
+ *
253
+ * NodePlayer.registerIpc(ipcMain, {
254
+ * getWindow: () => mainWindow,
255
+ * licensePath: app.isPackaged
256
+ * ? path.join(process.resourcesPath, 'license.dat')
257
+ * : path.join(__dirname, 'license.dat'),
258
+ * })
259
+ */
260
+ static registerIpc(ipcMain, options = {}) {
261
+ const { getWindow, licensePath } = options;
262
+
263
+ const send = (channel, data) => {
264
+ const win = getWindow && getWindow();
265
+ if (win && !win.isDestroyed()) {
266
+ win.webContents.send(channel, data);
267
+ }
268
+ };
269
+
270
+ // 预探测:分析 URL 是否可连接 + 视频/音频参数 + 截图(与 player 实例无关)
271
+ // 返回 { success, info?: { video, audio, screenshot } }
272
+ ipcMain.handle('player:getMediaInfo', async (event, url) => {
273
+ try {
274
+ const info = await NodePlayer.getMediaInfo(url);
275
+ return { success: true, info }
276
+ } catch (e) {
277
+ return { success: false, error: e.message }
278
+ }
279
+ });
280
+
281
+ ipcMain.handle('player:create', (event, id, playerOptions) => {
282
+ if (_ipcPlayers.has(id)) {
283
+ return { success: false, error: 'Player already exists' }
284
+ }
285
+
286
+ const opts = playerOptions || {};
287
+ if (!opts.licensePath && licensePath) {
288
+ opts.licensePath = licensePath;
289
+ }
290
+
291
+ const player = new NodePlayer(opts);
292
+
293
+ player.on('event', (code, msg) => {
294
+ send(`player:event:${id}`, { code, msg });
295
+ });
296
+
297
+ player.on('info', (info) => {
298
+ send(`player:info:${id}`, info);
299
+ });
300
+
301
+ player.on('data', (data) => {
302
+ send(`player:data:${id}`, data);
303
+ });
304
+
305
+ _ipcPlayers.set(id, player);
306
+ return { success: true }
307
+ });
308
+
309
+ ipcMain.handle('player:start', (event, id, url) => {
310
+ const player = _ipcPlayers.get(id);
311
+ if (!player) {
312
+ return { success: false, error: 'Player not found' }
313
+ }
314
+ try {
315
+ const result = player.start(url);
316
+ return { success: result }
317
+ } catch (e) {
318
+ return { success: false, error: e.message }
319
+ }
320
+ });
321
+
322
+ ipcMain.handle('player:stop', (event, id) => {
323
+ const player = _ipcPlayers.get(id);
324
+ if (!player) {
325
+ return { success: false, error: 'Player not found' }
326
+ }
327
+ try {
328
+ player.stop();
329
+ return { success: true }
330
+ } catch (e) {
331
+ return { success: false, error: e.message }
332
+ }
333
+ });
334
+
335
+ ipcMain.handle('player:destroy', (event, id) => {
336
+ const player = _ipcPlayers.get(id);
337
+ if (!player) {
338
+ return { success: false, error: 'Player not found' }
339
+ }
340
+ try {
341
+ player.stop();
342
+ _ipcPlayers.delete(id);
343
+ return { success: true }
344
+ } catch (e) {
345
+ return { success: false, error: e.message }
346
+ }
347
+ });
348
+
349
+ ipcMain.handle('player:startRecord', (event, id, outputPath) => {
350
+ const player = _ipcPlayers.get(id);
351
+ if (!player) {
352
+ return { success: false, error: 'Player not found' }
353
+ }
354
+ try {
355
+ const savePath = outputPath || join(
356
+ process.cwd(),
357
+ `record_${id}_${Date.now()}.mp4`
358
+ );
359
+ const dir = dirname(savePath);
360
+ if (!existsSync(dir)) {
361
+ mkdirSync(dir, { recursive: true });
362
+ }
363
+ player.startRecord(savePath);
364
+ return { success: true, path: savePath }
365
+ } catch (e) {
366
+ return { success: false, error: e.message }
367
+ }
368
+ });
369
+
370
+ ipcMain.handle('player:stopRecord', (event, id) => {
371
+ const player = _ipcPlayers.get(id);
372
+ if (!player) {
373
+ return { success: false, error: 'Player not found' }
374
+ }
375
+ try {
376
+ player.stopRecord();
377
+ return { success: true }
378
+ } catch (e) {
379
+ return { success: false, error: e.message }
380
+ }
381
+ });
382
+
383
+ ipcMain.handle('player:screenshot', (event, id, outputPath, base64Data) => {
384
+ try {
385
+ const savePath = outputPath || join(
386
+ process.cwd(),
387
+ `screenshot_${id}_${Date.now()}.jpg`
388
+ );
389
+ const dir = dirname(savePath);
390
+ if (!existsSync(dir)) {
391
+ mkdirSync(dir, { recursive: true });
392
+ }
393
+ writeFileSync(savePath, Buffer.from(base64Data, 'base64'));
394
+ return { success: true, path: savePath }
395
+ } catch (e) {
396
+ return { success: false, error: e.message }
397
+ }
398
+ });
399
+ }
400
+
401
+ /**
402
+ * 注销 Electron ipcMain 处理器,停止所有播放器并清理资源。
403
+ *
404
+ * @param {object} ipcMain - Electron ipcMain
405
+ *
406
+ * @example
407
+ * mainWindow.on('closed', () => {
408
+ * NodePlayer.unregisterIpc(ipcMain)
409
+ * })
410
+ */
411
+ static unregisterIpc(ipcMain) {
412
+ _ipcPlayers.forEach(player => {
413
+ try { player.stop(); } catch (e) { /* ignore */ }
414
+ });
415
+ _ipcPlayers.clear();
416
+
417
+ _ipcChannels.forEach(ch => ipcMain.removeHandler(ch));
418
+ }
419
+
420
+ // ============ 流媒体探测(静态方法)============
421
+
422
+ /**
423
+ * 异步获取流媒体信息(不阻塞 Node 事件循环)。
424
+ *
425
+ * 用于在添加 URL 到播放器之前,先分析流:
426
+ * - 是否可连接
427
+ * - 视频/音频编码、分辨率、采样率
428
+ * - 截取首帧作为预览图(MJPEG Buffer)
429
+ *
430
+ * @param {string} url - 流地址 (RTSP/RTMP/KMP/HTTP...)
431
+ * @returns {Promise<{video: object, audio: object, screenshot: Buffer|null}>}
432
+ * - video: { codecId:number, width:number, height:number }
433
+ * codecId 为 AV_CODEC_ID_* 原始值(27=H264, 173=HEVC...)
434
+ * - audio: { codecId:number, sampleRate:number, channels:number }
435
+ * 无音频时三个字段均为 0
436
+ * - screenshot: MJPEG 二进制 Buffer;解码失败/无视频时为 null
437
+ *
438
+ * @example
439
+ * const info = await NodePlayer.getMediaInfo('rtsp://...')
440
+ * console.log(info.video.width + 'x' + info.video.height)
441
+ * if (info.screenshot) {
442
+ * const base64 = info.screenshot.toString('base64')
443
+ * previewImg.src = 'data:image/jpeg;base64,' + base64
444
+ * }
445
+ */
446
+ static getMediaInfo(url) {
447
+ if (typeof url !== 'string' || !url) {
448
+ // 同步抛出 TypeError,遵循 Node.js fs.promises.* 的惯例
449
+ throw new TypeError('url must be a non-empty string')
450
+ }
451
+ // 原生层返回 Promise;用 Promise.resolve 统一异常路径,避免被调用方漏 catch
452
+ return Promise.resolve(getNative().getMediaInfo(url))
453
+ }
454
+
455
+ }
456
+
457
+ js = NodePlayer;
458
+ return js;
459
+ }
15
460
 
16
461
  var jsExports = requireJs();
17
462
  var index = /*@__PURE__*/getDefaultExportFromCjs(jsExports);