unified-video-framework 1.4.387 → 1.4.389

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.
@@ -1,508 +0,0 @@
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 blackoutOverlay: HTMLElement | null = null;
18
- private blackoutTimeout: NodeJS.Timeout | null = null;
19
- private canvasRenderer: CanvasVideoRenderer | null = null;
20
- private detectionIntervals: NodeJS.Timeout[] = [];
21
- private eventListeners: Array<{ target: any; event: string; handler: any }> = [];
22
- private isActive: boolean = false;
23
- private devToolsOpen: boolean = false;
24
- private originalGetDisplayMedia: any = null;
25
-
26
- constructor(opts: ScreenProtectionOptions) {
27
- this.opts = opts;
28
- }
29
-
30
- public activate(): void {
31
- if (this.isActive) return;
32
-
33
- console.log('[ScreenProtectionController] Activating screen protection...');
34
-
35
- // Apply video element protection
36
- this.applyVideoProtection();
37
-
38
- // Initialize canvas rendering if enabled
39
- this.initializeCanvasRendering();
40
-
41
- // Create interference overlay (only if not using canvas)
42
- if (!this.canvasRenderer) {
43
- this.createInterferenceOverlay();
44
- }
45
-
46
- // Setup behavioral detection
47
- this.setupDetection();
48
-
49
- this.isActive = true;
50
- }
51
-
52
- public deactivate(): void {
53
- if (!this.isActive) return;
54
-
55
- console.log('[ScreenProtectionController] Deactivating screen protection...');
56
-
57
- // Deactivate canvas renderer
58
- if (this.canvasRenderer) {
59
- this.canvasRenderer.deactivate();
60
- this.canvasRenderer = null;
61
- }
62
-
63
- // Remove overlay
64
- if (this.overlayElement && this.overlayElement.parentElement) {
65
- this.overlayElement.parentElement.removeChild(this.overlayElement);
66
- this.overlayElement = null;
67
- }
68
-
69
- // Remove blackout overlay
70
- this.removeBlackoutOverlay();
71
-
72
- // Clear all intervals
73
- this.detectionIntervals.forEach(interval => clearInterval(interval));
74
- this.detectionIntervals = [];
75
-
76
- // Remove all event listeners
77
- this.eventListeners.forEach(({ target, event, handler }) => {
78
- target.removeEventListener(event, handler);
79
- });
80
- this.eventListeners = [];
81
-
82
- // Restore original getDisplayMedia
83
- if (this.originalGetDisplayMedia && navigator.mediaDevices) {
84
- navigator.mediaDevices.getDisplayMedia = this.originalGetDisplayMedia;
85
- this.originalGetDisplayMedia = null;
86
- }
87
-
88
- this.isActive = false;
89
- }
90
-
91
- public destroy(): void {
92
- this.deactivate();
93
- }
94
-
95
- private initializeCanvasRendering(): void {
96
- // Check if canvas rendering is enabled
97
- if (!this.opts.config.canvasRendering?.enabled) {
98
- return;
99
- }
100
-
101
- console.log('[ScreenProtectionController] Initializing canvas-based video rendering...');
102
- console.warn('[ScreenProtectionController] WARNING: Canvas rendering does NOT prevent screenshots.');
103
- console.warn('[ScreenProtectionController] This only adds obfuscation. Use DRM for true protection.');
104
-
105
- // Get watermark text
106
- const watermarkText = this.opts.config.forensicWatermark?.userId
107
- ? `PROTECTED - ${this.opts.config.forensicWatermark.userId}`
108
- : 'PROTECTED CONTENT';
109
-
110
- // Create canvas renderer
111
- this.canvasRenderer = new CanvasVideoRenderer({
112
- sourceVideo: this.opts.videoElement,
113
- containerElement: this.opts.containerElement,
114
- watermarkText: watermarkText,
115
- enableNoise: this.opts.config.canvasRendering.enableNoise ?? true,
116
- enableDynamicTransforms: this.opts.config.canvasRendering.enableDynamicTransforms ?? true
117
- });
118
-
119
- // Activate canvas rendering
120
- this.canvasRenderer.activate();
121
- }
122
-
123
- private applyVideoProtection(): void {
124
- const { videoElement, containerElement } = this.opts;
125
-
126
- try {
127
- // Prevent Picture-in-Picture
128
- videoElement.disablePictureInPicture = true;
129
- (videoElement as any).autoPictureInPicture = false;
130
- } catch (e) {
131
- console.warn('[ScreenProtection] Could not disable PiP:', e);
132
- }
133
-
134
- try {
135
- // Set controlsList to hide download button (if supported)
136
- if ('controlsList' in videoElement) {
137
- (videoElement as any).controlsList.add('nodownload');
138
- }
139
- } catch (e) {
140
- console.warn('[ScreenProtection] controlsList not supported:', e);
141
- }
142
-
143
- // Prevent text selection
144
- containerElement.style.userSelect = 'none';
145
- containerElement.style.webkitUserSelect = 'none';
146
- (containerElement.style as any).webkitTouchCallout = 'none';
147
-
148
- // Prevent right-click context menu
149
- const contextMenuHandler = (e: Event): boolean => {
150
- if (containerElement.contains(e.target as Node)) {
151
- e.preventDefault();
152
- e.stopPropagation();
153
- return false;
154
- }
155
- return true;
156
- };
157
- document.addEventListener('contextmenu', contextMenuHandler);
158
- this.eventListeners.push({ target: document, event: 'contextmenu', handler: contextMenuHandler });
159
-
160
- console.log('[ScreenProtection] Video protection applied');
161
- }
162
-
163
- private createInterferenceOverlay(): void {
164
- const { containerElement } = this.opts;
165
-
166
- // Create transparent overlay
167
- this.overlayElement = document.createElement('div');
168
- this.overlayElement.className = 'uvf-screen-protection-overlay';
169
- this.overlayElement.setAttribute('aria-hidden', 'true');
170
-
171
- // Style the overlay
172
- Object.assign(this.overlayElement.style, {
173
- position: 'absolute',
174
- top: '0',
175
- left: '0',
176
- width: '100%',
177
- height: '100%',
178
- pointerEvents: 'none', // Don't block user interactions
179
- zIndex: '3', // Between video (1) and watermark (5)
180
- mixBlendMode: 'screen',
181
- opacity: '0.01',
182
- background: 'transparent',
183
- // Add subtle noise pattern
184
- 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")',
185
- transition: 'opacity 0.3s ease-in-out'
186
- });
187
-
188
- // Insert into container
189
- const watermarkCanvas = containerElement.querySelector('.uvf-watermark-layer');
190
- if (watermarkCanvas) {
191
- containerElement.insertBefore(this.overlayElement, watermarkCanvas);
192
- } else {
193
- containerElement.appendChild(this.overlayElement);
194
- }
195
-
196
- // Start overlay animation
197
- this.animateOverlay();
198
-
199
- console.log('[ScreenProtection] Interference overlay created');
200
- }
201
-
202
- private animateOverlay(): void {
203
- if (!this.overlayElement || !this.isActive) return;
204
-
205
- // Subtle opacity pulsing
206
- const baseOpacity = 0.01;
207
- const variation = baseOpacity * 0.5;
208
- const newOpacity = baseOpacity + (Math.random() * variation - variation / 2);
209
-
210
- this.overlayElement.style.opacity = newOpacity.toString();
211
-
212
- // Continue animation
213
- setTimeout(() => this.animateOverlay(), 3000 + Math.random() * 2000);
214
- }
215
-
216
- private setupDetection(): void {
217
- // DevTools detection
218
- this.detectDevTools();
219
-
220
- // Focus loss detection
221
- this.detectFocusLoss();
222
-
223
- // Visibility change detection
224
- this.detectVisibilityChange();
225
-
226
- // Screen Capture API detection
227
- this.detectScreenCaptureAPI();
228
-
229
- console.log('[ScreenProtection] Detection mechanisms active');
230
- }
231
-
232
- private detectDevTools(): void {
233
- const threshold = 160;
234
- let previousState = false;
235
-
236
- const checkDevTools = () => {
237
- if (!this.isActive) return;
238
-
239
- const widthDiff = window.outerWidth - window.innerWidth;
240
- const heightDiff = window.outerHeight - window.innerHeight;
241
-
242
- const isOpen = widthDiff > threshold || heightDiff > threshold;
243
-
244
- if (isOpen && !previousState) {
245
- this.devToolsOpen = true;
246
- this.handleDetection({
247
- type: 'devtools',
248
- timestamp: Date.now(),
249
- details: { widthDiff, heightDiff }
250
- });
251
-
252
- if (this.opts.onDevToolsDetected) {
253
- this.opts.onDevToolsDetected();
254
- }
255
- } else if (!isOpen && previousState) {
256
- this.devToolsOpen = false;
257
- }
258
-
259
- previousState = isOpen;
260
- };
261
-
262
- const interval = setInterval(checkDevTools, 500);
263
- this.detectionIntervals.push(interval);
264
- }
265
-
266
- private detectFocusLoss(): void {
267
- const blurHandler = () => {
268
- this.handleDetection({
269
- type: 'focus-loss',
270
- timestamp: Date.now(),
271
- details: { documentHidden: document.hidden }
272
- });
273
- };
274
-
275
- window.addEventListener('blur', blurHandler);
276
- this.eventListeners.push({ target: window, event: 'blur', handler: blurHandler });
277
- }
278
-
279
- private detectVisibilityChange(): void {
280
- const visibilityHandler = () => {
281
- if (document.hidden) {
282
- this.handleDetection({
283
- type: 'visibility-change',
284
- timestamp: Date.now(),
285
- details: { visibilityState: document.visibilityState }
286
- });
287
- }
288
- };
289
-
290
- document.addEventListener('visibilitychange', visibilityHandler);
291
- this.eventListeners.push({ target: document, event: 'visibilitychange', handler: visibilityHandler });
292
- }
293
-
294
- private detectScreenCaptureAPI(): void {
295
- // Monkey-patch getDisplayMedia if it exists
296
- if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
297
- this.originalGetDisplayMedia = navigator.mediaDevices.getDisplayMedia.bind(
298
- navigator.mediaDevices
299
- );
300
-
301
- const self = this;
302
- navigator.mediaDevices.getDisplayMedia = async function (...args: any[]) {
303
- // Detected screen capture attempt
304
- self.handleDetection({
305
- type: 'screen-capture-api',
306
- timestamp: Date.now(),
307
- details: { api: 'getDisplayMedia', args: args.length }
308
- });
309
-
310
- // Still allow the call to proceed (can't block it)
311
- return self.originalGetDisplayMedia(...args);
312
- };
313
- }
314
- }
315
-
316
- private handleDetection(event: ScreenCaptureEvent): void {
317
- console.warn('[ScreenProtection] Detection event:', event);
318
-
319
- // Trigger callback
320
- if (this.opts.onDetection) {
321
- this.opts.onDetection(event);
322
- }
323
-
324
- // Send to backend tracking endpoint if configured
325
- if (this.opts.config.trackingEndpoint) {
326
- this.sendTrackingEvent(event);
327
- }
328
-
329
- // Execute configured action
330
- const action = this.opts.config.onDetection || 'warn';
331
-
332
- switch (action) {
333
- case 'warn':
334
- this.executeWarnAction();
335
- break;
336
- case 'pause':
337
- this.executePauseAction();
338
- break;
339
- case 'degrade':
340
- this.executeDegradeAction();
341
- break;
342
- case 'blackout':
343
- this.executeBlackoutAction();
344
- break;
345
- }
346
- }
347
-
348
- private async sendTrackingEvent(event: ScreenCaptureEvent): Promise<void> {
349
- try {
350
- await fetch(this.opts.config.trackingEndpoint!, {
351
- method: 'POST',
352
- headers: {
353
- 'Content-Type': 'application/json'
354
- },
355
- body: JSON.stringify({
356
- event,
357
- userAgent: navigator.userAgent,
358
- timestamp: new Date().toISOString(),
359
- forensicData: this.opts.config.forensicWatermark
360
- })
361
- });
362
- } catch (error) {
363
- console.error('[ScreenProtection] Failed to send tracking event:', error);
364
- }
365
- }
366
-
367
- private executeWarnAction(): void {
368
- const message = this.opts.config.warningMessage ||
369
- 'Screen recording or screenshot detected. This content is protected.';
370
-
371
- console.warn(`[ScreenProtection] ${message}`);
372
-
373
- // Could also show visual warning in player (future enhancement)
374
- }
375
-
376
- private executePauseAction(): void {
377
- console.warn('[ScreenProtection] Pausing playback due to suspicious activity');
378
-
379
- if (this.opts.onPause) {
380
- this.opts.onPause();
381
- } else {
382
- this.opts.videoElement.pause();
383
- }
384
- }
385
-
386
- private executeDegradeAction(): void {
387
- console.warn('[ScreenProtection] Degrading video quality due to suspicious activity');
388
-
389
- // Apply visual degradation
390
- this.opts.videoElement.style.filter = 'blur(10px) brightness(0.5)';
391
-
392
- // Restore after 5 seconds
393
- setTimeout(() => {
394
- if (this.opts.videoElement.style.filter) {
395
- this.opts.videoElement.style.filter = '';
396
- }
397
- }, 5000);
398
- }
399
-
400
- private executeBlackoutAction(): void {
401
- console.warn('[ScreenProtection] BLACKOUT activated - Suspicious recording activity detected');
402
-
403
- // Pause video immediately
404
- if (this.opts.onPause) {
405
- this.opts.onPause();
406
- } else {
407
- this.opts.videoElement.pause();
408
- }
409
-
410
- // Remove existing blackout if present
411
- if (this.blackoutOverlay && this.blackoutOverlay.parentElement) {
412
- this.blackoutOverlay.parentElement.removeChild(this.blackoutOverlay);
413
- this.blackoutOverlay = null;
414
- }
415
-
416
- // Clear existing timeout
417
- if (this.blackoutTimeout) {
418
- clearTimeout(this.blackoutTimeout);
419
- this.blackoutTimeout = null;
420
- }
421
-
422
- // Create full-screen black overlay
423
- this.blackoutOverlay = document.createElement('div');
424
- this.blackoutOverlay.className = 'uvf-blackout-overlay';
425
- this.blackoutOverlay.setAttribute('aria-live', 'assertive');
426
- this.blackoutOverlay.setAttribute('role', 'alert');
427
-
428
- // Style the blackout overlay
429
- Object.assign(this.blackoutOverlay.style, {
430
- position: 'absolute',
431
- top: '0',
432
- left: '0',
433
- width: '100%',
434
- height: '100%',
435
- backgroundColor: '#000000',
436
- zIndex: '9999',
437
- display: 'flex',
438
- flexDirection: 'column',
439
- alignItems: 'center',
440
- justifyContent: 'center',
441
- color: '#ffffff',
442
- fontFamily: 'Arial, sans-serif',
443
- textAlign: 'center',
444
- padding: '20px',
445
- boxSizing: 'border-box'
446
- });
447
-
448
- // Warning message
449
- const message = this.opts.config.warningMessage ||
450
- 'CONTENT PROTECTION ACTIVATED\n\nScreen recording detected.\nPlayback has been paused.';
451
-
452
- const messageElement = document.createElement('div');
453
- messageElement.style.cssText = `
454
- font-size: 24px;
455
- font-weight: bold;
456
- margin-bottom: 20px;
457
- white-space: pre-line;
458
- text-shadow: 0 0 10px rgba(255, 0, 0, 0.5);
459
- `;
460
- messageElement.textContent = message;
461
-
462
- // Warning icon
463
- const iconElement = document.createElement('div');
464
- iconElement.style.cssText = `
465
- font-size: 64px;
466
- margin-bottom: 20px;
467
- animation: pulse 2s infinite;
468
- `;
469
- iconElement.textContent = '⚠️';
470
-
471
- // Add pulsing animation
472
- const style = document.createElement('style');
473
- style.textContent = `
474
- @keyframes pulse {
475
- 0%, 100% { opacity: 1; transform: scale(1); }
476
- 50% { opacity: 0.7; transform: scale(1.1); }
477
- }
478
- `;
479
- document.head.appendChild(style);
480
-
481
- this.blackoutOverlay.appendChild(iconElement);
482
- this.blackoutOverlay.appendChild(messageElement);
483
-
484
- // Add to container
485
- this.opts.containerElement.appendChild(this.blackoutOverlay);
486
-
487
- // Auto-restore after configured duration
488
- const duration = this.opts.config.blackoutDuration || 10000; // Default 10 seconds
489
- this.blackoutTimeout = setTimeout(() => {
490
- this.removeBlackoutOverlay();
491
- }, duration);
492
-
493
- console.warn(`[ScreenProtection] Blackout will auto-restore in ${duration / 1000} seconds`);
494
- }
495
-
496
- private removeBlackoutOverlay(): void {
497
- if (this.blackoutOverlay && this.blackoutOverlay.parentElement) {
498
- this.blackoutOverlay.parentElement.removeChild(this.blackoutOverlay);
499
- this.blackoutOverlay = null;
500
- console.log('[ScreenProtection] Blackout overlay removed');
501
- }
502
-
503
- if (this.blackoutTimeout) {
504
- clearTimeout(this.blackoutTimeout);
505
- this.blackoutTimeout = null;
506
- }
507
- }
508
- }