senza-sdk 4.2.65-90c49ac.0 → 4.2.65-a3f8c5a.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/bundle.js +1 -1
- package/dist/bundle.js.LICENSE.txt +4 -0
- package/package.json +3 -2
- package/src/devSequence.js +35 -0
- package/src/lifecycle.js +24 -34
- package/src/remotePlayer.js +95 -26
- package/src/senzaShakaPlayer.js +86 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "senza-sdk",
|
|
3
|
-
"version": "4.2.65-
|
|
3
|
+
"version": "4.2.65-a3f8c5a.0",
|
|
4
4
|
"main": "./src/api.js",
|
|
5
5
|
"description": "API for Senza application",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"shaka-player": "^4.12.5"
|
|
53
|
+
"shaka-player": "^4.12.5",
|
|
54
|
+
"moment": "^2.30.1"
|
|
54
55
|
}
|
|
55
56
|
}
|
package/src/devSequence.js
CHANGED
|
@@ -204,6 +204,7 @@ const setupSequence = (components, items) => {
|
|
|
204
204
|
};
|
|
205
205
|
methodInject(components, inject, (name) => name.startsWith("_") || name.startsWith("get"));
|
|
206
206
|
handleKeys(items);
|
|
207
|
+
playersEvents(components, items);
|
|
207
208
|
sdkLogger.log("Sequence initialized.");
|
|
208
209
|
};
|
|
209
210
|
|
|
@@ -257,3 +258,37 @@ export const showSequence = (visible = true) => {
|
|
|
257
258
|
}
|
|
258
259
|
container.style.visibility = visible ? "visible" : "hidden";
|
|
259
260
|
};
|
|
261
|
+
function playersEvents(components, items) {
|
|
262
|
+
components.remotePlayer.addEventListener("videoelementattached", () => {
|
|
263
|
+
const video = components.remotePlayer._videoElement;
|
|
264
|
+
if (video && !video._devSequenceRateChangeHandler) {
|
|
265
|
+
video._devSequenceRateChangeHandler = () => {
|
|
266
|
+
items.push({
|
|
267
|
+
component: "localPlayer",
|
|
268
|
+
id: "ratechange=" + video.playbackRate,
|
|
269
|
+
time: performance.now() - currentTime
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
video.addEventListener("ratechange", video._devSequenceRateChangeHandler);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (video && !video._devSequencePlayingHandler) {
|
|
276
|
+
video._devSequencePlayingHandler = () => {
|
|
277
|
+
items.push({
|
|
278
|
+
component: "localPlayer",
|
|
279
|
+
id: "playing event",
|
|
280
|
+
time: performance.now() - currentTime
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
video.addEventListener("playing", video._devSequencePlayingHandler);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
components.remotePlayer.addEventListener("playing", () => {
|
|
287
|
+
items.push({
|
|
288
|
+
component: "remotePlayer",
|
|
289
|
+
id: "playing event",
|
|
290
|
+
time: performance.now() - currentTime
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
package/src/lifecycle.js
CHANGED
|
@@ -102,9 +102,7 @@ class Lifecycle extends EventTarget {
|
|
|
102
102
|
* @private
|
|
103
103
|
*/
|
|
104
104
|
this._isInitialized = false;
|
|
105
|
-
this.
|
|
106
|
-
this._inTransitionToBackground = false;
|
|
107
|
-
this._inTransitionToStandby = false;
|
|
105
|
+
this._inTransition = false;
|
|
108
106
|
|
|
109
107
|
/**
|
|
110
108
|
* Event listeners manager for the userdisconnected event
|
|
@@ -386,7 +384,7 @@ class Lifecycle extends EventTarget {
|
|
|
386
384
|
// This api is part of epic HSDEV-713
|
|
387
385
|
_moveToUiStandby() {
|
|
388
386
|
if (window.cefQuery) {
|
|
389
|
-
this.
|
|
387
|
+
this._inTransition = true;
|
|
390
388
|
return new Promise((resolve, reject) => {
|
|
391
389
|
const FCID = getFCID();
|
|
392
390
|
const request = { target: "TC", waitForResponse: false, internalAction: "uiExit", message: JSON.stringify({ type: "uiStandbyRequest", fcid: FCID }) };
|
|
@@ -397,12 +395,12 @@ class Lifecycle extends EventTarget {
|
|
|
397
395
|
persistent: false,
|
|
398
396
|
onSuccess: () => {
|
|
399
397
|
logger.log("[ moveToUiStandby ] moveToUiStandby successfully sent");
|
|
400
|
-
this.
|
|
398
|
+
this._inTransition = false;
|
|
401
399
|
resolve(true);
|
|
402
400
|
},
|
|
403
401
|
onFailure: (code, msg) => {
|
|
404
402
|
logger.error(`[ moveToUiStandby ] moveToUiStandby failed: ${code} ${msg}`);
|
|
405
|
-
this.
|
|
403
|
+
this._inTransition = false;
|
|
406
404
|
reject(`moveToUiStandby failed: ${code} ${msg}`);
|
|
407
405
|
}
|
|
408
406
|
});
|
|
@@ -536,14 +534,6 @@ class Lifecycle extends EventTarget {
|
|
|
536
534
|
this._countdown = null;
|
|
537
535
|
}
|
|
538
536
|
|
|
539
|
-
/**
|
|
540
|
-
* @private
|
|
541
|
-
*/
|
|
542
|
-
_isInTransition() {
|
|
543
|
-
return this._inTransitionToForeground || this._inTransitionToBackground || this._inTransitionToStandby;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
|
|
547
537
|
/**
|
|
548
538
|
* @deprecated use lifecycle.state instead.
|
|
549
539
|
* Async function that returns the ui lifecycle state
|
|
@@ -586,19 +576,14 @@ class Lifecycle extends EventTarget {
|
|
|
586
576
|
*/
|
|
587
577
|
moveToForeground() {
|
|
588
578
|
if (window.cefQuery) {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
sdkLogger.warn(`lifecycle moveToForeground: No need to transition to foreground, state: ${this._state} transition: ${inTransition}`);
|
|
579
|
+
if (this._inTransition || this._state === this.UiState.FOREGROUND || this._state === this.UiState.IN_TRANSITION_TO_FOREGROUND) {
|
|
580
|
+
sdkLogger.warn(`lifecycle moveToForeground: No need to transition to foreground, state: ${this._state} transition: ${this._inTransition}`);
|
|
592
581
|
return Promise.resolve(false);
|
|
593
582
|
}
|
|
594
|
-
this.
|
|
583
|
+
this._inTransition = true;
|
|
595
584
|
alarmManager._moveToForegroundCalled();
|
|
596
585
|
const FCID = getFCID();
|
|
597
586
|
if (this._remotePlayerApiVersion >= 2) {
|
|
598
|
-
// Only update to playing UI if we started seeking in ABR. But, if we are seeking while already paused, keep the target seek state as is.
|
|
599
|
-
if (remotePlayer._isSeekingByApplication && remotePlayer._targetSeekPlayingState === TargetPlayingState.PLAYING_ABR) {
|
|
600
|
-
remotePlayer._targetSeekPlayingState = TargetPlayingState.PLAYING_UI;
|
|
601
|
-
}
|
|
602
587
|
return new Promise((resolve, reject) => {
|
|
603
588
|
const FCID = getFCID();
|
|
604
589
|
const logger = sdkLogger.withFields({ FCID });
|
|
@@ -618,14 +603,14 @@ class Lifecycle extends EventTarget {
|
|
|
618
603
|
onSuccess: () => {
|
|
619
604
|
const duration = Date.now() - timeBeforeSendingRequest;
|
|
620
605
|
logger.withFields({ duration }).log(`stop completed successfully after ${duration} ms`);
|
|
621
|
-
this.
|
|
606
|
+
this._inTransition = false;
|
|
622
607
|
timerId = clearTimer(timerId);
|
|
623
608
|
resolve(true);
|
|
624
609
|
},
|
|
625
610
|
onFailure: (code, msg) => {
|
|
626
611
|
const duration = Date.now() - timeBeforeSendingRequest;
|
|
627
612
|
logger.withFields({ duration }).log(`stop failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
|
|
628
|
-
this.
|
|
613
|
+
this._inTransition = false;
|
|
629
614
|
timerId = clearTimer(timerId);
|
|
630
615
|
reject(new SenzaError(code, msg));
|
|
631
616
|
}
|
|
@@ -634,7 +619,7 @@ class Lifecycle extends EventTarget {
|
|
|
634
619
|
const timeout = this._remotePlayerConfirmationTimeout + 1000;
|
|
635
620
|
timerId = setTimeout(() => {
|
|
636
621
|
logger.log(`stop reached timeout of ${timeout} ms, canceling query id ${queryId}`);
|
|
637
|
-
this.
|
|
622
|
+
this._inTransition = false;
|
|
638
623
|
window.cefQueryCancel(queryId);
|
|
639
624
|
reject(new SenzaError(6000, `stop reached timeout of ${timeout} ms`));
|
|
640
625
|
}, timeout, queryId);
|
|
@@ -649,11 +634,11 @@ class Lifecycle extends EventTarget {
|
|
|
649
634
|
persistent: false,
|
|
650
635
|
onSuccess: () => {
|
|
651
636
|
logger.log("uiActiveRequest successfully sent");
|
|
652
|
-
this.
|
|
637
|
+
this._inTransition = false;
|
|
653
638
|
resolve(true);
|
|
654
639
|
},
|
|
655
640
|
onFailure: (code, msg) => {
|
|
656
|
-
this.
|
|
641
|
+
this._inTransition = false;
|
|
657
642
|
logger.error(`uiActiveRequest failed: ${code} ${msg}`);
|
|
658
643
|
reject(`uiActiveRequest failed: ${code} ${msg}`);
|
|
659
644
|
}
|
|
@@ -666,6 +651,10 @@ class Lifecycle extends EventTarget {
|
|
|
666
651
|
|
|
667
652
|
_moveToBackground() {
|
|
668
653
|
if (window.cefQuery) {
|
|
654
|
+
if (this._inTransition || this._state === this.UiState.BACKGROUND || this._state === this.UiState.IN_TRANSITION_TO_BACKGROUND) {
|
|
655
|
+
sdkLogger.warn(`lifecycle moveToBackground: No need to transition to background, state: ${this._state} transition: ${this._inTransition}`);
|
|
656
|
+
return Promise.resolve(false);
|
|
657
|
+
}
|
|
669
658
|
// If audio sync is disabled, we only need to sync before remote player starts playing
|
|
670
659
|
if (!isAudioSyncConfigured()) {
|
|
671
660
|
remotePlayer._syncRemotePlayerWithLocalPlayer();
|
|
@@ -677,7 +666,7 @@ class Lifecycle extends EventTarget {
|
|
|
677
666
|
return this._moveToUiStandby();
|
|
678
667
|
}
|
|
679
668
|
|
|
680
|
-
this.
|
|
669
|
+
this._inTransition = true;
|
|
681
670
|
return new Promise((resolve, reject) => {
|
|
682
671
|
const FCID = getFCID();
|
|
683
672
|
const logger = sdkLogger.withFields({ FCID });
|
|
@@ -718,14 +707,14 @@ class Lifecycle extends EventTarget {
|
|
|
718
707
|
onSuccess: () => {
|
|
719
708
|
const duration = Date.now() - timeBeforeSendingRequest;
|
|
720
709
|
logger.withFields({ duration }).log(`play completed successfully after ${duration} ms`);
|
|
721
|
-
this.
|
|
710
|
+
this._inTransition = false;
|
|
722
711
|
timerId = clearTimer(timerId);
|
|
723
712
|
resolve();
|
|
724
713
|
},
|
|
725
714
|
onFailure: (code, msg) => {
|
|
726
715
|
const duration = Date.now() - timeBeforeSendingRequest;
|
|
727
716
|
logger.withFields({ duration }).log(`play failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
|
|
728
|
-
this.
|
|
717
|
+
this._inTransition = false;
|
|
729
718
|
timerId = clearTimer(timerId);
|
|
730
719
|
reject(new SenzaError(code, msg));
|
|
731
720
|
}
|
|
@@ -735,7 +724,7 @@ class Lifecycle extends EventTarget {
|
|
|
735
724
|
const timeout = this._remotePlayerConfirmationTimeout + 1000;
|
|
736
725
|
timerId = setTimeout(() => {
|
|
737
726
|
logger.log(`play reached timeout of ${timeout} ms, canceling query id ${queryId}`);
|
|
738
|
-
this.
|
|
727
|
+
this._inTransition = false;
|
|
739
728
|
window.cefQueryCancel(queryId);
|
|
740
729
|
reject(new SenzaError(6000, `play reached timeout of ${timeout} ms`));
|
|
741
730
|
}, timeout, queryId);
|
|
@@ -759,11 +748,11 @@ class Lifecycle extends EventTarget {
|
|
|
759
748
|
*/
|
|
760
749
|
moveToBackground() {
|
|
761
750
|
if (window.cefQuery) {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
sdkLogger.warn(`lifecycle moveToBackground: No need to transition to background, state: ${this._state} transition: ${inTransition}`);
|
|
751
|
+
if (this._inTransition || this._state === this.UiState.BACKGROUND || this._state === this.UiState.IN_TRANSITION_TO_BACKGROUND) {
|
|
752
|
+
sdkLogger.warn(`lifecycle moveToBackground: No need to transition to background, state: ${this._state} transition: ${this._inTransition}`);
|
|
765
753
|
return Promise.resolve(false);
|
|
766
754
|
}
|
|
755
|
+
|
|
767
756
|
if (remotePlayer._isSeekingByApplication) {
|
|
768
757
|
remotePlayer._targetSeekPlayingState = TargetPlayingState.PLAYING_ABR;
|
|
769
758
|
return Promise.resolve(true);
|
|
@@ -795,6 +784,7 @@ class Lifecycle extends EventTarget {
|
|
|
795
784
|
}
|
|
796
785
|
|
|
797
786
|
// TODO: Need to discuss checking for a valid tenantId in the Senza platform
|
|
787
|
+
remotePlayer._blackoutTime = undefined;
|
|
798
788
|
const contentHubTenantId = getPlatformInfo().sessionInfo?.homeSessionInfo?.tenantInfo?.contentHubTenantId;
|
|
799
789
|
const homeTenantId = getPlatformInfo().sessionInfo?.homeSessionInfo?.tenantId;
|
|
800
790
|
sdkLogger.log(`SwitchTenant for ${tenantId}`);
|
package/src/remotePlayer.js
CHANGED
|
@@ -68,6 +68,7 @@ function setPlaybackInfo(playbackInfo) {
|
|
|
68
68
|
* @typedef {Object} Config
|
|
69
69
|
* @property {string} preferredAudioLanguage
|
|
70
70
|
* @property {string} preferredSubtitlesLanguage
|
|
71
|
+
* @property {number} minSuggestedPresentationDelay - minimal delay allowed for live playback in seconds
|
|
71
72
|
* @property {boolean} autoPlay - (Not implemented yet) upon loading start playing automatically
|
|
72
73
|
*/
|
|
73
74
|
|
|
@@ -78,9 +79,9 @@ function setPlaybackInfo(playbackInfo) {
|
|
|
78
79
|
* @fires ended
|
|
79
80
|
* @fires error
|
|
80
81
|
* @fires onloadmodechange
|
|
82
|
+
* @fires playing
|
|
81
83
|
* @fires seeking (Not implemented yet)
|
|
82
84
|
* @fires seeked (Not implemented yet)
|
|
83
|
-
* @fires loadedmetadata (Not implemented yet)
|
|
84
85
|
*/
|
|
85
86
|
class RemotePlayer extends EventTarget {
|
|
86
87
|
constructor() {
|
|
@@ -91,7 +92,8 @@ class RemotePlayer extends EventTarget {
|
|
|
91
92
|
*/
|
|
92
93
|
this._config = {
|
|
93
94
|
preferredAudioLanguage: "",
|
|
94
|
-
preferredSubtitlesLanguage: ""
|
|
95
|
+
preferredSubtitlesLanguage: "",
|
|
96
|
+
minSuggestedPresentationDelay: 0
|
|
95
97
|
};
|
|
96
98
|
/**
|
|
97
99
|
* @type {string}
|
|
@@ -161,6 +163,13 @@ class RemotePlayer extends EventTarget {
|
|
|
161
163
|
*/
|
|
162
164
|
this._isPlaying = false;
|
|
163
165
|
|
|
166
|
+
/**
|
|
167
|
+
* @type {string}
|
|
168
|
+
* @description the last set blackout time as epoch seconds.
|
|
169
|
+
* @private
|
|
170
|
+
*/
|
|
171
|
+
this._blackoutTime = undefined;
|
|
172
|
+
|
|
164
173
|
/**
|
|
165
174
|
* @event RemotePlayer#canplay
|
|
166
175
|
* @description canplay event will be dispatched when the remote player can start play the event
|
|
@@ -524,10 +533,10 @@ class RemotePlayer extends EventTarget {
|
|
|
524
533
|
|
|
525
534
|
/** setting values for properties in the player configuration using an object.
|
|
526
535
|
* If the config does not support a property this is a no-op.
|
|
527
|
-
* @param {
|
|
536
|
+
* @param {Config} props the object with all the different properties to change.
|
|
528
537
|
* @example
|
|
529
538
|
* remotePlayer.configure({ preferredAudioLanguage: 'en-US' })
|
|
530
|
-
*
|
|
539
|
+
* remotePlayer.configure({ minSuggestedPresentationDelay: 6 })
|
|
531
540
|
* */
|
|
532
541
|
configure(props) {
|
|
533
542
|
Object.entries(props).forEach(([key, value]) => {
|
|
@@ -896,6 +905,9 @@ class RemotePlayer extends EventTarget {
|
|
|
896
905
|
this._updateSeekListeners(video);
|
|
897
906
|
}
|
|
898
907
|
this._videoElement = video;
|
|
908
|
+
|
|
909
|
+
// Emit a custom event to notify about the attachment of the video element
|
|
910
|
+
this.dispatchEvent(new Event("videoelementattached"));
|
|
899
911
|
}
|
|
900
912
|
}
|
|
901
913
|
|
|
@@ -911,7 +923,7 @@ class RemotePlayer extends EventTarget {
|
|
|
911
923
|
/** Tell the remote player to load the given URL.
|
|
912
924
|
* @param {string} url url to load
|
|
913
925
|
* @param {number} [position] start position in seconds (if not provided, start from beginning (VOD) or current time (LTV))
|
|
914
|
-
|
|
926
|
+
* @returns {Promise}
|
|
915
927
|
* @throws {RemotePlayerError} error object contains code & msg
|
|
916
928
|
*
|
|
917
929
|
* */
|
|
@@ -946,6 +958,7 @@ class RemotePlayer extends EventTarget {
|
|
|
946
958
|
this._abortSetAudioLanguage = true;
|
|
947
959
|
this._abortSetSubtitleLanguage = true;
|
|
948
960
|
this._abortSeeking = true;
|
|
961
|
+
this._blackoutTime = undefined;
|
|
949
962
|
if (reset) {
|
|
950
963
|
this._reset();
|
|
951
964
|
}
|
|
@@ -976,6 +989,11 @@ class RemotePlayer extends EventTarget {
|
|
|
976
989
|
message.action = "load";
|
|
977
990
|
message.audioLanguage = audioLanguage;
|
|
978
991
|
message.subtitlesLanguage = subtitlesLanguage;
|
|
992
|
+
if (this.getConfiguration().minSuggestedPresentationDelay > 0) {
|
|
993
|
+
message.cloudPlayerParams = {
|
|
994
|
+
"mspd": this.getConfiguration().minSuggestedPresentationDelay
|
|
995
|
+
};
|
|
996
|
+
}
|
|
979
997
|
} else {
|
|
980
998
|
message.type = "setPlayableUri";
|
|
981
999
|
}
|
|
@@ -1133,13 +1151,8 @@ class RemotePlayer extends EventTarget {
|
|
|
1133
1151
|
|
|
1134
1152
|
// If seeking in progress, wait for seek to complete before playing
|
|
1135
1153
|
if (this._isSeekingByApplication) {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
this._targetSeekPlayingState = TargetPlayingState.PLAYING_UI;
|
|
1139
|
-
} else {
|
|
1140
|
-
sdkLogger.info("application requesting play during seek. setting targetSeekPlayingState to PLAYING_ABR");
|
|
1141
|
-
this._targetSeekPlayingState = TargetPlayingState.PLAYING_ABR;
|
|
1142
|
-
}
|
|
1154
|
+
sdkLogger.info("application requesting play during seek");
|
|
1155
|
+
this._targetSeekPlayingState = TargetPlayingState.PLAYING_UI;
|
|
1143
1156
|
return Promise.resolve(true);
|
|
1144
1157
|
}
|
|
1145
1158
|
/*
|
|
@@ -1557,6 +1570,62 @@ class RemotePlayer extends EventTarget {
|
|
|
1557
1570
|
}
|
|
1558
1571
|
}
|
|
1559
1572
|
|
|
1573
|
+
setBlackoutTime(blackoutTime) {
|
|
1574
|
+
|
|
1575
|
+
if (!this._isInitialized) {
|
|
1576
|
+
throw new RemotePlayerError(6500, "Cannot call setBlackoutTime() if remote player is not initialized");
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
if (this._loadMode !== this.LoadMode.LOADED) {
|
|
1580
|
+
throw new RemotePlayerError(6001, "Cannot call setBlackoutTime() if player is not loaded");
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
if (window.cefQuery) {
|
|
1584
|
+
const FCID = getFCID();
|
|
1585
|
+
const logger = sdkLogger.withFields({ FCID });
|
|
1586
|
+
logger.log("remotePlayer setBlackoutTime: sending screenBlackout action");
|
|
1587
|
+
const message = {
|
|
1588
|
+
type: "remotePlayer.screenBlackout",
|
|
1589
|
+
class: "remotePlayer",
|
|
1590
|
+
action: "screenBlackout",
|
|
1591
|
+
fcid: FCID,
|
|
1592
|
+
blackoutTime
|
|
1593
|
+
};
|
|
1594
|
+
const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
|
|
1595
|
+
return new Promise((resolve, reject) => {
|
|
1596
|
+
let timerId = 0;
|
|
1597
|
+
const timeBeforeSendingRequest = Date.now();
|
|
1598
|
+
const queryId = window.cefQuery({
|
|
1599
|
+
request: JSON.stringify(request),
|
|
1600
|
+
persistent: false,
|
|
1601
|
+
onSuccess: () => {
|
|
1602
|
+
this._blackoutTime = blackoutTime;
|
|
1603
|
+
const duration = Date.now() - timeBeforeSendingRequest;
|
|
1604
|
+
logger.withFields({ duration }).log(`setBlackoutTime completed successfully after ${duration} ms`);
|
|
1605
|
+
timerId = clearTimer(timerId);
|
|
1606
|
+
resolve();
|
|
1607
|
+
},
|
|
1608
|
+
onFailure: (code, msg) => {
|
|
1609
|
+
const duration = Date.now() - timeBeforeSendingRequest;
|
|
1610
|
+
logger.withFields({ duration }).log(`setBlackoutTime failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
|
|
1611
|
+
timerId = clearTimer(timerId);
|
|
1612
|
+
reject(new RemotePlayerError(code, msg));
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
logger.log(`window.cefQuery for setBlackoutTime returned query id ${queryId}`);
|
|
1617
|
+
const timeout = this._remotePlayerConfirmationTimeout + 1000;
|
|
1618
|
+
timerId = setTimeout(() => {
|
|
1619
|
+
logger.log(`setBlackoutTime reached timeout of ${timeout} ms, canceling query id ${queryId}`);
|
|
1620
|
+
window.cefQueryCancel(queryId);
|
|
1621
|
+
reject(new RemotePlayerError(6000, `setBlackoutTime reached timeout of ${timeout} ms`));
|
|
1622
|
+
}, timeout, queryId);
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
sdkLogger.error("remotePlayer setBlackoutTime: window.cefQuery is undefined");
|
|
1626
|
+
return Promise.resolve(undefined);
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1560
1629
|
/**
|
|
1561
1630
|
* Getter/Setter for currentTime
|
|
1562
1631
|
*/
|
|
@@ -1661,7 +1730,9 @@ class RemotePlayer extends EventTarget {
|
|
|
1661
1730
|
}
|
|
1662
1731
|
}
|
|
1663
1732
|
|
|
1664
|
-
|
|
1733
|
+
// Only allow seeking in foreground. Still ignore the initialized local player seeking event above
|
|
1734
|
+
if (this._remotePlayerApiVersion >= 2 && !this._isSeekingByPlatform && !this._isSeekingByApplication &&
|
|
1735
|
+
(lifecycle.state === lifecycle.UiState.FOREGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_FOREGROUND)) {
|
|
1665
1736
|
this._atomicSeek();
|
|
1666
1737
|
} else {
|
|
1667
1738
|
sdkLogger.info(`Seeking: skipping seeking event to currentTime: ${playbackPosition}, internalSeek: ${this._isSeekingByPlatform}, localPlayerSeek: ${this._isSeekingByApplication}, state: ${lifecycle.state}`);
|
|
@@ -1680,16 +1751,16 @@ class RemotePlayer extends EventTarget {
|
|
|
1680
1751
|
* */
|
|
1681
1752
|
async _atomicSeek() {
|
|
1682
1753
|
sdkLogger.info("Seeking: local video element seeking start while isPlaying: ", this._isPlaying);
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1754
|
+
|
|
1755
|
+
// Initialize the target playing state unless changed during the seek process
|
|
1756
|
+
// In the future, we should allow for seeking in background. Currently, there's no
|
|
1757
|
+
// way to know when the web application will call moveToForeground (i.e Before/After seek)
|
|
1758
|
+
// Therefore, for now, we will assume the target is either paused or playing in ui unless
|
|
1759
|
+
// specifically receiving a moveToBackground during the process.
|
|
1760
|
+
// if (this._isPlaying && (lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND)) {
|
|
1761
|
+
// this._targetSeekPlayingState = TargetPlayingState.PLAYING_ABR;
|
|
1762
|
+
// }
|
|
1763
|
+
this._targetSeekPlayingState = this._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
|
|
1693
1764
|
|
|
1694
1765
|
// The platform could be currently syncing audio/video using playback rate. Reset when performing seek.
|
|
1695
1766
|
if (this._videoElement) {
|
|
@@ -1756,7 +1827,7 @@ class RemotePlayer extends EventTarget {
|
|
|
1756
1827
|
|
|
1757
1828
|
// If in TargetPlayingState.PAUSE, no need to resume.
|
|
1758
1829
|
// Resume without awaiting to avoid blocking the seek process anymore
|
|
1759
|
-
// In case where we aborted
|
|
1830
|
+
// In case where we aborted, we don't want to resume playback.
|
|
1760
1831
|
if (!this._abortSeeking) {
|
|
1761
1832
|
if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_UI) {
|
|
1762
1833
|
if (!this._isAudioSyncEnabled()) {
|
|
@@ -1765,8 +1836,6 @@ class RemotePlayer extends EventTarget {
|
|
|
1765
1836
|
// resume audio play only if _isAudioSyncEnabled
|
|
1766
1837
|
this._play(StreamType.AUDIO);
|
|
1767
1838
|
} else if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_ABR) {
|
|
1768
|
-
// When moving back to background, we need to put the remote player back into play mode
|
|
1769
|
-
this._changePlayMode(true);
|
|
1770
1839
|
lifecycle._moveToBackground();
|
|
1771
1840
|
}
|
|
1772
1841
|
}
|
package/src/senzaShakaPlayer.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as shaka from "shaka-player";
|
|
2
2
|
import { remotePlayer, lifecycle, getPlatformInfo } from "./api";
|
|
3
3
|
import { sdkLogger, iso6393to1 } from "./utils";
|
|
4
|
+
import moment from "moment";
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
// Define custom error category
|
|
6
8
|
shaka.util.Error.Category.SENZA_PLAYER_ERROR = 50;
|
|
@@ -76,7 +78,7 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
76
78
|
* @private
|
|
77
79
|
* @type {number}
|
|
78
80
|
* @description Timeout in milliseconds to wait for playing event
|
|
79
|
-
* @default
|
|
81
|
+
* @default 4000
|
|
80
82
|
*/
|
|
81
83
|
_playingTimeout = 4000;
|
|
82
84
|
|
|
@@ -305,6 +307,45 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
305
307
|
|
|
306
308
|
}
|
|
307
309
|
|
|
310
|
+
/**
|
|
311
|
+
* @private
|
|
312
|
+
* @type {number}
|
|
313
|
+
* @description Minimum suggested presentation delay in seconds
|
|
314
|
+
* @default 15
|
|
315
|
+
*/
|
|
316
|
+
_minSuggestedPresentationDelay = 15;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Modifies the suggestedPresentationDelay in the manifest text
|
|
320
|
+
* @private
|
|
321
|
+
* @param {string} manifestText - The MPD manifest text
|
|
322
|
+
* @returns {string} - Modified manifest text , or undefined if no modification was done
|
|
323
|
+
*/
|
|
324
|
+
_updateManifestDelayIfBelowMinimum(manifestText) {
|
|
325
|
+
// Look for suggestedPresentationDelay attribute
|
|
326
|
+
const match = manifestText.match(/suggestedPresentationDelay="([^"]+)"/);
|
|
327
|
+
if (match) {
|
|
328
|
+
const durationString = match[1];
|
|
329
|
+
const duration = moment.duration(durationString);
|
|
330
|
+
const currentDelay = duration.asSeconds();
|
|
331
|
+
|
|
332
|
+
sdkLogger.info(`Found suggestedPresentationDelay in manifest: ${currentDelay.toFixed(3)}s`);
|
|
333
|
+
|
|
334
|
+
if (currentDelay < this._minSuggestedPresentationDelay) {
|
|
335
|
+
// Replace the value in the manifest text with 3 decimal places
|
|
336
|
+
manifestText = manifestText.replace(
|
|
337
|
+
/suggestedPresentationDelay="[^"]+"/,
|
|
338
|
+
`suggestedPresentationDelay="PT${this._minSuggestedPresentationDelay.toFixed(3)}S"`
|
|
339
|
+
);
|
|
340
|
+
sdkLogger.info(`Updated manifest suggestedPresentationDelay to ${this._minSuggestedPresentationDelay.toFixed(3)}s`);
|
|
341
|
+
return manifestText;
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
344
|
+
sdkLogger.info("suggestedPresentationDelay is not defined at the manifest");
|
|
345
|
+
}
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
|
|
308
349
|
/**
|
|
309
350
|
* Creates an instance of SenzaShakaPlayer, which is a subclass of shaka.Player.
|
|
310
351
|
*
|
|
@@ -332,14 +373,38 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
332
373
|
const playTimeout = getPlatformInfo()?.sessionInfo?.settings?.["ui-streamer"]?.playingEventTimeout;
|
|
333
374
|
this._playingTimeout = (playTimeout >= 0) ? playTimeout*1000 : this._playingTimeout;
|
|
334
375
|
|
|
376
|
+
// Initialize minSuggestedPresentationDelay from UI settings or use default
|
|
377
|
+
const uiSettings = getPlatformInfo()?.sessionInfo?.settings?.["ui-streamer"];
|
|
378
|
+
if (uiSettings?.minSuggestedPresentationDelay !== undefined) {
|
|
379
|
+
this._minSuggestedPresentationDelay = uiSettings.minSuggestedPresentationDelay;
|
|
380
|
+
sdkLogger.info(`Using configured minSuggestedPresentationDelay: ${this._minSuggestedPresentationDelay}s`);
|
|
381
|
+
}
|
|
382
|
+
|
|
335
383
|
// if video element is provided, add the listeres here. In this case ,there is no need to call attach.
|
|
336
384
|
if (videoElement) {
|
|
337
385
|
this._attach(videoElement);
|
|
338
386
|
sdkLogger.warn("SenzaShakaPlayer constructor Adding videoElement in the constructor is going to be deprecated in the future. Please use attach method instead.");
|
|
339
387
|
}
|
|
340
388
|
|
|
389
|
+
this.configure({
|
|
390
|
+
manifest: {
|
|
391
|
+
defaultPresentationDelay: this._minSuggestedPresentationDelay // in seconds
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
remotePlayer.configure({
|
|
396
|
+
minSuggestedPresentationDelay: this._minSuggestedPresentationDelay
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
this.addEventListener("buffering", () => {
|
|
400
|
+
if (this.videoElement) {
|
|
401
|
+
sdkLogger.info("Buffering at time:", this.videoElement.currentTime);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
341
405
|
}
|
|
342
406
|
|
|
407
|
+
|
|
343
408
|
_attach(videoElement) {
|
|
344
409
|
this.videoElement = videoElement;
|
|
345
410
|
|
|
@@ -618,16 +683,30 @@ export class SenzaShakaPlayer extends shaka.Player {
|
|
|
618
683
|
|
|
619
684
|
// This callbakc will be activated when the manifest is loaded. It will trigger load of the remote player.
|
|
620
685
|
// This will ensure that the remote player is loaded only after the manifest is loaded by local player.
|
|
621
|
-
const responseFilterCallback = async (type) => {
|
|
622
|
-
if (type === shaka.net.NetworkingEngine.RequestType.MANIFEST
|
|
623
|
-
manifestLoadHandled = true;
|
|
686
|
+
const responseFilterCallback = async (type, response) => {
|
|
687
|
+
if (type === shaka.net.NetworkingEngine.RequestType.MANIFEST) {
|
|
624
688
|
try {
|
|
625
|
-
|
|
626
|
-
|
|
689
|
+
if (response.data && this._minSuggestedPresentationDelay > 0) {
|
|
690
|
+
const manifestText = new TextDecoder().decode(response.data);
|
|
691
|
+
const modifiedText = this._updateManifestDelayIfBelowMinimum(manifestText);
|
|
692
|
+
if (modifiedText) {
|
|
693
|
+
const responseData = new TextEncoder().encode(modifiedText).buffer;
|
|
694
|
+
response.data = responseData;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
627
697
|
} catch (error) {
|
|
628
|
-
|
|
698
|
+
sdkLogger.error("Error processing manifest:", error);
|
|
629
699
|
}
|
|
630
700
|
|
|
701
|
+
if (!manifestLoadHandled) {
|
|
702
|
+
manifestLoadHandled = true;
|
|
703
|
+
try {
|
|
704
|
+
await this._remotePlayerLoad(url, startTime);
|
|
705
|
+
remoteLoadResolver();
|
|
706
|
+
} catch (error) {
|
|
707
|
+
remoteLoadRejecter(error);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
631
710
|
}
|
|
632
711
|
};
|
|
633
712
|
|