senza-sdk 4.4.1-4ca75a1.0 → 4.4.1

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.
@@ -0,0 +1,82 @@
1
+ /*
2
+ @license
3
+ Copyright 2006 The Closure Library Authors
4
+ SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ /*
8
+ @license
9
+ Copyright 2008 The Closure Library Authors
10
+ SPDX-License-Identifier: Apache-2.0
11
+ */
12
+
13
+ /*
14
+ @license
15
+ Copyright 2013 Ali Al Dallal
16
+
17
+ Licensed under the MIT license.
18
+
19
+ Permission is hereby granted, free of charge, to any person obtaining a copy
20
+ of this software and associated documentation files (the "Software"), to deal
21
+ in the Software without restriction, including without limitation the rights
22
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23
+ copies of the Software, and to permit persons to whom the Software is
24
+ furnished to do so, subject to the following conditions:
25
+
26
+ The above copyright notice and this permission notice shall be included in
27
+ all copies or substantial portions of the Software.
28
+
29
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35
+ SOFTWARE.
36
+ */
37
+
38
+ /*
39
+ @license
40
+ MSS Transmuxer
41
+ Copyright 2015 Dash Industry Forum
42
+ SPDX-License-Identifier: BSD-3-Clause
43
+ */
44
+
45
+ /*
46
+ @license
47
+ Shaka Player
48
+ Copyright 2016 Google LLC
49
+ SPDX-License-Identifier: Apache-2.0
50
+ */
51
+
52
+ /*
53
+ @license
54
+ Shaka Player
55
+ Copyright 2022 Google LLC
56
+ SPDX-License-Identifier: Apache-2.0
57
+ */
58
+
59
+ /*
60
+ @license
61
+ Shaka Player
62
+ Copyright 2023 Google LLC
63
+ SPDX-License-Identifier: Apache-2.0
64
+ */
65
+
66
+ /*
67
+ @license
68
+ Shaka Player
69
+ Copyright 2025 Google LLC
70
+ SPDX-License-Identifier: Apache-2.0
71
+ */
72
+
73
+ /*
74
+ @license
75
+ tXml
76
+ Copyright 2015 Tobias Nickel
77
+ SPDX-License-Identifier: MIT
78
+ */
79
+
80
+ //! moment.js
81
+
82
+ //! moment.js locale configuration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "senza-sdk",
3
- "version": "4.4.1-4ca75a1.0",
3
+ "version": "4.4.1",
4
4
  "main": "./src/api.js",
5
5
  "description": "API for Senza application",
6
6
  "license": "MIT",
@@ -53,15 +53,15 @@
53
53
  "statements": 93.46
54
54
  },
55
55
  "src/interface": {
56
- "branches": 68.42,
57
- "functions": 39.63,
58
- "lines": 64.94,
59
- "statements": 65.32
56
+ "branches": 63.9,
57
+ "functions": 39.28,
58
+ "lines": 64.61,
59
+ "statements": 65
60
60
  }
61
61
  }
62
62
  },
63
63
  "dependencies": {
64
- "shaka-player": "^4.12.5",
64
+ "shaka-player": "4.15.8",
65
65
  "moment": "^2.30.1"
66
66
  }
67
67
  }
package/src/api.js CHANGED
@@ -308,6 +308,9 @@ let ShakaPlayerImplementation;
308
308
  let shakaImplementation;
309
309
 
