shaka-player 4.10.13 → 4.11.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.
Files changed (91) hide show
  1. package/README.md +5 -1
  2. package/dist/controls.css +3 -3
  3. package/dist/controls.css.map +1 -1
  4. package/dist/deps.js +11 -11
  5. package/dist/locales.js +38 -34
  6. package/dist/shaka-player.compiled.d.ts +68 -33
  7. package/dist/shaka-player.compiled.debug.d.ts +68 -33
  8. package/dist/shaka-player.compiled.debug.externs.js +584 -544
  9. package/dist/shaka-player.compiled.debug.js +715 -676
  10. package/dist/shaka-player.compiled.debug.map +4 -4
  11. package/dist/shaka-player.compiled.externs.js +584 -544
  12. package/dist/shaka-player.compiled.js +1394 -1371
  13. package/dist/shaka-player.compiled.map +4 -4
  14. package/dist/shaka-player.ui.d.ts +76 -34
  15. package/dist/shaka-player.ui.debug.d.ts +76 -34
  16. package/dist/shaka-player.ui.debug.externs.js +77 -26
  17. package/dist/shaka-player.ui.debug.js +774 -720
  18. package/dist/shaka-player.ui.debug.map +4 -4
  19. package/dist/shaka-player.ui.externs.js +77 -26
  20. package/dist/shaka-player.ui.js +1649 -1617
  21. package/dist/shaka-player.ui.map +4 -4
  22. package/externs/mediasession.js +10 -1
  23. package/externs/shaka/ads.js +79 -2
  24. package/externs/shaka/manifest.js +6 -1
  25. package/externs/shaka/manifest_parser.js +6 -2
  26. package/externs/shaka/net.js +23 -2
  27. package/externs/shaka/player.js +414 -103
  28. package/externs/shaka/text.js +2 -1
  29. package/lib/abr/simple_abr_manager.js +5 -0
  30. package/lib/ads/ad_manager.js +45 -1
  31. package/lib/ads/ad_utils.js +124 -1
  32. package/lib/ads/interstitial_ad.js +9 -2
  33. package/lib/ads/interstitial_ad_manager.js +216 -101
  34. package/lib/ads/media_tailor_ad.js +1 -39
  35. package/lib/ads/media_tailor_ad_manager.js +32 -16
  36. package/lib/cea/cea608_data_channel.js +40 -8
  37. package/lib/cea/cea608_memory.js +28 -0
  38. package/lib/dash/dash_parser.js +162 -25
  39. package/lib/dash/segment_base.js +3 -2
  40. package/lib/dash/segment_list.js +6 -3
  41. package/lib/dash/segment_template.js +104 -20
  42. package/lib/hls/hls_parser.js +319 -215
  43. package/lib/media/drm_engine.js +11 -3
  44. package/lib/media/media_source_engine.js +0 -8
  45. package/lib/media/preload_manager.js +16 -4
  46. package/lib/media/segment_reference.js +3 -0
  47. package/lib/media/segment_utils.js +8 -1
  48. package/lib/media/streaming_engine.js +10 -6
  49. package/lib/mss/mss_parser.js +14 -2
  50. package/lib/net/http_fetch_plugin.js +12 -5
  51. package/lib/net/http_xhr_plugin.js +7 -3
  52. package/lib/net/networking_engine.js +21 -1
  53. package/lib/offline/manifest_converter.js +1 -0
  54. package/lib/offline/storage.js +1 -0
  55. package/lib/player.js +336 -121
  56. package/lib/text/cue.js +373 -0
  57. package/lib/text/mp4_ttml_parser.js +51 -1
  58. package/lib/text/mp4_vtt_parser.js +2 -5
  59. package/lib/text/sbv_text_parser.js +2 -31
  60. package/lib/text/srt_text_parser.js +1 -1
  61. package/lib/text/text_engine.js +1 -1
  62. package/lib/text/text_utils.js +1 -0
  63. package/lib/text/ttml_text_parser.js +14 -4
  64. package/lib/text/ui_text_displayer.js +0 -4
  65. package/lib/text/vtt_text_parser.js +10 -415
  66. package/lib/util/abortable_operation.js +8 -0
  67. package/lib/util/cmcd_manager.js +38 -0
  68. package/lib/util/error.js +16 -0
  69. package/lib/util/fake_event.js +1 -0
  70. package/lib/util/manifest_parser_utils.js +12 -3
  71. package/lib/util/player_configuration.js +4 -2
  72. package/lib/util/stream_utils.js +57 -2
  73. package/lib/util/tXml.js +2 -1
  74. package/lib/util/text_parser.js +46 -0
  75. package/lib/util/ts_parser.js +32 -3
  76. package/package.json +1 -1
  77. package/shaka-player.uncompiled.js +1 -1
  78. package/ui/chapter_selection.js +179 -0
  79. package/ui/controls.js +151 -0
  80. package/ui/enums.js +1 -0
  81. package/ui/externs/ui.js +7 -1
  82. package/ui/less/containers.less +3 -4
  83. package/ui/less/overflow_menu.less +5 -0
  84. package/ui/less/tooltip.less +1 -1
  85. package/ui/locales/en.json +1 -0
  86. package/ui/locales/es.json +1 -0
  87. package/ui/locales/fr.json +1 -0
  88. package/ui/locales/source.json +4 -0
  89. package/ui/seek_bar.js +3 -2
  90. package/ui/ui.js +2 -0
  91. package/lib/polyfill/abort_controller.js +0 -158
