nodeplayer-addon 0.3.0 → 0.3.1

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/SKILL.md DELETED
@@ -1,684 +0,0 @@
1
- ---
2
- name: nodeplayer-addon
3
- description: 使用 nodeplayer-addon 的完整指南,包括使用流程、常见模式、最佳实践。当你需要查询 nodeplayer-addon、生成组件代码或解决使用问题时,请使用此技能。
4
- ---
5
-
6
- # nodeplayer-addon — AI Skill Document
7
-
8
- > Version: 0.2.3 | Package: `nodeplayer-addon`
9
- > An Electron native player addon (N-API C++) supporting RTSP / RTMP streaming protocols.
10
-
11
- ---
12
-
13
- ## Table of Contents
14
-
15
- 1. [Installation & Imports](#installation--imports)
16
- 2. [Architecture Overview](#architecture-overview)
17
- 3. [NodePlayer (Main Process)](#nodeplayer-main-process)
18
- 4. [VideoPlayer (Renderer Process)](#videoplayer-renderer-process)
19
- 5. [IPC Bridge Protocol](#ipc-bridge-protocol)
20
- 6. [Preload API Interface](#preload-api-interface)
21
- 7. [Event Codes Reference](#event-codes-reference)
22
- 8. [Stream Info Object](#stream-info-object)
23
- 9. [Integration Patterns](#integration-patterns)
24
- 10. [License & Trial Mode](#license--trial-mode)
25
- 11. [Troubleshooting](#troubleshooting)
26
-
27
- ---
28
-
29
- ## Installation & Imports
30
-
31
- ```bash
32
- npm install nodeplayer-addon
33
- ```
34
-
35
- ### Package Exports
36
-
37
- | Subpath | Import (ESM) | Require (CJS) |
38
- |---------|-------------|---------------|
39
- | `nodeplayer-addon` | `dist/index.mjs` | `dist/index.cjs` |
40
- | `nodeplayer-addon/video-player` | `dist/video-player.mjs` | `dist/video-player.umd.js` |
41
-
42
- ### Import Examples
43
-
44
- ```javascript
45
- // Main process (CJS)
46
- const NodePlayer = require('nodeplayer-addon')
47
-
48
- // Main process (ESM)
49
- import NodePlayer from 'nodeplayer-addon'
50
-
51
- // Renderer process (ESM)
52
- import VideoPlayer from 'nodeplayer-addon/video-player'
53
-
54
- // Renderer process (UMD via <script>)
55
- // <script src="node_modules/nodeplayer-addon/dist/video-player.umd.js"></script>
56
- // → global window.VideoPlayer
57
- ```
58
-
59
- ### Prebuilds Directory Structure
60
-
61
- ```
62
- nodeplayer-addon/
63
- ├── dist/
64
- │ ├── index.cjs — NodePlayer (CommonJS)
65
- │ ├── index.mjs — NodePlayer (ESM)
66
- │ ├── video-player.mjs — VideoPlayer (ESM)
67
- │ └── video-player.umd.js — VideoPlayer (UMD)
68
- └── prebuilds/
69
- ├── darwin-arm64/node_player.node
70
- ├── darwin-x64/node_player.node
71
- ├── linux-arm64/node_player.node
72
- ├── linux-x64/node_player.node
73
- └── win32-x64/node_player.node
74
- ```
75
-
76
- The native `.node` binary is lazy-loaded at runtime from `prebuilds/{platform}-{arch}/` or `build/Release/`.
77
-
78
- ---
79
-
80
- ## Architecture Overview
81
-
82
- ```
83
- ┌─────────────────────────────────────────────────────────────┐
84
- │ Electron Application │
85
- │ │
86
- │ ┌──────────────────┐ IPC ┌────────────────────┐ │
87
- │ │ Main Process │◄──────────────►│ Renderer Process │ │
88
- │ │ │ │ │ │
89
- │ │ NodePlayer │ ipcMain/ │ VideoPlayer │ │
90
- │ │ (C++ N-API) │ ipcRenderer │ (MSE + Canvas) │ │
91
- │ │ │ │ │ │
92
- │ │ registerIpc() │ preload.js │ <video> element │ │
93
- │ └──────────────────┘ contextBridge └────────────────────┘ │
94
- └─────────────────────────────────────────────────────────────┘
95
-
96
-
97
- RTSP / RTMP streams
98
- (FFmpeg demux → decode → fMP4 segments)
99
- ```
100
-
101
- **Data Flow:**
102
-
103
- ```
104
- C++ native (FFmpeg demux)
105
- → NodePlayer 'data' event [main process]
106
- → ipcMain → webContents.send() [main → renderer]
107
- → preload contextBridge [IPC bridge]
108
- → VideoPlayer._handleData() [renderer]
109
- → FIFO queue → SourceBuffer.appendBuffer()
110
- → <video> playback
111
- ```
112
-
113
- ---
114
-
115
- ## NodePlayer (Main Process)
116
-
117
- > **File**: `dist/index.cjs` / `dist/index.mjs`
118
- > **Extends**: `EventEmitter`
119
- > **Runtime**: Node.js / Electron main process only (requires native `.node` binary)
120
-
121
- ### Constructor
122
-
123
- ```javascript
124
- const player = new NodePlayer(options?)
125
- ```
126
-
127
- | Parameter | Type | Default | Description |
128
- |-----------|------|---------|-------------|
129
- | `options` | `object` | `{}` | Configuration options |
130
- | `options.licensePath` | `string` | `undefined` | License file path. If omitted or empty → trial mode (cumulative 10 min) |
131
-
132
- ### Instance Properties
133
-
134
- | Property | Type | Description |
135
- |----------|------|-------------|
136
- | `isTrialMode` | `boolean` (readonly) | `true` if no `licensePath` was provided |
137
-
138
- ### Instance Methods
139
-
140
- #### `start(url)` → `boolean`
141
-
142
- Start the streaming pipeline. Internally validates the license before connecting.
143
-
144
- | Parameter | Type | Description |
145
- |-----------|------|-------------|
146
- | `url` | `string` | Stream URL (RTSP / RTMP protocol) |
147
- | **Returns** | `boolean` | `true` if started successfully, `false` on error |
148
-
149
- **Throws**: `Error('Pipeline already started')` if called twice.
150
-
151
- **Events emitted on error**: `'error'` event with the error object.
152
-
153
- #### `stop()` → `void`
154
-
155
- Stop the streaming pipeline. Safe to call multiple times (no-op if not started).
156
-
157
- #### `startRecord(outputPath)` → `void`
158
-
159
- Start recording to an MP4 file.
160
-
161
- | Parameter | Type | Description |
162
- |-----------|------|-------------|
163
- | `outputPath` | `string` | Output MP4 file path |
164
-
165
- **Throws**: `Error('Pipeline not started')` if pipeline is not running.
166
-
167
- #### `stopRecord()` → `void`
168
-
169
- Stop recording. Generates a complete MP4 file at the path specified in `startRecord()`.
170
-
171
- ### Events
172
-
173
- All events are forwarded from the native C++ layer.
174
-
175
- | Event | Callback Signature | Description |
176
- |-------|-------------------|-------------|
177
- | `'event'` | `(code: number, msg: string) => void` | Pipeline status events (connection, errors, recording) |
178
- | `'info'` | `(info: StreamInfo) => void` | Stream information (codecs, resolution) — emitted once after connection |
179
- | `'data'` | `(buffer: Buffer) => void` | fMP4 segment data for MSE playback |
180
- | `'error'` | `(err: Error) => void` | Runtime errors |
181
-
182
- ### Static Methods
183
-
184
- #### `NodePlayer.registerIpc(ipcMain, options)` → `void`
185
-
186
- Register IPC handlers to bridge player operations to the renderer process.
187
-
188
- | Parameter | Type | Description |
189
- |-----------|------|-------------|
190
- | `ipcMain` | `object` | Electron's `ipcMain` module |
191
- | `options` | `object` | Configuration |
192
- | `options.getWindow` | `() => BrowserWindow` | Function returning the current BrowserWindow |
193
- | `options.licensePath` | `string?` | Default license path for all players (fallback) |
194
-
195
- **Registered IPC Channels:**
196
-
197
- | Channel | Direction | Params | Returns |
198
- |---------|-----------|--------|---------|
199
- | `player:create` | renderer → main | `(id, playerOptions?)` | `{ success: boolean, error?: string }` |
200
- | `player:start` | renderer → main | `(id, url)` | `{ success: boolean, error?: string }` |
201
- | `player:stop` | renderer → main | `(id)` | `{ success: boolean, error?: string }` |
202
- | `player:destroy` | renderer → main | `(id)` | `{ success: boolean, error?: string }` |
203
- | `player:startRecord` | renderer → main | `(id, outputPath?)` | `{ success: boolean, path?: string, error?: string }` |
204
- | `player:stopRecord` | renderer → main | `(id)` | `{ success: boolean, error?: string }` |
205
- | `player:screenshot` | renderer → main | `(id, outputPath?, base64Data)` | `{ success: boolean, path?: string, error?: string }` |
206
-
207
- **Push Events (main → renderer):**
208
-
209
- | Channel | Data Shape |
210
- |---------|-----------|
211
- | `player:event:${id}` | `{ code: number, msg: string }` |
212
- | `player:info:${id}` | `StreamInfo` object |
213
- | `player:data:${id}` | `Buffer` (fMP4 segment) |
214
-
215
- #### `NodePlayer.unregisterIpc(ipcMain)` → `void`
216
-
217
- Unregister all IPC handlers, stop all players, and clean up resources.
218
-
219
- | Parameter | Type | Description |
220
- |-----------|------|-------------|
221
- | `ipcMain` | `object` | Electron's `ipcMain` module |
222
-
223
- ---
224
-
225
- ## VideoPlayer (Renderer Process)
226
-
227
- > **File**: `dist/video-player.mjs` / `dist/video-player.umd.js`
228
- > **Runtime**: Electron renderer process (uses MediaSource Extensions, Canvas API)
229
- > **Dependency**: Requires `window.electronAPI` exposed via preload script
230
-
231
- ### Constructor
232
-
233
- ```javascript
234
- const player = new VideoPlayer(video, id, options?)
235
- ```
236
-
237
- | Parameter | Type | Description |
238
- |-----------|------|-------------|
239
- | `video` | `HTMLVideoElement` | The `<video>` element to render into |
240
- | `id` | `string` | Unique player identifier (used for IPC channel names) |
241
- | `options` | `object` | Optional configuration |
242
- | `options.api` | `object` | Custom IPC bridge (default: `window.electronAPI`) |
243
-
244
- ### Instance Properties
245
-
246
- | Property | Type | Default | Description |
247
- |----------|------|---------|-------------|
248
- | `id` | `string` | — | Player identifier |
249
- | `video` | `HTMLVideoElement` | — | Video element reference |
250
- | `isStarted` | `boolean` | `false` | Whether playback has started |
251
- | `isReady` | `boolean` | `false` | Whether MediaSource + SourceBuffer are ready |
252
- | `isRecording` | `boolean` | `false` | Whether recording is active |
253
- | `videoCodecString` | `string \| null` | `null` | Video codec MIME string (e.g., `"avc1.640029"`) |
254
- | `audioCodecString` | `string \| null` | `null` | Audio codec MIME string (e.g., `"mp4a.40.2"`) |
255
-
256
- ### Instance Methods
257
-
258
- #### `on(event, fn)` → `this`
259
-
260
- Register an event listener. Supports chaining and multiple listeners per event.
261
-
262
- | Parameter | Type | Description |
263
- |-----------|------|-------------|
264
- | `event` | `'event' \| 'error'` | Event name |
265
- | `fn` | `function` | Listener function |
266
- | **Returns** | `this` | Supports chaining |
267
-
268
- **Events:**
269
-
270
- | Event | Callback | Description |
271
- |-------|----------|-------------|
272
- | `'event'` | `(code: number, msg: string) => void` | Native event forwarded raw — connection status, recording lifecycle, etc. See [Event Codes Reference](#event-codes-reference) |
273
- | `'error'` | `(err: Error) => void` | JS-layer errors (IPC failures, MediaSource init errors, data processing errors) |
274
-
275
- #### `off(event, fn)` → `this`
276
-
277
- Remove a previously registered event listener.
278
-
279
- | Parameter | Type | Description |
280
- |-----------|------|-------------|
281
- | `event` | `'event' \| 'error'` | Event name |
282
- | `fn` | `function` | The listener function to remove |
283
- | **Returns** | `this` | Supports chaining |
284
-
285
- #### `start(url)` → `Promise<void>`
286
-
287
- Start playback. Creates the player via IPC, subscribes to events, and starts streaming.
288
-
289
- | Parameter | Type | Description |
290
- |-----------|------|-------------|
291
- | `url` | `string` | RTSP / RTMP stream URL |
292
-
293
- **Lifecycle**: `createPlayer` → subscribe events → `startPlayer` → wait for `info` event → init MediaSource → play.
294
-
295
- #### `stop()` → `Promise<void>`
296
-
297
- Stop playback and release all resources. Unsubscribes from IPC events, destroys MediaSource, and clears the canvas.
298
-
299
- #### `startRecord(outputPath?)` → `Promise<Result>`
300
-
301
- Start recording the current stream.
302
-
303
- | Parameter | Type | Description |
304
- |-----------|------|-------------|
305
- | `outputPath` | `string?` | Output file path (optional, auto-generated if omitted) |
306
- | **Returns** | `Promise<{success, path?, error?}>` | Result with file path on success |
307
-
308
- #### `stopRecord()` → `Promise<Result>`
309
-
310
- Stop recording.
311
-
312
- | **Returns** | `Promise<{success, error?}>` | Result |
313
- |-------------|------------------------------|--------|
314
-
315
- #### `captureScreenshot(quality?)` → `string | null`
316
-
317
- Capture the current video frame as a JPG data URL. Uses an internal canvas.
318
-
319
- | Parameter | Type | Default | Description |
320
- |-----------|------|---------|-------------|
321
- | `quality` | `number` | `0.9` | JPG quality (0–1) |
322
- | **Returns** | `string \| null` | `data:image/jpeg;base64,...` or `null` if not ready |
323
-
324
- #### `saveScreenshot(outputPath?, quality?)` → `Promise<Result>`
325
-
326
- Capture the current frame and save it to a file via IPC.
327
-
328
- | Parameter | Type | Default | Description |
329
- |-----------|------|---------|-------------|
330
- | `outputPath` | `string?` | auto-generated | Save path |
331
- | `quality` | `number` | `0.9` | JPG quality (0–1) |
332
- | **Returns** | `Promise<{success, path?, error?}>` | Result with saved file path |
333
-
334
- ---
335
-
336
- ## IPC Bridge Protocol
337
-
338
- ### Channel Naming Convention
339
-
340
- ```
341
- player:{action} — invoke channels (renderer → main)
342
- player:{event}:{id} — push channels (main → renderer)
343
- ```
344
-
345
- ### Request/Response Pattern
346
-
347
- All invoke channels return a unified result object:
348
-
349
- ```typescript
350
- interface IpcResult {
351
- success: boolean
352
- error?: string // present when success === false
353
- path?: string // present for startRecord and screenshot
354
- }
355
- ```
356
-
357
- ### Multi-Player Support
358
-
359
- Each player is identified by a unique `id` string. The main process maintains a `Map<string, NodePlayer>` internally. This allows multiple simultaneous streams in a single Electron window.
360
-
361
- ---
362
-
363
- ## Preload API Interface
364
-
365
- The preload script must expose `window.electronAPI` via `contextBridge.exposeInMainWorld`. The interface VideoPlayer expects:
366
-
367
- ```typescript
368
- interface ElectronAPI {
369
- // IPC invoke methods
370
- createPlayer(id: string, options?: object): Promise<{success: boolean, error?: string}>
371
- startPlayer(id: string, url: string): Promise<{success: boolean, error?: string}>
372
- stopPlayer(id: string): Promise<{success: boolean, error?: string}>
373
- destroyPlayer(id: string): Promise<{success: boolean, error?: string}>
374
- startRecord(id: string, outputPath?: string): Promise<{success: boolean, path?: string, error?: string}>
375
- stopRecord(id: string): Promise<{success: boolean, error?: string}>
376
- saveScreenshot(id: string, outputPath?: string, base64Data?: string): Promise<{success: boolean, path?: string, error?: string}>
377
-
378
- // Event subscription methods (return unsubscribe functions)
379
- onEvent(id: string, callback: (data: {code: number, msg: string}) => void): () => void
380
- onInfo(id: string, callback: (info: StreamInfo) => void): () => void
381
- onData(id: string, callback: (data: ArrayBuffer) => void): () => void
382
- }
383
- ```
384
-
385
- ---
386
-
387
- ## Event Codes Reference
388
-
389
- ### Connection Events (1xxx)
390
-
391
- | Code | Constant | Description |
392
- |------|----------|-------------|
393
- | `1000` | — | Connecting |
394
- | `1001` | — | Connected |
395
- | `1002` | — | Connection failed (`msg` contains details) |
396
- | `1003` | — | Reconnecting |
397
- | `1004` | — | Disconnected |
398
- | `1005` | — | Network error (`msg` contains details) |
399
- | `1006` | — | Connection timeout (`msg` contains details) |
400
-
401
- ### Recording Events (3xxx)
402
-
403
- | Code | Constant | Description |
404
- |------|----------|-------------|
405
- | `3001` | — | Recording started |
406
- | `3002` | — | Recording stopped |
407
- | `3003` | — | Recording error (`msg` contains details) |
408
-
409
- ---
410
-
411
- ## Stream Info Object
412
-
413
- Emitted via the `'info'` event / `player:info:${id}` IPC channel:
414
-
415
- ```typescript
416
- interface StreamInfo {
417
- video?: {
418
- codecString: string // e.g., "avc1.640029" (H.264), "hvc1.1.6.L93.B0" (H.265)
419
- width: number // e.g., 1920
420
- height: number // e.g., 1080
421
- }
422
- audio?: {
423
- codecString: string // e.g., "mp4a.40.2" (AAC-LC)
424
- sampleRate: number // e.g., 44100
425
- channels: number // e.g., 2
426
- }
427
- }
428
- ```
429
-
430
- `codecString` values are used to construct the MediaSource MIME type:
431
-
432
- ```
433
- video/mp4; codecs="<videoCodecString>,<audioCodecString>"
434
- ```
435
-
436
- ---
437
-
438
- ## Integration Patterns
439
-
440
- ### Pattern 1: Full Electron Integration (Recommended)
441
-
442
- This is the standard 3-file pattern for Electron apps with `contextIsolation: true`.
443
-
444
- #### main.js
445
-
446
- ```javascript
447
- const { app, BrowserWindow, ipcMain } = require('electron')
448
- const path = require('path')
449
- const NodePlayer = require('nodeplayer-addon')
450
-
451
- let mainWindow
452
-
453
- function createWindow() {
454
- mainWindow = new BrowserWindow({
455
- webPreferences: {
456
- preload: path.join(__dirname, 'preload.js'),
457
- contextIsolation: true,
458
- nodeIntegration: false,
459
- },
460
- })
461
- mainWindow.loadFile('index.html')
462
-
463
- // Register IPC bridge — connects NodePlayer to renderer
464
- NodePlayer.registerIpc(ipcMain, {
465
- getWindow: () => mainWindow,
466
- licensePath: app.isPackaged
467
- ? path.join(process.resourcesPath, 'license.dat')
468
- : path.join(__dirname, 'license.dat'),
469
- })
470
-
471
- mainWindow.on('closed', () => {
472
- NodePlayer.unregisterIpc(ipcMain)
473
- mainWindow = null
474
- })
475
- }
476
-
477
- app.on('ready', createWindow)
478
- app.on('window-all-closed', () => app.quit())
479
- ```
480
-
481
- #### preload.js
482
-
483
- ```javascript
484
- const { contextBridge, ipcRenderer } = require('electron')
485
-
486
- contextBridge.exposeInMainWorld('electronAPI', {
487
- // Invoke methods
488
- createPlayer: (id, options) => ipcRenderer.invoke('player:create', id, options),
489
- startPlayer: (id, url) => ipcRenderer.invoke('player:start', id, url),
490
- stopPlayer: (id) => ipcRenderer.invoke('player:stop', id),
491
- destroyPlayer: (id) => ipcRenderer.invoke('player:destroy', id),
492
- startRecord: (id, outputPath) => ipcRenderer.invoke('player:startRecord', id, outputPath),
493
- stopRecord: (id) => ipcRenderer.invoke('player:stopRecord', id),
494
- saveScreenshot: (id, outputPath, base64Data) => ipcRenderer.invoke('player:screenshot', id, outputPath, base64Data),
495
-
496
- // Event subscriptions (return unsubscribe functions)
497
- onEvent: (id, callback) => {
498
- const channel = `player:event:${id}`
499
- const handler = (event, data) => callback(data)
500
- ipcRenderer.on(channel, handler)
501
- return () => ipcRenderer.removeListener(channel, handler)
502
- },
503
- onInfo: (id, callback) => {
504
- const channel = `player:info:${id}`
505
- const handler = (event, data) => callback(data)
506
- ipcRenderer.on(channel, handler)
507
- return () => ipcRenderer.removeListener(channel, handler)
508
- },
509
- onData: (id, callback) => {
510
- const channel = `player:data:${id}`
511
- const handler = (event, data) => callback(data)
512
- ipcRenderer.on(channel, handler)
513
- return () => ipcRenderer.removeListener(channel, handler)
514
- },
515
- })
516
- ```
517
-
518
- #### renderer.js
519
-
520
- ```javascript
521
- import VideoPlayer from 'nodeplayer-addon/video-player'
522
-
523
- const videoEl = document.querySelector('video')
524
-
525
- const player = new VideoPlayer(videoEl, 'stream-1')
526
-
527
- player.on('event', (code, msg) => {
528
- console.log(`[stream-1] Event: ${code} ${msg}`)
529
- })
530
-
531
- player.on('error', (err) => {
532
- console.error(`[stream-1] Error: ${err.message}`)
533
- })
534
-
535
- // Start playback
536
- await player.start('rtsp://192.168.1.100:554/stream')
537
-
538
- // Record
539
- const recResult = await player.startRecord('/path/to/recording.mp4')
540
-
541
- // Screenshot
542
- const dataUrl = player.captureScreenshot()
543
-
544
- // Or save screenshot to file via IPC
545
- const saveResult = await player.saveScreenshot('/path/to/snapshot.jpg')
546
-
547
- // Cleanup
548
- await player.stop()
549
- ```
550
-
551
- ### Pattern 2: Main-Process Only (No Renderer)
552
-
553
- For Node.js scripts or Electron main-process-only usage:
554
-
555
- ```javascript
556
- const NodePlayer = require('nodeplayer-addon')
557
-
558
- const player = new NodePlayer({ licensePath: './license.dat' })
559
-
560
- player.on('event', (code, msg) => {
561
- console.log('Event:', code, msg)
562
- })
563
-
564
- player.on('info', (info) => {
565
- console.log('Video:', info.video?.codecString, info.video?.width, 'x', info.video?.height)
566
- console.log('Audio:', info.audio?.codecString, info.audio?.sampleRate, 'Hz')
567
- })
568
-
569
- player.on('data', (buffer) => {
570
- console.log('fMP4 segment:', buffer.length, 'bytes')
571
- })
572
-
573
- player.start('rtsp://192.168.1.100:554/stream')
574
-
575
- // Record
576
- player.startRecord('./output.mp4')
577
- // ... later
578
- player.stopRecord()
579
-
580
- player.stop()
581
- ```
582
-
583
- ### Pattern 3: Multi-Player Grid
584
-
585
- ```javascript
586
- // renderer.js — create multiple players for a grid layout
587
- const streams = [
588
- { id: 'cam-1', url: 'rtsp://192.168.1.100:554/stream' },
589
- { id: 'cam-2', url: 'rtsp://192.168.1.101:554/stream' },
590
- { id: 'cam-3', url: 'rtsp://192.168.1.102:554/stream' },
591
- { id: 'cam-4', url: 'rtsp://192.168.1.103:554/stream' },
592
- ]
593
-
594
- const players = streams.map(({ id, url }) => {
595
- const videoEl = document.getElementById(id)
596
- const player = new VideoPlayer(videoEl, id)
597
- player.on('event', (code, msg) => {
598
- if (code <= 1999) updateStatusLabel(id, `${code}: ${msg}`)
599
- })
600
- player.on('error', (err) => { updateStatusLabel(id, 'Error: ' + err.message) })
601
- player.start(url)
602
- return player
603
- })
604
-
605
- // Stop all
606
- await Promise.all(players.map(p => p.stop()))
607
- ```
608
-
609
- ---
610
-
611
- ## License & Trial Mode
612
-
613
- | Mode | Trigger | Limitation |
614
- |------|---------|------------|
615
- | **Trial** | `new NodePlayer()` or `new NodePlayer({})` | Cumulative 10 minutes of playback |
616
- | **Licensed** | `new NodePlayer({ licensePath: '/path/to/license.dat' })` | Full functionality, no time limit |
617
-
618
- Check mode at runtime:
619
-
620
- ```javascript
621
- if (player.isTrialMode) {
622
- console.log('Running in trial mode (10 min cumulative)')
623
- }
624
- ```
625
-
626
- ---
627
-
628
- ## Troubleshooting
629
-
630
- ### `Cannot find native module`
631
-
632
- **Cause**: The platform-specific `.node` binary is missing from `prebuilds/`.
633
-
634
- **Fix**: Ensure the correct prebuild directory exists for your platform:
635
- - `prebuilds/darwin-arm64/` — macOS Apple Silicon
636
- - `prebuilds/darwin-x64/` — macOS Intel
637
- - `prebuilds/linux-x64/` — Linux x64
638
- - `prebuilds/win32-x64/` — Windows x64
639
-
640
- ### Webpack / Vite Build Errors
641
-
642
- **Fix**: Exclude `nodeplayer-addon` from bundling:
643
-
644
- ```javascript
645
- // webpack.config.js
646
- module.exports = {
647
- externals: { 'nodeplayer-addon': 'commonjs nodeplayer-addon' },
648
- }
649
-
650
- // vite.config.js
651
- export default {
652
- optimizeDeps: { exclude: ['nodeplayer-addon'] },
653
- }
654
- ```
655
-
656
- ### MediaSource Codec Support
657
-
658
- The renderer relies on Chromium's built-in codec support via MSE. Supported codecs vary by platform:
659
-
660
- | Codec | `codecString` | Support |
661
- |-------|--------------|---------|
662
- | H.264 / AVC | `avc1.XXXXXX` | Universal |
663
- | H.265 / HEVC | `hvc1.X.X.X.X` | macOS 10.13+, Windows 10+, some Linux |
664
- | AAC | `mp4a.40.2` | Universal |
665
-
666
- If `addSourceBuffer()` throws `QuotaExceededError` or `NotSupportedError`, the codec is not supported by the current Chromium build.
667
-
668
- ### IPC Data Not Reaching Renderer
669
-
670
- **Check**:
671
- 1. `NodePlayer.registerIpc()` was called in main process
672
- 2. Preload script exposes `window.electronAPI` with `onData`, `onInfo`, `onEvent`
673
- 3. `contextIsolation: true` and `nodeIntegration: false` in BrowserWindow config
674
- 4. BrowserWindow is not destroyed when data arrives
675
-
676
- ### Recording Fails
677
-
678
- **Ensure**: `startRecord()` is called only after `start()` succeeds. The pipeline must be running.
679
-
680
- ### Trial Mode Expired
681
-
682
- **Symptom**: `start()` returns `false` or throws.
683
-
684
- **Fix**: Provide a valid `licensePath` in the constructor options.