senza-sdk 4.4.1-daf0312.0 → 4.4.2-efcb53d.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 +28 -3
- package/package.json +6 -6
- package/src/api.js +3 -0
- package/src/implementation/api.js +1 -1
- package/src/implementation/lifecycle.js +41 -29
- package/src/implementation/remotePlayer.js +101 -48
- package/src/implementation/senzaShakaPlayer.js +142 -72
- package/src/implementation/utils.js +18 -206
- package/src/interface/remotePlayer.js +47 -4
- package/src/interface/version.js +1 -1
|
@@ -12,9 +12,27 @@
|
|
|
12
12
|
|
|
13
13
|
/*
|
|
14
14
|
@license
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
Copyright 2013 Ali Al Dallal
|
|
16
|
+
|
|
17
|
+
Licensed under the MIT license.
|
|
18
|
+
|
|
19
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
20
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
21
|
+
in the Software without restriction, including without limitation the rights
|
|
22
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
23
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
24
|
+
furnished to do so, subject to the following conditions:
|
|
25
|
+
|
|
26
|
+
The above copyright notice and this permission notice shall be included in
|
|
27
|
+
all copies or substantial portions of the Software.
|
|
28
|
+
|
|
29
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
30
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
31
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
32
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
33
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
34
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
35
|
+
SOFTWARE.
|
|
18
36
|
*/
|
|
19
37
|
|
|
20
38
|
/*
|
|
@@ -45,6 +63,13 @@
|
|
|
45
63
|
SPDX-License-Identifier: Apache-2.0
|
|
46
64
|
*/
|
|
47
65
|
|
|
66
|
+
/*
|
|
67
|
+
@license
|
|
68
|
+
Shaka Player
|
|
69
|
+
Copyright 2025 Google LLC
|
|
70
|
+
SPDX-License-Identifier: Apache-2.0
|
|
71
|
+
*/
|
|
72
|
+
|
|
48
73
|
/*
|
|
49
74
|
@license
|
|
50
75
|
tXml
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "senza-sdk",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.2-efcb53d.0",
|
|
4
4
|
"main": "./src/api.js",
|
|
5
5
|
"description": "API for Senza application",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,15 +53,15 @@
|
|
|
53
53
|
"statements": 93.46
|
|
54
54
|
},
|
|
55
55
|
"src/interface": {
|
|
56
|
-
"branches":
|
|
57
|
-
"functions": 39.
|
|
58
|
-
"lines": 64.
|
|
59
|
-
"statements": 65
|
|
56
|
+
"branches": 63.9,
|
|
57
|
+
"functions": 39.28,
|
|
58
|
+
"lines": 64.61,
|
|
59
|
+
"statements": 65
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"shaka-player": "
|
|
64
|
+
"shaka-player": "4.15.8",
|
|
65
65
|
"moment": "^2.30.1"
|
|
66
66
|
}
|
|
67
67
|
}
|
package/src/api.js
CHANGED
|
@@ -308,6 +308,9 @@ let ShakaPlayerImplementation;
|
|
|
308
308
|
let shakaImplementation;
|
|
309
309
|
|
|
310
310
|
if (!isRunningE2E()) {
|
|
311
|
+
// ------------------------------------------------------------------------------------------------
|
|
312
|
+
// -- Do NOT change this log, as ui-streamer is checking this string to detect abnormal behavior --
|
|
313
|
+
// ------------------------------------------------------------------------------------------------
|
|
311
314
|
sdkLogger.warn("Senza SDK is not running in E2E environment. Some features may not work as expected.");
|
|
312
315
|
|
|
313
316
|
lifecycle = new Lifecycle();
|
|
@@ -130,7 +130,7 @@ export async function init(interfaceApiVersion, showSequenceFunc, initSequenceFu
|
|
|
130
130
|
|
|
131
131
|
// Initialize lifecycle first to make sure the state is updated.
|
|
132
132
|
await lifecycle._init(sessionInfoObj?.settings?.["ui-streamer"], triggerEvent);
|
|
133
|
-
await remotePlayer._init(sessionInfoObj
|
|
133
|
+
await remotePlayer._init(sessionInfoObj, triggerEvent);
|
|
134
134
|
alarmManager._init();
|
|
135
135
|
messageManager._init();
|
|
136
136
|
sdkLogger.log("All submodules initialized");
|
|
@@ -29,7 +29,9 @@ class Lifecycle extends LifecycleInterface {
|
|
|
29
29
|
* @private
|
|
30
30
|
*/
|
|
31
31
|
this._isInitialized = false;
|
|
32
|
-
this.
|
|
32
|
+
this._inTransitionToForeground = false;
|
|
33
|
+
this._inTransitionToBackground = false;
|
|
34
|
+
this._inTransitionToStandby = false;
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* Event listeners manager for the userdisconnected event
|
|
@@ -310,10 +312,10 @@ class Lifecycle extends LifecycleInterface {
|
|
|
310
312
|
// This api is part of epic HSDEV-713
|
|
311
313
|
_moveToUiStandby() {
|
|
312
314
|
if (window.cefQuery) {
|
|
313
|
-
this.
|
|
315
|
+
this._inTransitionToStandby = true;
|
|
314
316
|
return new Promise((resolve, reject) => {
|
|
315
317
|
const FCID = getFCID();
|
|
316
|
-
const request = { target: "TC", waitForResponse: false, internalAction: "uiExit", message: JSON.stringify({ type: "uiStandbyRequest",
|
|
318
|
+
const request = { target: "TC", waitForResponse: false, internalAction: "uiExit", message: JSON.stringify({ type: "uiStandbyRequest", fcid: FCID }) };
|
|
317
319
|
const logger = sdkLogger.withFields({ FCID });
|
|
318
320
|
logger.log("lifecycle moveToUiStandby: sending uiStandbyRequest");
|
|
319
321
|
window.cefQuery({
|
|
@@ -321,12 +323,12 @@ class Lifecycle extends LifecycleInterface {
|
|
|
321
323
|
persistent: false,
|
|
322
324
|
onSuccess: () => {
|
|
323
325
|
logger.log("[ moveToUiStandby ] moveToUiStandby successfully sent");
|
|
324
|
-
this.
|
|
326
|
+
this._inTransitionToStandby = false;
|
|
325
327
|
resolve(true);
|
|
326
328
|
},
|
|
327
329
|
onFailure: (code, msg) => {
|
|
328
330
|
logger.error(`[ moveToUiStandby ] moveToUiStandby failed: ${code} ${msg}`);
|
|
329
|
-
this.
|
|
331
|
+
this._inTransitionToStandby = false;
|
|
330
332
|
reject(`moveToUiStandby failed: ${code} ${msg}`);
|
|
331
333
|
}
|
|
332
334
|
});
|
|
@@ -423,6 +425,14 @@ class Lifecycle extends LifecycleInterface {
|
|
|
423
425
|
this._countdown = null;
|
|
424
426
|
}
|
|
425
427
|
|
|
428
|
+
/**
|
|
429
|
+
* @private
|
|
430
|
+
*/
|
|
431
|
+
_isInTransition() {
|
|
432
|
+
return this._inTransitionToForeground || this._inTransitionToBackground || this._inTransitionToStandby;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
|
|
426
436
|
getState() {
|
|
427
437
|
if (window.cefQuery) {
|
|
428
438
|
return new Promise((resolve, reject) => {
|
|
@@ -445,14 +455,19 @@ class Lifecycle extends LifecycleInterface {
|
|
|
445
455
|
|
|
446
456
|
moveToForeground() {
|
|
447
457
|
if (window.cefQuery) {
|
|
448
|
-
|
|
449
|
-
|
|
458
|
+
const inTransition = this._isInTransition();
|
|
459
|
+
if (inTransition || this._state === this.UiState.FOREGROUND || this._state === this.UiState.IN_TRANSITION_TO_FOREGROUND) {
|
|
460
|
+
sdkLogger.warn(`lifecycle moveToForeground: No need to transition to foreground, state: ${this._state} transition: ${inTransition}`);
|
|
450
461
|
return Promise.resolve(false);
|
|
451
462
|
}
|
|
452
|
-
this.
|
|
463
|
+
this._inTransitionToForeground = true;
|
|
453
464
|
alarmManager._moveToForegroundCalled();
|
|
454
465
|
const FCID = getFCID();
|
|
455
466
|
if (this._remotePlayerApiVersion >= 2) {
|
|
467
|
+
// 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.
|
|
468
|
+
if (remotePlayer._isSeekingByApplication && remotePlayer._targetSeekPlayingState === TargetPlayingState.PLAYING_ABR) {
|
|
469
|
+
remotePlayer._targetSeekPlayingState = TargetPlayingState.PLAYING_UI;
|
|
470
|
+
}
|
|
456
471
|
return new Promise((resolve, reject) => {
|
|
457
472
|
const FCID = getFCID();
|
|
458
473
|
const logger = sdkLogger.withFields({ FCID });
|
|
@@ -472,14 +487,14 @@ class Lifecycle extends LifecycleInterface {
|
|
|
472
487
|
onSuccess: () => {
|
|
473
488
|
const duration = Date.now() - timeBeforeSendingRequest;
|
|
474
489
|
logger.withFields({ duration }).log(`stop completed successfully after ${duration} ms`);
|
|
475
|
-
this.
|
|
490
|
+
this._inTransitionToForeground = false;
|
|
476
491
|
timerId = clearTimer(timerId);
|
|
477
492
|
resolve(true);
|
|
478
493
|
},
|
|
479
494
|
onFailure: (code, msg) => {
|
|
480
495
|
const duration = Date.now() - timeBeforeSendingRequest;
|
|
481
496
|
logger.withFields({ duration }).log(`stop failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
|
|
482
|
-
this.
|
|
497
|
+
this._inTransitionToForeground = false;
|
|
483
498
|
timerId = clearTimer(timerId);
|
|
484
499
|
reject(new SenzaError(code, msg));
|
|
485
500
|
}
|
|
@@ -488,7 +503,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
488
503
|
const timeout = this._remotePlayerConfirmationTimeout + 1000;
|
|
489
504
|
timerId = setTimeout(() => {
|
|
490
505
|
logger.log(`stop reached timeout of ${timeout} ms, canceling query id ${queryId}`);
|
|
491
|
-
this.
|
|
506
|
+
this._inTransitionToForeground = false;
|
|
492
507
|
window.cefQueryCancel(queryId);
|
|
493
508
|
reject(new SenzaError(6000, `stop reached timeout of ${timeout} ms`));
|
|
494
509
|
}, timeout, queryId);
|
|
@@ -503,11 +518,11 @@ class Lifecycle extends LifecycleInterface {
|
|
|
503
518
|
persistent: false,
|
|
504
519
|
onSuccess: () => {
|
|
505
520
|
logger.log("uiActiveRequest successfully sent");
|
|
506
|
-
this.
|
|
521
|
+
this._inTransitionToForeground = false;
|
|
507
522
|
resolve(true);
|
|
508
523
|
},
|
|
509
524
|
onFailure: (code, msg) => {
|
|
510
|
-
this.
|
|
525
|
+
this._inTransitionToForeground = false;
|
|
511
526
|
logger.error(`uiActiveRequest failed: ${code} ${msg}`);
|
|
512
527
|
reject(`uiActiveRequest failed: ${code} ${msg}`);
|
|
513
528
|
}
|
|
@@ -520,10 +535,6 @@ class Lifecycle extends LifecycleInterface {
|
|
|
520
535
|
|
|
521
536
|
_moveToBackground() {
|
|
522
537
|
if (window.cefQuery) {
|
|
523
|
-
if (this._inTransition || this._state === this.UiState.BACKGROUND || this._state === this.UiState.IN_TRANSITION_TO_BACKGROUND) {
|
|
524
|
-
sdkLogger.warn(`lifecycle moveToBackground: No need to transition to background, state: ${this._state} transition: ${this._inTransition}`);
|
|
525
|
-
return Promise.resolve(false);
|
|
526
|
-
}
|
|
527
538
|
// If audio sync is disabled, we only need to sync before remote player starts playing
|
|
528
539
|
if (!isAudioSyncConfigured()) {
|
|
529
540
|
remotePlayer._syncRemotePlayerWithLocalPlayer();
|
|
@@ -534,8 +545,8 @@ class Lifecycle extends LifecycleInterface {
|
|
|
534
545
|
if (!remotePlayer._isPlaying) {
|
|
535
546
|
return this._moveToUiStandby();
|
|
536
547
|
}
|
|
537
|
-
|
|
538
|
-
this.
|
|
548
|
+
remotePlayer._changePlayMode(true);
|
|
549
|
+
this._inTransitionToBackground = true;
|
|
539
550
|
return new Promise((resolve, reject) => {
|
|
540
551
|
const FCID = getFCID();
|
|
541
552
|
const logger = sdkLogger.withFields({ FCID });
|
|
@@ -548,14 +559,15 @@ class Lifecycle extends LifecycleInterface {
|
|
|
548
559
|
action: "play",
|
|
549
560
|
fcid: FCID,
|
|
550
561
|
audioLanguage,
|
|
551
|
-
subtitlesLanguage
|
|
562
|
+
subtitlesLanguage,
|
|
563
|
+
autoTune: remotePlayer._autoTune
|
|
552
564
|
};
|
|
553
565
|
if (this._remotePlayerApiVersion >= 2) {
|
|
554
566
|
message.type = "remotePlayer.play";
|
|
555
567
|
message.class = "remotePlayer";
|
|
556
|
-
|
|
568
|
+
// in background, client expects to get NON_SEAMLESS switch mode.
|
|
569
|
+
message.switchMode = remotePlayer._isAudioSyncEnabled() && this._state !== this.UiState.BACKGROUND ? SwitchMode.SEAMLESS : SwitchMode.NON_SEAMLESS;
|
|
557
570
|
message.streamType = remotePlayer.textTrackVisibility ? (StreamType.AUDIO | StreamType.VIDEO | StreamType.SUBTITLE) : (StreamType.AUDIO | StreamType.VIDEO);
|
|
558
|
-
message.autoPlay = remotePlayer._autoPlay;
|
|
559
571
|
request = {
|
|
560
572
|
target: "TC",
|
|
561
573
|
waitForResponse: true,
|
|
@@ -568,7 +580,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
568
580
|
}
|
|
569
581
|
request = message;
|
|
570
582
|
}
|
|
571
|
-
logger.log(`lifecycle moveToBackground: sending play action audioLanguage=${message.audioLanguage} subtitlesLanguage=${message.subtitlesLanguage} textTrackVisibility=${remotePlayer.textTrackVisibility}`);
|
|
583
|
+
logger.log(`lifecycle moveToBackground: sending play action audioLanguage=${message.audioLanguage} subtitlesLanguage=${message.subtitlesLanguage} textTrackVisibility=${remotePlayer.textTrackVisibility} autoTune=${remotePlayer._autoTune}`);
|
|
572
584
|
let timerId = 0;
|
|
573
585
|
const timeBeforeSendingRequest = Date.now();
|
|
574
586
|
const queryId = window.cefQuery({
|
|
@@ -577,14 +589,14 @@ class Lifecycle extends LifecycleInterface {
|
|
|
577
589
|
onSuccess: () => {
|
|
578
590
|
const duration = Date.now() - timeBeforeSendingRequest;
|
|
579
591
|
logger.withFields({ duration }).log(`play completed successfully after ${duration} ms`);
|
|
580
|
-
this.
|
|
592
|
+
this._inTransitionToBackground = false;
|
|
581
593
|
timerId = clearTimer(timerId);
|
|
582
594
|
resolve();
|
|
583
595
|
},
|
|
584
596
|
onFailure: (code, msg) => {
|
|
585
597
|
const duration = Date.now() - timeBeforeSendingRequest;
|
|
586
598
|
logger.withFields({ duration }).log(`play failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
|
|
587
|
-
this.
|
|
599
|
+
this._inTransitionToBackground = false;
|
|
588
600
|
timerId = clearTimer(timerId);
|
|
589
601
|
reject(new SenzaError(code, msg));
|
|
590
602
|
}
|
|
@@ -594,7 +606,7 @@ class Lifecycle extends LifecycleInterface {
|
|
|
594
606
|
const timeout = this._remotePlayerConfirmationTimeout + 1000;
|
|
595
607
|
timerId = setTimeout(() => {
|
|
596
608
|
logger.log(`play reached timeout of ${timeout} ms, canceling query id ${queryId}`);
|
|
597
|
-
this.
|
|
609
|
+
this._inTransitionToBackground = false;
|
|
598
610
|
window.cefQueryCancel(queryId);
|
|
599
611
|
reject(new SenzaError(6000, `play reached timeout of ${timeout} ms`));
|
|
600
612
|
}, timeout, queryId);
|
|
@@ -607,11 +619,11 @@ class Lifecycle extends LifecycleInterface {
|
|
|
607
619
|
|
|
608
620
|
moveToBackground() {
|
|
609
621
|
if (window.cefQuery) {
|
|
610
|
-
|
|
611
|
-
|
|
622
|
+
const inTransition = this._isInTransition();
|
|
623
|
+
if (inTransition || this._state === this.UiState.BACKGROUND || this._state === this.UiState.IN_TRANSITION_TO_BACKGROUND) {
|
|
624
|
+
sdkLogger.warn(`lifecycle moveToBackground: No need to transition to background, state: ${this._state} transition: ${inTransition}`);
|
|
612
625
|
return Promise.resolve(false);
|
|
613
626
|
}
|
|
614
|
-
|
|
615
627
|
if (remotePlayer._isSeekingByApplication) {
|
|
616
628
|
remotePlayer._targetSeekPlayingState = TargetPlayingState.PLAYING_ABR;
|
|
617
629
|
return Promise.resolve(true);
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
SeekState,
|
|
12
12
|
TargetPlayingState,
|
|
13
13
|
isSubtitlesTranslationAllowed,
|
|
14
|
-
isSubtitlesTranslationPattern
|
|
14
|
+
isSubtitlesTranslationPattern,
|
|
15
|
+
isAppVersionAboveOrEqual
|
|
15
16
|
} from "./utils";
|
|
16
17
|
import { lifecycle } from "./lifecycle";
|
|
17
18
|
import { writeLicenseResponse } from "./api";
|
|
@@ -143,10 +144,12 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
/** @private Initialize the remote player
|
|
146
|
-
* @param {Object}
|
|
147
|
+
* @param {Object} sessionObj session information object
|
|
148
|
+
* @param {Object} triggerEvent trigger event object
|
|
147
149
|
* */
|
|
148
|
-
async _init(
|
|
150
|
+
async _init(sessionObj, triggerEvent) {
|
|
149
151
|
sdkLogger.info("Initializing RemotePlayer");
|
|
152
|
+
const uiStreamerSettings = sessionObj?.settings?.["ui-streamer"];
|
|
150
153
|
let playerState = {
|
|
151
154
|
isLoaded: false,
|
|
152
155
|
playbackUrl: ""
|
|
@@ -180,7 +183,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
180
183
|
}
|
|
181
184
|
}
|
|
182
185
|
this._loadMode = playerState?.isLoaded ? this.LoadMode.LOADED : this.LoadMode.NOT_LOADED;
|
|
183
|
-
this.
|
|
186
|
+
this._autoTune = false;
|
|
184
187
|
|
|
185
188
|
this._isPlaying = window?.sessionStorage?.getItem("senzaSdk_isPlaying") === "true";
|
|
186
189
|
|
|
@@ -194,6 +197,9 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
194
197
|
this._remotePlayerConfirmationTimeout = uiStreamerSettings?.remotePlayerConfirmationTimeout ?? DEFAULT_REMOTE_PLAYER_CONFIRMATION_TIMEOUT;
|
|
195
198
|
this._remotePlayerApiVersion = uiStreamerSettings?.remotePlayerApiVersion || 1;
|
|
196
199
|
this._multiSeekDelay = uiStreamerSettings?.multiSeekDelay || MULTI_SEEK_DELAY_MSEC;
|
|
200
|
+
this._uiAvSyncIntervalMs = uiStreamerSettings?.uiAvSyncIntervalMs;
|
|
201
|
+
this._abrAvSyncIntervalMs = uiStreamerSettings?.abrAvSyncIntervalMs;
|
|
202
|
+
this._deviceAppVersion = sessionObj?.manifest?.["device-app"] || "";
|
|
197
203
|
|
|
198
204
|
sdkLogger.info(`remotePLayer isPlaying=${this._isPlaying}`);
|
|
199
205
|
|
|
@@ -205,6 +211,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
205
211
|
}
|
|
206
212
|
this._availabilityStartTime = playbackMetadata.availabilityStartTime;
|
|
207
213
|
this._updateTracks(playbackMetadata);
|
|
214
|
+
|
|
208
215
|
}
|
|
209
216
|
|
|
210
217
|
/**
|
|
@@ -528,7 +535,8 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
528
535
|
};
|
|
529
536
|
let waitForResponse = false;
|
|
530
537
|
if (this._remotePlayerApiVersion >= 2) {
|
|
531
|
-
|
|
538
|
+
// in background, client expects to get NON_SEAMLESS switch mode.
|
|
539
|
+
message.switchMode = this._isAudioSyncEnabled() && lifecycle.state !== lifecycle.UiState.BACKGROUND ? SwitchMode.SEAMLESS : SwitchMode.NON_SEAMLESS;
|
|
532
540
|
message.streamType = streamType;
|
|
533
541
|
waitForResponse = true;
|
|
534
542
|
|
|
@@ -781,6 +789,12 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
781
789
|
playbackPosition,
|
|
782
790
|
fcid: FCID
|
|
783
791
|
};
|
|
792
|
+
if (this._uiAvSyncIntervalMs !== undefined) {
|
|
793
|
+
message.uiAvSyncIntervalMs = this._uiAvSyncIntervalMs;
|
|
794
|
+
}
|
|
795
|
+
if (this._abrAvSyncIntervalMs !== undefined) {
|
|
796
|
+
message.abrAvSyncIntervalMs = this._abrAvSyncIntervalMs;
|
|
797
|
+
}
|
|
784
798
|
if (this._remotePlayerApiVersion >= 2) {
|
|
785
799
|
message.type = "remotePlayer.load";
|
|
786
800
|
message.class = "remotePlayer";
|
|
@@ -920,10 +934,11 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
920
934
|
* remotePlayer.play();
|
|
921
935
|
* lifecycle.moveToBackground();
|
|
922
936
|
*
|
|
937
|
+
* @param {boolean} [autoTune=false] Should be true when the play was triggered automatically by the app and not by a user action.
|
|
923
938
|
* @returns {Promise}
|
|
924
939
|
* @throws {RemotePlayerError} error object contains code & msg
|
|
925
940
|
*/
|
|
926
|
-
play(
|
|
941
|
+
play(autoTune = false) {
|
|
927
942
|
if (!this._isInitialized) {
|
|
928
943
|
throw new RemotePlayerError(6500, "Cannot call play() if remote player is not initialized");
|
|
929
944
|
}
|
|
@@ -934,7 +949,7 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
934
949
|
}
|
|
935
950
|
|
|
936
951
|
this._changePlayMode(true);
|
|
937
|
-
this.
|
|
952
|
+
this._autoTune = autoTune;
|
|
938
953
|
|
|
939
954
|
if (this._isSetAudioInProgress) {
|
|
940
955
|
sdkLogger.info("application requesting play during setAudioLanguage");
|
|
@@ -950,8 +965,13 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
950
965
|
|
|
951
966
|
// If seeking in progress, wait for seek to complete before playing
|
|
952
967
|
if (this._isSeekingByApplication) {
|
|
953
|
-
|
|
954
|
-
|
|
968
|
+
if (this._inTransitionToForeground || lifecycle.state === lifecycle.UiState.FOREGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_FOREGROUND) {
|
|
969
|
+
sdkLogger.info("application requesting play during seek. setting targetSeekPlayingState to PLAYING_UI");
|
|
970
|
+
this._targetSeekPlayingState = TargetPlayingState.PLAYING_UI;
|
|
971
|
+
} else {
|
|
972
|
+
sdkLogger.info("application requesting play during seek. setting targetSeekPlayingState to PLAYING_ABR");
|
|
973
|
+
this._targetSeekPlayingState = TargetPlayingState.PLAYING_ABR;
|
|
974
|
+
}
|
|
955
975
|
return Promise.resolve(true);
|
|
956
976
|
}
|
|
957
977
|
/*
|
|
@@ -1083,24 +1103,30 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1083
1103
|
* @throws {RemotePlayerError} If the player is not initialized or not loaded.
|
|
1084
1104
|
**/
|
|
1085
1105
|
selectAudioTrack(audioTrackId) {
|
|
1106
|
+
return this._selectAudioTrack(audioTrackId, false);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
_selectAudioTrack(audioTrackId, force = false) {
|
|
1086
1110
|
if (!this._isInitialized) {
|
|
1087
1111
|
throw new RemotePlayerError(6500, "Cannot call selectAudioTrack() if remote player is not initialized");
|
|
1088
1112
|
}
|
|
1089
1113
|
if (this._loadMode !== this.LoadMode.LOADED) {
|
|
1090
1114
|
throw new RemotePlayerError(6001, "Cannot call selectAudioTrack() if remote player is not loaded");
|
|
1091
1115
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1116
|
+
if (!force) {
|
|
1117
|
+
let found = false;
|
|
1118
|
+
for (const track of this.getAudioTracks()) {
|
|
1119
|
+
if (track.id === audioTrackId) {
|
|
1120
|
+
found = true;
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
if (!found) {
|
|
1125
|
+
sdkLogger.warn(`Invalid audioTrackId ${audioTrackId}`);
|
|
1126
|
+
return Promise.resolve();
|
|
1098
1127
|
}
|
|
1099
1128
|
}
|
|
1100
|
-
|
|
1101
|
-
sdkLogger.warn(`Invalid audioTrackId ${audioTrackId}`);
|
|
1102
|
-
return Promise.resolve();
|
|
1103
|
-
}
|
|
1129
|
+
const prevSelectedAudioTrack = this._selectedAudioTrack;
|
|
1104
1130
|
if (this._selectedAudioTrack === audioTrackId) {
|
|
1105
1131
|
return Promise.resolve(); // Audio language already selected
|
|
1106
1132
|
}
|
|
@@ -1117,6 +1143,12 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1117
1143
|
}
|
|
1118
1144
|
}
|
|
1119
1145
|
|
|
1146
|
+
async selectAudioLanguage(language, role="", accessibilityPurposeCode=this.AccessibilityPurposeCode.NONE) {
|
|
1147
|
+
const trackId = this._generateSenzaTrackId(language, role, accessibilityPurposeCode);
|
|
1148
|
+
return this._selectAudioTrack(trackId, true);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
|
|
1120
1152
|
/**
|
|
1121
1153
|
* Handles the asynchronous selection of an audio track.
|
|
1122
1154
|
* If the player is playing, it pauses before changing the track and resumes playback if necessary.
|
|
@@ -1210,6 +1242,11 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1210
1242
|
return Promise.resolve(undefined);
|
|
1211
1243
|
}
|
|
1212
1244
|
|
|
1245
|
+
async selectTextLanguage(language, role="", accessibilityPurposeCode=this.AccessibilityPurposeCode.NONE) {
|
|
1246
|
+
const trackId = this._generateSenzaTrackId(language, role, accessibilityPurposeCode);
|
|
1247
|
+
return this._selectTextTrack(trackId, true);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1213
1250
|
/** Select a specific text (subtitle) track.
|
|
1214
1251
|
* Track id should come from a call to getTextTracks.
|
|
1215
1252
|
* If no tracks exist - this is a no-op.
|
|
@@ -1219,24 +1256,31 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1219
1256
|
* @throws {RemotePlayerError} If the player is not initialized or not loaded.
|
|
1220
1257
|
* */
|
|
1221
1258
|
selectTextTrack(textTrackId) {
|
|
1259
|
+
return this._selectTextTrack(textTrackId, false);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
_selectTextTrack(textTrackId, force = false) {
|
|
1222
1263
|
if (!this._isInitialized) {
|
|
1223
1264
|
throw new RemotePlayerError(6500, "Cannot call selectTextTrack() if remote player is not initialized");
|
|
1224
1265
|
}
|
|
1225
1266
|
if (this._loadMode !== this.LoadMode.LOADED) {
|
|
1226
1267
|
throw new RemotePlayerError(6001, "Cannot call selectTextTrack() if remote player is not loaded");
|
|
1227
1268
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1269
|
+
if (!force) {
|
|
1270
|
+
let found = false;
|
|
1271
|
+
for (const track of this.getTextTracks()) {
|
|
1272
|
+
if (track.id === textTrackId) {
|
|
1273
|
+
found = true;
|
|
1274
|
+
break;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
if (!found) {
|
|
1278
|
+
sdkLogger.warn(`Invalid textTrackId ${textTrackId}`);
|
|
1279
|
+
return Promise.resolve();
|
|
1234
1280
|
}
|
|
1235
1281
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
return Promise.resolve();
|
|
1239
|
-
}
|
|
1282
|
+
|
|
1283
|
+
const prevSelectedTextTrack = this._selectedSubtitlesTrack;
|
|
1240
1284
|
if (this._selectedSubtitlesTrack === textTrackId) {
|
|
1241
1285
|
return Promise.resolve(); // Subtitle language already selected
|
|
1242
1286
|
}
|
|
@@ -1512,6 +1556,8 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1512
1556
|
* @private
|
|
1513
1557
|
*/
|
|
1514
1558
|
async _startSeeking(playbackPosition) {
|
|
1559
|
+
const backgroundSeekMinClientAppVersion = "25.27.8";
|
|
1560
|
+
|
|
1515
1561
|
if (this._isSetAudioInProgress || this._isSetSubtitlesInProgress) {
|
|
1516
1562
|
sdkLogger.info("Seeking not supported while setAudioLanguage or setSubtitleLanguage are in progress.");
|
|
1517
1563
|
return;
|
|
@@ -1527,10 +1573,12 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1527
1573
|
return;
|
|
1528
1574
|
}
|
|
1529
1575
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1576
|
+
const backgroundSeekSupported = isAppVersionAboveOrEqual(this._deviceAppVersion, backgroundSeekMinClientAppVersion);
|
|
1577
|
+
if (!backgroundSeekSupported && (lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND)) {
|
|
1578
|
+
sdkLogger.warn(`Seeking: background seek not supported for device app version: ${this._deviceAppVersion}`);
|
|
1579
|
+
}
|
|
1580
|
+
if (this._remotePlayerApiVersion >= 2 && !this._isSeekingByPlatform && !this._isSeekingByApplication
|
|
1581
|
+
&& (lifecycle.state === lifecycle.UiState.FOREGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_FOREGROUND || backgroundSeekSupported)) {
|
|
1534
1582
|
this._atomicSeek();
|
|
1535
1583
|
} else {
|
|
1536
1584
|
sdkLogger.info(`Seeking: skipping seeking event to currentTime: ${playbackPosition}, internalSeek: ${this._isSeekingByPlatform}, localPlayerSeek: ${this._isSeekingByApplication}, state: ${lifecycle.state}`);
|
|
@@ -1549,16 +1597,16 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1549
1597
|
* */
|
|
1550
1598
|
async _atomicSeek() {
|
|
1551
1599
|
sdkLogger.info("Seeking: local video element seeking start while isPlaying: ", this._isPlaying);
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1600
|
+
if (this._isPlaying) {
|
|
1601
|
+
if (!(lifecycle._inTransitionToForeground || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_FOREGROUND) && (lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND)) {
|
|
1602
|
+
sdkLogger.info("seek in background", this._isPlaying);
|
|
1603
|
+
this._targetSeekPlayingState = TargetPlayingState.PLAYING_ABR;
|
|
1604
|
+
} else {
|
|
1605
|
+
this._targetSeekPlayingState = TargetPlayingState.PLAYING_UI;
|
|
1606
|
+
}
|
|
1607
|
+
} else {
|
|
1608
|
+
this._targetSeekPlayingState = TargetPlayingState.PAUSED;
|
|
1609
|
+
}
|
|
1562
1610
|
|
|
1563
1611
|
// The platform could be currently syncing audio/video using playback rate. Reset when performing seek.
|
|
1564
1612
|
if (this._videoElement) {
|
|
@@ -1625,14 +1673,13 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1625
1673
|
|
|
1626
1674
|
// If in TargetPlayingState.PAUSE, no need to resume.
|
|
1627
1675
|
// Resume without awaiting to avoid blocking the seek process anymore
|
|
1628
|
-
// In case where we aborted, we don't want to resume playback.
|
|
1676
|
+
// In case where we aborted (new load or unload called), we don't want to resume playback.
|
|
1629
1677
|
if (!this._abortSeeking) {
|
|
1630
1678
|
if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_UI) {
|
|
1631
|
-
if
|
|
1632
|
-
|
|
1679
|
+
// When in foreground back from ABR, we are considered playing. But, if AIU is not enabled, we shouldn't resume it.
|
|
1680
|
+
if (this._isAudioSyncEnabled()) {
|
|
1681
|
+
this._play(StreamType.AUDIO);
|
|
1633
1682
|
}
|
|
1634
|
-
// resume audio play only if _isAudioSyncEnabled
|
|
1635
|
-
this._play(StreamType.AUDIO);
|
|
1636
1683
|
} else if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_ABR) {
|
|
1637
1684
|
lifecycle._moveToBackground();
|
|
1638
1685
|
}
|
|
@@ -1776,6 +1823,12 @@ class RemotePlayer extends RemotePlayerInterface {
|
|
|
1776
1823
|
return setLanguageError ? Promise.reject(setLanguageError) : Promise.resolve();
|
|
1777
1824
|
}
|
|
1778
1825
|
|
|
1826
|
+
_generateSenzaTrackId(language, role, accessibilityPurpose) {
|
|
1827
|
+
const l = language || "";
|
|
1828
|
+
const r = role || "";
|
|
1829
|
+
const ap = accessibilityPurpose || "";
|
|
1830
|
+
return `${l}:${r}:${ap}`;
|
|
1831
|
+
}
|
|
1779
1832
|
_setScreenBlackout(blackoutTime) {
|
|
1780
1833
|
if (window.cefQuery) {
|
|
1781
1834
|
const FCID = getFCID();
|