@guardvideo/player-sdk 3.4.0 → 3.5.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/EventTracker.d.ts +3 -0
- package/dist/core/EventTracker.d.ts.map +1 -1
- package/dist/core/player.d.ts +7 -0
- package/dist/core/player.d.ts.map +1 -1
- package/dist/index.esm.js +154 -23
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +154 -23
- package/dist/index.js.map +1 -1
- package/dist/vanilla/guardvideo-player.js +154 -23
- 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
|
@@ -21,6 +21,8 @@ export declare class EventTracker {
|
|
|
21
21
|
private accumulator;
|
|
22
22
|
private destroyed;
|
|
23
23
|
private _onBeforeUnload;
|
|
24
|
+
private flushBackoff;
|
|
25
|
+
private flushing;
|
|
24
26
|
constructor(config: EventTrackerConfig);
|
|
25
27
|
track(type: string, positionSeconds?: number, payload?: Record<string, unknown>): void;
|
|
26
28
|
startChunk(position: number): void;
|
|
@@ -30,6 +32,7 @@ export declare class EventTracker {
|
|
|
30
32
|
flush(): Promise<void>;
|
|
31
33
|
destroy(): void;
|
|
32
34
|
private startAutoFlush;
|
|
35
|
+
private restartAutoFlush;
|
|
33
36
|
private hookPageUnload;
|
|
34
37
|
private sendBatch;
|
|
35
38
|
private randomNonce;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventTracker.d.ts","sourceRoot":"","sources":["../../src/core/EventTracker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"EventTracker.d.ts","sourceRoot":"","sources":["../../src/core/EventTracker.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IAEjC,QAAQ,EAAE,MAAM,CAAC;IAEjB,OAAO,EAAE,MAAM,CAAC;IAEhB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,YAAY,EAAE,MAAM,CAAC;IAErB,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAMD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,eAAe,CAA6B;IAEpD,OAAO,CAAC,YAAY,CAAK;IAEzB,OAAO,CAAC,QAAQ,CAAS;gBAEb,MAAM,EAAE,kBAAkB;IAoBtC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAqBtF,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKlC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKjC,QAAQ,IAAI,IAAI;IAKhB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAK/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB5B,OAAO,IAAI,IAAI;IAYf,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,cAAc;YA+BR,SAAS;IA4DvB,OAAO,CAAC,WAAW;YASL,IAAI;CAenB"}
|
package/dist/core/player.d.ts
CHANGED
|
@@ -13,6 +13,11 @@ export declare class GuardVideoPlayer implements PlayerInstance {
|
|
|
13
13
|
private sessionId;
|
|
14
14
|
private playlistNonce;
|
|
15
15
|
private nonceRefreshInProgress;
|
|
16
|
+
private nonceRefreshTimer;
|
|
17
|
+
private retryBackoff;
|
|
18
|
+
private readonly MAX_BACKOFF;
|
|
19
|
+
private rateLimitCooldownUntil;
|
|
20
|
+
private nonceRefreshPromise;
|
|
16
21
|
private _onRateChange;
|
|
17
22
|
constructor(videoElement: HTMLVideoElement, videoId: string, config: PlayerConfig);
|
|
18
23
|
private log;
|
|
@@ -27,7 +32,9 @@ export declare class GuardVideoPlayer implements PlayerInstance {
|
|
|
27
32
|
private fetchEmbedToken;
|
|
28
33
|
private solveChallenge;
|
|
29
34
|
private acquireNonce;
|
|
35
|
+
private scheduleNonceRefresh;
|
|
30
36
|
private refreshNonce;
|
|
37
|
+
private enterRateLimitCooldown;
|
|
31
38
|
private initializePlayer;
|
|
32
39
|
private initializeHls;
|
|
33
40
|
private setupVideoEventListeners;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../../src/core/player.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,YAAY,EAEZ,WAAW,EACX,cAAc,EACd,YAAY,EAIb,MAAM,SAAS,CAAC;AA8BjB,qBAAa,gBAAiB,YAAW,cAAc;
|
|
1
|
+
{"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../../src/core/player.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,YAAY,EAEZ,WAAW,EACX,cAAc,EACd,YAAY,EAIb,MAAM,SAAS,CAAC;AA8BjB,qBAAa,gBAAiB,YAAW,cAAc;IA+BnD,OAAO,CAAC,OAAO;IA9BjB,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,GAAG,CAAoB;IAC/B,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,UAAU,CAAmC;IACrD,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,SAAS,CAAqB;IAEtC,OAAO,CAAC,aAAa,CAAuB;IAE5C,OAAO,CAAC,sBAAsB,CAAS;IAEvC,OAAO,CAAC,iBAAiB,CAA8C;IAEvE,OAAO,CAAC,YAAY,CAAQ;IAE5B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,OAAO,CAAC,sBAAsB,CAAK;IAEnC,OAAO,CAAC,mBAAmB,CAA8B;IAGzD,OAAO,CAAC,aAAa,CAAkC;gBAGrD,YAAY,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,EACvB,MAAM,EAAE,YAAY;IAqDtB,OAAO,CAAC,GAAG;IAMX,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,cAAc;YAWR,UAAU;YAkCV,sBAAsB;IA6CpC,OAAO,CAAC,gBAAgB;YA0CV,eAAe;YAgDf,cAAc;YAmEd,YAAY;IAqD1B,OAAO,CAAC,oBAAoB;YAed,YAAY;IAmB1B,OAAO,CAAC,sBAAsB;YAKhB,gBAAgB;IAiC9B,OAAO,CAAC,aAAa;IA6IrB,OAAO,CAAC,wBAAwB;IAuBhC,OAAO,CAAC,WAAW;IASN,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,KAAK,IAAI,IAAI;IAEb,cAAc,IAAI,MAAM;IAExB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAExB,WAAW,IAAI,MAAM;IAErB,SAAS,IAAI,MAAM;IAEnB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,gBAAgB,IAAI,YAAY,EAAE;IAWlC,iBAAiB,IAAI,YAAY,GAAG,IAAI;IAExC,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAOpC,QAAQ,IAAI,WAAW;IAEvB,OAAO,IAAI,IAAI;CAgCvB"}
|
package/dist/index.esm.js
CHANGED
|
@@ -68,8 +68,19 @@ class WatchChunkAccumulator {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
const VALID_EVENT_TYPES = [
|
|
72
|
+
'watch_chunk',
|
|
73
|
+
'play',
|
|
74
|
+
'pause',
|
|
75
|
+
'seek',
|
|
76
|
+
'ended',
|
|
77
|
+
'quality_change',
|
|
78
|
+
'error',
|
|
79
|
+
'security_event',
|
|
80
|
+
];
|
|
71
81
|
const MAX_BATCH_SIZE = 25;
|
|
72
82
|
const DEFAULT_FLUSH_INTERVAL = 5000;
|
|
83
|
+
const MAX_FLUSH_BACKOFF = 60000;
|
|
73
84
|
class EventTracker {
|
|
74
85
|
constructor(config) {
|
|
75
86
|
this.buffer = [];
|
|
@@ -77,6 +88,8 @@ class EventTracker {
|
|
|
77
88
|
this.accumulator = new WatchChunkAccumulator();
|
|
78
89
|
this.destroyed = false;
|
|
79
90
|
this._onBeforeUnload = null;
|
|
91
|
+
this.flushBackoff = 0;
|
|
92
|
+
this.flushing = false;
|
|
80
93
|
this.config = {
|
|
81
94
|
endpoint: config.endpoint,
|
|
82
95
|
tokenId: config.tokenId,
|
|
@@ -92,6 +105,12 @@ class EventTracker {
|
|
|
92
105
|
track(type, positionSeconds, payload) {
|
|
93
106
|
if (this.destroyed)
|
|
94
107
|
return;
|
|
108
|
+
if (!VALID_EVENT_TYPES.includes(type)) {
|
|
109
|
+
if (this.config.debug) {
|
|
110
|
+
console.warn('[GuardVideo EventTracker] Skipping unknown event type:', type);
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
95
114
|
this.buffer.push({
|
|
96
115
|
event_type: type,
|
|
97
116
|
event_at: new Date().toISOString(),
|
|
@@ -115,7 +134,7 @@ class EventTracker {
|
|
|
115
134
|
this.config.sessionId = sessionId;
|
|
116
135
|
}
|
|
117
136
|
async flush() {
|
|
118
|
-
if (this.destroyed)
|
|
137
|
+
if (this.destroyed || this.flushing)
|
|
119
138
|
return;
|
|
120
139
|
const chunks = this.accumulator.drain();
|
|
121
140
|
for (const c of chunks) {
|
|
@@ -123,8 +142,14 @@ class EventTracker {
|
|
|
123
142
|
}
|
|
124
143
|
if (this.buffer.length === 0)
|
|
125
144
|
return;
|
|
126
|
-
|
|
127
|
-
|
|
145
|
+
this.flushing = true;
|
|
146
|
+
try {
|
|
147
|
+
const batch = this.buffer.splice(0, MAX_BATCH_SIZE);
|
|
148
|
+
await this.sendBatch(batch);
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
this.flushing = false;
|
|
152
|
+
}
|
|
128
153
|
}
|
|
129
154
|
destroy() {
|
|
130
155
|
this.destroyed = true;
|
|
@@ -139,6 +164,11 @@ class EventTracker {
|
|
|
139
164
|
startAutoFlush() {
|
|
140
165
|
this.flushTimer = setInterval(() => this.flush(), this.config.flushIntervalMs);
|
|
141
166
|
}
|
|
167
|
+
restartAutoFlush(intervalMs) {
|
|
168
|
+
if (this.flushTimer)
|
|
169
|
+
clearInterval(this.flushTimer);
|
|
170
|
+
this.flushTimer = setInterval(() => this.flush(), intervalMs);
|
|
171
|
+
}
|
|
142
172
|
hookPageUnload() {
|
|
143
173
|
if (typeof window === 'undefined')
|
|
144
174
|
return;
|
|
@@ -184,8 +214,25 @@ class EventTracker {
|
|
|
184
214
|
body,
|
|
185
215
|
credentials: 'omit',
|
|
186
216
|
});
|
|
187
|
-
if (!resp.ok
|
|
188
|
-
|
|
217
|
+
if (!resp.ok) {
|
|
218
|
+
if (resp.status === 429) {
|
|
219
|
+
this.flushBackoff = Math.min((this.flushBackoff || this.config.flushIntervalMs) * 2, MAX_FLUSH_BACKOFF);
|
|
220
|
+
if (this.config.debug) {
|
|
221
|
+
console.warn('[GuardVideo EventTracker] 429 — backing off to', this.flushBackoff, 'ms');
|
|
222
|
+
}
|
|
223
|
+
this.buffer.unshift(...events);
|
|
224
|
+
this.restartAutoFlush(this.flushBackoff);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (this.config.debug) {
|
|
228
|
+
console.warn('[GuardVideo EventTracker] Flush failed:', resp.status);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
if (this.flushBackoff > 0) {
|
|
233
|
+
this.flushBackoff = 0;
|
|
234
|
+
this.restartAutoFlush(this.config.flushIntervalMs);
|
|
235
|
+
}
|
|
189
236
|
}
|
|
190
237
|
}
|
|
191
238
|
catch (err) {
|
|
@@ -401,6 +448,11 @@ class GuardVideoPlayer {
|
|
|
401
448
|
this._onSecurityEvent = null;
|
|
402
449
|
this.playlistNonce = null;
|
|
403
450
|
this.nonceRefreshInProgress = false;
|
|
451
|
+
this.nonceRefreshTimer = null;
|
|
452
|
+
this.retryBackoff = 1000;
|
|
453
|
+
this.MAX_BACKOFF = 30000;
|
|
454
|
+
this.rateLimitCooldownUntil = 0;
|
|
455
|
+
this.nonceRefreshPromise = null;
|
|
404
456
|
this._onRateChange = this.enforceMaxRate.bind(this);
|
|
405
457
|
this.videoElement = videoElement;
|
|
406
458
|
this.config = {
|
|
@@ -611,9 +663,18 @@ class GuardVideoPlayer {
|
|
|
611
663
|
async solveChallenge(tokenId) {
|
|
612
664
|
if (!this.config.apiBaseUrl)
|
|
613
665
|
return undefined;
|
|
666
|
+
if (Date.now() < this.rateLimitCooldownUntil) {
|
|
667
|
+
this.log('Challenge skipped — rate limit cooldown active');
|
|
668
|
+
return undefined;
|
|
669
|
+
}
|
|
614
670
|
try {
|
|
615
671
|
const url = this.config.apiBaseUrl + '/videos/challenge/' + encodeURIComponent(tokenId);
|
|
616
672
|
const resp = await fetch(url, { credentials: 'omit' });
|
|
673
|
+
if (resp.status === 429) {
|
|
674
|
+
this.enterRateLimitCooldown();
|
|
675
|
+
this.log('Challenge hit 429 — entering cooldown');
|
|
676
|
+
return undefined;
|
|
677
|
+
}
|
|
617
678
|
if (!resp.ok) {
|
|
618
679
|
this.log('Challenge fetch failed (challenge may be disabled)', resp.status);
|
|
619
680
|
return undefined;
|
|
@@ -639,6 +700,10 @@ class GuardVideoPlayer {
|
|
|
639
700
|
async acquireNonce(tokenId) {
|
|
640
701
|
if (!this.config.apiBaseUrl)
|
|
641
702
|
return null;
|
|
703
|
+
if (Date.now() < this.rateLimitCooldownUntil) {
|
|
704
|
+
this.log('Nonce acquisition skipped — rate limit cooldown active');
|
|
705
|
+
return this.playlistNonce;
|
|
706
|
+
}
|
|
642
707
|
try {
|
|
643
708
|
const challengeResult = await this.solveChallenge(tokenId);
|
|
644
709
|
const url = this.config.apiBaseUrl + '/videos/playlist-session';
|
|
@@ -652,12 +717,19 @@ class GuardVideoPlayer {
|
|
|
652
717
|
body: JSON.stringify(body),
|
|
653
718
|
credentials: 'omit',
|
|
654
719
|
});
|
|
720
|
+
if (resp.status === 429) {
|
|
721
|
+
this.enterRateLimitCooldown();
|
|
722
|
+
this.log('Nonce acquisition hit 429 — entering cooldown');
|
|
723
|
+
return this.playlistNonce;
|
|
724
|
+
}
|
|
655
725
|
if (!resp.ok) {
|
|
656
726
|
this.log('Playlist session nonce acquisition failed', resp.status);
|
|
657
727
|
return null;
|
|
658
728
|
}
|
|
659
729
|
const data = await resp.json();
|
|
660
730
|
this.log('Playlist nonce acquired, expires in ' + data.expiresIn + 's');
|
|
731
|
+
this.retryBackoff = 1000;
|
|
732
|
+
this.scheduleNonceRefresh(data.expiresIn);
|
|
661
733
|
return data.nonce;
|
|
662
734
|
}
|
|
663
735
|
catch (err) {
|
|
@@ -665,19 +737,38 @@ class GuardVideoPlayer {
|
|
|
665
737
|
return null;
|
|
666
738
|
}
|
|
667
739
|
}
|
|
740
|
+
scheduleNonceRefresh(expiresInSeconds) {
|
|
741
|
+
if (this.nonceRefreshTimer)
|
|
742
|
+
clearTimeout(this.nonceRefreshTimer);
|
|
743
|
+
const refreshMs = Math.max(5000, expiresInSeconds * 750);
|
|
744
|
+
this.nonceRefreshTimer = setTimeout(() => {
|
|
745
|
+
this.refreshNonce().catch(() => {
|
|
746
|
+
this.log('Proactive nonce refresh failed');
|
|
747
|
+
});
|
|
748
|
+
}, refreshMs);
|
|
749
|
+
}
|
|
668
750
|
async refreshNonce() {
|
|
669
|
-
if (this.nonceRefreshInProgress)
|
|
670
|
-
return;
|
|
751
|
+
if (this.nonceRefreshInProgress && this.nonceRefreshPromise) {
|
|
752
|
+
return this.nonceRefreshPromise;
|
|
753
|
+
}
|
|
671
754
|
if (!this.embedToken)
|
|
672
755
|
return;
|
|
673
756
|
this.nonceRefreshInProgress = true;
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
757
|
+
this.nonceRefreshPromise = (async () => {
|
|
758
|
+
try {
|
|
759
|
+
const nonce = await this.acquireNonce(this.embedToken.tokenId);
|
|
760
|
+
this.playlistNonce = nonce;
|
|
761
|
+
}
|
|
762
|
+
finally {
|
|
763
|
+
this.nonceRefreshInProgress = false;
|
|
764
|
+
this.nonceRefreshPromise = null;
|
|
765
|
+
}
|
|
766
|
+
})();
|
|
767
|
+
return this.nonceRefreshPromise;
|
|
768
|
+
}
|
|
769
|
+
enterRateLimitCooldown() {
|
|
770
|
+
this.rateLimitCooldownUntil = Date.now() + this.retryBackoff;
|
|
771
|
+
this.retryBackoff = Math.min(this.retryBackoff * 2, this.MAX_BACKOFF);
|
|
681
772
|
}
|
|
682
773
|
async initializePlayer() {
|
|
683
774
|
if (!this.embedToken) {
|
|
@@ -717,8 +808,12 @@ class GuardVideoPlayer {
|
|
|
717
808
|
enableWorker: true,
|
|
718
809
|
lowLatencyMode: false,
|
|
719
810
|
liveSyncDurationCount: 3,
|
|
720
|
-
manifestLoadingMaxRetry:
|
|
721
|
-
|
|
811
|
+
manifestLoadingMaxRetry: 3,
|
|
812
|
+
manifestLoadingRetryDelay: 2000,
|
|
813
|
+
levelLoadingMaxRetry: 3,
|
|
814
|
+
levelLoadingRetryDelay: 2000,
|
|
815
|
+
fragLoadingMaxRetry: 4,
|
|
816
|
+
fragLoadingRetryDelay: 1000,
|
|
722
817
|
xhrSetup(xhr, url) {
|
|
723
818
|
if (url.includes('/watermark-stream/') && url.includes('.m3u8')) {
|
|
724
819
|
if (self.playlistNonce) {
|
|
@@ -726,10 +821,6 @@ class GuardVideoPlayer {
|
|
|
726
821
|
const nonceUrl = url + separator + 'nonce=' + encodeURIComponent(self.playlistNonce);
|
|
727
822
|
xhr.open('GET', nonceUrl, true);
|
|
728
823
|
self.log('Injected nonce into playlist request');
|
|
729
|
-
self.playlistNonce = null;
|
|
730
|
-
self.refreshNonce().catch(() => {
|
|
731
|
-
self.log('Background nonce refresh failed');
|
|
732
|
-
});
|
|
733
824
|
}
|
|
734
825
|
}
|
|
735
826
|
},
|
|
@@ -741,6 +832,7 @@ class GuardVideoPlayer {
|
|
|
741
832
|
this.log('HLS manifest parsed', { levels: data.levels.map((l) => l.height + 'p') });
|
|
742
833
|
this.setState(PlayerState.READY);
|
|
743
834
|
this.config.onReady();
|
|
835
|
+
this.retryBackoff = 1000;
|
|
744
836
|
if (this.config.autoplay)
|
|
745
837
|
this.play();
|
|
746
838
|
});
|
|
@@ -756,18 +848,52 @@ class GuardVideoPlayer {
|
|
|
756
848
|
this.currentQuality = quality;
|
|
757
849
|
this.log('Quality switched to ' + quality.name);
|
|
758
850
|
this.config.onQualityChange(quality.name);
|
|
851
|
+
this.eventTracker?.track('quality_change', this.videoElement.currentTime, {
|
|
852
|
+
level: data.level,
|
|
853
|
+
height: level.height,
|
|
854
|
+
bitrate: level.bitrate,
|
|
855
|
+
});
|
|
759
856
|
});
|
|
760
857
|
this.hls.on(Hls.Events.ERROR, (_event, data) => {
|
|
761
858
|
this.error('HLS Error', data);
|
|
859
|
+
this.eventTracker?.track('error', this.videoElement.currentTime, {
|
|
860
|
+
type: data.type,
|
|
861
|
+
details: data.details,
|
|
862
|
+
fatal: data.fatal,
|
|
863
|
+
});
|
|
864
|
+
if (Date.now() < this.rateLimitCooldownUntil) {
|
|
865
|
+
this.log('Suppressing retry — rate limit cooldown active (' +
|
|
866
|
+
Math.ceil((this.rateLimitCooldownUntil - Date.now()) / 1000) + 's remaining)');
|
|
867
|
+
if (data.fatal) {
|
|
868
|
+
const delay = this.rateLimitCooldownUntil - Date.now() + 500;
|
|
869
|
+
setTimeout(() => this.hls?.startLoad(), delay);
|
|
870
|
+
}
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
const httpStatus = data.response?.code;
|
|
874
|
+
if (httpStatus === 429) {
|
|
875
|
+
this.enterRateLimitCooldown();
|
|
876
|
+
this.log('429 detected — entering cooldown for ' + this.retryBackoff + 'ms');
|
|
877
|
+
setTimeout(() => {
|
|
878
|
+
if (this.embedToken?.forensicWatermark) {
|
|
879
|
+
this.refreshNonce().then(() => this.hls?.startLoad()).catch(() => this.hls?.startLoad());
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
this.hls?.startLoad();
|
|
883
|
+
}
|
|
884
|
+
}, this.retryBackoff);
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
762
887
|
if (data.type === Hls.ErrorTypes.NETWORK_ERROR &&
|
|
763
888
|
data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR &&
|
|
764
889
|
this.embedToken?.forensicWatermark) {
|
|
765
890
|
this.log('Playlist load failed — refreshing nonce before retry');
|
|
766
891
|
this.refreshNonce().then(() => {
|
|
767
|
-
setTimeout(() => this.hls?.startLoad(),
|
|
892
|
+
setTimeout(() => this.hls?.startLoad(), this.retryBackoff);
|
|
768
893
|
}).catch(() => {
|
|
769
|
-
setTimeout(() => this.hls?.startLoad(),
|
|
894
|
+
setTimeout(() => this.hls?.startLoad(), this.retryBackoff);
|
|
770
895
|
});
|
|
896
|
+
this.retryBackoff = Math.min(this.retryBackoff * 1.5, this.MAX_BACKOFF);
|
|
771
897
|
return;
|
|
772
898
|
}
|
|
773
899
|
if (data.fatal) {
|
|
@@ -777,7 +903,8 @@ class GuardVideoPlayer {
|
|
|
777
903
|
if (this.embedToken?.forensicWatermark) {
|
|
778
904
|
this.refreshNonce().catch(() => { });
|
|
779
905
|
}
|
|
780
|
-
setTimeout(() => this.hls?.startLoad(),
|
|
906
|
+
setTimeout(() => this.hls?.startLoad(), this.retryBackoff);
|
|
907
|
+
this.retryBackoff = Math.min(this.retryBackoff * 1.5, this.MAX_BACKOFF);
|
|
781
908
|
break;
|
|
782
909
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
|
783
910
|
this.error('Media error, attempting recovery...');
|
|
@@ -855,6 +982,10 @@ class GuardVideoPlayer {
|
|
|
855
982
|
getState() { return this.state; }
|
|
856
983
|
destroy() {
|
|
857
984
|
this.log('Destroying player');
|
|
985
|
+
if (this.nonceRefreshTimer) {
|
|
986
|
+
clearTimeout(this.nonceRefreshTimer);
|
|
987
|
+
this.nonceRefreshTimer = null;
|
|
988
|
+
}
|
|
858
989
|
if (this.eventTracker) {
|
|
859
990
|
this.eventTracker.destroy();
|
|
860
991
|
this.eventTracker = null;
|