shaka-player 4.13.0 → 4.13.2

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.
Files changed (67) hide show
  1. package/CONTRIBUTORS +1 -0
  2. package/dist/deps.js +4 -4
  3. package/dist/locales.js +36 -36
  4. package/dist/shaka-player.compiled.d.ts +1 -1
  5. package/dist/shaka-player.compiled.debug.d.ts +1 -1
  6. package/dist/shaka-player.compiled.debug.externs.js +2 -9
  7. package/dist/shaka-player.compiled.debug.js +491 -478
  8. package/dist/shaka-player.compiled.debug.map +4 -4
  9. package/dist/shaka-player.compiled.externs.js +2 -9
  10. package/dist/shaka-player.compiled.js +1455 -1443
  11. package/dist/shaka-player.compiled.map +4 -4
  12. package/dist/shaka-player.ui.d.ts +1 -1
  13. package/dist/shaka-player.ui.debug.d.ts +1 -1
  14. package/dist/shaka-player.ui.debug.externs.js +2 -9
  15. package/dist/shaka-player.ui.debug.js +544 -530
  16. package/dist/shaka-player.ui.debug.map +4 -4
  17. package/dist/shaka-player.ui.externs.js +2 -9
  18. package/dist/shaka-player.ui.js +1627 -1613
  19. package/dist/shaka-player.ui.map +4 -4
  20. package/eslint.config.mjs +14 -1
  21. package/lib/cast/cast_proxy.js +50 -12
  22. package/lib/cast/cast_receiver.js +6 -9
  23. package/lib/cast/cast_sender.js +14 -11
  24. package/lib/cast/cast_utils.js +43 -46
  25. package/lib/cea/mp4_cea_parser.js +23 -23
  26. package/lib/dash/content_protection.js +1 -1
  27. package/lib/dash/dash_parser.js +39 -29
  28. package/lib/dash/mpd_utils.js +8 -9
  29. package/lib/dash/segment_list.js +2 -2
  30. package/lib/dash/segment_template.js +9 -9
  31. package/lib/debug/log.js +16 -17
  32. package/lib/hls/hls_parser.js +122 -99
  33. package/lib/media/closed_caption_parser.js +5 -5
  34. package/lib/media/content_workarounds.js +78 -0
  35. package/lib/media/example_based_criteria.js +4 -3
  36. package/lib/media/manifest_parser.js +8 -12
  37. package/lib/media/media_source_engine.js +136 -119
  38. package/lib/media/preload_manager.js +10 -5
  39. package/lib/media/region_observer.js +9 -8
  40. package/lib/media/region_timeline.js +10 -8
  41. package/lib/media/streaming_engine.js +25 -9
  42. package/lib/mss/mss_parser.js +6 -7
  43. package/lib/net/http_plugin_utils.js +1 -1
  44. package/lib/net/networking_engine.js +7 -7
  45. package/lib/net/networking_utils.js +38 -39
  46. package/lib/offline/stream_bandwidth_estimator.js +8 -8
  47. package/lib/player.js +98 -47
  48. package/lib/polyfill/media_capabilities.js +8 -9
  49. package/lib/text/cue.js +6 -2
  50. package/lib/text/text_engine.js +7 -7
  51. package/lib/text/ttml_text_parser.js +14 -16
  52. package/lib/text/ui_text_displayer.js +25 -4
  53. package/lib/transmuxer/ac3_transmuxer.js +6 -1
  54. package/lib/transmuxer/transmuxer_engine.js +6 -6
  55. package/lib/util/manifest_parser_utils.js +7 -8
  56. package/lib/util/mp4_generator.js +23 -8
  57. package/lib/util/mp4_parser.js +10 -10
  58. package/lib/util/multi_map.js +22 -20
  59. package/lib/util/platform.js +40 -9
  60. package/lib/util/player_configuration.js +1 -1
  61. package/lib/util/stream_utils.js +35 -17
  62. package/package.json +2 -2
  63. package/project-words.txt +1 -0
  64. package/third_party/closure-uri/uri.js +13 -13
  65. package/ui/ad_statistics_button.js +17 -16
  66. package/ui/audio_language_selection.js +6 -1
  67. package/ui/statistics_button.js +36 -35
