senza-sdk 4.2.49 → 4.2.51-00bf04a.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.49",
3
+ "version": "4.2.51-00bf04a.0",
4
4
  "main": "./src/api.js",
5
5
  "description": "API for Senza application",
6
6
  "license": "MIT",
@@ -9,7 +9,9 @@ import {
9
9
  SeekState,
10
10
  TargetPlayingState,
11
11
  isSubtitlesTranslationAllowed,
12
- isSubtitlesTranslationPattern
12
+ isSubtitlesTranslationPattern,
13
+ SetAudioLanguageState,
14
+ SetSubtitleLanguageState
13
15
  } from "./utils";
14
16
  import { lifecycle } from "./lifecycle";
15
17
  import { writeLicenseResponse } from "./api";
@@ -586,7 +588,7 @@ class RemotePlayer extends EventTarget {
586
588
  currentFramePTS: metadata.mediaTime.toString(),
587
589
  ptsSessionId: this._ptsSessionId
588
590
  };
589
- const request = {target: "UI-Streamer", waitForResponse: false, message: JSON.stringify(message)};
591
+ const request = { target: "UI-Streamer", waitForResponse: false, message: JSON.stringify(message) };
590
592
  window.cefQuery({
591
593
  request: JSON.stringify(request),
592
594
  persistent: false
@@ -890,6 +892,8 @@ class RemotePlayer extends EventTarget {
890
892
  if (this._loadMode === this.LoadMode.LOADING || this._loadMode === this.LoadMode.UNLOADING) {
891
893
  throw new RemotePlayerError(6501, "Cannot call load() while previous load/unload is still in progress");
892
894
  }
895
+ this._abortSetAudioLanguage = true;
896
+ this._abortSetSubtitleLanguage = true;
893
897
  this._abortSeeking = true;
894
898
  if (reset) {
895
899
  this._reset();
@@ -1063,6 +1067,18 @@ class RemotePlayer extends EventTarget {
1063
1067
  }
1064
1068
  }
1065
1069
 
1070
+ if (this._isSetAudioByApplication) {
1071
+ sdkLogger.info("application requesting play during setAudioLanguage");
1072
+ this._targetSetAudioPlayingState = TargetPlayingState.PLAYING_UI;
1073
+ return Promise.resolve(true);
1074
+ }
1075
+
1076
+ if (this._isSetSubtitleByApplication) {
1077
+ sdkLogger.info("application requesting play during setSubtitleLanguage");
1078
+ this._targetSetSubtitlePlayingState = TargetPlayingState.PLAYING_UI;
1079
+ return Promise.resolve(true);
1080
+ }
1081
+
1066
1082
  // If seeking in progress, wait for seek to complete before playing
1067
1083
  if (this._isSeekingByApplication) {
1068
1084
  sdkLogger.info("application requesting play during seek");
@@ -1102,6 +1118,16 @@ class RemotePlayer extends EventTarget {
1102
1118
  this._targetSeekPlayingState = TargetPlayingState.PAUSED;
1103
1119
  return Promise.resolve(true);
1104
1120
  }
1121
+ if (this._isSetAudioByApplication) {
1122
+ sdkLogger.info("application requesting pause during setAudioLanguage");
1123
+ this._targetSetAudioPlayingState = TargetPlayingState.PAUSED;
1124
+ return Promise.resolve(true);
1125
+ }
1126
+ if (this._isSetSubtitleByApplication) {
1127
+ sdkLogger.info("application requesting pause during setSubtitleLanguage");
1128
+ this._targetSetSubtitlePlayingState = TargetPlayingState.PAUSED;
1129
+ return Promise.resolve(true);
1130
+ }
1105
1131
  return this._pause();
1106
1132
  }
1107
1133
 
@@ -1189,7 +1215,6 @@ class RemotePlayer extends EventTarget {
1189
1215
  const prevSelectedAudioTrack = this._selectedAudioTrack;
1190
1216
  for (const track of this.getAudioTracks()) {
1191
1217
  if (track.id === audioTrackId) {
1192
- this._selectedAudioTrack = audioTrackId;
1193
1218
  found = true;
1194
1219
  break;
1195
1220
  }
@@ -1198,19 +1223,34 @@ class RemotePlayer extends EventTarget {
1198
1223
  sdkLogger.warn(`Invalid audioTrackId ${audioTrackId}`);
1199
1224
  return Promise.resolve();
1200
1225
  }
1201
- if (this._remotePlayerApiVersion < 2) {
1202
- return Promise.resolve(); // Resolve immediately for older versions
1203
- }
1204
- if (prevSelectedAudioTrack === this._selectedAudioTrack) {
1226
+ if (this._selectedAudioTrack === audioTrackId) {
1205
1227
  return Promise.resolve(); // Audio language already selected
1206
1228
  }
1207
1229
 
1208
- return this._selectAudioTrack(audioTrackId, prevSelectedAudioTrack);
1230
+ switch (this._remotePlayerApiVersion) {
1231
+ case 0:
1232
+ case 1:
1233
+ this._selectedAudioTrack = audioTrackId;
1234
+ return Promise.resolve(); // Resolve immediately for older versions
1235
+ case 2:
1236
+ return this._selectAudioTrackV2(audioTrackId, prevSelectedAudioTrack);
1237
+ default:
1238
+ this._pendingAudioLanguage = audioTrackId;
1239
+ return this._atomicSetAudioLanguage();
1240
+ }
1209
1241
  }
1210
1242
 
1211
- async _selectAudioTrack(audioTrackId, prevSelectedAudioTrack) {
1243
+ /**
1244
+ * Handles the asynchronous selection of an audio track.
1245
+ * If the player is playing, it pauses before changing the track and resumes playback if necessary.
1246
+ *
1247
+ * @param {string} audioTrackId - The ID of the audio track to select.
1248
+ * @param {string} prevSelectedAudioTrack - The previously selected audio track ID.
1249
+ * @returns {Promise<void>} Resolves when the operation is complete.
1250
+ * */
1251
+ async _selectAudioTrackV2(audioTrackId, prevSelectedAudioTrack) {
1212
1252
  const prevIsPlaying = this._isPlaying;
1213
- sdkLogger.log(`remotePlayer _selectAudioTrack: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying}`);
1253
+ sdkLogger.log(`remotePlayer _selectAudioTrackV2: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying}`);
1214
1254
  try {
1215
1255
  if (this._isPlaying) await this.pause();
1216
1256
  let position = this.currentTime;
@@ -1218,9 +1258,9 @@ class RemotePlayer extends EventTarget {
1218
1258
  position = this._videoElement.currentTime;
1219
1259
  }
1220
1260
  await this._load(this._loadedUrl, position, audioTrackId, undefined, false);
1261
+ this._selectedAudioTrack = audioTrackId;
1221
1262
  } catch (e) {
1222
- // Do NOT reject - just log and revert selection
1223
- this._selectedAudioTrack = prevSelectedAudioTrack;
1263
+ // Do NOT reject - just log
1224
1264
  sdkLogger.warn(`Failed to select audio track ${audioTrackId}: ${e.message}`);
1225
1265
  return;
1226
1266
  }
@@ -1237,6 +1277,63 @@ class RemotePlayer extends EventTarget {
1237
1277
  }
1238
1278
  }
1239
1279
 
1280
+ /**
1281
+ * Handles the asynchronous selection of an audio track.
1282
+ * If the player is playing, it stops subtitle streamType before changing the track and resumes subtitle streamType playback if necessary.
1283
+ * Available only from v3 and on
1284
+ *
1285
+ * @param {string} audioTrackId - The ID of the audio track to select.
1286
+ * @param {string} prevSelectedAudioTrack - The previously selected audio track ID.
1287
+ * @returns {Promise<void>} Resolves when the operation is complete.
1288
+ * */
1289
+ async _selectAudioTrackV3(audioTrackId, prevSelectedAudioTrack) {
1290
+ sdkLogger.log(`remotePlayer _selectAudioTrackV3: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying}`);
1291
+ if (window.cefQuery) {
1292
+ const FCID = getFCID();
1293
+ const logger = sdkLogger.withFields({ FCID });
1294
+ logger.log("remotePlayer _selectAudioTrackV3: sending setAudioLanguage action");
1295
+ const message = {
1296
+ type: "remotePlayer.setAudioLanguage",
1297
+ class: "remotePlayer",
1298
+ action: "setAudioLanguage",
1299
+ fcid: FCID,
1300
+ language: audioTrackId,
1301
+ };
1302
+ const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
1303
+ return new Promise((resolve, reject) => {
1304
+ let timerId = 0;
1305
+ const timeBeforeSendingRequest = Date.now();
1306
+ const queryId = window.cefQuery({
1307
+ request: JSON.stringify(request),
1308
+ persistent: false,
1309
+ onSuccess: () => {
1310
+ this._selectedAudioTrack = audioTrackId;
1311
+ const duration = Date.now() - timeBeforeSendingRequest;
1312
+ logger.withFields({ duration }).log(`setAudioLanguage completed successfully after ${duration} ms`);
1313
+ timerId = clearTimer(timerId);
1314
+ resolve();
1315
+ },
1316
+ onFailure: (code, msg) => {
1317
+ const duration = Date.now() - timeBeforeSendingRequest;
1318
+ logger.withFields({ duration }).log(`setAudioLanguage failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
1319
+ timerId = clearTimer(timerId);
1320
+ reject(new RemotePlayerError(code, msg));
1321
+ }
1322
+ });
1323
+ logger.log(`window.cefQuery for setAudioLanguage returned query id ${queryId}`);
1324
+ const timeout = this._remotePlayerConfirmationTimeout + 1000;
1325
+ timerId = setTimeout(() => {
1326
+ logger.log(`setAudioLanguage reached timeout of ${timeout} ms, canceling query id ${queryId}`);
1327
+ window.cefQueryCancel(queryId);
1328
+ reject(new RemotePlayerError(6000, `setAudioLanguage reached timeout of ${timeout} ms`));
1329
+ }, timeout, queryId);
1330
+ });
1331
+ }
1332
+
1333
+ sdkLogger.error("remotePlayer _selectAudioTrackV3: window.cefQuery is undefined");
1334
+ return Promise.resolve(undefined);
1335
+ }
1336
+
1240
1337
  /** Select a specific text (subtitle) track.
1241
1338
  * Track id should come from a call to getTextTracks.
1242
1339
  * If no tracks exist - this is a no-op.
@@ -1256,7 +1353,6 @@ class RemotePlayer extends EventTarget {
1256
1353
  const prevSelectedTextTrack = this._selectedSubtitlesTrack;
1257
1354
  for (const track of this.getTextTracks()) {
1258
1355
  if (track.id === textTrackId) {
1259
- this._selectedSubtitlesTrack = textTrackId;
1260
1356
  found = true;
1261
1357
  break;
1262
1358
  }
@@ -1265,14 +1361,21 @@ class RemotePlayer extends EventTarget {
1265
1361
  sdkLogger.warn(`Invalid textTrackId ${textTrackId}`);
1266
1362
  return Promise.resolve();
1267
1363
  }
1268
- if (this._remotePlayerApiVersion < 2) {
1269
- return Promise.resolve(); // Resolve immediately for older versions
1270
- }
1271
- if (prevSelectedTextTrack === this._selectedSubtitlesTrack) {
1364
+ if (this._selectedSubtitlesTrack === textTrackId) {
1272
1365
  return Promise.resolve(); // Subtitle language already selected
1273
1366
  }
1274
1367
 
1275
- return this._selectTextTrack(textTrackId, prevSelectedTextTrack);
1368
+ switch (this._remotePlayerApiVersion) {
1369
+ case 0:
1370
+ case 1:
1371
+ this._selectedSubtitlesTrack = textTrackId;
1372
+ return Promise.resolve(); // Resolve immediately for older versions
1373
+ case 2:
1374
+ return this._selectTextTrackV2(textTrackId, prevSelectedTextTrack);
1375
+ default:
1376
+ this._pendingSubtitleLanguage = textTrackId;
1377
+ return this._atomicSetSubtitleLanguage();
1378
+ }
1276
1379
  }
1277
1380
 
1278
1381
  /**
@@ -1283,9 +1386,9 @@ class RemotePlayer extends EventTarget {
1283
1386
  * @param {string} prevSelectedTextTrack - The previously selected text track ID.
1284
1387
  * @returns {Promise<void>} Resolves when the operation is complete.
1285
1388
  * */
1286
- async _selectTextTrack(textTrackId, prevSelectedTextTrack) {
1389
+ async _selectTextTrackV2(textTrackId, prevSelectedTextTrack) {
1287
1390
  const prevIsPlaying = this._isPlaying;
1288
- sdkLogger.log(`remotePlayer _selectTextTrack: prevTextTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying}`);
1391
+ sdkLogger.log(`remotePlayer _selectTextTrackV2: prevTextTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying}`);
1289
1392
  try {
1290
1393
  if (this._isPlaying) await this.pause();
1291
1394
  let position = this.currentTime;
@@ -1293,9 +1396,9 @@ class RemotePlayer extends EventTarget {
1293
1396
  position = this._videoElement.currentTime;
1294
1397
  }
1295
1398
  await this._load(this._loadedUrl, position, undefined, textTrackId, false);
1399
+ this._selectedSubtitlesTrack = textTrackId;
1296
1400
  } catch (e) {
1297
- // Do NOT reject - just log and revert selection
1298
- this._selectedSubtitlesTrack = prevSelectedTextTrack;
1401
+ // Do NOT reject - just log
1299
1402
  sdkLogger.warn(`Failed to select text track ${textTrackId}: ${e.message}`);
1300
1403
  return;
1301
1404
  }
@@ -1312,6 +1415,65 @@ class RemotePlayer extends EventTarget {
1312
1415
  }
1313
1416
  }
1314
1417
 
1418
+ /**
1419
+ * Handles the asynchronous selection of a text track.
1420
+ * If the player is playing, it stops subtitle streamType before changing the track and resumes subtitle streamType playback if necessary.
1421
+ * Available only from v3 and on
1422
+ *
1423
+ * @param {string} textTrackId - The ID of the text track to select.
1424
+ * @param {string} prevSelectedTextTrack - The previously selected text track ID.
1425
+ * @returns {Promise<void>} Resolves when the operation is complete.
1426
+ * */
1427
+ async _selectTextTrackV3(textTrackId, prevSelectedTextTrack) {
1428
+ sdkLogger.log(`remotePlayer _selectTextTrackV3: prevAudioTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying}`);
1429
+ if (window.cefQuery) {
1430
+ const FCID = getFCID();
1431
+ const logger = sdkLogger.withFields({ FCID });
1432
+ logger.log("remotePlayer _selectTextTrackV3: sending setSubtitleLanguage action");
1433
+ const message = {
1434
+ type: "remotePlayer.setSubtitleLanguage",
1435
+ class: "remotePlayer",
1436
+ action: "setSubtitleLanguage",
1437
+ fcid: FCID,
1438
+ language: textTrackId,
1439
+ };
1440
+ const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
1441
+ return new Promise((resolve, reject) => {
1442
+ let timerId = 0;
1443
+ const timeBeforeSendingRequest = Date.now();
1444
+ const queryId = window.cefQuery({
1445
+ request: JSON.stringify(request),
1446
+ persistent: false,
1447
+ onSuccess: () => {
1448
+ this._selectedSubtitlesTrack = textTrackId;
1449
+ const duration = Date.now() - timeBeforeSendingRequest;
1450
+ logger.withFields({ duration }).log(`setSubtitleLanguage completed successfully after ${duration} ms`);
1451
+ timerId = clearTimer(timerId);
1452
+ resolve();
1453
+ },
1454
+ onFailure: (code, msg) => {
1455
+ const duration = Date.now() - timeBeforeSendingRequest;
1456
+ logger.withFields({ duration }).log(`setSubtitleLanguage failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
1457
+ timerId = clearTimer(timerId);
1458
+ reject(new RemotePlayerError(code, msg));
1459
+ }
1460
+ });
1461
+ logger.log(`window.cefQuery for setSubtitleLanguage returned query id ${queryId}`);
1462
+ const timeout = this._remotePlayerConfirmationTimeout + 1000;
1463
+ timerId = setTimeout(() => {
1464
+ logger.log(`setSubtitleLanguage reached timeout of ${timeout} ms, canceling query id ${queryId}`);
1465
+ window.cefQueryCancel(queryId);
1466
+ reject(new RemotePlayerError(6000, `setSubtitleLanguage reached timeout of ${timeout} ms`));
1467
+ }, timeout, queryId);
1468
+ });
1469
+ }
1470
+ sdkLogger.error("remotePlayer _selectAudioTrackV3: window.cefQuery is undefined");
1471
+ return Promise.resolve(undefined);
1472
+
1473
+
1474
+ }
1475
+
1476
+
1315
1477
  /**
1316
1478
  * Enable or disable the subtitles.
1317
1479
  * If the player is in an unloaded state, the request will be applied next time content is played.
@@ -1538,6 +1700,274 @@ class RemotePlayer extends EventTarget {
1538
1700
  this._isSeekingByApplication = false;
1539
1701
  sdkLogger.info("Seeking: local video element seeking end");
1540
1702
  }
1703
+
1704
+ async _stopAudio() {
1705
+ if (window.cefQuery) {
1706
+ if (this._remotePlayerApiVersion >= 2) {
1707
+ return new Promise((resolve, reject) => {
1708
+ const FCID = getFCID();
1709
+ const logger = sdkLogger.withFields({ FCID });
1710
+ logger.log(`remotePlayer _stopAudio: sending stop action remotePlayer._isPlaying=${remotePlayer._isPlaying}`);
1711
+ const message = {
1712
+ type: "remotePlayer.stop",
1713
+ class: "remotePlayer",
1714
+ action: "stop",
1715
+ streamType: StreamType.AUDIO,
1716
+ fcid: FCID
1717
+ };
1718
+ let timerId = 0;
1719
+ const timeBeforeSendingRequest = Date.now();
1720
+ const queryId = window.cefQuery({
1721
+ request: JSON.stringify({ target: "TC", waitForResponse: true, internalAction: "uiActive", message: JSON.stringify(message) }),
1722
+ persistent: false,
1723
+ onSuccess: () => {
1724
+ const duration = Date.now() - timeBeforeSendingRequest;
1725
+ logger.withFields({ duration }).log(`stop audio completed successfully after ${duration} ms`);
1726
+ this._inTransition = false;
1727
+ timerId = clearTimer(timerId);
1728
+ resolve(true);
1729
+ },
1730
+ onFailure: (code, msg) => {
1731
+ const duration = Date.now() - timeBeforeSendingRequest;
1732
+ logger.withFields({ duration }).log(`stop audio failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
1733
+ this._inTransition = false;
1734
+ timerId = clearTimer(timerId);
1735
+ reject(new SenzaError(code, msg));
1736
+ }
1737
+ });
1738
+ logger.log(`window.cefQuery for stop audio returned query id ${queryId}`);
1739
+ const timeout = this._remotePlayerConfirmationTimeout + 1000;
1740
+ timerId = setTimeout(() => {
1741
+ logger.log(`stop audio reached timeout of ${timeout} ms, canceling query id ${queryId}`);
1742
+ this._inTransition = false;
1743
+ window.cefQueryCancel(queryId);
1744
+ reject(new SenzaError(6000, `stop audio reached timeout of ${timeout} ms`));
1745
+ }, timeout, queryId);
1746
+ });
1747
+ }
1748
+ }
1749
+
1750
+ }
1751
+
1752
+ async _stopSubtitle() {
1753
+ if (window.cefQuery) {
1754
+ if (this._remotePlayerApiVersion >= 2) {
1755
+ return new Promise((resolve, reject) => {
1756
+ const FCID = getFCID();
1757
+ const logger = sdkLogger.withFields({ FCID });
1758
+ logger.log(`remotePlayer _stopSubtitle: sending stop action remotePlayer._isPlaying=${remotePlayer._isPlaying}`);
1759
+ const message = {
1760
+ type: "remotePlayer.stop",
1761
+ class: "remotePlayer",
1762
+ action: "stop",
1763
+ streamType: StreamType.SUBTITLE,
1764
+ fcid: FCID
1765
+ };
1766
+ let timerId = 0;
1767
+ const timeBeforeSendingRequest = Date.now();
1768
+ const queryId = window.cefQuery({
1769
+ request: JSON.stringify({ target: "TC", waitForResponse: true, internalAction: "uiActive", message: JSON.stringify(message) }),
1770
+ persistent: false,
1771
+ onSuccess: () => {
1772
+ const duration = Date.now() - timeBeforeSendingRequest;
1773
+ logger.withFields({ duration }).log(`stop subtitle completed successfully after ${duration} ms`);
1774
+ this._inTransition = false;
1775
+ timerId = clearTimer(timerId);
1776
+ resolve(true);
1777
+ },
1778
+ onFailure: (code, msg) => {
1779
+ const duration = Date.now() - timeBeforeSendingRequest;
1780
+ logger.withFields({ duration }).log(`stop subtitle failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
1781
+ this._inTransition = false;
1782
+ timerId = clearTimer(timerId);
1783
+ reject(new SenzaError(code, msg));
1784
+ }
1785
+ });
1786
+ logger.log(`window.cefQuery for stop returned query id ${queryId}`);
1787
+ const timeout = this._remotePlayerConfirmationTimeout + 1000;
1788
+ timerId = setTimeout(() => {
1789
+ logger.log(`stop reached timeout of ${timeout} ms, canceling query id ${queryId}`);
1790
+ window.cefQueryCancel(queryId);
1791
+ reject(new SenzaError(6000, `stop reached timeout of ${timeout} ms`));
1792
+ }, timeout, queryId);
1793
+ });
1794
+ }
1795
+ }
1796
+
1797
+ }
1798
+
1799
+ async _playSubtitle() {
1800
+ if (window.cefQuery) {
1801
+ if (this._remotePlayerApiVersion >= 2) {
1802
+ return new Promise((resolve, reject) => {
1803
+ const FCID = getFCID();
1804
+ const logger = sdkLogger.withFields({ FCID });
1805
+ logger.log("remotePlayer play subtitle: sending play action");
1806
+ const configuration = remotePlayer.getConfiguration();
1807
+ const subtitlesLanguage = remotePlayer.textTrackVisibility && (remotePlayer._selectedSubtitlesTrack || configuration.preferredSubtitlesLanguage) || "";
1808
+ let request;
1809
+ const message = {
1810
+ action: "play",
1811
+ fcid: FCID,
1812
+ subtitlesLanguage
1813
+ };
1814
+ if (this._remotePlayerApiVersion >= 2) {
1815
+ message.type = "remotePlayer.play";
1816
+ message.class = "remotePlayer";
1817
+ message.streamType = StreamType.SUBTITLE;
1818
+ request = {
1819
+ target: "TC",
1820
+ waitForResponse: true,
1821
+ internalAction: "uiExit",
1822
+ message: JSON.stringify(message)
1823
+ };
1824
+ } else {
1825
+ request = message;
1826
+ }
1827
+ let timerId = 0;
1828
+ const timeBeforeSendingRequest = Date.now();
1829
+ const queryId = window.cefQuery({
1830
+ request: JSON.stringify(request),
1831
+ persistent: false,
1832
+ onSuccess: () => {
1833
+ const duration = Date.now() - timeBeforeSendingRequest;
1834
+ logger.withFields({ duration }).log(`play subtitle completed successfully after ${duration} ms`);
1835
+ this._inTransition = false;
1836
+ timerId = clearTimer(timerId);
1837
+ resolve();
1838
+ },
1839
+ onFailure: (code, msg) => {
1840
+ const duration = Date.now() - timeBeforeSendingRequest;
1841
+ logger.withFields({ duration }).log(`play subtitle failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
1842
+ this._inTransition = false;
1843
+ timerId = clearTimer(timerId);
1844
+ reject(new SenzaError(code, msg));
1845
+ }
1846
+ });
1847
+ if (this._remotePlayerApiVersion >= 2) {
1848
+ logger.log(`window.cefQuery for play subtitle returned query id ${queryId}`);
1849
+ const timeout = this._remotePlayerConfirmationTimeout + 1000;
1850
+ timerId = setTimeout(() => {
1851
+ logger.log(`play subtitle reached timeout of ${timeout} ms, canceling query id ${queryId}`);
1852
+ this._inTransition = false;
1853
+ window.cefQueryCancel(queryId);
1854
+ reject(new SenzaError(6000, `play subtitle reached timeout of ${timeout} ms`));
1855
+ }, timeout, queryId);
1856
+ }
1857
+ });
1858
+ }
1859
+
1860
+ }
1861
+ sdkLogger.error("remotePlayer play subtitle: window.cefQuery is undefined");
1862
+ return Promise.resolve(undefined);
1863
+ }
1864
+
1865
+ async _atomicSetSubtitleLanguage() {
1866
+ sdkLogger.info("SetSubtitleLanguage: local video element set start while isPLaying=", this._isPlaying);
1867
+
1868
+ this._targetSetSubtitlePlayingState = this._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
1869
+ this._abortSetSubtitleLanguage = false;
1870
+ this._isSetSubtitleByApplication = true;
1871
+
1872
+ let state = SetSubtitleLanguageState.INIT;
1873
+
1874
+ let previousPendingSubtitleLanguage = this._pendingSubtitleLanguage;
1875
+ let initialSubtitleLanguage = this._pendingSubtitleLanguage;
1876
+ let res;
1877
+
1878
+ while (!this._abortSetSubtitleLanguage && state !== SetSubtitleLanguageState.DONE) {
1879
+ try {
1880
+ switch(state) {
1881
+ case SetSubtitleLanguageState.INIT:
1882
+ sdkLogger.info("BEFORE STOP BEFORE SET SUBTITLE");
1883
+ await this._stopSubtitle();
1884
+ sdkLogger.info("AFTER STOP BEFORE SET SUBTITLE");
1885
+ state = SetSubtitleLanguageState.SET;
1886
+ break;
1887
+ case SetSubtitleLanguageState.STOPPED:
1888
+ state = SetSubtitleLanguageState.SET;
1889
+ break;
1890
+ case SetSubtitleLanguageState.SET:
1891
+ initialSubtitleLanguage = this._pendingSubtitleLanguage;
1892
+ previousPendingSubtitleLanguage = this._selectedSubtitleTrack;
1893
+ res = await this._selectTextTrackV3(initialSubtitleLanguage, previousPendingSubtitleLanguage);
1894
+ sdkLogger.info("BEFORE PLAY AFTER SET SUBTITLE 1");
1895
+ state = SetSubtitleLanguageState.DONE;
1896
+ break;
1897
+ }
1898
+ } catch (error) {
1899
+ sdkLogger.info(`Error during set subtitle process: ${error.message}`);
1900
+ state = SetSubtitleLanguageState.DONE;
1901
+ res = Promise.reject(error);
1902
+ }
1903
+ }
1904
+
1905
+ if (!this._abortSetSubtitleLanguage) {
1906
+ sdkLogger.info("TARGET PLAYING STATE", this._targetSetSubtitlePlayingState);
1907
+ sdkLogger.info("TEXT TRACK VISIBILITY", this._textTrackVisibility);
1908
+ if (this._targetSetSubtitlePlayingState === TargetPlayingState.PLAYING_UI) {
1909
+ sdkLogger.info("BEFORE PLAY AFTER SET SUBTITLE 2");
1910
+ this._playSubtitle();
1911
+ }
1912
+ }
1913
+ this._isSetSubtitleByApplication = false;
1914
+ sdkLogger.info("SetSubtitleLanguage: local video element set subtitle end");
1915
+ return res
1916
+ }
1917
+
1918
+ async _atomicSetAudioLanguage() {
1919
+ sdkLogger.info("SetAudioLanguage: local video element set start while isPLaying=", this._isPlaying);
1920
+
1921
+ this._targetSetAudioPlayingState = this._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
1922
+ this._abortSetAudioLanguage = false;
1923
+ this._isSetAudioByApplication = true;
1924
+
1925
+ let state = SetAudioLanguageState.INIT;
1926
+
1927
+ let previousPendingAudioLanguage = this._pendingAudioLanguage;
1928
+ let initialAudioLanguage= this._pendingAudioLanguage;
1929
+ let res;
1930
+
1931
+ while (!this._abortSetAudioLanguage && state !== SetAudioLanguageState.DONE) {
1932
+ try {
1933
+
1934
+ switch(state) {
1935
+ case SetAudioLanguageState.INIT:
1936
+ sdkLogger.info("BEFORE STOP BEFORE SET AUDIO");
1937
+ await this._stopAudio();
1938
+ sdkLogger.info("AFTER STOP BEFORE SET AUDIO");
1939
+ state = SetAudioLanguageState.SET;
1940
+ break;
1941
+ case SetAudioLanguageState.STOPPED:
1942
+ state = SetAudioLanguageState.SET;
1943
+ break;
1944
+ case SetAudioLanguageState.SET:
1945
+ initialAudioLanguage = this._pendingAudioLanguage;
1946
+ previousPendingAudioLanguage = this._selectedAudioTrack;
1947
+ res = await this._selectAudioTrackV3(initialAudioLanguage, previousPendingAudioLanguage);
1948
+ sdkLogger.info("BEFORE PLAY AFTER SET AUDIO 1");
1949
+ state = SetAudioLanguageState.DONE;
1950
+ break;
1951
+ }
1952
+ } catch (error) {
1953
+ sdkLogger.info(`Error during set audio process: ${error.message}`);
1954
+ state = SetAudioLanguageState.DONE;
1955
+ res = Promise.reject(error);
1956
+ }
1957
+ }
1958
+
1959
+ if (!this._abortSetAudioLanguage) {
1960
+ sdkLogger.info("TARGET PLAYING STATE", this._targetSetAudioPlayingState);
1961
+ if (this._targetSetAudioPlayingState === TargetPlayingState.PLAYING_UI) {
1962
+ sdkLogger.info("BEFORE PLAY AFTER SET AUDIO 2");
1963
+ this._play();
1964
+ }
1965
+ }
1966
+
1967
+ this._isSetAudioByApplication = false;
1968
+ sdkLogger.info("SetAudioLanguage: local video element set audio end");
1969
+ return res
1970
+ }
1541
1971
  }
1542
1972
  /**
1543
1973
  *
@@ -1,5 +1,5 @@
1
1
  import * as shaka from "shaka-player";
2
- import { remotePlayer, lifecycle } from "./api";
2
+ import { remotePlayer, lifecycle, getPlatformInfo } from "./api";
3
3
  import { sdkLogger, iso6393to1 } from "./utils";
4
4
 
5
5
  // Define custom error category
@@ -386,6 +386,18 @@ export class SenzaShakaPlayer extends shaka.Player {
386
386
  ]);
387
387
  };
388
388
 
389
+ // For live streams, we can set a default offset from the live edge
390
+ // This allows for synchronizing the start position for both local and remote players
391
+ // Note: For VOD content, negative start times are treated as 0
392
+ const { defaultInitialLiveOffset, minInitialLiveOffset } = getPlatformInfo()?.sessionInfo?.settings?.["ui-streamer"] || {};
393
+ if ((startTime === undefined || startTime === 0) && defaultInitialLiveOffset !== undefined) {
394
+ sdkLogger.debug(`load() was called with startTime=${startTime}, setting startTime to ${-defaultInitialLiveOffset}`);
395
+ startTime = -defaultInitialLiveOffset;
396
+ } else if (startTime <= 0 && minInitialLiveOffset !== undefined && startTime > -minInitialLiveOffset) {
397
+ sdkLogger.debug(`load() was called with startTime=${startTime}, setting startTime to ${-minInitialLiveOffset}`);
398
+ startTime = -minInitialLiveOffset;
399
+ }
400
+
389
401
  if (!this.isInRemotePlayback || remotePlayer.getAssetUri() !== url) {
390
402
  this._audioTracksMap = {};
391
403
  this._videoTracksMap = {};
package/src/utils.js CHANGED
@@ -155,6 +155,24 @@ export const TargetPlayingState = Object.freeze({
155
155
  PLAYING_ABR: "playingAbr"
156
156
  });
157
157
 
158
+ export const SetAudioLanguageState = Object.freeze({
159
+ INIT: "init",
160
+ STOPPED: "stopped",
161
+ SET: "set",
162
+ MULTI_SET: "multiSet",
163
+ WAITING: "waiting",
164
+ DONE: "done"
165
+ });
166
+
167
+ export const SetSubtitleLanguageState = Object.freeze({
168
+ INIT: "init",
169
+ STOPPED: "stopped",
170
+ SET: "set",
171
+ MULTI_SET: "multiSet",
172
+ WAITING: "waiting",
173
+ DONE: "done"
174
+ });
175
+
158
176
  export const iso6393to1 = {
159
177
  "aar": "aa",
160
178
  "abk": "ab",