unified-video-framework 1.4.381 → 1.4.383
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 +9 -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 +14 -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 +9709 -9644
- package/packages/web/dist/WebPlayer.js.map +1 -1
- 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 +3 -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/ScreenProtectionController.d.ts +31 -0
- package/packages/web/dist/security/ScreenProtectionController.d.ts.map +1 -0
- package/packages/web/dist/security/ScreenProtectionController.js +193 -0
- package/packages/web/dist/security/ScreenProtectionController.js.map +1 -0
- package/packages/web/src/WebPlayer.ts +87 -2
- package/packages/web/src/index.ts +0 -3
- package/packages/web/src/react/WebPlayerView.tsx +5 -0
- package/packages/web/src/security/ScreenProtectionController.ts +276 -0
- package/scripts/fix-imports.js +22 -12
- 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,285 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Netflix-Like DRM Protection Interface
|
|
3
|
-
*
|
|
4
|
-
* Provides comprehensive content protection against:
|
|
5
|
-
* - Screen recording
|
|
6
|
-
* - Audio capture
|
|
7
|
-
* - Screenshots
|
|
8
|
-
*
|
|
9
|
-
* While allowing:
|
|
10
|
-
* - Official casting (Chromecast, AirPlay, Smart TVs)
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* DRM Protection Configuration
|
|
15
|
-
* All features can be enabled/disabled via boolean flags
|
|
16
|
-
*/
|
|
17
|
-
export interface IDRMProtectionConfig {
|
|
18
|
-
/**
|
|
19
|
-
* Master switch - enables/disables all DRM protection
|
|
20
|
-
* @default false
|
|
21
|
-
*/
|
|
22
|
-
enabled: boolean;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Block screen recording (video capture)
|
|
26
|
-
* @default true (when enabled)
|
|
27
|
-
*/
|
|
28
|
-
blockScreenRecording?: boolean;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Block audio capture (system audio recording)
|
|
32
|
-
* @default true (when enabled)
|
|
33
|
-
*/
|
|
34
|
-
blockAudioCapture?: boolean;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Block screenshots
|
|
38
|
-
* @default true (when enabled)
|
|
39
|
-
*/
|
|
40
|
-
blockScreenshots?: boolean;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Allow official casting (Chromecast, AirPlay, Smart TVs)
|
|
44
|
-
* @default true (when enabled)
|
|
45
|
-
*/
|
|
46
|
-
allowCasting?: boolean;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Block display mirroring (prevent screen sharing apps)
|
|
50
|
-
* @default true (when enabled)
|
|
51
|
-
*/
|
|
52
|
-
blockMirroring?: boolean;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* DRM license server URL
|
|
56
|
-
* Required for Widevine/PlayReady/FairPlay
|
|
57
|
-
*/
|
|
58
|
-
licenseServerUrl?: string;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* DRM certificate URL (for FairPlay)
|
|
62
|
-
*/
|
|
63
|
-
certificateUrl?: string;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Custom headers for license requests
|
|
67
|
-
*/
|
|
68
|
-
licenseHeaders?: Record<string, string>;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Widevine security level (Android)
|
|
72
|
-
* L1 = Hardware-backed DRM (most secure)
|
|
73
|
-
* L3 = Software DRM
|
|
74
|
-
* @default 'L1'
|
|
75
|
-
*/
|
|
76
|
-
widevineSecurityLevel?: 'L1' | 'L3';
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Callback when screen recording is detected
|
|
80
|
-
*/
|
|
81
|
-
onScreenRecordingDetected?: () => void;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Callback when screenshot is attempted
|
|
85
|
-
*/
|
|
86
|
-
onScreenshotAttempted?: () => void;
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Callback when mirroring is detected
|
|
90
|
-
*/
|
|
91
|
-
onMirroringDetected?: () => void;
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Callback when casting starts
|
|
95
|
-
*/
|
|
96
|
-
onCastingStarted?: (deviceName: string, deviceType: CastDeviceType) => void;
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Callback when casting ends
|
|
100
|
-
*/
|
|
101
|
-
onCastingEnded?: () => void;
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Callback when DRM license is acquired
|
|
105
|
-
*/
|
|
106
|
-
onLicenseAcquired?: () => void;
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Callback when DRM error occurs
|
|
110
|
-
*/
|
|
111
|
-
onDRMError?: (error: DRMError) => void;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Cast device types
|
|
116
|
-
*/
|
|
117
|
-
export type CastDeviceType = 'chromecast' | 'airplay' | 'smart_tv' | 'dlna' | 'miracast';
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* DRM Error types
|
|
121
|
-
*/
|
|
122
|
-
export interface DRMError {
|
|
123
|
-
code: DRMErrorCode;
|
|
124
|
-
message: string;
|
|
125
|
-
platform: 'web' | 'android' | 'ios';
|
|
126
|
-
recoverable: boolean;
|
|
127
|
-
details?: any;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export enum DRMErrorCode {
|
|
131
|
-
LICENSE_REQUEST_FAILED = 'LICENSE_REQUEST_FAILED',
|
|
132
|
-
LICENSE_SERVER_UNREACHABLE = 'LICENSE_SERVER_UNREACHABLE',
|
|
133
|
-
INVALID_LICENSE = 'INVALID_LICENSE',
|
|
134
|
-
DEVICE_NOT_SUPPORTED = 'DEVICE_NOT_SUPPORTED',
|
|
135
|
-
WIDEVINE_NOT_AVAILABLE = 'WIDEVINE_NOT_AVAILABLE',
|
|
136
|
-
FAIRPLAY_NOT_AVAILABLE = 'FAIRPLAY_NOT_AVAILABLE',
|
|
137
|
-
PLAYREADY_NOT_AVAILABLE = 'PLAYREADY_NOT_AVAILABLE',
|
|
138
|
-
HARDWARE_DRM_NOT_AVAILABLE = 'HARDWARE_DRM_NOT_AVAILABLE',
|
|
139
|
-
SCREEN_RECORDING_DETECTED = 'SCREEN_RECORDING_DETECTED',
|
|
140
|
-
MIRRORING_DETECTED = 'MIRRORING_DETECTED',
|
|
141
|
-
HDMI_OUTPUT_BLOCKED = 'HDMI_OUTPUT_BLOCKED',
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* DRM Protection Status
|
|
146
|
-
*/
|
|
147
|
-
export interface IDRMProtectionStatus {
|
|
148
|
-
/**
|
|
149
|
-
* Is DRM protection active
|
|
150
|
-
*/
|
|
151
|
-
isProtected: boolean;
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Active DRM system (Widevine, FairPlay, PlayReady)
|
|
155
|
-
*/
|
|
156
|
-
drmSystem: 'widevine' | 'fairplay' | 'playready' | 'none';
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Is screen recording currently blocked
|
|
160
|
-
*/
|
|
161
|
-
isScreenRecordingBlocked: boolean;
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Is audio capture currently blocked
|
|
165
|
-
*/
|
|
166
|
-
isAudioCaptureBlocked: boolean;
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Is screenshot currently blocked
|
|
170
|
-
*/
|
|
171
|
-
isScreenshotBlocked: boolean;
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Is casting active
|
|
175
|
-
*/
|
|
176
|
-
isCasting: boolean;
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Current cast device (if casting)
|
|
180
|
-
*/
|
|
181
|
-
castDevice?: {
|
|
182
|
-
name: string;
|
|
183
|
-
type: CastDeviceType;
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Is screen recording detected (may still be attempting to record)
|
|
188
|
-
*/
|
|
189
|
-
screenRecordingDetected: boolean;
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Is mirroring detected
|
|
193
|
-
*/
|
|
194
|
-
mirroringDetected: boolean;
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* DRM security level (for Widevine)
|
|
198
|
-
*/
|
|
199
|
-
securityLevel?: 'L1' | 'L3';
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* License expiration time (if available)
|
|
203
|
-
*/
|
|
204
|
-
licenseExpiration?: Date;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* DRM Protection Controller Interface
|
|
209
|
-
*/
|
|
210
|
-
export interface IDRMProtection {
|
|
211
|
-
/**
|
|
212
|
-
* Initialize DRM protection
|
|
213
|
-
*/
|
|
214
|
-
initialize(config: IDRMProtectionConfig): Promise<void>;
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Get current protection status
|
|
218
|
-
*/
|
|
219
|
-
getStatus(): IDRMProtectionStatus;
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Enable/disable DRM protection
|
|
223
|
-
*/
|
|
224
|
-
setEnabled(enabled: boolean): void;
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Enable/disable specific protection feature
|
|
228
|
-
*/
|
|
229
|
-
setFeature(feature: keyof IDRMProtectionConfig, enabled: boolean): void;
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Start casting to device
|
|
233
|
-
*/
|
|
234
|
-
startCasting(deviceId: string): Promise<void>;
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Stop casting
|
|
238
|
-
*/
|
|
239
|
-
stopCasting(): Promise<void>;
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Get available cast devices
|
|
243
|
-
*/
|
|
244
|
-
getAvailableCastDevices(): Promise<CastDevice[]>;
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Renew DRM license
|
|
248
|
-
*/
|
|
249
|
-
renewLicense(): Promise<void>;
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Cleanup DRM resources
|
|
253
|
-
*/
|
|
254
|
-
dispose(): void;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Cast device information
|
|
259
|
-
*/
|
|
260
|
-
export interface CastDevice {
|
|
261
|
-
id: string;
|
|
262
|
-
name: string;
|
|
263
|
-
type: CastDeviceType;
|
|
264
|
-
isAvailable: boolean;
|
|
265
|
-
capabilities: {
|
|
266
|
-
supportsVideo: boolean;
|
|
267
|
-
supportsAudio: boolean;
|
|
268
|
-
supportsDRM: boolean;
|
|
269
|
-
maxResolution?: string;
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* DRM Key System Configuration
|
|
275
|
-
*/
|
|
276
|
-
export interface DRMKeySystemConfig {
|
|
277
|
-
keySystem: string;
|
|
278
|
-
licenseServerUrl: string;
|
|
279
|
-
certificateUrl?: string;
|
|
280
|
-
headers?: Record<string, string>;
|
|
281
|
-
audioRobustness?: string;
|
|
282
|
-
videoRobustness?: string;
|
|
283
|
-
persistentState?: 'required' | 'optional' | 'not-allowed';
|
|
284
|
-
distinctiveIdentifier?: 'required' | 'optional' | 'not-allowed';
|
|
285
|
-
}
|
|
@@ -1,419 +0,0 @@
|
|
|
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
|
-
*/
|