id-scanner-lib 1.6.7 → 2.0.0

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.
Files changed (48) hide show
  1. package/dist/id-scanner-lib.esm.js +994 -1139
  2. package/dist/id-scanner-lib.esm.js.map +1 -1
  3. package/dist/id-scanner-lib.js +995 -1144
  4. package/dist/id-scanner-lib.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/compat/index.ts +7 -0
  7. package/src/compat/v1-adapter.ts +84 -0
  8. package/src/core/camera-manager.ts +43 -76
  9. package/src/core/camera-stream-manager.ts +318 -0
  10. package/src/core/config.ts +113 -267
  11. package/src/core/errors.ts +68 -117
  12. package/src/core/logger.ts +158 -81
  13. package/src/core/resource-manager.ts +150 -0
  14. package/src/core/scanner.ts +109 -0
  15. package/src/core/utils/browser.ts +7 -0
  16. package/src/core/utils/canvas-pool.ts +171 -0
  17. package/src/core/utils/canvas.ts +7 -0
  18. package/src/core/utils/image.ts +7 -0
  19. package/src/core/utils/index.ts +9 -0
  20. package/src/core/utils/resource-manager.ts +155 -0
  21. package/src/core/utils/validate.ts +7 -0
  22. package/src/core/utils/worker.ts +130 -0
  23. package/src/modules/face/comparator/comparator.ts +45 -0
  24. package/src/modules/face/comparator/index.ts +1 -0
  25. package/src/modules/face/detector/detector.ts +83 -0
  26. package/src/modules/face/detector/index.ts +2 -0
  27. package/src/modules/face/detector/types.ts +80 -0
  28. package/src/modules/face/face-comparator.ts +150 -0
  29. package/src/modules/face/face-detector-options.ts +104 -0
  30. package/src/modules/face/face-detector.ts +121 -376
  31. package/src/modules/face/face-detector.ts.bak +991 -0
  32. package/src/modules/face/face-model-loader.ts +222 -0
  33. package/src/modules/face/face-result-converter.ts +225 -0
  34. package/src/modules/face/face-tracker.ts +207 -0
  35. package/src/modules/face/liveness/index.ts +7 -0
  36. package/src/modules/face/liveness-detector.ts +2 -2
  37. package/src/modules/face/tracker/index.ts +7 -0
  38. package/src/modules/id-card/anti-fake/index.ts +7 -0
  39. package/src/modules/id-card/detector/index.ts +7 -0
  40. package/src/modules/id-card/id-card-text-parser.ts +151 -0
  41. package/src/modules/id-card/ocr-processor.ts +20 -257
  42. package/src/modules/id-card/ocr-worker.ts +2 -183
  43. package/src/modules/id-card/parser/index.ts +7 -0
  44. package/src/modules/qr/scanner/index.ts +7 -0
  45. package/src/utils/canvas-pool.ts +273 -0
  46. package/src/utils/edge-detector.ts +232 -0
  47. package/src/utils/image-processing.ts +92 -419
  48. package/src/utils/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "id-scanner-lib",
3
- "version": "1.6.7",
3
+ "version": "2.0.0",
4
4
  "description": "Browser-based ID card, QR code, and face recognition scanner with liveness detection",
5
5
  "main": "dist/id-scanner-lib.js",
