senza-sdk 4.2.55-9808823.0 → 4.2.55-e39d067.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.55-9808823.0",
3
+ "version": "4.2.55-e39d067.0",
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();
@@ -1064,6 +1104,20 @@ class RemotePlayer extends EventTarget {
1064
1104
  }
1065
1105
  }
1066
1106
 
1107
+ this._changePlayMode(true);
1108
+
1109
+ if (this._isSetAudioInProgress) {
1110
+ sdkLogger.info("application requesting play during setAudioLanguage");
1111
+ this._targetSetAudioPlayingState = TargetPlayingState.PLAYING_UI;
1112
+ return Promise.resolve(true);
1113
+ }
1114
+
1115
+ if (this._isSetSubtitlesInProgress) {
1116
+ sdkLogger.info("application requesting play during setSubtitleLanguage");
1117
+ this._targetSetSubtitlePlayingState = TargetPlayingState.PLAYING_UI;
1118
+ return Promise.resolve(true);
1119
+ }
1120
+
1067
1121
  // If seeking in progress, wait for seek to complete before playing
1068
1122
  if (this._isSeekingByApplication) {
1069
1123
  sdkLogger.info("application requesting play during seek");
@@ -1078,7 +1132,13 @@ class RemotePlayer extends EventTarget {
1078
1132
  this._seek(this._videoElement.currentTime, false);
1079
1133
  }
1080
1134
  }*/
1081
- return this._play();
1135
+
1136
+ if (this._remotePlayerApiVersion >= 2 && !this._isAudioSyncEnabled()) {
1137
+ sdkLogger.info("play was called but _isAudioSyncEnabled is disabled, ignoring.");
1138
+ return Promise.resolve(true);
1139
+ }
1140
+
1141
+ return this._play(StreamType.AUDIO);
1082
1142
  }
1083
1143
 
