Skip to content

Commit 70268b6

Browse files
committed
added 'addCertUrls()' API to MediaPlayer for certificates aquisition. Moreover, added CertUrlUtils for normalizing and deduping cert urls.
1 parent 3a7f9a4 commit 70268b6

File tree

4 files changed

+115
-56
lines changed

4 files changed

+115
-56
lines changed

src/dash/vo/ContentProtection.js

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2929
* POSSIBILITY OF SUCH DAMAGE.
3030
*/
31+
import CertUrlUtils from '../../streaming/utils/CertUrlUtils';
3132
import DescriptorType from './DescriptorType.js'
3233
import DashConstants from '../constants/DashConstants.js';
3334

@@ -65,7 +66,7 @@ class ContentProtection extends DescriptorType {
6566
this.pro = data.hasOwnProperty(DashConstants.PRO) ? data[DashConstants.PRO] : null;
6667
this.laUrl = data.hasOwnProperty(DashConstants.LA_URL) ? data[DashConstants.LA_URL] : data.hasOwnProperty(DashConstants.LA_URL_LOWER_CASE) ? data[DashConstants.LA_URL_LOWER_CASE] : null;
6768
const rawCert = data[DashConstants.CERT_URL] || data[DashConstants.CERT_URL_LOWER_CASE];
68-
this.certUrls = ContentProtection._normalizeCertUrls(rawCert);
69+
this.certUrls = CertUrlUtils.normalizeCertUrls(rawCert);
6970
}
7071
}
7172

@@ -87,41 +88,6 @@ class ContentProtection extends DescriptorType {
8788
});
8889
}
8990
}
90-
91-
/**
92-
* Normalize CERT_URL data into an array of {url, certType|null} objects.
93-
* Handles string, object, or array input.
94-
* @param {string|object|array} raw
95-
* @returns {Array<{url: string, certType: string|null}>}
96-
* @private
97-
*/
98-
static _normalizeCertUrls(raw) {
99-
if (!raw) { return []; }
100-
const arr = Array.isArray(raw) ? raw : [raw];
101-
return arr.map(item => {
102-
if (!item) { return null; }
103-
if (typeof item === 'string') {
104-
const url = item.trim();
105-
return url ? { url, certType: null } : null;
106-
}
107-
if (typeof item === 'object') {
108-
let url = (item.__text || item.text || '').trim();
109-
if (!url && typeof item.url === 'string') { // fallback if pre-normalized
110-
url = item.url.trim();
111-
}
112-
let certType = item.certType || item['@certType'] || null;
113-
if (certType && typeof certType === 'string') {
114-
certType = certType.trim();
115-
if (certType === '') { certType = null; }
116-
} else {
117-
certType = null;
118-
}
119-
// We intentionally do not enforce any allowed vocabulary; unknown values are accepted as-is.
120-
return url ? { url, certType } : null;
121-
}
122-
return null;
123-
}).filter(Boolean);
124-
}
12591
}
12692

12793
export default ContentProtection;

src/streaming/MediaPlayer.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import ThroughputController from './controllers/ThroughputController.js';
7373
import TimelineConverter from '../dash/utils/TimelineConverter.js';
7474
import URIFragmentModel from './models/URIFragmentModel.js';
7575
import URLUtils from '../streaming/utils/URLUtils.js';
76+
import CertUrlUtils from './utils/CertUrlUtils.js';
7677
import VideoModel from './models/VideoModel.js';
7778
import {Cta608Parser} from '@svta/common-media-library/cta/608/Cta608Parser';
7879
import {HTTPRequest} from './vo/metrics/HTTPRequest.js';
@@ -2027,6 +2028,37 @@ function MediaPlayer() {
20272028
}
20282029
}
20292030