6
6
  "module": "dist/id-scanner-lib.esm.js",
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file Compat Index
3
+ * @description Compatibility layer for v1 API
4
+ * @module compat
5
+ */
6
+
7
+ export {};
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @file V1 Adapter (Compatibility Layer)
3
+ * @description Provides backward compatibility with v1 IDScanner API
4
+ * @module compat/v1-adapter
5
+ */
6
+
7
+ import { Scanner, ScannerConfig } from '../core/scanner';
8
+ import { EventEmitter } from '../core/event-emitter';
9
+
10
+ /**
11
+ * V1 IDScanner configuration
12
+ */
13
+ export interface IDScannerConfig {
14
+ /** Debug mode */
15
+ debug?: boolean;
16
+ /** Callback when faces are detected */
17
+ onFaceDetected?: (faces: any[]) => void;
18
+ /** Callback on error */
19
+ onError?: (error: Error) => void;
20
+ }
21
+
22
+ /**
23
+ * IDScanner class - provides v1 API compatibility
24
+ * Wraps the new Scanner class with the old IDScanner interface
25
+ */
26
+ export class IDScanner {
27
+ private _scanner: Scanner;
28
+ private _config: IDScannerConfig;
29
+ private _eventEmitter: EventEmitter;
30
+
31
+ /**
32
+ * Create a new IDScanner instance
33
+ * @param config Configuration
34
+ */
35
+ constructor(config: IDScannerConfig = {}) {
36
+ this._config = config;
37
+ this._scanner = new Scanner({ debug: config.debug });
38
+ this._eventEmitter = new EventEmitter();
39
+ }
40
+
41
+ /**
42
+ * Initialize the scanner
43
+ */
44
+ async initialize(): Promise<void> {
45
+ await this._scanner.initialize();
46
+ }
47
+
48
+ /**
49
+ * Start face recognition on a video element
50
+ * @param video Video element to process
51
+ */
52
+ async startFaceRecognition(video: HTMLVideoElement): Promise<void> {
53
+ const faces = await this._scanner.detectFace(video);
54
+ if (this._config.onFaceDetected) {
55
+ this._config.onFaceDetected(faces);
56
+ }
57
+ this._eventEmitter.emit('faceDetected', faces);
58
+ }
59
+
60
+ /**
61
+ * Stop the scanner and release resources
62
+ */
63
+ async stop(): Promise<void> {
64
+ await this._scanner.destroy();
65
+ }
66
+
67
+ /**
68
+ * Register an event handler
69
+ * @param event Event name
70
+ * @param handler Event handler
71
+ */
72
+ on(event: string, handler: (data?: any) => void): void {
73
+ this._eventEmitter.on(event, handler);
74
+ }
75
+
76
+ /**
77
+ * Unregister an event handler
78
+ * @param event Event name
79
+ * @param handler Event handler
80
+ */
81
+ off(event: string, handler: (data?: any) => void): void {
82
+ this._eventEmitter.off(event, handler);
83
+ }
84
+ }
@@ -10,6 +10,7 @@ import { ConfigManager } from './config';
10
10
  import { Result } from './result';
11
11
  import { CameraAccessError, DeviceError } from './errors';
12
12
  import { getMediaConstraints } from '../utils';
13
+ import { CameraStreamManager, StreamState } from './camera-stream-manager';
13
14
 
14
15
  /**
15
16
  * 摄像头设备信息
@@ -108,8 +109,8 @@ export class CameraManager extends EventEmitter {
108
109
  private readonly config: ConfigManager;
109
110
  /** 视频元素 */
110
111
  private videoElement: HTMLVideoElement | null = null;
111
- /** 媒体流 */
112
- private mediaStream: MediaStream | null = null;
112
+ /** 摄像头流管理器 */
113
+ private streamManager: CameraStreamManager;
113
114
  /** 摄像头状态 */
114
115
  private status: CameraStatus = CameraStatus.NOT_INITIALIZED;
115
116
  /** 可用的摄像头设备列表 */
@@ -138,6 +139,18 @@ export class CameraManager extends EventEmitter {
138
139
  super();
139
140
  this.logger = Logger.getInstance();
140
141
  this.config = ConfigManager.getInstance();
142
+ this.streamManager = new CameraStreamManager();
143
+
144
+ // 转发流管理器的轨道结束事件
145
+ this.streamManager.on('stream:track:ended', () => {
146
+ this.emit(CameraEvent.TRACK_ENDED);
147
+ this.stop();
148
+ });
149
+
150
+ // 转发流管理器的分辨率变化事件
151
+ this.streamManager.on('stream:resolution:change', (data: any) => {
152
+ this.emit(CameraEvent.RESOLUTION_CHANGE, data);
153
+ });
141
154
  }
142
155
 
