@guardvideo/player-sdk 3.1.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 +6 -0
- package/dist/core/player.d.ts.map +1 -1
- package/dist/index.esm.js +119 -2
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +119 -2
- package/dist/index.js.map +1 -1
- package/dist/vanilla/guardvideo-player.js +119 -2
- 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 = {
|
|
@@ -37121,8 +37123,12 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
|
|
|
37121
37123
|
this.setState(exports.PlayerState.LOADING);
|
|
37122
37124
|
this.embedToken = await this.fetchEmbedToken();
|
|
37123
37125
|
this.log('Embed token received', this.embedToken);
|
|
37124
|
-
await this.initializePlayer();
|
|
37125
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
|
+
}
|
|
37131
|
+
await this.initializePlayer();
|
|
37126
37132
|
}
|
|
37127
37133
|
catch (err) {
|
|
37128
37134
|
this.handleError({
|
|
@@ -37155,6 +37161,7 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
|
|
|
37155
37161
|
if (cfg.enableWatermark && cfg.watermarkText) {
|
|
37156
37162
|
this.config.onWatermark?.(cfg.watermarkText);
|
|
37157
37163
|
}
|
|
37164
|
+
this.sessionId = cfg.sessionId ?? undefined;
|
|
37158
37165
|
if (this.config.trackViewerEvents && cfg.eventsSecret) {
|
|
37159
37166
|
this.initEventTracker(cfg.eventsSecret, cfg.sessionId ?? undefined);
|
|
37160
37167
|
}
|
|
@@ -37225,11 +37232,89 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
|
|
|
37225
37232
|
}
|
|
37226
37233
|
return await response.json();
|
|
37227
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
|
+
}
|
|
37228
37308
|
async initializePlayer() {
|
|
37229
37309
|
if (!this.embedToken) {
|
|
37230
37310
|
throw new Error('No embed token available');
|
|
37231
37311
|
}
|
|
37232
|
-
const
|
|
37312
|
+
const basePlayerUrl = this.embedToken.playerUrl;
|
|
37313
|
+
const playerUrl = this.sessionId
|
|
37314
|
+
? (basePlayerUrl.includes('?')
|
|
37315
|
+
? `${basePlayerUrl}&session=${encodeURIComponent(this.sessionId)}`
|
|
37316
|
+
: `${basePlayerUrl}?session=${encodeURIComponent(this.sessionId)}`)
|
|
37317
|
+
: basePlayerUrl;
|
|
37233
37318
|
this.log('Initializing player with URL', playerUrl);
|
|
37234
37319
|
this.videoElement.controls = this.config.controls;
|
|
37235
37320
|
if (this.config.className) {
|
|
@@ -37252,10 +37337,28 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
|
|
|
37252
37337
|
}
|
|
37253
37338
|
initializeHls(playerUrl) {
|
|
37254
37339
|
this.log('Using HLS.js for adaptive streaming');
|
|
37340
|
+
const self = this;
|
|
37255
37341
|
this.hls = new Hls({
|
|
37256
37342
|
debug: this.config.debug,
|
|
37257
37343
|
enableWorker: true,
|
|
37258
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
|
+
},
|
|
37259
37362
|
...this.config.hlsConfig,
|
|
37260
37363
|
});
|
|
37261
37364
|
this.hls.loadSource(playerUrl);
|
|
@@ -37282,10 +37385,24 @@ Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timeline
|
|
|
37282
37385
|
});
|
|
37283
37386
|
this.hls.on(Hls.Events.ERROR, (_event, data) => {
|
|
37284
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
|
+
}
|
|
37285
37399
|
if (data.fatal) {
|
|
37286
37400
|
switch (data.type) {
|
|
37287
37401
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
|
37288
37402
|
this.error('Network error, attempting recovery...');
|
|
37403
|
+
if (this.embedToken?.forensicWatermark) {
|
|
37404
|
+
this.refreshNonce().catch(() => { });
|
|
37405
|
+
}
|
|
37289
37406
|
setTimeout(() => this.hls?.startLoad(), 1000);
|
|
37290
37407
|
break;
|
|
37291
37408
|
case Hls.ErrorTypes.MEDIA_ERROR:
|