@guardvideo/player-sdk 3.2.0 → 3.4.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 +109 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +109 -0
- package/dist/index.js.map +1 -1
- package/dist/vanilla/guardvideo-player.js +109 -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,77 @@ 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 proof-of-work...');
|
|
37247
|
+
const encoder = new TextEncoder();
|
|
37248
|
+
const keyMaterial = await crypto.subtle.importKey('raw', encoder.encode(challenge.nonce), 'PBKDF2', false, ['deriveBits']);
|
|
37249
|
+
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
|
+
this.log('Challenge proof-of-work completed');
|
|
37256
|
+
return { nonce: challenge.nonce };
|
|
37257
|
+
}
|
|
37258
|
+
catch (err) {
|
|
37259
|
+
this.log('Challenge computation failed (non-fatal):', err);
|
|
37260
|
+
return undefined;
|
|
37261
|
+
}
|
|
37262
|
+
}
|
|
37263
|
+
async acquireNonce(tokenId) {
|
|
37264
|
+
if (!this.config.apiBaseUrl)
|
|
37265
|
+
return null;
|
|
37266
|
+
try {
|
|
37267
|
+
const challengeResult = await this.solveChallenge(tokenId);
|
|
37268
|
+
const url = this.config.apiBaseUrl + '/videos/playlist-session';
|
|
37269
|
+
const body = { tokenId };
|
|
37270
|
+
if (challengeResult) {
|
|
37271
|
+
body.challengeNonce = challengeResult.nonce;
|
|
37272
|
+
}
|
|
37273
|
+
const resp = await fetch(url, {
|
|
37274
|
+
method: 'POST',
|
|
37275
|
+
headers: { 'Content-Type': 'application/json' },
|
|
37276
|
+
body: JSON.stringify(body),
|
|
37277
|
+
credentials: 'omit',
|
|
37278
|
+
});
|
|
37279
|
+
if (!resp.ok) {
|
|
37280
|
+
this.log('Playlist session nonce acquisition failed', resp.status);
|
|
37281
|
+
return null;
|
|
37282
|
+
}
|
|
37283
|
+
const data = await resp.json();
|
|
37284
|
+
this.log('Playlist nonce acquired, expires in ' + data.expiresIn + 's');
|
|
37285
|
+
return data.nonce;
|
|
37286
|
+
}
|
|
37287
|
+
catch (err) {
|
|
37288
|
+
this.log('Nonce acquisition error (non-fatal):', err);
|
|
37289
|
+
return null;
|
|
37290
|
+
}
|
|
37291
|
+
}
|
|
37292
|
+
async refreshNonce() {
|
|
37293
|
+
if (this.nonceRefreshInProgress)
|
|
37294
|
+
return;
|
|
37295
|
+
if (!this.embedToken)
|
|
37296
|
+
return;
|
|
37297
|
+
this.nonceRefreshInProgress = true;
|
|
37298
|
+
try {
|
|
37299
|
+
const nonce = await this.acquireNonce(this.embedToken.tokenId);
|
|
37300
|
+
this.playlistNonce = nonce;
|
|
37301
|
+
}
|
|
37302
|
+
finally {
|
|
37303
|
+
this.nonceRefreshInProgress = false;
|
|
37304
|
+
}
|
|
37305
|
+
}
|
|
37229
37306
|
async initializePlayer() {
|
|
37230
37307
|
if (!this.embedToken) {
|
|
37231
37308
|
throw new Error('No embed token available');
|
|
@@ -37258,10 +37335,28 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
|
|
|
37258
37335
|
}
|
|
37259
37336
|
initializeHls(playerUrl) {
|
|
37260
37337
|
this.log('Using HLS.js for adaptive streaming');
|
|
37338
|
+
const self = this;
|
|
37261
37339
|
this.hls = new Hls({
|
|
37262
37340
|
debug: this.config.debug,
|
|
37263
37341
|
enableWorker: true,
|
|
37264
37342
|
lowLatencyMode: false,
|
|
37343
|
+
liveSyncDurationCount: 3,
|
|
37344
|
+
manifestLoadingMaxRetry: 6,
|
|
37345
|
+
levelLoadingMaxRetry: 6,
|
|
37346
|
+
xhrSetup(xhr, url) {
|
|
37347
|
+
if (url.includes('/watermark-stream/') && url.includes('.m3u8')) {
|
|
37348
|
+
if (self.playlistNonce) {
|
|
37349
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
37350
|
+
const nonceUrl = url + separator + 'nonce=' + encodeURIComponent(self.playlistNonce);
|
|
37351
|
+
xhr.open('GET', nonceUrl, true);
|
|
37352
|
+
self.log('Injected nonce into playlist request');
|
|
37353
|
+
self.playlistNonce = null;
|
|
37354
|
+
self.refreshNonce().catch(() => {
|
|
37355
|
+
self.log('Background nonce refresh failed');
|
|
37356
|
+
});
|
|
37357
|
+
}
|
|
37358
|
+
}
|
|
37359
|
+
},
|
|
37265
37360
|
...this.config.hlsConfig,
|
|
37266
37361
|
});
|
|
37267
37362
|
this.hls.loadSource(playerUrl);
|
|
@@ -37288,10 +37383,24 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
|
|
|
37288
37383
|
});
|
|
37289
37384
|
this.hls.on(Hls.Events.ERROR, (_event, data) => {
|
|
37290
37385
|
this.error('HLS Error', data);
|
|
37386
|
+
if (data.type === Hls.ErrorTypes.NETWORK_ERROR &&
|
|
37387
|
+
data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR &&
|
|
37388
|
+
this.embedToken?.forensicWatermark) {
|
|
37389
|
+
this.log('Playlist load failed — refreshing nonce before retry');
|
|
37390
|
+
this.refreshNonce().then(() => {
|
|
37391
|
+
setTimeout(() => this.hls?.startLoad(), 500);
|
|
37392
|
+
}).catch(() => {
|
|
37393
|
+
setTimeout(() => this.hls?.startLoad(), 1000);
|
|
37394
|
+
});
|
|
37395
|
+
return;
|
|
37396
|
+
}
|
|
37291
37397
|
if (data.fatal) {
|
|
37292
37398
|
switch (data.type) {
|
|
37293
37399
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
|
37294
37400
|
this.error('Network error, attempting recovery...');
|
|
37401
|
+
if (this.embedToken?.forensicWatermark) {
|
|
37402
|
+
this.refreshNonce().catch(() => { });
|
|
37403
|
+
}
|
|
37295
37404
|
setTimeout(() => this.hls?.startLoad(), 1000);
|
|
37296
37405
|
break;
|
|
37297
37406
|
case Hls.ErrorTypes.MEDIA_ERROR:
|