unified-video-framework 1.4.387 → 1.4.388
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/package.json +1 -1
- package/packages/core/dist/interfaces.d.ts +1 -29
- package/packages/core/dist/interfaces.d.ts.map +1 -1
- package/packages/core/src/interfaces.ts +1 -56
- package/packages/web/dist/WebPlayer.d.ts +0 -2
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +2 -89
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/dist/index.d.ts +0 -6
- package/packages/web/dist/index.d.ts.map +1 -1
- package/packages/web/dist/index.js +0 -3
- package/packages/web/dist/index.js.map +1 -1
- package/packages/web/dist/react/WebPlayerView.d.ts +0 -22
- package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
- package/packages/web/dist/react/WebPlayerView.js +18 -0
- package/packages/web/dist/react/WebPlayerView.js.map +1 -1
- package/packages/web/src/WebPlayer.ts +2 -120
- package/packages/web/src/index.ts +0 -10
- package/packages/web/src/react/WebPlayerView.tsx +28 -25
- package/scripts/fix-imports.js +0 -44
- package/packages/web/src/drm/DRMHelper.ts +0 -203
- package/packages/web/src/security/CanvasVideoRenderer.ts +0 -246
- package/packages/web/src/security/ScreenProtectionController.ts +0 -508
|
@@ -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
|
-
}
|