senza-sdk 4.3.6 → 4.3.7-b2908c5.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.
@@ -12,9 +12,27 @@
12
12
 
13
13
  /*
14
14
  @license
15
- EME Encryption Scheme Polyfill
16
- Copyright 2019 Google LLC
17
- SPDX-License-Identifier: Apache-2.0
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.
18
36
  */
19
37
 
20
38
  /*
@@ -45,6 +63,13 @@
45
63
  SPDX-License-Identifier: Apache-2.0
46
64
  */
47
65
 
66
+ /*
67
+ @license
68
+ Shaka Player
69
+ Copyright 2025 Google LLC
70
+ SPDX-License-Identifier: Apache-2.0
71
+ */
72
+
48
73
  /*
49
74
  @license
50
75
  tXml
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "senza-sdk",
3
- "version": "4.3.6",
3
+ "version": "4.3.7-b2908c5.0",
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
  }
@@ -63,21 +63,21 @@ class AlarmManager extends AlarmManagerInterface {
63
63
  const alarmHandlingDuration = Date.now() - timeBeforeAlarmHandling;
64
64
  logger.log(`All callbacks for alarm '${e.detail.alarmName}' are finished within ${alarmHandlingDuration}ms`);
65
65
  // If this alarm triggered the application, and moveToForeground has not been called as a result of the alarm
66
- // (i.e. the application is still in the background), we want to intentionally terminate the ui in order to save resources.
66
+ // (i.e. the application is still in the background), we want to intentionally disconnect the ui in order to save resources.
67
67
  const isTriggering = lifecycle.triggerEvent.type === "alarmFiredEvent" && lifecycle._triggerEventFcid && lifecycle._triggerEventFcid === e.detail.fcid;
68
68
  if (isTriggering) {
69
69
  if (!this._moveToForegroundHasBeenCalled && window.cefQuery) {
70
- logger.log("Application is about to be terminated since didn't move to foreground");
71
- const message = { type: "terminating" };
70
+ logger.log("Application is about to be disconnected since didn't move to foreground");
71
+ const message = { type: "disconnect" };
72
72
  const request = { target: "TC", waitForResponse: false, message: JSON.stringify(message) };
73
73
  window.cefQuery({
74
74
  request: JSON.stringify(request),
75
75
  persistent: false,
76
76
  onSuccess: () => {
77
- logger.log("terminating request successfully sent");
77
+ logger.log("disconnect request successfully sent");
78
78
  },
79
79
  onFailure: (code, msg) => {
80
- logger.error(`terminating request failed: ${code} ${msg}`);
80
+ logger.error(`disconnect request failed: ${code} ${msg}`);
81
81
  }
82
82
  });
83
83
  }
@@ -192,7 +192,7 @@ class Lifecycle extends LifecycleInterface {
192
192
  // Use the event manager to dispatch the event and wait for all listeners
193
193
  await this._eventManager.dispatch("userdisconnected", event);
194
194
 
195
- this._sendTerminatingMessage();
195
+ this._sendDisconnectMessage();
196
196
  });
197
197
 
198
198
  typeof document !== "undefined" && document.addEventListener("keydown", () => {
@@ -777,14 +777,14 @@ class Lifecycle extends LifecycleInterface {
777
777
  }
778
778
 
779
779
  /**
780
- * Sends the terminating message to the platform
780
+ * Sends the disconnect message to the platform
781
781
  * @private
782
782
  */