@@ -32,6 +32,7 @@ shaka.util.PlayerConfiguration = class {
32
32
  // This is a relatively safe default in the absence of clues from the
33
33
  // browser. For slower connections, the default estimate may be too high.
34
34
  const bandwidthEstimate = 1e6; // 1Mbps
35
+ const minBytes = 16e3;
35
36
 
36
37
  let abrMaxHeight = Infinity;
37
38
 
@@ -162,7 +163,6 @@ shaka.util.PlayerConfiguration = class {
162
163
  ignoreManifestProgramDateTimeForTypes: [],
163
164
  mediaPlaylistFullMimeType:
164
165
  'video/mp2t; codecs="avc1.42E01E, mp4a.40.2"',
165
- useSafariBehaviorForLive: true,
166
166
  liveSegmentsDelay: 3,
167
167
  sequenceMode: shaka.util.Platform.supportsSequenceMode(),
168
168
  ignoreManifestTimestampsInSegmentsMode: false,
@@ -225,6 +225,7 @@ shaka.util.PlayerConfiguration = class {
225
225
  autoLowLatencyMode: false,
226
226
  forceHTTP: false,
227
227
  forceHTTPS: false,
228
+ minBytesForProgressEvents: minBytes,
228
229
  preferNativeHls: false,
229
230
  updateIntervalSeconds: 1,
230
231
  dispatchAllEmsgBoxes: false,
@@ -341,7 +342,7 @@ shaka.util.PlayerConfiguration = class {
341
342
  },
342
343
  advanced: {
343
344
  minTotalBytes: 128e3,
344
- minBytes: 16e3,
345
+ minBytes,
345
346
  fastHalfLife: 2,
346
347
  slowHalfLife: 5,
347
348
  },
@@ -352,6 +353,7 @@ shaka.util.PlayerConfiguration = class {
352
353
  safeMarginSwitch: 0,
353
354
  cacheLoadThreshold: 20,
354
355
  minTimeToSwitch: shaka.util.Platform.isApple() ? 0.5 : 0,
356
+ preferNetworkInformationBandwidth: false,
355
357
  };
356
358
 
357
359
  const cmcd = {
@@ -118,9 +118,33 @@ shaka.util.StreamUtils = class {
118
118
  validAudioStreamsMap.set(groupId, validAudioStreams);
119
119
  }
120
120
 
121
+ // Keys based in MimeUtils.getNormalizedCodec. Lower is better
122
+ const videoCodecPreference = {
123
+ 'vp8': 1,
124
+ 'avc': 1,
125
+ 'dovi-avc': 0.95,
126
+ 'vp9': 0.9,
127
+ 'vp09': 0.9,
128
+ 'hevc': 0.85,
129
+ 'dovi-hevc': 0.8,
130
+ 'av01': 0.75,
131
+ 'dovi-av1': 0.7,
132
+ 'vvc': 0.65,
133
+ };
134
+
121
135
  const videoStreams = Array.from(videoStreamsSet)
122
136
  .sort((v1, v2) => {
123
- if (!v1.bandwidth || !v2.bandwidth) {
137
+ if (!v1.bandwidth || !v2.bandwidth || v1.bandwidth == v2.bandwidth) {
138
+ if (v1.codecs && v2.codecs && v1.codecs != v2.codecs &&
139
+ v1.width == v2.width) {
140
+ const v1Codecs = MimeUtils.getNormalizedCodec(v1.codecs);
141
+ const v2Codecs = MimeUtils.getNormalizedCodec(v2.codecs);
142
+ if (v1Codecs != v2Codecs) {
143
+ const indexV1 = videoCodecPreference[v1Codecs] || 1;
144
+ const indexV2 = videoCodecPreference[v2Codecs] || 1;
145
+ return indexV1 - indexV2;
146
+ }
147
+ }
124
148
  return v1.width - v2.width;
125
149
  }
126
150
  return v1.bandwidth - v2.bandwidth;
@@ -1480,7 +1504,7 @@ shaka.util.StreamUtils = class {
1480
1504
  // in DASH this information comes at the stream level and not at the
1481
1505
  // segment level.
1482
1506
  if (stream.segmentIndex) {
1483
- reference = stream.segmentIndex.get(0);
1507
+ reference = stream.segmentIndex.earliestReference();
1484
1508
  }
1485
1509
  let layout = stream.tilesLayout;
1486
1510
  if (reference) {
@@ -1860,6 +1884,37 @@ shaka.util.StreamUtils = class {
1860
1884
  }
1861
1885
 
1862
1886
 
1887
+ /**
1888
+ * Set the best iframe stream to the original stream.
1889
+ *
1890
+ * @param {!shaka.extern.Stream} stream
1891
+ * @param {!Array.<!shaka.extern.Stream>} iFrameStreams
1892
+ */
1893
+ static setBetterIFrameStream(stream, iFrameStreams) {
1894
+ if (!iFrameStreams.length) {
1895
+ return;
1896
+ }
1897
+ const validStreams = iFrameStreams.filter((iFrameStream) =>
1898
+ shaka.util.MimeUtils.getNormalizedCodec(stream.codecs) ==
1899
+ shaka.util.MimeUtils.getNormalizedCodec(iFrameStream.codecs))
1900
+ .sort((a, b) => {
1901
+ if (!a.bandwidth || !b.bandwidth || a.bandwidth == b.bandwidth) {
1902
+ return (a.width || 0) - (b.width || 0);
1903
+ }
1904
+ return a.bandwidth - b.bandwidth;
1905
+ });
1906
+ stream.trickModeVideo = validStreams[0];
1907
+ if (validStreams.length > 1) {
1908
+ const sameResolutionStream = validStreams.find((iFrameStream) =>
1909
+ stream.width == iFrameStream.width &&
1910
+ stream.height == iFrameStream.height);
1911
+ if (sameResolutionStream) {
1912
+ stream.trickModeVideo = sameResolutionStream;
1913
+ }
1914
+ }
1915
+ }
1916
+
1917
+
1863
1918
  /**
1864
1919
  * Returns a string of a variant, with the attribute values of its audio
1865
1920
  * and/or video streams for log printing.
package/lib/util/tXml.js CHANGED
@@ -62,7 +62,8 @@ shaka.util.TXml = class {
62
62
  if (!expectedRootElemName && result.length) {
63
63
  return result[0];
64
64
  }
65
- const rootNode = result.find((n) => n.tagName === expectedRootElemName);
65
+ const rootNode = result.find((n) =>
66
+ expectedRootElemName.split(',').includes(n.tagName));
66
67
  if (rootNode) {
67
68
  return rootNode;
68
69
  }
@@ -131,4 +131,50 @@ shaka.util.TextParser = class {
131
131
  };
132
132
  }
133
133
  }
134
+
135
+ /**
136
+ * Parses a time.
137
+ *
138
+ * @return {?number}
139
+ */
140
+ parseTime() {
141
+ const results = this.readRegex(shaka.util.TextParser.timeFormat_);
142
+ if (results == null) {
143
+ return null;
144
+ }
145
+ // This capture is optional, but will still be in the array as undefined,
146
+ // in which case it is 0.
147
+ const hours = Number(results[1]) || 0;
148
+ const minutes = Number(results[2]);
149
+ const seconds = Number(results[3]);
150
+ const milliseconds = Number(results[6]) || 0;
151
+ if (minutes > 59 || seconds > 59) {
152
+ return null;
153
+ }
154
+
155
+ return (milliseconds / 1000) + seconds + (minutes * 60) + (hours * 3600);
156
+ }
157
+
158
+ /**
159
+ * Parses a time from string.
160
+ *
161
+ * @param {?string} time
162
+ * @return {?number}
163
+ */
164
+ static parseTime(time) {
165
+ if (!time) {
166
+ return null;
167
+ }
168
+ const parser = new shaka.util.TextParser(time);
169
+ return parser.parseTime();
170
+ }
134
171
  };
172
+
173
+ /**
174
+ * @const
175
+ * @private {!RegExp}
176
+ * @example 00:00.000 or 00:00:00.000 or 0:00:00.000 or
177
+ * 00:00.00 or 00:00:00.00 or 0:00:00.00 or 00:00:00
178
+ */
179
+ shaka.util.TextParser.timeFormat_ =
180
+ /(?:(\d{1,}):)?(\d{2}):(\d{2})((\.(\d{1,3})))?/g;
@@ -6,6 +6,7 @@
6
6
 
7
7
  goog.provide('shaka.util.TsParser');
8
8
 
9
+ goog.require('goog.asserts');
9
10
  goog.require('shaka.Deprecate');
10
11
  goog.require('shaka.log');
11
12
  goog.require('shaka.util.ExpGolomb');
@@ -862,7 +863,8 @@ shaka.util.TsParser = class {
862
863
  /**
863
864
  * Return the video information
864
865
  *
865
- * @return {{height: ?string, width: ?string, codec: ?string}}
866
+ * @return {{height: ?string, width: ?string, codec: ?string,
867
+ * frameRate: ?string}}
866
868
  * @export
867
869
  */
868
870
  getVideoInfo() {
@@ -872,10 +874,32 @@ shaka.util.TsParser = class {
872
874
  return this.getAvcInfo_();
873
875
  }
874
876
 
877
+ /**
878
+ * @return {?string}
879
+ * @private
880
+ */
881
+ getFrameRate_() {
882
+ const timescale = shaka.util.TsParser.Timescale;
883
+ const videoData = this.getVideoData();
884
+ if (videoData.length > 1) {
885
+ const firstPts = videoData[0].pts;
886
+ goog.asserts.assert(typeof(firstPts) == 'number',
887
+ 'Should be an number!');
888
+ const secondPts = videoData[1].pts;
889
+ goog.asserts.assert(typeof(secondPts) == 'number',
890
+ 'Should be an number!');
891
+ if (!isNaN(secondPts - firstPts)) {
892
+ return String(1 / (secondPts - firstPts) * timescale);
893
+ }
894
+ }
895
+ return null;
896
+ }
897
+
875
898
  /**
876
899
  * Return the video information for AVC
877
900
  *
878
- * @return {{height: ?string, width: ?string, codec: ?string}}
901
+ * @return {{height: ?string, width: ?string, codec: ?string,
902
+ * frameRate: ?string}}
879
903
  * @private
880
904
  */
881
905
  getAvcInfo_() {
@@ -884,6 +908,7 @@ shaka.util.TsParser = class {
884
908
  height: null,
885
909
  width: null,
886
910
  codec: null,
911
+ frameRate: null,
887
912
  };
888
913
  const videoNalus = this.getVideoNalus();
889
914
  if (!videoNalus.length) {
@@ -995,6 +1020,7 @@ shaka.util.TsParser = class {
995
1020
  frameCropLeftOffset * 2 - frameCropRightOffset * 2);
996
1021
  videoInfo.codec = 'avc1.' + this.byteToHex_(profileIdc) +
997
1022
  this.byteToHex_(profileCompatibility) + this.byteToHex_(levelIdc);
1023
+ videoInfo.frameRate = this.getFrameRate_();
998
1024
 
999
1025
  return videoInfo;
1000
1026
  }
@@ -1002,7 +1028,8 @@ shaka.util.TsParser = class {
1002
1028
  /**
1003
1029
  * Return the video information for HVC
1004
1030
  *
1005
- * @return {{height: ?string, width: ?string, codec: ?string}}
1031
+ * @return {{height: ?string, width: ?string, codec: ?string,
1032
+ * frameRate: ?string}}
1006
1033
  * @private
1007
1034
  */
1008
1035
  getHvcInfo_() {
@@ -1011,6 +1038,7 @@ shaka.util.TsParser = class {
1011
1038
  height: null,
1012
1039
  width: null,
1013
1040
  codec: null,
1041
+ frameRate: null,
1014
1042
  };
1015
1043
  const videoNalus = this.getVideoNalus();
1016
1044
  if (!videoNalus.length) {
@@ -1135,6 +1163,7 @@ shaka.util.TsParser = class {
1135
1163
  generalConstraintIndicatorFlags1.toString(16).toUpperCase();
1136
1164
  }
1137
1165
  videoInfo.codec = codec;
1166
+ videoInfo.frameRate = this.getFrameRate_();
1138
1167
 
1139
1168
  return videoInfo;
1140
1169
  }
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.10.13",
4
+ "version": "4.11.0",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -33,7 +33,6 @@ goog.require('shaka.offline.OfflineManifestParser');
33
33
  goog.require('shaka.offline.OfflineScheme');
34
34
  goog.require('shaka.offline.Storage');
35
35
  goog.require('shaka.offline.indexeddb.StorageMechanism');
36
- goog.require('shaka.polyfill.AbortController');
37
36
  goog.require('shaka.polyfill.Aria');
38
37
  goog.require('shaka.polyfill.EncryptionScheme');
39
38
  goog.require('shaka.polyfill.Fullscreen');
@@ -90,6 +89,7 @@ goog.require('shaka.ui.AdStatisticsButton');
90
89
  goog.require('shaka.ui.AirPlayButton');
91
90
  goog.require('shaka.ui.BigPlayButton');
92
91
  goog.require('shaka.ui.CastButton');
92
+ goog.require('shaka.ui.ChapterSelection');
93
93
  goog.require('shaka.ui.ContextMenu');
94
94
  goog.require('shaka.ui.Element');
95
95
  goog.require('shaka.ui.FastForwardButton');
@@ -0,0 +1,179 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+
8
+ goog.provide('shaka.ui.ChapterSelection');
9
+
10
+ goog.require('shaka.ui.Controls');
11
+ goog.require('shaka.ui.Enums');
12
+ goog.require('shaka.ui.Locales');
13
+ goog.require('shaka.ui.Localization');
14
+ goog.require('shaka.ui.OverflowMenu');
15
+ goog.require('shaka.ui.SettingsMenu');
16
+ goog.require('shaka.ui.Utils');
17
+ goog.require('shaka.util.Dom');
18
+ goog.requireType('shaka.ui.Controls');
19
+
20
+ /**
21
+ * @extends {shaka.ui.SettingsMenu}
22
+ * @final
23
+ * @export
24
+ */
25
+ shaka.ui.ChapterSelection = class extends shaka.ui.SettingsMenu {
26
+ /**
27
+ * @param {!HTMLElement} parent
28
+ * @param {!shaka.ui.Controls} controls
29
+ */
30
+ constructor(parent, controls) {
31
+ super(parent, controls, shaka.ui.Enums.MaterialDesignIcons.CHAPTER);
32
+
33
+ this.button.classList.add('shaka-chapter-button');
34
+ this.menu.classList.add('shaka-chapters');
35
+ this.button.classList.add('shaka-tooltip-status');
36
+
37
+ /** @type {!Array<shaka.extern.Chapter>} */
38
+ this.chapters_ = [];
39
+
40
+ this.chaptersLanguage_ = 'und';
41
+
42
+ this.eventManager.listen(
43
+ this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
44
+ this.updateLocalizedStrings_();
45
+ this.updateChapters_();
46
+ });
47
+
48
+ this.eventManager.listen(
49
+ this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
50
+ this.updateLocalizedStrings_();
51
+ this.updateChapters_();
52
+ });
53
+
54
+ this.eventManager.listen(this.player, 'unloading', () => {
55
+ this.deletePreviousChapters_();
56
+ this.chaptersLanguage_ = 'und';
57
+ this.chapters_ = [];
58
+ });
59
+
60
+ this.eventManager.listen(this.player, 'trackschanged', () => {
61
+ this.updateChapters_();
62
+ });
63
+
64
+ // Set up all the strings in the user's preferred language.
65
+ this.updateLocalizedStrings_();
66
+
67
+ this.updateChapters_();
68
+ }
69
+
70
+ /**
71
+ * @private
72
+ */
73
+ updateLocalizedStrings_() {
74
+ const LocIds = shaka.ui.Locales.Ids;
75
+
76
+ this.backButton.ariaLabel = this.localization.resolve(LocIds.BACK);
77
+ this.button.ariaLabel = this.localization.resolve(LocIds.CHAPTERS);
78
+ this.nameSpan.textContent = this.localization.resolve(LocIds.CHAPTERS);
79
+ this.backSpan.textContent = this.localization.resolve(LocIds.CHAPTERS);
80
+ }
81
+
82
+ /**
83
+ * @private
84
+ */
85
+ deletePreviousChapters_() {
86
+ // 1. Save the back to menu button
87
+ const backButton = shaka.ui.Utils.getFirstDescendantWithClassName(
88
+ this.menu, 'shaka-back-to-overflow-button');
89
+
90
+ // 2. Remove everything
91
+ shaka.util.Dom.removeAllChildren(this.menu);
92
+
93
+ // 3. Add the backTo Menu button back
94
+ this.menu.appendChild(backButton);
95
+
96
+ // 4. Hidden button
97
+ shaka.ui.Utils.setDisplay(this.button, false);
98
+ }
99
+
100
+ /**
101
+ * @private
102
+ */
103
+ updateChapters_() {
104
+ /**
105
+ * Does a value compare on chapters.
106
+ * @param {shaka.extern.Chapter} a
107
+ * @param {shaka.extern.Chapter} b
108
+ * @return {boolean}
109
+ */
110
+ const chaptersEqual = (a, b) => {
111
+ return (!a && !b) || (a.id === b.id && a.title === b.title &&
112
+ a.startTime === b.startTime && a.endTime === b.endTime);
113
+ };
114
+
115
+ let nextLanguage = 'und';
116
+ /** @type {!Array<shaka.extern.Chapter>} */
117
+ let nextChapters = [];
118
+
119
+ const currentLocales = this.localization.getCurrentLocales();
120
+ for (const locale of Array.from(currentLocales)) {
121
+ nextLanguage = locale;
122
+ nextChapters = this.player.getChapters(nextLanguage);
123
+ if (nextChapters.length) {
124
+ break;
125
+ }
126
+ }
127
+ if (!nextChapters.length) {
128
+ nextLanguage = 'und';
129
+ nextChapters = this.player.getChapters(nextLanguage);
130
+ }
131
+
132
+ const languageChanged = nextLanguage !== this.chaptersLanguage_;
133
+ const chaptersChanged = this.chapters_.length !== nextChapters.length ||
134
+ !this.chapters_.some((c, idx) => {
135
+ const n = nextChapters.at(idx);
136
+ return chaptersEqual(c, n) ||
137
+ nextChapters.some((n) => chaptersEqual(c, n));
138
+ });
139
+
140
+ this.chaptersLanguage_ = nextLanguage;
141
+ this.chapters_ = nextChapters;
142
+ if (!nextChapters.length) {
143
+ this.deletePreviousChapters_();
144
+ } else if (languageChanged || chaptersChanged) {
145
+ for (const chapter of this.chapters_) {
146
+ const button = shaka.util.Dom.createButton();
147
+ const span = shaka.util.Dom.createHTMLElement('span');
148
+ span.classList.add('shaka-chapter');
149
+ span.textContent = chapter.title;
150
+ button.appendChild(span);
151
+
152
+ this.eventManager.listen(button, 'click', () => {
153
+ this.video.currentTime = chapter.startTime;
154
+ });
155
+
156
+ this.menu.appendChild(button);
157
+ }
158
+ shaka.ui.Utils.setDisplay(this.button, true);
159
+ shaka.ui.Utils.focusOnTheChosenItem(this.menu);
160
+ }
161
+ }
162
+ };
163
+
164
+ /**
165
+ * @implements {shaka.extern.IUIElement.Factory}
166
+ * @final
167
+ */
168
+ shaka.ui.ChapterSelection.Factory = class {
169
+ /** @override */
170
+ create(rootElement, controls) {
171
+ return new shaka.ui.ChapterSelection(rootElement, controls);
172
+ }
173
+ };
174
+
175
+ shaka.ui.OverflowMenu.registerElement(
176
+ 'chapter', new shaka.ui.ChapterSelection.Factory());
177
+
178
+ shaka.ui.Controls.registerElement(
179
+ 'chapter', new shaka.ui.ChapterSelection.Factory());
package/ui/controls.js CHANGED
@@ -181,6 +181,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
181
181
  // Configure and create the layout of the controls
182
182
  this.configure(this.config_);
183
183
  this.addEventListeners_();
184
+ this.setupMediaSession_();
184
185
 
185
186
  /**
186
187
  * The pressed keys set is used to record which keys are currently pressed
@@ -274,6 +275,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
274
275
  this.localization_ = null;
275
276
  this.pressedKeys_.clear();
276
277
 
278
+ this.removeMediaSession_();
279
+
277
280
  // FakeEventTarget implements IReleasable
278
281
  super.release();
279
282
  }
@@ -1235,6 +1238,154 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
1235
1238
  }
1236
1239
 
1237
1240
 
1241
+ /**
1242
+ * @private
1243
+ */
1244
+ setupMediaSession_() {
1245
+ if (!this.config_.setupMediaSession || !navigator.mediaSession) {
1246
+ return;
1247
+ }
1248
+ const addMediaSessionHandler = (type, callback) => {
1249
+ try {
1250
+ navigator.mediaSession.setActionHandler(type, (details) => {
1251
+ callback(details);
1252
+ });
1253
+ } catch (error) {
1254
+ shaka.log.warning(
1255
+ `The "${type}" media session action is not supported.`);
1256
+ }
1257
+ };
1258
+ const updatePositionState = () => {
1259
+ const seekRange = this.player_.seekRange();
1260
+ let duration = seekRange.end - seekRange.start;
1261
+ const position = parseFloat(
1262
+ (this.video_.currentTime - seekRange.start).toFixed(2));
1263
+ if (this.player_.isLive() && Math.abs(duration - position) < 1) {
1264
+ // Positive infinity indicates media without a defined end, such as a
1265
+ // live stream.
1266
+ duration = Infinity;
1267
+ }
1268
+ try {
1269
+ if ((this.ad_ && this.ad_.isLinear())) {
1270
+ navigator.mediaSession.setPositionState();
1271
+ } else {
1272
+ navigator.mediaSession.setPositionState({
1273
+ duration: Math.max(0, duration),
1274
+ playbackRate: this.video_.playbackRate,
1275
+ position: Math.max(0, position),
1276
+ });
1277
+ }
1278
+ } catch (error) {
1279
+ shaka.log.warning(
1280
+ 'setPositionState in media session is not supported.');
1281
+ }
1282
+ };
1283
+ const commonHandler = (details) => {
1284
+ const keyboardSeekDistance = this.config_.keyboardSeekDistance;
1285
+ const seekRange = this.player_.seekRange();
1286
+ switch (details.action) {
1287
+ case 'pause':
1288
+ this.onPlayPauseClick_();
1289
+ break;
1290
+ case 'play':
1291
+ this.onPlayPauseClick_();
1292
+ break;
1293
+ case 'seekbackward':
1294
+ if (!this.ad_ || !this.ad_.isLinear()) {
1295
+ this.seek_(this.seekBar_.getValue() -
1296
+ (details.seekOffset || keyboardSeekDistance));
1297
+ }
1298
+ break;
1299
+ case 'seekforward':
1300
+ if (!this.ad_ || !this.ad_.isLinear()) {
1301
+ this.seek_(this.seekBar_.getValue() +
1302
+ (details.seekOffset || keyboardSeekDistance));
1303
+ }
1304
+ break;
1305
+ case 'seekto':
1306
+ if (!this.ad_ || !this.ad_.isLinear()) {
1307
+ this.seek_(seekRange.start + details.seekTime);
1308
+ }
1309
+ break;
1310
+ case 'stop':
1311
+ this.player_.unload();
1312
+ break;
1313
+ case 'enterpictureinpicture':
1314
+ if (!this.ad_ || !this.ad_.isLinear()) {
1315
+ this.togglePiP();
1316
+ }
1317
+ break;
1318
+ }
1319
+ };
1320
+
1321
+ addMediaSessionHandler('pause', commonHandler);
1322
+ addMediaSessionHandler('play', commonHandler);
1323
+ addMediaSessionHandler('seekbackward', commonHandler);
1324
+ addMediaSessionHandler('seekforward', commonHandler);
1325
+ addMediaSessionHandler('seekto', commonHandler);
1326
+ addMediaSessionHandler('stop', commonHandler);
1327
+ addMediaSessionHandler('enterpictureinpicture', commonHandler);
1328
+
1329
+ this.eventManager_.listen(this.video_, 'timeupdate', () => {
1330
+ updatePositionState();
1331
+ });
1332
+
1333
+ this.eventManager_.listen(this.player_, 'metadata', (event) => {
1334
+ const payload = event['payload'];
1335
+ if (!payload) {
1336
+ return;
1337
+ }
1338
+ let title;
1339
+ if (payload['key'] == 'TIT2' && payload['data']) {
1340
+ title = payload['data'];
1341
+ }
1342
+ let imageUrl;
1343
+ if (payload['key'] == 'APIC' && payload['mimeType'] == '-->') {
1344
+ imageUrl = payload['data'];
1345
+ }
1346
+ if (navigator.mediaSession.metadata && title) {
1347
+ navigator.mediaSession.metadata.title = title;
1348
+ }
1349
+ if (imageUrl) {
1350
+ const video = /** @type {HTMLVideoElement} */ (this.localVideo_);
1351
+ if (imageUrl != video.poster) {
1352
+ video.poster = imageUrl;
1353
+ }
1354
+ if (navigator.mediaSession.metadata) {
1355
+ navigator.mediaSession.metadata.artwork = [{src: imageUrl}];
1356
+ }
1357
+ }
1358
+ });
1359
+ }
1360
+
1361
+
1362
+ /**
1363
+ * @private
1364
+ */
1365
+ removeMediaSession_() {
1366
+ if (!this.config_.setupMediaSession || !navigator.mediaSession) {
1367
+ return;
1368
+ }
1369
+ try {
1370
+ navigator.mediaSession.setPositionState();
1371
+ } catch (error) {}
1372
+
1373
+ const disableMediaSessionHandler = (type) => {
1374
+ try {
1375
+ navigator.mediaSession.setActionHandler(type, null);
1376
+ } catch (error) {}
1377
+ };
1378
+
1379
+ disableMediaSessionHandler('pause');
1380
+ disableMediaSessionHandler('play');
1381
+ disableMediaSessionHandler('seekbackward');
1382
+ disableMediaSessionHandler('seekforward');
1383
+ disableMediaSessionHandler('seekto');
1384
+ disableMediaSessionHandler('stop');
1385
+ disableMediaSessionHandler('enterpictureinpicture');
1386
+ }
1387
+
1388
+
1238
1389
  /**
1239
1390
  * When a mobile device is rotated to landscape layout, and the video is
1240
1391
  * loaded, make the demo app go into fullscreen.
package/ui/enums.js CHANGED
@@ -47,4 +47,5 @@ shaka.ui.Enums.MaterialDesignIcons = {
47
47
  'RECENTER_VR': 'control_camera',
48
48
  'TOGGLE_STEREOSCOPIC': '3d_rotation',
49
49
  'DOWNLOAD': 'download',
50
+ 'CHAPTER': 'bookmarks',
50
51
  };
package/ui/externs/ui.js CHANGED
@@ -96,7 +96,8 @@ shaka.extern.UIVolumeBarColors;
96
96
  * tapSeekDistance: number,
97
97
  * refreshTickInSeconds: number,
98
98
  * displayInVrMode: boolean,
99
- * defaultVrProjectionMode: string
99
+ * defaultVrProjectionMode: string,
100
+ * setupMediaSession: boolean
100
101
  * }}
101
102
  *
102
103
  * @property {!Array.<string>} controlPanelElements
@@ -247,6 +248,11 @@ shaka.extern.UIVolumeBarColors;
247
248
  * Indicate the default VR projection mode.
248
249
  * Possible values: <code>'equirectangular'</code> or <code>'cubemap'</code>.
249
250
  * Defaults to <code>'equirectangular'</code>.
251
+ * @property {boolean} setupMediaSession
252
+ * If true, MediaSession controls will be managed by the UI. It will also use
253
+ * the ID3 APIC and TIT2 as image and title in Media Session, and ID3 APIC
254
+ * will also be used to change video poster.
255
+ * Defaults to true.
250
256
  * @exportDoc
251
257
  */
252
258
  shaka.extern.UIConfiguration;
@@ -22,10 +22,9 @@
22
22
  }
23
23
 
24
24
  /* Set the fonts for all other content. */
25
- * {
26
- font-family: Roboto-Regular, Roboto, sans-serif, TengwarTelcontar;
27
- -webkit-font-smoothing: antialiased;
28
- }
25
+ font-family: Roboto, sans-serif, TengwarTelcontar;
26
+ font-weight: normal;
27
+ -webkit-font-smoothing: antialiased;
29
28
  }
30
29
 
31
30
  /* Each browser has a different prefixed pseudo-class for fullscreened elements.