1084
1144
  /** Pauses the currently playing audio or video
@@ -1098,11 +1158,24 @@ class RemotePlayer extends EventTarget {
1098
1158
  throw new RemotePlayerError(6001, "Cannot call pause() if player is not loaded");
1099
1159
  }
1100
1160
  }
1161
+
1162
+ this._changePlayMode(false);
1163
+
1101
1164
  if (this._isSeekingByApplication) {
1102
1165
  sdkLogger.info("application requesting pause during seek");
1103
1166
  this._targetSeekPlayingState = TargetPlayingState.PAUSED;
1104
1167
  return Promise.resolve(true);
1105
1168
  }
1169
+ if (this._isSetAudioInProgress) {
1170
+ sdkLogger.info("application requesting pause during setAudioLanguage");
1171
+ this._targetSetAudioPlayingState = TargetPlayingState.PAUSED;
1172
+ return Promise.resolve(true);
1173
+ }
1174
+ if (this._isSetSubtitlesInProgress) {
1175
+ sdkLogger.info("application requesting pause during setSubtitleLanguage");
1176
+ this._targetSetSubtitlePlayingState = TargetPlayingState.PAUSED;
1177
+ return Promise.resolve(true);
1178
+ }
1106
1179
  return this._pause();
1107
1180
  }
1108
1181
 
@@ -1190,7 +1263,6 @@ class RemotePlayer extends EventTarget {
1190
1263
  const prevSelectedAudioTrack = this._selectedAudioTrack;
1191
1264
  for (const track of this.getAudioTracks()) {
1192
1265
  if (track.id === audioTrackId) {
1193
- this._selectedAudioTrack = audioTrackId;
1194
1266
  found = true;
1195
1267
  break;
1196
1268
  }
@@ -1199,19 +1271,33 @@ class RemotePlayer extends EventTarget {
1199
1271
  sdkLogger.warn(`Invalid audioTrackId ${audioTrackId}`);
1200
1272
  return Promise.resolve();
1201
1273
  }
1202
- if (this._remotePlayerApiVersion < 2) {
1203
- return Promise.resolve(); // Resolve immediately for older versions
1204
- }
1205
- if (prevSelectedAudioTrack === this._selectedAudioTrack) {
1274
+ if (this._selectedAudioTrack === audioTrackId) {
1206
1275
  return Promise.resolve(); // Audio language already selected
1207
1276
  }
1208
1277
 
1209
- return this._selectAudioTrack(audioTrackId, prevSelectedAudioTrack);
1278
+ switch (this._remotePlayerApiVersion) {
1279
+ case 0:
1280
+ case 1:
1281
+ this._selectedAudioTrack = audioTrackId;
1282
+ return Promise.resolve(); // Resolve immediately for older versions
1283
+ case 2:
1284
+ return this._selectAudioTrackV2(audioTrackId, prevSelectedAudioTrack);
1285
+ default:
1286
+ return this._atomicSetAudioLanguage(audioTrackId, prevSelectedAudioTrack);
1287
+ }
1210
1288
  }
1211
1289
 
1212
- async _selectAudioTrack(audioTrackId, prevSelectedAudioTrack) {
1290
+ /**
1291
+ * Handles the asynchronous selection of an audio track.
1292
+ * If the player is playing, it pauses before changing the track and resumes playback if necessary.
1293
+ *
1294
+ * @param {string} audioTrackId - The ID of the audio track to select.
1295
+ * @param {string} prevSelectedAudioTrack - The previously selected audio track ID.
1296
+ * @returns {Promise<void>} Resolves when the operation is complete.
1297
+ * */
1298
+ async _selectAudioTrackV2(audioTrackId, prevSelectedAudioTrack) {
1213
1299
  const prevIsPlaying = this._isPlaying;
1214
- sdkLogger.log(`remotePlayer _selectAudioTrack: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying}`);
1300
+ sdkLogger.log(`remotePlayer _selectAudioTrackV2: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying}`);
1215
1301
  try {
1216
1302
  if (this._isPlaying) await this.pause();
1217
1303
  let position = this.currentTime;
@@ -1219,9 +1305,9 @@ class RemotePlayer extends EventTarget {
1219
1305
  position = this._videoElement.currentTime;
1220
1306
  }
1221
1307
  await this._load(this._loadedUrl, position, audioTrackId, undefined, false);
1308
+ this._selectedAudioTrack = audioTrackId;
1222
1309
  } catch (e) {
1223
- // Do NOT reject - just log and revert selection
1224
- this._selectedAudioTrack = prevSelectedAudioTrack;
1310
+ // Do NOT reject - just log
1225
1311
  sdkLogger.warn(`Failed to select audio track ${audioTrackId}: ${e.message}`);
1226
1312
  return;
1227
1313
  }
@@ -1238,6 +1324,62 @@ class RemotePlayer extends EventTarget {
1238
1324
  }
1239
1325
  }
1240
1326
 
