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.
- package/package.json +1 -1
- package/packages/core/dist/interfaces.d.ts +28 -2
- package/packages/core/dist/interfaces.d.ts.map +1 -1
- package/packages/core/dist/interfaces.js +0 -1
- package/packages/core/dist/interfaces.js.map +1 -1
- package/packages/core/src/interfaces.ts +53 -3
- package/packages/react-native/dist/drm/AndroidDRMProtection.js +1 -1
- package/packages/react-native/dist/drm/iOSDRMProtection.js +1 -1
- package/packages/web/dist/WebPlayer.d.ts +2 -0
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +9731 -9644
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/dist/drm/DRMHelper.d.ts +28 -0
- package/packages/web/dist/drm/DRMHelper.d.ts.map +1 -0
- package/packages/web/dist/drm/DRMHelper.js +117 -0
- package/packages/web/dist/drm/DRMHelper.js.map +1 -0
- package/packages/web/dist/index.d.ts +0 -1
- package/packages/web/dist/index.d.ts.map +1 -1
- package/packages/web/dist/index.js +6 -7
- package/packages/web/dist/index.js.map +1 -1
- package/packages/web/dist/react/WebPlayerView.d.ts +21 -0
- package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
- package/packages/web/dist/react/WebPlayerView.js.map +1 -1
- package/packages/web/dist/security/CanvasVideoRenderer.d.ts +26 -0
- package/packages/web/dist/security/CanvasVideoRenderer.d.ts.map +1 -0
- package/packages/web/dist/security/CanvasVideoRenderer.js +143 -0
- package/packages/web/dist/security/CanvasVideoRenderer.js.map +1 -0
- package/packages/web/dist/security/ScreenProtectionController.d.ts +39 -0
- package/packages/web/dist/security/ScreenProtectionController.d.ts.map +1 -0
- package/packages/web/dist/security/ScreenProtectionController.js +278 -0
- package/packages/web/dist/security/ScreenProtectionController.js.map +1 -0
- package/packages/web/src/WebPlayer.ts +120 -2
- package/packages/web/src/drm/DRMHelper.ts +203 -0
- package/packages/web/src/index.ts +0 -3
- package/packages/web/src/react/WebPlayerView.tsx +24 -0
- package/packages/web/src/security/CanvasVideoRenderer.ts +246 -0
- package/packages/web/src/security/ScreenProtectionController.ts +391 -0
- package/scripts/fix-imports.js +18 -16
- package/packages/core/src/interfaces/IDRMProtection.ts +0 -285
- package/packages/react-native/src/drm/AndroidDRMProtection.ts +0 -419
- package/packages/react-native/src/drm/iOSDRMProtection.ts +0 -415
- package/packages/web/src/drm/WebDRMProtection.ts +0 -596
|
@@ -1,596 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Web DRM Protection Implementation
|
|
3
|
-
*
|
|
4
|
-
* Implements Netflix-like content protection for web browsers using:
|
|
5
|
-
* - Encrypted Media Extensions (EME)
|
|
6
|
-
* - Widevine / PlayReady DRM
|
|
7
|
-
* - Screen recording detection
|
|
8
|
-
* - Tab capture blocking
|
|
9
|
-
* - Chromecast support
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
IDRMProtection,
|
|
14
|
-
IDRMProtectionConfig,
|
|
15
|
-
IDRMProtectionStatus,
|
|
16
|
-
CastDevice,
|
|
17
|
-
CastDeviceType,
|
|
18
|
-
DRMError,
|
|
19
|
-
DRMErrorCode,
|
|
20
|
-
DRMKeySystemConfig,
|
|
21
|
-
} from '@unified-video/core';
|
|
22
|
-
|
|
23
|
-
export class WebDRMProtection implements IDRMProtection {
|
|
24
|
-
private config: IDRMProtectionConfig;
|
|
25
|
-
private videoElement: HTMLVideoElement;
|
|
26
|
-
private mediaKeys: MediaKeys | null = null;
|
|
27
|
-
private status: IDRMProtectionStatus;
|
|
28
|
-
private screenRecordingCheckInterval: number | null = null;
|
|
29
|
-
private castSession: any = null;
|
|
30
|
-
private castContext: any = null;
|
|
31
|
-
|
|
32
|
-
// Key Systems in preference order
|
|
33
|
-
private readonly KEY_SYSTEMS = {
|
|
34
|
-
widevine: 'com.widevine.alpha',
|
|
35
|
-
playready: 'com.microsoft.playready',
|
|
36
|
-
clearkey: 'org.w3.clearkey', // Fallback for testing
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
constructor(videoElement: HTMLVideoElement) {
|
|
40
|
-
this.videoElement = videoElement;
|
|
41
|
-
this.config = { enabled: false };
|
|
42
|
-
this.status = {
|
|
43
|
-
isProtected: false,
|
|
44
|
-
drmSystem: 'none',
|
|
45
|
-
isScreenRecordingBlocked: false,
|
|
46
|
-
isAudioCaptureBlocked: false,
|
|
47
|
-
isScreenshotBlocked: false,
|
|
48
|
-
isCasting: false,
|
|
49
|
-
screenRecordingDetected: false,
|
|
50
|
-
mirroringDetected: false,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Initialize DRM protection
|
|
56
|
-
*/
|
|
57
|
-
async initialize(config: IDRMProtectionConfig): Promise<void> {
|
|
58
|
-
this.config = {
|
|
59
|
-
blockScreenRecording: true,
|
|
60
|
-
blockAudioCapture: true,
|
|
61
|
-
blockScreenshots: true,
|
|
62
|
-
allowCasting: true,
|
|
63
|
-
blockMirroring: true,
|
|
64
|
-
widevineSecurityLevel: 'L1',
|
|
65
|
-
...config,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
if (!this.config.enabled) {
|
|
69
|
-
console.log('[DRM] Protection disabled by configuration');
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
console.log('[DRM] Initializing web DRM protection...', this.config);
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
// Check if EME is supported
|
|
77
|
-
if (!this.isEMESupported()) {
|
|
78
|
-
throw this.createError(
|
|
79
|
-
DRMErrorCode.DEVICE_NOT_SUPPORTED,
|
|
80
|
-
'Encrypted Media Extensions (EME) not supported'
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Initialize DRM key system
|
|
85
|
-
if (this.config.licenseServerUrl) {
|
|
86
|
-
await this.initializeDRMKeySystem();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Start screen recording detection
|
|
90
|
-
if (this.config.blockScreenRecording) {
|
|
91
|
-
this.startScreenRecordingDetection();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Initialize casting support
|
|
95
|
-
if (this.config.allowCasting) {
|
|
96
|
-
await this.initializeCastSupport();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Block screenshot capture
|
|
100
|
-
if (this.config.blockScreenshots) {
|
|
101
|
-
this.blockScreenshots();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Prevent tab capture
|
|
105
|
-
if (this.config.blockMirroring) {
|
|
106
|
-
this.preventTabCapture();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
this.status.isProtected = true;
|
|
110
|
-
console.log('[DRM] Protection initialized successfully');
|
|
111
|
-
} catch (error) {
|
|
112
|
-
console.error('[DRM] Initialization failed:', error);
|
|
113
|
-
this.config.onDRMError?.(error as DRMError);
|
|
114
|
-
throw error;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Check if EME is supported
|
|
120
|
-
*/
|
|
121
|
-
private isEMESupported(): boolean {
|
|
122
|
-
return !!(
|
|
123
|
-
window.navigator &&
|
|
124
|
-
(window.navigator as any).requestMediaKeySystemAccess &&
|
|
125
|
-
window.MediaKeys
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Initialize DRM Key System (Widevine/PlayReady)
|
|
131
|
-
*/
|
|
132
|
-
private async initializeDRMKeySystem(): Promise<void> {
|
|
133
|
-
console.log('[DRM] Initializing key system...');
|
|
134
|
-
|
|
135
|
-
// Try Widevine first, then PlayReady
|
|
136
|
-
const keySystemConfigs: DRMKeySystemConfig[] = [
|
|
137
|
-
{
|
|
138
|
-
keySystem: this.KEY_SYSTEMS.widevine,
|
|
139
|
-
licenseServerUrl: this.config.licenseServerUrl!,
|
|
140
|
-
certificateUrl: this.config.certificateUrl,
|
|
141
|
-
headers: this.config.licenseHeaders,
|
|
142
|
-
// Widevine robustness levels
|
|
143
|
-
videoRobustness: this.config.widevineSecurityLevel === 'L1' ? 'HW_SECURE_ALL' : 'SW_SECURE_CRYPTO',
|
|
144
|
-
audioRobustness: this.config.widevineSecurityLevel === 'L1' ? 'HW_SECURE_ALL' : 'SW_SECURE_CRYPTO',
|
|
145
|
-
persistentState: 'optional',
|
|
146
|
-
distinctiveIdentifier: 'optional',
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
keySystem: this.KEY_SYSTEMS.playready,
|
|
150
|
-
licenseServerUrl: this.config.licenseServerUrl!,
|
|
151
|
-
headers: this.config.licenseHeaders,
|
|
152
|
-
videoRobustness: '3000', // PlayReady SL3000
|
|
153
|
-
audioRobustness: '3000',
|
|
154
|
-
persistentState: 'optional',
|
|
155
|
-
distinctiveIdentifier: 'optional',
|
|
156
|
-
},
|
|
157
|
-
];
|
|
158
|
-
|
|
159
|
-
for (const config of keySystemConfigs) {
|
|
160
|
-
try {
|
|
161
|
-
await this.requestMediaKeySystemAccess(config);
|
|
162
|
-
console.log(`[DRM] Successfully initialized ${config.keySystem}`);
|
|
163
|
-
return;
|
|
164
|
-
} catch (error) {
|
|
165
|
-
console.warn(`[DRM] ${config.keySystem} not available:`, error);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
throw this.createError(
|
|
170
|
-
DRMErrorCode.WIDEVINE_NOT_AVAILABLE,
|
|
171
|
-
'No supported DRM system available (tried Widevine and PlayReady)'
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Request Media Key System Access
|
|
177
|
-
*/
|
|
178
|
-
private async requestMediaKeySystemAccess(config: DRMKeySystemConfig): Promise<void> {
|
|
179
|
-
const keySystemConfig = [
|
|
180
|
-
{
|
|
181
|
-
initDataTypes: ['cenc', 'keyids', 'webm'],
|
|
182
|
-
audioCapabilities: [
|
|
183
|
-
{
|
|
184
|
-
contentType: 'audio/mp4; codecs="mp4a.40.2"',
|
|
185
|
-
robustness: config.audioRobustness || '',
|
|
186
|
-
},
|
|
187
|
-
],
|
|
188
|
-
videoCapabilities: [
|
|
189
|
-
{
|
|
190
|
-
contentType: 'video/mp4; codecs="avc1.42E01E"',
|
|
191
|
-
robustness: config.videoRobustness || '',
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
contentType: 'video/webm; codecs="vp9"',
|
|
195
|
-
robustness: config.videoRobustness || '',
|
|
196
|
-
},
|
|
197
|
-
],
|
|
198
|
-
distinctiveIdentifier: config.distinctiveIdentifier || 'optional',
|
|
199
|
-
persistentState: config.persistentState || 'optional',
|
|
200
|
-
},
|
|
201
|
-
];
|
|
202
|
-
|
|
203
|
-
const keySystemAccess = await navigator.requestMediaKeySystemAccess(
|
|
204
|
-
config.keySystem,
|
|
205
|
-
keySystemConfig
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
this.mediaKeys = await keySystemAccess.createMediaKeys();
|
|
209
|
-
await this.videoElement.setMediaKeys(this.mediaKeys);
|
|
210
|
-
|
|
211
|
-
// Set up license acquisition
|
|
212
|
-
this.setupLicenseAcquisition(config);
|
|
213
|
-
|
|
214
|
-
// Update status
|
|
215
|
-
if (config.keySystem === this.KEY_SYSTEMS.widevine) {
|
|
216
|
-
this.status.drmSystem = 'widevine';
|
|
217
|
-
this.status.securityLevel = config.videoRobustness === 'HW_SECURE_ALL' ? 'L1' : 'L3';
|
|
218
|
-
} else if (config.keySystem === this.KEY_SYSTEMS.playready) {
|
|
219
|
-
this.status.drmSystem = 'playready';
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Setup license acquisition from license server
|
|
225
|
-
*/
|
|
226
|
-
private setupLicenseAcquisition(config: DRMKeySystemConfig): void {
|
|
227
|
-
this.videoElement.addEventListener('encrypted', async (event: any) => {
|
|
228
|
-
console.log('[DRM] Encrypted event received, requesting license...');
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
if (!this.mediaKeys) {
|
|
232
|
-
throw new Error('MediaKeys not initialized');
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const session = this.mediaKeys.createSession();
|
|
236
|
-
|
|
237
|
-
// Listen for license messages
|
|
238
|
-
session.addEventListener('message', async (messageEvent: any) => {
|
|
239
|
-
console.log('[DRM] License request message received');
|
|
240
|
-
|
|
241
|
-
try {
|
|
242
|
-
const response = await fetch(config.licenseServerUrl, {
|
|
243
|
-
method: 'POST',
|
|
244
|
-
headers: {
|
|
245
|
-
'Content-Type': 'application/octet-stream',
|
|
246
|
-
...config.headers,
|
|
247
|
-
},
|
|
248
|
-
body: messageEvent.message,
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
if (!response.ok) {
|
|
252
|
-
throw this.createError(
|
|
253
|
-
DRMErrorCode.LICENSE_REQUEST_FAILED,
|
|
254
|
-
`License server returned ${response.status}`
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const license = await response.arrayBuffer();
|
|
259
|
-
await session.update(license);
|
|
260
|
-
|
|
261
|
-
console.log('[DRM] License acquired successfully');
|
|
262
|
-
this.config.onLicenseAcquired?.();
|
|
263
|
-
} catch (error) {
|
|
264
|
-
console.error('[DRM] License acquisition failed:', error);
|
|
265
|
-
this.config.onDRMError?.(error as DRMError);
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// Generate license request
|
|
270
|
-
await session.generateRequest(event.initDataType, event.initData);
|
|
271
|
-
} catch (error) {
|
|
272
|
-
console.error('[DRM] Failed to generate license request:', error);
|
|
273
|
-
this.config.onDRMError?.(error as DRMError);
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Start screen recording detection
|
|
280
|
-
* Uses multiple detection methods
|
|
281
|
-
*/
|
|
282
|
-
private startScreenRecordingDetection(): void {
|
|
283
|
-
console.log('[DRM] Starting screen recording detection...');
|
|
284
|
-
|
|
285
|
-
// Method 1: getDisplayMedia API detection
|
|
286
|
-
this.detectGetDisplayMedia();
|
|
287
|
-
|
|
288
|
-
// Method 2: Canvas fingerprinting detection
|
|
289
|
-
this.detectCanvasCapture();
|
|
290
|
-
|
|
291
|
-
// Method 3: Tab capture detection (Chrome)
|
|
292
|
-
this.detectTabCapture();
|
|
293
|
-
|
|
294
|
-
// Method 4: Periodic frame analysis
|
|
295
|
-
this.screenRecordingCheckInterval = window.setInterval(() => {
|
|
296
|
-
this.analyzeScreenRecordingSignals();
|
|
297
|
-
}, 1000);
|
|
298
|
-
|
|
299
|
-
this.status.isScreenRecordingBlocked = true;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Detect getDisplayMedia screen capture
|
|
304
|
-
*/
|
|
305
|
-
private detectGetDisplayMedia(): void {
|
|
306
|
-
if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Override getDisplayMedia to detect screen capture attempts
|
|
311
|
-
const originalGetDisplayMedia = navigator.mediaDevices.getDisplayMedia.bind(
|
|
312
|
-
navigator.mediaDevices
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
navigator.mediaDevices.getDisplayMedia = async function (constraints?: any) {
|
|
316
|
-
console.warn('[DRM] Screen capture attempt detected via getDisplayMedia');
|
|
317
|
-
// Block the request or notify
|
|
318
|
-
throw new DOMException('Screen capture blocked by DRM protection', 'NotAllowedError');
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Detect canvas-based capture
|
|
324
|
-
*/
|
|
325
|
-
private detectCanvasCapture(): void {
|
|
326
|
-
// Monitor canvas operations on the video
|
|
327
|
-
const observer = new MutationObserver((mutations) => {
|
|
328
|
-
mutations.forEach((mutation) => {
|
|
329
|
-
if (mutation.type === 'childList') {
|
|
330
|
-
mutation.addedNodes.forEach((node) => {
|
|
331
|
-
if (node.nodeName === 'CANVAS') {
|
|
332
|
-
console.warn('[DRM] Canvas element detected - potential screen capture');
|
|
333
|
-
this.handleScreenRecordingDetected();
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
observer.observe(document.body, {
|
|
341
|
-
childList: true,
|
|
342
|
-
subtree: true,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Detect tab capture (Chrome-specific)
|
|
348
|
-
*/
|
|
349
|
-
private detectTabCapture(): void {
|
|
350
|
-
// Chrome exposes tab capture status
|
|
351
|
-
if ('mediaSession' in navigator) {
|
|
352
|
-
document.addEventListener('visibilitychange', () => {
|
|
353
|
-
if (document.hidden) {
|
|
354
|
-
console.log('[DRM] Tab hidden - checking for capture...');
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Analyze various signals for screen recording
|
|
362
|
-
*/
|
|
363
|
-
private analyzeScreenRecordingSignals(): void {
|
|
364
|
-
// Check if video is playing in background tab
|
|
365
|
-
if (document.hidden && !this.videoElement.paused) {
|
|
366
|
-
console.warn('[DRM] Video playing in hidden tab - possible screen recording');
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Check for unusual video element states
|
|
370
|
-
if ((this.videoElement as any).captureStream) {
|
|
371
|
-
console.warn('[DRM] captureStream API available - monitoring...');
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Handle screen recording detection
|
|
377
|
-
*/
|
|
378
|
-
private handleScreenRecordingDetected(): void {
|
|
379
|
-
console.error('[DRM] ⚠️ SCREEN RECORDING DETECTED!');
|
|
380
|
-
this.status.screenRecordingDetected = true;
|
|
381
|
-
|
|
382
|
-
// Black out video
|
|
383
|
-
this.videoElement.style.filter = 'brightness(0)';
|
|
384
|
-
this.videoElement.pause();
|
|
385
|
-
|
|
386
|
-
// Notify callback
|
|
387
|
-
this.config.onScreenRecordingDetected?.();
|
|
388
|
-
|
|
389
|
-
// Show warning overlay
|
|
390
|
-
this.showProtectionWarning('Screen recording detected. Playback blocked.');
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Block screenshots
|
|
395
|
-
*/
|
|
396
|
-
private blockScreenshots(): void {
|
|
397
|
-
// CSS-based screenshot prevention
|
|
398
|
-
this.videoElement.style.webkitUserSelect = 'none';
|
|
399
|
-
this.videoElement.style.userSelect = 'none';
|
|
400
|
-
this.videoElement.setAttribute('oncontextmenu', 'return false');
|
|
401
|
-
|
|
402
|
-
// Prevent drag-and-drop
|
|
403
|
-
this.videoElement.addEventListener('dragstart', (e) => e.preventDefault());
|
|
404
|
-
|
|
405
|
-
// Detect PrintScreen key
|
|
406
|
-
document.addEventListener('keyup', (e) => {
|
|
407
|
-
if (e.key === 'PrintScreen') {
|
|
408
|
-
console.warn('[DRM] Screenshot attempt detected (PrintScreen key)');
|
|
409
|
-
this.config.onScreenshotAttempted?.();
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
this.status.isScreenshotBlocked = true;
|
|
414
|
-
console.log('[DRM] Screenshot blocking enabled');
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Prevent tab capture and mirroring
|
|
419
|
-
*/
|
|
420
|
-
private preventTabCapture(): void {
|
|
421
|
-
// Detect mirroring via multiple screens
|
|
422
|
-
if (window.screen && (window.screen as any).isExtended) {
|
|
423
|
-
console.warn('[DRM] Extended display detected');
|
|
424
|
-
this.status.mirroringDetected = true;
|
|
425
|
-
this.config.onMirroringDetected?.();
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Monitor presentation API
|
|
429
|
-
if ('presentation' in navigator) {
|
|
430
|
-
console.log('[DRM] Monitoring presentation API for mirroring...');
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Initialize Chromecast support
|
|
436
|
-
*/
|
|
437
|
-
private async initializeCastSupport(): Promise<void> {
|
|
438
|
-
if (!(window as any).chrome || !(window as any).chrome.cast) {
|
|
439
|
-
console.warn('[DRM] Google Cast API not available');
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
console.log('[DRM] Initializing Chromecast support...');
|
|
444
|
-
|
|
445
|
-
// Load Cast SDK
|
|
446
|
-
const script = document.createElement('script');
|
|
447
|
-
script.src = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1';
|
|
448
|
-
document.head.appendChild(script);
|
|
449
|
-
|
|
450
|
-
await new Promise((resolve) => {
|
|
451
|
-
script.onload = resolve;
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
// Initialize Cast API
|
|
455
|
-
(window as any).__onGCastApiAvailable = (isAvailable: boolean) => {
|
|
456
|
-
if (isAvailable) {
|
|
457
|
-
this.castContext = (window as any).cast.framework.CastContext.getInstance();
|
|
458
|
-
this.castContext.setOptions({
|
|
459
|
-
receiverApplicationId: (window as any).chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,
|
|
460
|
-
autoJoinPolicy: (window as any).chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
console.log('[DRM] Chromecast initialized');
|
|
464
|
-
}
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Get current protection status
|
|
470
|
-
*/
|
|
471
|
-
getStatus(): IDRMProtectionStatus {
|
|
472
|
-
return { ...this.status };
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Enable/disable DRM protection
|
|
477
|
-
*/
|
|
478
|
-
setEnabled(enabled: boolean): void {
|
|
479
|
-
this.config.enabled = enabled;
|
|
480
|
-
if (!enabled) {
|
|
481
|
-
this.dispose();
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Enable/disable specific feature
|
|
487
|
-
*/
|
|
488
|
-
setFeature(feature: keyof IDRMProtectionConfig, enabled: boolean): void {
|
|
489
|
-
(this.config as any)[feature] = enabled;
|
|
490
|
-
|
|
491
|
-
// Re-apply feature if changing during playback
|
|
492
|
-
switch (feature) {
|
|
493
|
-
case 'blockScreenRecording':
|
|
494
|
-
if (enabled) {
|
|
495
|
-
this.startScreenRecordingDetection();
|
|
496
|
-
} else if (this.screenRecordingCheckInterval) {
|
|
497
|
-
clearInterval(this.screenRecordingCheckInterval);
|
|
498
|
-
}
|
|
499
|
-
break;
|
|
500
|
-
case 'blockScreenshots':
|
|
501
|
-
this.status.isScreenshotBlocked = enabled;
|
|
502
|
-
break;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* Start casting
|
|
508
|
-
*/
|
|
509
|
-
async startCasting(deviceId: string): Promise<void> {
|
|
510
|
-
if (!this.castContext) {
|
|
511
|
-
throw new Error('Cast not initialized');
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// Cast implementation
|
|
515
|
-
console.log('[DRM] Starting cast to device:', deviceId);
|
|
516
|
-
this.status.isCasting = true;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/**
|
|
520
|
-
* Stop casting
|
|
521
|
-
*/
|
|
522
|
-
async stopCasting(): Promise<void> {
|
|
523
|
-
if (this.castSession) {
|
|
524
|
-
this.castSession.endSession(true);
|
|
525
|
-
this.castSession = null;
|
|
526
|
-
}
|
|
527
|
-
this.status.isCasting = false;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Get available cast devices
|
|
532
|
-
*/
|
|
533
|
-
async getAvailableCastDevices(): Promise<CastDevice[]> {
|
|
534
|
-
// Return available Chromecast devices
|
|
535
|
-
return [];
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
/**
|
|
539
|
-
* Renew DRM license
|
|
540
|
-
*/
|
|
541
|
-
async renewLicense(): Promise<void> {
|
|
542
|
-
console.log('[DRM] Renewing license...');
|
|
543
|
-
// Re-trigger license acquisition
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* Show protection warning overlay
|
|
548
|
-
*/
|
|
549
|
-
private showProtectionWarning(message: string): void {
|
|
550
|
-
const overlay = document.createElement('div');
|
|
551
|
-
overlay.style.cssText = `
|
|
552
|
-
position: absolute;
|
|
553
|
-
top: 0;
|
|
554
|
-
left: 0;
|
|
555
|
-
width: 100%;
|
|
556
|
-
height: 100%;
|
|
557
|
-
background: #000;
|
|
558
|
-
color: #fff;
|
|
559
|
-
display: flex;
|
|
560
|
-
align-items: center;
|
|
561
|
-
justify-content: center;
|
|
562
|
-
font-size: 18px;
|
|
563
|
-
z-index: 9999999;
|
|
564
|
-
`;
|
|
565
|
-
overlay.textContent = message;
|
|
566
|
-
this.videoElement.parentElement?.appendChild(overlay);
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* Create DRM error
|
|
571
|
-
*/
|
|
572
|
-
private createError(code: DRMErrorCode, message: string): DRMError {
|
|
573
|
-
return {
|
|
574
|
-
code,
|
|
575
|
-
message,
|
|
576
|
-
platform: 'web',
|
|
577
|
-
recoverable: false,
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
/**
|
|
582
|
-
* Cleanup resources
|
|
583
|
-
*/
|
|
584
|
-
dispose(): void {
|
|
585
|
-
if (this.screenRecordingCheckInterval) {
|
|
586
|
-
clearInterval(this.screenRecordingCheckInterval);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
if (this.castSession) {
|
|
590
|
-
this.stopCasting();
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
this.status.isProtected = false;
|
|
594
|
-
console.log('[DRM] Protection disposed');
|
|
595
|
-
}
|
|
596
|
-
}
|