unified-video-framework 1.4.382 → 1.4.384

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 (42) hide show
  1. package/package.json +1 -1
  2. package/packages/core/dist/interfaces.d.ts +28 -2
  3. package/packages/core/dist/interfaces.d.ts.map +1 -1
  4. package/packages/core/dist/interfaces.js +0 -1
  5. package/packages/core/dist/interfaces.js.map +1 -1
  6. package/packages/core/src/interfaces.ts +53 -3
  7. package/packages/react-native/dist/drm/AndroidDRMProtection.js +1 -1
  8. package/packages/react-native/dist/drm/iOSDRMProtection.js +1 -1
  9. package/packages/web/dist/WebPlayer.d.ts +2 -0
  10. package/packages/web/dist/WebPlayer.d.ts.map +1 -1
  11. package/packages/web/dist/WebPlayer.js +9731 -9644
  12. package/packages/web/dist/WebPlayer.js.map +1 -1
  13. package/packages/web/dist/drm/DRMHelper.d.ts +28 -0
  14. package/packages/web/dist/drm/DRMHelper.d.ts.map +1 -0
  15. package/packages/web/dist/drm/DRMHelper.js +117 -0
  16. package/packages/web/dist/drm/DRMHelper.js.map +1 -0
  17. package/packages/web/dist/index.d.ts +0 -1
  18. package/packages/web/dist/index.d.ts.map +1 -1
  19. package/packages/web/dist/index.js +6 -7
  20. package/packages/web/dist/index.js.map +1 -1
  21. package/packages/web/dist/react/WebPlayerView.d.ts +21 -0
  22. package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
  23. package/packages/web/dist/react/WebPlayerView.js.map +1 -1
  24. package/packages/web/dist/security/CanvasVideoRenderer.d.ts +26 -0
  25. package/packages/web/dist/security/CanvasVideoRenderer.d.ts.map +1 -0
  26. package/packages/web/dist/security/CanvasVideoRenderer.js +143 -0
  27. package/packages/web/dist/security/CanvasVideoRenderer.js.map +1 -0
  28. package/packages/web/dist/security/ScreenProtectionController.d.ts +39 -0
  29. package/packages/web/dist/security/ScreenProtectionController.d.ts.map +1 -0
  30. package/packages/web/dist/security/ScreenProtectionController.js +278 -0
  31. package/packages/web/dist/security/ScreenProtectionController.js.map +1 -0
  32. package/packages/web/src/WebPlayer.ts +120 -2
  33. package/packages/web/src/drm/DRMHelper.ts +203 -0
  34. package/packages/web/src/index.ts +0 -3
  35. package/packages/web/src/react/WebPlayerView.tsx +24 -0
  36. package/packages/web/src/security/CanvasVideoRenderer.ts +246 -0
  37. package/packages/web/src/security/ScreenProtectionController.ts +391 -0
  38. package/scripts/fix-imports.js +18 -16
  39. package/packages/core/src/interfaces/IDRMProtection.ts +0 -285
  40. package/packages/react-native/src/drm/AndroidDRMProtection.ts +0 -419
  41. package/packages/react-native/src/drm/iOSDRMProtection.ts +0 -415
  42. package/packages/web/src/drm/WebDRMProtection.ts +0 -596
