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
@@ -0,0 +1,391 @@
1
+ import { ScreenCaptureEvent, EnhancedScreenProtectionConfig } from '@unified-video/core';
2
+ import { CanvasVideoRenderer } from './CanvasVideoRenderer';
3
+
4
+ export interface ScreenProtectionOptions {
5
+ videoElement: HTMLVideoElement;
6
+ containerElement: HTMLElement;
7
+ config: EnhancedScreenProtectionConfig;
8
+ watermarkConfig?: any;
9
+ onDetection?: (event: ScreenCaptureEvent) => void;
10
+ onDevToolsDetected?: () => void;
11
+ onPause?: () => void; // Callback to pause video
12
+ }
13
+
14
+ export class ScreenProtectionController {
15
+ private opts: ScreenProtectionOptions;
16
+ private overlayElement: HTMLElement | null = null;
17
+ private canvasRenderer: CanvasVideoRenderer | null = null;
18
+ private detectionIntervals: NodeJS.Timeout[] = [];
19
+ private eventListeners: Array<{ target: any; event: string; handler: any }> = [];
20
+ private isActive: boolean = false;
21
+ private devToolsOpen: boolean = false;
22
+ private originalGetDisplayMedia: any = null;
23
+
24
+ constructor(opts: ScreenProtectionOptions) {
25
+ this.opts = opts;
26
+ }
27
+
28
+ public activate(): void {
29
+ if (this.isActive) return;
30
+
31
+ console.log('[ScreenProtectionController] Activating screen protection...');
32
+
33
+ // Apply video element protection
34
+ this.applyVideoProtection();
35
+
36
+ // Initialize canvas rendering if enabled
37
+ this.initializeCanvasRendering();
38
+
39
+ // Create interference overlay (only if not using canvas)
40
+ if (!this.canvasRenderer) {
41
+ this.createInterferenceOverlay();
42
+ }
43
+
44
+ // Setup behavioral detection
45
+ this.setupDetection();
46
+
47
+ this.isActive = true;
48
+ }
49
+
50
+ public deactivate(): void {
51
+ if (!this.isActive) return;
52
+
53
+ console.log('[ScreenProtectionController] Deactivating screen protection...');
54
+
55
+ // Deactivate canvas renderer
56
+ if (this.canvasRenderer) {
57
+ this.canvasRenderer.deactivate();
58
+ this.canvasRenderer = null;
59
+ }
60
+
61
+ // Remove overlay
62
+ if (this.overlayElement && this.overlayElement.parentElement) {
63
+ this.overlayElement.parentElement.removeChild(this.overlayElement);
64
+ this.overlayElement = null;
65
+ }
66
+
67
+ // Clear all intervals
68
+ this.detectionIntervals.forEach(interval => clearInterval(interval));
69
+ this.detectionIntervals = [];
70
+
71
+ // Remove all event listeners
72
+ this.eventListeners.forEach(({ target, event, handler }) => {
73
+ target.removeEventListener(event, handler);
74
+ });
75
+ this.eventListeners = [];
76
+
77
+ // Restore original getDisplayMedia
78
+ if (this.originalGetDisplayMedia && navigator.mediaDevices) {
79
+ navigator.mediaDevices.getDisplayMedia = this.originalGetDisplayMedia;
80
+ this.originalGetDisplayMedia = null;
81
+ }
82
+
83
+ this.isActive = false;
84
+ }
85
+
86
+ public destroy(): void {
87
+ this.deactivate();
88
+ }
89
+
90
+ private initializeCanvasRendering(): void {
91
+ // Check if canvas rendering is enabled
92
+ if (!this.opts.config.canvasRendering?.enabled) {
93
+ return;
94
+ }
95
+
96
+ console.log('[ScreenProtectionController] Initializing canvas-based video rendering...');
97
+ console.warn('[ScreenProtectionController] WARNING: Canvas rendering does NOT prevent screenshots.');
98
+ console.warn('[ScreenProtectionController] This only adds obfuscation. Use DRM for true protection.');
99
+
100
+ // Get watermark text
101
+ const watermarkText = this.opts.config.forensicWatermark?.userId
102
+ ? `PROTECTED - ${this.opts.config.forensicWatermark.userId}`
103
+ : 'PROTECTED CONTENT';
104
+
105
+ // Create canvas renderer
106
+ this.canvasRenderer = new CanvasVideoRenderer({
107
+ sourceVideo: this.opts.videoElement,
108
+ containerElement: this.opts.containerElement,
109
+ watermarkText: watermarkText,
110
+ enableNoise: this.opts.config.canvasRendering.enableNoise ?? true,
111
+ enableDynamicTransforms: this.opts.config.canvasRendering.enableDynamicTransforms ?? true
112
+ });
113
+
114
+ // Activate canvas rendering
115
+ this.canvasRenderer.activate();
116
+ }
117
+
118
+ private applyVideoProtection(): void {
119
+ const { videoElement, containerElement } = this.opts;
120
+
121
+ try {
122
+ // Prevent Picture-in-Picture
123
+ videoElement.disablePictureInPicture = true;
124
+ (videoElement as any).autoPictureInPicture = false;
125
+ } catch (e) {
126
+ console.warn('[ScreenProtection] Could not disable PiP:', e);
127
+ }
128
+
129
+ try {
130
+ // Set controlsList to hide download button (if supported)
131
+ if ('controlsList' in videoElement) {
132
+ (videoElement as any).controlsList.add('nodownload');
133
+ }
134
+ } catch (e) {
135
+ console.warn('[ScreenProtection] controlsList not supported:', e);
136
+ }
137
+
138
+ // Prevent text selection
139
+ containerElement.style.userSelect = 'none';
140
+ containerElement.style.webkitUserSelect = 'none';
141
+ (containerElement.style as any).webkitTouchCallout = 'none';
142
+
143
+ // Prevent right-click context menu
144
+ const contextMenuHandler = (e: Event): boolean => {
145
+ if (containerElement.contains(e.target as Node)) {
146
+ e.preventDefault();
147
+ e.stopPropagation();
148
+ return false;
149
+ }
150
+ return true;
151
+ };
152
+ document.addEventListener('contextmenu', contextMenuHandler);
153
+ this.eventListeners.push({ target: document, event: 'contextmenu', handler: contextMenuHandler });
154
+
155
+ console.log('[ScreenProtection] Video protection applied');
156
+ }
157
+
158
+ private createInterferenceOverlay(): void {
159
+ const { containerElement } = this.opts;
160
+
161
+ // Create transparent overlay
162
+ this.overlayElement = document.createElement('div');
163
+ this.overlayElement.className = 'uvf-screen-protection-overlay';
164
+ this.overlayElement.setAttribute('aria-hidden', 'true');
165
+
166
+ // Style the overlay
167
+ Object.assign(this.overlayElement.style, {
168
+ position: 'absolute',
169
+ top: '0',
170
+ left: '0',
171
+ width: '100%',
172
+ height: '100%',
173
+ pointerEvents: 'none', // Don't block user interactions
174
+ zIndex: '3', // Between video (1) and watermark (5)
175
+ mixBlendMode: 'screen',
176
+ opacity: '0.01',
177
+ background: 'transparent',
178
+ // Add subtle noise pattern
179
+ backgroundImage: 'url("data:image/svg+xml,%3Csvg viewBox=\'0 0 256 256\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cfilter id=\'noise\'%3E%3CfeTurbulence type=\'fractalNoise\' baseFrequency=\'0.9\' numOctaves=\'4\' stitchTiles=\'stitch\'/%3E%3C/filter%3E%3Crect width=\'100%25\' height=\'100%25\' filter=\'url(%23noise)\' opacity=\'0.05\'/%3E%3C/svg%3E")',
180
+ transition: 'opacity 0.3s ease-in-out'
181
+ });
182
+
183
+ // Insert into container
184
+ const watermarkCanvas = containerElement.querySelector('.uvf-watermark-layer');
185
+ if (watermarkCanvas) {
186
+ containerElement.insertBefore(this.overlayElement, watermarkCanvas);
187
+ } else {
188
+ containerElement.appendChild(this.overlayElement);
189
+ }
190
+
191
+ // Start overlay animation
192
+ this.animateOverlay();
193
+
194
+ console.log('[ScreenProtection] Interference overlay created');
195
+ }
196
+
197
+ private animateOverlay(): void {
198
+ if (!this.overlayElement || !this.isActive) return;
199
+
200
+ // Subtle opacity pulsing
201
+ const baseOpacity = 0.01;
202
+ const variation = baseOpacity * 0.5;
203
+ const newOpacity = baseOpacity + (Math.random() * variation - variation / 2);
204
+
205
+ this.overlayElement.style.opacity = newOpacity.toString();
206
+
207
+ // Continue animation
208
+ setTimeout(() => this.animateOverlay(), 3000 + Math.random() * 2000);
209
+ }
210
+
211
+ private setupDetection(): void {
212
+ // DevTools detection
213
+ this.detectDevTools();
214
+
215
+ // Focus loss detection
216
+ this.detectFocusLoss();
217
+
218
+ // Visibility change detection
219
+ this.detectVisibilityChange();
220
+
221
+ // Screen Capture API detection
222
+ this.detectScreenCaptureAPI();
223
+
224
+ console.log('[ScreenProtection] Detection mechanisms active');
225
+ }
226
+
227
+ private detectDevTools(): void {
228
+ const threshold = 160;
229
+ let previousState = false;
230
+
231
+ const checkDevTools = () => {
232
+ if (!this.isActive) return;
233
+
234
+ const widthDiff = window.outerWidth - window.innerWidth;
235
+ const heightDiff = window.outerHeight - window.innerHeight;
236
+
237
+ const isOpen = widthDiff > threshold || heightDiff > threshold;
238
+
239
+ if (isOpen && !previousState) {
240
+ this.devToolsOpen = true;
241
+ this.handleDetection({
242
+ type: 'devtools',
243
+ timestamp: Date.now(),
244
+ details: { widthDiff, heightDiff }
245
+ });
246
+
247
+ if (this.opts.onDevToolsDetected) {
248
+ this.opts.onDevToolsDetected();
249
+ }
250
+ } else if (!isOpen && previousState) {
251
+ this.devToolsOpen = false;
252
+ }
253
+
254
+ previousState = isOpen;
255
+ };
256
+
257
+ const interval = setInterval(checkDevTools, 500);
258
+ this.detectionIntervals.push(interval);
259
+ }
260
+
261
+ private detectFocusLoss(): void {
262
+ const blurHandler = () => {
263
+ this.handleDetection({
264
+ type: 'focus-loss',
265
+ timestamp: Date.now(),
266
+ details: { documentHidden: document.hidden }
267
+ });
268
+ };
269
+
270
+ window.addEventListener('blur', blurHandler);
271
+ this.eventListeners.push({ target: window, event: 'blur', handler: blurHandler });
272
+ }
273
+
274
+ private detectVisibilityChange(): void {
275
+ const visibilityHandler = () => {
276
+ if (document.hidden) {
277
+ this.handleDetection({
278
+ type: 'visibility-change',
279
+ timestamp: Date.now(),
280
+ details: { visibilityState: document.visibilityState }
281
+ });
282
+ }
283
+ };
284
+
285
+ document.addEventListener('visibilitychange', visibilityHandler);
286
+ this.eventListeners.push({ target: document, event: 'visibilitychange', handler: visibilityHandler });
287
+ }
288
+
289
+ private detectScreenCaptureAPI(): void {
290
+ // Monkey-patch getDisplayMedia if it exists
291
+ if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
292
+ this.originalGetDisplayMedia = navigator.mediaDevices.getDisplayMedia.bind(
293
+ navigator.mediaDevices
294
+ );
295
+
296
+ const self = this;
297
+ navigator.mediaDevices.getDisplayMedia = async function (...args: any[]) {
298
+ // Detected screen capture attempt
299
+ self.handleDetection({
300
+ type: 'screen-capture-api',
301
+ timestamp: Date.now(),
302
+ details: { api: 'getDisplayMedia', args: args.length }
303
+ });
304
+
305
+ // Still allow the call to proceed (can't block it)
306
+ return self.originalGetDisplayMedia(...args);
307
+ };
308
+ }
309
+ }
310
+
311
+ private handleDetection(event: ScreenCaptureEvent): void {
312
+ console.warn('[ScreenProtection] Detection event:', event);
313
+
314
+ // Trigger callback
315
+ if (this.opts.onDetection) {
316
+ this.opts.onDetection(event);
317
+ }
318
+
319
+ // Send to backend tracking endpoint if configured
320
+ if (this.opts.config.trackingEndpoint) {
321
+ this.sendTrackingEvent(event);
322
+ }
323
+
324
+ // Execute configured action
325
+ const action = this.opts.config.onDetection || 'warn';
326
+
327
+ switch (action) {
328
+ case 'warn':
329
+ this.executeWarnAction();
330
+ break;
331
+ case 'pause':
332
+ this.executePauseAction();
333
+ break;
334
+ case 'degrade':
335
+ this.executeDegradeAction();
336
+ break;
337
+ }
338
+ }
339
+
340
+ private async sendTrackingEvent(event: ScreenCaptureEvent): Promise<void> {
341
+ try {
342
+ await fetch(this.opts.config.trackingEndpoint!, {
343
+ method: 'POST',
344
+ headers: {
345
+ 'Content-Type': 'application/json'
346
+ },
347
+ body: JSON.stringify({
348
+ event,
349
+ userAgent: navigator.userAgent,
350
+ timestamp: new Date().toISOString(),
351
+ forensicData: this.opts.config.forensicWatermark
352
+ })
353
+ });
354
+ } catch (error) {
355
+ console.error('[ScreenProtection] Failed to send tracking event:', error);
356
+ }
357
+ }
358
+
359
+ private executeWarnAction(): void {
360
+ const message = this.opts.config.warningMessage ||
361
+ 'Screen recording or screenshot detected. This content is protected.';
362
+
363
+ console.warn(`[ScreenProtection] ${message}`);
364
+
365
+ // Could also show visual warning in player (future enhancement)
366
+ }
367
+
368
+ private executePauseAction(): void {
369
+ console.warn('[ScreenProtection] Pausing playback due to suspicious activity');
370
+
371
+ if (this.opts.onPause) {
372
+ this.opts.onPause();
373
+ } else {
374
+ this.opts.videoElement.pause();
375
+ }
376
+ }
377
+
378
+ private executeDegradeAction(): void {
379
+ console.warn('[ScreenProtection] Degrading video quality due to suspicious activity');
380
+
381
+ // Apply visual degradation
382
+ this.opts.videoElement.style.filter = 'blur(10px) brightness(0.5)';
383
+
384
+ // Restore after 5 seconds
385
+ setTimeout(() => {
386
+ if (this.opts.videoElement.style.filter) {
387
+ this.opts.videoElement.style.filter = '';
388
+ }
389
+ }, 5000);
390
+ }
391
+ }
@@ -10,21 +10,22 @@ const path = require('path');
10
10
 
