unified-video-framework 1.4.377 → 1.4.378
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/IDRMProtection.d.ts +90 -0
- package/packages/core/dist/interfaces/IDRMProtection.d.ts.map +1 -0
- package/packages/core/dist/interfaces/IDRMProtection.js +15 -0
- package/packages/core/dist/interfaces/IDRMProtection.js.map +1 -0
- package/packages/core/dist/interfaces.d.ts +1 -0
- package/packages/core/dist/interfaces.d.ts.map +1 -1
- package/packages/core/dist/interfaces.js +1 -0
- package/packages/core/dist/interfaces.js.map +1 -1
- package/packages/core/src/interfaces/IDRMProtection.ts +285 -0
- package/packages/core/src/interfaces.ts +2 -0
- package/packages/react-native/dist/drm/AndroidDRMProtection.d.ts +21 -0
- package/packages/react-native/dist/drm/AndroidDRMProtection.d.ts.map +1 -0
- package/packages/react-native/dist/drm/AndroidDRMProtection.js +184 -0
- package/packages/react-native/dist/drm/AndroidDRMProtection.js.map +1 -0
- package/packages/react-native/dist/drm/iOSDRMProtection.d.ts +21 -0
- package/packages/react-native/dist/drm/iOSDRMProtection.d.ts.map +1 -0
- package/packages/react-native/dist/drm/iOSDRMProtection.js +172 -0
- package/packages/react-native/dist/drm/iOSDRMProtection.js.map +1 -0
- package/packages/react-native/src/drm/AndroidDRMProtection.ts +419 -0
- package/packages/react-native/src/drm/iOSDRMProtection.ts +415 -0
- package/packages/web/dist/drm/WebDRMProtection.d.ts +37 -0
- package/packages/web/dist/drm/WebDRMProtection.d.ts.map +1 -0
- package/packages/web/dist/drm/WebDRMProtection.js +378 -0
- package/packages/web/dist/drm/WebDRMProtection.js.map +1 -0
- package/packages/web/dist/index.d.ts +1 -0
- package/packages/web/dist/index.d.ts.map +1 -1
- package/packages/web/dist/index.js +1 -0
- package/packages/web/dist/index.js.map +1 -1
- package/packages/web/src/drm/WebDRMProtection.ts +596 -0
- package/packages/web/src/index.ts +3 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Android DRM Protection Implementation
|
|
3
|
+
*
|
|
4
|
+
* Implements Netflix-like content protection for Android using:
|
|
5
|
+
* - FLAG_SECURE for screenshot/recording prevention
|
|
6
|
+
* - Widevine DRM (L1/L3)
|
|
7
|
+
* - MediaDrm + ExoPlayer
|
|
8
|
+
* - Chromecast support (remote playback, not mirroring)
|
|
9
|
+
* - Display mirroring detection
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { NativeModules, NativeEventEmitter } from 'react-native';
|
|
13
|
+
import {
|
|
14
|
+
IDRMProtection,
|
|
15
|
+
IDRMProtectionConfig,
|
|
16
|
+
IDRMProtectionStatus,
|
|
17
|
+
CastDevice,
|
|
18
|
+
DRMErrorCode,
|
|
19
|
+
} from '@unified-video/core';
|
|
20
|
+
|
|
21
|
+
const { DRMProtectionModule } = NativeModules;
|
|
22
|
+
const drmEventEmitter = new NativeEventEmitter(DRMProtectionModule);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Android DRM Protection
|
|
26
|
+
*/
|
|
27
|
+
export class AndroidDRMProtection implements IDRMProtection {
|
|
28
|
+
private config: IDRMProtectionConfig;
|
|
29
|
+
private status: IDRMProtectionStatus;
|
|
30
|
+
private listeners: any[] = [];
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
this.config = { enabled: false };
|
|
34
|
+
this.status = {
|
|
35
|
+
isProtected: false,
|
|
36
|
+
drmSystem: 'none',
|
|
37
|
+
isScreenRecordingBlocked: false,
|
|
38
|
+
isAudioCaptureBlocked: false,
|
|
39
|
+
isScreenshotBlocked: false,
|
|
40
|
+
isCasting: false,
|
|
41
|
+
screenRecordingDetected: false,
|
|
42
|
+
mirroringDetected: false,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Initialize DRM protection
|
|
48
|
+
*/
|
|
49
|
+
async initialize(config: IDRMProtectionConfig): Promise<void> {
|
|
50
|
+
this.config = {
|
|
51
|
+
blockScreenRecording: true,
|
|
52
|
+
blockAudioCapture: true,
|
|
53
|
+
blockScreenshots: true,
|
|
54
|
+
allowCasting: true,
|
|
55
|
+
blockMirroring: true,
|
|
56
|
+
widevineSecurityLevel: 'L1',
|
|
57
|
+
...config,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (!this.config.enabled) {
|
|
61
|
+
console.log('[DRM-Android] Protection disabled');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log('[DRM-Android] Initializing...', this.config);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
// Enable FLAG_SECURE to block screenshots and screen recording
|
|
69
|
+
if (this.config.blockScreenshots || this.config.blockScreenRecording) {
|
|
70
|
+
await DRMProtectionModule.enableFlagSecure(true);
|
|
71
|
+
this.status.isScreenshotBlocked = true;
|
|
72
|
+
this.status.isScreenRecordingBlocked = true;
|
|
73
|
+
console.log('[DRM-Android] FLAG_SECURE enabled');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Initialize Widevine DRM
|
|
77
|
+
if (this.config.licenseServerUrl) {
|
|
78
|
+
await this.initializeWidevine();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Start mirroring detection
|
|
82
|
+
if (this.config.blockMirroring) {
|
|
83
|
+
await this.startMirroringDetection();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Initialize Chromecast
|
|
87
|
+
if (this.config.allowCasting) {
|
|
88
|
+
await this.initializeChromecast();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Setup event listeners
|
|
92
|
+
this.setupEventListeners();
|
|
93
|
+
|
|
94
|
+
this.status.isProtected = true;
|
|
95
|
+
console.log('[DRM-Android] Protection initialized');
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('[DRM-Android] Initialization failed:', error);
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Initialize Widevine DRM
|
|
104
|
+
*/
|
|
105
|
+
private async initializeWidevine(): Promise<void> {
|
|
106
|
+
console.log('[DRM-Android] Initializing Widevine...');
|
|
107
|
+
|
|
108
|
+
const widevineConfig = {
|
|
109
|
+
licenseServerUrl: this.config.licenseServerUrl,
|
|
110
|
+
licenseHeaders: this.config.licenseHeaders,
|
|
111
|
+
securityLevel: this.config.widevineSecurityLevel,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const result = await DRMProtectionModule.initializeWidevine(widevineConfig);
|
|
115
|
+
|
|
116
|
+
if (result.success) {
|
|
117
|
+
this.status.drmSystem = 'widevine';
|
|
118
|
+
this.status.securityLevel = result.securityLevel;
|
|
119
|
+
console.log(`[DRM-Android] Widevine initialized with security level: ${result.securityLevel}`);
|
|
120
|
+
|
|
121
|
+
if (result.securityLevel === 'L3' && this.config.widevineSecurityLevel === 'L1') {
|
|
122
|
+
console.warn('[DRM-Android] ⚠️ Requested L1 but got L3 - device may not support hardware DRM');
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
throw {
|
|
126
|
+
code: DRMErrorCode.WIDEVINE_NOT_AVAILABLE,
|
|
127
|
+
message: 'Widevine initialization failed',
|
|
128
|
+
platform: 'android',
|
|
129
|
+
recoverable: false,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Start display mirroring detection
|
|
136
|
+
*/
|
|
137
|
+
private async startMirroringDetection(): Promise<void> {
|
|
138
|
+
console.log('[DRM-Android] Starting mirroring detection...');
|
|
139
|
+
|
|
140
|
+
// Monitor for external displays and mirroring
|
|
141
|
+
await DRMProtectionModule.startMirroringDetection();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Initialize Chromecast
|
|
146
|
+
*/
|
|
147
|
+
private async initializeChromecast(): Promise<void> {
|
|
148
|
+
console.log('[DRM-Android] Initializing Chromecast...');
|
|
149
|
+
|
|
150
|
+
await DRMProtectionModule.initializeChromecast({
|
|
151
|
+
appId: 'CC1AD845', // Default Media Receiver
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Setup event listeners
|
|
157
|
+
*/
|
|
158
|
+
private setupEventListeners(): void {
|
|
159
|
+
// Screen recording detected
|
|
160
|
+
this.listeners.push(
|
|
161
|
+
drmEventEmitter.addListener('onScreenRecordingDetected', () => {
|
|
162
|
+
console.warn('[DRM-Android] ⚠️ Screen recording detected!');
|
|
163
|
+
this.status.screenRecordingDetected = true;
|
|
164
|
+
this.config.onScreenRecordingDetected?.();
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Mirroring detected
|
|
169
|
+
this.listeners.push(
|
|
170
|
+
drmEventEmitter.addListener('onMirroringDetected', (event: any) => {
|
|
171
|
+
console.warn('[DRM-Android] ⚠️ Display mirroring detected:', event.displayName);
|
|
172
|
+
this.status.mirroringDetected = true;
|
|
173
|
+
this.config.onMirroringDetected?.();
|
|
174
|
+
})
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Casting started
|
|
178
|
+
this.listeners.push(
|
|
179
|
+
drmEventEmitter.addListener('onCastingStarted', (event: any) => {
|
|
180
|
+
console.log('[DRM-Android] Casting started:', event.deviceName);
|
|
181
|
+
this.status.isCasting = true;
|
|
182
|
+
this.status.castDevice = {
|
|
183
|
+
name: event.deviceName,
|
|
184
|
+
type: 'chromecast',
|
|
185
|
+
};
|
|
186
|
+
this.config.onCastingStarted?.(event.deviceName, 'chromecast');
|
|
187
|
+
})
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Casting ended
|
|
191
|
+
this.listeners.push(
|
|
192
|
+
drmEventEmitter.addListener('onCastingEnded', () => {
|
|
193
|
+
console.log('[DRM-Android] Casting ended');
|
|
194
|
+
this.status.isCasting = false;
|
|
195
|
+
this.status.castDevice = undefined;
|
|
196
|
+
this.config.onCastingEnded?.();
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// License acquired
|
|
201
|
+
this.listeners.push(
|
|
202
|
+
drmEventEmitter.addListener('onLicenseAcquired', (event: any) => {
|
|
203
|
+
console.log('[DRM-Android] License acquired');
|
|
204
|
+
this.status.licenseExpiration = event.expirationTime ? new Date(event.expirationTime) : undefined;
|
|
205
|
+
this.config.onLicenseAcquired?.();
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// DRM error
|
|
210
|
+
this.listeners.push(
|
|
211
|
+
drmEventEmitter.addListener('onDRMError', (error: any) => {
|
|
212
|
+
console.error('[DRM-Android] DRM Error:', error);
|
|
213
|
+
this.config.onDRMError?.(error);
|
|
214
|
+
})
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get current status
|
|
220
|
+
*/
|
|
221
|
+
getStatus(): IDRMProtectionStatus {
|
|
222
|
+
return { ...this.status };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Enable/disable protection
|
|
227
|
+
*/
|
|
228
|
+
setEnabled(enabled: boolean): void {
|
|
229
|
+
this.config.enabled = enabled;
|
|
230
|
+
DRMProtectionModule.setEnabled(enabled);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Set specific feature
|
|
235
|
+
*/
|
|
236
|
+
setFeature(feature: keyof IDRMProtectionConfig, enabled: boolean): void {
|
|
237
|
+
(this.config as any)[feature] = enabled;
|
|
238
|
+
|
|
239
|
+
switch (feature) {
|
|
240
|
+
case 'blockScreenshots':
|
|
241
|
+
case 'blockScreenRecording':
|
|
242
|
+
DRMProtectionModule.enableFlagSecure(enabled);
|
|
243
|
+
this.status.isScreenshotBlocked = enabled;
|
|
244
|
+
this.status.isScreenRecordingBlocked = enabled;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Start casting
|
|
251
|
+
*/
|
|
252
|
+
async startCasting(deviceId: string): Promise<void> {
|
|
253
|
+
await DRMProtectionModule.startCasting(deviceId);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Stop casting
|
|
258
|
+
*/
|
|
259
|
+
async stopCasting(): Promise<void> {
|
|
260
|
+
await DRMProtectionModule.stopCasting();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Get available cast devices
|
|
265
|
+
*/
|
|
266
|
+
async getAvailableCastDevices(): Promise<CastDevice[]> {
|
|
267
|
+
const devices = await DRMProtectionModule.getAvailableCastDevices();
|
|
268
|
+
return devices.map((device: any) => ({
|
|
269
|
+
id: device.id,
|
|
270
|
+
name: device.name,
|
|
271
|
+
type: 'chromecast',
|
|
272
|
+
isAvailable: device.isAvailable,
|
|
273
|
+
capabilities: {
|
|
274
|
+
supportsVideo: true,
|
|
275
|
+
supportsAudio: true,
|
|
276
|
+
supportsDRM: device.supportsDRM,
|
|
277
|
+
maxResolution: device.maxResolution,
|
|
278
|
+
},
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Renew license
|
|
284
|
+
*/
|
|
285
|
+
async renewLicense(): Promise<void> {
|
|
286
|
+
await DRMProtectionModule.renewLicense();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Cleanup
|
|
291
|
+
*/
|
|
292
|
+
dispose(): void {
|
|
293
|
+
this.listeners.forEach((listener) => listener.remove());
|
|
294
|
+
this.listeners = [];
|
|
295
|
+
DRMProtectionModule.dispose();
|
|
296
|
+
this.status.isProtected = false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* NATIVE ANDROID MODULE (Java/Kotlin)
|
|
302
|
+
*
|
|
303
|
+
* File: android/app/src/main/java/com/yourapp/DRMProtectionModule.kt
|
|
304
|
+
*
|
|
305
|
+
* ```kotlin
|
|
306
|
+
* package com.yourapp
|
|
307
|
+
*
|
|
308
|
+
* import android.view.WindowManager
|
|
309
|
+
* import android.hardware.display.DisplayManager
|
|
310
|
+
* import android.media.MediaDrm
|
|
311
|
+
* import com.facebook.react.bridge.*
|
|
312
|
+
* import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
313
|
+
* import com.google.android.gms.cast.framework.CastContext
|
|
314
|
+
* import com.google.android.exoplayer2.drm.*
|
|
315
|
+
*
|
|
316
|
+
* class DRMProtectionModule(reactContext: ReactApplicationContext) :
|
|
317
|
+
* ReactContextBaseJavaModule(reactContext) {
|
|
318
|
+
*
|
|
319
|
+
* private val WIDEVINE_UUID = UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL)
|
|
320
|
+
* private var mediaDrm: MediaDrm? = null
|
|
321
|
+
* private var displayManager: DisplayManager? = null
|
|
322
|
+
*
|
|
323
|
+
* override fun getName(): String = "DRMProtectionModule"
|
|
324
|
+
*
|
|
325
|
+
* @ReactMethod
|
|
326
|
+
* fun enableFlagSecure(enabled: Boolean, promise: Promise) {
|
|
327
|
+
* try {
|
|
328
|
+
* currentActivity?.runOnUiThread {
|
|
329
|
+
* val window = currentActivity?.window
|
|
330
|
+
* if (enabled) {
|
|
331
|
+
* window?.setFlags(
|
|
332
|
+
* WindowManager.LayoutParams.FLAG_SECURE,
|
|
333
|
+
* WindowManager.LayoutParams.FLAG_SECURE
|
|
334
|
+
* )
|
|
335
|
+
* } else {
|
|
336
|
+
* window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
|
337
|
+
* }
|
|
338
|
+
* }
|
|
339
|
+
* promise.resolve(true)
|
|
340
|
+
* } catch (e: Exception) {
|
|
341
|
+
* promise.reject("FLAG_SECURE_ERROR", e.message)
|
|
342
|
+
* }
|
|
343
|
+
* }
|
|
344
|
+
*
|
|
345
|
+
* @ReactMethod
|
|
346
|
+
* fun initializeWidevine(config: ReadableMap, promise: Promise) {
|
|
347
|
+
* try {
|
|
348
|
+
* mediaDrm = MediaDrm(WIDEVINE_UUID)
|
|
349
|
+
*
|
|
350
|
+
* val securityLevel = mediaDrm?.getPropertyString("securityLevel")
|
|
351
|
+
* val hdcpLevel = mediaDrm?.getPropertyString("hdcpLevel")
|
|
352
|
+
*
|
|
353
|
+
* val result = Arguments.createMap().apply {
|
|
354
|
+
* putBoolean("success", true)
|
|
355
|
+
* putString("securityLevel", securityLevel)
|
|
356
|
+
* putString("hdcpLevel", hdcpLevel)
|
|
357
|
+
* }
|
|
358
|
+
*
|
|
359
|
+
* promise.resolve(result)
|
|
360
|
+
* } catch (e: Exception) {
|
|
361
|
+
* promise.reject("WIDEVINE_ERROR", e.message)
|
|
362
|
+
* }
|
|
363
|
+
* }
|
|
364
|
+
*
|
|
365
|
+
* @ReactMethod
|
|
366
|
+
* fun startMirroringDetection(promise: Promise) {
|
|
367
|
+
* try {
|
|
368
|
+
* displayManager = reactApplicationContext
|
|
369
|
+
* .getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
|
370
|
+
*
|
|
371
|
+
* val callback = object : DisplayManager.DisplayListener {
|
|
372
|
+
* override fun onDisplayAdded(displayId: Int) {
|
|
373
|
+
* val display = displayManager?.getDisplay(displayId)
|
|
374
|
+
* if (display != null && display.displayId != Display.DEFAULT_DISPLAY) {
|
|
375
|
+
* // External display detected - could be mirroring
|
|
376
|
+
* sendEvent("onMirroringDetected", Arguments.createMap().apply {
|
|
377
|
+
* putInt("displayId", displayId)
|
|
378
|
+
* putString("displayName", display.name)
|
|
379
|
+
* })
|
|
380
|
+
* }
|
|
381
|
+
* }
|
|
382
|
+
*
|
|
383
|
+
* override fun onDisplayRemoved(displayId: Int) {}
|
|
384
|
+
* override fun onDisplayChanged(displayId: Int) {}
|
|
385
|
+
* }
|
|
386
|
+
*
|
|
387
|
+
* displayManager?.registerDisplayListener(callback, null)
|
|
388
|
+
* promise.resolve(true)
|
|
389
|
+
* } catch (e: Exception) {
|
|
390
|
+
* promise.reject("MIRRORING_DETECTION_ERROR", e.message)
|
|
391
|
+
* }
|
|
392
|
+
* }
|
|
393
|
+
*
|
|
394
|
+
* @ReactMethod
|
|
395
|
+
* fun initializeChromecast(config: ReadableMap, promise: Promise) {
|
|
396
|
+
* try {
|
|
397
|
+
* // Initialize Google Cast
|
|
398
|
+
* CastContext.getSharedInstance(reactApplicationContext)
|
|
399
|
+
* promise.resolve(true)
|
|
400
|
+
* } catch (e: Exception) {
|
|
401
|
+
* promise.reject("CHROMECAST_ERROR", e.message)
|
|
402
|
+
* }
|
|
403
|
+
* }
|
|
404
|
+
*
|
|
405
|
+
* private fun sendEvent(eventName: String, params: WritableMap) {
|
|
406
|
+
* reactApplicationContext
|
|
407
|
+
* .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
408
|
+
* .emit(eventName, params)
|
|
409
|
+
* }
|
|
410
|
+
* }
|
|
411
|
+
* ```
|
|
412
|
+
*
|
|
413
|
+
* MANIFEST PERMISSIONS:
|
|
414
|
+
* ```xml
|
|
415
|
+
* <uses-permission android:name="android.permission.INTERNET" />
|
|
416
|
+
* <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
417
|
+
* <uses-permission android:name="com.google.android.gms.permission.AD_ID" />
|
|
418
|
+
* ```
|
|
419
|
+
*/
|