senza-sdk 4.2.54-d09bbe7.0 → 4.2.55-3686561.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "senza-sdk",
3
- "version": "4.2.54-d09bbe7.0",
3
+ "version": "4.2.55-3686561.0",
4
4
  "main": "./src/api.js",
5
5
  "description": "API for Senza application",
6
6
  "license": "MIT",
@@ -694,7 +694,7 @@ class RemotePlayer extends EventTarget {
694
694
  return Promise.resolve(undefined);
695
695
  }
696
696
 
697
- _play() {
697
+ _play(streamType) {
698
698
  if (window.cefQuery) {
699
699
  const FCID = getFCID();
700
700
  const logger = sdkLogger.withFields({ FCID });
@@ -714,16 +714,12 @@ class RemotePlayer extends EventTarget {
714
714
  playbackPosition: this.currentTime
715
715
  };
716
716
  let waitForResponse = false;
717
- this._changePlayMode(true);
718
717
  if (this._remotePlayerApiVersion >= 2) {
719
718
  if (this._isAudioSyncEnabled()) {
720
719
  message.switchMode = SwitchMode.SEAMLESS;
721
- message.streamType = StreamType.AUDIO;
722
- waitForResponse = true;
723
- } else {
724
- logger.log("remotePlayer play request ignored and will be sent with the lifecycle.moveToBackground()");
725
- return Promise.resolve();
720
+ message.streamType = streamType;
726
721
  }
722
+ waitForResponse = true;
727
723
  }
728
724
  const request = { target: "TC", waitForResponse: waitForResponse, message: JSON.stringify(message) };
729
725
  return new Promise((resolve, reject) => {
@@ -762,7 +758,6 @@ class RemotePlayer extends EventTarget {
762
758
 
763
759
  _pause() {
764
760
  if (window.cefQuery) {
765
- this._changePlayMode(false);
766
761
  const isForegroundState = lifecycle.state === lifecycle.UiState.FOREGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_FOREGROUND;
767
762
  if (this._remotePlayerApiVersion >= 2 && !this._isAudioSyncEnabled() && isForegroundState) {
768
763
  sdkLogger.info("remotePlayer pause: application in foreground, remote player is not playing.");
@@ -816,6 +811,51 @@ class RemotePlayer extends EventTarget {
816
811
  return Promise.resolve(undefined);
817
812
  }
818
813
 
814
+ _stop(streamType) {
815
+ if (window.cefQuery) {
816
+ const FCID = getFCID();
817
+ const logger = sdkLogger.withFields({ FCID });
818
+ logger.log(`remotePlayer stop: sending stop action for streamType ${streamType}`);
819
+ const message = {
820
+ type: "remotePlayer.stop",
821
+ class: "remotePlayer",
822
+ action: "stop",
823
+ streamType: streamType,
824
+ fcid: FCID
825
+ };
826
+ const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
827
+ return new Promise((resolve, reject) => {
828
+ let timerId = 0;
829
+ const timeBeforeSendingRequest = Date.now();
830
+ const queryId = window.cefQuery({
831
+ request: JSON.stringify(request),
832
+ persistent: false,
833
+ onSuccess: () => {
834
+ const duration = Date.now() - timeBeforeSendingRequest;
835
+ logger.withFields({ duration }).log(`stop completed successfully after ${duration} ms`);
836
+ timerId = clearTimer(timerId);
837
+ resolve();
838
+ },
839
+ onFailure: (code, msg) => {
840
+ const duration = Date.now() - timeBeforeSendingRequest;
841
+ logger.withFields({ duration }).log(`stop failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
842
+ timerId = clearTimer(timerId);
843
+ reject(new RemotePlayerError(code, msg));
844
+ }
845
+ });
846
+ logger.log(`window.cefQuery for stop returned query id ${queryId}`);
847
+ const timeout = this._remotePlayerConfirmationTimeout + 1000;
848
+ timerId = setTimeout(() => {
849
+ logger.log(`stop reached timeout of ${timeout} ms, canceling query id ${queryId}`);
850
+ window.cefQueryCancel(queryId);
851
+ reject(new RemotePlayerError(6000, `stop reached timeout of ${timeout} ms`));
852
+ }, timeout, queryId);
853
+ });
854
+ }
855
+ sdkLogger.error("remotePlayer stop: window.cefQuery is undefined");
856
+ return Promise.resolve(undefined);
857
+ }
858
+
819
859
  /** In order to support a seamless switch between the video in the UI and ABR, the web application must
820
860
  * register the video element being used for the currently played video before calling the load and play apis.
821
861
  * @param {object} video The video element currently playing video in the web application
@@ -891,6 +931,8 @@ class RemotePlayer extends EventTarget {
891
931
  if (this._loadMode === this.LoadMode.LOADING || this._loadMode === this.LoadMode.UNLOADING) {
892
932
  throw new RemotePlayerError(6501, "Cannot call load() while previous load/unload is still in progress");
893
933
  }
934
+ this._abortSetAudioLanguage = true;
935
+ this._abortSetSubtitleLanguage = true;
894
936
  this._abortSeeking = true;
895
937
  if (reset) {
896
938
  this._reset();
@@ -1064,6 +1106,20 @@ class RemotePlayer extends EventTarget {
1064
1106
  }
1065
1107
  }
1066
1108
 
1109
+ this._changePlayMode(true);
1110
+
1111
+ if (this._isSetAudioInProgress) {
1112
+ sdkLogger.info("application requesting play during setAudioLanguage");
1113
+ this._targetSetAudioPlayingState = TargetPlayingState.PLAYING_UI;
1114
+ return Promise.resolve(true);
1115
+ }
1116
+
1117
+ if (this._isSetSubtitlesInProgress) {
1118
+ sdkLogger.info("application requesting play during setSubtitleLanguage");
1119
+ this._targetSetSubtitlePlayingState = TargetPlayingState.PLAYING_UI;
1120
+ return Promise.resolve(true);
1121
+ }
1122
+
1067
1123
  // If seeking in progress, wait for seek to complete before playing
1068
1124
  if (this._isSeekingByApplication) {
1069
1125
  sdkLogger.info("application requesting play during seek");
@@ -1078,7 +1134,13 @@ class RemotePlayer extends EventTarget {
1078
1134
  this._seek(this._videoElement.currentTime, false);
1079
1135
  }
1080
1136
  }*/
1081
- return this._play();
1137
+
1138
+ if (this._remotePlayerApiVersion >= 2 && !this._isAudioSyncEnabled()) {
1139
+ sdkLogger.info("play was called but _isAudioSyncEnabled is disabled, ignoring.");
1140
+ return Promise.resolve(true);
1141
+ }
1142
+
1143
+ return this._play(StreamType.AUDIO);
1082
1144
  }
1083
1145
 
1084
1146
  /** Pauses the currently playing audio or video
@@ -1098,11 +1160,24 @@ class RemotePlayer extends EventTarget {
1098
1160
  throw new RemotePlayerError(6001, "Cannot call pause() if player is not loaded");
1099
1161
  }
1100
1162
  }
1163
+
1164
+ this._changePlayMode(false);
1165
+
1101
1166
  if (this._isSeekingByApplication) {
1102
1167
  sdkLogger.info("application requesting pause during seek");
1103
1168
  this._targetSeekPlayingState = TargetPlayingState.PAUSED;
1104
1169
  return Promise.resolve(true);
1105
1170
  }
1171
+ if (this._isSetAudioInProgress) {
1172
+ sdkLogger.info("application requesting pause during setAudioLanguage");
1173
+ this._targetSetAudioPlayingState = TargetPlayingState.PAUSED;
1174
+ return Promise.resolve(true);
1175
+ }
1176
+ if (this._isSetSubtitlesInProgress) {
1177
+ sdkLogger.info("application requesting pause during setSubtitleLanguage");
1178
+ this._targetSetSubtitlePlayingState = TargetPlayingState.PAUSED;
1179
+ return Promise.resolve(true);
1180
+ }
1106
1181
  return this._pause();
1107
1182
  }
1108
1183
 
@@ -1190,7 +1265,6 @@ class RemotePlayer extends EventTarget {
1190
1265
  const prevSelectedAudioTrack = this._selectedAudioTrack;
1191
1266
  for (const track of this.getAudioTracks()) {
1192
1267
  if (track.id === audioTrackId) {
1193
- this._selectedAudioTrack = audioTrackId;
1194
1268
  found = true;
1195
1269
  break;
1196
1270
  }
@@ -1199,19 +1273,33 @@ class RemotePlayer extends EventTarget {
1199
1273
  sdkLogger.warn(`Invalid audioTrackId ${audioTrackId}`);
1200
1274
  return Promise.resolve();
1201
1275
  }
1202
- if (this._remotePlayerApiVersion < 2) {
1203
- return Promise.resolve(); // Resolve immediately for older versions
1204
- }
1205
- if (prevSelectedAudioTrack === this._selectedAudioTrack) {
1276
+ if (this._selectedAudioTrack === audioTrackId) {
1206
1277
  return Promise.resolve(); // Audio language already selected
1207
1278
  }
1208
1279
 
1209
- return this._selectAudioTrack(audioTrackId, prevSelectedAudioTrack);
1280
+ switch (this._remotePlayerApiVersion) {
1281
+ case 0:
1282
+ case 1:
1283
+ this._selectedAudioTrack = audioTrackId;
1284
+ return Promise.resolve(); // Resolve immediately for older versions
1285
+ case 2:
1286
+ return this._selectAudioTrackV2(audioTrackId, prevSelectedAudioTrack);
1287
+ default:
1288
+ return this._atomicSetAudioLanguage(audioTrackId, prevSelectedAudioTrack);
1289
+ }
1210
1290
  }
1211
1291
 
1212
- async _selectAudioTrack(audioTrackId, prevSelectedAudioTrack) {
1292
+ /**
1293
+ * Handles the asynchronous selection of an audio track.
1294
+ * If the player is playing, it pauses before changing the track and resumes playback if necessary.
1295
+ *
1296
+ * @param {string} audioTrackId - The ID of the audio track to select.
1297
+ * @param {string} prevSelectedAudioTrack - The previously selected audio track ID.
1298
+ * @returns {Promise<void>} Resolves when the operation is complete.
1299
+ * */
1300
+ async _selectAudioTrackV2(audioTrackId, prevSelectedAudioTrack) {
1213
1301
  const prevIsPlaying = this._isPlaying;
1214
- sdkLogger.log(`remotePlayer _selectAudioTrack: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying}`);
1302
+ sdkLogger.log(`remotePlayer _selectAudioTrackV2: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying}`);
1215
1303
  try {
1216
1304
  if (this._isPlaying) await this.pause();
1217
1305
  let position = this.currentTime;
@@ -1219,9 +1307,9 @@ class RemotePlayer extends EventTarget {
1219
1307
  position = this._videoElement.currentTime;
1220
1308
  }
1221
1309
  await this._load(this._loadedUrl, position, audioTrackId, undefined, false);
1310
+ this._selectedAudioTrack = audioTrackId;
1222
1311
  } catch (e) {
1223
- // Do NOT reject - just log and revert selection
1224
- this._selectedAudioTrack = prevSelectedAudioTrack;
1312
+ // Do NOT reject - just log
1225
1313
  sdkLogger.warn(`Failed to select audio track ${audioTrackId}: ${e.message}`);
1226
1314
  return;
1227
1315
  }
@@ -1238,6 +1326,62 @@ class RemotePlayer extends EventTarget {
1238
1326
  }
1239
1327
  }
1240
1328
 
1329
+ /**
1330
+ * Handles the asynchronous selection of an audio track.
1331
+ * If the player is playing, it stops audio streamType before changing the track and resumes audio streamType playback if necessary.
1332
+ * Available only from v3 and on
1333
+ *
1334
+ * @param {string} audioTrackId - The ID of the audio track to select.
1335
+ * @returns {Promise<void>} Resolves when the operation is complete.
1336
+ * */
1337
+ async _selectAudioTrackV3(audioTrackId) {
1338
+ sdkLogger.log(`remotePlayer _selectAudioTrackV3: audioTrackId=${audioTrackId} isPlaying=${this._isPlaying}`);
1339
+ if (window.cefQuery) {
1340
+ const FCID = getFCID();
1341
+ const logger = sdkLogger.withFields({ FCID });
1342
+ logger.log("remotePlayer _selectAudioTrackV3: sending setAudioLanguage action");
1343
+ const message = {
1344
+ type: "remotePlayer.setAudioLanguage",
1345
+ class: "remotePlayer",
1346
+ action: "setAudioLanguage",
1347
+ fcid: FCID,
1348
+ language: audioTrackId
1349
+ };
1350
+ const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
1351
+ return new Promise((resolve, reject) => {
1352
+ let timerId = 0;
1353
+ const timeBeforeSendingRequest = Date.now();
1354
+ const queryId = window.cefQuery({
1355
+ request: JSON.stringify(request),
1356
+ persistent: false,
1357
+ onSuccess: () => {
1358
+ this._selectedAudioTrack = audioTrackId;
1359
+ const duration = Date.now() - timeBeforeSendingRequest;
1360
+ logger.withFields({ duration }).log(`setAudioLanguage completed successfully after ${duration} ms`);
1361
+ timerId = clearTimer(timerId);
1362
+ resolve();
1363
+ },
1364
+ onFailure: (code, msg) => {
1365
+ const duration = Date.now() - timeBeforeSendingRequest;
1366
+ logger.withFields({ duration }).log(`setAudioLanguage failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
1367
+ timerId = clearTimer(timerId);
1368
+ reject(new RemotePlayerError(code, msg));
1369
+ }
1370
+ });
1371
+ logger.log(`window.cefQuery for setAudioLanguage returned query id ${queryId}`);
1372
+ const timeout = this._remotePlayerConfirmationTimeout + 1000;
1373
+ timerId = setTimeout(() => {
1374
+ logger.log(`setAudioLanguage reached timeout of ${timeout} ms, canceling query id ${queryId}`);
1375
+ window.cefQueryCancel(queryId);
1376
+ reject(new RemotePlayerError(6000, `setAudioLanguage reached timeout of ${timeout} ms`));
1377
+ }, timeout, queryId);
1378
+ });
1379
+ }
1380
+
1381
+ sdkLogger.error("remotePlayer _selectAudioTrackV3: window.cefQuery is undefined");
1382
+ return Promise.resolve(undefined);
1383
+ }
1384
+
1241
1385
  /** Select a specific text (subtitle) track.
1242
1386
  * Track id should come from a call to getTextTracks.
1243
1387
  * If no tracks exist - this is a no-op.
@@ -1257,7 +1401,6 @@ class RemotePlayer extends EventTarget {
1257
1401
  const prevSelectedTextTrack = this._selectedSubtitlesTrack;
1258
1402
  for (const track of this.getTextTracks()) {
1259
1403
  if (track.id === textTrackId) {
1260
- this._selectedSubtitlesTrack = textTrackId;
1261
1404
  found = true;
1262
1405
  break;
1263
1406
  }
@@ -1266,14 +1409,20 @@ class RemotePlayer extends EventTarget {
1266
1409
  sdkLogger.warn(`Invalid textTrackId ${textTrackId}`);
1267
1410
  return Promise.resolve();
1268
1411
  }
1269
- if (this._remotePlayerApiVersion < 2) {
1270
- return Promise.resolve(); // Resolve immediately for older versions
1271
- }
1272
- if (prevSelectedTextTrack === this._selectedSubtitlesTrack) {
1412
+ if (this._selectedSubtitlesTrack === textTrackId) {
1273
1413
  return Promise.resolve(); // Subtitle language already selected
1274
1414
  }
1275
1415
 
1276
- return this._selectTextTrack(textTrackId, prevSelectedTextTrack);
1416
+ switch (this._remotePlayerApiVersion) {
1417
+ case 0:
1418
+ case 1:
1419
+ this._selectedSubtitlesTrack = textTrackId;
1420
+ return Promise.resolve(); // Resolve immediately for older versions
1421
+ case 2:
1422
+ return this._selectTextTrackV2(textTrackId, prevSelectedTextTrack);
1423
+ default:
1424
+ return this._atomicSetSubtitleLanguage(textTrackId, prevSelectedTextTrack);
1425
+ }
1277
1426
  }
1278
1427
 
1279
1428
  /**
@@ -1284,9 +1433,9 @@ class RemotePlayer extends EventTarget {
1284
1433
  * @param {string} prevSelectedTextTrack - The previously selected text track ID.
1285
1434
  * @returns {Promise<void>} Resolves when the operation is complete.
1286
1435
  * */
1287
- async _selectTextTrack(textTrackId, prevSelectedTextTrack) {
1436
+ async _selectTextTrackV2(textTrackId, prevSelectedTextTrack) {
1288
1437
  const prevIsPlaying = this._isPlaying;
1289
- sdkLogger.log(`remotePlayer _selectTextTrack: prevTextTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying}`);
1438
+ sdkLogger.log(`remotePlayer _selectTextTrackV2: prevTextTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying}`);
1290
1439
  try {
1291
1440
  if (this._isPlaying) await this.pause();
1292
1441
  let position = this.currentTime;
@@ -1294,9 +1443,9 @@ class RemotePlayer extends EventTarget {
1294
1443
  position = this._videoElement.currentTime;
1295
1444
  }
1296
1445
  await this._load(this._loadedUrl, position, undefined, textTrackId, false);
1446
+ this._selectedSubtitlesTrack = textTrackId;
1297
1447
  } catch (e) {
1298
- // Do NOT reject - just log and revert selection
1299
- this._selectedSubtitlesTrack = prevSelectedTextTrack;
1448
+ // Do NOT reject - just log
1300
1449
  sdkLogger.warn(`Failed to select text track ${textTrackId}: ${e.message}`);
1301
1450
  return;
1302
1451
  }
@@ -1313,6 +1462,60 @@ class RemotePlayer extends EventTarget {
1313
1462
  }
1314
1463
  }
1315
1464
 
1465
+ /**
1466
+ * Handles the asynchronous selection of a text track.
1467
+ * If the player is playing, it stops subtitle streamType before changing the track and resumes subtitle streamType playback if necessary.
1468
+ * Available only from v3 and on
1469
+ *
1470
+ * @param {string} textTrackId - The ID of the text track to select.
1471
+ * @returns {Promise<void>} Resolves when the operation is complete.
1472
+ * */
1473
+ async _selectTextTrackV3(textTrackId) {
1474
+ if (window.cefQuery) {
1475
+ const FCID = getFCID();
1476
+ const logger = sdkLogger.withFields({ FCID });
1477
+ logger.log("remotePlayer _selectTextTrackV3: sending setSubtitleLanguage action");
1478
+ const message = {
1479
+ type: "remotePlayer.setSubtitleLanguage",
1480
+ class: "remotePlayer",
1481
+ action: "setSubtitleLanguage",
1482
+ fcid: FCID,
1483
+ language: textTrackId
1484
+ };
1485
+ const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
1486
+ return new Promise((resolve, reject) => {
1487
+ let timerId = 0;
1488
+ const timeBeforeSendingRequest = Date.now();
1489
+ const queryId = window.cefQuery({
1490
+ request: JSON.stringify(request),
1491
+ persistent: false,
1492
+ onSuccess: () => {
1493
+ this._selectedSubtitlesTrack = textTrackId;
1494
+ const duration = Date.now() - timeBeforeSendingRequest;
1495
+ logger.withFields({ duration }).log(`setSubtitleLanguage completed successfully after ${duration} ms`);
1496
+ timerId = clearTimer(timerId);
1497
+ resolve();
1498
+ },
1499
+ onFailure: (code, msg) => {
1500
+ const duration = Date.now() - timeBeforeSendingRequest;
1501
+ logger.withFields({ duration }).log(`setSubtitleLanguage failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
1502
+ timerId = clearTimer(timerId);
1503
+ reject(new RemotePlayerError(code, msg));
1504
+ }
1505
+ });
1506
+ logger.log(`window.cefQuery for setSubtitleLanguage returned query id ${queryId}`);
1507
+ const timeout = this._remotePlayerConfirmationTimeout + 1000;
1508
+ timerId = setTimeout(() => {
1509
+ logger.log(`setSubtitleLanguage reached timeout of ${timeout} ms, canceling query id ${queryId}`);
1510
+ window.cefQueryCancel(queryId);
1511
+ reject(new RemotePlayerError(6000, `setSubtitleLanguage reached timeout of ${timeout} ms`));
1512
+ }, timeout, queryId);
1513
+ });
1514
+ }
1515
+ sdkLogger.error("remotePlayer _selectTextTrackV3: window.cefQuery is undefined");
1516
+ return Promise.resolve(undefined);
1517
+ }
1518
+
1316
1519
  /**
1317
1520
  * Enable or disable the subtitles.
1318
1521
  * If the player is in an unloaded state, the request will be applied next time content is played.
@@ -1428,7 +1631,7 @@ class RemotePlayer extends EventTarget {
1428
1631
 
1429
1632
  // If the local player is just initializing after loading the manifest, ignore the seeking event.
1430
1633
  if (this._localPlayerLoadCurrentTime === 0 && this._videoElement.currentTime - this._localPlayerLoadCurrentTime > ONE_DAY_SECONDS) {
1431
- sdkLogger.info(`Seeking ignored for video currentTime init: currentTime=${this._videoElement.currentTime} loadCurrentTime=${this._localPlayerLoadCurrentTime}`);
1634
+ sdkLogger.info(`Seeking ignored for video currentTime init: currentTime: ${this._videoElement.currentTime}, loadCurrentTime: ${this._localPlayerLoadCurrentTime}`);
1432
1635
  this._localPlayerLoadCurrentTime = this._videoElement.currentTime;
1433
1636
  return;
1434
1637
  }
@@ -1439,7 +1642,7 @@ class RemotePlayer extends EventTarget {
1439
1642
  (lifecycle.state === lifecycle.UiState.FOREGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_FOREGROUND)) {
1440
1643
  this._atomicSeek();
1441
1644
  } else {
1442
- sdkLogger.info(`Seeking: skipping seeking event to currentTime=${playbackPosition} internalSeek=${this._isSeekingByPlatform} localPlayerSeek=${this._isSeekingByApplication} state="${lifecycle.state}"`);
1645
+ sdkLogger.info(`Seeking: skipping seeking event to currentTime: ${playbackPosition}, internalSeek: ${this._isSeekingByPlatform}, localPlayerSeek: ${this._isSeekingByApplication}, state: ${lifecycle.state}`);
1443
1646
  }
1444
1647
  }
1445
1648
 
@@ -1454,7 +1657,7 @@ class RemotePlayer extends EventTarget {
1454
1657
  * @private
1455
1658
  * */
1456
1659
  async _atomicSeek() {
1457
- sdkLogger.info("Seeking: local video element seeking start while isPLaying=", this._isPlaying);
1660
+ sdkLogger.info("Seeking: local video element seeking start while isPlaying: ", this._isPlaying);
1458
1661
 
1459
1662
  // Initialize the target playing state unless changed during the seek process
1460
1663
  // In the future, we should allow for seeking in background. Currently, there's no
@@ -1534,7 +1737,11 @@ class RemotePlayer extends EventTarget {
1534
1737
  // In case where we aborted, we don't want to resume playback.
1535
1738
  if (!this._abortSeeking) {
1536
1739
  if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_UI) {
1537
- this._play();
1740
+ if (!this._isAudioSyncEnabled()) {
1741
+ return Promise.resolve(true);
1742
+ }
1743
+ // resume audio play only if _isAudioSyncEnabled
1744
+ this._play(StreamType.AUDIO);
1538
1745
  } else if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_ABR) {
1539
1746
  lifecycle._moveToBackground();
1540
1747
  }
@@ -1543,6 +1750,118 @@ class RemotePlayer extends EventTarget {
1543
1750
  this._isSeekingByApplication = false;
1544
1751
  sdkLogger.info("Seeking: local video element seeking end");
1545
1752
  }
1753
+
1754
+ async _atomicSetAudioLanguage(audioTrackId, prevSelectedAudioTrack) {
1755
+ if (this._isSetAudioInProgress) {
1756
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} already in progress, ignoring.`);
1757
+ return Promise.resolve();
1758
+ }
1759
+
1760
+ this._targetSetAudioPlayingState = this._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
1761
+ sdkLogger.log(`remotePlayer _atomicSetAudioLanguage: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying} targetState=${this._targetSetAudioPlayingState}`);
1762
+
1763
+ this._abortSetAudioLanguage = false;
1764
+ this._isSetAudioInProgress = true;
1765
+
1766
+ let res;
1767
+ let isStopped = false; // flag to call play if selectAudioV3 fails
1768
+
1769
+ try {
1770
+ await this._stop(StreamType.AUDIO);
1771
+ if (this._abortSetAudioLanguage) {
1772
+ this._isSetAudioInProgress = false;
1773
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} aborted.`);
1774
+ return res;
1775
+ }
1776
+ isStopped = true;
1777
+ } catch (error) {
1778
+ this._isSetAudioInProgress = false;
1779
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} failed on stop. aborting.`);
1780
+ return Promise.reject(error);
1781
+ }
1782
+
1783
+ try {
1784
+ res = await this._selectAudioTrackV3(audioTrackId);
1785
+ if (this._abortSetAudioLanguage) {
1786
+ this._isSetAudioInProgress = false;
1787
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} aborted.`);
1788
+ return res;
1789
+ }
1790
+ } catch (error) {
1791
+ sdkLogger.error(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} failed with error ${error.message}.`);
1792
+ res = Promise.reject(error);
1793
+ }
1794
+
1795
+ if (this._targetSetAudioPlayingState !== TargetPlayingState.PAUSED && isStopped) {
1796
+ try {
1797
+ await this._play(StreamType.AUDIO);
1798
+ } catch (error) {
1799
+ this._isSetAudioInProgress = false;
1800
+ sdkLogger.error(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} failed to play with error ${error.message}.`);
1801
+ return Promise.reject(error);
1802
+ }
1803
+ }
1804
+
1805
+ this._isSetAudioInProgress = false;
1806
+ sdkLogger.log(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} ended.`);
1807
+ return res;
1808
+ }
1809
+
1810
+ async _atomicSetSubtitleLanguage(textTrackId, prevSelectedTextTrack) {
1811
+ if (this._isSetSubtitlesInProgress) {
1812
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} already in progress., ignoring.`);
1813
+ return Promise.resolve();
1814
+ }
1815
+
1816
+ this._targetSetSubtitlePlayingState = this._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
1817
+ sdkLogger.log(`remotePlayer _atomicSetSubtitleLanguage: prevTextTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying} targetState=${this._targetSetSubtitlePlayingState}`);
1818
+
1819
+ this._abortSetSubtitleLanguage = false;
1820
+ this._isSetSubtitlesInProgress = true;
1821
+
1822
+ let res;
1823
+ let isStopped = false;
1824
+ try {
1825
+ await this._stop(StreamType.SUBTITLE);
1826
+ if (this._abortSetSubtitleLanguage) {
1827
+ this._isSetSubtitlesInProgress = false;
1828
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} aborted.`);
1829
+ return res;
1830
+ }
1831
+ isStopped = true;
1832
+ } catch (error) {
1833
+ this._isSetSubtitlesInProgress = false;
1834
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} failed on stop. aborting.`);
1835
+ return Promise.reject(error);
1836
+ }
1837
+
1838
+ try {
1839
+ res = await this._selectTextTrackV3(textTrackId);
1840
+ if (this._abortSetSubtitleLanguage) {
1841
+ this._isSetSubtitlesInProgress = false;
1842
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} aborted.`);
1843
+ return res;
1844
+ }
1845
+ } catch (error) {
1846
+ sdkLogger.error(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} failed with error ${error.message}.`);
1847
+ res = Promise.reject(error);
1848
+ }
1849
+
1850
+ if (this._targetSetSubtitlePlayingState !== TargetPlayingState.PAUSED && isStopped) {
1851
+ try {
1852
+ await this._play(StreamType.SUBTITLE | StreamType.AUDIO);
1853
+ } catch (error) {
1854
+ this._isSetSubtitlesInProgress = false;
1855
+ sdkLogger.error(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} failed to play with error ${error.message}.`);
1856
+ return Promise.reject(error);
1857
+ }
1858
+ }
1859
+
1860
+ this._isSetSubtitlesInProgress = false;
1861
+ sdkLogger.log(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} ended.`);
1862
+ return res;
1863
+ }
1864
+
1546
1865
  }
1547
1866
  /**
1548
1867
  *