310
310
  if (!isRunningE2E()) {
311
+ // ------------------------------------------------------------------------------------------------
312
+ // -- Do NOT change this log, as ui-streamer is checking this string to detect abnormal behavior --
313
+ // ------------------------------------------------------------------------------------------------
311
314
  sdkLogger.warn("Senza SDK is not running in E2E environment. Some features may not work as expected.");
312
315
 
313
316
  lifecycle = new Lifecycle();
@@ -559,7 +559,8 @@ class Lifecycle extends LifecycleInterface {
559
559
  action: "play",
560
560
  fcid: FCID,
561
561
  audioLanguage,
562
- subtitlesLanguage
562
+ subtitlesLanguage,
563
+ autoTune: remotePlayer._autoTune
563
564
  };
564
565
  if (this._remotePlayerApiVersion >= 2) {
565
566
  message.type = "remotePlayer.play";
@@ -579,7 +580,7 @@ class Lifecycle extends LifecycleInterface {
579
580
  }
580
581
  request = message;
581
582
  }
582
- logger.log(`lifecycle moveToBackground: sending play action audioLanguage=${message.audioLanguage} subtitlesLanguage=${message.subtitlesLanguage} textTrackVisibility=${remotePlayer.textTrackVisibility}`);
583
+ logger.log(`lifecycle moveToBackground: sending play action audioLanguage=${message.audioLanguage} subtitlesLanguage=${message.subtitlesLanguage} textTrackVisibility=${remotePlayer.textTrackVisibility} autoTune=${remotePlayer._autoTune}`);
583
584
  let timerId = 0;
584
585
  const timeBeforeSendingRequest = Date.now();
585
586
  const queryId = window.cefQuery({
@@ -183,6 +183,7 @@ class RemotePlayer extends RemotePlayerInterface {
183
183
  }
184
184
  }
185
185
  this._loadMode = playerState?.isLoaded ? this.LoadMode.LOADED : this.LoadMode.NOT_LOADED;
186
+ this._autoTune = false;
186
187
 
187
188
  this._isPlaying = window?.sessionStorage?.getItem("senzaSdk_isPlaying") === "true";
188
189
 
@@ -196,6 +197,8 @@ class RemotePlayer extends RemotePlayerInterface {
196
197
  this._remotePlayerConfirmationTimeout = uiStreamerSettings?.remotePlayerConfirmationTimeout ?? DEFAULT_REMOTE_PLAYER_CONFIRMATION_TIMEOUT;
197
198
  this._remotePlayerApiVersion = uiStreamerSettings?.remotePlayerApiVersion || 1;
198
199
  this._multiSeekDelay = uiStreamerSettings?.multiSeekDelay || MULTI_SEEK_DELAY_MSEC;
200
+ this._uiAvSyncIntervalMs = uiStreamerSettings?.uiAvSyncIntervalMs;
201
+ this._abrAvSyncIntervalMs = uiStreamerSettings?.abrAvSyncIntervalMs;
199
202
  this._deviceAppVersion = sessionObj?.manifest?.["device-app"] || "";
200
203
 
201
204
  sdkLogger.info(`remotePLayer isPlaying=${this._isPlaying}`);
@@ -208,6 +211,7 @@ class RemotePlayer extends RemotePlayerInterface {
208
211
  }
209
212
  this._availabilityStartTime = playbackMetadata.availabilityStartTime;
210
213
  this._updateTracks(playbackMetadata);
214
+
211
215
  }
212
216
 
213
217
  /**
@@ -785,6 +789,12 @@ class RemotePlayer extends RemotePlayerInterface {
785
789
  playbackPosition,
786
790
  fcid: FCID
787
791
  };
792
+ if (this._uiAvSyncIntervalMs !== undefined) {
793
+ message.uiAvSyncIntervalMs = this._uiAvSyncIntervalMs;
794
+ }
795
+ if (this._abrAvSyncIntervalMs !== undefined) {
796
+ message.abrAvSyncIntervalMs = this._abrAvSyncIntervalMs;
797
+ }
788
798
  if (this._remotePlayerApiVersion >= 2) {
789
799
  message.type = "remotePlayer.load";
790
800
  message.class = "remotePlayer";
@@ -924,10 +934,11 @@ class RemotePlayer extends RemotePlayerInterface {
924
934
  * remotePlayer.play();
925
935
  * lifecycle.moveToBackground();
926
936
  *
937
+ * @param {boolean} [autoTune=false] Should be true when the play was triggered automatically by the app and not by a user action.
927
938
  * @returns {Promise}
928
939
  * @throws {RemotePlayerError} error object contains code & msg
929
940
  */
930
- play() {
941
+ play(autoTune = false) {
931
942
  if (!this._isInitialized) {
932
943
  throw new RemotePlayerError(6500, "Cannot call play() if remote player is not initialized");
933
944
  }
@@ -938,6 +949,7 @@ class RemotePlayer extends RemotePlayerInterface {
938
949
  }
939
950
 
940
951
  this._changePlayMode(true);
952
+ this._autoTune = autoTune;
941
953
 
942
954
  if (this._isSetAudioInProgress) {
943
955
  sdkLogger.info("application requesting play during setAudioLanguage");
@@ -1091,24 +1103,30 @@ class RemotePlayer extends RemotePlayerInterface {
1091
1103
  * @throws {RemotePlayerError} If the player is not initialized or not loaded.
1092
1104
  **/
1093
1105
  selectAudioTrack(audioTrackId) {
1106
+ return this._selectAudioTrack(audioTrackId, false);
1107
+ }
1108
+
1109
+ _selectAudioTrack(audioTrackId, force = false) {
1094
1110
  if (!this._isInitialized) {
1095
1111
  throw new RemotePlayerError(6500, "Cannot call selectAudioTrack() if remote player is not initialized");
1096
1112
  }
1097
1113
  if (this._loadMode !== this.LoadMode.LOADED) {
1098
1114
  throw new RemotePlayerError(6001, "Cannot call selectAudioTrack() if remote player is not loaded");
1099
1115
  }
1100
- let found = false;
1101
- const prevSelectedAudioTrack = this._selectedAudioTrack;
1102
- for (const track of this.getAudioTracks()) {
1103
- if (track.id === audioTrackId) {
1104
- found = true;
1105
- break;
1116
+ if (!force) {
1117
+ let found = false;
1118
+ for (const track of this.getAudioTracks()) {
1119
+ if (track.id === audioTrackId) {
1120
+ found = true;
1121
+ break;
1122
+ }
1123
+ }
1124
+ if (!found) {
1125
+ sdkLogger.warn(`Invalid audioTrackId ${audioTrackId}`);
1126
+ return Promise.resolve();
1106
1127
  }
1107
1128
  }
1108
- if (!found) {
1109
- sdkLogger.warn(`Invalid audioTrackId ${audioTrackId}`);
1110
- return Promise.resolve();
1111
- }
1129
+ const prevSelectedAudioTrack = this._selectedAudioTrack;
1112
1130
  if (this._selectedAudioTrack === audioTrackId) {
1113
1131
  return Promise.resolve(); // Audio language already selected
1114
1132
  }
@@ -1125,6 +1143,12 @@ class RemotePlayer extends RemotePlayerInterface {
1125
1143
  }
1126
1144
  }
1127
1145
 
1146
+ async selectAudioLanguage(language, role="", accessibilityPurposeCode=this.AccessibilityPurposeCode.NONE) {
1147
+ const trackId = this._generateSenzaTrackId(language, role, accessibilityPurposeCode);
1148
+ return this._selectAudioTrack(trackId, true);
1149
+ }
1150
+
1151
+
1128
1152
  /**
1129
1153
  * Handles the asynchronous selection of an audio track.
1130
1154
  * If the player is playing, it pauses before changing the track and resumes playback if necessary.
@@ -1218,6 +1242,11 @@ class RemotePlayer extends RemotePlayerInterface {
1218
1242
  return Promise.resolve(undefined);
1219
1243
  }
1220
1244
 
1245
+ async selectTextLanguage(language, role="", accessibilityPurposeCode=this.AccessibilityPurposeCode.NONE) {
1246
+ const trackId = this._generateSenzaTrackId(language, role, accessibilityPurposeCode);
1247
+ return this._selectTextTrack(trackId, true);
1248
+ }
1249
+
1221
1250
  /** Select a specific text (subtitle) track.
1222
1251
  * Track id should come from a call to getTextTracks.
1223
1252
  * If no tracks exist - this is a no-op.
@@ -1227,24 +1256,31 @@ class RemotePlayer extends RemotePlayerInterface {
1227
1256
  * @throws {RemotePlayerError} If the player is not initialized or not loaded.
1228
1257
  * */
1229
1258
  selectTextTrack(textTrackId) {
1259
+ return this._selectTextTrack(textTrackId, false);
1260
+ }
1261
+
1262
+ _selectTextTrack(textTrackId, force = false) {
1230
1263
  if (!this._isInitialized) {
1231
1264
  throw new RemotePlayerError(6500, "Cannot call selectTextTrack() if remote player is not initialized");
1232
1265
  }
1233
1266
  if (this._loadMode !== this.LoadMode.LOADED) {
1234
1267
  throw new RemotePlayerError(6001, "Cannot call selectTextTrack() if remote player is not loaded");
1235
1268
  }
1236
- let found = false;
1237
- const prevSelectedTextTrack = this._selectedSubtitlesTrack;
1238
- for (const track of this.getTextTracks()) {
1239
- if (track.id === textTrackId) {
1240
- found = true;
1241
- break;
1269
+ if (!force) {
1270
+ let found = false;
1271
+ for (const track of this.getTextTracks()) {
1272
+ if (track.id === textTrackId) {
1273
+ found = true;
1274
+ break;
1275
+ }
1276
+ }
1277
+ if (!found) {
1278
+ sdkLogger.warn(`Invalid textTrackId ${textTrackId}`);
1279
+ return Promise.resolve();
1242
1280
  }
1243
1281
  }
1244
- if (!found) {
1245
- sdkLogger.warn(`Invalid textTrackId ${textTrackId}`);
1246
- return Promise.resolve();
1247
- }
1282
+
1283
+ const prevSelectedTextTrack = this._selectedSubtitlesTrack;
1248
1284
  if (this._selectedSubtitlesTrack === textTrackId) {
1249
1285
  return Promise.resolve(); // Subtitle language already selected
1250
1286
  }
@@ -1640,11 +1676,10 @@ class RemotePlayer extends RemotePlayerInterface {
1640
1676
  // In case where we aborted (new load or unload called), we don't want to resume playback.
1641
1677
  if (!this._abortSeeking) {
1642
1678
  if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_UI) {
1643
- if (!this._isAudioSyncEnabled()) {
1644
- return Promise.resolve(true);
1679
+ // When in foreground back from ABR, we are considered playing. But, if AIU is not enabled, we shouldn't resume it.
1680
+ if (this._isAudioSyncEnabled()) {
1681
+ this._play(StreamType.AUDIO);
1645
1682
  }
1646
- // resume audio play only if _isAudioSyncEnabled
1647
- this._play(StreamType.AUDIO);
1648
1683
  } else if (this._targetSeekPlayingState === TargetPlayingState.PLAYING_ABR) {
1649
1684
  lifecycle._moveToBackground();
1650
1685
  }
@@ -1788,6 +1823,12 @@ class RemotePlayer extends RemotePlayerInterface {
1788
1823
  return setLanguageError ? Promise.reject(setLanguageError) : Promise.resolve();
1789
1824
  }
1790
1825
 
1826
+ _generateSenzaTrackId(language, role, accessibilityPurpose) {
1827
+ const l = language || "";
1828
+ const r = role || "";
1829
+ const ap = accessibilityPurpose || "";
1830
+ return `${l}:${r}:${ap}`;
1831
+ }
1791
1832
  _setScreenBlackout(blackoutTime) {
1792
1833
  if (window.cefQuery) {
1793
1834
  const FCID = getFCID();
@@ -1,10 +1,9 @@
1
1
  import { SenzaShakaPlayer as SenzaShakaInterface, shaka } from "../interface/senzaShakaPlayer";
2
-
2
+ import * as shakaDebug from "shaka-player/dist/shaka-player.compiled.debug.js";
3
3
  import { remotePlayer, lifecycle, getPlatformInfo } from "./api";
4
- import { sdkLogger, iso6393to1 } from "./utils";
4
+ import { sdkLogger } from "./utils";
5
5
  import moment from "moment";
6
6
 
7
-
8
7
  // Define custom error category
9
8
  shaka.util.Error.Category.SENZA_PLAYER_ERROR = 50;
10
9
  shaka.util.Error.Code.SENZA_PLAYER_ERROR = 10500;
@@ -32,7 +31,6 @@ class SenzaError extends shaka.util.Error {
32
31
  }
33
32
  }
34
33
 
35
-
36
34
  export class SenzaShakaPlayer extends SenzaShakaInterface {
37
35
  /** @private {SenzaShakaPlayer|null} Previous instance of the player */
38
36
  static _prevInstance = null;
@@ -134,7 +132,7 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
134
132
  this.videoElement.play();
135
133
  }
136
134
  },
137
- "seeked" : () => {
135
+ "seeked": () => {
138
136
  // In case of background seek - we need to pause the local player after "seeked" until remote player is "playing" to avoid difference between the players.
139
137
  if (this.isInRemotePlayback) {
140
138
  this._pauseForDelayedSeek = true;
@@ -164,57 +162,57 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
164
162
  },
165
163
  "license-request": async (event) => {
166
164
  sdkLogger.log("remotePlayer", "license-request", "Got license-request event from remote player");
165
+ let responseBody = "Unknown Error";
166
+ let status = 500;
167
167
 
168
- // Extract license body from event
169
- const requestBuffer = event?.detail?.licenseRequest;
170
- const requestBufferStr = String.fromCharCode.apply(null, new Uint8Array(requestBuffer));
171
- const decodedLicenseRequest = window.atob(requestBufferStr); // Decode from base64
172
- const licenseRequestBytes = Uint8Array.from(decodedLicenseRequest, (l) => l.charCodeAt(0));
173
-
174
- const request = {
175
- body: licenseRequestBytes.buffer,
176
- uris: [this.getConfiguration().drm.servers["com.widevine.alpha"]], // TODO: safe gaurd against undefined and other server types
177
- method: "POST",
178
- originatesFromRemotePlayer: true
179
- };
168
+ try {
180
169
 
181
- const response = await this.getNetworkingEngine().request(shaka.net.NetworkingEngine.RequestType.LICENSE, request).promise;
170
+ if (!this?.getConfiguration()?.drm?.servers?.["com.widevine.alpha"]) {
171
+ status = 400;
172
+ responseBody = "Missing Widevine license server";
173
+ sdkLogger.warn("remotePlayer", "license-request", "missing Widevine license server");
174
+ return; // the finally will send the response
175
+ }
176
+ // Extract license body from event
177
+ const requestBuffer = event?.detail?.licenseRequest;
178
+ if (!requestBuffer) {
179
+ status = 400;
180
+ responseBody = "Missing license request buffer";
181
+ sdkLogger.warn("remotePlayer", "license-request", "missing license request buffer");
182
+ return; // the finally will send the response
183
+ }
184
+ const requestBufferStr = String.fromCharCode.apply(null, new Uint8Array(requestBuffer));
185
+ const decodedLicenseRequest = window.atob(requestBufferStr); // Decode from base64
186
+ const licenseRequestBytes = Uint8Array.from(decodedLicenseRequest, (l) => l.charCodeAt(0));
182
187
 
183
- let responseBody = response.data;
184
- if (response.status < 200 || response.status >= 300) {
185
- responseBody = response.data ?? String.fromCharCode(new Uint8Array(response.data));
186
- sdkLogger.error("remotePlayer", "license-request", "failed to to get response from widevine:", response.status, responseBody);
187
- }
188
- // Write response to remote player
189
- sdkLogger.log("remotePlayer", "license-request", "Writing response to remote player", response.status);
190
- event.writeLicenseResponse(response.status, responseBody);
188
+ const request = {
189
+ body: licenseRequestBytes.buffer,
190
+ uris: [this.getConfiguration().drm.servers["com.widevine.alpha"]],
191
+ method: "POST",
192
+ originatesFromRemotePlayer: true
193
+ };
191
194
 
192
- },
193
- "tracksupdate": () => {
194
- this._audioTracksMap = {};
195
- this._textTracksMap = {};
196
- // create a map between language code and track id to be used for selection. If the language appears more than once the last one will be taken.
197
- const audioTracks = remotePlayer.getAudioTracks();
198
- for (const track of audioTracks) {
199
- let lang = track.lang;
200
-
201
- if (lang.length === 3) {
202
- lang = iso6393to1[lang] || lang;
203
- }
204
- this._audioTracksMap[lang] = track.id;
205
- }
195
+ const response = await this.getNetworkingEngine().request(shaka.net.NetworkingEngine.RequestType.LICENSE, request).promise;
206
196
 
207
- const textTracks = remotePlayer.getTextTracks();
208
- for (const track of textTracks) {
209
- if (!track.autoTranslate) {
210
- let lang = track.lang;
197
+ responseBody = response.data;
198
+ status = response.status;
211
199
 
212
- if (lang.length === 3) {
213
- lang = iso6393to1[lang] || lang;
214
- }
215
- this._textTracksMap[lang] = track.id;
200
+ if (status < 200 || status >= 300) {
201
+ responseBody = response.data ?? String.fromCharCode(new Uint8Array(response.data));
202
+ sdkLogger.error("remotePlayer", "license-request", "failed to to get response from widevine:", status, responseBody);
216
203
  }
204
+ } catch (error) {
205
+ sdkLogger.error("remotePlayer", "license-request", "License request failed:", error);
206
+ // For network errors, the error.data array contains [uri, status, responseText, ...]
207
+ status = error?.data?.[1] ?? 500;
208
+ responseBody = error?.data?.[2] ?? `Error response: ${error.message || error.toString()}`;
209
+ sdkLogger.error("remotePlayer", "license-request", "failed to get response from widevine:", status, responseBody);
210
+ } finally {
211
+ // Write response to remote player
212
+ sdkLogger.log("remotePlayer", "license-request", "Writing response to remote player", status);
213
+ event.writeLicenseResponse(status, responseBody);
217
214
  }
215
+
218
216
  },
219
217
  "playing": async () => {
220
218
  sdkLogger.info("remotePlayer playing event received");
@@ -453,8 +451,6 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
453
451
  }
454
452
 
455
453
  await super.detach(keepAdManager);
456
- this._audioTracksMap = {};
457
- this._textTracksMap = {};
458
454
  this._removeVideoElementEventListeners();
459
455
  try {
460
456
  if (remotePlayer.getAssetUri() !== "") {
@@ -484,30 +480,65 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
484
480
  }
485
481
 
486
482
  getTextLanguages() {
487
- return Object.keys(this._textTracksMap);
483
+ const tracks = this.remotePlayer.getTextTracks();
484
+ return [...new Set(tracks.map(item => item.lang))];
488
485
  }
489
486
 
490
487
  getAudioLanguages() {
491
- return Object.keys(this._audioTracksMap);
488
+ const tracks = this.remotePlayer.getAudioTracks();
489
+ return [...new Set(tracks.map(item => item.lang))];
490
+ }
491
+
492
+
493
+ selectVariantTrack(track, clearBuffer = false, safeMargin = 0) {
494
+ const audioLang = track.language;
495
+ const audioRole = Array.isArray(track.audioRoles) && track.audioRoles.length > 0
496
+ ? track.audioRoles[0]
497
+ : null;
498
+ const audioId = track.audioId;
499
+ sdkLogger.log(`selectVariantTrack() called: audio id ${audioId} language ${audioLang}, role ${audioRole}, accessibilityPurpose: ${track.accessibilityPurpose}`);
500
+
501
+ const apCode = this._getAccessibilityCodeFromPurpose(track.accessibilityPurpose);
502
+ remotePlayer.selectAudioLanguage(audioLang, audioRole, apCode);
503
+
504
+ super.selectVariantTrack(track, clearBuffer, safeMargin);
505
+
506
+ }
507
+
508
+ selectAudioTrack(audioTrack, safeMargin = 0) {
509
+ const audioId = audioTrack.id;
510
+ const audioLang = audioTrack.language;
511
+ const role = Array.isArray(audioTrack.roles) && audioTrack.roles.length > 0
512
+ ? audioTrack.roles[0]
513
+ : null;
514
+ sdkLogger.log(`selectAudioTrack() called: audio id ${audioId} language ${audioLang}, role ${role}, accessibilityPurpose: ${audioTrack.accessibilityPurpose}`);
515
+ const apCode = this._getAccessibilityCodeFromPurpose(audioTrack.accessibilityPurpose);
516
+ remotePlayer.selectAudioLanguage(audioLang, role, apCode);
517
+
518
+ super.selectAudioTrack(audioTrack, safeMargin);
492
519
  }
493
520
 
494
521
  selectAudioLanguage(language, role) {
495
- sdkLogger.log("Selecting audio language:", language, "with role: ", role);
496
- if (this._audioTracksMap[language]) {
497
- remotePlayer.selectAudioTrack(this._audioTracksMap[language]);
498
- } else {
499
- sdkLogger.warn(`Language ${language} not found in audio tracks map`);
500
- }
522
+ sdkLogger.log("selectAudioLanguage() Selecting audio language:", language, "with role: ", role);
523
+ remotePlayer.selectAudioLanguage(language, role);
501
524
  super.selectAudioLanguage(language, role);
502
525
  }
503
526
 
527
+ selectTextTrack(textTrack) {
528
+ const textId = textTrack.id;
529
+ const textLang = textTrack.language;
530
+ const role = Array.isArray(textTrack.roles) && textTrack.roles.length > 0
531
+ ? textTrack.roles[0]
532
+ : null;
533
+ sdkLogger.log(`selectTextTrack() called: text id ${textId} language ${textLang}, role ${role}, accessibilityPurpose: ${textTrack.accessibilityPurpose}`);
534
+ const apCode = this._getAccessibilityCodeFromPurpose(textTrack.accessibilityPurpose);
535
+ remotePlayer.selectTextLanguage(textLang, role, apCode);
536
+
537
+ super.selectTextTrack(textTrack);
538
+ }
504
539
  selectTextLanguage(language, role) {
505
- sdkLogger.log("Selecting text language:", language, "with role:", role);
506
- if (this._textTracksMap[language]) {
507
- remotePlayer.selectTextTrack(this._textTracksMap[language]);
508
- } else {
509
- sdkLogger.warn(`Language ${language} not found in text tracks map`);
510
- }
540
+ sdkLogger.log("selectTextLanguage() Selecting text language:", language, "with role: ", role);
541
+ remotePlayer.selectTextLanguage(language, role);
511
542
  super.selectTextLanguage(language, role);
512
543
  }
513
544
 
@@ -734,7 +765,6 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
734
765
  };
735
766
 
736
767
  if (!this.isInRemotePlayback || remotePlayer.getAssetUri() !== url) {
737
- this._audioTracksMap = {};
738
768
  this._videoTracksMap = {};
739
769
  try {
740
770
  if (this.remotePlayer.getLoadMode() === this.remotePlayer.LoadMode.UNLOADING) {
@@ -789,6 +819,32 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
789
819
  }
790
820
  }
791
821
 
822
+ /**
823
+ * Returns accessibility code of Shaka's accessibility purpose string
824
+ * @param {string} purpose
825
+ * @returns {null|string}
826
+ * @private
827
+ */
828
+ _getAccessibilityCodeFromPurpose(purpose) {
829
+ switch (purpose) {
830
+ case shakaDebug.media.ManifestParser.AccessibilityPurpose.VISUALLY_IMPAIRED:
831
+ return remotePlayer.AccessibilityPurposeCode.VISUALLY_IMPAIRED;
832
+ case shakaDebug.media.ManifestParser.AccessibilityPurpose.HARD_OF_HEARING:
833
+ return remotePlayer.AccessibilityPurposeCode.HARD_OF_HEARING;
834
+ case shakaDebug.media.ManifestParser.AccessibilityPurpose.SPOKEN_SUBTITLES:
835
+ return remotePlayer.AccessibilityPurposeCode.SPOKEN_SUBTITLES;
836
+ case undefined:
837
+ case null:
838
+ case "":
839
+ return remotePlayer.AccessibilityPurposeCode.NONE;
840
+ default:
841
+ sdkLogger.warn(`_getAccessibilityCodeFromPurpose(): unknown accessibilityPurpose ${purpose}`);
842
+ return remotePlayer.AccessibilityPurposeCode.NONE;
843
+ }
844
+
845
+ }
846
+
847
+
792
848
  }
793
849
 
794
850
  shaka.Player = SenzaShakaPlayer;