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.
- package/package.json +1 -1
- package/packages/core/dist/interfaces.d.ts +1 -28
- package/packages/core/dist/interfaces.d.ts.map +1 -1
- package/packages/core/src/interfaces.ts +1 -53
- 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 -21
- 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/dist/security/ScreenProtectionController.d.ts +4 -0
- package/packages/web/dist/security/ScreenProtectionController.d.ts.map +1 -1
- package/packages/web/dist/security/ScreenProtectionController.js +90 -0
- package/packages/web/dist/security/ScreenProtectionController.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 -24
- 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 -391
|
@@ -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
|
-
|
|
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
|
|
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);
|
package/scripts/fix-imports.js
CHANGED
|
@@ -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
|
-
};
|