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,203 @@
1
+ /**
2
+ * DRM Helper Utility
3
+ *
4
+ * Simplifies DRM configuration for users who need maximum security (true black screen protection).
5
+ * This is Option B - for advanced users who can encrypt their videos and set up license servers.
6
+ */
7
+
8
+ import { DRMConfig, DRMType } from '../../../core/dist';
9
+
10
+ export interface DRMHelperOptions {
11
+ licenseServerUrl: string;
12
+ certificateUrl?: string; // Required for FairPlay (Safari)
13
+ headers?: Record<string, string>;
14
+ preferredDRM?: 'widevine' | 'playready' | 'fairplay' | 'auto';
15
+ }
16
+
17
+ export class DRMHelper {
18
+ /**
19
+ * Detect the best DRM system for the current browser
20
+ */
21
+ static detectBestDRM(): DRMType {
22
+ const ua = navigator.userAgent.toLowerCase();
23
+
24
+ // Safari - use FairPlay
25
+ if (ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1) {
26
+ return DRMType.FAIRPLAY;
27
+ }
28
+
29
+ // Edge - prefer PlayReady, fallback to Widevine
30
+ if (ua.indexOf('edg') !== -1) {
31
+ return DRMType.PLAYREADY;
32
+ }
33
+
34
+ // Chrome, Firefox, Opera - use Widevine
35
+ return DRMType.WIDEVINE;
36
+ }
37
+
38
+ /**
39
+ * Create a simple DRM configuration
40
+ *
41
+ * @example
42
+ * const drmConfig = DRMHelper.createConfig({
43
+ * licenseServerUrl: 'https://your-license-server.com/license',
44
+ * certificateUrl: 'https://your-license-server.com/cert', // FairPlay only
45
+ * headers: {
46
+ * 'Authorization': 'Bearer your-token'
47
+ * }
48
+ * });
49
+ */
50
+ static createConfig(options: DRMHelperOptions): DRMConfig {
51
+ const preferredDRMMap: Record<string, DRMType> = {
52
+ 'widevine': DRMType.WIDEVINE,
53
+ 'playready': DRMType.PLAYREADY,
54
+ 'fairplay': DRMType.FAIRPLAY
55
+ };
56
+
57
+ const drmType = options.preferredDRM === 'auto' || !options.preferredDRM
58
+ ? this.detectBestDRM()
59
+ : preferredDRMMap[options.preferredDRM];
60
+
61
+ return {
62
+ type: drmType,
63
+ licenseUrl: options.licenseServerUrl,
64
+ certificateUrl: options.certificateUrl,
65
+ headers: options.headers || {},
66
+ // Widevine security level (L1 = hardware-backed, most secure)
67
+ ...(drmType === DRMType.WIDEVINE && { securityLevel: 'L1' as any })
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Check if browser supports DRM
73
+ */
74
+ static async checkDRMSupport(): Promise<{
75
+ widevine: boolean;
76
+ playready: boolean;
77
+ fairplay: boolean;
78
+ recommended: DRMType;
79
+ }> {
80
+ const keySystemMap = {
81
+ widevine: 'com.widevine.alpha',
82
+ playready: 'com.microsoft.playready',
83
+ fairplay: 'com.apple.fps'
84
+ };
85
+
86
+ const support = {
87
+ widevine: false,
88
+ playready: false,
89
+ fairplay: false,
90
+ recommended: 'widevine' as DRMType
91
+ };
92
+
93
+ if (!navigator.requestMediaKeySystemAccess) {
94
+ return support;
95
+ }
96
+
97
+ // Check Widevine
98
+ try {
99
+ await navigator.requestMediaKeySystemAccess(keySystemMap.widevine, [{
100
+ initDataTypes: ['cenc'],
101
+ videoCapabilities: [{ contentType: 'video/mp4; codecs="avc1.42E01E"' }]
102
+ }]);
103
+ support.widevine = true;
104
+ } catch (e) {}
105
+
106
+ // Check PlayReady
107
+ try {
108
+ await navigator.requestMediaKeySystemAccess(keySystemMap.playready, [{
109
+ initDataTypes: ['cenc'],
110
+ videoCapabilities: [{ contentType: 'video/mp4; codecs="avc1.42E01E"' }]
111
+ }]);
112
+ support.playready = true;
113
+ } catch (e) {}
114
+
115
+ // Check FairPlay
116
+ try {
117
+ await navigator.requestMediaKeySystemAccess(keySystemMap.fairplay, [{
118
+ initDataTypes: ['skd'],
119
+ videoCapabilities: [{ contentType: 'video/mp4; codecs="avc1.42E01E"' }]
120
+ }]);
121
+ support.fairplay = true;
122
+ } catch (e) {}
123
+
124
+ // Set recommended based on what's supported
125
+ support.recommended = this.detectBestDRM();
126
+
127
+ return support;
128
+ }
129
+
130
+ /**
131
+ * Validate DRM configuration
132
+ */
133
+ static validateConfig(config: DRMConfig): { valid: boolean; errors: string[] } {
134
+ const errors: string[] = [];
135
+
136
+ if (!config.type) {
137
+ errors.push('DRM type is required');
138
+ }
139
+
140
+ if (!config.licenseUrl) {
141
+ errors.push('License server URL is required');
142
+ }
143
+
144
+ if (config.type === 'fairplay' && !config.certificateUrl) {
145
+ errors.push('Certificate URL is required for FairPlay DRM');
146
+ }
147
+
148
+ try {
149
+ new URL(config.licenseUrl || '');
150
+ } catch (e) {
151
+ errors.push('License URL must be a valid URL');
152
+ }
153
+
154
+ return {
155
+ valid: errors.length === 0,
156
+ errors
157
+ };
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Quick start examples for common DRM providers
163
+ */
164
+ export const DRMProviderExamples = {
165
+ /**
166
+ * BuyDRM KeyOS
167
+ * https://www.buydrm.com/
168
+ */
169
+ BuyDRM: (customerId: string, videoId: string): DRMHelperOptions => ({
170
+ licenseServerUrl: `https://wv.service.expressplay.com/hms/wv/rights/?ExpressPlayToken=${customerId}`,
171
+ preferredDRM: 'auto'
172
+ }),
173
+
174
+ /**
175
+ * Irdeto
176
+ * https://irdeto.com/
177
+ */
178
+ Irdeto: (licenseUrl: string): DRMHelperOptions => ({
179
+ licenseServerUrl: licenseUrl,
180
+ preferredDRM: 'auto'
181
+ }),
182
+
183
+ /**
184
+ * Verimatrix
185
+ * https://www.verimatrix.com/
186
+ */
187
+ Verimatrix: (licenseUrl: string, token: string): DRMHelperOptions => ({
188
+ licenseServerUrl: licenseUrl,
189
+ headers: {
190
+ 'Authorization': `Bearer ${token}`
191
+ },
192
+ preferredDRM: 'auto'
193
+ }),
194
+
195
+ /**
196
+ * Custom DRM server
197
+ */
198
+ Custom: (licenseUrl: string, certificateUrl?: string): DRMHelperOptions => ({
199
+ licenseServerUrl: licenseUrl,
200
+ certificateUrl: certificateUrl,
201
+ preferredDRM: 'auto'
202
+ })
203
+ };
@@ -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,30 @@ export type WebPlayerViewProps = {
432
432
 
433
433
  // Flash News Ticker
434
434
  flashNewsTicker?: FlashNewsTickerConfig; // Flash news ticker configuration
435
+
436
+ // Screen Protection (Simple: boolean | Advanced: config object)
437
+ screenProtection?: boolean | {
438
+ enabled: boolean;
439
+ forensicWatermark?: {
440
+ userId?: string;
441
+ sessionId?: string;
442
+ invisible?: boolean;
443
+ };
444
+ canvasRendering?: {
445
+ enabled: boolean;
446
+ enableNoise?: boolean;
447
+ enableDynamicTransforms?: boolean;
448
+ };
449
+ aggressiveMode?: boolean;
450
+ onDetection?: 'warn' | 'pause' | 'degrade';
451
+ warningMessage?: string;
452
+ trackingEndpoint?: string;
453
+ onScreenCaptureDetected?: (event: any) => void;
454
+ onDevToolsDetected?: () => void;
455
+ };
456
+ // Legacy callbacks (deprecated)
457
+ onScreenCaptureDetected?: (event: any) => void;
458
+ onDevToolsDetected?: () => void;
435
459
  };
436
460
 
437
461
  export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Canvas Video Renderer
3
+ *
4
+ * IMPORTANT LIMITATION:
5
+ * This canvas-based approach does NOT prevent screenshots/recordings from showing content.
6
+ * - Screenshots capture the canvas output (not black screen)
7
+ * - Screen recorders capture the rendered canvas frames
8
+ * - Only DRM provides true black screen protection
9
+ *
10
+ * What this DOES provide:
11
+ * - Obfuscation layer (makes automated piracy tools slightly harder)
12
+ * - Dynamic noise/watermarking that's harder to remove
13
+ * - Video element is hidden (minor deterrent only)
14
+ *
15
+ * Use this only as an ADDITIONAL layer on top of other protections, not as primary protection.
16
+ */
17
+
18
+ export interface CanvasRendererOptions {
19
+ sourceVideo: HTMLVideoElement;
20
+ containerElement: HTMLElement;
21
+ watermarkText?: string;
22
+ enableNoise?: boolean;
23
+ enableDynamicTransforms?: boolean;
24
+ }
25
+
26
+ export class CanvasVideoRenderer {
27
+ private opts: CanvasRendererOptions;
28
+ private canvas: HTMLCanvasElement;
29
+ private ctx: CanvasRenderingContext2D;
30
+ private animationFrameId: number | null = null;
31
+ private isActive: boolean = false;
32
+ private noiseOffset: number = 0;
33
+
34
+ constructor(opts: CanvasRendererOptions) {
35
+ this.opts = opts;
36
+
37
+ // Create canvas element
38
+ this.canvas = document.createElement('canvas');
39
+ this.canvas.className = 'uvf-canvas-video-renderer';
40
+ this.canvas.style.cssText = `
41
+ position: absolute;
42
+ top: 0;
43
+ left: 0;
44
+ width: 100%;
45
+ height: 100%;
46
+ object-fit: contain;
47
+ z-index: 2;
48
+ `;
49
+
50
+ const context = this.canvas.getContext('2d', {
51
+ alpha: false,
52
+ desynchronized: true, // Hint for better performance
53
+ willReadFrequently: false
54
+ });
55
+
56
+ if (!context) {
57
+ throw new Error('[CanvasRenderer] Failed to get 2D context');
58
+ }
59
+
60
+ this.ctx = context;
61
+ }
62
+
63
+ public activate(): void {
64
+ if (this.isActive) return;
65
+
66
+ console.log('[CanvasRenderer] Activating canvas-based video rendering...');
67
+ console.warn('[CanvasRenderer] WARNING: Canvas rendering does NOT prevent screenshots showing content.');
68
+ console.warn('[CanvasRenderer] This only adds obfuscation. Use DRM for true protection.');
69
+
70
+ // Hide the original video element
71
+ this.opts.sourceVideo.style.opacity = '0';
72
+ this.opts.sourceVideo.style.pointerEvents = 'none';
73
+
74
+ // Add canvas to container
75
+ this.opts.containerElement.appendChild(this.canvas);
76
+
77
+ // Set canvas dimensions to match video
78
+ this.updateCanvasSize();
79
+
80
+ // Start rendering loop
81
+ this.isActive = true;
82
+ this.renderFrame();
83
+
84
+ // Update canvas size on video resize
85
+ this.opts.sourceVideo.addEventListener('loadedmetadata', () => {
86
+ this.updateCanvasSize();
87
+ });
88
+
89
+ console.log('[CanvasRenderer] Canvas rendering active');
90
+ }
91
+
92
+ public deactivate(): void {
93
+ if (!this.isActive) return;
94
+
95
+ console.log('[CanvasRenderer] Deactivating canvas rendering...');
96
+
97
+ this.isActive = false;
98
+
99
+ // Stop animation loop
100
+ if (this.animationFrameId !== null) {
101
+ cancelAnimationFrame(this.animationFrameId);
102
+ this.animationFrameId = null;
103
+ }
104
+
105
+ // Show original video
106
+ this.opts.sourceVideo.style.opacity = '1';
107
+ this.opts.sourceVideo.style.pointerEvents = 'auto';
108
+
109
+ // Remove canvas
110
+ if (this.canvas.parentElement) {
111
+ this.canvas.parentElement.removeChild(this.canvas);
112
+ }
113
+ }
114
+
115
+ public destroy(): void {
116
+ this.deactivate();
117
+ }
118
+
119
+ private updateCanvasSize(): void {
120
+ const video = this.opts.sourceVideo;
121
+
122
+ // Set internal canvas resolution to match video
123
+ this.canvas.width = video.videoWidth || 1920;
124
+ this.canvas.height = video.videoHeight || 1080;
125
+
126
+ console.log(`[CanvasRenderer] Canvas size: ${this.canvas.width}x${this.canvas.height}`);
127
+ }
128
+
129
+ private renderFrame = (): void => {
130
+ if (!this.isActive) return;
131
+
132
+ const video = this.opts.sourceVideo;
133
+
134
+ // Only render if video has content and is playing
135
+ if (video.readyState >= video.HAVE_CURRENT_DATA) {
136
+ // Clear canvas
137
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
138
+
139
+ // Apply dynamic transforms (makes automated extraction slightly harder)
140
+ if (this.opts.enableDynamicTransforms) {
141
+ this.ctx.save();
142
+
143
+ // Subtle random positioning (imperceptible to humans, confuses some bots)
144
+ const jitterX = Math.sin(Date.now() / 1000) * 0.5;
145
+ const jitterY = Math.cos(Date.now() / 1000) * 0.5;
146
+ this.ctx.translate(jitterX, jitterY);
147
+ }
148
+
149
+ // Draw video frame to canvas
150
+ this.ctx.drawImage(video, 0, 0, this.canvas.width, this.canvas.height);
151
+
152
+ // Apply noise layer (makes pixel-perfect extraction harder)
153
+ if (this.opts.enableNoise) {
154
+ this.applyNoiseLayer();
155
+ }
156
+
157
+ // Draw watermark overlay
158
+ if (this.opts.watermarkText) {
159
+ this.drawDynamicWatermark();
160
+ }
161
+
162
+ if (this.opts.enableDynamicTransforms) {
163
+ this.ctx.restore();
164
+ }
165
+ }
166
+
167
+ // Continue rendering loop
168
+ this.animationFrameId = requestAnimationFrame(this.renderFrame);
169
+ };
170
+
171
+ private applyNoiseLayer(): void {
172
+ // Create subtle noise that changes over time
173
+ const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
174
+ const data = imageData.data;
175
+
176
+ // Very subtle noise (imperceptible to humans, confuses some automated tools)
177
+ for (let i = 0; i < data.length; i += 4) {
178
+ const noise = (Math.random() - 0.5) * 2; // -1 to 1
179
+ data[i] += noise; // R
180
+ data[i + 1] += noise; // G
181
+ data[i + 2] += noise; // B
182
+ // Alpha (i+3) unchanged
183
+ }
184
+
185
+ this.ctx.putImageData(imageData, 0, 0);
186
+ }
187
+
188
+ private drawDynamicWatermark(): void {
189
+ const text = this.opts.watermarkText!;
190
+ const fontSize = Math.floor(this.canvas.height / 30);
191
+
192
+ this.ctx.save();
193
+
194
+ // Configure text style
195
+ this.ctx.font = `${fontSize}px Arial, sans-serif`;
196
+ this.ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
197
+ this.ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
198
+ this.ctx.lineWidth = 1;
199
+ this.ctx.textAlign = 'center';
200
+ this.ctx.textBaseline = 'middle';
201
+
202
+ // Rotating watermark (harder to crop out)
203
+ const time = Date.now() / 1000;
204
+ const positions = [
205
+ { x: this.canvas.width * 0.25, y: this.canvas.height * 0.25 },
206
+ { x: this.canvas.width * 0.75, y: this.canvas.height * 0.25 },
207
+ { x: this.canvas.width * 0.25, y: this.canvas.height * 0.75 },
208
+ { x: this.canvas.width * 0.75, y: this.canvas.height * 0.75 },
209
+ { x: this.canvas.width * 0.5, y: this.canvas.height * 0.5 }
210
+ ];
211
+
212
+ positions.forEach((pos, index) => {
213
+ this.ctx.save();
214
+ this.ctx.translate(pos.x, pos.y);
215
+
216
+ // Rotate each watermark slightly
217
+ const rotation = (Math.sin(time + index) * 15 * Math.PI) / 180;
218
+ this.ctx.rotate(rotation);
219
+
220
+ // Pulsing opacity
221
+ const opacity = 0.2 + Math.sin(time * 2 + index) * 0.1;
222
+ this.ctx.globalAlpha = opacity;
223
+
224
+ this.ctx.strokeText(text, 0, 0);
225
+ this.ctx.fillText(text, 0, 0);
226
+
227
+ this.ctx.restore();
228
+ });
229
+
230
+ this.ctx.restore();
231
+ }
232
+
233
+ /**
234
+ * Get the canvas element (for external manipulation if needed)
235
+ */
236
+ public getCanvas(): HTMLCanvasElement {
237
+ return this.canvas;
238
+ }
239
+
240
+ /**
241
+ * Check if rendering is active
242
+ */
243
+ public isRendering(): boolean {
244
+ return this.isActive;
245
+ }
246
+ }