senza-sdk 4.2.50 → 4.2.51-8029b92.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.50",
3
+ "version": "4.2.51-8029b92.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";
@@ -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,106 @@ class RemotePlayer extends EventTarget {
1538
1700
  this._isSeekingByApplication = false;
1539
1701
  sdkLogger.info("Seeking: local video element seeking end");
1540
1702
  }
1703
+
1704
+ async _atomicSetSubtitleLanguage() {
1705
+ sdkLogger.info("SetSubtitleLanguage: local video element set start while isPLaying=", this._isPlaying);
1706
+
1707
+ this._targetSeekPlayingState = this._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
1708
+ this._abortSetSubtitleLanguage = false;
1709
+ this._isSetSubtitleByApplication = true;
1710
+
1711
+ let state = SetSubtitleLanguageState.INIT;
1712
+
1713
+ let previousPendingSubtitleLanguage = this._pendingSubtitleLanguage;
1714
+ let initialSubtitleLanguage= this._pendingSubtitleLanguage;
1715
+ let res;
1716
+
1717
+ while (!this._abortSetSubtitleLanguage && state !== SetSubtitleLanguageState.DONE) {
1718
+ try {
1719
+ // TODO - Implement the logic for setting audio language
1720
+ switch(state) {
1721
+ case SetSubtitleLanguageState.INIT:
1722
+ state = this._isPlaying ? SetSubtitleLanguageState.STOPPED : SetSubtitleLanguageState.SET;
1723
+ break;
1724
+ case SetSubtitleLanguageState.STOPPED:
1725
+ await lifecycle.moveToForeground();
1726
+ state = SetSubtitleLanguageState.SET;
1727
+ break;
1728
+ case SetSubtitleLanguageState.SET:
1729
+ initialSubtitleLanguage = this._pendingSubtitleLanguage;
1730
+ previousPendingSubtitleLanguage = this._selectedSubtitleTrack;
1731
+ res = await this._selectTextTrackV3(initialSubtitleLanguage, previousPendingSubtitleLanguage);
1732
+ state = SetSubtitleLanguageState.DONE;
1733
+ break;
1734
+ }
1735
+ } catch (error) {
1736
+ sdkLogger.error(`Error during set subtitle process: ${error.message}`);
1737
+ state = SetSubtitleLanguageState.DONE;
1738
+ res = Promise.reject(error);
1739
+ } finally {
1740
+ if (!this._abortSetSubtitleLanguage) {
1741
+ if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_UI) {
1742
+ this._play();
1743
+ } else if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_ABR) {
1744
+ lifecycle._moveToBackground();
1745
+ }
1746
+ }
1747
+ this._isSetSubtitleByApplication = false;
1748
+ sdkLogger.info("SetSubtitleLanguage: local video element set subtitle end");
1749
+ }
1750
+ }
1751
+ return res
1752
+ }
1753
+
1754
+ async _atomicSetAudioLanguage() {
1755
+ sdkLogger.info("SetAudioLanguage: local video element set start while isPLaying=", this._isPlaying);
1756
+
1757
+ this._targetSeekPlayingState = this._isPlaying ? TargetPlayingState.PLAYING_UI : TargetPlayingState.PAUSED;
1758
+ this._abortSetAudioLanguage = false;
1759
+ this._isSetAudioByApplication = true;
1760
+
1761
+ let state = SetAudioLanguageState.INIT;
1762
+
1763
+ let previousPendingAudioLanguage = this._pendingAudioLanguage;
1764
+ let initialAudioLanguage= this._pendingAudioLanguage;
1765
+ let res;
1766
+
1767
+ while (!this._abortSetAudioLanguage && state !== SetAudioLanguageState.DONE) {
1768
+ try {
1769
+ // TODO - Implement the logic for setting audio language
1770
+ switch(state) {
1771
+ case SetAudioLanguageState.INIT:
1772
+ state = this._isPlaying ? SetAudioLanguageState.STOPPED : SetAudioLanguageState.SET;
1773
+ break;
1774
+ case SetAudioLanguageState.STOPPED:
1775
+ await lifecycle.moveToForeground();
1776
+ state = SetAudioLanguageState.SET;
1777
+ break;
1778
+ case SetAudioLanguageState.SET:
1779
+ initialAudioLanguage = this._pendingAudioLanguage;
1780
+ previousPendingAudioLanguage = this._selectedAudioTrack;
1781
+ res = await this._selectAudioTrackV3(initialAudioLanguage, previousPendingAudioLanguage);
1782
+ state = SetAudioLanguageState.DONE;
1783
+ break;
1784
+ }
1785
+ } catch (error) {
1786
+ sdkLogger.error(`Error during set audio process: ${error.message}`);
1787
+ state = SetAudioLanguageState.DONE;
1788
+ res = Promise.reject(error);
1789
+ } finally {
1790
+ if (!this._abortSetAudioLanguage) {
1791
+ if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_UI) {
1792
+ this._play();
1793
+ } else if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_ABR) {
1794
+ lifecycle._moveToBackground();
1795
+ }
1796
+ }
1797
+ this._isSetAudioByApplication = false;
1798
+ sdkLogger.info("SetAudioLanguage: local video element set audio end");
1799
+ }
1800
+ }
1801
+ return res
1802
+ }
1541
1803
  }
1542
1804
  /**
1543
1805
  *
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",