143
156
  /**
@@ -272,33 +285,20 @@ export class CameraManager extends EventEmitter {
272
285
  constraints = getMediaConstraints(width, height, facingMode, frameRate);
273
286
  }
274
287
 
275
- // 获取媒体流
276
- this.logger.debug('CameraManager', `Requesting camera access: ${JSON.stringify(constraints)}`);
277
- const stream = await navigator.mediaDevices.getUserMedia(constraints);
278
-
279
- // 停止旧的媒体流
280
- this.stopMediaStream();
281
-
282
- // 设置新的媒体流
283
- this.mediaStream = stream;
288
+ // 使用流管理器启动摄像头
289
+ const stream = await this.streamManager.start(constraints, this.videoElement);
284
290
 
285
291
  // 获取实际选择的设备ID
286
292
  const videoTrack = stream.getVideoTracks()[0];
287
293
  if (videoTrack) {
288
294
  this.activeDeviceId = videoTrack.getSettings().deviceId || null;
289
-
290
- // 监听轨道结束事件
291
- videoTrack.onended = this.handleTrackEnded.bind(this);
292
295
  }
293
296
 
294
- // 将流连接到视频元素
297
+ // 创建视频准备就绪Promise
298
+ this.createVideoReadyPromise();
299
+
300
+ // 开始播放
295
301
  if (this.videoElement) {
296
- this.videoElement.srcObject = stream;
297
-
298
- // 创建视频准备就绪Promise
299
- this.createVideoReadyPromise();
300
-
301
- // 开始播放
302
302
  const playPromise = this.videoElement.play();
303
303
  if (playPromise) {
304
304
  await playPromise;
@@ -342,9 +342,7 @@ export class CameraManager extends EventEmitter {
342
342
  return false;
343
343
  }
344
344
 
345
- if (this.videoElement) {
346
- this.videoElement.pause();
347
- }
345
+ this.streamManager.pause();
348
346
 
349
347
  // 暂停帧处理
350
348
  this.stopFrameProcessing();
@@ -363,29 +361,18 @@ export class CameraManager extends EventEmitter {
363
361
  return false;
364
362
  }
365
363
 
366
- if (this.videoElement && this.videoElement.paused && this.mediaStream) {
367
- try {
368
- await this.videoElement.play();
369
-
370
- // 恢复帧处理
371
- if (this.frameProcessingEnabled) {
372
- this.startFrameProcessing();
373
- }
374
-
375
- this.status = CameraStatus.ACTIVE;
376
- this.emit(CameraEvent.RESUME);
377
-
378
- return true;
379
- } catch (error) {
380
- const errorMessage = error instanceof Error ? error.message : String(error);
381
- this.logger.error('CameraManager', `Failed to resume camera: ${errorMessage}`, error instanceof Error ? error : new Error(errorMessage));
382
-
383
- this.emit(CameraEvent.ERROR, {
384
- error: new CameraAccessError(`Resume failed: ${errorMessage}`)
385
- });
386
-
387
- return false;
364
+ const resumed = await this.streamManager.resume();
365
+
366
+ if (resumed) {
367
+ // 恢复帧处理
368
+ if (this.frameProcessingEnabled) {
369
+ this.startFrameProcessing();
388
370
  }
371
+
372
+ this.status = CameraStatus.ACTIVE;
373
+ this.emit(CameraEvent.RESUME);
374
+
375
+ return true;
389
376
  }
390
377
 
391
378
  return false;
@@ -408,8 +395,8 @@ export class CameraManager extends EventEmitter {
408
395
  this.videoElement.srcObject = null;
409
396
  }
410
397
 
411
- // 停止媒体流
412
- this.stopMediaStream();
398
+ // 停止流
399
+ this.streamManager.stop();
413
400
 
414
401
  this.status = CameraStatus.STOPPED;
415
402
  this.emit(CameraEvent.STOP);
@@ -464,7 +451,7 @@ export class CameraManager extends EventEmitter {
464
451
  async loadDevices(): Promise<CameraDevice[]> {
465
452
  try {
466
453
  // 请求媒体设备权限
467
- if (!this.mediaStream) {
454
+ if (!this.streamManager.getStream()) {
468
455
  // 短暂获取摄像头权限以列出设备标签
469
456
  const tempStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
470
457
 
@@ -545,7 +532,7 @@ export class CameraManager extends EventEmitter {
545
532
  * 获取当前媒体流
546
533
  */
547
534
  getMediaStream(): MediaStream | null {
548
- return this.mediaStream;
535
+ return this.streamManager.getStream();
549
536
  }
550
537
 