11
11
  function fixImports(filePath) {
12
12
  let content = fs.readFileSync(filePath, 'utf8');
13
-
14
- // Fix core imports - remove .js extension for webpack compatibility
13
+
14
+ // Replace @unified-video/core imports with relative paths
15
15
  if (filePath.includes(path.join('packages', 'web', 'dist'))) {
16
- // Remove .js extension from ALL core imports (webpack can't resolve them in node_modules)
16
+ // Fix CommonJS require statements
17
17
  content = content.replace(
18
- /from\s+["']\.\.\/\.\.\/core\/dist\/([^"']+)\.js["']/g,
19
- 'from "../../core/dist/$1"'
18
+ /require\(["']@unified-video\/core["']\)/g,
19
+ 'require("../../core/dist")'
20
20
  );
21
+ // Fix ES module import statements
21
22
  content = content.replace(
22
- /import\s+["']\.\.\/\.\.\/core\/dist\/([^"']+)\.js["']/g,
23
- 'import "../../core/dist/$1"'
23
+ /from\s+["']@unified-video\/core["']/g,
24
+ 'from "../../core/dist/index.js"'
24
25
  );
25
26
  content = content.replace(
26
- /export\s+\*\s+from\s+["']\.\.\/\.\.\/core\/dist\/([^"']+)\.js["']/g,
27
- 'export * from "../../core/dist/$1"'
27
+ /import\s+["']@unified-video\/core["']/g,
28
+ 'import "../../core/dist/index.js"'
28
29
  );
29
30
 
30
31
  // Fix relative imports within the same package to include .js extension
@@ -42,18 +43,19 @@ function fixImports(filePath) {
42
43
  }
43
44
  );
44
45
  } else if (filePath.includes(path.join('packages', 'react-native', 'dist'))) {
45
- // Remove .js extension from ALL core imports
46
+ // Fix CommonJS require statements
46
47
  content = content.replace(
47
- /from\s+["']\.\.\/\.\.\/core\/dist\/([^"']+)\.js["']/g,
48
- 'from "../../core/dist/$1"'
48
+ /require\(["']@unified-video\/core["']\)/g,
49
+ 'require("../../core/dist")'
49
50
  );
51
+ // Fix ES module import statements
50
52
  content = content.replace(
51
- /import\s+["']\.\.\/\.\.\/core\/dist\/([^"']+)\.js["']/g,
52
- 'import "../../core/dist/$1"'
53
+ /from\s+["']@unified-video\/core["']/g,
54
+ 'from "../../core/dist/index.js"'
53
55
  );
54
56
  content = content.replace(
55
- /export\s+\*\s+from\s+["']\.\.\/\.\.\/core\/dist\/([^"']+)\.js["']/g,
56
- 'export * from "../../core/dist/$1"'
57
+ /import\s+["']@unified-video\/core["']/g,
58
+ 'import "../../core/dist/index.js"'
57
59
  );
58
60
 
59
61
  // Fix relative imports within the same package to include .js extension