unified-video-framework 1.4.386 → 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.
@@ -22,8 +22,6 @@ 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';
27
25
 
28
26
  // Dynamic imports for streaming libraries
29
27
  declare global {
@@ -95,9 +93,6 @@ export class WebPlayer extends BasePlayer {
95
93
  // Paywall
96
94
  private paywallController: any = null;
97
95
 
98
- // Screen protection
99
- private screenProtectionController: ScreenProtectionController | null = null;
100
-
101
96
  // Play/pause coordination to prevent race conditions
102
97
  private _playPromise: Promise<void> | null = null;
103
98
  private _deferredPause = false;
@@ -358,11 +353,6 @@ export class WebPlayer extends BasePlayer {
358
353
  this.setupFullscreenListeners();
359
354
  this.setupUserInteractionTracking();
360
355
 
361
- // Setup screen protection if enabled
362
- if ((this.config as any).screenProtection === true) {
363
- this.initializeScreenProtection();
364
- }
365
-
366
356
  // Initialize chapter manager if enabled
367
357
  if (this.chapterConfig.enabled && this.video) {
368
358
  this.setupChapterManager();
@@ -8120,27 +8110,10 @@ export class WebPlayer extends BasePlayer {
8120
8110
  }
8121
8111
 
8122
8112
  // Render the watermark
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
- }
8113
+ ctx.fillText(text, x, y);
8141
8114
  ctx.restore();
8142
8115
 
8143
- this.debugLog('Watermark rendered:', { text, x, y, multiWatermark: !!((this.config as any).screenProtection && this.screenProtectionController) });
8116
+ this.debugLog('Watermark rendered:', { text, x, y });
8144
8117
  };
8145
8118
 
8146
8119
  // Set up interval with configured frequency
@@ -8150,91 +8123,6 @@ export class WebPlayer extends BasePlayer {
8150
8123
  this.debugLog('Watermark setup complete with update interval:', config.updateInterval + 'ms');
8151
8124
  }
8152
8125
 
8153
- private initializeScreenProtection(): void {
8154
- if (!this.video || !this.container) return;
8155
-
8156
- // Normalize config: convert boolean to EnhancedScreenProtectionConfig
8157
- const screenProtConfig = (this.config as any).screenProtection;
8158
- const enhancedConfig = typeof screenProtConfig === 'boolean'
8159
- ? { enabled: true } // Simple mode: just { enabled: true }
8160
- : screenProtConfig; // Advanced mode: full config object
8161
-
8162
- if (!enhancedConfig || !enhancedConfig.enabled) return;
8163
-
8164
- console.warn(
8165
- '[Unified Video Framework] Screen Protection Enabled\n' +
8166
- 'Note: Browsers cannot completely block screen recording. ' +
8167
- 'This provides detection and deterrence through watermarking and monitoring.\n' +
8168
- 'For DRM-level protection, use encrypted content with Widevine/FairPlay.'
8169
- );
8170
-
8171
- // Enhance watermark for protection
8172
- const watermarkText = enhancedConfig.forensicWatermark?.userId
8173
- ? `PROTECTED - ${enhancedConfig.forensicWatermark.userId}`
8174
- : 'PROTECTED CONTENT';
8175
-
8176
- if (!(this.config as any).watermark) {
8177
- (this.config as any).watermark = {
8178
- enabled: true,
8179
- text: watermarkText,
8180
- randomPosition: true,
8181
- updateInterval: 1000 // Rapid updates
8182
- };
8183
- } else if ((this.config as any).watermark.enabled !== false) {
8184
- // Force aggressive watermarking
8185
- (this.config as any).watermark.randomPosition = true;
8186
- (this.config as any).watermark.updateInterval = Math.min(
8187
- (this.config as any).watermark.updateInterval || 5000,
8188
- 1000
8189
- );
8190
- // Add forensic watermark user ID if provided
8191
- if (enhancedConfig.forensicWatermark?.userId) {
8192
- (this.config as any).watermark.text = watermarkText;
8193
- }
8194
- }
8195
- this.setupWatermark(); // Re-setup with new config
8196
-
8197
- // Create protection controller
8198
- this.screenProtectionController = new ScreenProtectionController({
8199
- videoElement: this.video,
8200
- containerElement: this.container,
8201
- config: enhancedConfig,
8202
- watermarkConfig: (this.config as any).watermark,
8203
- onDetection: (event: ScreenCaptureEvent) => {
8204
- console.warn('[Screen Protection] Detection:', event);
8205
- this.emit('onScreenCaptureDetected', event);
8206
-
8207
- // Call config callback if provided
8208
- if (enhancedConfig.onScreenCaptureDetected) {
8209
- enhancedConfig.onScreenCaptureDetected(event);
8210
- }
8211
- // Also check legacy callback
8212
- if ((this.config as any).onScreenCaptureDetected) {
8213
- (this.config as any).onScreenCaptureDetected(event);
8214
- }
8215
- },
8216
- onDevToolsDetected: () => {
8217
- console.warn('[Screen Protection] DevTools detected');
8218
- this.emit('onDevToolsDetected');
8219
-
8220
- // Call config callback if provided
8221
- if (enhancedConfig.onDevToolsDetected) {
8222
- enhancedConfig.onDevToolsDetected();
8223
- }
8224
- // Also check legacy callback
8225
- if ((this.config as any).onDevToolsDetected) {
8226
- (this.config as any).onDevToolsDetected();
8227
- }
8228
- },
8229
- onPause: () => {
8230
- // Pause video when requested by protection controller
8231
- this.pause();
8232
- }
8233
- });
8234
-
8235
- this.screenProtectionController.activate();
8236
- }
8237
-
8238
8126
  public setPaywallConfig(config: any) {
8239
8127
  try {
8240
8128
  if (!config) return;
@@ -11390,12 +11278,6 @@ export class WebPlayer extends BasePlayer {
11390
11278
  this.paywallController = null;
11391
11279
  }
11392
11280
 
11393
- // Destroy screen protection controller
11394
- if (this.screenProtectionController) {
11395
- this.screenProtectionController.destroy();
11396
- this.screenProtectionController = null;
11397
- }
11398
-
11399
11281
  // Destroy chapter managers
11400
11282
  if (this.chapterManager && typeof this.chapterManager.destroy === 'function') {
11401
11283
  this.chapterManager.destroy();
@@ -11,16 +11,6 @@ export { WebPlayer } from './WebPlayer';
11
11
  export { WebPlayerView } from './react/WebPlayerView';
12
12
  export { SecureVideoPlayer } from './SecureVideoPlayer';
13
13
 
14
- // Export DRM utilities
15
- export { DRMHelper, DRMProviderExamples } from './drm/DRMHelper';
16
- export type { DRMHelperOptions } from './drm/DRMHelper';
17
-
18
- // Export security controllers
19
- export { CanvasVideoRenderer } from './security/CanvasVideoRenderer';
20
- export { ScreenProtectionController } from './security/ScreenProtectionController';
21
- export type { CanvasRendererOptions } from './security/CanvasVideoRenderer';
22
- export type { ScreenProtectionOptions } from './security/ScreenProtectionController';
23
-
24
14
  // Export EPG (Electronic Program Guide) components
25
15
  export * from './react/EPG';
26
16
 
@@ -432,30 +432,6 @@ 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;
459
435
  };
460
436
 
461
437
  export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
@@ -1286,6 +1262,13 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
1286
1262
  midrollTimes: props.googleAds.midrollTimes,
1287
1263
  companionAdSlots: props.googleAds.companionAdSlots,
1288
1264
  onAdStart: () => {
1265
+ // Clear the fallback timeout since a pre-roll ad is actually starting
1266
+ if (prerollFallbackTimeout) {
1267
+ clearTimeout(prerollFallbackTimeout);
1268
+ prerollFallbackTimeout = null;
1269
+ console.log('✅ Pre-roll ad started, fallback timeout cleared');
1270
+ }
1271
+
1289
1272
  setIsAdPlaying(true);
1290
1273
  // Notify player to block keyboard controls
1291
1274
  if (typeof (player as any).setAdPlaying === 'function') {
@@ -1302,6 +1285,12 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
1302
1285
  props.googleAds?.onAdEnd?.();
1303
1286
  },
1304
1287
  onAdError: (error) => {
1288
+ // Clear the fallback timeout on ad error
1289
+ if (prerollFallbackTimeout) {
1290
+ clearTimeout(prerollFallbackTimeout);
1291
+ prerollFallbackTimeout = null;
1292
+ }
1293
+
1305
1294
  setIsAdPlaying(false);
1306
1295
  // Notify player to unblock keyboard controls
1307
1296
  if (typeof (player as any).setAdPlaying === 'function') {
@@ -1343,6 +1332,7 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
1343
1332
  // Initialize ad display container on first user interaction
1344
1333
  // Chrome requires this to be called on a user gesture
1345
1334
  let adContainerInitialized = false;
1335
+ let prerollFallbackTimeout: NodeJS.Timeout | null = null;
1346
1336
 
1347
1337
  const initAdsOnUserGesture = () => {
1348
1338
  if (!adContainerInitialized && adsManagerRef.current) {
@@ -1359,6 +1349,20 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
1359
1349
  const handleFirstPlay = () => {
1360
1350
  initAdsOnUserGesture();
1361
1351
  if (adsManagerRef.current && adContainerInitialized) {
1352
+ // CRITICAL FIX: Pause video immediately to prevent content playback during ad loading
1353
+ // This prevents 2-4 seconds of video playing while pre-roll ads are being fetched
1354
+ console.log('🛑 Pausing video for pre-roll ads...');
1355
+ videoElement.pause();
1356
+
1357
+ // Safety mechanism: If no pre-roll ad starts within 1 second, resume video
1358
+ // This handles cases where only mid-roll or post-roll ads are configured
1359
+ prerollFallbackTimeout = setTimeout(() => {
1360
+ if (!adsManagerRef.current?.isPlayingAd()) {
1361
+ console.log('⏩ No pre-roll ad detected, resuming video playback');
1362
+ videoElement.play().catch(() => {});
1363
+ }
1364
+ }, 1000);
1365
+
1362
1366
  adsManagerRef.current.requestAds();
1363
1367
  }
1364
1368
  videoElement.removeEventListener('play', handleFirstPlay);
@@ -28,24 +28,6 @@ function fixImports(filePath) {
28
28
  'import "../../core/dist/index.js"'
29
29
  );
30
30
 
31
- // Fix imports to core/dist to use index.js (both with and without .js)
32
- content = content.replace(
33
- /from\s+["']\.\.\/\.\.\/\.\.\/core\/dist\.js["']/g,
34
- 'from "../../../core/dist/index.js"'
35
- );
36
- content = content.replace(
37
- /from\s+["']\.\.\/\.\.\/\.\.\/core\/dist["']/g,
38
- 'from "../../../core/dist/index.js"'
39
- );
40
- content = content.replace(
41
- /from\s+["']\.\.\/\.\.\/core\/dist\.js["']/g,
42
- 'from "../../core/dist/index.js"'
43
- );
44
- content = content.replace(
45
- /from\s+["']\.\.\/\.\.\/core\/dist["']/g,
46
- 'from "../../core/dist/index.js"'
47
- );
48
-
49
31
  // Fix relative imports within the same package to include .js extension
50
32
  content = content.replace(
51
33
  /from\s+["']\.\/([^"']+)(?<!\.js)["']/g,
@@ -54,10 +36,6 @@ function fixImports(filePath) {
54
36
  content = content.replace(
55
37
  /from\s+["']\.\.?\/([^"']+)(?<!\.js)["']/g,
56
38
  (match, p1) => {
57
- // Skip if it's already been handled (ends with index.js)
58
- if (p1.includes('index.js')) {
59
- return match;
60
- }
61
39
  if (p1.includes('/')) {
62
40
  return `from "../${p1.replace(/([^\/]+)$/, '$1.js')}"`;
63
41
  }
@@ -80,24 +58,6 @@ function fixImports(filePath) {
80
58
  'import "../../core/dist/index.js"'
81
59
  );
82
60
 
83
- // Fix imports to core/dist to use index.js (both with and without .js)
84
- content = content.replace(
85
- /from\s+["']\.\.\/\.\.\/\.\.\/core\/dist\.js["']/g,
86
- 'from "../../../core/dist/index.js"'
87
- );
88
- content = content.replace(
89
- /from\s+["']\.\.\/\.\.\/\.\.\/core\/dist["']/g,
90
- 'from "../../../core/dist/index.js"'
91
- );
92
- content = content.replace(
93
- /from\s+["']\.\.\/\.\.\/core\/dist\.js["']/g,
94
- 'from "../../core/dist/index.js"'
95
- );
96
- content = content.replace(
97
- /from\s+["']\.\.\/\.\.\/core\/dist["']/g,
98
- 'from "../../core/dist/index.js"'
99
- );
100
-
101
61
  // Fix relative imports within the same package to include .js extension
102
62
  content = content.replace(
103
63
  /from\s+["']\.\/([^"']+)(?<!\.js)["']/g,
@@ -106,10 +66,6 @@ function fixImports(filePath) {
106
66
  content = content.replace(
107
67
  /from\s+["']\.\.?\/([^"']+)(?<!\.js)["']/g,
108
68
  (match, p1) => {
109
- // Skip if it's already been handled (ends with index.js)
110
- if (p1.includes('index.js')) {
111
- return match;
112
- }
113
69
  if (p1.includes('/')) {
114
70
  return `from "../${p1.replace(/([^\/]+)$/, '$1.js')}"`;
115
71
  }
@@ -1,203 +0,0 @@
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
- };