@guardvideo/player-sdk 3.2.0 → 3.3.0
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/dist/core/player.d.ts +5 -0
- package/dist/core/player.d.ts.map +1 -1
- package/dist/index.esm.js +111 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +111 -0
- package/dist/index.js.map +1 -1
- package/dist/vanilla/guardvideo-player.js +111 -0
- package/dist/vanilla/guardvideo-player.js.map +1 -1
- package/dist/vanilla/guardvideo-player.min.js +1 -1
- package/dist/vanilla/guardvideo-player.min.js.map +1 -1
- package/package.json +1 -1
|
@@ -37023,6 +37023,8 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
|
|
|
37023
37023
|
this.eventTracker = null;
|
|
37024
37024
|
this.sdkFingerprint = null;
|
|
37025
37025
|
this._onSecurityEvent = null;
|
|
37026
|
+
this.playlistNonce = null;
|
|
37027
|
+
this.nonceRefreshInProgress = false;
|
|
37026
37028
|
this._onRateChange = this.enforceMaxRate.bind(this);
|
|
37027
37029
|
this.videoElement = videoElement;
|
|
37028
37030
|
this.config = {
|
|
@@ -37122,6 +37124,10 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
|
|
|
37122
37124
|
this.embedToken = await this.fetchEmbedToken();
|
|
37123
37125
|
this.log('Embed token received', this.embedToken);
|
|
37124
37126
|
await this.fetchAndApplyWatermark();
|
|
37127
|
+
if (this.embedToken.forensicWatermark) {
|
|
37128
|
+
this.playlistNonce = await this.acquireNonce(this.embedToken.tokenId);
|
|
37129
|
+
this.log('Initial playlist nonce: ' + (this.playlistNonce ? 'acquired' : 'skipped'));
|
|
37130
|
+
}
|
|
37125
37131
|
await this.initializePlayer();
|
|
37126
37132
|
}
|
|
37127
37133
|
catch (err) {
|
|
@@ -37226,6 +37232,79 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
|
|
|
37226
37232
|
}
|
|
37227
37233
|
return await response.json();
|
|
37228
37234
|
}
|
|
37235
|
+
async solveChallenge(tokenId) {
|
|
37236
|
+
if (!this.config.apiBaseUrl)
|
|
37237
|
+
return undefined;
|
|
37238
|
+
try {
|
|
37239
|
+
const url = this.config.apiBaseUrl + '/videos/challenge/' + encodeURIComponent(tokenId);
|
|
37240
|
+
const resp = await fetch(url, { credentials: 'omit' });
|
|
37241
|
+
if (!resp.ok) {
|
|
37242
|
+
this.log('Challenge fetch failed (challenge may be disabled)', resp.status);
|
|
37243
|
+
return undefined;
|
|
37244
|
+
}
|
|
37245
|
+
const challenge = await resp.json();
|
|
37246
|
+
this.log('Challenge received, computing PBKDF2 response...');
|
|
37247
|
+
const encoder = new TextEncoder();
|
|
37248
|
+
const keyMaterial = await crypto.subtle.importKey('raw', encoder.encode(challenge.nonce), 'PBKDF2', false, ['deriveBits']);
|
|
37249
|
+
const derivedBits = await crypto.subtle.deriveBits({
|
|
37250
|
+
name: 'PBKDF2',
|
|
37251
|
+
salt: encoder.encode(challenge.salt),
|
|
37252
|
+
iterations: challenge.iterations,
|
|
37253
|
+
hash: 'SHA-256',
|
|
37254
|
+
}, keyMaterial, challenge.keyLength * 8);
|
|
37255
|
+
const hashArray = Array.from(new Uint8Array(derivedBits));
|
|
37256
|
+
const hexResponse = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
37257
|
+
this.log('Challenge response computed');
|
|
37258
|
+
return hexResponse;
|
|
37259
|
+
}
|
|
37260
|
+
catch (err) {
|
|
37261
|
+
this.log('Challenge computation failed (non-fatal):', err);
|
|
37262
|
+
return undefined;
|
|
37263
|
+
}
|
|
37264
|
+
}
|
|
37265
|
+
async acquireNonce(tokenId) {
|
|
37266
|
+
if (!this.config.apiBaseUrl)
|
|
37267
|
+
return null;
|
|
37268
|
+
try {
|
|
37269
|
+
const challengeResponse = await this.solveChallenge(tokenId);
|
|
37270
|
+
const url = this.config.apiBaseUrl + '/videos/playlist-session';
|
|
37271
|
+
const body = { tokenId };
|
|
37272
|
+
if (challengeResponse) {
|
|
37273
|
+
body.challengeResponse = challengeResponse;
|
|
37274
|
+
}
|
|
37275
|
+
const resp = await fetch(url, {
|
|
37276
|
+
method: 'POST',
|
|
37277
|
+
headers: { 'Content-Type': 'application/json' },
|
|
37278
|
+
body: JSON.stringify(body),
|
|
37279
|
+
credentials: 'omit',
|
|
37280
|
+
});
|
|
37281
|
+
if (!resp.ok) {
|
|
37282
|
+
this.log('Playlist session nonce acquisition failed', resp.status);
|
|
37283
|
+
return null;
|
|
37284
|
+
}
|
|
37285
|
+
const data = await resp.json();
|
|
37286
|
+
this.log('Playlist nonce acquired, expires in ' + data.expiresIn + 's');
|
|
37287
|
+
return data.nonce;
|
|
37288
|
+
}
|
|
37289
|
+
catch (err) {
|
|
37290
|
+
this.log('Nonce acquisition error (non-fatal):', err);
|
|
37291
|
+
return null;
|
|
37292
|
+
}
|
|
37293
|
+
}
|
|
37294
|
+
async refreshNonce() {
|
|
37295
|
+
if (this.nonceRefreshInProgress)
|
|
37296
|
+
return;
|
|
37297
|
+
if (!this.embedToken)
|
|
37298
|
+
return;
|
|
37299
|
+
this.nonceRefreshInProgress = true;
|
|
37300
|
+
try {
|
|
37301
|
+
const nonce = await this.acquireNonce(this.embedToken.tokenId);
|
|
37302
|
+
this.playlistNonce = nonce;
|
|
37303
|
+
}
|
|
37304
|
+
finally {
|
|
37305
|
+
this.nonceRefreshInProgress = false;
|
|
37306
|
+
}
|
|
37307
|
+
}
|
|
37229
37308
|
async initializePlayer() {
|
|
37230
37309
|
if (!this.embedToken) {
|
|
37231
37310
|
throw new Error('No embed token available');
|
|
@@ -37258,10 +37337,28 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
|
|
|
37258
37337
|
}
|
|
37259
37338
|
initializeHls(playerUrl) {
|
|
37260
37339
|
this.log('Using HLS.js for adaptive streaming');
|
|
37340
|
+
const self = this;
|
|
37261
37341
|
this.hls = new Hls({
|
|
37262
37342
|
debug: this.config.debug,
|
|
37263
37343
|
enableWorker: true,
|
|
37264
37344
|
lowLatencyMode: false,
|
|
37345
|
+
liveSyncDurationCount: 3,
|
|
37346
|
+
manifestLoadingMaxRetry: 6,
|
|
37347
|
+
levelLoadingMaxRetry: 6,
|
|
37348
|
+
xhrSetup(xhr, url) {
|
|
37349
|
+
if (url.includes('/watermark-stream/') && url.includes('.m3u8')) {
|
|
37350
|
+
if (self.playlistNonce) {
|
|
37351
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
37352
|
+
const nonceUrl = url + separator + 'nonce=' + encodeURIComponent(self.playlistNonce);
|
|
37353
|
+
xhr.open('GET', nonceUrl, true);
|
|
37354
|
+
self.log('Injected nonce into playlist request');
|
|
37355
|
+
self.playlistNonce = null;
|
|
37356
|
+
self.refreshNonce().catch(() => {
|
|
37357
|
+
self.log('Background nonce refresh failed');
|
|
37358
|
+
});
|
|
37359
|
+
}
|
|
37360
|
+
}
|
|
37361
|
+
},
|
|
37265
37362
|
...this.config.hlsConfig,
|
|
37266
37363
|
});
|
|
37267
37364
|
this.hls.loadSource(playerUrl);
|
|
@@ -37288,10 +37385,24 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
|
|
|
37288
37385
|
});
|
|
37289
37386
|
this.hls.on(Hls.Events.ERROR, (_event, data) => {
|
|
37290
37387
|
this.error('HLS Error', data);
|
|
37388
|
+
if (data.type === Hls.ErrorTypes.NETWORK_ERROR &&
|
|
37389
|
+
data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR &&
|
|
37390
|
+
this.embedToken?.forensicWatermark) {
|
|
37391
|
+
this.log('Playlist load failed — refreshing nonce before retry');
|
|
37392
|
+
this.refreshNonce().then(() => {
|
|
37393
|
+
setTimeout(() => this.hls?.startLoad(), 500);
|
|
37394
|
+
}).catch(() => {
|
|
37395
|
+
setTimeout(() => this.hls?.startLoad(), 1000);
|
|
37396
|
+
});
|
|
37397
|
+
return;
|
|
37398
|
+
}
|
|
37291
37399
|
if (data.fatal) {
|
|
37292
37400
|
switch (data.type) {
|
|
37293
37401
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
|
37294
37402
|
this.error('Network error, attempting recovery...');
|
|
37403
|
+
if (this.embedToken?.forensicWatermark) {
|
|
37404
|
+
this.refreshNonce().catch(() => { });
|
|
37405
|
+
}
|
|
37295
37406
|
setTimeout(() => this.hls?.startLoad(), 1000);
|
|
37296
37407
|
break;
|
|
37297
37408
|
case Hls.ErrorTypes.MEDIA_ERROR:
|