senza-sdk 4.2.49 → 4.2.51-44b32dd.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-44b32dd.0",
4
4
  "main": "./src/api.js",
5
5
  "description": "API for Senza application",
6
6
  "license": "MIT",
@@ -586,7 +586,7 @@ class RemotePlayer extends EventTarget {
586
586
  currentFramePTS: metadata.mediaTime.toString(),
587
587
  ptsSessionId: this._ptsSessionId
588
588
  };
589
- const request = {target: "UI-Streamer", waitForResponse: false, message: JSON.stringify(message)};
589
+ const request = { target: "UI-Streamer", waitForResponse: false, message: JSON.stringify(message) };
590
590
  window.cefQuery({
591
591
  request: JSON.stringify(request),
592
592
  persistent: false
@@ -1189,7 +1189,6 @@ class RemotePlayer extends EventTarget {
1189
1189
  const prevSelectedAudioTrack = this._selectedAudioTrack;
1190
1190
  for (const track of this.getAudioTracks()) {
1191
1191
  if (track.id === audioTrackId) {
1192
- this._selectedAudioTrack = audioTrackId;
1193
1192
  found = true;
1194
1193
  break;
1195
1194
  }
@@ -1198,19 +1197,33 @@ class RemotePlayer extends EventTarget {
1198
1197
  sdkLogger.warn(`Invalid audioTrackId ${audioTrackId}`);
1199
1198
  return Promise.resolve();
1200
1199
  }
1201
- if (this._remotePlayerApiVersion < 2) {
1202
- return Promise.resolve(); // Resolve immediately for older versions
1203
- }
1204
- if (prevSelectedAudioTrack === this._selectedAudioTrack) {
1200
+ if (this._selectedAudioTrack === audioTrackId) {
1205
1201
  return Promise.resolve(); // Audio language already selected
1206
1202
  }
1207
1203
 
1208
- return this._selectAudioTrack(audioTrackId, prevSelectedAudioTrack);
1204
+ switch (this._remotePlayerApiVersion) {
1205
+ case 0:
1206
+ case 1:
1207
+ this._selectedAudioTrack = audioTrackId;
1208
+ return Promise.resolve(); // Resolve immediately for older versions
1209
+ case 2:
1210
+ return this._selectAudioTrackV2(audioTrackId, prevSelectedAudioTrack);
1211
+ default:
1212
+ return this._selectAudioTrackV3(audioTrackId, prevSelectedAudioTrack);
1213
+ }
1209
1214
  }
1210
1215
 
1211
- async _selectAudioTrack(audioTrackId, prevSelectedAudioTrack) {
1216
+ /**
1217
+ * Handles the asynchronous selection of an audio track.
1218
+ * If the player is playing, it pauses before changing the track and resumes playback if necessary.
1219
+ *
1220
+ * @param {string} audioTrackId - The ID of the audio track to select.
1221
+ * @param {string} prevSelectedAudioTrack - The previously selected audio track ID.
1222
+ * @returns {Promise<void>} Resolves when the operation is complete.
1223
+ * */
1224
+ async _selectAudioTrackV2(audioTrackId, prevSelectedAudioTrack) {
1212
1225
  const prevIsPlaying = this._isPlaying;
1213
- sdkLogger.log(`remotePlayer _selectAudioTrack: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying}`);
1226
+ sdkLogger.log(`remotePlayer _selectAudioTrackV2: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying}`);
1214
1227
  try {
1215
1228
  if (this._isPlaying) await this.pause();
1216
1229
  let position = this.currentTime;
@@ -1218,9 +1231,9 @@ class RemotePlayer extends EventTarget {
1218
1231
  position = this._videoElement.currentTime;
1219
1232
  }
1220
1233
  await this._load(this._loadedUrl, position, audioTrackId, undefined, false);
1234
+ this._selectedAudioTrack = audioTrackId;
1221
1235
  } catch (e) {
1222
- // Do NOT reject - just log and revert selection
1223
- this._selectedAudioTrack = prevSelectedAudioTrack;
1236
+ // Do NOT reject - just log
1224
1237
  sdkLogger.warn(`Failed to select audio track ${audioTrackId}: ${e.message}`);
1225
1238
  return;
1226
1239
  }
@@ -1237,6 +1250,63 @@ class RemotePlayer extends EventTarget {
1237
1250
  }
1238
1251
  }
1239
1252
 
1253
+ /**
1254
+ * Handles the asynchronous selection of an audio track.
1255
+ * If the player is playing, it stops subtitle streamType before changing the track and resumes subtitle streamType playback if necessary.
1256
+ * Available only from v3 and on
1257
+ *
1258
+ * @param {string} audioTrackId - The ID of the audio track to select.
1259
+ * @param {string} prevSelectedAudioTrack - The previously selected audio track ID.
1260
+ * @returns {Promise<void>} Resolves when the operation is complete.
1261
+ * */
1262
+ async _selectAudioTrackV3(audioTrackId, prevSelectedAudioTrack) {
1263
+ sdkLogger.log(`remotePlayer _selectAudioTrackV3: prevAudioTrack=${prevSelectedAudioTrack} audioTrackId=${audioTrackId} isPlaying=${this._isPlaying}`);
1264
+ if (window.cefQuery) {
1265
+ const FCID = getFCID();
1266
+ const logger = sdkLogger.withFields({ FCID });
1267
+ logger.log("remotePlayer _selectAudioTrackV3: sending setAudioLanguage action");
1268
+ const message = {
1269
+ type: "remotePlayer.setAudioLanguage",
1270
+ class: "remotePlayer",
1271
+ action: "setAudioLanguage",
1272
+ fcid: FCID,
1273
+ language: audioTrackId,
1274
+ };
1275
+ const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
1276
+ return new Promise((resolve, reject) => {
1277
+ let timerId = 0;
1278
+ const timeBeforeSendingRequest = Date.now();
1279
+ const queryId = window.cefQuery({
1280
+ request: JSON.stringify(request),
1281
+ persistent: false,
1282
+ onSuccess: () => {
1283
+ this._selectedAudioTrack = audioTrackId;
1284
+ const duration = Date.now() - timeBeforeSendingRequest;
1285
+ logger.withFields({ duration }).log(`setAudioLanguage completed successfully after ${duration} ms`);
1286
+ timerId = clearTimer(timerId);
1287
+ resolve();
1288
+ },
1289
+ onFailure: (code, msg) => {
1290
+ const duration = Date.now() - timeBeforeSendingRequest;
1291
+ logger.withFields({ duration }).log(`setAudioLanguage failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
1292
+ timerId = clearTimer(timerId);
1293
+ reject(new RemotePlayerError(code, msg));
1294
+ }
1295
+ });
1296
+ logger.log(`window.cefQuery for setAudioLanguage returned query id ${queryId}`);
1297
+ const timeout = this._remotePlayerConfirmationTimeout + 1000;
1298
+ timerId = setTimeout(() => {
1299
+ logger.log(`setAudioLanguage reached timeout of ${timeout} ms, canceling query id ${queryId}`);
1300
+ window.cefQueryCancel(queryId);
1301
+ reject(new RemotePlayerError(6000, `setAudioLanguage reached timeout of ${timeout} ms`));
1302
+ }, timeout, queryId);
1303
+ });
1304
+ }
1305
+
1306
+ sdkLogger.error("remotePlayer _selectAudioTrackV3: window.cefQuery is undefined");
1307
+ return Promise.resolve(undefined);
1308
+ }
1309
+
1240
1310
  /** Select a specific text (subtitle) track.
1241
1311
  * Track id should come from a call to getTextTracks.
1242
1312
  * If no tracks exist - this is a no-op.
@@ -1256,7 +1326,6 @@ class RemotePlayer extends EventTarget {
1256
1326
  const prevSelectedTextTrack = this._selectedSubtitlesTrack;
1257
1327
  for (const track of this.getTextTracks()) {
1258
1328
  if (track.id === textTrackId) {
1259
- this._selectedSubtitlesTrack = textTrackId;
1260
1329
  found = true;
1261
1330
  break;
1262
1331
  }
@@ -1265,14 +1334,20 @@ class RemotePlayer extends EventTarget {
1265
1334
  sdkLogger.warn(`Invalid textTrackId ${textTrackId}`);
1266
1335
  return Promise.resolve();
1267
1336
  }
1268
- if (this._remotePlayerApiVersion < 2) {
1269
- return Promise.resolve(); // Resolve immediately for older versions
1270
- }
1271
- if (prevSelectedTextTrack === this._selectedSubtitlesTrack) {
1337
+ if (this._selectedSubtitlesTrack === textTrackId) {
1272
1338
  return Promise.resolve(); // Subtitle language already selected
1273
1339
  }
1274
1340
 
1275
- return this._selectTextTrack(textTrackId, prevSelectedTextTrack);
1341
+ switch (this._remotePlayerApiVersion) {
1342
+ case 0:
1343
+ case 1:
1344
+ this._selectedSubtitlesTrack = textTrackId;
1345
+ return Promise.resolve(); // Resolve immediately for older versions
1346
+ case 2:
1347
+ return this._selectTextTrackV2(textTrackId, prevSelectedTextTrack);
1348
+ default:
1349
+ return this._selectTextTrackV3(textTrackId, prevSelectedTextTrack);
1350
+ }
1276
1351
  }
1277
1352
 
1278
1353
  /**
@@ -1283,9 +1358,9 @@ class RemotePlayer extends EventTarget {
1283
1358
  * @param {string} prevSelectedTextTrack - The previously selected text track ID.
1284
1359
  * @returns {Promise<void>} Resolves when the operation is complete.
1285
1360
  * */
1286
- async _selectTextTrack(textTrackId, prevSelectedTextTrack) {
1361
+ async _selectTextTrackV2(textTrackId, prevSelectedTextTrack) {
1287
1362
  const prevIsPlaying = this._isPlaying;
1288
- sdkLogger.log(`remotePlayer _selectTextTrack: prevTextTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying}`);
1363
+ sdkLogger.log(`remotePlayer _selectTextTrackV2: prevTextTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying}`);
1289
1364
  try {
1290
1365
  if (this._isPlaying) await this.pause();
1291
1366
  let position = this.currentTime;
@@ -1293,9 +1368,9 @@ class RemotePlayer extends EventTarget {
1293
1368
  position = this._videoElement.currentTime;
1294
1369
  }
1295
1370
  await this._load(this._loadedUrl, position, undefined, textTrackId, false);
1371
+ this._selectedSubtitlesTrack = textTrackId;
1296
1372
  } catch (e) {
1297
- // Do NOT reject - just log and revert selection
1298
- this._selectedSubtitlesTrack = prevSelectedTextTrack;
1373
+ // Do NOT reject - just log
1299
1374
  sdkLogger.warn(`Failed to select text track ${textTrackId}: ${e.message}`);
1300
1375
  return;
1301
1376
  }
@@ -1312,6 +1387,65 @@ class RemotePlayer extends EventTarget {
1312
1387
  }
1313
1388
  }
1314
1389
 
1390
+ /**
1391
+ * Handles the asynchronous selection of a text track.
1392
+ * If the player is playing, it stops subtitle streamType before changing the track and resumes subtitle streamType playback if necessary.
1393
+ * Available only from v3 and on
1394
+ *
1395
+ * @param {string} textTrackId - The ID of the text track to select.
1396
+ * @param {string} prevSelectedTextTrack - The previously selected text track ID.
1397
+ * @returns {Promise<void>} Resolves when the operation is complete.
1398
+ * */
1399
+ async _selectTextTrackV3(textTrackId, prevSelectedTextTrack) {
1400
+ sdkLogger.log(`remotePlayer _selectTextTrackV3: prevAudioTrack=${prevSelectedTextTrack} textTrackId=${textTrackId} isPlaying=${this._isPlaying}`);
1401
+ if (window.cefQuery) {
1402
+ const FCID = getFCID();
1403
+ const logger = sdkLogger.withFields({ FCID });
1404
+ logger.log("remotePlayer _selectTextTrackV3: sending setSubtitleLanguage action");
1405
+ const message = {
1406
+ type: "remotePlayer.setSubtitleLanguage",
1407
+ class: "remotePlayer",
1408
+ action: "setSubtitleLanguage",
1409
+ fcid: FCID,
1410
+ language: textTrackId,
1411
+ };
1412
+ const request = { target: "TC", waitForResponse: true, message: JSON.stringify(message) };
1413
+ return new Promise((resolve, reject) => {
1414
+ let timerId = 0;
1415
+ const timeBeforeSendingRequest = Date.now();
1416
+ const queryId = window.cefQuery({
1417
+ request: JSON.stringify(request),
1418
+ persistent: false,
1419
+ onSuccess: () => {
1420
+ this._selectedSubtitlesTrack = textTrackId;
1421
+ const duration = Date.now() - timeBeforeSendingRequest;
1422
+ logger.withFields({ duration }).log(`setSubtitleLanguage completed successfully after ${duration} ms`);
1423
+ timerId = clearTimer(timerId);
1424
+ resolve();
1425
+ },
1426
+ onFailure: (code, msg) => {
1427
+ const duration = Date.now() - timeBeforeSendingRequest;
1428
+ logger.withFields({ duration }).log(`setSubtitleLanguage failed after ${duration} ms. Error code: ${code}, error message: ${msg}`);
1429
+ timerId = clearTimer(timerId);
1430
+ reject(new RemotePlayerError(code, msg));
1431
+ }
1432
+ });
1433
+ logger.log(`window.cefQuery for setSubtitleLanguage returned query id ${queryId}`);
1434
+ const timeout = this._remotePlayerConfirmationTimeout + 1000;
1435
+ timerId = setTimeout(() => {
1436
+ logger.log(`setSubtitleLanguage reached timeout of ${timeout} ms, canceling query id ${queryId}`);
1437
+ window.cefQueryCancel(queryId);
1438
+ reject(new RemotePlayerError(6000, `setSubtitleLanguage reached timeout of ${timeout} ms`));
1439
+ }, timeout, queryId);
1440
+ });
1441
+ }
1442
+ sdkLogger.error("remotePlayer _selectAudioTrackV3: window.cefQuery is undefined");
1443
+ return Promise.resolve(undefined);
1444
+
1445
+
1446
+ }
1447
+
1448
+
1315
1449
  /**
1316
1450
  * Enable or disable the subtitles.
1317
1451
  * If the player is in an unloaded state, the request will be applied next time content is played.
@@ -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 = {};