783
- _sendTerminatingMessage() {
783
+ _sendDisconnectMessage() {
784
784
  if (window.cefQuery) {
785
785
  const FCID = getFCID();
786
786
  const message = {
787
- type: "terminating",
787
+ type: "disconnect",
788
788
  fcid: FCID
789
789
  };
790
790
  const request = { target: "TC", waitForResponse: false, message: JSON.stringify(message) };
@@ -793,10 +793,10 @@ class Lifecycle extends LifecycleInterface {
793
793
  request: JSON.stringify(request),
794
794
  persistent: false,
795
795
  onSuccess: () => {
796
- sdkLogger.log("Terminating signal sent after userdisconnected event was processed");
796
+ sdkLogger.log("Disconnect signal sent after userdisconnected event was processed");
797
797
  },
798
798
  onFailure: (code, msg) => {
799
- sdkLogger.error(`Failed to send terminating signal: ${code} ${msg}`);
799
+ sdkLogger.error(`Failed to send disconnect signal: ${code} ${msg}`);
800
800
  }
801
801
  });
802
802
  }
@@ -1081,24 +1081,30 @@ class RemotePlayer extends RemotePlayerInterface {
1081
1081
  * @throws {RemotePlayerError} If the player is not initialized or not loaded.
1082
1082
  **/
1083
1083
  selectAudioTrack(audioTrackId) {
1084
+ return this._selectAudioTrack(audioTrackId, false);
1085
+ }
1086
+
1087
+ _selectAudioTrack(audioTrackId, force = false) {
1084
1088
  if (!this._isInitialized) {
1085
1089
  throw new RemotePlayerError(6500, "Cannot call selectAudioTrack() if remote player is not initialized");
1086
1090
  }
1087
1091
  if (this._loadMode !== this.LoadMode.LOADED) {
1088
1092
  throw new RemotePlayerError(6001, "Cannot call selectAudioTrack() if remote player is not loaded");
1089
1093
  }
1090
- let found = false;
1091
- const prevSelectedAudioTrack = this._selectedAudioTrack;
1092
- for (const track of this.getAudioTracks()) {
1093
- if (track.id === audioTrackId) {
1094
- found = true;
1095
- break;
1094
+ if (!force) {
1095
+ let found = false;
1096
+ for (const track of this.getAudioTracks()) {
1097
+ if (track.id === audioTrackId) {
1098
+ found = true;
1099
+ break;
1100
+ }
1101
+ }
1102
+ if (!found) {
1103
+ sdkLogger.warn(`Invalid audioTrackId ${audioTrackId}`);
1104
+ return Promise.resolve();
1096
1105
  }
1097
1106
  }
1098
- if (!found) {
1099
- sdkLogger.warn(`Invalid audioTrackId ${audioTrackId}`);
1100
- return Promise.resolve();
1101
- }
1107
+ const prevSelectedAudioTrack = this._selectedAudioTrack;
1102
1108
  if (this._selectedAudioTrack === audioTrackId) {
1103
1109
  return Promise.resolve(); // Audio language already selected
1104
1110
  }
@@ -1115,6 +1121,12 @@ class RemotePlayer extends RemotePlayerInterface {
1115
1121
  }
1116
1122
  }
1117
1123
 
1124
+ async selectAudioLanguage(language, role="", accessibilityPurposeCode=this.AccessibilityPurposeCode.NONE) {
1125
+ const trackId = this._generateSenzaTrackId(language, role, accessibilityPurposeCode);
1126
+ return this._selectAudioTrack(trackId, true);
1127
+ }
1128
+
1129
+
1118
1130
  /**
1119
1131
  * Handles the asynchronous selection of an audio track.
1120
1132
  * If the player is playing, it pauses before changing the track and resumes playback if necessary.
@@ -1208,6 +1220,11 @@ class RemotePlayer extends RemotePlayerInterface {
1208
1220
  return Promise.resolve(undefined);
1209
1221
  }
1210
1222
 
1223
+ async selectTextLanguage(language, role="", accessibilityPurposeCode=this.AccessibilityPurposeCode.NONE) {
1224
+ const trackId = this._generateSenzaTrackId(language, role, accessibilityPurposeCode);
1225
+ return this._selectTextTrack(trackId, true);
1226
+ }
1227
+
1211
1228
  /** Select a specific text (subtitle) track.
1212
1229
  * Track id should come from a call to getTextTracks.
1213
1230
  * If no tracks exist - this is a no-op.
@@ -1217,24 +1234,31 @@ class RemotePlayer extends RemotePlayerInterface {
1217
1234
  * @throws {RemotePlayerError} If the player is not initialized or not loaded.
1218
1235
  * */
1219
1236
  selectTextTrack(textTrackId) {
1237
+ return this._selectTextTrack(textTrackId, false);
1238
+ }
1239
+
1240
+ _selectTextTrack(textTrackId, force = false) {
1220
1241
  if (!this._isInitialized) {
1221
1242
  throw new RemotePlayerError(6500, "Cannot call selectTextTrack() if remote player is not initialized");
1222
1243
  }
1223
1244
  if (this._loadMode !== this.LoadMode.LOADED) {
1224
1245
  throw new RemotePlayerError(6001, "Cannot call selectTextTrack() if remote player is not loaded");
1225
1246
  }
1226
- let found = false;
1227
- const prevSelectedTextTrack = this._selectedSubtitlesTrack;
1228
- for (const track of this.getTextTracks()) {
1229
- if (track.id === textTrackId) {
1230
- found = true;
1231
- break;
1247
+ if (!force) {
1248
+ let found = false;
1249
+ for (const track of this.getTextTracks()) {
1250
+ if (track.id === textTrackId) {
1251
+ found = true;
1252
+ break;
1253
+ }
1254
+ }
1255
+ if (!found) {
1256
+ sdkLogger.warn(`Invalid textTrackId ${textTrackId}`);
1257
+ return Promise.resolve();
1232
1258
  }
1233
1259
  }
1234
- if (!found) {
1235
- sdkLogger.warn(`Invalid textTrackId ${textTrackId}`);
1236
- return Promise.resolve();
1237
- }
1260
+
1261
+ const prevSelectedTextTrack = this._selectedSubtitlesTrack;
1238
1262
  if (this._selectedSubtitlesTrack === textTrackId) {
1239
1263
  return Promise.resolve(); // Subtitle language already selected
1240
1264
  }
@@ -1774,6 +1798,12 @@ class RemotePlayer extends RemotePlayerInterface {
1774
1798
  return setLanguageError ? Promise.reject(setLanguageError) : Promise.resolve();
1775
1799
  }
1776
1800
 
1801
+ _generateSenzaTrackId(language, role, accessibilityPurpose) {
1802
+ const l = language || "";
1803
+ const r = role || "";
1804
+ const ap = accessibilityPurpose || "";
1805
+ return `${l}:${r}:${ap}`;
1806
+ }
1777
1807
  _setScreenBlackout(blackoutTime) {
1778
1808
  if (window.cefQuery) {
1779
1809
  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;
@@ -176,32 +174,6 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
176
174
  event.writeLicenseResponse(response.status, responseBody);
177
175
 
178
176
  },
179
- "tracksupdate": () => {
180
- this._audioTracksMap = {};
181
- this._textTracksMap = {};
182
- // 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.
183
- const audioTracks = remotePlayer.getAudioTracks();
184
- for (const track of audioTracks) {
185
- let lang = track.lang;
186
-
187
- if (lang.length === 3) {
188
- lang = iso6393to1[lang] || lang;
189
- }
190
- this._audioTracksMap[lang] = track.id;
191
- }
192
-
193
- const textTracks = remotePlayer.getTextTracks();
194
- for (const track of textTracks) {
195
- if (!track.autoTranslate) {
196
- let lang = track.lang;
197
-
198
- if (lang.length === 3) {
199
- lang = iso6393to1[lang] || lang;
200
- }
201
- this._textTracksMap[lang] = track.id;
202
- }
203
- }
204
- },
205
177
  "playing": async () => {
206
178
  sdkLogger.info("remotePlayer playing event received");
207
179
  // If playing timeout was not expored, and the feature is set, handle the playing event
@@ -439,8 +411,6 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
439
411
  }
440
412
 
441
413
  await super.detach(keepAdManager);
442
- this._audioTracksMap = {};
443
- this._textTracksMap = {};
444
414
  this._removeVideoElementEventListeners();
445
415
  try {
446
416
  if (remotePlayer.getAssetUri() !== "") {
@@ -470,30 +440,65 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
470
440
  }
471
441
 
472
442
  getTextLanguages() {
473
- return Object.keys(this._textTracksMap);
443
+ const tracks = this.remotePlayer.getTextTracks();
444
+ return [...new Set(tracks.map(item => item.lang))];
474
445
  }
475
446
 
476
447
  getAudioLanguages() {
477
- return Object.keys(this._audioTracksMap);
448
+ const tracks = this.remotePlayer.getAudioTracks();
449
+ return [...new Set(tracks.map(item => item.lang))];
450
+ }
451
+
452
+
453
+ selectVariantTrack(track, clearBuffer = false, safeMargin = 0) {
454
+ const audioLang = track.language;
455
+ const audioRole = Array.isArray(track.audioRoles) && track.audioRoles.length > 0
456
+ ? track.audioRoles[0]
457
+ : null;
458
+ const audioId = track.audioId;
459
+ sdkLogger.log(`selectVariantTrack() called: audio id ${audioId} language ${audioLang}, role ${audioRole}, accessibilityPurpose: ${track.accessibilityPurpose}`);
460
+
461
+ const apCode = this._getAccessibilityCodeFromPurpose(track.accessibilityPurpose);
462
+ remotePlayer.selectAudioLanguage(audioLang, audioRole, apCode);
463
+
464
+ super.selectVariantTrack(track, clearBuffer, safeMargin);
465
+
466
+ }
467
+
468
+ selectAudioTrack(audioTrack, safeMargin = 0) {
469
+ const audioId = audioTrack.id;
470
+ const audioLang = audioTrack.language;
471
+ const role = Array.isArray(audioTrack.roles) && audioTrack.roles.length > 0
472
+ ? audioTrack.roles[0]
473
+ : null;
474
+ sdkLogger.log(`selectAudioTrack() called: audio id ${audioId} language ${audioLang}, role ${role}, accessibilityPurpose: ${audioTrack.accessibilityPurpose}`);
475
+ const apCode = this._getAccessibilityCodeFromPurpose(audioTrack.accessibilityPurpose);
476
+ remotePlayer.selectAudioLanguage(audioLang, role, apCode);
477
+
478
+ super.selectAudioTrack(audioTrack, safeMargin);
478
479
  }
479
480
 
480
481
  selectAudioLanguage(language, role) {
481
- sdkLogger.log("Selecting audio language:", language, "with role: ", role);
482
- if (this._audioTracksMap[language]) {
483
- remotePlayer.selectAudioTrack(this._audioTracksMap[language]);
484
- } else {
485
- sdkLogger.warn(`Language ${language} not found in audio tracks map`);
486
- }
482
+ sdkLogger.log("selectAudioLanguage() Selecting audio language:", language, "with role: ", role);
483
+ remotePlayer.selectAudioLanguage(language, role);
487
484
  super.selectAudioLanguage(language, role);
488
485
  }
489
486
 
487
+ selectTextTrack(textTrack) {
488
+ const textId = textTrack.id;
489
+ const textLang = textTrack.language;
490
+ const role = Array.isArray(textTrack.roles) && textTrack.roles.length > 0
491
+ ? textTrack.roles[0]
492
+ : null;
493
+ sdkLogger.log(`selectTextTrack() called: text id ${textId} language ${textLang}, role ${role}, accessibilityPurpose: ${textTrack.accessibilityPurpose}`);
494
+ const apCode = this._getAccessibilityCodeFromPurpose(textTrack.accessibilityPurpose);
495
+ remotePlayer.selectTextLanguage(textLang, role, apCode);
496
+
497
+ super.selectTextTrack(textTrack);
498
+ }
490
499
  selectTextLanguage(language, role) {
491
- sdkLogger.log("Selecting text language:", language, "with role:", role);
492
- if (this._textTracksMap[language]) {
493
- remotePlayer.selectTextTrack(this._textTracksMap[language]);
494
- } else {
495
- sdkLogger.warn(`Language ${language} not found in text tracks map`);
496
- }
500
+ sdkLogger.log("selectTextLanguage() Selecting text language:", language, "with role: ", role);
501
+ remotePlayer.selectTextLanguage(language, role);
497
502
  super.selectTextLanguage(language, role);
498
503
  }
499
504
 
@@ -720,7 +725,6 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
720
725
  };
721
726
 
722
727
  if (!this.isInRemotePlayback || remotePlayer.getAssetUri() !== url) {
723
- this._audioTracksMap = {};
724
728
  this._videoTracksMap = {};
725
729
  try {
726
730
  if (this.remotePlayer.getLoadMode() === this.remotePlayer.LoadMode.UNLOADING) {
@@ -775,6 +779,32 @@ export class SenzaShakaPlayer extends SenzaShakaInterface {
775
779
  }
776
780
  }
777
781
 
782
+ /**
783
+ * Returns accessibility code of Shaka's accessibility purpose string
784
+ * @param {string} purpose
785
+ * @returns {null|string}
786
+ * @private
787
+ */
788
+ _getAccessibilityCodeFromPurpose(purpose) {
789
+ switch (purpose) {
790
+ case shakaDebug.media.ManifestParser.AccessibilityPurpose.VISUALLY_IMPAIRED:
791
+ return remotePlayer.AccessibilityPurposeCode.VISUALLY_IMPAIRED;
792
+ case shakaDebug.media.ManifestParser.AccessibilityPurpose.HARD_OF_HEARING:
793
+ return remotePlayer.AccessibilityPurposeCode.HARD_OF_HEARING;
794
+ case shakaDebug.media.ManifestParser.AccessibilityPurpose.SPOKEN_SUBTITLES:
795
+ return remotePlayer.AccessibilityPurposeCode.SPOKEN_SUBTITLES;
796
+ case undefined:
797
+ case null:
798
+ case "":
799
+ return remotePlayer.AccessibilityPurposeCode.NONE;
800
+ default:
801
+ sdkLogger.warn(`_getAccessibilityCodeFromPurpose(): unknown accessibilityPurpose ${purpose}`);
802
+ return remotePlayer.AccessibilityPurposeCode.NONE;
803
+ }
804
+
805
+ }
806
+
807
+
778
808
  }
779
809
 
780
810
  shaka.Player = SenzaShakaPlayer;
@@ -163,209 +163,3 @@ export const TargetPlayingState = Object.freeze({
163
163
  PLAYING_UI: "playingUi",
164
164
  PLAYING_ABR: "playingAbr"
165
165
  });
166
-
167
- export const iso6393to1 = {
168
- "aar": "aa",
169
- "abk": "ab",
170
- "afr": "af",
171
- "aka": "ak",
172
- "alb": "sq",
173
- "amh": "am",
174
- "ara": "ar",
175
- "arg": "an",
176
- "arm": "hy",
177
- "asm": "as",
178
- "ava": "av",
179
- "ave": "ae",
180
- "aym": "ay",
181
- "aze": "az",
182
- "bak": "ba",
183
- "bam": "bm",
184
- "baq": "eu",
185
- "bel": "be",
186
- "ben": "bn",
187
- "bih": "bh",
188
- "bis": "bi",
189
- "bod": "bo",
190
- "bos": "bs",
191
- "bre": "br",
192
- "bul": "bg",
193
- "bur": "my",
194
- "cat": "ca",
195
- "ces": "cs",
196
- "cha": "ch",
197
- "che": "ce",
198
- "chi": "zh",
199
- "chu": "cu",
200
- "chv": "cv",
201
- "cor": "kw",
202
- "cos": "co",
203
- "cre": "cr",
204
- "cym": "cy",
205
- "dan": "da",
206
- "deu": "de",
207
- "div": "dv",
208
- "dut": "nl",
209
- "dzo": "dz",
210
- "ell": "el",
211
- "eng": "en",
212
- "epo": "eo",
213
- "est": "et",
214
- "eus": "eu",
215
- "ewe": "ee",
216
- "fao": "fo",
217
- "fas": "fa",
218
- "fij": "fj",
219
- "fin": "fi",
220
- "fra": "fr",
221
- "fre": "fr",
222
- "fry": "fy",
223
- "ful": "ff",
224
- "geo": "ka",
225
- "ger": "de",
226
- "gla": "gd",
227
- "gle": "ga",
228
- "glg": "gl",
229
- "glv": "gv",
230
- "gre": "el",
231
- "grn": "gn",
232
- "guj": "gu",
233
- "hat": "ht",
234
- "hau": "ha",
235
- "heb": "he",
236
- "her": "hz",
237
- "hin": "hi",
238
- "hmo": "ho",
239
- "hrv": "hr",
240
- "hun": "hu",
241
- "hye": "hy",
242
- "ibo": "ig",
243
- "ice": "is",
244
- "ido": "io",
245
- "iii": "ii",
246
- "iku": "iu",
247
- "ile": "ie",
248
- "ina": "ia",
249
- "ind": "id",
250
- "ipk": "ik",
251
- "isl": "is",
252
- "ita": "it",
253
- "jav": "jv",
254
- "jpn": "ja",
255
- "kal": "kl",
256
- "kan": "kn",
257
- "kas": "ks",
258
- "kat": "ka",
259
- "kau": "kr",
260
- "kaz": "kk",
261
- "khm": "km",
262
- "kik": "ki",
263
- "kin": "rw",
264
- "kir": "ky",
265
- "kom": "kv",
266
- "kon": "kg",
267
- "kor": "ko",
268
- "kua": "kj",
269
- "kur": "ku",
270
- "lao": "lo",
271
- "lat": "la",
272
- "lav": "lv",
273
- "lim": "li",
274
- "lin": "ln",
275
- "lit": "lt",
276
- "ltz": "lb",
277
- "lub": "lu",
278
- "lug": "lg",
279
- "mac": "mk",
280
- "mah": "mh",
281
- "mal": "ml",
282
- "mao": "mi",
283
- "mar": "mr",
284
- "may": "ms",
285
- "mkd": "mk",
286
- "mlg": "mg",
287
- "mlt": "mt",
288
- "mon": "mn",
289
- "mri": "mi",
290
- "msa": "ms",
291
- "mya": "my",
292
- "nau": "na",
293
- "nav": "nv",
294
- "nbl": "nr",
295
- "nde": "nd",
296
- "ndo": "ng",
297
- "nep": "ne",
298
- "nld": "nl",
299
- "nno": "nn",
300
- "nob": "nb",
301
- "nor": "no",
302
- "nya": "ny",
303
- "oci": "oc",
304
- "oji": "oj",
305
- "ori": "or",
306
- "orm": "om",
307
- "oss": "os",
308
- "pan": "pa",
309
- "per": "fa",
310
- "pli": "pi",
311
- "pol": "pl",
312
- "por": "pt",
313
- "pus": "ps",
314
- "que": "qu",
315
- "roh": "rm",
316
- "ron": "ro",
317
- "rum": "ro",
318
- "run": "rn",
319
- "rus": "ru",
320
- "sag": "sg",
321
- "san": "sa",
322
- "sin": "si",
323
- "slk": "sk",
324
- "slo": "sk",
325
- "slv": "sl",
326
- "sme": "se",
327
- "smo": "sm",
328
- "sna": "sn",
329
- "snd": "sd",
330
- "som": "so",
331
- "sot": "st",
332
- "spa": "es",
333
- "sqi": "sq",
334
- "srd": "sc",
335
- "srp": "sr",
336
- "ssw": "ss",
337
- "sun": "su",
338
- "swa": "sw",
339
- "swe": "sv",
340
- "tah": "ty",
341
- "tam": "ta",
342
- "tat": "tt",
343
- "tel": "te",
344
- "tgk": "tg",
345
- "tgl": "tl",
346
- "tha": "th",
347
- "tib": "bo",
348
- "tir": "ti",
349
- "ton": "to",
350
- "tsn": "tn",
351
- "tso": "ts",
352
- "tuk": "tk",
353
- "tur": "tr",
354
- "twi": "tw",
355
- "uig": "ug",
356
- "ukr": "uk",
357
- "urd": "ur",
358
- "uzb": "uz",
359
- "ven": "ve",
360
- "vie": "vi",
361
- "vol": "vo",
362
- "wel": "cy",
363
- "wln": "wa",
364
- "wol": "wo",
365
- "xho": "xh",
366
- "yid": "yi",
367
- "yor": "yo",
368
- "zha": "za",
369
- "zho": "zh",
370
- "zul": "zu"
371
- };