551
538
  /**
@@ -567,9 +554,9 @@ export class CameraManager extends EventEmitter {
567
554
  this.videoElement.playsInline = true; // iOS需要
568
555
  this.videoElement.muted = true;
569
556
 
570
- // 如果已有流,附加到视频元素
571
- if (this.mediaStream && this.status === CameraStatus.ACTIVE) {
572
- this.videoElement.srcObject = this.mediaStream;
557
+ // 如果流已活动,附加到视频元素
558
+ if (this.streamManager.getStream() && this.status === CameraStatus.ACTIVE) {
559
+ this.videoElement.srcObject = this.streamManager.getStream();
573
560
  this.videoElement.play().catch(error => {
574
561
  this.logger.error('CameraManager', `Failed to play video: ${error.message}`, error);
575
562
  });
@@ -671,8 +658,8 @@ export class CameraManager extends EventEmitter {
671
658
  dispose(): void {
672
659
  this.stop();
673
660
 
674
- // 停止并释放媒体流
675
- this.stopMediaStream();
661
+ // 释放流管理器资源
662
+ this.streamManager.dispose();
676
663
 
677
664
  if (this.canvas) {
678
665
  // 清空 canvas 内容
@@ -695,26 +682,6 @@ export class CameraManager extends EventEmitter {
695
682
  this.logger.debug('CameraManager', 'Camera resources disposed');
696
683
  }
697
684
 
698
- /**
699
- * 停止媒体流并释放轨道
700
- */
701
- private stopMediaStream(): void {
702
- if (this.mediaStream) {
703
- this.mediaStream.getTracks().forEach(track => track.stop());
704
- this.mediaStream = null;
705
- }
706
- }
707
-
708
- /**
709
- * 处理媒体流轨道结束事件
710
- */
711
- private handleTrackEnded(): void {
712
- this.logger.debug('CameraManager', 'Camera track ended');
713
-
714
- this.emit(CameraEvent.TRACK_ENDED);
715
- this.stop();
716
- }
717
-
718
685
  /**
719
686
  * 创建视频准备就绪的Promise
720
687
  */
@@ -810,4 +777,4 @@ export class CameraManager extends EventEmitter {
810
777
  this.logger.error('CameraManager', `Frame processing error: ${errorMessage}`, error instanceof Error ? error : new Error(errorMessage));
811
778
  }
812
779
  }
