shaka-player 4.3.13 → 4.3.14

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 (124) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/question.md +7 -0
  3. package/.github/workflows/release-please.yaml +2 -27
  4. package/AUTHORS +5 -0
  5. package/CHANGELOG.md +87 -0
  6. package/CODE_OF_CONDUCT.md +1 -1
  7. package/CONTRIBUTORS +6 -0
  8. package/README.md +2 -0
  9. package/demo/common/asset.js +0 -1
  10. package/demo/common/assets.js +0 -1
  11. package/demo/custom.js +2 -0
  12. package/dist/demo.compiled.debug.js +1 -1
  13. package/dist/demo.compiled.debug.map +1 -1
  14. package/dist/demo.compiled.js +1 -1
  15. package/dist/demo.compiled.map +1 -1
  16. package/dist/deps.js +6 -6
  17. package/dist/locales.js +29 -29
  18. package/dist/receiver.compiled.debug.map +1 -1
  19. package/dist/receiver.compiled.map +1 -1
  20. package/dist/shaka-player.compiled.d.ts +25 -5
  21. package/dist/shaka-player.compiled.debug.d.ts +25 -5
  22. package/dist/shaka-player.compiled.debug.externs.js +73 -45
  23. package/dist/shaka-player.compiled.debug.js +414 -396
  24. package/dist/shaka-player.compiled.debug.map +4 -4
  25. package/dist/shaka-player.compiled.externs.js +73 -45
  26. package/dist/shaka-player.compiled.js +912 -903
  27. package/dist/shaka-player.compiled.map +4 -4
  28. package/dist/shaka-player.ui.d.ts +37 -6
  29. package/dist/shaka-player.ui.debug.d.ts +37 -6
  30. package/dist/shaka-player.ui.debug.externs.js +74 -45
  31. package/dist/shaka-player.ui.debug.js +438 -418
  32. package/dist/shaka-player.ui.debug.map +4 -4
  33. package/dist/shaka-player.ui.externs.js +74 -45
  34. package/dist/shaka-player.ui.js +1060 -1051
  35. package/dist/shaka-player.ui.map +4 -4
  36. package/docs/tutorials/ad_monetization.md +54 -0
  37. package/docs/tutorials/fairplay.md +2 -2
  38. package/docs/tutorials/index.json +1 -0
  39. package/docs/tutorials/plugins.md +4 -0
  40. package/docs/tutorials/text-displayer.md +15 -0
  41. package/docs/tutorials/ui-customization.md +29 -0
  42. package/docs/tutorials/ui.md +4 -1
  43. package/externs/lcevc.js +6 -0
  44. package/externs/shaka/manifest_parser.js +8 -0
  45. package/externs/shaka/player.js +1 -1
  46. package/externs/shaka/text.js +3 -1
  47. package/lib/abr/simple_abr_manager.js +1 -1
  48. package/lib/ads/client_side_ad.js +9 -0
  49. package/lib/ads/client_side_ad_manager.js +7 -2
  50. package/lib/ads/server_side_ad_manager.js +4 -0
  51. package/lib/cast/cast_proxy.js +18 -0
  52. package/lib/cea/cea608_memory.js +1 -1
  53. package/lib/cea/cea_decoder.js +11 -1
  54. package/lib/dash/dash_parser.js +75 -41
  55. package/lib/dash/mpd_utils.js +39 -27
  56. package/lib/hls/hls_parser.js +90 -27
  57. package/lib/hls/hls_utils.js +5 -5
  58. package/lib/media/content_workarounds.js +30 -0
  59. package/lib/media/drm_engine.js +16 -12
  60. package/lib/media/media_source_engine.js +4 -2
  61. package/lib/media/play_rate_controller.js +1 -0
  62. package/lib/media/presentation_timeline.js +11 -0
  63. package/lib/media/quality_observer.js +1 -1
  64. package/lib/media/segment_reference.js +5 -2
  65. package/lib/media/streaming_engine.js +50 -30
  66. package/lib/media/transmuxer.js +1 -1
  67. package/lib/media/video_wrapper.js +4 -3
  68. package/lib/offline/offline_manifest_parser.js +5 -0
  69. package/lib/offline/session_deleter.js +0 -8
  70. package/lib/offline/storage.js +4 -1
  71. package/lib/player.js +79 -18
  72. package/lib/polyfill/media_capabilities.js +23 -3
  73. package/lib/text/mp4_ttml_parser.js +2 -2
  74. package/lib/text/simple_text_displayer.js +7 -6
  75. package/lib/text/srt_text_parser.js +2 -2
  76. package/lib/text/text_engine.js +3 -2
  77. package/lib/text/ttml_text_parser.js +33 -5
  78. package/lib/text/ui_text_displayer.js +81 -15
  79. package/lib/text/vtt_text_parser.js +15 -2
  80. package/lib/util/buffer_utils.js +26 -4
  81. package/lib/util/cmcd_manager.js +10 -0
  82. package/lib/util/fairplay_utils.js +1 -1
  83. package/lib/util/manifest_parser_utils.js +14 -8
  84. package/lib/util/object_utils.js +1 -1
  85. package/lib/util/periods.js +91 -36
  86. package/lib/util/platform.js +39 -2
  87. package/lib/util/player_configuration.js +5 -3
  88. package/lib/util/stream_utils.js +36 -28
  89. package/lib/util/xml_utils.js +5 -0
  90. package/package.json +2 -2
  91. package/test/cea/cea_decoder_unit.js +40 -0
  92. package/test/dash/dash_parser_live_unit.js +5 -8
  93. package/test/hls/hls_parser_unit.js +50 -3
  94. package/test/media/buffering_observer_unit.js +13 -0
  95. package/test/media/content_workarounds_unit.js +1 -1
  96. package/test/media/drm_engine_unit.js +30 -3
  97. package/test/media/media_source_engine_unit.js +1 -1
  98. package/test/media/transmuxer_integration.js +2 -2
  99. package/test/offline/storage_integration.js +3 -0
  100. package/test/player_integration.js +40 -40
  101. package/test/player_unit.js +31 -0
  102. package/test/polyfill/media_capabilities_unit.js +12 -6
  103. package/test/test/util/fake_drm_engine.js +7 -0
  104. package/test/test/util/layout_tests.js +2 -1
  105. package/test/test/util/simple_fakes.js +2 -2
  106. package/test/test/util/test_scheme.js +3 -0
  107. package/test/test/util/waiter.js +5 -0
  108. package/test/text/mp4_ttml_parser_unit.js +4 -4
  109. package/test/text/simple_text_displayer_unit.js +1 -1
  110. package/test/text/srt_text_parser_unit.js +1 -1
  111. package/test/text/text_engine_unit.js +6 -0
  112. package/test/text/ttml_text_parser_unit.js +49 -18
  113. package/test/text/ui_text_displayer_unit.js +9 -0
  114. package/test/util/buffer_utils_unit.js +1 -1
  115. package/test/util/periods_unit.js +113 -0
  116. package/third_party/closure-uri/uri.js +1 -1
  117. package/ui/externs/ui.js +24 -0
  118. package/ui/language_utils.js +11 -5
  119. package/ui/play_button.js +20 -0
  120. package/ui/playback_rate_selection.js +4 -0
  121. package/ui/presentation_time.js +5 -5
  122. package/ui/small_play_button.js +2 -2
  123. package/ui/volume_bar.js +10 -6
  124. /package/.github/{config.yml → ISSUE_TEMPLATE/config.yml} +0 -0
