unified-video-framework 1.4.381 → 1.4.383

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 (32) hide show
  1. package/package.json +1 -1
  2. package/packages/core/dist/interfaces.d.ts +9 -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 +14 -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 +9709 -9644
  12. package/packages/web/dist/WebPlayer.js.map +1 -1
  13. package/packages/web/dist/index.d.ts +0 -1
  14. package/packages/web/dist/index.d.ts.map +1 -1
  15. package/packages/web/dist/index.js +6 -7
  16. package/packages/web/dist/index.js.map +1 -1
  17. package/packages/web/dist/react/WebPlayerView.d.ts +3 -0
  18. package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
  19. package/packages/web/dist/react/WebPlayerView.js.map +1 -1
  20. package/packages/web/dist/security/ScreenProtectionController.d.ts +31 -0
  21. package/packages/web/dist/security/ScreenProtectionController.d.ts.map +1 -0
  22. package/packages/web/dist/security/ScreenProtectionController.js +193 -0
  23. package/packages/web/dist/security/ScreenProtectionController.js.map +1 -0
  24. package/packages/web/src/WebPlayer.ts +87 -2
  25. package/packages/web/src/index.ts +0 -3
  26. package/packages/web/src/react/WebPlayerView.tsx +5 -0
  27. package/packages/web/src/security/ScreenProtectionController.ts +276 -0
  28. package/scripts/fix-imports.js +22 -12
  29. package/packages/core/src/interfaces/IDRMProtection.ts +0 -285
  30. package/packages/react-native/src/drm/AndroidDRMProtection.ts +0 -419
  31. package/packages/react-native/src/drm/iOSDRMProtection.ts +0 -415
  32. package/packages/web/src/drm/WebDRMProtection.ts +0 -596
@@ -22,6 +22,8 @@ import {
22
22
  } from './chapters/types/ChapterTypes';
23
23
  import type { FlashNewsTickerConfig } from './react/types/FlashNewsTickerTypes';
24
24
  import YouTubeExtractor from './utils/YouTubeExtractor';
25
+ import { ScreenProtectionController } from './security/ScreenProtectionController';
26
+ import type { ScreenCaptureEvent } from '../../core/dist';
25
27
 
26
28
  // Dynamic imports for streaming libraries