@@ -17,11 +17,11 @@ goog.require('shaka.util.DataViewReader');
17
17
  shaka.util.Mp4Parser = class {
18
18
  /** */
19
19
  constructor() {
20
- /** @private {!Object<number, shaka.util.Mp4Parser.BoxType_>} */
21
- this.headers_ = [];
20
+ /** @private {!Map<number, shaka.util.Mp4Parser.BoxType_>} */
21
+ this.headers_ = new Map();
22
22
 
23
- /** @private {!Object<number, !shaka.util.Mp4Parser.CallbackType>} */
24
- this.boxDefinitions_ = [];
23
+ /** @private {!Map<number, !shaka.util.Mp4Parser.CallbackType>} */
24
+ this.boxDefinitions_ = new Map();
25
25
 
26
26
  /** @private {boolean} */
27
27
  this.done_ = false;
@@ -38,8 +38,8 @@ shaka.util.Mp4Parser = class {
38
38
  */
39
39
  box(type, definition) {
40
40
  const typeCode = shaka.util.Mp4Parser.typeFromString_(type);
41
- this.headers_[typeCode] = shaka.util.Mp4Parser.BoxType_.BASIC_BOX;
42
- this.boxDefinitions_[typeCode] = definition;
41
+ this.headers_.set(typeCode, shaka.util.Mp4Parser.BoxType_.BASIC_BOX);
42
+ this.boxDefinitions_.set(typeCode, definition);
43
43
  return this;
44
44
  }
45
45
 
@@ -54,8 +54,8 @@ shaka.util.Mp4Parser = class {
54
54
  */
55
55
  fullBox(type, definition) {
56
56
  const typeCode = shaka.util.Mp4Parser.typeFromString_(type);
57
- this.headers_[typeCode] = shaka.util.Mp4Parser.BoxType_.FULL_BOX;
58
- this.boxDefinitions_[typeCode] = definition;
57
+ this.headers_.set(typeCode, shaka.util.Mp4Parser.BoxType_.FULL_BOX);
58
+ this.boxDefinitions_.set(typeCode, definition);
59
59
  return this;
60
60
  }
61
61
 
@@ -135,13 +135,13 @@ shaka.util.Mp4Parser = class {
135
135
  break;
136
136
  }
137
137
 
138
- const boxDefinition = this.boxDefinitions_[type];
138
+ const boxDefinition = this.boxDefinitions_.get(type);
139
139
 
140
140
  if (boxDefinition) {
141
141
  let version = null;
142
142
  let flags = null;
143
143
 
144
- if (this.headers_[type] == shaka.util.Mp4Parser.BoxType_.FULL_BOX) {
144
+ if (this.headers_.get(type) == shaka.util.Mp4Parser.BoxType_.FULL_BOX) {
145
145
  if (stopOnPartial && reader.getPosition() + 4 > reader.getLength()) {
146
146
  this.done_ = true;
147
147
  return;
@@ -14,8 +14,8 @@ goog.provide('shaka.util.MultiMap');
14
14
  shaka.util.MultiMap = class {
15
15
  /** */
16
16
  constructor() {
17
- /** @private {!Object<string, !Array<T>>} */
18
- this.map_ = {};
17
+ /** @private {!Map<string, !Array<T>>} */
18
+ this.map_ = new Map();
19
19
  }
20
20
 
21
21
 
@@ -25,11 +25,10 @@ shaka.util.MultiMap = class {
25
25
  * @param {T} value
26
26
  */
27
27
  push(key, value) {
28
- // eslint-disable-next-line no-prototype-builtins
29
- if (this.map_.hasOwnProperty(key)) {
30
- this.map_[key].push(value);
28
+ if (this.map_.has(key)) {
29
+ this.map_.get(key).push(value);
31
30
  } else {
32
- this.map_[key] = [value];
31
+ this.map_.set(key, [value]);
33
32
  }
34
33
  }
35
34
 
@@ -40,10 +39,12 @@ shaka.util.MultiMap = class {
40
39
  * @return {Array<T>} or null if no such key exists.
41
40
  */
42
41
  get(key) {
43
- const list = this.map_[key];
42
+ if (!this.map_.has(key)) {
43
+ return null;
44
+ }
44
45
  // slice() clones the list so that it and the map can each be modified
45
46
  // without affecting the other.
46
- return list ? list.slice() : null;
47
+ return this.map_.get(key).slice();
47
48
  }
48
49
 
49
50
 
@@ -53,8 +54,8 @@ shaka.util.MultiMap = class {
53
54
  */
54
55
  getAll() {
55
56
  const list = [];
56
- for (const key in this.map_) {
57
- list.push(...this.map_[key]);
57
+ for (const value of this.map_.values()) {
58
+ list.push(...value);
58
59
  }
59
60
  return list;
60
61
  }
@@ -66,14 +67,15 @@ shaka.util.MultiMap = class {
66
67
  * @param {T} value
67
68
  */
68
69
  remove(key, value) {
69
- if (!(key in this.map_)) {
70
+ if (!this.map_.has(key)) {
70
71
  return;
71
72
  }
72
- this.map_[key] = this.map_[key].filter((i) => i != value);
73
- if (this.map_[key].length == 0) {
73
+ const newValue = this.map_.get(key).filter((i) => i != value);
74
+ this.map_.set(key, newValue);
75
+ if (!newValue.length) {
74
76
  // Delete the array if it's empty, so that |get| will reliably return null
75
77
  // "if no such key exists", instead of sometimes returning an empty array.
76
- delete this.map_[key];
78
+ this.map_.delete(key);
77
79
  }
78
80
  }
79
81
 
@@ -82,7 +84,7 @@ shaka.util.MultiMap = class {
82
84
  * Clear all keys and values from the multimap.
83
85
  */
84
86
  clear() {
85
- this.map_ = {};
87
+ this.map_.clear();
86
88
  }
87
89
 
88
90
 
@@ -90,9 +92,9 @@ shaka.util.MultiMap = class {
90
92
  * @param {function(string, !Array<T>)} callback
91
93
  */
92
94
  forEach(callback) {
93
- for (const key in this.map_) {
94
- callback(key, this.map_[key]);
95
- }
95
+ this.map_.forEach((value, key) => {
96
+ callback(key, value);
97
+ });
96
98
  }
97
99
 
98
100
  /**
@@ -100,7 +102,7 @@ shaka.util.MultiMap = class {
100
102
  * @return {number}
101
103
  */
102
104
  size() {
103
- return Object.keys(this.map_).length;
105
+ return this.map_.size;
104
106
  }
105
107
 
106
108
  /**
@@ -108,6 +110,6 @@ shaka.util.MultiMap = class {
108
110
  * @return {!Array<string>}
109
111
  */
110
112
  keys() {
111
- return Object.keys(this.map_);
113
+ return Array.from(this.map_.keys());
112
114
  }
113
115
  };
@@ -307,13 +307,15 @@ shaka.util.Platform = class {
307
307
  !shaka.util.Platform.isTizen() &&
308
308
  !shaka.util.Platform.isEOS() &&
309
309
  !shaka.util.Platform.isAPL() &&
310
+ !shaka.util.Platform.isAPP() &&
310
311
  !shaka.util.Platform.isVirginMedia() &&
311
312
  !shaka.util.Platform.isOrange() &&
312
313
  !shaka.util.Platform.isPS4() &&
313
314
  !shaka.util.Platform.isAmazonFireTV() &&
314
315
  !shaka.util.Platform.isComcastX1() &&
315
316
  !shaka.util.Platform.isZenterio() &&
316
- !shaka.util.Platform.isSkyQ();
317
+ !shaka.util.Platform.isSkyQ() &&
318
+ !shaka.util.Platform.isMultiChoice();
317
319
  }
318
320
 
319
321
  /**
@@ -396,6 +398,16 @@ shaka.util.Platform = class {
396
398
  return shaka.util.Platform.userAgentContains_('DT_STB_BCM');
397
399
  }
398
400
 
401
+ /**
402
+ * Check if the current platform is a MultiChoice STB.
403
+ * @return {boolean}
404
+ */
405
+ static isMultiChoice() {
406
+ // cspell: ignore MDMP
407
+ return shaka.util.Platform.userAgentContains_('MDMP1001S') ||
408
+ shaka.util.Platform.userAgentContains_('PS5525IMC');
409
+ }
410
+
399
411
  /**
400
412
  * Returns a major version number for Safari, or Safari-based iOS browsers.
401
413
  *
@@ -461,6 +473,14 @@ shaka.util.Platform = class {
461
473
  return shaka.util.Platform.userAgentContains_('PC=APL');
462
474
  }
463
475
 
476
+ /**
477
+ * Check if the current platform is an APPSTB set-top box.
478
+ * @return {boolean}
479
+ */
480
+ static isAPP() {
481
+ return shaka.util.Platform.userAgentContains_('PC=APPSTB');
482
+ }
483
+
464
484
  /**
465
485
  * Guesses if the platform is a mobile one (iOS or Android).
466
486
  *
@@ -568,7 +588,8 @@ shaka.util.Platform = class {
568
588
  Platform.isEOS() || Platform.isAPL() ||
569
589
  Platform.isVirginMedia() || Platform.isOrange() ||
570
590
  Platform.isComcastX1() || Platform.isChromecast() ||
571
- Platform.isHisense() || Platform.isZenterio()) {
591
+ Platform.isHisense() || Platform.isZenterio() ||
592
+ Platform.isAPP() || Platform.isMultiChoice()) {
572
593
  return true;
573
594
  }
574
595
  return false;
@@ -641,6 +662,19 @@ shaka.util.Platform = class {
641
662
  (Platform.isEdge() && Platform.isWindows() && isPlayReady);
642
663
  }
643
664
 
665
+ /**
666
+ * Returns true if the platform requires AC-3 signalling in init
667
+ * segments to be replaced with EC-3 signalling.
668
+ * For such platforms, MediaSourceEngine will attempt to work
669
+ * around it by inserting fake EC-3 signalling into
670
+ * initialization segments.
671
+ *
672
+ * @return {boolean}
673
+ */
674
+ static requiresEC3InitSegments() {
675
+ return shaka.util.Platform.isTizen3();
676
+ }
677
+
644
678
  /**
645
679
  * Returns true if the platform supports SourceBuffer "sequence mode".
646
680
  *
@@ -649,7 +683,7 @@ shaka.util.Platform = class {
649
683
  static supportsSequenceMode() {
650
684
  const Platform = shaka.util.Platform;
651
685
  if (Platform.isTizen3() || Platform.isTizen2() ||
652
- Platform.isWebOS3() || Platform.isPS4()) {
686
+ Platform.isWebOS3() || Platform.isPS4() || Platform.isPS5()) {
653
687
  return false;
654
688
  }
655
689
  return true;
@@ -705,16 +739,13 @@ shaka.util.Platform = class {
705
739
  }
706
740
 
707
741
  /**
708
- * Returns true if MediaKeys is polyfilled
742
+ * Returns true if MediaKeys is polyfilled by the specified polyfill.
709
743
  *
710
- * @param {string=} polyfillType
744
+ * @param {string} polyfillType
711
745
  * @return {boolean}
712
746
  */
713
747
  static isMediaKeysPolyfilled(polyfillType) {
714
- if (polyfillType) {
715
- return polyfillType === window.shakaMediaKeysPolyfill;
716
- }
717
- return !!window.shakaMediaKeysPolyfill;
748
+ return polyfillType === window.shakaMediaKeysPolyfill;
718
749
  }
719
750
 
720
751
 
@@ -75,7 +75,7 @@ shaka.util.PlayerConfiguration = class {
75
75
  persistentSessionOnlinePlayback: false,
76
76
  persistentSessionsMetadata: [],
77
77
  initDataTransform: (initData, initDataType, drmInfo) => {
78
- if (shaka.util.Platform.isMediaKeysPolyfilled() &&
78
+ if (shaka.util.Platform.isMediaKeysPolyfilled('apple') &&
79
79
  initDataType == 'skd') {
80
80
  const cert = drmInfo.serverCertificate;
81
81
  const contentId =
@@ -461,15 +461,15 @@ shaka.util.StreamUtils = class {
461
461
  * @param {!Array<!shaka.extern.Variant>} variants
462
462
  */
463
463
  static overrideDolbyVisionCodecs(variants) {
464
- /** @type {!Object<string, string>} */
465
- const codecMap = {
466
- 'dvav': 'avc3',
467
- 'dva1': 'avc1',
468
- 'dvhe': 'hev1',
469
- 'dvh1': 'hvc1',
470
- 'dvc1': 'vvc1',
471
- 'dvi1': 'vvi1',
472
- };
464
+ /** @type {!Map<string, string>} */
465
+ const codecMap = new Map()
466
+ .set('dvav', 'avc3')
467
+ .set('dva1', 'avc1')
468
+ .set('dvhe', 'hev1')
469
+ .set('dvh1', 'hvc1')
470
+ .set('dvc1', 'vvc1')
471
+ .set('dvi1', 'vvi1');
472
+
473
473
  /** @type {!Set<!shaka.extern.Stream>} */
474
474
  const streams = new Set();
475
475
  for (const variant of variants) {
@@ -478,9 +478,9 @@ shaka.util.StreamUtils = class {
478
478
  }
479
479
  }
480
480
  for (const video of streams) {
481
- for (const dvCodec of Object.keys(codecMap)) {
481
+ for (const [dvCodec, replacement] of codecMap) {
482
482
  if (video.codecs.includes(dvCodec)) {
483
- video.codecs = video.codecs.replace(dvCodec, codecMap[dvCodec]);
483
+ video.codecs = video.codecs.replace(dvCodec, replacement);
484
484
  break;
485
485
  }
486
486
  }
@@ -586,10 +586,10 @@ shaka.util.StreamUtils = class {
586
586
  shaka.util.ObjectUtils.alphabeticalKeyOrderStringify(decodingConfig);
587
587
 
588
588
  const cache = StreamUtils.decodingConfigCache_;
589
- if (cache[cacheKey]) {
589
+ if (cache.has(cacheKey)) {
590
590
  shaka.log.v2('Using cached results of mediaCapabilities.decodingInfo',
591
591
  'for key', cacheKey);
592
- finalResult = merge(finalResult, cache[cacheKey]);
592
+ finalResult = merge(finalResult, cache.get(cacheKey));
593
593
  } else {
594
594
  // Do a final pass-over of the decoding config: if a given stream has
595
595
  // multiple codecs, that suggests that it switches between those codecs
@@ -606,7 +606,7 @@ shaka.util.StreamUtils = class {
606
606
  acc = merge(acc, result);
607
607
  }
608
608
  if (acc) {
609
- cache[cacheKey] = acc;
609
+ cache.set(cacheKey, acc);
610
610
  finalResult = merge(finalResult, acc);
611
611
  }
612
612
  }));
@@ -1067,6 +1067,11 @@ shaka.util.StreamUtils = class {
1067
1067
  }
1068
1068
  }
1069
1069
 
1070
+ if (codecs.toLowerCase() == 'ac-3' &&
1071
+ shaka.util.Platform.requiresEC3InitSegments()) {
1072
+ return 'ec-3';
1073
+ }
1074
+
1070
1075
  return codecs;
1071
1076
  }
1072
1077
 
@@ -1262,6 +1267,9 @@ shaka.util.StreamUtils = class {
1262
1267
  * @return {shaka.extern.Track}
1263
1268
  */
1264
1269
  static variantToTrack(variant) {
1270
+ const ManifestParserUtils = shaka.util.ManifestParserUtils;
1271
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
1272
+
1265
1273
  /** @type {?shaka.extern.Stream} */
1266
1274
  const audio = variant.audio;
1267
1275
  /** @type {?shaka.extern.Stream} */
@@ -1380,6 +1388,16 @@ shaka.util.StreamUtils = class {
1380
1388
  track.audioSamplingRate = video.audioSamplingRate;
1381
1389
  track.spatialAudio = video.spatialAudio;
1382
1390
  track.originalLanguage = video.originalLanguage;
1391
+ track.audioMimeType = videoMimeType;
1392
+ const allCodecs = videoCodec.split(',');
1393
+ try {
1394
+ track.videoCodec = ManifestParserUtils.guessCodecs(
1395
+ ContentType.VIDEO, allCodecs);
1396
+ track.audioCodec = ManifestParserUtils.guessCodecs(
1397
+ ContentType.AUDIO, allCodecs);
1398
+ } catch (e) {
1399
+ // Ignore this error.
1400
+ }
1383
1401
  }
1384
1402
  }
1385
1403
 
@@ -1936,7 +1954,7 @@ shaka.util.StreamUtils = class {
1936
1954
  * Clears underlying decoding config cache.
1937
1955
  */
1938
1956
  static clearDecodingConfigCache() {
1939
- shaka.util.StreamUtils.decodingConfigCache_ = {};
1957
+ shaka.util.StreamUtils.decodingConfigCache_.clear();
1940
1958
  }
1941
1959
  };
1942
1960
 
@@ -1945,10 +1963,10 @@ shaka.util.StreamUtils = class {
1945
1963
  * A cache of results from mediaCapabilities.decodingInfo, indexed by the
1946
1964
  * (stringified) decodingConfig.
1947
1965
  *
1948
- * @type {Object<(!string), (!MediaCapabilitiesDecodingInfo)>}
1966
+ * @type {Map<string, !MediaCapabilitiesDecodingInfo>}
1949
1967
  * @private
1950
1968
  */
1951
- shaka.util.StreamUtils.decodingConfigCache_ = {};
1969
+ shaka.util.StreamUtils.decodingConfigCache_ = new Map();
1952
1970
 
1953
1971
 
1954
1972
  /** @private {number} */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.13.0",
4
+ "version": "4.13.2",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -109,7 +109,7 @@
109
109
  "prepublishOnly": "python build/checkversion.py && python build/all.py --force"
110
110
  },
111
111
  "dependencies": {
112
- "eme-encryption-scheme-polyfill": "^2.2.0"
112
+ "eme-encryption-scheme-polyfill": "^2.2.1"
113
113
  },
114
114
  "clean-package": {
115
115
  "remove": [
package/project-words.txt CHANGED
@@ -160,6 +160,7 @@ linksource
160
160
  longname
161
161
 
162
162
  # 3rd party
163
+ APPSTB
163
164
  Bitcodin
164
165
  Brightcove
165
166
  Broadpeak
@@ -733,7 +733,7 @@ goog.Uri.QueryData = function(query, uri) {
733
733
  */
734
734
  goog.Uri.QueryData.prototype.ensureKeyMapInitialized_ = function() {
735
735
  if (!this.keyMap_) {
736
- this.keyMap_ = {};
736
+ this.keyMap_ = new Map();
737
737
  this.count_ = 0;
738
738
 
739
739
  if (this.encodedQuery_) {
@@ -764,7 +764,7 @@ goog.Uri.QueryData.prototype.ensureKeyMapInitialized_ = function() {
764
764
  * We need to use a Map because we cannot guarantee that the key names will
765
765
  * not be problematic for IE.
766
766
  *
767
- * @type {Object<string, !Array<string>>}
767
+ * @type {Map<string, !Array<string>>}
768
768
  * @private
769
769
  */
770
770
  goog.Uri.QueryData.prototype.keyMap_ = null;
@@ -798,9 +798,9 @@ goog.Uri.QueryData.prototype.add = function(key, value) {
798
798
  // Invalidate the cache.
799
799
  this.encodedQuery_ = null;
800
800
 
801
- var values = this.keyMap_.hasOwnProperty(key) ? this.keyMap_[key] : null;
801
+ var values = this.keyMap_.has(key) ? this.keyMap_.get(key) : null;
802
802
  if (!values) {
803
- this.keyMap_[key] = (values = []);
803
+ this.keyMap_.set(key, (values = []));
804
804
  }
805
805
  values.push(value);
806
806
  goog.asserts.assert(this.count_ != null, 'Should not be null.');
@@ -820,10 +820,10 @@ goog.Uri.QueryData.prototype.add = function(key, value) {
820
820
  // Invalidate the cache.
821
821
  this.encodedQuery_ = null;
822
822
 
823
- if (!this.keyMap_.hasOwnProperty(key)) {
823
+ if (!this.keyMap_.has(key)) {
824
824
  this.add(key, value);
825
825
  } else {
826
- this.keyMap_[key] = [value];
826
+ this.keyMap_.set(key, [value]);
827
827
  }
828
828
 
829
829
  return this;
@@ -838,7 +838,7 @@ goog.Uri.QueryData.prototype.add = function(key, value) {
838
838
  */
839
839
  goog.Uri.QueryData.prototype.get = function(key) {
840
840
  this.ensureKeyMapInitialized_();
841
- return this.keyMap_[key] || [];
841
+ return this.keyMap_.get(key) || [];
842
842
  };
843
843
 
844
844
 
@@ -851,15 +851,15 @@ goog.Uri.QueryData.prototype.toString = function() {
851
851
  return this.encodedQuery_;
852
852
  }
853
853
 
854
- if (!this.keyMap_) {
854
+ if (!this.keyMap_ || !this.keyMap_.size) {
855
855
  return '';
856
856
  }
857
857
 
858
858
  var sb = [];
859
859
 
860
- for (var key in this.keyMap_) {
860
+ for (const key of this.keyMap_.keys()) {
861
861
  var encodedKey = encodeURIComponent(key);
862
- var val = this.keyMap_[key];
862
+ var val = this.keyMap_.get(key);
863
863
  for (var j = 0; j < val.length; j++) {
864
864
  var param = encodedKey;
865
865
  // Ensure that null and undefined are encoded into the url as
@@ -891,9 +891,9 @@ goog.Uri.QueryData.prototype.clone = function() {
891
891
  var rv = new goog.Uri.QueryData();
892
892
  rv.encodedQuery_ = this.encodedQuery_;
893
893
  if (this.keyMap_) {
894
- var cloneMap = {};
895
- for (var key in this.keyMap_) {
896
- cloneMap[key] = this.keyMap_[key].concat();
894
+ var cloneMap = new Map();
895
+ for (const [key, val] of this.keyMap_) {
896
+ cloneMap.set(key, val.concat());
897
897
  }
898
898
  rv.keyMap_ = cloneMap;
899
899
  rv.count_ = this.count_;
@@ -75,11 +75,13 @@ shaka.ui.AdStatisticsButton = class extends shaka.ui.Element {
75
75
  /** @private {!Array} */
76
76
  this.statisticsList_ = [];
77
77
 
78
- /** @private {!Object<string, (number | !Array<number>)>} */
78
+ /** @private {!shaka.extern.AdsStats} */
79
79
  this.currentStats_ = this.adManager.getStats();
80
80
 
81
- /** @private {!Object<string, HTMLElement>} */
82
- this.displayedElements_ = {};
81
+ shaka.ui.Utils.setDisplay(this.button_, this.currentStats_.started > 0);
82
+
83
+ /** @private {!Map<string, HTMLElement>} */
84
+ this.displayedElements_ = new Map();
83
85
 
84
86
  const parseLoadTimes = (name) => {
85
87
  let totalTime = 0;
@@ -95,15 +97,14 @@ shaka.ui.AdStatisticsButton = class extends shaka.ui.Element {
95
97
  return this.currentStats_[name];
96
98
  };
97
99
 
98
- /** @private {!Object<string, function(string): string>} */
99
- this.parseFrom_ = {
100
- 'loadTimes': parseLoadTimes,
101
- 'averageLoadTime': showNumber,
102
- 'started': showNumber,
103
- 'playedCompletely': showNumber,
104
- 'skipped': showNumber,
105
- 'errors': showNumber,
106
- };
100
+ /** @private {!Map<string, function(string): string>} */
101
+ this.parseFrom_ = new Map()
102
+ .set('loadTimes', parseLoadTimes)
103
+ .set('averageLoadTime', showNumber)
104
+ .set('started', showNumber)
105
+ .set('playedCompletely', showNumber)
106
+ .set('skipped', showNumber)
107
+ .set('errors', showNumber);
107
108
 
108
109
  /** @private {shaka.util.Timer} */
109
110
  this.timer_ = new shaka.util.Timer(() => {
@@ -183,10 +184,10 @@ shaka.ui.AdStatisticsButton = class extends shaka.ui.Element {
183
184
  section.appendChild(label);
184
185
 
185
186
  const value = shaka.util.Dom.createHTMLElement('span');
186
- value.textContent = this.parseFrom_[name](name);
187
+ value.textContent = this.parseFrom_.get(name)(name);
187
188
  section.appendChild(value);
188
189
 
189
- this.displayedElements_[name] = value;
190
+ this.displayedElements_.set(name, value);
190
191
 
191
192
  return section;
192
193
  }
@@ -220,8 +221,8 @@ shaka.ui.AdStatisticsButton = class extends shaka.ui.Element {
220
221
  this.currentStats_ = this.adManager.getStats();
221
222
 
222
223
  for (const name of this.statisticsList_) {
223
- this.displayedElements_[name].textContent =
224
- this.parseFrom_[name](name);
224
+ this.displayedElements_.get(name).textContent =
225
+ this.parseFrom_.get(name)(name);
225
226
  }
226
227
  }
227
228
 
@@ -107,8 +107,13 @@ shaka.ui.AudioLanguageSelection = class extends shaka.ui.SettingsMenu {
107
107
  if (track.audioCodec) {
108
108
  codec = track.audioCodec;
109
109
  }
110
+ let label = '';
111
+ if (track.label) {
112
+ label = track.label;
113
+ }
110
114
  this.player.selectAudioLanguage(track.language, track.roles[0],
111
- channelsCount, /* safeMargin= */ 0, codec, track.spatialAudio);
115
+ channelsCount, /* safeMargin= */ 0, codec, track.spatialAudio,
116
+ label);
112
117
  }
113
118
 
114
119