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/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/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.
|