unified-video-framework 1.4.411 → 1.4.413
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/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +62 -6
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/dist/drm/BunnyCDNDRMProvider.d.ts +13 -0
- package/packages/web/dist/drm/BunnyCDNDRMProvider.d.ts.map +1 -0
- package/packages/web/dist/drm/BunnyCDNDRMProvider.js +65 -0
- package/packages/web/dist/drm/BunnyCDNDRMProvider.js.map +1 -0
- package/packages/web/dist/drm/DRMManager.d.ts +20 -0
- package/packages/web/dist/drm/DRMManager.d.ts.map +1 -0
- package/packages/web/dist/drm/DRMManager.js +126 -0
- package/packages/web/dist/drm/DRMManager.js.map +1 -0
- package/packages/web/dist/drm/FairPlayDRMHandler.d.ts +24 -0
- package/packages/web/dist/drm/FairPlayDRMHandler.d.ts.map +1 -0
- package/packages/web/dist/drm/FairPlayDRMHandler.js +190 -0
- package/packages/web/dist/drm/FairPlayDRMHandler.js.map +1 -0
- package/packages/web/dist/drm/WidevineDRMHandler.d.ts +21 -0
- package/packages/web/dist/drm/WidevineDRMHandler.d.ts.map +1 -0
- package/packages/web/dist/drm/WidevineDRMHandler.js +143 -0
- package/packages/web/dist/drm/WidevineDRMHandler.js.map +1 -0
- package/packages/web/dist/drm/index.d.ts +15 -0
- package/packages/web/dist/drm/index.d.ts.map +1 -0
- package/packages/web/dist/drm/index.js +13 -0
- package/packages/web/dist/drm/index.js.map +1 -0
- package/packages/web/dist/drm/providers/BunnyNetProvider.d.ts +19 -0
- package/packages/web/dist/drm/providers/BunnyNetProvider.d.ts.map +1 -0
- package/packages/web/dist/drm/providers/BunnyNetProvider.js +112 -0
- package/packages/web/dist/drm/providers/BunnyNetProvider.js.map +1 -0
- package/packages/web/dist/drm/providers/GenericProvider.d.ts +30 -0
- package/packages/web/dist/drm/providers/GenericProvider.d.ts.map +1 -0
- package/packages/web/dist/drm/providers/GenericProvider.js +102 -0
- package/packages/web/dist/drm/providers/GenericProvider.js.map +1 -0
- package/packages/web/dist/drm/systems/BaseDRM.d.ts +18 -0
- package/packages/web/dist/drm/systems/BaseDRM.d.ts.map +1 -0
- package/packages/web/dist/drm/systems/BaseDRM.js +29 -0
- package/packages/web/dist/drm/systems/BaseDRM.js.map +1 -0
- package/packages/web/dist/drm/systems/FairPlayDRM.d.ts +32 -0
- package/packages/web/dist/drm/systems/FairPlayDRM.d.ts.map +1 -0
- package/packages/web/dist/drm/systems/FairPlayDRM.js +198 -0
- package/packages/web/dist/drm/systems/FairPlayDRM.js.map +1 -0
- package/packages/web/dist/drm/systems/PlayReadyDRM.d.ts +9 -0
- package/packages/web/dist/drm/systems/PlayReadyDRM.d.ts.map +1 -0
- package/packages/web/dist/drm/systems/PlayReadyDRM.js +92 -0
- package/packages/web/dist/drm/systems/PlayReadyDRM.js.map +1 -0
- package/packages/web/dist/drm/systems/WidevineDRM.d.ts +9 -0
- package/packages/web/dist/drm/systems/WidevineDRM.d.ts.map +1 -0
- package/packages/web/dist/drm/systems/WidevineDRM.js +73 -0
- package/packages/web/dist/drm/systems/WidevineDRM.js.map +1 -0
- package/packages/web/dist/drm/types/BunnyNetTypes.d.ts +20 -0
- package/packages/web/dist/drm/types/BunnyNetTypes.d.ts.map +1 -0
- package/packages/web/dist/drm/types/BunnyNetTypes.js +8 -0
- package/packages/web/dist/drm/types/BunnyNetTypes.js.map +1 -0
- package/packages/web/dist/drm/types/DRMTypes.d.ts +59 -0
- package/packages/web/dist/drm/types/DRMTypes.d.ts.map +1 -0
- package/packages/web/dist/drm/types/DRMTypes.js +21 -0
- package/packages/web/dist/drm/types/DRMTypes.js.map +1 -0
- package/packages/web/dist/drm/utils/BrowserDetector.d.ts +20 -0
- package/packages/web/dist/drm/utils/BrowserDetector.d.ts.map +1 -0
- package/packages/web/dist/drm/utils/BrowserDetector.js +168 -0
- package/packages/web/dist/drm/utils/BrowserDetector.js.map +1 -0
- package/packages/web/dist/drm/utils/CertificateManager.d.ts +15 -0
- package/packages/web/dist/drm/utils/CertificateManager.d.ts.map +1 -0
- package/packages/web/dist/drm/utils/CertificateManager.js +46 -0
- package/packages/web/dist/drm/utils/CertificateManager.js.map +1 -0
- package/packages/web/dist/drm/utils/DRMErrorHandler.d.ts +7 -0
- package/packages/web/dist/drm/utils/DRMErrorHandler.d.ts.map +1 -0
- package/packages/web/dist/drm/utils/DRMErrorHandler.js +49 -0
- package/packages/web/dist/drm/utils/DRMErrorHandler.js.map +1 -0
- package/packages/web/dist/drm/utils/LicenseRequestHandler.d.ts +15 -0
- package/packages/web/dist/drm/utils/LicenseRequestHandler.d.ts.map +1 -0
- package/packages/web/dist/drm/utils/LicenseRequestHandler.js +110 -0
- package/packages/web/dist/drm/utils/LicenseRequestHandler.js.map +1 -0
- package/packages/web/src/WebPlayer.ts +80 -12
- package/packages/web/src/drm/DRMManager.ts +213 -0
- package/packages/web/src/drm/index.ts +37 -0
- package/packages/web/src/drm/providers/BunnyNetProvider.ts +170 -0
- package/packages/web/src/drm/providers/GenericProvider.ts +148 -0
- package/packages/web/src/drm/systems/BaseDRM.ts +68 -0
- package/packages/web/src/drm/systems/FairPlayDRM.ts +305 -0
- package/packages/web/src/drm/systems/PlayReadyDRM.ts +131 -0
- package/packages/web/src/drm/systems/WidevineDRM.ts +105 -0
- package/packages/web/src/drm/types/BunnyNetTypes.ts +35 -0
- package/packages/web/src/drm/types/DRMTypes.ts +91 -0
- package/packages/web/src/drm/utils/BrowserDetector.ts +232 -0
- package/packages/web/src/drm/utils/CertificateManager.ts +86 -0
- package/packages/web/src/drm/utils/DRMErrorHandler.ts +84 -0
- package/packages/web/src/drm/utils/LicenseRequestHandler.ts +180 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlayReady DRM Implementation
|
|
3
|
+
* Implements PlayReady DRM for Edge and IE
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BaseDRM } from './BaseDRM';
|
|
7
|
+
import { KEY_SYSTEMS, DRMErrorCode } from '../types/DRMTypes';
|
|
8
|
+
import { DRMType } from '@unified-video/core';
|
|
9
|
+
import { DRMErrorHandler } from '../utils/DRMErrorHandler';
|
|
10
|
+
|
|
11
|
+
export class PlayReadyDRM extends BaseDRM {
|
|
12
|
+
async initialize(): Promise<void> {
|
|
13
|
+
this.log('Initializing PlayReady DRM...');
|
|
14
|
+
|
|
15
|
+
// PlayReady is primarily configured through HLS.js and dash.js
|
|
16
|
+
// Basic EME setup for validation
|
|
17
|
+
try {
|
|
18
|
+
const keySystemAccess = await navigator.requestMediaKeySystemAccess(
|
|
19
|
+
this.getKeySystem(),
|
|
20
|
+
this.getKeySystemConfiguration()
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
this.log('PlayReady key system access granted');
|
|
24
|
+
this.mediaKeys = await keySystemAccess.createMediaKeys();
|
|
25
|
+
|
|
26
|
+
// For HLS.js and dash.js, they handle the actual EME setup
|
|
27
|
+
// We just validate that the system is available
|
|
28
|
+
|
|
29
|
+
} catch (error: any) {
|
|
30
|
+
// Try fallback key system
|
|
31
|
+
try {
|
|
32
|
+
const fallbackKeySystem = KEY_SYSTEMS[DRMType.PLAYREADY][1];
|
|
33
|
+
const keySystemAccess = await navigator.requestMediaKeySystemAccess(
|
|
34
|
+
fallbackKeySystem,
|
|
35
|
+
this.getKeySystemConfiguration()
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
this.log('PlayReady fallback key system access granted');
|
|
39
|
+
this.mediaKeys = await keySystemAccess.createMediaKeys();
|
|
40
|
+
} catch (fallbackError: any) {
|
|
41
|
+
this.log('PlayReady initialization failed:', fallbackError);
|
|
42
|
+
throw DRMErrorHandler.createError(
|
|
43
|
+
DRMErrorCode.KEY_SYSTEM_ACCESS_DENIED,
|
|
44
|
+
'Failed to access PlayReady DRM system',
|
|
45
|
+
true,
|
|
46
|
+
fallbackError
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.log('PlayReady DRM initialized');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getKeySystem(): string {
|
|
55
|
+
return KEY_SYSTEMS[DRMType.PLAYREADY][0];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getHLSConfig(): any {
|
|
59
|
+
this.log('Generating HLS.js config for PlayReady');
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
emeEnabled: true,
|
|
63
|
+
drmSystems: {
|
|
64
|
+
[this.getKeySystem()]: {
|
|
65
|
+
licenseUrl: this.config.licenseUrl || this.config.playreadyOptions?.licenseUrl,
|
|
66
|
+
customData: this.config.customData || this.config.playreadyOptions?.customData,
|
|
67
|
+
},
|
|
68
|
+
// Add fallback key system
|
|
69
|
+
[KEY_SYSTEMS[DRMType.PLAYREADY][1]]: {
|
|
70
|
+
licenseUrl: this.config.licenseUrl || this.config.playreadyOptions?.licenseUrl,
|
|
71
|
+
customData: this.config.customData || this.config.playreadyOptions?.customData,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
requestMediaKeySystemAccessFunc: navigator.requestMediaKeySystemAccess.bind(navigator),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getDashProtectionData(): any {
|
|
79
|
+
this.log('Generating dash.js protection data for PlayReady');
|
|
80
|
+
|
|
81
|
+
const licenseUrl = this.config.licenseUrl || this.config.playreadyOptions?.licenseUrl;
|
|
82
|
+
|
|
83
|
+
const protectionData: any = {
|
|
84
|
+
[this.getKeySystem()]: {
|
|
85
|
+
serverURL: licenseUrl,
|
|
86
|
+
},
|
|
87
|
+
// Add fallback key system
|
|
88
|
+
[KEY_SYSTEMS[DRMType.PLAYREADY][1]]: {
|
|
89
|
+
serverURL: licenseUrl,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Add custom headers if provided
|
|
94
|
+
if (this.config.headers && Object.keys(this.config.headers).length > 0) {
|
|
95
|
+
protectionData[this.getKeySystem()].httpRequestHeaders = this.config.headers;
|
|
96
|
+
protectionData[KEY_SYSTEMS[DRMType.PLAYREADY][1]].httpRequestHeaders = this.config.headers;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Add custom data if provided
|
|
100
|
+
const customData = this.config.customData || this.config.playreadyOptions?.customData;
|
|
101
|
+
if (customData) {
|
|
102
|
+
protectionData[this.getKeySystem()].customData = customData;
|
|
103
|
+
protectionData[KEY_SYSTEMS[DRMType.PLAYREADY][1]].customData = customData;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return protectionData;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private getKeySystemConfiguration(): MediaKeySystemConfiguration[] {
|
|
110
|
+
return [{
|
|
111
|
+
initDataTypes: ['cenc'],
|
|
112
|
+
audioCapabilities: [{
|
|
113
|
+
contentType: 'audio/mp4; codecs="mp4a.40.2"',
|
|
114
|
+
robustness: 'SW_SECURE_CRYPTO',
|
|
115
|
+
}],
|
|
116
|
+
videoCapabilities: [
|
|
117
|
+
{
|
|
118
|
+
contentType: 'video/mp4; codecs="avc1.42E01E"',
|
|
119
|
+
robustness: 'SW_SECURE_CRYPTO',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
contentType: 'video/mp4; codecs="avc1.4d401f"',
|
|
123
|
+
robustness: 'SW_SECURE_CRYPTO',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
distinctiveIdentifier: 'not-allowed',
|
|
127
|
+
persistentState: 'not-allowed',
|
|
128
|
+
sessionTypes: ['temporary'],
|
|
129
|
+
}];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Widevine DRM Implementation
|
|
3
|
+
* Implements Widevine DRM for Chrome, Firefox, Edge, Android
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BaseDRM } from './BaseDRM';
|
|
7
|
+
import { KEY_SYSTEMS, DRMErrorCode } from '../types/DRMTypes';
|
|
8
|
+
import { DRMType } from '@unified-video/core';
|
|
9
|
+
import { DRMErrorHandler } from '../utils/DRMErrorHandler';
|
|
10
|
+
|
|
11
|
+
export class WidevineDRM extends BaseDRM {
|
|
12
|
+
async initialize(): Promise<void> {
|
|
13
|
+
this.log('Initializing Widevine DRM...');
|
|
14
|
+
|
|
15
|
+
// Widevine is primarily configured through HLS.js and dash.js
|
|
16
|
+
// Basic EME setup for validation
|
|
17
|
+
try {
|
|
18
|
+
const keySystemAccess = await navigator.requestMediaKeySystemAccess(
|
|
19
|
+
this.getKeySystem(),
|
|
20
|
+
this.getKeySystemConfiguration()
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
this.log('Widevine key system access granted');
|
|
24
|
+
this.mediaKeys = await keySystemAccess.createMediaKeys();
|
|
25
|
+
|
|
26
|
+
// For HLS.js and dash.js, they handle the actual EME setup
|
|
27
|
+
// We just validate that the system is available
|
|
28
|
+
|
|
29
|
+
} catch (error: any) {
|
|
30
|
+
this.log('Widevine initialization failed:', error);
|
|
31
|
+
throw DRMErrorHandler.createError(
|
|
32
|
+
DRMErrorCode.KEY_SYSTEM_ACCESS_DENIED,
|
|
33
|
+
'Failed to access Widevine DRM system',
|
|
34
|
+
true,
|
|
35
|
+
error
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.log('Widevine DRM initialized');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getKeySystem(): string {
|
|
43
|
+
return KEY_SYSTEMS[DRMType.WIDEVINE][0];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getHLSConfig(): any {
|
|
47
|
+
this.log('Generating HLS.js config for Widevine');
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
emeEnabled: true,
|
|
51
|
+
drmSystems: {
|
|
52
|
+
[this.getKeySystem()]: {
|
|
53
|
+
licenseUrl: this.config.licenseUrl || this.config.widevineOptions?.licenseUrl,
|
|
54
|
+
serverCertificate: this.config.widevineOptions?.serverCertificate,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
requestMediaKeySystemAccessFunc: navigator.requestMediaKeySystemAccess.bind(navigator),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getDashProtectionData(): any {
|
|
62
|
+
this.log('Generating dash.js protection data for Widevine');
|
|
63
|
+
|
|
64
|
+
const protectionData: any = {
|
|
65
|
+
[this.getKeySystem()]: {
|
|
66
|
+
serverURL: this.config.licenseUrl || this.config.widevineOptions?.licenseUrl,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Add custom headers if provided
|
|
71
|
+
if (this.config.headers && Object.keys(this.config.headers).length > 0) {
|
|
72
|
+
protectionData[this.getKeySystem()].httpRequestHeaders = this.config.headers;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Add server certificate if provided
|
|
76
|
+
if (this.config.widevineOptions?.serverCertificate) {
|
|
77
|
+
protectionData[this.getKeySystem()].serverCertificate = this.config.widevineOptions.serverCertificate;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return protectionData;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private getKeySystemConfiguration(): MediaKeySystemConfiguration[] {
|
|
84
|
+
return [{
|
|
85
|
+
initDataTypes: ['cenc'],
|
|
86
|
+
audioCapabilities: [{
|
|
87
|
+
contentType: 'audio/mp4; codecs="mp4a.40.2"',
|
|
88
|
+
robustness: 'SW_SECURE_CRYPTO',
|
|
89
|
+
}],
|
|
90
|
+
videoCapabilities: [
|
|
91
|
+
{
|
|
92
|
+
contentType: 'video/mp4; codecs="avc1.42E01E"',
|
|
93
|
+
robustness: 'SW_SECURE_CRYPTO',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
contentType: 'video/mp4; codecs="avc1.4d401f"',
|
|
97
|
+
robustness: 'SW_SECURE_CRYPTO',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
distinctiveIdentifier: 'not-allowed',
|
|
101
|
+
persistentState: 'not-allowed',
|
|
102
|
+
sessionTypes: ['temporary'],
|
|
103
|
+
}];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bunny.net DRM Type Definitions
|
|
3
|
+
* Type definitions specific to Bunny.net MediaCage DRM integration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DRMType } from '@unified-video/core';
|
|
7
|
+
|
|
8
|
+
export interface BunnyNetConfig {
|
|
9
|
+
libraryId: string;
|
|
10
|
+
videoId: string;
|
|
11
|
+
drmType?: DRMType;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface BunnyNetEndpoints {
|
|
15
|
+
widevine: string;
|
|
16
|
+
fairplayCert: string;
|
|
17
|
+
fairplayLicense: string;
|
|
18
|
+
playready: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const BUNNY_NET_BASE_URL = 'https://video.bunnycdn.com';
|
|
22
|
+
|
|
23
|
+
export const BUNNY_NET_ENDPOINTS = {
|
|
24
|
+
WIDEVINE_LICENSE: (libraryId: string, videoId: string) =>
|
|
25
|
+
`${BUNNY_NET_BASE_URL}/WidevineLicense/${libraryId}/${videoId}`,
|
|
26
|
+
|
|
27
|
+
FAIRPLAY_CERTIFICATE: (libraryId: string) =>
|
|
28
|
+
`${BUNNY_NET_BASE_URL}/FairPlay/${libraryId}/certificate`,
|
|
29
|
+
|
|
30
|
+
FAIRPLAY_LICENSE: (libraryId: string, videoId: string) =>
|
|
31
|
+
`${BUNNY_NET_BASE_URL}/FairPlay/${libraryId}/license/?videoId=${videoId}`,
|
|
32
|
+
|
|
33
|
+
PLAYREADY_LICENSE: (libraryId: string, videoId: string) =>
|
|
34
|
+
`${BUNNY_NET_BASE_URL}/PlayReadyLicense/${libraryId}/${videoId}`,
|
|
35
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DRM Type Definitions
|
|
3
|
+
* Core type definitions and enums for DRM functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DRMConfig, DRMType } from '@unified-video/core';
|
|
7
|
+
|
|
8
|
+
// Extended DRM configuration for internal use
|
|
9
|
+
export interface ExtendedDRMConfig extends DRMConfig {
|
|
10
|
+
// Provider-specific settings
|
|
11
|
+
provider?: 'bunny' | 'generic' | string;
|
|
12
|
+
|
|
13
|
+
// Library/Video IDs for Bunny.net
|
|
14
|
+
libraryId?: string;
|
|
15
|
+
videoId?: string;
|
|
16
|
+
|
|
17
|
+
// Advanced options
|
|
18
|
+
retryConfig?: {
|
|
19
|
+
maxRetries: number;
|
|
20
|
+
retryDelay: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Debugging
|
|
24
|
+
debug?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Browser DRM capabilities
|
|
28
|
+
export interface DRMCapabilities {
|
|
29
|
+
widevine: boolean;
|
|
30
|
+
fairplay: boolean;
|
|
31
|
+
playready: boolean;
|
|
32
|
+
supportedType: DRMType | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// License request/response types
|
|
36
|
+
export interface LicenseRequest {
|
|
37
|
+
url: string;
|
|
38
|
+
method: 'POST' | 'GET';
|
|
39
|
+
headers: Record<string, string>;
|
|
40
|
+
body: ArrayBuffer | string;
|
|
41
|
+
responseType: 'arraybuffer' | 'text' | 'json';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface LicenseResponse {
|
|
45
|
+
license: ArrayBuffer;
|
|
46
|
+
metadata?: any;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// EME event types
|
|
50
|
+
export interface EMEMessageEvent {
|
|
51
|
+
messageType: string;
|
|
52
|
+
message: ArrayBuffer;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// DRM initialization result
|
|
56
|
+
export interface DRMInitResult {
|
|
57
|
+
success: boolean;
|
|
58
|
+
drmType: DRMType;
|
|
59
|
+
keySystem: string;
|
|
60
|
+
error?: DRMError;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// DRM-specific errors
|
|
64
|
+
export interface DRMError {
|
|
65
|
+
code: DRMErrorCode;
|
|
66
|
+
message: string;
|
|
67
|
+
fatal: boolean;
|
|
68
|
+
details?: any;
|
|
69
|
+
systemError?: any;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export enum DRMErrorCode {
|
|
73
|
+
UNSUPPORTED_BROWSER = 'DRM_UNSUPPORTED_BROWSER',
|
|
74
|
+
CERTIFICATE_LOAD_FAILED = 'DRM_CERTIFICATE_LOAD_FAILED',
|
|
75
|
+
LICENSE_REQUEST_FAILED = 'DRM_LICENSE_REQUEST_FAILED',
|
|
76
|
+
KEY_SYSTEM_ACCESS_DENIED = 'DRM_KEY_SYSTEM_ACCESS_DENIED',
|
|
77
|
+
MEDIA_KEYS_CREATION_FAILED = 'DRM_MEDIA_KEYS_CREATION_FAILED',
|
|
78
|
+
SESSION_CREATION_FAILED = 'DRM_SESSION_CREATION_FAILED',
|
|
79
|
+
LICENSE_INVALID = 'DRM_LICENSE_INVALID',
|
|
80
|
+
CONFIGURATION_ERROR = 'DRM_CONFIGURATION_ERROR',
|
|
81
|
+
NETWORK_ERROR = 'DRM_NETWORK_ERROR',
|
|
82
|
+
TIMEOUT = 'DRM_TIMEOUT',
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Key system mappings
|
|
86
|
+
export const KEY_SYSTEMS: Record<DRMType, string[]> = {
|
|
87
|
+
[DRMType.WIDEVINE]: ['com.widevine.alpha'],
|
|
88
|
+
[DRMType.PLAYREADY]: ['com.microsoft.playready', 'com.microsoft.playready.recommendation'],
|
|
89
|
+
[DRMType.FAIRPLAY]: ['com.apple.fps.1_0', 'com.apple.fps'],
|
|
90
|
+
[DRMType.CLEARKEY]: ['webkit-org.w3.clearkey', 'org.w3.clearkey'],
|
|
91
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser DRM Detector
|
|
3
|
+
* Detects browser capabilities and determines optimal DRM system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DRMType } from '@unified-video/core';
|
|
7
|
+
import { DRMCapabilities, KEY_SYSTEMS } from '../types/DRMTypes';
|
|
8
|
+
|
|
9
|
+
export class BrowserDetector {
|
|
10
|
+
private static instance: BrowserDetector;
|
|
11
|
+
private capabilities: DRMCapabilities | null = null;
|
|
12
|
+
|
|
13
|
+
private constructor() {}
|
|
14
|
+
|
|
15
|
+
static getInstance(): BrowserDetector {
|
|
16
|
+
if (!BrowserDetector.instance) {
|
|
17
|
+
BrowserDetector.instance = new BrowserDetector();
|
|
18
|
+
}
|
|
19
|
+
return BrowserDetector.instance;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Detect DRM capabilities of current browser
|
|
24
|
+
*/
|
|
25
|
+
async detectCapabilities(): Promise<DRMCapabilities> {
|
|
26
|
+
if (this.capabilities) {
|
|
27
|
+
return this.capabilities;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const capabilities: DRMCapabilities = {
|
|
31
|
+
widevine: false,
|
|
32
|
+
fairplay: false,
|
|
33
|
+
playready: false,
|
|
34
|
+
supportedType: null,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Check for FairPlay (Safari/iOS) - highest priority on Apple devices
|
|
38
|
+
if (this.isSafari() || this.isWebKitBased()) {
|
|
39
|
+
capabilities.fairplay = await this.checkFairPlaySupport();
|
|
40
|
+
if (capabilities.fairplay) {
|
|
41
|
+
capabilities.supportedType = DRMType.FAIRPLAY;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check for Widevine (Chrome, Firefox, Edge)
|
|
46
|
+
if (!capabilities.supportedType) {
|
|
47
|
+
capabilities.widevine = await this.checkWidevineSupport();
|
|
48
|
+
if (capabilities.widevine) {
|
|
49
|
+
capabilities.supportedType = DRMType.WIDEVINE;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check for PlayReady (Edge, IE)
|
|
54
|
+
if (!capabilities.supportedType && this.isEdge()) {
|
|
55
|
+
capabilities.playready = await this.checkPlayReadySupport();
|
|
56
|
+
if (capabilities.playready) {
|
|
57
|
+
capabilities.supportedType = DRMType.PLAYREADY;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.capabilities = capabilities;
|
|
62
|
+
return capabilities;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get recommended DRM type for current browser
|
|
67
|
+
*/
|
|
68
|
+
async getRecommendedDRMType(): Promise<DRMType | null> {
|
|
69
|
+
const caps = await this.detectCapabilities();
|
|
70
|
+
return caps.supportedType;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check if specific DRM type is supported
|
|
75
|
+
*/
|
|
76
|
+
async isDRMTypeSupported(drmType: DRMType): Promise<boolean> {
|
|
77
|
+
const caps = await this.detectCapabilities();
|
|
78
|
+
switch (drmType) {
|
|
79
|
+
case DRMType.WIDEVINE:
|
|
80
|
+
return caps.widevine;
|
|
81
|
+
case DRMType.FAIRPLAY:
|
|
82
|
+
return caps.fairplay;
|
|
83
|
+
case DRMType.PLAYREADY:
|
|
84
|
+
return caps.playready;
|
|
85
|
+
default:
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if browser is Safari
|
|
92
|
+
*/
|
|
93
|
+
private isSafari(): boolean {
|
|
94
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
95
|
+
return ua.includes('safari') && !ua.includes('chrome') && !ua.includes('chromium');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Check if browser is WebKit-based
|
|
100
|
+
*/
|
|
101
|
+
private isWebKitBased(): boolean {
|
|
102
|
+
return 'WebKitMediaKeys' in window;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if browser is Edge
|
|
107
|
+
*/
|
|
108
|
+
private isEdge(): boolean {
|
|
109
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
110
|
+
return ua.includes('edg/');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check FairPlay support
|
|
115
|
+
*/
|
|
116
|
+
private async checkFairPlaySupport(): Promise<boolean> {
|
|
117
|
+
try {
|
|
118
|
+
// Check for WebKit-specific FairPlay API
|
|
119
|
+
if ('WebKitMediaKeys' in window) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check via EME API
|
|
124
|
+
if (navigator.requestMediaKeySystemAccess) {
|
|
125
|
+
const config: MediaKeySystemConfiguration[] = [{
|
|
126
|
+
initDataTypes: ['sinf', 'skd'],
|
|
127
|
+
videoCapabilities: [{ contentType: 'application/vnd.apple.mpegurl' }],
|
|
128
|
+
}];
|
|
129
|
+
|
|
130
|
+
for (const keySystem of KEY_SYSTEMS[DRMType.FAIRPLAY]) {
|
|
131
|
+
try {
|
|
132
|
+
await navigator.requestMediaKeySystemAccess(keySystem, config);
|
|
133
|
+
return true;
|
|
134
|
+
} catch (e) {
|
|
135
|
+
// Try next key system
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return false;
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Check Widevine support
|
|
149
|
+
*/
|
|
150
|
+
private async checkWidevineSupport(): Promise<boolean> {
|
|
151
|
+
try {
|
|
152
|
+
if (!navigator.requestMediaKeySystemAccess) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const config: MediaKeySystemConfiguration[] = [{
|
|
157
|
+
initDataTypes: ['cenc'],
|
|
158
|
+
videoCapabilities: [{
|
|
159
|
+
contentType: 'video/mp4; codecs="avc1.42E01E"',
|
|
160
|
+
}],
|
|
161
|
+
}];
|
|
162
|
+
|
|
163
|
+
for (const keySystem of KEY_SYSTEMS[DRMType.WIDEVINE]) {
|
|
164
|
+
try {
|
|
165
|
+
await navigator.requestMediaKeySystemAccess(keySystem, config);
|
|
166
|
+
return true;
|
|
167
|
+
} catch (e) {
|
|
168
|
+
// Try next key system
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return false;
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Check PlayReady support
|
|
181
|
+
*/
|
|
182
|
+
private async checkPlayReadySupport(): Promise<boolean> {
|
|
183
|
+
try {
|
|
184
|
+
if (!navigator.requestMediaKeySystemAccess) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const config: MediaKeySystemConfiguration[] = [{
|
|
189
|
+
initDataTypes: ['cenc'],
|
|
190
|
+
videoCapabilities: [{
|
|
191
|
+
contentType: 'video/mp4; codecs="avc1.42E01E"',
|
|
192
|
+
}],
|
|
193
|
+
}];
|
|
194
|
+
|
|
195
|
+
for (const keySystem of KEY_SYSTEMS[DRMType.PLAYREADY]) {
|
|
196
|
+
try {
|
|
197
|
+
await navigator.requestMediaKeySystemAccess(keySystem, config);
|
|
198
|
+
return true;
|
|
199
|
+
} catch (e) {
|
|
200
|
+
// Try next key system
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return false;
|
|
206
|
+
} catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get user-friendly browser name
|
|
213
|
+
*/
|
|
214
|
+
getBrowserName(): string {
|
|
215
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
216
|
+
|
|
217
|
+
if (ua.includes('edg/')) return 'Edge';
|
|
218
|
+
if (ua.includes('chrome') || ua.includes('chromium')) return 'Chrome';
|
|
219
|
+
if (ua.includes('firefox')) return 'Firefox';
|
|
220
|
+
if (ua.includes('safari')) return 'Safari';
|
|
221
|
+
if (ua.includes('opera') || ua.includes('opr/')) return 'Opera';
|
|
222
|
+
|
|
223
|
+
return 'Unknown';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Reset cached capabilities (useful for testing)
|
|
228
|
+
*/
|
|
229
|
+
resetCapabilities(): void {
|
|
230
|
+
this.capabilities = null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Certificate Manager
|
|
3
|
+
* Manages FairPlay DRM certificates with in-memory caching
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class CertificateManager {
|
|
7
|
+
private static instance: CertificateManager;
|
|
8
|
+
private cache: Map<string, ArrayBuffer>;
|
|
9
|
+
private maxCacheSize: number = 10; // Max number of certificates to cache
|
|
10
|
+
|
|
11
|
+
private constructor() {
|
|
12
|
+
this.cache = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static getInstance(): CertificateManager {
|
|
16
|
+
if (!CertificateManager.instance) {
|
|
17
|
+
CertificateManager.instance = new CertificateManager();
|
|
18
|
+
}
|
|
19
|
+
return CertificateManager.instance;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get certificate from cache
|
|
24
|
+
*/
|
|
25
|
+
getCertificate(url: string): ArrayBuffer | null {
|
|
26
|
+
return this.cache.get(url) || null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Store certificate in cache
|
|
31
|
+
*/
|
|
32
|
+
setCertificate(url: string, certificate: ArrayBuffer): void {
|
|
33
|
+
// Implement LRU eviction if cache is full
|
|
34
|
+
if (this.cache.size >= this.maxCacheSize && !this.cache.has(url)) {
|
|
35
|
+
const firstKey = this.cache.keys().next().value;
|
|
36
|
+
if (firstKey) {
|
|
37
|
+
this.cache.delete(firstKey);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.cache.set(url, certificate);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if certificate is cached
|
|
46
|
+
*/
|
|
47
|
+
hasCertificate(url: string): boolean {
|
|
48
|
+
return this.cache.has(url);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Clear all cached certificates
|
|
53
|
+
*/
|
|
54
|
+
clearCache(): void {
|
|
55
|
+
this.cache.clear();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Remove specific certificate from cache
|
|
60
|
+
*/
|
|
61
|
+
removeCertificate(url: string): boolean {
|
|
62
|
+
return this.cache.delete(url);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get cache size
|
|
67
|
+
*/
|
|
68
|
+
getCacheSize(): number {
|
|
69
|
+
return this.cache.size;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Set max cache size
|
|
74
|
+
*/
|
|
75
|
+
setMaxCacheSize(size: number): void {
|
|
76
|
+
this.maxCacheSize = size;
|
|
77
|
+
|
|
78
|
+
// Evict old entries if current size exceeds new max
|
|
79
|
+
while (this.cache.size > this.maxCacheSize) {
|
|
80
|
+
const firstKey = this.cache.keys().next().value;
|
|
81
|
+
if (firstKey) {
|
|
82
|
+
this.cache.delete(firstKey);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|