813
- }
780
+ }
@@ -0,0 +1,318 @@
1
+ /**
2
+ * @file 摄像头流管理器
3
+ * @description 管理摄像头视频流的创建、销毁、暂停和恢复
4
+ * @module core/camera-stream-manager
5
+ */
6
+
7
+ import { EventEmitter } from './event-emitter';
8
+ import { Logger } from './logger';
9
+ import { CameraAccessError } from './errors';
10
+ import { Disposable } from '../utils/resource-manager';
11
+
12
+ /**
13
+ * 摄像头流状态
14
+ */
15
+ export enum StreamState {
16
+ /** 空闲 */
17
+ IDLE = 'idle',
18
+ /** 启动中 */
19
+ STARTING = 'starting',
20
+ /** 活动中 */
21
+ ACTIVE = 'active',
22
+ /** 已暂停 */
23
+ PAUSED = 'paused',
24
+ /** 已停止 */
25
+ STOPPED = 'stopped',
26
+ /** 错误 */
27
+ ERROR = 'error'
28
+ }
29
+
30
+ /**
31
+ * 摄像头流事件
32
+ */
33
+ export enum CameraStreamEvent {
34
+ /** 流启动 */
35
+ START = 'stream:start',
36
+ /** 流停止 */
37
+ STOP = 'stream:stop',
38
+ /** 流暂停 */
39
+ PAUSE = 'stream:pause',
40
+ /** 流恢复 */
41
+ RESUME = 'stream:resume',
42
+ /** 流错误 */
43
+ ERROR = 'stream:error',
44
+ /** 轨道结束 */
45
+ TRACK_ENDED = 'stream:track:ended',
46
+ /** 分辨率变化 */
47
+ RESOLUTION_CHANGE = 'stream:resolution:change'
48
+ }
49
+
50
+ /**
51
+ * 摄像头流管理器接口
52
+ */
53
+ export interface ICameraStreamManager {
54
+ /** 获取媒体流 */
55
+ getStream(): MediaStream | null;
56
+ /** 是否活动 */
57
+ isActive(): boolean;
58
+ /** 获取当前状态 */
59
+ getState(): StreamState;
60
+ /** 释放资源 */
61
+ dispose(): Promise<void>;
62
+ }
63
+
64
+ /**
65
+ * 摄像头流管理器
66
+ * 负责摄像头视频流的生命周期管理
67
+ */
68
+ export class CameraStreamManager extends EventEmitter implements ICameraStreamManager, Disposable {
69
+ /** 日志记录器 */
70
+ private readonly logger: Logger;
71
+ /** 媒体流 */
72
+ private mediaStream: MediaStream | null = null;
73
+ /** 流状态 */
74
+ private state: StreamState = StreamState.IDLE;
75
+ /** 视频轨道 */
76
+ private videoTrack: MediaStreamTrack | null = null;
77
+ /** 音频轨道 */
78
+ private audioTrack: MediaStreamTrack | null = null;
79
+ /** 视频元素引用 */
80
+ private videoElement: HTMLVideoElement | null = null;
81
+ /** 是否已暂停(用于pause/resume) */
82
+ private isPaused: boolean = false;
83
+
84
+ /**
85
+ * 构造函数
86
+ */
87
+ constructor() {
88
+ super();
89
+ this.logger = Logger.getInstance();
90
+ }
91
+
92
+ /**
93
+ * 获取媒体流
94
+ */
95
+ getStream(): MediaStream | null {
96
+ return this.mediaStream;
97
+ }
98
+
99
+ /**
100
+ * 是否活动
101
+ */
102
+ isActive(): boolean {
103
+ return this.state === StreamState.ACTIVE && !this.isPaused;
104
+ }
105
+
106
+ /**
107
+ * 获取当前状态
108
+ */
109
+ getState(): StreamState {
110
+ return this.state;
111
+ }
112
+
113
+ /**
114
+ * 启动摄像头流
115
+ * @param constraints 媒体约束
116
+ * @param videoElement 可选的视频元素
117
+ */
118
+ async start(
119
+ constraints: MediaStreamConstraints,
120
+ videoElement?: HTMLVideoElement | null
121
+ ): Promise<MediaStream> {
122
+ if (this.state === StreamState.ACTIVE && this.mediaStream) {
123
+ this.logger.debug('CameraStreamManager', 'Stream already active');
124
+ return this.mediaStream;
125
+ }
126
+
127
+ this.state = StreamState.STARTING;
128
+ this.logger.debug('CameraStreamManager', `Requesting camera access: ${JSON.stringify(constraints)}`);
129
+
130
+ try {
131
+ // 停止旧流
132
+ this.stopStreamOnly();
133
+
134
+ // 获取新流
135
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
136
+ this.mediaStream = stream;
137
+
138
+ // 获取轨道
139
+ const tracks = stream.getTracks();
140
+ this.videoTrack = tracks.find(t => t.kind === 'video') || null;
141
+ this.audioTrack = tracks.find(t => t.kind === 'audio') || null;
142
+
143
+ // 监听轨道结束事件
144
+ if (this.videoTrack) {
145
+ this.videoTrack.onended = this.handleTrackEnded.bind(this);
146
+ }
147
+
148
+ // 保存视频元素引用
149
+ this.videoElement = videoElement || null;
150
+
151
+ // 将流连接到视频元素
152
+ if (this.videoElement) {
153
+ this.videoElement.srcObject = stream;
154
+ }
155
+
156
+ this.state = StreamState.ACTIVE;
157
+ this.isPaused = false;
158
+
159
+ this.emit(CameraStreamEvent.START, {
160
+ stream,
161
+ deviceId: this.videoTrack?.getSettings().deviceId,
162
+ settings: this.videoTrack?.getSettings()
163
+ });
164
+
165
+ return stream;
166
+ } catch (error) {
167
+ this.state = StreamState.ERROR;
168
+ const errorMessage = error instanceof Error ? error.message : String(error);
169
+ this.logger.error('CameraStreamManager', `Failed to start stream: ${errorMessage}`, error instanceof Error ? error : new Error(errorMessage));
170
+
171
+ const cameraError = new CameraAccessError(errorMessage);
172
+ this.emit(CameraStreamEvent.ERROR, { error: cameraError });
173
+ throw cameraError;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * 停止摄像头流
179
+ */
180
+ stop(): void {
181
+ if (this.state !== StreamState.ACTIVE && this.state !== StreamState.PAUSED) {
182
+ return;
183
+ }
184
+
185
+ // 停止所有轨道
186
+ this.stopStreamOnly();
187
+
188
+ // 断开视频元素连接
189
+ if (this.videoElement) {
190
+ this.videoElement.pause();
191
+ this.videoElement.srcObject = null;
192
+ this.videoElement = null;
193
+ }
194
+
195
+ this.state = StreamState.STOPPED;
196
+ this.isPaused = false;
197
+
198
+ this.emit(CameraStreamEvent.STOP);
199
+ }
200
+
201
+ /**
202
+ * 暂停摄像头流
203
+ */
204
+ pause(): void {
205
+ if (this.state !== StreamState.ACTIVE || this.isPaused) {
206
+ return;
207
+ }
208
+
209
+ // 暂停视频元素
210
+ if (this.videoElement) {
211
+ this.videoElement.pause();
212
+ }
213
+
214
+ this.isPaused = true;
215
+ this.state = StreamState.PAUSED;
216
+
217
+ this.emit(CameraStreamEvent.PAUSE);
218
+ }
219
+
220
+ /**
221
+ * 恢复摄像头流
222
+ * @returns 是否成功恢复
223
+ */
224
+ async resume(): Promise<boolean> {
225
+ if (this.state !== StreamState.PAUSED || !this.isPaused) {
226
+ return false;
227
+ }
228
+
229
+ if (this.videoElement && this.videoElement.paused && this.mediaStream) {
230
+ try {
231
+ await this.videoElement.play();
232
+ this.isPaused = false;
233
+ this.state = StreamState.ACTIVE;
234
+
235
+ this.emit(CameraStreamEvent.RESUME);
236
+ return true;
237
+ } catch (error) {
238
+ const errorMessage = error instanceof Error ? error.message : String(error);
239
+ this.logger.error('CameraStreamManager', `Failed to resume stream: ${errorMessage}`, error instanceof Error ? error : new Error(errorMessage));
240
+ return false;
241
+ }
242
+ }
243
+
244
+ return false;
245
+ }
246
+
247
+ /**
248
+ * 连接到视频元素
249
+ * @param videoElement 视频元素
250
+ */
251
+ attachToVideoElement(videoElement: HTMLVideoElement): void {
252
+ this.videoElement = videoElement;
253
+
254
+ if (this.mediaStream) {
255
+ videoElement.srcObject = this.mediaStream;
256
+ }
257
+ }
258
+
259
+ /**
260
+ * 断开视频元素连接
261
+ */
262
+ detachVideoElement(): void {
263
+ if (this.videoElement) {
264
+ this.videoElement.pause();
265
+ this.videoElement.srcObject = null;
266
+ this.videoElement = null;
267
+ }
268
+ }
269
+
270
+ /**
271
+ * 获取视频轨道设置
272
+ */
273
+ getVideoSettings(): MediaTrackSettings | null {
274
+ return this.videoTrack?.getSettings() || null;
275
+ }
276
+
277
+ /**
278
+ * 获取视频轨道
279
+ */
280
+ getVideoTrack(): MediaStreamTrack | null {
281
+ return this.videoTrack;
282
+ }
283
+
284
+ /**
285
+ * 释放资源
286
+ */
287
+ async dispose(): Promise<void> {
288
+ this.stop();
289
+
290
+ this.videoTrack = null;
291
+ this.audioTrack = null;
292
+ this.mediaStream = null;
293
+ this.state = StreamState.IDLE;
294
+
295
+ this.logger.debug('CameraStreamManager', 'CameraStreamManager resources disposed');
296
+ }
297
+
298
+ /**
299
+ * 停止媒体流(仅停止轨道不断开视频元素)
300
+ */
301
+ private stopStreamOnly(): void {
302
+ if (this.mediaStream) {
303
+ this.mediaStream.getTracks().forEach(track => track.stop());
304
+ this.mediaStream = null;
305
+ }
306
+
307
+ this.videoTrack = null;
308
+ this.audioTrack = null;
309
+ }
310
+
311
+ /**
312
+ * 处理轨道结束事件
313
+ */
314
+ private handleTrackEnded(): void {
315
+ this.logger.debug('CameraStreamManager', 'Camera track ended');
316
+ this.emit(CameraStreamEvent.TRACK_ENDED);
317
+ }
318
+ }