2031+
/**
2032+
* Adds one or multiple certificate URLs (Certurl) for a given key system. These URLs
2033+
* override (i.e. take priority over) manifest-provided Certurl entries when acquiring
2034+
* the server certificate. They are tried before manifest entries and in the provided order.
2035+
* Input may be a single string, an object {url, certType}, or an array of those.
2036+
* @param {string} keySystemString EME key system identifier e.g. 'com.widevine.alpha'
2037+
* @param {string|object|Array<string|object>} certUrls One or many certificate URL descriptors
2038+
* @example
2039+
* // Single URL
2040+
* player.addCertUrls('com.widevine.alpha', 'https://example.com/wv_cert');
2041+
* // Multiple with certType labels
2042+
* player.addCertUrls('com.widevine.alpha', [
2043+
* { url: 'https://example.com/wv_cert_primary', certType: 'primary' },
2044+
* { url: 'https://backup.example.com/wv_cert_backup', certType: 'backup' }
2045+
* ]);
2046+
*/
2047+
function addCertUrls(keySystemString, certUrls) {
2048+
if (!keySystemString || !certUrls) { return; }
2049+
const normalized = CertUrlUtils.normalizeCertUrls(certUrls);
2050+
if (!normalized.length) { return; }
2051+
// Ensure protectionData structure
2052+
protectionData = protectionData || {};
2053+
const existing = protectionData[keySystemString] || {};
2054+
const existingList = Array.isArray(existing.certUrls) ? existing.certUrls : [];
2055+
// Merge and dedupe via shared utility (existing first order preserved)
2056+
const merged = existingList.concat(normalized);
2057+
existing.certUrls = CertUrlUtils.dedupeCertUrls(merged);
2058+
protectionData[keySystemString] = existing;
2059+
setProtectionData(protectionData);
2060+
}
2061+
20302062
/*
20312063
---------------------------------------------------------------------------
20322064
@@ -2923,6 +2955,7 @@ function MediaPlayer() {
29232955
setMute,
29242956
setPlaybackRate,
29252957
setProtectionData,
2958+
addCertUrls,
29262959
setRepresentationForTypeById,
29272960
setRepresentationForTypeByIndex,
29282961
setTextTrack,

src/streaming/protection/controllers/ProtectionController.js

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import DashJSError from '../../vo/DashJSError.js';
3737
import LicenseRequest from '../vo/LicenseRequest.js';
3838
import LicenseResponse from '../vo/LicenseResponse.js';
3939
import {HTTPRequest} from '../../vo/metrics/HTTPRequest.js';
40+
import CertUrlUtils from '../../utils/CertUrlUtils';
4041
import Utils from '../../../core/Utils.js';
4142
import Constants from '../../constants/Constants.js';
4243
import FactoryMaker from '../../../core/FactoryMaker.js';
@@ -278,36 +279,30 @@ function ProtectionController(config) {
278279
*/
279280
function _collectCertificateUrlsForSelectedKeySystem(preferredType) {
280281
const urls = [];
282+
// 1. API-provided certUrls (protData) take priority
283+
const protData = _getProtDataForKeySystem(selectedKeySystem);
284+
if (protData && Array.isArray(protData.certUrls) && protData.certUrls.length) {
285+
protData.certUrls.forEach(c => { urls.push(c); });
286+
}
287+
// 2. Manifest-provided certUrls
281288
mediaInfoArr.forEach(mediaInfo => {
282289
if (!mediaInfo || !mediaInfo.contentProtection) { return; }
283290
mediaInfo.contentProtection.forEach(contentProtection => {
284-
// Match by schemeIdUri UUID presence in keySystems metadata later; for now collect all
285291
if (contentProtection && Array.isArray(contentProtection.certUrls) && contentProtection.certUrls.length) {
286292
contentProtection.certUrls.forEach(c => { urls.push(c); });
287293
}
288294
});
289295
});
296+
// Preferred type filter (try only matching ones first if any)
290297
if (preferredType) {
291-
const filtered = urls.filter(c => c.certType === preferredType);
292-
if (filtered.length) { return _dedupeCertUrls(filtered); }
298+
const preferred = urls.filter(c => c.certType === preferredType);
299+
if (preferred.length) {
300+
const others = urls.filter(c => c.certType !== preferredType);
301+
// Preferred first, then others for fallback
302+
return CertUrlUtils.dedupeCertUrls(preferred.concat(others));
303+
}
293304
}
294-
return _dedupeCertUrls(urls);
295-
}
296-
297-
/**
298-
* Deduplicate certificate URL list by URL + certType
299-
* @param {Array<{url:string, certType:string|null}>} list
300-
* @return {Array<{url:string, certType:string|null}>}
301-
* @private
302-
*/
303-
function _dedupeCertUrls(list) {
304-
const seen = new Set();
305-
return list.filter(item => {
306-
const key = item.url + '||' + (item.certType || '');
307-
if (seen.has(key)) { return false; }
308-
seen.add(key);
309-
return true;
310-
});
305+
return CertUrlUtils.dedupeCertUrls(urls);
311306
}
312307

313308
/**
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Utility functions for DASH Certurl normalization.
3+
* Shared by ContentProtection parsing and MediaPlayer.addCertUrls API.
4+
*
5+
* A Certurl entry may appear as:
6+
* - String: 'https://example.com/cert'
7+
* - Object parsed from XML: { __text: 'https://example.com/cert', '@certType': 'primary' }
8+
* - Pre-normalized object: { url: 'https://example.com/cert', certType: 'primary' }
9+
* - Array of any of the above
10+
*
11+
* The normalization returns an array of objects: { url: string, certType: string|null }
12+
* Empty or invalid entries are filtered out. Whitespace is trimmed.
13+
*/
14+
function normalizeCertUrls(raw) {
15+
if (!raw) { return []; }
16+
const arr = Array.isArray(raw) ? raw : [raw];
17+
return arr.map(item => {
18+
if (!item) { return null; }
19+
if (typeof item === 'string') {
20+
const url = item.trim();
21+
return url ? { url, certType: null } : null;
22+
}
23+
if (typeof item === 'object') {
24+
let url = (item.__text || item.text || '').trim();
25+
if (!url && typeof item.url === 'string') { // fallback if pre-normalized
26+
url = item.url.trim();
27+
}
28+
let certType = item.certType || item['@certType'] || null;
29+
if (certType && typeof certType === 'string') {
30+
certType = certType.trim();
31+
if (certType === '') { certType = null; }
32+
} else {
33+
certType = null;
34+
}
35+
return url ? { url, certType } : null;
36+
}
37+
return null;
38+
}).filter(Boolean);
39+
}
40+
41+
/**
42+
* Deduplicates an array of Certurl descriptor objects by URL + certType combination.
43+
* Keeps first occurrence order stable.
44+
* @param {Array<{url:string, certType:string|null}>} list
45+
* @returns {Array<{url:string, certType:string|null}>}
46+
*/
47+
function dedupeCertUrls(list) {
48+
if (!Array.isArray(list) || list.length === 0) { return []; }
49+
const seen = new Set();
50+
const result = [];
51+
for (let i = 0; i < list.length; i++) {
52+
const item = list[i];
53+
if (!item || !item.url) { continue; }
54+
const key = item.url + '||' + (item.certType || '');
55+
if (seen.has(key)) { continue; }
56+
seen.add(key);
57+
result.push(item);
58+
}
59+
return result;
60+
}
61+
62+
export default {
63+
normalizeCertUrls,
64+
dedupeCertUrls
65+
};

0 commit comments

Comments
 (0)