1327
+ /**
1328
+ * Handles the asynchronous selection of an audio track.
1329
+ * If the player is playing, it stops audio streamType before changing the track and resumes audio streamType playback if necessary.
1330
+ * Available only from v3 and on
1331
+ *
1332
+ * @param {string} audioTrackId - The ID of the audio track to select.
1333
+ * @returns {Promise<void>} Resolves when the operation is complete.
1334
+ * */
1335
+ async _selectAudioTrackV3(audioTrackId) {
1336
+ sdkLogger.log(`remotePlayer _selectAudioTrackV3: audioTrackId=${audioTrackId} isPlaying=${this._isPlaying}`);
1337
+ if (window.cefQuery) {
1338
+ const FCID = getFCID();
1339
+ const logger = sdkLogger.withFields({ FCID });
1340
+ logger.log("remotePlayer _selectAudioTrackV3: sending setAudioLanguage action");
1341
+ const message = {
1342
+ type: "remotePlayer.setAudioLanguage",
1343
+ class: "remotePlayer",
1344
+ action: "setAudioLanguage",
1345
+ fcid: FCID,
1346
+ language: audioTrackId
1347
+ };
1348
+ const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
1349
+ return new Promise((resolve, reject) => {
1350
+ let timerId = 0;
1351
+ const timeBeforeSendingRequest = Date.now();
1352
+ const queryId = window.cefQuery({
1353
+ request: JSON.stringify(request),
1354
+ persistent: false,
1355
+ onSuccess: () => {
1356
+ this._selectedAudioTrack = audioTrackId;
1357
+ const duration = Date.now() - timeBeforeSendingRequest;
1358
+ logger.withFields({ duration }).log(`setAudioLanguage completed successfully after ${duration} ms`);
1359
+ timerId = clearTimer(timerId);
1360
+ resolve();
1361
+ },
1362
+ onFailure: (code, msg) => {
1363
+ const duration = Date.now() - timeBeforeSendingRequest;
1364
+ logger.withFields({ duration }).log(`setAudioLanguage failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
1365
+ timerId = clearTimer(timerId);
1366
+ reject(new RemotePlayerError(code, msg));
1367
+ }
1368
+ });
1369
+ logger.log(`window.cefQuery for setAudioLanguage returned query id ${queryId}`);
1370
+ const timeout = this._remotePlayerConfirmationTimeout + 1000;
1371
+ timerId = setTimeout(() => {
1372
+ logger.log(`setAudioLanguage reached timeout of ${timeout} ms, canceling query id ${queryId}`);
1373
+ window.cefQueryCancel(queryId);
1374
+ reject(new RemotePlayerError(6000, `setAudioLanguage reached timeout of ${timeout} ms`));
1375
+ }, timeout, queryId);
1376
+ });
1377
+ }
1378
+
1379
+ sdkLogger.error("remotePlayer _selectAudioTrackV3: window.cefQuery is undefined");
1380
+ return Promise.resolve(undefined);
1381
+ }
1382
+
1241
1383
  /** Select a specific text (subtitle) track.
1242
1384
  * Track id should come from a call to getTextTracks.
1243
1385
  * If no tracks exist - this is a no-op.
@@ -1257,7 +1399,6 @@ class RemotePlayer extends EventTarget {
1257
1399
  const prevSelectedTextTrack = this._selectedSubtitlesTrack;
1258
1400
  for (const track of this.getTextTracks()) {
1259
1401
  if (track.id === textTrackId) {
1260
- this._selectedSubtitlesTrack = textTrackId;
1261
1402
  found = true;
1262
1403
  break;
1263
1404
  }
@@ -1266,14 +1407,20 @@ class RemotePlayer extends EventTarget {
1266
1407
  sdkLogger.warn(`Invalid textTrackId ${textTrackId}`);
1267
1408
  return Promise.resolve();
1268
1409
  }
1269
- if (this._remotePlayerApiVersion < 2) {
1270
- return Promise.resolve(); // Resolve immediately for older versions
1271
- }
1272
- if (prevSelectedTextTrack === this._selectedSubtitlesTrack) {
1410
+ if (this._selectedSubtitlesTrack === textTrackId) {
1273
1411
  return Promise.resolve(); // Subtitle language already selected
1274
1412
  }
1275
1413
 
1276
- return this._selectTextTrack(textTrackId, prevSelectedTextTrack);
1414
+ switch (this._remotePlayerApiVersion) {
1415
+ case 0:
1416
+ case 1:
1417
+ this._selectedSubtitlesTrack = textTrackId;
1418
+ return Promise.resolve(); // Resolve immediately for older versions
1419
+ case 2:
1420
+ return this._selectTextTrackV2(textTrackId, prevSelectedTextTrack);
1421
+ default:
1422
+ return this._atomicSetSubtitleLanguage(textTrackId, prevSelectedTextTrack);
1423
+ }
1277
1424
  }
1278
1425
 
1279
1426
  /**
@@ -1284,9 +1431,9 @@ class RemotePlayer extends EventTarget {
1284
1431
  * @param {string} prevSelectedTextTrack - The previously selected text track ID.
1285
1432
  * @returns {Promise<void>} Resolves when the operation is complete.
1286
1433
  * */
1287
- async _selectTextTrack(textTrackId, prevSelectedTextTrack) {
1434
+ async _selectTextTrackV2(textTrackId, prevSelectedTextTrack) {
1288
1435
  const prevIsPlaying = this._isPlaying;
1289
- sdkLogger.log(`remotePlayer _selectTextTrack: prevTextTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying}`);
1436
+ sdkLogger.log(`remotePlayer _selectTextTrackV2: prevTextTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying}`);
1290
1437
  try {
1291
1438
  if (this._isPlaying) await this.pause();
1292
1439
  let position = this.currentTime;
@@ -1294,9 +1441,9 @@ class RemotePlayer extends EventTarget {
1294
1441
  position = this._videoElement.currentTime;
1295
1442
  }
1296
1443
  await this._load(this._loadedUrl, position, undefined, textTrackId, false);
1444
+ this._selectedSubtitlesTrack = textTrackId;
1297
1445
  } catch (e) {
1298
- // Do NOT reject - just log and revert selection
1299
- this._selectedSubtitlesTrack = prevSelectedTextTrack;
1446
+ // Do NOT reject - just log
1300
1447
  sdkLogger.warn(`Failed to select text track ${textTrackId}: ${e.message}`);
1301
1448
  return;
1302
1449
  }
@@ -1313,6 +1460,60 @@ class RemotePlayer extends EventTarget {
1313
1460
  }
1314
1461
  }
1315
1462
 
1463
+ /**
1464
+ * Handles the asynchronous selection of a text track.
1465
+ * If the player is playing, it stops subtitle streamType before changing the track and resumes subtitle streamType playback if necessary.
1466
+ * Available only from v3 and on
1467
+ *
1468
+ * @param {string} textTrackId - The ID of the text track to select.
1469
+ * @returns {Promise<void>} Resolves when the operation is complete.
1470
+ * */
1471
+ async _selectTextTrackV3(textTrackId) {
1472
+ if (window.cefQuery) {
1473
+ const FCID = getFCID();
1474
+ const logger = sdkLogger.withFields({ FCID });
1475
+ logger.log("remotePlayer _selectTextTrackV3: sending setSubtitleLanguage action");
1476
+ const message = {
1477
+ type: "remotePlayer.setSubtitleLanguage",
1478
+ class: "remotePlayer",
1479
+ action: "setSubtitleLanguage",
1480
+ fcid: FCID,
1481
+ language: textTrackId
1482
+ };
1483
+ const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
1484
+ return new Promise((resolve, reject) => {
1485
+ let timerId = 0;
1486
+ const timeBeforeSendingRequest = Date.now();
1487
+ const queryId = window.cefQuery({
1488
+ request: JSON.stringify(request),
1489
+ persistent: false,
1490
+ onSuccess: () => {
1491
+ this._selectedSubtitlesTrack = textTrackId;
1492
+ const duration = Date.now() - timeBeforeSendingRequest;
1493
+ logger.withFields({ duration }).log(`setSubtitleLanguage completed successfully after ${duration} ms`);
1494
+ timerId = clearTimer(timerId);
1495
+ resolve();
1496
+ },
1497
+ onFailure: (code, msg) => {
1498
+ const duration = Date.now() - timeBeforeSendingRequest;
1499
+ logger.withFields({ duration }).log(`setSubtitleLanguage failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
1500
+ timerId = clearTimer(timerId);
1501
+ reject(new RemotePlayerError(code, msg));
1502
+ }
1503
+ });
1504
+ logger.log(`window.cefQuery for setSubtitleLanguage returned query id ${queryId}`);
1505
+ const timeout = this._remotePlayerConfirmationTimeout + 1000;
1506
+ timerId = setTimeout(() => {
1507
+ logger.log(`setSubtitleLanguage reached timeout of ${timeout} ms, canceling query id ${queryId}`);
1508
+ window.cefQueryCancel(queryId);
1509
+ reject(new RemotePlayerError(6000, `setSubtitleLanguage reached timeout of ${timeout} ms`));
1510
+ }, timeout, queryId);
1511
+ });
1512
+ }
1513
+ sdkLogger.error("remotePlayer _selectTextTrackV3: window.cefQuery is undefined");
1514
+ return Promise.resolve(undefined);
1515
+ }
1516
+
1316
1517
  /**
1317
1518
  * Enable or disable the subtitles.
1318
1519
  * If the player is in an unloaded state, the request will be applied next time content is played.
@@ -1423,6 +1624,11 @@ class RemotePlayer extends EventTarget {
1423
1624
  * @private
1424
1625
  */