@@ -638,8 +638,8 @@ describe('TtmlTextParser', () => {
638
638
  id: 'subtitleArea',
639
639
  viewportAnchorX: 50,
640
640
  viewportAnchorY: 16,
641
- width: 100,
642
- height: 100,
641
+ width: 50,
642
+ height: 84,
643
643
  },
644
644
  startTime: 62.05,
645
645
  endTime: 3723.2,
@@ -666,8 +666,8 @@ describe('TtmlTextParser', () => {
666
666
  region: {
667
667
  viewportAnchorX: 50,
668
668
  viewportAnchorY: 16,
669
- width: 100,
670
- height: 100,
669
+ width: 50,
670
+ height: 84,
671
671
  },
672
672
  startTime: 62.05,
673
673
  endTime: 3723.2,
@@ -694,8 +694,8 @@ describe('TtmlTextParser', () => {
694
694
  region: {
695
695
  viewportAnchorX: 50,
696
696
  viewportAnchorY: 16,
697
- width: 100,
698
- height: 100,
697
+ width: 50,
698
+ height: 84,
699
699
  },
700
700
  startTime: 62.05,
701
701
  endTime: 3723.2,
@@ -872,8 +872,8 @@ describe('TtmlTextParser', () => {
872
872
  viewportAnchorY: 16,
873
873
  regionAnchorX: 0,
874
874
  regionAnchorY: 0,
875
- width: 100,
876
- height: 100,
875
+ width: 50,
876
+ height: 84,
877
877
  heightUnits: CueRegion.units.PERCENTAGE,
878
878
  widthUnits: CueRegion.units.PERCENTAGE,
879
879
  viewportAnchorUnits: CueRegion.units.PERCENTAGE,
@@ -945,8 +945,8 @@ describe('TtmlTextParser', () => {
945
945
  id: 'subtitleArea',
946
946
  viewportAnchorX: 50,
947
947
  viewportAnchorY: 16,
948
- width: 100,
949
- height: 100,
948
+ width: 50,
949
+ height: 84,
950
950
  },
951
951
  startTime: 62.05,
952
952
  endTime: 3723.2,
@@ -973,8 +973,8 @@ describe('TtmlTextParser', () => {
973
973
  region: {
974
974
  viewportAnchorX: 50,
975
975
  viewportAnchorY: 16,
976
- width: 100,
977
- height: 100,
976
+ width: 50,
977
+ height: 84,
978
978
  },
979
979
  startTime: 62.05,
980
980
  endTime: 3723.2,
@@ -1001,8 +1001,8 @@ describe('TtmlTextParser', () => {
1001
1001
  region: {
1002
1002
  viewportAnchorX: 50,
1003
1003
  viewportAnchorY: 16,
1004
- width: 100,
1005
- height: 100,
1004
+ width: 50,
1005
+ height: 84,
1006
1006
  },
1007
1007
  startTime: 62.05,
1008
1008
  endTime: 3723.2,
@@ -1032,8 +1032,8 @@ describe('TtmlTextParser', () => {
1032
1032
  id: 'subtitleArea',
1033
1033
  viewportAnchorX: 12.2,
1034
1034
  viewportAnchorY: 50.005,
1035
- width: 100,
1036
- height: 100,
1035
+ width: 87.8,
1036
+ height: 49.995,
1037
1037
  },
1038
1038
  startTime: 62.05,
1039
1039
  endTime: 3723.2,
@@ -1238,6 +1238,35 @@ describe('TtmlTextParser', () => {
1238
1238
  });
1239
1239
  });
1240
1240
 
1241
+ it('supports smpte:backgroundImage attribute with url', () => {
1242
+ verifyHelper(
1243
+ [
1244
+ {
1245
+ startTime: 62.05,
1246
+ endTime: 3723.2,
1247
+ payload: '',
1248
+ },
1249
+ ],
1250
+ '<tt ' +
1251
+ 'xmlns:ttm="http://www.w3.org/ns/ttml#metadata" ' +
1252
+ 'xmlns:smpte="http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt">' +
1253
+ '<metadata>' +
1254
+ '<smpte:image imageType="PNG" encoding="Base64" xml:id="img_0">' +
1255
+ 'base64EncodedImage</smpte:image>' +
1256
+ '</metadata>' +
1257
+ '<body><div smpte:backgroundImage="img_0.png">' +
1258
+ '<p begin="01:02.05" end="01:02:03.200"></p>' +
1259
+ '</div></body></tt>',
1260
+ {periodStart: 0, segmentStart: 60, segmentEnd: 3730, vttOffset: 0},
1261
+ {startTime: 62.05, endTime: 3723.2},
1262
+ {
1263
+ startTime: 62.05,
1264
+ endTime: 3723.2,
1265
+ backgroundImage: 'foo://bar/img_0.png',
1266
+ isContainer: false,
1267
+ });
1268
+ });
1269
+
1241
1270
  it('supports smpte:backgroundImage attribute in div element', () => {
1242
1271
  verifyHelper(
1243
1272
  [],
@@ -2060,7 +2089,8 @@ describe('TtmlTextParser', () => {
2060
2089
  function verifyHelper(cues, text, time, bodyProperties, divProperties) {
2061
2090
  const data =
2062
2091
  shaka.util.BufferUtils.toUint8(shaka.util.StringUtils.toUTF8(text));
2063
- const result = new shaka.text.TtmlTextParser().parseMedia(data, time);
2092
+ const result = new shaka.text.TtmlTextParser()
2093
+ .parseMedia(data, time, 'foo://bar');
2064
2094
  shaka.test.TtmlUtils.verifyHelper(
2065
2095
  cues, result, bodyProperties, divProperties);
2066
2096
  }
@@ -2087,7 +2117,8 @@ describe('TtmlTextParser', () => {
2087
2117
  expect(() => {
2088
2118
  new shaka.text.TtmlTextParser().parseMedia(
2089
2119
  shaka.util.BufferUtils.toUint8(data),
2090
- {periodStart: 0, segmentStart: 0, segmentEnd: 10, vttOffset: 0});
2120
+ {periodStart: 0, segmentStart: 0, segmentEnd: 10, vttOffset: 0},
2121
+ 'foo://bar');
2091
2122
  }).toThrow(error);
2092
2123
  }
2093
2124
  });
@@ -605,4 +605,13 @@ describe('UITextDisplayer', () => {
605
605
  (e) => e.nodeType == Node.ELEMENT_NODE);
606
606
  expect(childrenOfTwo.length).toBe(3);
607
607
  });
608
+
609
+ it('textDisplayer does not crash if destroy is called more than once', () => {
610
+ expect(videoContainer.childNodes.length).toBe(1);
611
+
612
+ textDisplayer.destroy();
613
+ textDisplayer.destroy();
614
+
615
+ expect(videoContainer.childNodes.length).toBe(0);
616
+ });
608
617
  });
@@ -42,7 +42,7 @@ describe('BufferUtils', () => {
42
42
 
43
43
  it('compares with same buffer', () => {
44
44
  const a = new Uint8Array([0, 1, 2, 3]);
45
- const b = new Uint16Array(a.buffer);
45
+ const b = BufferUtils.toUint16(a);
46
46
  expect(BufferUtils.equal(a, b)).toBe(true);
47
47
  });
48
48
 
@@ -598,6 +598,55 @@ describe('PeriodCombiner', () => {
598
598
  }
599
599
  });
600
600
 
601
+ // Regression test for #6054, where we failed on multi-period content with
602
+ // different numbers of forced-subtitle streams per period.
603
+ it('Does not combine subtitle and forced-subtitle tracks', async () => {
604
+ const videoStreams = [makeVideoStream(1280)];
605
+ const audioStreams = [makeAudioStream('en', /* channels= */ 2)];
606
+ const imageStreams = [];
607
+
608
+ const forcedSubtitle = makeTextStream('de');
609
+ forcedSubtitle.roles = ['forced-subtitle'];
610
+ forcedSubtitle.forced = true;
611
+
612
+ const subtitle = makeTextStream('de');
613
+ subtitle.roles = ['subtitle'];
614
+
615
+ /** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
616
+ const periods = [
617
+ {
618
+ id: '1',
619
+ videoStreams,
620
+ audioStreams,
621
+ textStreams: [
622
+ forcedSubtitle,
623
+ subtitle,
624
+ ],
625
+ imageStreams,
626
+ },
627
+ {
628
+ id: '2',
629
+ videoStreams,
630
+ audioStreams,
631
+ textStreams: [
632
+ subtitle,
633
+ ],
634
+ imageStreams,
635
+ },
636
+ {
637
+ id: '3',
638
+ videoStreams,
639
+ audioStreams,
640
+ textStreams: [],
641
+ imageStreams,
642
+ },
643
+ ];
644
+
645
+ await combiner.combinePeriods(periods, /* isDynamic= */ true);
646
+ const textStreams = combiner.getTextStreams();
647
+ expect(textStreams.every((s) => s.roles.length === 1)).toBe(true);
648
+ });
649
+
601
650
  // Regression test for #3383, where we failed on multi-period content with
602
651
  // multiple image streams per period.
603
652
  it('Can handle multiple image streams', async () => {
@@ -1004,6 +1053,70 @@ describe('PeriodCombiner', () => {
1004
1053
  expect(audio2.originalId).toBe('2,4');
1005
1054
  });
1006
1055
 
1056
+ it('Matches streams with labels', async () => {
1057
+ const stream1 = makeAudioStream('en', /* channels= */ 2);
1058
+ stream1.originalId = '1';
1059
+ stream1.bandwidth = 129597;
1060
+ stream1.codecs = 'mp4a.40.2';
1061
+
1062
+ const stream2 = makeAudioStream('en', /* channels= */ 2);
1063
+ stream2.originalId = '2';
1064
+ stream2.bandwidth = 129637;
1065
+ stream2.codecs = 'mp4a.40.2';
1066
+ stream2.label = 'description';
1067
+
1068
+ const stream3 = makeAudioStream('en', /* channels= */ 2);
1069
+ stream3.originalId = '3';
1070
+ stream3.bandwidth = 131037;
1071
+ stream3.codecs = 'mp4a.40.2';
1072
+
1073
+ const stream4 = makeAudioStream('en', /* channels= */ 2);
1074
+ stream4.originalId = '4';
1075
+ stream4.bandwidth = 131034;
1076
+ stream4.codecs = 'mp4a.40.2';
1077
+ stream4.label = 'description';
1078
+
1079
+ /** @type {!Array.<shaka.util.PeriodCombiner.Period>} */
1080
+ const periods = [
1081
+ {
1082
+ id: '0',
1083
+ videoStreams: [
1084
+ makeVideoStream(1080),
1085
+ ],
1086
+ audioStreams: [
1087
+ stream1,
1088
+ stream2,
1089
+ ],
1090
+ textStreams: [],
1091
+ imageStreams: [],
1092
+ },
1093
+ {
1094
+ id: '1',
1095
+ videoStreams: [
1096
+ makeVideoStream(1080),
1097
+ ],
1098
+ audioStreams: [
1099
+ stream3,
1100
+ stream4,
1101
+ ],
1102
+ textStreams: [],
1103
+ imageStreams: [],
1104
+ },
1105
+ ];
1106
+
1107
+ await combiner.combinePeriods(periods, /* isDynamic= */ true);
1108
+ const variants = combiner.getVariants();
1109
+ expect(variants.length).toBe(2);
1110
+ // We can use the originalId field to see what each track is composed of.
1111
+ const audio1 = variants[0].audio;
1112
+ expect(audio1.label).toBe(null);
1113
+ expect(audio1.originalId).toBe('1,3');
1114
+
1115
+ const audio2 = variants[1].audio;
1116
+ expect(audio2.label).toBe('description');
1117
+ expect(audio2.originalId).toBe('2,4');
1118
+ });
1119
+
1007
1120
  it('Matches streams with related codecs', async () => {
1008
1121
  const stream1 = makeVideoStream(1080);
1009
1122
  stream1.originalId = '1';
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Use new goog.Uri(string) to parse a URI string.
10
10
  *
11
- * e.g: <code>var myUri = new goog.Uri(window.location);</code>
11
+ * e.g.: <code>var myUri = new goog.Uri(window.location);</code>
12
12
  *
13
13
  * Implements RFC 3986 for parsing/formatting URIs.
14
14
  * http://www.ietf.org/rfc/rfc3986.txt
package/ui/externs/ui.js CHANGED
@@ -443,3 +443,27 @@ shaka.extern.IUISeekBar.Factory = class {
443
443
  */
444
444
  create(rootElement, controls) {}
445
445
  };
446
+
447
+ /**
448
+ * @interface
449
+ * @exportDoc
450
+ */
451
+ shaka.extern.IUIPlayButton = class {
452
+ /**
453
+ * @param {!HTMLElement} parent
454
+ * @param {!shaka.ui.Controls} controls
455
+ */
456
+ constructor(parent, controls) {
457
+ /**
458
+ * @protected {!HTMLButtonElement}
459
+ * @exportDoc
460
+ */
461
+ this.button;
462
+ }
463
+
464
+ /** @return {boolean} */
465
+ isPaused() {}
466
+
467
+ /** @return {boolean} */
468
+ isEnded() {}
469
+ };
@@ -55,7 +55,11 @@ shaka.ui.LanguageUtils = class {
55
55
  }
56
56
  };
57
57
 
58
- const getCombination = (language, rolesString) => {
58
+ const getCombination = (language, rolesString, label) => {
59
+ if (label &&
60
+ trackLabelFormat == shaka.ui.Overlay.TrackLabelFormat.LABEL) {
61
+ return language + ': ' + label + ': ' + rolesString;
62
+ }
59
63
  return language + ': ' + rolesString;
60
64
  };
61
65
 
@@ -72,7 +76,8 @@ shaka.ui.LanguageUtils = class {
72
76
  /** @type {!Set.<string>} */
73
77
  const combinationsMade = new Set();
74
78
  const selectedCombination = selectedTrack ? getCombination(
75
- selectedTrack.language, getRolesString(selectedTrack)) : '';
79
+ selectedTrack.language, getRolesString(selectedTrack),
80
+ selectedTrack.label) : '';
76
81
 
77
82
  for (const track of tracks) {
78
83
  const language = track.language;
@@ -80,7 +85,8 @@ shaka.ui.LanguageUtils = class {
80
85
  const LocIds = shaka.ui.Locales.Ids;
81
86
  const forcedString = localization.resolve(LocIds.SUBTITLE_FORCED);
82
87
  const rolesString = getRolesString(track);
83
- const combinationName = getCombination(language, rolesString);
88
+ const label = track.label;
89
+ const combinationName = getCombination(language, rolesString, label);
84
90
  if (combinationsMade.has(combinationName)) {
85
91
  continue;
86
92
  }
@@ -124,8 +130,8 @@ shaka.ui.LanguageUtils = class {
124
130
  }
125
131
  break;
126
132
  case shaka.ui.Overlay.TrackLabelFormat.LABEL:
127
- if (track.label) {
128
- span.textContent = track.label;
133
+ if (label) {
134
+ span.textContent = label;
129
135
  } else {
130
136
  // Fallback behavior. This probably shouldn't happen.
131
137
  shaka.log.alwaysWarn('Track #' + track.id + ' does not have a ' +
package/ui/play_button.js CHANGED
@@ -16,6 +16,7 @@ goog.requireType('shaka.ui.Controls');
16
16
 
17
17
  /**
18
18
  * @extends {shaka.ui.Element}
19
+ * @implements {shaka.extern.IUIPlayButton}
19
20
  * @export
20
21
  */
21
22
  shaka.ui.PlayButton = class extends shaka.ui.Element {
@@ -72,6 +73,11 @@ shaka.ui.PlayButton = class extends shaka.ui.Element {
72
73
  this.updateIcon();
73
74
  });
74
75
 
76
+ this.eventManager.listen(this.adManager, AdManager.AD_STOPPED, () => {
77
+ this.updateAriaLabel();
78
+ this.updateIcon();
79
+ });
80
+
75
81
  this.eventManager.listen(this.button, 'click', () => {
76
82
  if (this.ad && this.ad.isLinear()) {
77
83
  this.controls.playPauseAd();
@@ -90,6 +96,7 @@ shaka.ui.PlayButton = class extends shaka.ui.Element {
90
96
  /**
91
97
  * @return {boolean}
92
98
  * @protected
99
+ * @override
93
100
  */
94
101
  isPaused() {
95
102
  if (this.ad && this.ad.isLinear()) {
@@ -99,6 +106,19 @@ shaka.ui.PlayButton = class extends shaka.ui.Element {
99
106
  return this.controls.presentationIsPaused();
100
107
  }
101
108
 
109
+ /**
110
+ * @return {boolean}
111
+ * @protected
112
+ * @override
113
+ */
114
+ isEnded() {
115
+ if (this.ad && this.ad.isLinear()) {
116
+ return false;
117
+ }
118
+
119
+ return this.video.ended;
120
+ }
121
+
102
122
  /**
103
123
  * Called when the button's aria label needs to change.
104
124
  * To be overridden by subclasses.
@@ -44,6 +44,10 @@ shaka.ui.PlaybackRateSelection = class extends shaka.ui.SettingsMenu {
44
44
  this.updateLocalizedStrings_();
45
45
  });
46
46
 
47
+ this.eventManager.listen(this.player, 'loaded', () => {
48
+ this.updatePlaybackRateSelection_(this.player.getPlaybackRate());
49
+ });
50
+
47
51
  this.eventManager.listen(this.player, 'ratechange', () => {
48
52
  this.updatePlaybackRateSelection_(this.player.getPlaybackRate());
49
53
  });
@@ -64,7 +64,6 @@ shaka.ui.PresentationTimeTracker = class extends shaka.ui.Element {
64
64
  updateTime_() {
65
65
  const isSeeking = this.controls.isSeeking();
66
66
  let displayTime = this.controls.getDisplayTime();
67
- const duration = this.video.duration;
68
67
  const seekRange = this.player.seekRange();
69
68
  const seekRangeSize = seekRange.end - seekRange.start;
70
69
  const Utils = shaka.ui.Utils;
@@ -89,11 +88,12 @@ shaka.ui.PresentationTimeTracker = class extends shaka.ui.Element {
89
88
  this.currentTime_.disabled = true;
90
89
  }
91
90
  } else {
92
- const showHour = duration >= 3600;
91
+ const showHour = seekRangeSize >= 3600;
93
92
 
94
- let value = Utils.buildTimeString(displayTime, showHour);
95
- if (duration) {
96
- value += ' / ' + Utils.buildTimeString(duration, showHour);
93
+ const currentTime = Math.max(0, displayTime - seekRange.start);
94
+ let value = Utils.buildTimeString(currentTime, showHour);
95
+ if (seekRangeSize) {
96
+ value += ' / ' + Utils.buildTimeString(seekRangeSize, showHour);
97
97
  }
98
98
  this.setValue_(value);
99
99
  this.currentTime_.disabled = true;
@@ -38,7 +38,7 @@ shaka.ui.SmallPlayButton = class extends shaka.ui.PlayButton {
38
38
  /** @override */
39
39
  updateIcon() {
40
40
  const Icons = shaka.ui.Enums.MaterialDesignIcons;
41
- if (this.video.ended) {
41
+ if (this.isEnded()) {
42
42
  this.button.textContent = Icons.REPLAY;
43
43
  } else {
44
44
  this.button.textContent = this.isPaused() ? Icons.PLAY : Icons.PAUSE;
@@ -48,7 +48,7 @@ shaka.ui.SmallPlayButton = class extends shaka.ui.PlayButton {
48
48
  /** @override */
49
49
  updateAriaLabel() {
50
50
  const LocIds = shaka.ui.Locales.Ids;
51
- if (this.video.ended) {
51
+ if (this.isEnded()) {
52
52
  this.button.ariaLabel = this.localization.resolve(LocIds.REPLAY);
53
53
  } else {
54
54
  const label = this.isPaused() ? LocIds.PLAY : LocIds.PAUSE;
package/ui/volume_bar.js CHANGED
@@ -32,6 +32,10 @@ shaka.ui.VolumeBar = class extends shaka.ui.RangeElement {
32
32
  /** @private {!shaka.extern.UIConfiguration} */
33
33
  this.config_ = this.controls.getConfig();
34
34
 
35
+ // We use a range of 100 to avoid problems with Firefox.
36
+ // See https://github.com/shaka-project/shaka-player/issues/3987
37
+ this.setRange(0, 100);
38
+
35
39
  this.eventManager.listen(this.video,
36
40
  'volumechange',
37
41
  () => this.onPresentationVolumeChange_());
@@ -74,9 +78,9 @@ shaka.ui.VolumeBar = class extends shaka.ui.RangeElement {
74
78
  */
75
79
  onChange() {
76
80
  if (this.ad && this.ad.isLinear()) {
77
- this.ad.setVolume(this.getValue());
81
+ this.ad.setVolume(this.getValue() / 100);
78
82
  } else {
79
- this.video.volume = this.getValue();
83
+ this.video.volume = this.getValue() / 100;
80
84
  if (this.video.volume == 0) {
81
85
  this.video.muted = true;
82
86
  } else {
@@ -90,7 +94,7 @@ shaka.ui.VolumeBar = class extends shaka.ui.RangeElement {
90
94
  if (this.video.muted) {
91
95
  this.setValue(0);
92
96
  } else {
93
- this.setValue(this.video.volume);
97
+ this.setValue(this.video.volume * 100);
94
98
  }
95
99
 
96
100
  this.updateColors_();
@@ -102,7 +106,7 @@ shaka.ui.VolumeBar = class extends shaka.ui.RangeElement {
102
106
  'This.ad should exist at this point!');
103
107
 
104
108
  const volume = this.ad.getVolume();
105
- this.setValue(volume);
109
+ this.setValue(volume * 100);
106
110
  this.updateColors_();
107
111
  }
108
112
 
@@ -110,8 +114,8 @@ shaka.ui.VolumeBar = class extends shaka.ui.RangeElement {
110
114
  updateColors_() {
111
115
  const colors = this.config_.volumeBarColors;
112
116
  const gradient = ['to right'];
113
- gradient.push(colors.level + (this.getValue() * 100) + '%');
114
- gradient.push(colors.base + (this.getValue() * 100) + '%');
117
+ gradient.push(colors.level + this.getValue() + '%');
118
+ gradient.push(colors.base + this.getValue() + '%');
115
119
  gradient.push(colors.base + '100%');
116
120
 
117
121
  this.container.style.background =