27
29
  declare global {
@@ -93,6 +95,9 @@ export class WebPlayer extends BasePlayer {
93
95
  // Paywall
94
96
  private paywallController: any = null;
95
97
 
98
+ // Screen protection
99
+ private screenProtectionController: ScreenProtectionController | null = null;
100
+
96
101
  // Play/pause coordination to prevent race conditions
97
102
  private _playPromise: Promise<void> | null = null;
98
103
  private _deferredPause = false;
@@ -353,6 +358,11 @@ export class WebPlayer extends BasePlayer {
353
358
  this.setupFullscreenListeners();
354
359
  this.setupUserInteractionTracking();
355
360
 
361
+ // Setup screen protection if enabled
362
+ if ((this.config as any).screenProtection === true) {
363
+ this.initializeScreenProtection();
364
+ }
365
+
356
366
  // Initialize chapter manager if enabled
357
367
  if (this.chapterConfig.enabled && this.video) {
358
368
  this.setupChapterManager();
@@ -8110,10 +8120,27 @@ export class WebPlayer extends BasePlayer {
8110
8120
  }
8111
8121
 
8112
8122
  // Render the watermark
8113
- ctx.fillText(text, x, y);
8123
+ // If screen protection is active, render multiple watermarks
8124
+ if ((this.config as any).screenProtection && this.screenProtectionController) {
8125
+ const positions = [
8126
+ { x: 20, y: 40 },
8127
+ { x: Math.max(20, this.watermarkCanvas!.width - 200), y: 40 },
8128
+ { x: 20, y: Math.max(40, this.watermarkCanvas!.height - 60) },
8129
+ { x: Math.max(20, this.watermarkCanvas!.width - 200), y: Math.max(40, this.watermarkCanvas!.height - 60) }
8130
+ ];
8131
+
8132
+ positions.forEach((pos) => {
8133
+ const offsetX = Math.random() * 100 - 50;
8134
+ const offsetY = Math.random() * 50 - 25;
8135
+ ctx.fillText(text, pos.x + offsetX, pos.y + offsetY);
8136
+ });
8137
+ } else {
8138
+ // Normal single watermark rendering
8139
+ ctx.fillText(text, x, y);
8140
+ }
8114
8141
  ctx.restore();
8115
8142
 
8116
- this.debugLog('Watermark rendered:', { text, x, y });
8143
+ this.debugLog('Watermark rendered:', { text, x, y, multiWatermark: !!((this.config as any).screenProtection && this.screenProtectionController) });
8117
8144
  };
8118
8145
 
8119
8146
  // Set up interval with configured frequency
@@ -8123,6 +8150,58 @@ export class WebPlayer extends BasePlayer {
8123
8150
  this.debugLog('Watermark setup complete with update interval:', config.updateInterval + 'ms');
8124
8151
  }
8125
8152
 
8153
+ private initializeScreenProtection(): void {
8154
+ if (!this.video || !this.container) return;
8155
+
8156
+ console.warn(
8157
+ '[Unified Video Framework] Screen Protection Enabled\n' +
8158
+ 'Note: Browsers cannot completely block screen recording. ' +
8159
+ 'This provides detection and deterrence through watermarking and monitoring.\n' +
8160
+ 'For DRM-level protection, use encrypted content with Widevine/FairPlay.'
8161
+ );
8162
+
8163
+ // Enhance watermark for protection
8164
+ if (!(this.config as any).watermark) {
8165
+ (this.config as any).watermark = {
8166
+ enabled: true,
8167
+ text: 'PROTECTED CONTENT',
8168
+ randomPosition: true,
8169
+ updateInterval: 1000 // Rapid updates
8170
+ };
8171
+ } else if ((this.config as any).watermark.enabled !== false) {
8172
+ // Force aggressive watermarking
8173
+ (this.config as any).watermark.randomPosition = true;
8174
+ (this.config as any).watermark.updateInterval = Math.min(
8175
+ (this.config as any).watermark.updateInterval || 5000,
8176
+ 1000
8177
+ );
8178
+ }
8179
+ this.setupWatermark(); // Re-setup with new config
8180
+
8181
+ // Create protection controller
8182
+ this.screenProtectionController = new ScreenProtectionController({
8183
+ videoElement: this.video,
8184
+ containerElement: this.container,
8185
+ watermarkConfig: (this.config as any).watermark,
8186
+ onDetection: (event: ScreenCaptureEvent) => {
8187
+ console.warn('[Screen Protection] Detection:', event);
8188
+ this.emit('onScreenCaptureDetected', event);
8189
+ if ((this.config as any).onScreenCaptureDetected) {
8190
+ (this.config as any).onScreenCaptureDetected(event);
8191
+ }
8192
+ },
8193
+ onDevToolsDetected: () => {
8194
+ console.warn('[Screen Protection] DevTools detected');
8195
+ this.emit('onDevToolsDetected');
8196
+ if ((this.config as any).onDevToolsDetected) {
8197
+ (this.config as any).onDevToolsDetected();
8198
+ }
8199
+ }
8200
+ });
8201
+
8202
+ this.screenProtectionController.activate();
8203
+ }
8204
+
8126
8205
  public setPaywallConfig(config: any) {
8127
8206
  try {
8128
8207
  if (!config) return;
@@ -11278,6 +11357,12 @@ export class WebPlayer extends BasePlayer {
11278
11357
  this.paywallController = null;
11279
11358
  }
11280
11359
 
11360
+ // Destroy screen protection controller
11361
+ if (this.screenProtectionController) {
11362
+ this.screenProtectionController.destroy();
11363
+ this.screenProtectionController = null;
11364
+ }
11365
+
11281
11366
  // Destroy chapter managers
11282
11367
  if (this.chapterManager && typeof this.chapterManager.destroy === 'function') {
11283
11368
  this.chapterManager.destroy();
@@ -11,9 +11,6 @@ export { WebPlayer } from './WebPlayer';
11
11
  export { WebPlayerView } from './react/WebPlayerView';
12
12
  export { SecureVideoPlayer } from './SecureVideoPlayer';
13
13
 
14
- // Export DRM Protection
15
- export { WebDRMProtection } from './drm/WebDRMProtection';
16
-
17
14
  // Export EPG (Electronic Program Guide) components
18
15
  export * from './react/EPG';
19
16
 
@@ -432,6 +432,11 @@ export type WebPlayerViewProps = {
432
432
 
433
433
  // Flash News Ticker
434
434
  flashNewsTicker?: FlashNewsTickerConfig; // Flash news ticker configuration
435
+
436
+ // Screen Protection
437
+ screenProtection?: boolean; // Enable screen recording/screenshot prevention (default: false)
438
+ onScreenCaptureDetected?: (event: any) => void; // Called when potential screen capture detected
439
+ onDevToolsDetected?: () => void; // Called when DevTools opened
435
440
  };
436
441
 
437
442
  export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
@@ -0,0 +1,276 @@
1
+ import { ScreenCaptureEvent } from '@unified-video/core';
2
+
3
+ export interface ScreenProtectionOptions {
4
+ videoElement: HTMLVideoElement;
5
+ containerElement: HTMLElement;
6
+ watermarkConfig?: any;
7
+ onDetection?: (event: ScreenCaptureEvent) => void;
8
+ onDevToolsDetected?: () => void;
9
+ }
10
+
11
+ export class ScreenProtectionController {
12
+ private opts: ScreenProtectionOptions;
13
+ private overlayElement: HTMLElement | null = null;
14
+ private detectionIntervals: NodeJS.Timeout[] = [];
15
+ private eventListeners: Array<{ target: any; event: string; handler: any }> = [];
16
+ private isActive: boolean = false;
17
+ private devToolsOpen: boolean = false;
18
+ private originalGetDisplayMedia: any = null;
19
+
20
+ constructor(opts: ScreenProtectionOptions) {
21
+ this.opts = opts;
22
+ }
23
+
24
+ public activate(): void {
25
+ if (this.isActive) return;
26
+
27
+ console.log('[ScreenProtectionController] Activating screen protection...');
28
+
29
+ // Apply video element protection
30
+ this.applyVideoProtection();
31
+
32
+ // Create interference overlay
33
+ this.createInterferenceOverlay();
34
+
35
+ // Setup behavioral detection
36
+ this.setupDetection();
37
+
38
+ this.isActive = true;
39
+ }
40
+
41
+ public deactivate(): void {
42
+ if (!this.isActive) return;
43
+
44
+ console.log('[ScreenProtectionController] Deactivating screen protection...');
45
+
46
+ // Remove overlay
47
+ if (this.overlayElement && this.overlayElement.parentElement) {
48
+ this.overlayElement.parentElement.removeChild(this.overlayElement);
49
+ this.overlayElement = null;
50
+ }
51
+
52
+ // Clear all intervals
53
+ this.detectionIntervals.forEach(interval => clearInterval(interval));
54
+ this.detectionIntervals = [];
55
+
56
+ // Remove all event listeners
57
+ this.eventListeners.forEach(({ target, event, handler }) => {
58
+ target.removeEventListener(event, handler);
59
+ });
60
+ this.eventListeners = [];
61
+
62
+ // Restore original getDisplayMedia
63
+ if (this.originalGetDisplayMedia && navigator.mediaDevices) {
64
+ navigator.mediaDevices.getDisplayMedia = this.originalGetDisplayMedia;
65
+ this.originalGetDisplayMedia = null;
66
+ }
67
+
68
+ this.isActive = false;
69
+ }
70
+
71
+ public destroy(): void {
72
+ this.deactivate();
73
+ }
74
+
75
+ private applyVideoProtection(): void {
76
+ const { videoElement, containerElement } = this.opts;
77
+
78
+ try {
79
+ // Prevent Picture-in-Picture
80
+ videoElement.disablePictureInPicture = true;
81
+ (videoElement as any).autoPictureInPicture = false;
82
+ } catch (e) {
83
+ console.warn('[ScreenProtection] Could not disable PiP:', e);
84
+ }
85
+
86
+ try {
87
+ // Set controlsList to hide download button (if supported)
88
+ if ('controlsList' in videoElement) {
89
+ (videoElement as any).controlsList.add('nodownload');
90
+ }
91
+ } catch (e) {
92
+ console.warn('[ScreenProtection] controlsList not supported:', e);
93
+ }
94
+
95
+ // Prevent text selection
96
+ containerElement.style.userSelect = 'none';
97
+ containerElement.style.webkitUserSelect = 'none';
98
+ (containerElement.style as any).webkitTouchCallout = 'none';
99
+
100
+ // Prevent right-click context menu
101
+ const contextMenuHandler = (e: Event): boolean => {
102
+ if (containerElement.contains(e.target as Node)) {
103
+ e.preventDefault();
104
+ e.stopPropagation();
105
+ return false;
106
+ }
107
+ return true;
108
+ };
109
+ document.addEventListener('contextmenu', contextMenuHandler);
110
+ this.eventListeners.push({ target: document, event: 'contextmenu', handler: contextMenuHandler });
111
+
112
+ console.log('[ScreenProtection] Video protection applied');
113
+ }
114
+
115
+ private createInterferenceOverlay(): void {
116
+ const { containerElement } = this.opts;
117
+
118
+ // Create transparent overlay
119
+ this.overlayElement = document.createElement('div');
120
+ this.overlayElement.className = 'uvf-screen-protection-overlay';
121
+ this.overlayElement.setAttribute('aria-hidden', 'true');
122
+
123
+ // Style the overlay
124
+ Object.assign(this.overlayElement.style, {
125
+ position: 'absolute',
126
+ top: '0',
127
+ left: '0',
128
+ width: '100%',
129
+ height: '100%',
130
+ pointerEvents: 'none', // Don't block user interactions
131
+ zIndex: '3', // Between video (1) and watermark (5)
132
+ mixBlendMode: 'screen',
133
+ opacity: '0.01',
134
+ background: 'transparent',
135
+ // Add subtle noise pattern
136
+ 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")',
137
+ transition: 'opacity 0.3s ease-in-out'
138
+ });
139
+
140
+ // Insert into container
141
+ const watermarkCanvas = containerElement.querySelector('.uvf-watermark-layer');
142
+ if (watermarkCanvas) {
143
+ containerElement.insertBefore(this.overlayElement, watermarkCanvas);
144
+ } else {
145
+ containerElement.appendChild(this.overlayElement);
146
+ }
147
+
148
+ // Start overlay animation
149
+ this.animateOverlay();
150
+
151
+ console.log('[ScreenProtection] Interference overlay created');
152
+ }
153
+
154
+ private animateOverlay(): void {
155
+ if (!this.overlayElement || !this.isActive) return;
156
+
157
+ // Subtle opacity pulsing
158
+ const baseOpacity = 0.01;
159
+ const variation = baseOpacity * 0.5;
160
+ const newOpacity = baseOpacity + (Math.random() * variation - variation / 2);
161
+
162
+ this.overlayElement.style.opacity = newOpacity.toString();
163
+
164
+ // Continue animation
165
+ setTimeout(() => this.animateOverlay(), 3000 + Math.random() * 2000);
166
+ }
167
+
168
+ private setupDetection(): void {
169
+ // DevTools detection
170
+ this.detectDevTools();
171
+
172
+ // Focus loss detection
173
+ this.detectFocusLoss();
174
+
175
+ // Visibility change detection
176
+ this.detectVisibilityChange();
177
+
178
+ // Screen Capture API detection
179
+ this.detectScreenCaptureAPI();
180
+
181
+ console.log('[ScreenProtection] Detection mechanisms active');
182
+ }
183
+
184
+ private detectDevTools(): void {
185
+ const threshold = 160;
186
+ let previousState = false;
187
+
188
+ const checkDevTools = () => {
189
+ if (!this.isActive) return;
190
+
191
+ const widthDiff = window.outerWidth - window.innerWidth;
192
+ const heightDiff = window.outerHeight - window.innerHeight;
193
+
194
+ const isOpen = widthDiff > threshold || heightDiff > threshold;
195
+
196
+ if (isOpen && !previousState) {
197
+ this.devToolsOpen = true;
198
+ this.handleDetection({
199
+ type: 'devtools',
200
+ timestamp: Date.now(),
201
+ details: { widthDiff, heightDiff }
202
+ });
203
+
204
+ if (this.opts.onDevToolsDetected) {
205
+ this.opts.onDevToolsDetected();
206
+ }
207
+ } else if (!isOpen && previousState) {
208
+ this.devToolsOpen = false;
209
+ }
210
+
211
+ previousState = isOpen;
212
+ };
213
+
214
+ const interval = setInterval(checkDevTools, 500);
215
+ this.detectionIntervals.push(interval);
216
+ }
217
+
218
+ private detectFocusLoss(): void {
219
+ const blurHandler = () => {
220
+ this.handleDetection({
221
+ type: 'focus-loss',
222
+ timestamp: Date.now(),
223
+ details: { documentHidden: document.hidden }
224
+ });
225
+ };
226
+
227
+ window.addEventListener('blur', blurHandler);
228
+ this.eventListeners.push({ target: window, event: 'blur', handler: blurHandler });
229
+ }
230
+
231
+ private detectVisibilityChange(): void {
232
+ const visibilityHandler = () => {
233
+ if (document.hidden) {
234
+ this.handleDetection({
235
+ type: 'visibility-change',
236
+ timestamp: Date.now(),
237
+ details: { visibilityState: document.visibilityState }
238
+ });
239
+ }
240
+ };
241
+
242
+ document.addEventListener('visibilitychange', visibilityHandler);
243
+ this.eventListeners.push({ target: document, event: 'visibilitychange', handler: visibilityHandler });
244
+ }
245
+
246
+ private detectScreenCaptureAPI(): void {
247
+ // Monkey-patch getDisplayMedia if it exists
248
+ if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
249
+ this.originalGetDisplayMedia = navigator.mediaDevices.getDisplayMedia.bind(
250
+ navigator.mediaDevices
251
+ );
252
+
253
+ const self = this;
254
+ navigator.mediaDevices.getDisplayMedia = async function (...args: any[]) {
255
+ // Detected screen capture attempt
256
+ self.handleDetection({
257
+ type: 'screen-capture-api',
258
+ timestamp: Date.now(),
259
+ details: { api: 'getDisplayMedia', args: args.length }
260
+ });
261
+
262
+ // Still allow the call to proceed (can't block it)
263
+ return self.originalGetDisplayMedia(...args);
264
+ };
265
+ }
266
+ }
267
+
268
+ private handleDetection(event: ScreenCaptureEvent): void {
269
+ console.warn('[ScreenProtection] Detection event:', event);
270
+
271
+ // Trigger callback
272
+ if (this.opts.onDetection) {
273
+ this.opts.onDetection(event);
274
+ }
275
+ }
276
+ }
@@ -10,17 +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 core imports (webpack can't resolve them in node_modules)
16
+ // Fix CommonJS require statements
17
+ content = content.replace(
18
+ /require\(["']@unified-video\/core["']\)/g,
19
+ 'require("../../core/dist")'
20
+ );
21
+ // Fix ES module import statements
17
22
  content = content.replace(
18
- /from\s+["']\.\.\/\.\.\/core\/dist\/index\.js["']/g,
19
- 'from "../../core/dist/index"'
23
+ /from\s+["']@unified-video\/core["']/g,
24
+ 'from "../../core/dist/index.js"'
20
25
  );
21
26
  content = content.replace(
22
- /import\s+["']\.\.\/\.\.\/core\/dist\/index\.js["']/g,
23
- 'import "../../core/dist/index"'
27
+ /import\s+["']@unified-video\/core["']/g,
28
+ 'import "../../core/dist/index.js"'
24
29
  );
25
30
 
26
31
  // Fix relative imports within the same package to include .js extension
@@ -38,14 +43,19 @@ function fixImports(filePath) {
38
43
  }
39
44
  );
40
45
  } else if (filePath.includes(path.join('packages', 'react-native', 'dist'))) {
41
- // Remove .js extension from core imports
46
+ // Fix CommonJS require statements
47
+ content = content.replace(
48
+ /require\(["']@unified-video\/core["']\)/g,
49
+ 'require("../../core/dist")'
50
+ );
51
+ // Fix ES module import statements
42
52
  content = content.replace(
43
- /from\s+["']\.\.\/\.\.\/core\/dist\/index\.js["']/g,
44
- 'from "../../core/dist/index"'
53
+ /from\s+["']@unified-video\/core["']/g,
54
+ 'from "../../core/dist/index.js"'
45
55
  );
46
56
  content = content.replace(
47
- /import\s+["']\.\.\/\.\.\/core\/dist\/index\.js["']/g,
48
- 'import "../../core/dist/index"'
57
+ /import\s+["']@unified-video\/core["']/g,
58
+ 'import "../../core/dist/index.js"'
49
59
  );
50
60
 
51
61
  // Fix relative imports within the same package to include .js extension