1425
1626
  async _startSeeking(playbackPosition) {
1627
+ if (this._isSetAudioInProgress || this._isSetSubtitlesInProgress) {
1628
+ sdkLogger.info(`Seeking not supported while setAudioLanguage or setSubtitleLanguage are in progress.`);
1629
+ return;
1630
+ }
1631
+
1426
1632
  if (!this._isSeekingByPlatform) {
1427
1633
  this._pendingSeekPosition = playbackPosition;
1428
1634
 
@@ -1534,7 +1740,11 @@ class RemotePlayer extends EventTarget {
1534
1740
  // In case where we aborted, we don't want to resume playback.
1535
1741
  if (!this._abortSeeking) {
1536
1742
  if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_UI) {
1537
- this._play();
1743
+ if (!this._isAudioSyncEnabled()) {
1744
+ return Promise.resolve(true);
1745
+ }
1746
+ // resume audio play only if _isAudioSyncEnabled
1747
+ this._play(StreamType.AUDIO);
1538
1748
  } else if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_ABR) {
1539
1749
  lifecycle._moveToBackground();
1540
1750
  }
@@ -1543,6 +1753,139 @@ class RemotePlayer extends EventTarget {
1543
1753
  this._isSeekingByApplication = false;
1544
1754
  sdkLogger.info("Seeking: local video element seeking end");
1545
1755
  }
1756
+
1757
+ async _atomicSetAudioLanguage(audioTrackId, prevSelectedAudioTrack) {
1758
+ if (this._isSetAudioInProgress) {
1759
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} already in progress, ignoring.`);
1760
+ return Promise.resolve();
1761
+ }
1762
+ if (this._isSeekingByApplication) {
1763
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} ignored. seeking is already in progress.`);
1764
+ return Promise.resolve();
1765
+ }
1766
+
1767
+ this._targetSetAudioPlayingState = this._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
1768
+ sdkLogger.log(`remotePlayer _atomicSetAudioLanguage: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying} targetState=${this._targetSetAudioPlayingState}`);
1769
+
1770
+ this._abortSetAudioLanguage = false;
1771
+ this._isSetAudioInProgress = true;
1772
+
1773
+ try {
1774
+ await this._stop(StreamType.AUDIO);
1775
+ if (this._abortSetAudioLanguage) {
1776
+ this._isSetAudioInProgress = false;
1777
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} aborted.`);
1778
+ return Promise.resolve();
1779
+ }
1780
+ } catch (error) {
1781
+ this._isSetAudioInProgress = false;
1782
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} failed on stop. aborting.`);
1783
+ return Promise.reject(error);
1784
+ }
1785
+
1786
+ let setLanguageError;
1787
+ try {
1788
+ await this._selectAudioTrackV3(audioTrackId);
1789
+ } catch (error) {
1790
+ sdkLogger.error(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} failed with error ${error.message}.`);
1791
+ if (!this._abortSetAudioLanguage) {
1792
+ setLanguageError = error;
1793
+ }
1794
+ }
1795
+
1796
+ // check if load/unload were called
1797
+ if (this._abortSetAudioLanguage) {
1798
+ this._isSetAudioInProgress = false;
1799
+ sdkLogger.warn(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} aborted.`);
1800
+ return Promise.resolve();
1801
+ }
1802
+
1803
+ try {
1804
+ if (this._targetSetAudioPlayingState === TargetPlayingState.PLAYING_UI) {
1805
+ if (!this._isAudioSyncEnabled()) {
1806
+ this._isSetAudioInProgress = false;
1807
+ return Promise.resolve(true);
1808
+ }
1809
+ // resume audio play only if _isAudioSyncEnabled
1810
+ await this._play(StreamType.AUDIO);
1811
+ } else if (this._targetSetAudioPlayingState === TargetPlayingState.PLAYING_ABR) {
1812
+ await lifecycle._moveToBackground();
1813
+ }
1814
+ } catch(error) {
1815
+ sdkLogger.error(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} play failed with error ${error.message}.`);
1816
+ }
1817
+
1818
+ this._isSetAudioInProgress = false;
1819
+ sdkLogger.log(`remotePlayer _atomicSetAudioLanguage: audioTrackId=${audioTrackId} ended.`);
1820
+ return setLanguageError ? Promise.reject(setLanguageError) : Promise.resolve();
1821
+ }
1822
+
1823
+ async _atomicSetSubtitleLanguage(textTrackId, prevSelectedTextTrack) {
1824
+ if (this._isSetSubtitlesInProgress) {
1825
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} already in progress., ignoring.`);
1826
+ return Promise.resolve();
1827
+ }
1828
+ if (this._isSeekingByApplication) {
1829
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} ignored. seeking is already in progress.`);
1830
+ return Promise.resolve();
1831
+ }
1832
+
1833
+ this._targetSetSubtitlePlayingState = this._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
1834
+ sdkLogger.log(`remotePlayer _atomicSetSubtitleLanguage: prevTextTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying} targetState=${this._targetSetSubtitlePlayingState}`);
1835
+
1836
+ this._abortSetSubtitleLanguage = false;
1837
+ this._isSetSubtitlesInProgress = true;
1838
+
1839
+ try {
1840
+ await this._stop(StreamType.SUBTITLE);
1841
+ if (this._abortSetSubtitleLanguage) {
1842
+ this._isSetSubtitlesInProgress = false;
1843
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} aborted.`);
1844
+ return Promise.resolve();
1845
+ }
1846
+ } catch (error) {
1847
+ this._isSetSubtitlesInProgress = false;
1848
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} failed on stop. aborting.`);
1849
+ return Promise.reject(error);
1850
+ }
1851
+
1852
+ let setLanguageError;
1853
+ try {
1854
+ await this._selectTextTrackV3(textTrackId);
1855
+ } catch (error) {
1856
+ sdkLogger.error(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} failed with error ${error.message}.`);
1857
+ if (!this._abortSetSubtitleLanguage) {
1858
+ setLanguageError = error;
1859
+ }
1860
+ }
1861
+
1862
+ // check if load/unload were called
1863
+ if (this._abortSetSubtitleLanguage) {
1864
+ this._isSetSubtitlesInProgress = false;
1865
+ sdkLogger.warn(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} aborted.`);
1866
+ return Promise.resolve();
1867
+ }
1868
+
1869
+ try {
1870
+ if (this._targetSetSubtitlePlayingState === TargetPlayingState.PLAYING_UI) {
1871
+ if (!this._isAudioSyncEnabled()) {
1872
+ this._isSetSubtitlesInProgress = false;
1873
+ return Promise.resolve(true);
1874
+ }
1875
+ // resume audio play only if _isAudioSyncEnabled
1876
+ await this._play(StreamType.AUDIO);
1877
+ } else if (this._targetSetSubtitlePlayingState === TargetPlayingState.PLAYING_ABR) {
1878
+ await lifecycle._moveToBackground();
1879
+ }
1880
+ } catch (error) {
1881
+ sdkLogger.error(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} play failed with error ${error.message}.`);
1882
+ }
1883
+
1884
+ this._isSetSubtitlesInProgress = false;
1885
+ sdkLogger.log(`remotePlayer _atomicSetSubtitleLanguage: textTrackId=${textTrackId} ended.`);
1886
+ return setLanguageError ? Promise.reject(setLanguageError) : Promise.resolve();
1887
+ }
1888
+
1546
1889
  }
1547
1890
  /**
1548
1891
  *