@guardvideo/player-sdk 3.4.0 → 3.6.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 +13 -0
- package/dist/core/player.d.ts.map +1 -1
- package/dist/core/types.d.ts +1 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.esm.js +219 -26
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +219 -26
- package/dist/index.js.map +1 -1
- package/dist/react/GuardVideoPlayer.d.ts.map +1 -1
- package/dist/ui/PlayerUI.d.ts +1 -0
- package/dist/ui/PlayerUI.d.ts.map +1 -1
- package/dist/vanilla/guardvideo-player.js +219 -27
- 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
package/dist/index.js
CHANGED
|
@@ -70,8 +70,19 @@ class WatchChunkAccumulator {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
const VALID_EVENT_TYPES = [
|
|
74
|
+
'watch_chunk',
|
|
75
|
+
'play',
|
|
76
|
+
'pause',
|
|
77
|
+
'seek',
|
|
78
|
+
'ended',
|
|
79
|
+
'quality_change',
|
|
80
|
+
'error',
|
|
81
|
+
'security_event',
|
|
82
|
+
];
|
|
73
83
|
const MAX_BATCH_SIZE = 25;
|
|
74
84
|
const DEFAULT_FLUSH_INTERVAL = 5000;
|
|
85
|
+
const MAX_FLUSH_BACKOFF = 60000;
|
|
75
86
|
class EventTracker {
|
|
76
87
|
constructor(config) {
|
|
77
88
|
this.buffer = [];
|
|
@@ -79,6 +90,8 @@ class EventTracker {
|
|
|
79
90
|
this.accumulator = new WatchChunkAccumulator();
|
|
80
91
|
this.destroyed = false;
|
|
81
92
|
this._onBeforeUnload = null;
|
|
93
|
+
this.flushBackoff = 0;
|
|
94
|
+
this.flushing = false;
|
|
82
95
|
this.config = {
|
|
83
96
|
endpoint: config.endpoint,
|
|
84
97
|
tokenId: config.tokenId,
|
|
@@ -94,6 +107,12 @@ class EventTracker {
|
|
|
94
107
|
track(type, positionSeconds, payload) {
|
|
95
108
|
if (this.destroyed)
|
|
96
109
|
return;
|
|
110
|
+
if (!VALID_EVENT_TYPES.includes(type)) {
|
|
111
|
+
if (this.config.debug) {
|
|
112
|
+
console.warn('[GuardVideo EventTracker] Skipping unknown event type:', type);
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
97
116
|
this.buffer.push({
|
|
98
117
|
event_type: type,
|
|
99
118
|
event_at: new Date().toISOString(),
|
|
@@ -117,7 +136,7 @@ class EventTracker {
|
|
|
117
136
|
this.config.sessionId = sessionId;
|
|
118
137
|
}
|
|
119
138
|
async flush() {
|
|
120
|
-
if (this.destroyed)
|
|
139
|
+
if (this.destroyed || this.flushing)
|
|
121
140
|
return;
|
|
122
141
|
const chunks = this.accumulator.drain();
|
|
123
142
|
for (const c of chunks) {
|
|
@@ -125,8 +144,14 @@ class EventTracker {
|
|
|
125
144
|
}
|
|
126
145
|
if (this.buffer.length === 0)
|
|
127
146
|
return;
|
|
128
|
-
|
|
129
|
-
|
|
147
|
+
this.flushing = true;
|
|
148
|
+
try {
|
|
149
|
+
const batch = this.buffer.splice(0, MAX_BATCH_SIZE);
|
|
150
|
+
await this.sendBatch(batch);
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
this.flushing = false;
|
|
154
|
+
}
|
|
130
155
|
}
|
|
131
156
|
destroy() {
|
|
132
157
|
this.destroyed = true;
|
|
@@ -141,6 +166,11 @@ class EventTracker {
|
|
|
141
166
|
startAutoFlush() {
|
|
142
167
|
this.flushTimer = setInterval(() => this.flush(), this.config.flushIntervalMs);
|
|
143
168
|
}
|
|
169
|
+
restartAutoFlush(intervalMs) {
|
|
170
|
+
if (this.flushTimer)
|
|
171
|
+
clearInterval(this.flushTimer);
|
|
172
|
+
this.flushTimer = setInterval(() => this.flush(), intervalMs);
|
|
173
|
+
}
|
|
144
174
|
hookPageUnload() {
|
|
145
175
|
if (typeof window === 'undefined')
|
|
146
176
|
return;
|
|
@@ -186,8 +216,25 @@ class EventTracker {
|
|
|
186
216
|
body,
|
|
187
217
|
credentials: 'omit',
|
|
188
218
|
});
|
|
189
|
-
if (!resp.ok
|
|
190
|
-
|
|
219
|
+
if (!resp.ok) {
|
|
220
|
+
if (resp.status === 429) {
|
|
221
|
+
this.flushBackoff = Math.min((this.flushBackoff || this.config.flushIntervalMs) * 2, MAX_FLUSH_BACKOFF);
|
|
222
|
+
if (this.config.debug) {
|
|
223
|
+
console.warn('[GuardVideo EventTracker] 429 — backing off to', this.flushBackoff, 'ms');
|
|
224
|
+
}
|
|
225
|
+
this.buffer.unshift(...events);
|
|
226
|
+
this.restartAutoFlush(this.flushBackoff);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (this.config.debug) {
|
|
230
|
+
console.warn('[GuardVideo EventTracker] Flush failed:', resp.status);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
if (this.flushBackoff > 0) {
|
|
235
|
+
this.flushBackoff = 0;
|
|
236
|
+
this.restartAutoFlush(this.config.flushIntervalMs);
|
|
237
|
+
}
|
|
191
238
|
}
|
|
192
239
|
}
|
|
193
240
|
catch (err) {
|
|
@@ -403,6 +450,15 @@ class GuardVideoPlayer {
|
|
|
403
450
|
this._onSecurityEvent = null;
|
|
404
451
|
this.playlistNonce = null;
|
|
405
452
|
this.nonceRefreshInProgress = false;
|
|
453
|
+
this.nonceRefreshTimer = null;
|
|
454
|
+
this.retryBackoff = 1000;
|
|
455
|
+
this.MAX_BACKOFF = 30000;
|
|
456
|
+
this.rateLimitCooldownUntil = 0;
|
|
457
|
+
this.nonceRefreshPromise = null;
|
|
458
|
+
this.destroyed = false;
|
|
459
|
+
this.networkRetryCount = 0;
|
|
460
|
+
this.MAX_NETWORK_RETRIES = 6;
|
|
461
|
+
this.pendingRetryTimer = null;
|
|
406
462
|
this._onRateChange = this.enforceMaxRate.bind(this);
|
|
407
463
|
this.videoElement = videoElement;
|
|
408
464
|
this.config = {
|
|
@@ -613,9 +669,18 @@ class GuardVideoPlayer {
|
|
|
613
669
|
async solveChallenge(tokenId) {
|
|
614
670
|
if (!this.config.apiBaseUrl)
|
|
615
671
|
return undefined;
|
|
672
|
+
if (Date.now() < this.rateLimitCooldownUntil) {
|
|
673
|
+
this.log('Challenge skipped — rate limit cooldown active');
|
|
674
|
+
return undefined;
|
|
675
|
+
}
|
|
616
676
|
try {
|
|
617
677
|
const url = this.config.apiBaseUrl + '/videos/challenge/' + encodeURIComponent(tokenId);
|
|
618
678
|
const resp = await fetch(url, { credentials: 'omit' });
|
|
679
|
+
if (resp.status === 429) {
|
|
680
|
+
this.enterRateLimitCooldown();
|
|
681
|
+
this.log('Challenge hit 429 — entering cooldown');
|
|
682
|
+
return undefined;
|
|
683
|
+
}
|
|
619
684
|
if (!resp.ok) {
|
|
620
685
|
this.log('Challenge fetch failed (challenge may be disabled)', resp.status);
|
|
621
686
|
return undefined;
|
|
@@ -641,6 +706,10 @@ class GuardVideoPlayer {
|
|
|
641
706
|
async acquireNonce(tokenId) {
|
|
642
707
|
if (!this.config.apiBaseUrl)
|
|
643
708
|
return null;
|
|
709
|
+
if (Date.now() < this.rateLimitCooldownUntil) {
|
|
710
|
+
this.log('Nonce acquisition skipped — rate limit cooldown active');
|
|
711
|
+
return this.playlistNonce;
|
|
712
|
+
}
|
|
644
713
|
try {
|
|
645
714
|
const challengeResult = await this.solveChallenge(tokenId);
|
|
646
715
|
const url = this.config.apiBaseUrl + '/videos/playlist-session';
|
|
@@ -654,12 +723,20 @@ class GuardVideoPlayer {
|
|
|
654
723
|
body: JSON.stringify(body),
|
|
655
724
|
credentials: 'omit',
|
|
656
725
|
});
|
|
726
|
+
if (resp.status === 429) {
|
|
727
|
+
this.enterRateLimitCooldown();
|
|
728
|
+
this.log('Nonce acquisition hit 429 — entering cooldown');
|
|
729
|
+
return this.playlistNonce;
|
|
730
|
+
}
|
|
657
731
|
if (!resp.ok) {
|
|
658
732
|
this.log('Playlist session nonce acquisition failed', resp.status);
|
|
659
733
|
return null;
|
|
660
734
|
}
|
|
661
735
|
const data = await resp.json();
|
|
662
736
|
this.log('Playlist nonce acquired, expires in ' + data.expiresIn + 's');
|
|
737
|
+
this.retryBackoff = 1000;
|
|
738
|
+
this.networkRetryCount = 0;
|
|
739
|
+
this.scheduleNonceRefresh(data.expiresIn);
|
|
663
740
|
return data.nonce;
|
|
664
741
|
}
|
|
665
742
|
catch (err) {
|
|
@@ -667,19 +744,58 @@ class GuardVideoPlayer {
|
|
|
667
744
|
return null;
|
|
668
745
|
}
|
|
669
746
|
}
|
|
747
|
+
scheduleNonceRefresh(expiresInSeconds) {
|
|
748
|
+
if (this.nonceRefreshTimer)
|
|
749
|
+
clearTimeout(this.nonceRefreshTimer);
|
|
750
|
+
const refreshMs = Math.max(5000, expiresInSeconds * 750);
|
|
751
|
+
this.nonceRefreshTimer = setTimeout(() => {
|
|
752
|
+
if (this.destroyed)
|
|
753
|
+
return;
|
|
754
|
+
this.refreshNonce().catch(() => {
|
|
755
|
+
this.log('Proactive nonce refresh failed');
|
|
756
|
+
});
|
|
757
|
+
}, refreshMs);
|
|
758
|
+
}
|
|
670
759
|
async refreshNonce() {
|
|
671
|
-
if (this.nonceRefreshInProgress)
|
|
672
|
-
return;
|
|
760
|
+
if (this.nonceRefreshInProgress && this.nonceRefreshPromise) {
|
|
761
|
+
return this.nonceRefreshPromise;
|
|
762
|
+
}
|
|
673
763
|
if (!this.embedToken)
|
|
674
764
|
return;
|
|
675
765
|
this.nonceRefreshInProgress = true;
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
766
|
+
this.nonceRefreshPromise = (async () => {
|
|
767
|
+
try {
|
|
768
|
+
const nonce = await this.acquireNonce(this.embedToken.tokenId);
|
|
769
|
+
this.playlistNonce = nonce;
|
|
770
|
+
}
|
|
771
|
+
finally {
|
|
772
|
+
this.nonceRefreshInProgress = false;
|
|
773
|
+
this.nonceRefreshPromise = null;
|
|
774
|
+
}
|
|
775
|
+
})();
|
|
776
|
+
return this.nonceRefreshPromise;
|
|
777
|
+
}
|
|
778
|
+
enterRateLimitCooldown() {
|
|
779
|
+
this.rateLimitCooldownUntil = Date.now() + this.retryBackoff;
|
|
780
|
+
this.retryBackoff = Math.min(this.retryBackoff * 2, this.MAX_BACKOFF);
|
|
781
|
+
}
|
|
782
|
+
scheduleRetry(fn, delayMs) {
|
|
783
|
+
if (this.pendingRetryTimer)
|
|
784
|
+
clearTimeout(this.pendingRetryTimer);
|
|
785
|
+
this.pendingRetryTimer = setTimeout(() => {
|
|
786
|
+
this.pendingRetryTimer = null;
|
|
787
|
+
if (!this.destroyed && this.hls)
|
|
788
|
+
fn();
|
|
789
|
+
}, delayMs);
|
|
790
|
+
}
|
|
791
|
+
stopLoading() {
|
|
792
|
+
if (this.pendingRetryTimer) {
|
|
793
|
+
clearTimeout(this.pendingRetryTimer);
|
|
794
|
+
this.pendingRetryTimer = null;
|
|
682
795
|
}
|
|
796
|
+
this.hls?.stopLoad();
|
|
797
|
+
this.networkRetryCount = 0;
|
|
798
|
+
this.retryBackoff = 1000;
|
|
683
799
|
}
|
|
684
800
|
async initializePlayer() {
|
|
685
801
|
if (!this.embedToken) {
|
|
@@ -719,8 +835,12 @@ class GuardVideoPlayer {
|
|
|
719
835
|
enableWorker: true,
|
|
720
836
|
lowLatencyMode: false,
|
|
721
837
|
liveSyncDurationCount: 3,
|
|
722
|
-
manifestLoadingMaxRetry:
|
|
723
|
-
|
|
838
|
+
manifestLoadingMaxRetry: 3,
|
|
839
|
+
manifestLoadingRetryDelay: 2000,
|
|
840
|
+
levelLoadingMaxRetry: 3,
|
|
841
|
+
levelLoadingRetryDelay: 2000,
|
|
842
|
+
fragLoadingMaxRetry: 4,
|
|
843
|
+
fragLoadingRetryDelay: 1000,
|
|
724
844
|
xhrSetup(xhr, url) {
|
|
725
845
|
if (url.includes('/watermark-stream/') && url.includes('.m3u8')) {
|
|
726
846
|
if (self.playlistNonce) {
|
|
@@ -728,10 +848,6 @@ class GuardVideoPlayer {
|
|
|
728
848
|
const nonceUrl = url + separator + 'nonce=' + encodeURIComponent(self.playlistNonce);
|
|
729
849
|
xhr.open('GET', nonceUrl, true);
|
|
730
850
|
self.log('Injected nonce into playlist request');
|
|
731
|
-
self.playlistNonce = null;
|
|
732
|
-
self.refreshNonce().catch(() => {
|
|
733
|
-
self.log('Background nonce refresh failed');
|
|
734
|
-
});
|
|
735
851
|
}
|
|
736
852
|
}
|
|
737
853
|
},
|
|
@@ -743,6 +859,8 @@ class GuardVideoPlayer {
|
|
|
743
859
|
this.log('HLS manifest parsed', { levels: data.levels.map((l) => l.height + 'p') });
|
|
744
860
|
this.setState(exports.PlayerState.READY);
|
|
745
861
|
this.config.onReady();
|
|
862
|
+
this.retryBackoff = 1000;
|
|
863
|
+
this.networkRetryCount = 0;
|
|
746
864
|
if (this.config.autoplay)
|
|
747
865
|
this.play();
|
|
748
866
|
});
|
|
@@ -758,28 +876,77 @@ class GuardVideoPlayer {
|
|
|
758
876
|
this.currentQuality = quality;
|
|
759
877
|
this.log('Quality switched to ' + quality.name);
|
|
760
878
|
this.config.onQualityChange(quality.name);
|
|
879
|
+
this.eventTracker?.track('quality_change', this.videoElement.currentTime, {
|
|
880
|
+
level: data.level,
|
|
881
|
+
height: level.height,
|
|
882
|
+
bitrate: level.bitrate,
|
|
883
|
+
});
|
|
761
884
|
});
|
|
762
885
|
this.hls.on(Hls.Events.ERROR, (_event, data) => {
|
|
763
886
|
this.error('HLS Error', data);
|
|
887
|
+
this.eventTracker?.track('error', this.videoElement.currentTime, {
|
|
888
|
+
type: data.type,
|
|
889
|
+
details: data.details,
|
|
890
|
+
fatal: data.fatal,
|
|
891
|
+
});
|
|
892
|
+
if (this.destroyed)
|
|
893
|
+
return;
|
|
894
|
+
if (Date.now() < this.rateLimitCooldownUntil) {
|
|
895
|
+
this.log('Suppressing retry — rate limit cooldown active (' +
|
|
896
|
+
Math.ceil((this.rateLimitCooldownUntil - Date.now()) / 1000) + 's remaining)');
|
|
897
|
+
if (data.fatal) {
|
|
898
|
+
const delay = this.rateLimitCooldownUntil - Date.now() + 500;
|
|
899
|
+
this.scheduleRetry(() => this.hls?.startLoad(), delay);
|
|
900
|
+
}
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const httpStatus = data.response?.code;
|
|
904
|
+
if (httpStatus === 429) {
|
|
905
|
+
this.enterRateLimitCooldown();
|
|
906
|
+
this.log('429 detected — entering cooldown for ' + this.retryBackoff + 'ms');
|
|
907
|
+
this.scheduleRetry(() => {
|
|
908
|
+
if (this.embedToken?.forensicWatermark) {
|
|
909
|
+
this.refreshNonce().then(() => this.hls?.startLoad()).catch(() => this.hls?.startLoad());
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
this.hls?.startLoad();
|
|
913
|
+
}
|
|
914
|
+
}, this.retryBackoff);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
764
917
|
if (data.type === Hls.ErrorTypes.NETWORK_ERROR &&
|
|
765
918
|
data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR &&
|
|
766
919
|
this.embedToken?.forensicWatermark) {
|
|
767
|
-
this.
|
|
920
|
+
this.networkRetryCount++;
|
|
921
|
+
if (this.networkRetryCount > this.MAX_NETWORK_RETRIES) {
|
|
922
|
+
this.error('Max network retries exceeded for playlist load');
|
|
923
|
+
this.handleError({ code: 'NETWORK_ERROR', message: 'Playlist load failed after multiple retries', fatal: true, details: data });
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
this.log('Playlist load failed — refreshing nonce before retry (' + this.networkRetryCount + '/' + this.MAX_NETWORK_RETRIES + ')');
|
|
768
927
|
this.refreshNonce().then(() => {
|
|
769
|
-
|
|
928
|
+
this.scheduleRetry(() => this.hls?.startLoad(), this.retryBackoff);
|
|
770
929
|
}).catch(() => {
|
|
771
|
-
|
|
930
|
+
this.scheduleRetry(() => this.hls?.startLoad(), this.retryBackoff);
|
|
772
931
|
});
|
|
932
|
+
this.retryBackoff = Math.min(this.retryBackoff * 1.5, this.MAX_BACKOFF);
|
|
773
933
|
return;
|
|
774
934
|
}
|
|
775
935
|
if (data.fatal) {
|
|
776
936
|
switch (data.type) {
|
|
777
937
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
|
778
|
-
this.
|
|
938
|
+
this.networkRetryCount++;
|
|
939
|
+
if (this.networkRetryCount > this.MAX_NETWORK_RETRIES) {
|
|
940
|
+
this.error('Max network retries exceeded — giving up');
|
|
941
|
+
this.handleError({ code: 'NETWORK_ERROR', message: 'Network error after multiple retries. Please check your connection.', fatal: true, details: data });
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
this.error('Network error, attempting recovery (' + this.networkRetryCount + '/' + this.MAX_NETWORK_RETRIES + ')...');
|
|
779
945
|
if (this.embedToken?.forensicWatermark) {
|
|
780
946
|
this.refreshNonce().catch(() => { });
|
|
781
947
|
}
|
|
782
|
-
|
|
948
|
+
this.scheduleRetry(() => this.hls?.startLoad(), this.retryBackoff);
|
|
949
|
+
this.retryBackoff = Math.min(this.retryBackoff * 1.5, this.MAX_BACKOFF);
|
|
783
950
|
break;
|
|
784
951
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
|
785
952
|
this.error('Media error, attempting recovery...');
|
|
@@ -793,8 +960,21 @@ class GuardVideoPlayer {
|
|
|
793
960
|
this.setupVideoEventListeners();
|
|
794
961
|
}
|
|
795
962
|
setupVideoEventListeners() {
|
|
796
|
-
this.videoElement.addEventListener('playing', () =>
|
|
797
|
-
|
|
963
|
+
this.videoElement.addEventListener('playing', () => {
|
|
964
|
+
this.setState(exports.PlayerState.PLAYING);
|
|
965
|
+
if (this.hls)
|
|
966
|
+
this.hls.startLoad(-1);
|
|
967
|
+
});
|
|
968
|
+
this.videoElement.addEventListener('pause', () => {
|
|
969
|
+
this.setState(exports.PlayerState.PAUSED);
|
|
970
|
+
if (this.hls && !this.videoElement.seeking) {
|
|
971
|
+
this.hls.stopLoad();
|
|
972
|
+
if (this.pendingRetryTimer) {
|
|
973
|
+
clearTimeout(this.pendingRetryTimer);
|
|
974
|
+
this.pendingRetryTimer = null;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
});
|
|
798
978
|
this.videoElement.addEventListener('waiting', () => this.setState(exports.PlayerState.BUFFERING));
|
|
799
979
|
this.videoElement.addEventListener('error', () => {
|
|
800
980
|
const error = this.videoElement.error;
|
|
@@ -821,6 +1001,8 @@ class GuardVideoPlayer {
|
|
|
821
1001
|
}
|
|
822
1002
|
async play() {
|
|
823
1003
|
try {
|
|
1004
|
+
this.networkRetryCount = 0;
|
|
1005
|
+
this.retryBackoff = 1000;
|
|
824
1006
|
await this.videoElement.play();
|
|
825
1007
|
}
|
|
826
1008
|
catch (err) {
|
|
@@ -857,6 +1039,15 @@ class GuardVideoPlayer {
|
|
|
857
1039
|
getState() { return this.state; }
|
|
858
1040
|
destroy() {
|
|
859
1041
|
this.log('Destroying player');
|
|
1042
|
+
this.destroyed = true;
|
|
1043
|
+
if (this.pendingRetryTimer) {
|
|
1044
|
+
clearTimeout(this.pendingRetryTimer);
|
|
1045
|
+
this.pendingRetryTimer = null;
|
|
1046
|
+
}
|
|
1047
|
+
if (this.nonceRefreshTimer) {
|
|
1048
|
+
clearTimeout(this.nonceRefreshTimer);
|
|
1049
|
+
this.nonceRefreshTimer = null;
|
|
1050
|
+
}
|
|
860
1051
|
if (this.eventTracker) {
|
|
861
1052
|
this.eventTracker.destroy();
|
|
862
1053
|
this.eventTracker = null;
|
|
@@ -2567,6 +2758,7 @@ class PlayerUI {
|
|
|
2567
2758
|
}
|
|
2568
2759
|
play() { return this.corePlayer.play(); }
|
|
2569
2760
|
pause() { return this.corePlayer.pause(); }
|
|
2761
|
+
stopLoading() { return this.corePlayer.stopLoading(); }
|
|
2570
2762
|
seek(t) { return this.corePlayer.seek(t); }
|
|
2571
2763
|
getCurrentTime() { return this.corePlayer.getCurrentTime(); }
|
|
2572
2764
|
getDuration() { return this.corePlayer.getDuration(); }
|
|
@@ -2626,6 +2818,7 @@ const GuardVideoPlayerComponent = React.forwardRef((props, ref) => {
|
|
|
2626
2818
|
React.useImperativeHandle(ref, () => ({
|
|
2627
2819
|
play: () => uiRef.current?.play() ?? Promise.resolve(),
|
|
2628
2820
|
pause: () => uiRef.current?.pause(),
|
|
2821
|
+
stopLoading: () => uiRef.current?.stopLoading(),
|
|
2629
2822
|
seek: (t) => uiRef.current?.seek(t),
|
|
2630
2823
|
getCurrentTime: () => uiRef.current?.getCurrentTime() ?? 0,
|
|
2631
2824
|
getDuration: () => uiRef.current?.getDuration() ?? 0,
|