senza-sdk 4.2.55 → 4.2.56

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.55",
3
+ "version": "4.2.56",
4
4
  "main": "./src/api.js",
5
5
  "description": "API for Senza application",
6
6
  "license": "MIT",
package/src/lifecycle.js CHANGED
@@ -710,15 +710,24 @@ class Lifecycle extends EventTarget {
710
710
  * Failure to process the moveToBackground command will result in the promise being rejected.
711
711
  */
712
712
  moveToBackground() {
713
- if (remotePlayer._isSeekingByApplication) {
714
- if (window.cefQuery) {
715
- if (this._inTransition || this._state === this.UiState.BACKGROUND || this._state === this.UiState.IN_TRANSITION_TO_BACKGROUND) {
716
- sdkLogger.warn(`lifecycle moveToBackground: No need to transition to background, state: ${this._state} transition: ${this._inTransition}`);
717
- return Promise.resolve(false);
718
- }
713
+ if (window.cefQuery) {
714
+ if (this._inTransition || this._state === this.UiState.BACKGROUND || this._state === this.UiState.IN_TRANSITION_TO_BACKGROUND) {
715
+ sdkLogger.warn(`lifecycle moveToBackground: No need to transition to background, state: ${this._state} transition: ${this._inTransition}`);
716
+ return Promise.resolve(false);
717
+ }
718
+
719
+ if (remotePlayer._isSeekingByApplication) {
719
720
  remotePlayer._targetSeekPlayingState = TargetPlayingState.PLAYING_ABR;
720
721
  return Promise.resolve(true);
721
722
  }
723
+ if (remotePlayer._isSetSubtitlesInProgress) {
724
+ remotePlayer._targetSetSubtitlePlayingState = TargetPlayingState.PLAYING_ABR;
725
+ return Promise.resolve(true);
726
+ }
727
+ if (remotePlayer._isSetAudioInProgress) {
728
+ remotePlayer._targetSetAudioPlayingState = TargetPlayingState.PLAYING_ABR;
729
+ return Promise.resolve(true);
730
+ }
722
731
  }
723
732
  return this._moveToBackground();
724
733
  }
@@ -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,10 @@ 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
- if (this._isAudioSyncEnabled()) {
720
- 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();
726
- }
718
+ message.switchMode = this._isAudioSyncEnabled() ? SwitchMode.SEAMLESS : SwitchMode.NON_SEAMLESS;
719
+ message.streamType = streamType;
720
+ waitForResponse = true;
727
721
  }
728
722
  const request = { target: "TC", waitForResponse: waitForResponse, message: JSON.stringify(message) };
729
723
  return new Promise((resolve, reject) => {
@@ -762,7 +756,6 @@ class RemotePlayer extends EventTarget {
762
756
 
763
757
  _pause() {
764
758
  if (window.cefQuery) {
765
- this._changePlayMode(false);
766
759
  const isForegroundState = lifecycle.state === lifecycle.UiState.FOREGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_FOREGROUND;
767
760
  if (this._remotePlayerApiVersion >= 2 && !this._isAudioSyncEnabled() && isForegroundState) {
768
761
  sdkLogger.info("remotePlayer pause: application in foreground, remote player is not playing.");
@@ -816,6 +809,51 @@ class RemotePlayer extends EventTarget {
816
809
  return Promise.resolve(undefined);
817
810
  }
818
811
 
812
+ _stop(streamType) {
813
+ if (window.cefQuery) {
814
+ const FCID = getFCID();
815
+ const logger = sdkLogger.withFields({ FCID });
816
+ logger.log(`remotePlayer stop: sending stop action for streamType ${streamType}`);
817
+ const message = {
818
+ type: "remotePlayer.stop",
819
+ class: "remotePlayer",
820
+ action: "stop",
821
+ streamType: streamType,
822
+ fcid: FCID
823
+ };
824
+ const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
825
+ return new Promise((resolve, reject) => {
826
+ let timerId = 0;
827
+ const timeBeforeSendingRequest = Date.now();
828
+ const queryId = window.cefQuery({
829
+ request: JSON.stringify(request),
830
+ persistent: false,
831
+ onSuccess: () => {
832
+ const duration = Date.now() - timeBeforeSendingRequest;
833
+ logger.withFields({ duration }).log(`stop completed successfully after ${duration} ms`);
834
+ timerId = clearTimer(timerId);
835
+ resolve();
836
+ },
837
+ onFailure: (code, msg) => {
838
+ const duration = Date.now() - timeBeforeSendingRequest;
839
+ logger.withFields({ duration }).log(`stop failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
840
+ timerId = clearTimer(timerId);
841
+ reject(new RemotePlayerError(code, msg));
842
+ }
843
+ });
844
+ logger.log(`window.cefQuery for stop returned query id ${queryId}`);
845
+ const timeout = this._remotePlayerConfirmationTimeout + 1000;
846
+ timerId = setTimeout(() => {
847
+ logger.log(`stop reached timeout of ${timeout} ms, canceling query id ${queryId}`);
848
+ window.cefQueryCancel(queryId);
849
+ reject(new RemotePlayerError(6000, `stop reached timeout of ${timeout} ms`));
850
+ }, timeout, queryId);
851
+ });
852
+ }
853
+ sdkLogger.error("remotePlayer stop: window.cefQuery is undefined");
854
+ return Promise.resolve(undefined);
855
+ }
856
+
819
857
  /** In order to support a seamless switch between the video in the UI and ABR, the web application must
820
858
  * register the video element being used for the currently played video before calling the load and play apis.
821
859
  * @param {object} video The video element currently playing video in the web application
@@ -891,6 +929,8 @@ class RemotePlayer extends EventTarget {
891
929
  if (this._loadMode === this.LoadMode.LOADING || this._loadMode === this.LoadMode.UNLOADING) {
892
930
  throw new RemotePlayerError(6501, "Cannot call load() while previous load/unload is still in progress");
893
931
  }
932
+ this._abortSetAudioLanguage = true;
933
+ this._abortSetSubtitleLanguage = true;
894
934
  this._abortSeeking = true;
895
935
  if (reset) {
896
936
  this._reset();
@@ -991,6 +1031,8 @@ class RemotePlayer extends EventTarget {
991
1031
  if (this._loadMode === this.LoadMode.LOADING || this._loadMode === this.LoadMode.UNLOADING) {
992
1032
  throw new RemotePlayerError(6502, "Cannot call unload() while previous unload/load is still in progress");
993
1033
  }
1034
+ this._abortSetAudioLanguage = true;
1035
+ this._abortSetSubtitleLanguage = true;
994
1036
  this._abortSeeking = true;
995
1037
  const previousLoadMode = this._loadMode;
996
1038
  this._changeLoadMode(this.LoadMode.UNLOADING);
@@ -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
+ * 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
+ * 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.
@@ -1423,6 +1626,11 @@ class RemotePlayer extends EventTarget {
1423
1626
  * @private
1424
1627
  */
1425
1628
  async _startSeeking(playbackPosition) {
1629
+ if (this._isSetAudioInProgress || this._isSetSubtitlesInProgress) {
1630
+ sdkLogger.info("Seeking not supported while setAudioLanguage or setSubtitleLanguage are in progress.");
1631
+ return;
1632
+ }
1633
+
1426
1634
  if (!this._isSeekingByPlatform) {
1427
1635
  this._pendingSeekPosition = playbackPosition;
1428
1636
 
@@ -1534,7 +1742,11 @@ class RemotePlayer extends EventTarget {
1534
1742
  // In case where we aborted, we don't want to resume playback.
1535
1743
  if (!this._abortSeeking) {
1536
1744
  if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_UI) {
1537
- this._play();
1745
+ if (!this._isAudioSyncEnabled()) {
1746
+ return Promise.resolve(true);
1747
+ }
1748
+ // resume audio play only if _isAudioSyncEnabled
1749
+ this._play(StreamType.AUDIO);
1538
1750
  } else if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_ABR) {
1539
1751
  lifecycle._moveToBackground();
1540
1752
  }
@@ -1543,6 +1755,140 @@ class RemotePlayer extends EventTarget {
1543
1755
  this._isSeekingByApplication = false;
1544
1756
  sdkLogger.info("Seeking: local video element seeking end");
1545
1757
  }
1758
+
1759
+ async _atomicSetAudioLanguage(audioTrackId, prevSelectedAudioTrack) {
1760
+ if (this._isSetAudioInProgress) {
1761
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} already in progress, ignoring.`);
1762
+ return Promise.resolve();
1763
+ }
1764
+ if (this._isSeekingByApplication) {
1765
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} ignored. seeking is already in progress.`);
1766
+ return Promise.resolve();
1767
+ }
1768
+
1769
+ this._targetSetAudioPlayingState = this._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
1770
+ sdkLogger.log(`remotePlayer _atomicSetAudioLanguage: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying} targetState=${this._targetSetAudioPlayingState}`);
1771
+
1772
+ this._abortSetAudioLanguage = false;
1773
+ this._isSetAudioInProgress = true;
1774
+
1775
+ let setLanguageError;
1776
+ try {
1777
+ await this._stop(StreamType.AUDIO);
1778
+ } catch (error) {
1779
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} failed on stop with error ${error.message}.`);
1780
+ // ignore error
1781
+ }
1782
+
1783
+ // check if load/unload were called
1784
+ if (this._abortSetAudioLanguage) {
1785
+ this._isSetAudioInProgress = false;
1786
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} aborted right after stop.`);
1787
+ return Promise.resolve();
1788
+ }
1789
+
1790
+ try {
1791
+ await this._selectAudioTrackV3(audioTrackId);
1792
+ } catch (error) {
1793
+ sdkLogger.error(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} _selectAudioTrackV3 failed with error ${error.message}.`);
1794
+ if (!this._abortSetAudioLanguage) {
1795
+ setLanguageError = error;
1796
+ }
1797
+ }
1798
+
1799
+ // check if load/unload were called
1800
+ if (this._abortSetAudioLanguage) {
1801
+ this._isSetAudioInProgress = false;
1802
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} aborted right after _selectAudioTrackV3.`);
1803
+ return Promise.resolve();
1804
+ }
1805
+
1806
+ try {
1807
+ if (this._targetSetAudioPlayingState === TargetPlayingState.PLAYING_UI) {
1808
+ if (!this._isAudioSyncEnabled()) {
1809
+ this._isSetAudioInProgress = false;
1810
+ return Promise.resolve(true);
1811
+ }
1812
+ // resume audio play only if _isAudioSyncEnabled
1813
+ await this._play(StreamType.AUDIO);
1814
+ } else if (this._targetSetAudioPlayingState === TargetPlayingState.PLAYING_ABR) {
1815
+ await lifecycle._moveToBackground();
1816
+ }
1817
+ } catch (error) {
1818
+ sdkLogger.error(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} play failed with error ${error.message}.`);
1819
+ }
1820
+
1821
+ this._isSetAudioInProgress = false;
1822
+ sdkLogger.log(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} ended.`);
1823
+ return setLanguageError ? Promise.reject(setLanguageError) : Promise.resolve();
1824
+ }
1825
+
1826
+ async _atomicSetSubtitleLanguage(textTrackId, prevSelectedTextTrack) {
1827
+ if (this._isSetSubtitlesInProgress) {
1828
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} already in progress, ignoring.`);
1829
+ return Promise.resolve();
1830
+ }
1831
+ if (this._isSeekingByApplication) {
1832
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} ignored. seeking is already in progress.`);
1833
+ return Promise.resolve();
1834
+ }
1835
+
1836
+ this._targetSetSubtitlePlayingState = this._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
1837
+ sdkLogger.log(`remotePlayer _atomicSetSubtitleLanguage: prevTextTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying} targetState=${this._targetSetSubtitlePlayingState}`);
1838
+
1839
+ this._abortSetSubtitleLanguage = false;
1840
+ this._isSetSubtitlesInProgress = true;
1841
+
1842
+ let setLanguageError;
1843
+ try {
1844
+ await this._stop(StreamType.SUBTITLE);
1845
+ } catch (error) {
1846
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} stop failed with error ${error.message}.`);
1847
+ // ignore error
1848
+ }
1849
+
1850
+ // check if load/unload were called
1851
+ if (this._abortSetSubtitleLanguage) {
1852
+ this._isSetSubtitlesInProgress = false;
1853
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} aborted right after stop.`);
1854
+ return Promise.resolve();
1855
+ }
1856
+
1857
+ try {
1858
+ await this._selectTextTrackV3(textTrackId);
1859
+ } catch (error) {
1860
+ sdkLogger.error(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} _selectTextTrackV3 failed with error ${error.message}.`);
1861
+ if (!this._abortSetSubtitleLanguage) {
1862
+ setLanguageError = error;
1863
+ }
1864
+ }
1865
+
1866
+ // check if load/unload were called
1867
+ if (this._abortSetSubtitleLanguage) {
1868
+ this._isSetSubtitlesInProgress = false;
1869
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} aborted right after _selectTextTrackV3.`);
1870
+ return Promise.resolve();
1871
+ }
1872
+
1873
+ try {
1874
+ if (this._targetSetSubtitlePlayingState === TargetPlayingState.PLAYING_UI) {
1875
+ if (!this._isAudioSyncEnabled()) {
1876
+ this._isSetSubtitlesInProgress = false;
1877
+ return Promise.resolve(true);
1878
+ }
1879
+ // resume audio play only if _isAudioSyncEnabled
1880
+ await this._play(StreamType.AUDIO);
1881
+ } else if (this._targetSetSubtitlePlayingState === TargetPlayingState.PLAYING_ABR) {
1882
+ await lifecycle._moveToBackground();
1883
+ }
1884
+ } catch (error) {
1885
+ sdkLogger.error(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} play failed with error ${error.message}.`);
1886
+ }
1887
+
1888
+ this._isSetSubtitlesInProgress = false;
1889
+ sdkLogger.log(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} ended.`);
1890
+ return setLanguageError ? Promise.reject(setLanguageError) : Promise.resolve();
1891
+ }
1546
1892
  }
1547
1893
  /**
1548
1894
  *