@@ -1,596 +0,0 @@
1
- /**
2
- * Web DRM Protection Implementation
3
- *
4
- * Implements Netflix-like content protection for web browsers using:
5
- * - Encrypted Media Extensions (EME)
6
- * - Widevine / PlayReady DRM
7
- * - Screen recording detection
8
- * - Tab capture blocking
9
- * - Chromecast support
10
- */
11
-
12
- import {
13
- IDRMProtection,
14
- IDRMProtectionConfig,
15
- IDRMProtectionStatus,
16
- CastDevice,
17
- CastDeviceType,
18
- DRMError,
19
- DRMErrorCode,
20
- DRMKeySystemConfig,
21
- } from '@unified-video/core';
22
-
23
- export class WebDRMProtection implements IDRMProtection {
24
- private config: IDRMProtectionConfig;
25
- private videoElement: HTMLVideoElement;
26
- private mediaKeys: MediaKeys | null = null;
27
- private status: IDRMProtectionStatus;
28
- private screenRecordingCheckInterval: number | null = null;
29
- private castSession: any = null;
30
- private castContext: any = null;
31
-
32
- // Key Systems in preference order
33
- private readonly KEY_SYSTEMS = {
34
- widevine: 'com.widevine.alpha',
35
- playready: 'com.microsoft.playready',
36
- clearkey: 'org.w3.clearkey', // Fallback for testing
37
- };
38
-
39
- constructor(videoElement: HTMLVideoElement) {
40
- this.videoElement = videoElement;
41
- this.config = { enabled: false };
42
- this.status = {
43
- isProtected: false,
44
- drmSystem: 'none',
45
- isScreenRecordingBlocked: false,
46
- isAudioCaptureBlocked: false,
47
- isScreenshotBlocked: false,
48
- isCasting: false,
49
- screenRecordingDetected: false,
50
- mirroringDetected: false,
51
- };
52
- }
53
-
54
- /**
55
- * Initialize DRM protection
56
- */
57
- async initialize(config: IDRMProtectionConfig): Promise<void> {
58
- this.config = {
59
- blockScreenRecording: true,
60
- blockAudioCapture: true,
61
- blockScreenshots: true,
62
- allowCasting: true,
63
- blockMirroring: true,
64
- widevineSecurityLevel: 'L1',
65
- ...config,
66
- };
67
-
68
- if (!this.config.enabled) {
69
- console.log('[DRM] Protection disabled by configuration');
70
- return;
71
- }
72
-
73
- console.log('[DRM] Initializing web DRM protection...', this.config);
74
-
75
- try {
76
- // Check if EME is supported
77
- if (!this.isEMESupported()) {
78
- throw this.createError(
79
- DRMErrorCode.DEVICE_NOT_SUPPORTED,
80
- 'Encrypted Media Extensions (EME) not supported'
81
- );
82
- }
83
-
84
- // Initialize DRM key system
85
- if (this.config.licenseServerUrl) {
86
- await this.initializeDRMKeySystem();
87
- }
88
-
89
- // Start screen recording detection
90
- if (this.config.blockScreenRecording) {
91
- this.startScreenRecordingDetection();
92
- }
93
-
94
- // Initialize casting support
95
- if (this.config.allowCasting) {
96
- await this.initializeCastSupport();
97
- }
98
-
99
- // Block screenshot capture
100
- if (this.config.blockScreenshots) {
101
- this.blockScreenshots();
102
- }
103
-
104
- // Prevent tab capture
105
- if (this.config.blockMirroring) {
106
- this.preventTabCapture();
107
- }
108
-
109
- this.status.isProtected = true;
110
- console.log('[DRM] Protection initialized successfully');
111
- } catch (error) {
112
- console.error('[DRM] Initialization failed:', error);
113
- this.config.onDRMError?.(error as DRMError);
114
- throw error;
115
- }
116
- }
117
-
118
- /**
119
- * Check if EME is supported
120
- */
121
- private isEMESupported(): boolean {
122
- return !!(
123
- window.navigator &&
124
- (window.navigator as any).requestMediaKeySystemAccess &&
125
- window.MediaKeys
126
- );
127
- }
128
-
129
- /**
130
- * Initialize DRM Key System (Widevine/PlayReady)
131
- */
132
- private async initializeDRMKeySystem(): Promise<void> {
133
- console.log('[DRM] Initializing key system...');
134
-
135
- // Try Widevine first, then PlayReady
136
- const keySystemConfigs: DRMKeySystemConfig[] = [
137
- {
138
- keySystem: this.KEY_SYSTEMS.widevine,
139
- licenseServerUrl: this.config.licenseServerUrl!,
140
- certificateUrl: this.config.certificateUrl,
141
- headers: this.config.licenseHeaders,
142
- // Widevine robustness levels
143
- videoRobustness: this.config.widevineSecurityLevel === 'L1' ? 'HW_SECURE_ALL' : 'SW_SECURE_CRYPTO',
144
- audioRobustness: this.config.widevineSecurityLevel === 'L1' ? 'HW_SECURE_ALL' : 'SW_SECURE_CRYPTO',
145
- persistentState: 'optional',
146
- distinctiveIdentifier: 'optional',
147
- },
148
- {
149
- keySystem: this.KEY_SYSTEMS.playready,
150
- licenseServerUrl: this.config.licenseServerUrl!,
151
- headers: this.config.licenseHeaders,
152
- videoRobustness: '3000', // PlayReady SL3000
153
- audioRobustness: '3000',
154
- persistentState: 'optional',
155
- distinctiveIdentifier: 'optional',
156
- },
157
- ];
158
-
159
- for (const config of keySystemConfigs) {
160
- try {
161
- await this.requestMediaKeySystemAccess(config);
162
- console.log(`[DRM] Successfully initialized ${config.keySystem}`);
163
- return;
164
- } catch (error) {
165
- console.warn(`[DRM] ${config.keySystem} not available:`, error);
166
- }
167
- }
168
-
169
- throw this.createError(
170
- DRMErrorCode.WIDEVINE_NOT_AVAILABLE,
171
- 'No supported DRM system available (tried Widevine and PlayReady)'
172
- );
173
- }
174
-
175
- /**
176
- * Request Media Key System Access
177
- */
178
- private async requestMediaKeySystemAccess(config: DRMKeySystemConfig): Promise<void> {
179
- const keySystemConfig = [
180
- {
181
- initDataTypes: ['cenc', 'keyids', 'webm'],
182
- audioCapabilities: [
183
- {
184
- contentType: 'audio/mp4; codecs="mp4a.40.2"',
185
- robustness: config.audioRobustness || '',
186
- },
187
- ],
188
- videoCapabilities: [
189
- {
190
- contentType: 'video/mp4; codecs="avc1.42E01E"',
191
- robustness: config.videoRobustness || '',
192
- },
193
- {
194
- contentType: 'video/webm; codecs="vp9"',
195
- robustness: config.videoRobustness || '',
196
- },
197
- ],
198
- distinctiveIdentifier: config.distinctiveIdentifier || 'optional',
199
- persistentState: config.persistentState || 'optional',
200
- },
201
- ];
202
-
203
- const keySystemAccess = await navigator.requestMediaKeySystemAccess(
204
- config.keySystem,
205
- keySystemConfig
206
- );
207
-
208
- this.mediaKeys = await keySystemAccess.createMediaKeys();
209
- await this.videoElement.setMediaKeys(this.mediaKeys);
210
-
211
- // Set up license acquisition
212
- this.setupLicenseAcquisition(config);
213
-
214
- // Update status
215
- if (config.keySystem === this.KEY_SYSTEMS.widevine) {
216
- this.status.drmSystem = 'widevine';
217
- this.status.securityLevel = config.videoRobustness === 'HW_SECURE_ALL' ? 'L1' : 'L3';
218
- } else if (config.keySystem === this.KEY_SYSTEMS.playready) {
219
- this.status.drmSystem = 'playready';
220
- }
221
- }
222
-
223
- /**
224
- * Setup license acquisition from license server
225
- */
226
- private setupLicenseAcquisition(config: DRMKeySystemConfig): void {
227
- this.videoElement.addEventListener('encrypted', async (event: any) => {
228
- console.log('[DRM] Encrypted event received, requesting license...');
229
-
230
- try {
231
- if (!this.mediaKeys) {
232
- throw new Error('MediaKeys not initialized');
233
- }
234
-
235
- const session = this.mediaKeys.createSession();
236
-
237
- // Listen for license messages
238
- session.addEventListener('message', async (messageEvent: any) => {
239
- console.log('[DRM] License request message received');
240
-
241
- try {
242
- const response = await fetch(config.licenseServerUrl, {
243
- method: 'POST',
244
- headers: {
245
- 'Content-Type': 'application/octet-stream',
246
- ...config.headers,
247
- },
248
- body: messageEvent.message,
249
- });
250
-
251
- if (!response.ok) {
252
- throw this.createError(
253
- DRMErrorCode.LICENSE_REQUEST_FAILED,
254
- `License server returned ${response.status}`
255
- );
256
- }
257
-
258
- const license = await response.arrayBuffer();
259
- await session.update(license);
260
-
261
- console.log('[DRM] License acquired successfully');
262
- this.config.onLicenseAcquired?.();
263
- } catch (error) {
264
- console.error('[DRM] License acquisition failed:', error);
265
- this.config.onDRMError?.(error as DRMError);
266
- }
267
- });
268
-
269
- // Generate license request
270
- await session.generateRequest(event.initDataType, event.initData);
271
- } catch (error) {
272
- console.error('[DRM] Failed to generate license request:', error);
273
- this.config.onDRMError?.(error as DRMError);
274
- }
275
- });
276
- }
277
-
278
- /**
279
- * Start screen recording detection
280
- * Uses multiple detection methods
281
- */
282
- private startScreenRecordingDetection(): void {
283
- console.log('[DRM] Starting screen recording detection...');
284
-
285
- // Method 1: getDisplayMedia API detection
286
- this.detectGetDisplayMedia();
287
-
288
- // Method 2: Canvas fingerprinting detection
289
- this.detectCanvasCapture();
290
-
291
- // Method 3: Tab capture detection (Chrome)
292
- this.detectTabCapture();
293
-
294
- // Method 4: Periodic frame analysis
295
- this.screenRecordingCheckInterval = window.setInterval(() => {
296
- this.analyzeScreenRecordingSignals();
297
- }, 1000);
298
-
299
- this.status.isScreenRecordingBlocked = true;
300
- }
301
-
302
- /**
303
- * Detect getDisplayMedia screen capture
304
- */
305
- private detectGetDisplayMedia(): void {
306
- if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
307
- return;
308
- }
309
-
310
- // Override getDisplayMedia to detect screen capture attempts
311
- const originalGetDisplayMedia = navigator.mediaDevices.getDisplayMedia.bind(
312
- navigator.mediaDevices
313
- );
314
-
315
- navigator.mediaDevices.getDisplayMedia = async function (constraints?: any) {
316
- console.warn('[DRM] Screen capture attempt detected via getDisplayMedia');
317
- // Block the request or notify
318
- throw new DOMException('Screen capture blocked by DRM protection', 'NotAllowedError');
319
- };
320
- }
321
-
322
- /**
323
- * Detect canvas-based capture
324
- */
325
- private detectCanvasCapture(): void {
326
- // Monitor canvas operations on the video
327
- const observer = new MutationObserver((mutations) => {
328
- mutations.forEach((mutation) => {
329
- if (mutation.type === 'childList') {
330
- mutation.addedNodes.forEach((node) => {
331
- if (node.nodeName === 'CANVAS') {
332
- console.warn('[DRM] Canvas element detected - potential screen capture');
333
- this.handleScreenRecordingDetected();
334
- }
335
- });
336
- }
337
- });
338
- });
339
-
340
- observer.observe(document.body, {
341
- childList: true,
342
- subtree: true,
343
- });
344
- }
345
-
346
- /**
347
- * Detect tab capture (Chrome-specific)
348
- */
349
- private detectTabCapture(): void {
350
- // Chrome exposes tab capture status
351
- if ('mediaSession' in navigator) {
352
- document.addEventListener('visibilitychange', () => {
353
- if (document.hidden) {
354
- console.log('[DRM] Tab hidden - checking for capture...');
355
- }
356
- });
357
- }
358
- }
359
-
360
- /**
361
- * Analyze various signals for screen recording
362
- */
363
- private analyzeScreenRecordingSignals(): void {
364
- // Check if video is playing in background tab
365
- if (document.hidden && !this.videoElement.paused) {
366
- console.warn('[DRM] Video playing in hidden tab - possible screen recording');
367
- }
368
-
369
- // Check for unusual video element states
370
- if ((this.videoElement as any).captureStream) {
371
- console.warn('[DRM] captureStream API available - monitoring...');
372
- }
373
- }
374
-
375
- /**
376
- * Handle screen recording detection
377
- */
378
- private handleScreenRecordingDetected(): void {
379
- console.error('[DRM] ⚠️ SCREEN RECORDING DETECTED!');
380
- this.status.screenRecordingDetected = true;
381
-
382
- // Black out video
383
- this.videoElement.style.filter = 'brightness(0)';
384
- this.videoElement.pause();
385
-
386
- // Notify callback
387
- this.config.onScreenRecordingDetected?.();
388
-
389
- // Show warning overlay
390
- this.showProtectionWarning('Screen recording detected. Playback blocked.');
391
- }
392
-
393
- /**
394
- * Block screenshots
395
- */
396
- private blockScreenshots(): void {
397
- // CSS-based screenshot prevention
398
- this.videoElement.style.webkitUserSelect = 'none';
399
- this.videoElement.style.userSelect = 'none';
400
- this.videoElement.setAttribute('oncontextmenu', 'return false');
401
-
402
- // Prevent drag-and-drop
403
- this.videoElement.addEventListener('dragstart', (e) => e.preventDefault());
404
-
405
- // Detect PrintScreen key
406
- document.addEventListener('keyup', (e) => {
407
- if (e.key === 'PrintScreen') {
408
- console.warn('[DRM] Screenshot attempt detected (PrintScreen key)');
409
- this.config.onScreenshotAttempted?.();
410
- }
411
- });
412
-
413
- this.status.isScreenshotBlocked = true;
414
- console.log('[DRM] Screenshot blocking enabled');
415
- }
416
-
417
- /**
418
- * Prevent tab capture and mirroring
419
- */
420
- private preventTabCapture(): void {
421
- // Detect mirroring via multiple screens
422
- if (window.screen && (window.screen as any).isExtended) {
423
- console.warn('[DRM] Extended display detected');
424
- this.status.mirroringDetected = true;
425
- this.config.onMirroringDetected?.();
426
- }
427
-
428
- // Monitor presentation API
429
- if ('presentation' in navigator) {
430
- console.log('[DRM] Monitoring presentation API for mirroring...');
431
- }
432
- }
433
-
434
- /**
435
- * Initialize Chromecast support
436
- */
437
- private async initializeCastSupport(): Promise<void> {
438
- if (!(window as any).chrome || !(window as any).chrome.cast) {
439
- console.warn('[DRM] Google Cast API not available');
440
- return;
441
- }
442
-
443
- console.log('[DRM] Initializing Chromecast support...');
444
-
445
- // Load Cast SDK
446
- const script = document.createElement('script');
447
- script.src = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1';
448
- document.head.appendChild(script);
449
-
450
- await new Promise((resolve) => {
451
- script.onload = resolve;
452
- });
453
-
454
- // Initialize Cast API
455
- (window as any).__onGCastApiAvailable = (isAvailable: boolean) => {
456
- if (isAvailable) {
457
- this.castContext = (window as any).cast.framework.CastContext.getInstance();
458
- this.castContext.setOptions({
459
- receiverApplicationId: (window as any).chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,
460
- autoJoinPolicy: (window as any).chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
461
- });
462
-
463
- console.log('[DRM] Chromecast initialized');
464
- }
465
- };
466
- }
467
-
468
- /**
469
- * Get current protection status
470
- */
471
- getStatus(): IDRMProtectionStatus {
472
- return { ...this.status };
473
- }
474
-
475
- /**
476
- * Enable/disable DRM protection
477
- */
478
- setEnabled(enabled: boolean): void {
479
- this.config.enabled = enabled;
480
- if (!enabled) {
481
- this.dispose();
482
- }
483
- }
484
-
485
- /**
486
- * Enable/disable specific feature
487
- */
488
- setFeature(feature: keyof IDRMProtectionConfig, enabled: boolean): void {
489
- (this.config as any)[feature] = enabled;
490
-
491
- // Re-apply feature if changing during playback
492
- switch (feature) {
493
- case 'blockScreenRecording':
494
- if (enabled) {
495
- this.startScreenRecordingDetection();
496
- } else if (this.screenRecordingCheckInterval) {
497
- clearInterval(this.screenRecordingCheckInterval);
498
- }
499
- break;
500
- case 'blockScreenshots':
501
- this.status.isScreenshotBlocked = enabled;
502
- break;
503
- }
504
- }
505
-
506
- /**
507
- * Start casting
508
- */
509
- async startCasting(deviceId: string): Promise<void> {
510
- if (!this.castContext) {
511
- throw new Error('Cast not initialized');
512
- }
513
-
514
- // Cast implementation
515
- console.log('[DRM] Starting cast to device:', deviceId);
516
- this.status.isCasting = true;
517
- }
518
-
519
- /**
520
- * Stop casting
521
- */
522
- async stopCasting(): Promise<void> {
523
- if (this.castSession) {
524
- this.castSession.endSession(true);
525
- this.castSession = null;
526
- }
527
- this.status.isCasting = false;
528
- }
529
-
530
- /**
531
- * Get available cast devices
532
- */
533
- async getAvailableCastDevices(): Promise<CastDevice[]> {
534
- // Return available Chromecast devices
535
- return [];
536
- }
537
-
538
- /**
539
- * Renew DRM license
540
- */
541
- async renewLicense(): Promise<void> {
542
- console.log('[DRM] Renewing license...');
543
- // Re-trigger license acquisition
544
- }
545
-
546
- /**
547
- * Show protection warning overlay
548
- */
549
- private showProtectionWarning(message: string): void {
550
- const overlay = document.createElement('div');
551
- overlay.style.cssText = `
552
- position: absolute;
553
- top: 0;
554
- left: 0;
555
- width: 100%;
556
- height: 100%;
557
- background: #000;
558
- color: #fff;
559
- display: flex;
560
- align-items: center;
561
- justify-content: center;
562
- font-size: 18px;
563
- z-index: 9999999;
564
- `;
565
- overlay.textContent = message;
566
- this.videoElement.parentElement?.appendChild(overlay);
567
- }
568
-
569
- /**
570
- * Create DRM error
571
- */
572
- private createError(code: DRMErrorCode, message: string): DRMError {
573
- return {
574
- code,
575
- message,
576
- platform: 'web',
577
- recoverable: false,
578
- };
579
- }
580
-
581
- /**
582
- * Cleanup resources
583
- */
584
- dispose(): void {
585
- if (this.screenRecordingCheckInterval) {
586
- clearInterval(this.screenRecordingCheckInterval);
587
- }
588
-
589
- if (this.castSession) {
590
- this.stopCasting();
591
- }
592
-
593
- this.status.isProtected = false;
594
- console.log('[DRM] Protection disposed');
595
- }
596
- }