shaka-player 3.2.2 → 3.3.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 (156) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
  3. package/.github/ISSUE_TEMPLATE/question.md +1 -1
  4. package/AUTHORS +6 -5
  5. package/CHANGELOG.md +247 -0
  6. package/CONTRIBUTORS +4 -2
  7. package/build/types/core +3 -0
  8. package/build/types/offline +1 -0
  9. package/build/types/polyfill +1 -0
  10. package/build/types/ui +2 -0
  11. package/demo/common/assets.js +18 -0
  12. package/demo/common/message_ids.js +13 -1
  13. package/demo/config.js +31 -1
  14. package/demo/index.html +1 -1
  15. package/demo/locales/en.json +12 -0
  16. package/demo/locales/source.json +48 -0
  17. package/dist/controls.css +26 -2
  18. package/dist/controls.css.map +1 -1
  19. package/dist/demo.compiled.debug.js +19 -16
  20. package/dist/demo.compiled.debug.map +3 -3
  21. package/dist/demo.compiled.js +19 -16
  22. package/dist/demo.compiled.map +3 -3
  23. package/dist/deps.js +18 -11
  24. package/dist/locales.js +28 -26
  25. package/dist/receiver.compiled.debug.js +11 -9
  26. package/dist/receiver.compiled.debug.map +3 -3
  27. package/dist/receiver.compiled.js +11 -9
  28. package/dist/receiver.compiled.map +3 -3
  29. package/dist/shaka-player.compiled.d.ts +205 -15
  30. package/dist/shaka-player.compiled.debug.d.ts +205 -15
  31. package/dist/shaka-player.compiled.debug.externs.js +256 -6
  32. package/dist/shaka-player.compiled.debug.js +406 -354
  33. package/dist/shaka-player.compiled.debug.map +4 -4
  34. package/dist/shaka-player.compiled.externs.js +256 -6
  35. package/dist/shaka-player.compiled.js +826 -800
  36. package/dist/shaka-player.compiled.map +4 -4
  37. package/dist/shaka-player.ui.d.ts +221 -16
  38. package/dist/shaka-player.ui.debug.d.ts +221 -16
  39. package/dist/shaka-player.ui.debug.externs.js +283 -6
  40. package/dist/shaka-player.ui.debug.js +547 -476
  41. package/dist/shaka-player.ui.debug.map +4 -4
  42. package/dist/shaka-player.ui.externs.js +283 -6
  43. package/dist/shaka-player.ui.js +1019 -980
  44. package/dist/shaka-player.ui.map +4 -4
  45. package/docs/design/bg-fetch-after.gv +29 -0
  46. package/docs/design/bg-fetch-after.gv.png +0 -0
  47. package/docs/design/bg-fetch-before.gv +18 -0
  48. package/docs/design/bg-fetch-before.gv.png +0 -0
  49. package/docs/design/bg-fetch.md +134 -0
  50. package/docs/tutorials/blob-url.md +15 -0
  51. package/docs/tutorials/index.json +1 -0
  52. package/docs/tutorials/ui-customization.md +66 -0
  53. package/docs/tutorials/ui.md +5 -5
  54. package/externs/ima.js +3 -0
  55. package/externs/shaka/ads.js +5 -0
  56. package/externs/shaka/manifest.js +12 -1
  57. package/externs/shaka/manifest_parser.js +10 -1
  58. package/externs/shaka/net.js +20 -4
  59. package/externs/shaka/offline.js +31 -2
  60. package/externs/shaka/player.js +130 -5
  61. package/externs/shaka/text.js +14 -0
  62. package/externs/webcrypto.js +18 -0
  63. package/lib/abr/ewma.js +14 -0
  64. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  65. package/lib/abr/simple_abr_manager.js +6 -0
  66. package/lib/ads/client_side_ad.js +8 -0
  67. package/lib/ads/client_side_ad_manager.js +8 -4
  68. package/lib/ads/server_side_ad.js +7 -0
  69. package/lib/cast/cast_utils.js +1 -0
  70. package/lib/dash/dash_parser.js +48 -14
  71. package/lib/dash/segment_base.js +25 -1
  72. package/lib/dash/segment_list.js +18 -9
  73. package/lib/dash/segment_template.js +23 -10
  74. package/lib/hls/hls_parser.js +86 -23
  75. package/lib/media/quality_observer.js +291 -0
  76. package/lib/media/segment_index.js +6 -0
  77. package/lib/media/segment_reference.js +18 -3
  78. package/lib/media/streaming_engine.js +42 -4
  79. package/lib/net/http_fetch_plugin.js +31 -11
  80. package/lib/net/http_plugin_utils.js +1 -0
  81. package/lib/net/http_xhr_plugin.js +40 -14
  82. package/lib/net/networking_engine.js +81 -18
  83. package/lib/offline/download_info.js +68 -0
  84. package/lib/offline/download_manager.js +13 -5
  85. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  86. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  87. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  88. package/lib/offline/storage.js +393 -284
  89. package/lib/offline/stored_content_utils.js +2 -0
  90. package/lib/player.js +206 -7
  91. package/lib/polyfill/random_uuid.js +52 -0
  92. package/lib/text/cue.js +12 -0
  93. package/lib/text/ttml_text_parser.js +25 -2
  94. package/lib/text/ui_text_displayer.js +2 -2
  95. package/lib/util/cmcd_manager.js +826 -0
  96. package/lib/util/error.js +6 -0
  97. package/lib/util/language_utils.js +12 -0
  98. package/lib/util/mutex.js +52 -0
  99. package/lib/util/periods.js +98 -64
  100. package/lib/util/player_configuration.js +18 -0
  101. package/package.json +1 -1
  102. package/shaka-player.uncompiled.js +3 -0
  103. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  104. package/test/dash/dash_parser_live_unit.js +101 -1
  105. package/test/dash/dash_parser_manifest_unit.js +35 -1
  106. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  107. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  108. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  109. package/test/hls/hls_live_unit.js +2 -0
  110. package/test/hls/hls_parser_unit.js +2 -0
  111. package/test/media/quality_observer_unit.js +139 -0
  112. package/test/media/segment_reference_unit.js +16 -1
  113. package/test/media/streaming_engine_integration.js +2 -1
  114. package/test/media/streaming_engine_unit.js +2 -0
  115. package/test/net/http_plugin_unit.js +20 -15
  116. package/test/offline/storage_integration.js +108 -4
  117. package/test/player_integration.js +99 -0
  118. package/test/test/util/dash_parser_util.js +4 -0
  119. package/test/test/util/fake_ad.js +7 -0
  120. package/test/test/util/fake_drm_engine.js +9 -2
  121. package/test/test/util/manifest_generator.js +3 -2
  122. package/test/test/util/test_scheme.js +14 -1
  123. package/test/text/ttml_text_parser_unit.js +47 -0
  124. package/test/ui/ui_unit.js +250 -0
  125. package/test/util/cmcd_manager_unit.js +277 -0
  126. package/ui/airplay_button.js +1 -0
  127. package/ui/audio_language_selection.js +7 -0
  128. package/ui/cast_button.js +1 -0
  129. package/ui/context_menu.js +109 -0
  130. package/ui/controls.js +41 -7
  131. package/ui/controls.less +1 -0
  132. package/ui/enums.js +4 -0
  133. package/ui/externs/ui.js +28 -1
  134. package/ui/fast_forward_button.js +14 -5
  135. package/ui/fullscreen_button.js +1 -0
  136. package/ui/less/buttons.less +0 -4
  137. package/ui/less/containers.less +74 -0
  138. package/ui/less/overflow_menu.less +0 -10
  139. package/ui/less/tooltip.less +97 -0
  140. package/ui/locales/en.json +1 -0
  141. package/ui/locales/source.json +4 -0
  142. package/ui/loop_button.js +5 -0
  143. package/ui/mute_button.js +2 -1
  144. package/ui/overflow_menu.js +5 -25
  145. package/ui/pip_button.js +5 -0
  146. package/ui/play_button.js +2 -2
  147. package/ui/playback_rate_selection.js +8 -9
  148. package/ui/resolution_selection.js +7 -0
  149. package/ui/rewind_button.js +15 -6
  150. package/ui/seek_bar.js +4 -2
  151. package/ui/settings_menu.js +36 -4
  152. package/ui/small_play_button.js +1 -0
  153. package/ui/statistics_button.js +253 -0
  154. package/ui/text_selection.js +11 -4
  155. package/ui/ui.js +30 -0
  156. package/ui/volume_bar.js +1 -1
@@ -31,7 +31,7 @@ shaka.dash.SegmentTemplate = class {
31
31
  * @param {shaka.dash.DashParser.Context} context
32
32
  * @param {shaka.dash.DashParser.RequestInitSegmentCallback}
33
33
  * requestInitSegment
34
- * @param {!Object.<string, !shaka.media.SegmentIndex>} segmentIndexMap
34
+ * @param {!Object.<string, !shaka.extern.Stream>} streamMap
35
35
  * @param {boolean} isUpdate True if the manifest is being updated.
36
36
  * @param {number} segmentLimit The maximum number of segments to generate for
37
37
  * a SegmentTemplate with fixed duration.
@@ -39,8 +39,8 @@ shaka.dash.SegmentTemplate = class {
39
39
  * @return {shaka.dash.DashParser.StreamInfo}
40
40
  */
41
41
  static createStreamInfo(
42
- context, requestInitSegment, segmentIndexMap, isUpdate,
43
- segmentLimit, periodDurationMap) {
42
+ context, requestInitSegment, streamMap, isUpdate, segmentLimit,
43
+ periodDurationMap) {
44
44
  goog.asserts.assert(context.representation.segmentTemplate,
45
45
  'Should only be called with SegmentTemplate');
46
46
  const SegmentTemplate = shaka.dash.SegmentTemplate;
@@ -86,10 +86,14 @@ shaka.dash.SegmentTemplate = class {
86
86
  /** @type {shaka.media.SegmentIndex} */
87
87
  let segmentIndex = null;
88
88
  let id = null;
89
+ let stream = null;
89
90
  if (context.period.id && context.representation.id) {
90
91
  // Only check/store the index if period and representation IDs are set.
91
92
  id = context.period.id + ',' + context.representation.id;
92
- segmentIndex = segmentIndexMap[id];
93
+ stream = streamMap[id];
94
+ if (stream) {
95
+ segmentIndex = stream.segmentIndex;
96
+ }
93
97
  }
94
98
 
95
99
  const references = SegmentTemplate.createFromTimeline_(
@@ -117,9 +121,6 @@ shaka.dash.SegmentTemplate = class {
117
121
  context.presentationTimeline.getSegmentAvailabilityStart());
118
122
  } else {
119
123
  segmentIndex = new shaka.media.SegmentIndex(references);
120
- if (id && context.dynamic) {
121
- segmentIndexMap[id] = segmentIndex;
122
- }
123
124
  }
124
125
  context.presentationTimeline.notifySegments(references);
125
126
 
@@ -127,8 +128,20 @@ shaka.dash.SegmentTemplate = class {
127
128
  segmentIndex.fit(periodStart, periodEnd);
128
129
  }
129
130
 
131
+ if (stream && context.dynamic) {
132
+ stream.segmentIndex = segmentIndex;
133
+ }
134
+
130
135
  return {
131
- generateSegmentIndex: () => Promise.resolve(segmentIndex),
136
+ generateSegmentIndex: () => {
137
+ // If segmentIndex is deleted, or segmentIndex's references are
138
+ // released by closeSegmentIndex(), we should set the value of
139
+ // segmentIndex again.
140
+ if (!segmentIndex || segmentIndex.isEmpty()) {
141
+ segmentIndex.merge(references);
142
+ }
143
+ return Promise.resolve(segmentIndex);
144
+ },
132
145
  };
133
146
  }
134
147
  }
@@ -569,8 +582,8 @@ shaka.dash.SegmentTemplate = class {
569
582
  baseUris, [filledTemplate]);
570
583
  return resolvedUris;
571
584
  };
572
-
573
- return new shaka.media.InitSegmentReference(getUris, 0, null);
585
+ const qualityInfo = shaka.dash.SegmentBase.createQualityInfo(context);
586
+ return new shaka.media.InitSegmentReference(getUris, 0, null, qualityInfo);
574
587
  }
575
588
  };
576
589
 
@@ -25,6 +25,7 @@ goog.require('shaka.net.DataUriPlugin');
25
25
  goog.require('shaka.net.NetworkingEngine');
26
26
  goog.require('shaka.util.ArrayUtils');
27
27
  goog.require('shaka.util.BufferUtils');
28
+ goog.require('shaka.util.CmcdManager');
28
29
  goog.require('shaka.util.DataViewReader');
29
30
  goog.require('shaka.util.Error');
30
31
  goog.require('shaka.util.FakeEvent');
@@ -332,7 +333,8 @@ shaka.hls.HlsParser = class {
332
333
  const segments = await this.createSegments_(
333
334
  streamInfo.verbatimMediaPlaylistUri, playlist, stream.type,
334
335
  stream.mimeType, streamInfo.mediaSequenceToStartTime, mediaVariables,
335
- streamInfo.discontinuityToMediaSequence);
336
+ streamInfo.discontinuityToMediaSequence, stream.codecs,
337
+ stream.bandwidth);
336
338
 
337
339
  stream.segmentIndex.mergeAndEvict(
338
340
  segments, this.presentationTimeline_.getSegmentAvailabilityStart());
@@ -763,7 +765,7 @@ shaka.hls.HlsParser = class {
763
765
  const videoRange = tag.getAttributeValue('VIDEO-RANGE');
764
766
 
765
767
  const streamInfos = await this.createStreamInfosForVariantTag_(tag,
766
- resolution, frameRate);
768
+ resolution, frameRate, bandwidth);
767
769
 
768
770
  if (streamInfos) {
769
771
  goog.asserts.assert(streamInfos.audio.length ||
@@ -797,10 +799,11 @@ shaka.hls.HlsParser = class {
797
799
  * @param {!shaka.hls.Tag} tag
798
800
  * @param {?string} resolution
799
801
  * @param {?string} frameRate
802
+ * @param {number} bandwidth
800
803
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfos>}
801
804
  * @private
802
805
  */
803
- async createStreamInfosForVariantTag_(tag, resolution, frameRate) {
806
+ async createStreamInfosForVariantTag_(tag, resolution, frameRate, bandwidth) {
804
807
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
805
808
  /** @type {!Array.<string>} */
806
809
  let allCodecs = this.getCodecsForVariantTag_(tag);
@@ -870,7 +873,8 @@ shaka.hls.HlsParser = class {
870
873
  let streamInfo;
871
874
  if (!ignoreStream) {
872
875
  streamInfo =
873
- await this.createStreamInfoFromVariantTag_(tag, allCodecs, type);
876
+ await this.createStreamInfoFromVariantTag_(tag, allCodecs, type,
877
+ bandwidth);
874
878
  }
875
879
  if (streamInfo) {
876
880
  res[streamInfo.stream.type] = [streamInfo];
@@ -893,15 +897,12 @@ shaka.hls.HlsParser = class {
893
897
  */
894
898
  getCodecsForVariantTag_(tag) {
895
899
  // These are the default codecs to assume if none are specified.
896
- // The video codec is H.264, with baseline profile and level 3.0.
897
- // http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
898
- // The audio codec is "low-complexity" AAC.
899
900
  const defaultCodecsArray = [];
900
901
  if (!this.config_.disableVideo) {
901
- defaultCodecsArray.push('avc1.42E01E');
902
+ defaultCodecsArray.push(this.config_.hls.defaultVideoCodec);
902
903
  }
903
904
  if (!this.config_.disableAudio) {
904
- defaultCodecsArray.push('mp4a.40.2');
905
+ defaultCodecsArray.push(this.config_.hls.defaultAudioCodec);
905
906
  }
906
907
  const defaultCodecs = defaultCodecsArray.join(',');
907
908
 
@@ -1290,10 +1291,11 @@ shaka.hls.HlsParser = class {
1290
1291
  * @param {!shaka.hls.Tag} tag
1291
1292
  * @param {!Array.<string>} allCodecs
1292
1293
  * @param {string} type
1294
+ * @param {number} bandwidth
1293
1295
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1294
1296
  * @private
1295
1297
  */
1296
- async createStreamInfoFromVariantTag_(tag, allCodecs, type) {
1298
+ async createStreamInfoFromVariantTag_(tag, allCodecs, type, bandwidth) {
1297
1299
  goog.asserts.assert(tag.name == 'EXT-X-STREAM-INF',
1298
1300
  'Should only be called on variant tags!');
1299
1301
  const verbatimMediaPlaylistUri = this.variableSubstitution_(
@@ -1309,7 +1311,7 @@ shaka.hls.HlsParser = class {
1309
1311
  codecs, type, /* language= */ 'und', /* primary= */ false,
1310
1312
  /* name= */ null, /* channelcount= */ null, closedCaptions,
1311
1313
  /* characteristics= */ null, /* forced= */ false,
1312
- /* spatialAudio= */ false);
1314
+ /* spatialAudio= */ false, bandwidth);
1313
1315
  if (streamInfo == null) {
1314
1316
  return null;
1315
1317
  }
@@ -1336,12 +1338,13 @@ shaka.hls.HlsParser = class {
1336
1338
  * @param {?string} characteristics
1337
1339
  * @param {boolean} forced
1338
1340
  * @param {boolean} spatialAudio
1341
+ * @param {(number|undefined)} bandwidth
1339
1342
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1340
1343
  * @private
1341
1344
  */
1342
1345
  async createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, language,
1343
1346
  primary, name, channelsCount, closedCaptions, characteristics, forced,
1344
- spatialAudio) {
1347
+ spatialAudio, bandwidth = undefined) {
1345
1348
  // TODO: Refactor, too many parameters
1346
1349
  let absoluteMediaPlaylistUri = shaka.hls.Utils.constructAbsoluteUri(
1347
1350
  this.masterPlaylistUri_, verbatimMediaPlaylistUri);
@@ -1459,7 +1462,7 @@ shaka.hls.HlsParser = class {
1459
1462
  try {
1460
1463
  segments = await this.createSegments_(verbatimMediaPlaylistUri,
1461
1464
  playlist, type, mimeType, mediaSequenceToStartTime, mediaVariables,
1462
- discontinuityToMediaSequence);
1465
+ discontinuityToMediaSequence, codecs, bandwidth);
1463
1466
  } catch (error) {
1464
1467
  if (error.code == shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM) {
1465
1468
  shaka.log.alwaysWarn('Skipping unsupported HLS stream',
@@ -1887,11 +1890,14 @@ shaka.hls.HlsParser = class {
1887
1890
  * @param {!Map.<number, number>} mediaSequenceToStartTime
1888
1891
  * @param {!Map.<string, string>} variables
1889
1892
  * @param {!Map.<number, number>} discontinuityToMediaSequence
1893
+ * @param {string} codecs
1894
+ * @param {(number|undefined)} bandwidth
1890
1895
  * @return {!Promise<!Array.<!shaka.media.SegmentReference>>}
1891
1896
  * @private
1892
1897
  */
1893
1898
  async createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
1894
- mediaSequenceToStartTime, variables, discontinuityToMediaSequence) {
1899
+ mediaSequenceToStartTime, variables, discontinuityToMediaSequence,
1900
+ codecs, bandwidth) {
1895
1901
  /** @type {Array.<!shaka.hls.Segment>} */
1896
1902
  const hlsSegments = playlist.segments;
1897
1903
  goog.asserts.assert(hlsSegments.length, 'Playlist should have segments!');
@@ -1934,7 +1940,7 @@ shaka.hls.HlsParser = class {
1934
1940
  this.playlistStartTime_ = await this.getStartTime_(
1935
1941
  verbatimMediaPlaylistUri, initSegmentRef, mimeType,
1936
1942
  position, /* isDiscontinuity= */ false,
1937
- hlsSegments[0], variables, type);
1943
+ hlsSegments[0], variables, type, codecs, bandwidth);
1938
1944
  }
1939
1945
  firstStartTime = this.playlistStartTime_;
1940
1946
  }
@@ -1987,7 +1993,8 @@ shaka.hls.HlsParser = class {
1987
1993
  // eslint-disable-next-line no-await-in-loop
1988
1994
  timestampOffset = await this.getTimestampOffset_(
1989
1995
  discontintuitySequenceNum, verbatimMediaPlaylistUri, initSegmentRef,
1990
- mimeType, position, item, variables, startTime, type);
1996
+ mimeType, position, item, variables, startTime, type, codecs,
1997
+ bandwidth);
1991
1998
  }
1992
1999
 
1993
2000
  // If the stream is low latency and the user has not configured the
@@ -2041,13 +2048,16 @@ shaka.hls.HlsParser = class {
2041
2048
  * @param {!Map.<string, string>} variables
2042
2049
  * @param {number} startTime
2043
2050
  * @param {string} type
2051
+ * @param {string} codecs
2052
+ * @param {number|undefined} bandwidth
2044
2053
  * @return {!Promise.<number>}
2045
2054
  * @throws {shaka.util.Error}
2046
2055
  * @private
2047
2056
  */
2048
2057
  async getTimestampOffset_(discontintuitySequenceNum,
2049
2058
  verbatimMediaPlaylistUri, initSegmentRef,
2050
- mimeType, mediaSequenceNumber, segment, variables, startTime, type) {
2059
+ mimeType, mediaSequenceNumber, segment, variables, startTime, type,
2060
+ codecs, bandwidth) {
2051
2061
  let timestampOffset = 0;
2052
2062
  if (this.discontinuityToTso_.has(discontintuitySequenceNum)) {
2053
2063
  timestampOffset =
@@ -2056,7 +2066,7 @@ shaka.hls.HlsParser = class {
2056
2066
  const mediaStartTime = await this.getStartTime_(
2057
2067
  verbatimMediaPlaylistUri, initSegmentRef, mimeType,
2058
2068
  mediaSequenceNumber, /* isDiscontinuity= */ true, segment,
2059
- variables, type);
2069
+ variables, type, codecs, bandwidth);
2060
2070
  timestampOffset = startTime - mediaStartTime;
2061
2071
  shaka.log.v1('Segment timestampOffset =', timestampOffset);
2062
2072
  this.discontinuityToTso_.set(
@@ -2070,10 +2080,14 @@ shaka.hls.HlsParser = class {
2070
2080
  * segment if we have to.
2071
2081
  *
2072
2082
  * @param {!shaka.media.AnySegmentReference} reference
2083
+ * @param {string} type
2084
+ * @param {string} mimeType
2085
+ * @param {string} codecs
2086
+ * @param {number|undefined} bandwidth
2073
2087
  * @return {!Promise.<shaka.extern.Response>}
2074
2088
  * @private
2075
2089
  */
2076
- async fetchStartOfSegment_(reference) {
2090
+ async fetchStartOfSegment_(reference, type, mimeType, codecs, bandwidth) {
2077
2091
  const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
2078
2092
 
2079
2093
  // Create two requests:
@@ -2087,6 +2101,20 @@ shaka.hls.HlsParser = class {
2087
2101
  reference.endByte,
2088
2102
  this.config_.retryParameters);
2089
2103
 
2104
+ // We can only add partial CMCD data here because the stream
2105
+ // and manifest objects are still being created
2106
+ this.playerInterface_.modifySegmentRequest(
2107
+ fullRequest,
2108
+ {
2109
+ type: type,
2110
+ init: reference instanceof shaka.media.InitSegmentReference,
2111
+ duration: reference.endTime - reference.startTime,
2112
+ mimeType: mimeType,
2113
+ codecs: codecs,
2114
+ bandwidth: bandwidth,
2115
+ },
2116
+ );
2117
+
2090
2118
  if (this.config_.hls.useFullSegmentsForStartTime) {
2091
2119
  return this.makeNetworkRequest_(fullRequest, requestType);
2092
2120
  }
@@ -2097,6 +2125,18 @@ shaka.hls.HlsParser = class {
2097
2125
  reference.startByte + shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ - 1,
2098
2126
  this.config_.retryParameters);
2099
2127
 
2128
+ this.playerInterface_.modifySegmentRequest(
2129
+ partialRequest,
2130
+ {
2131
+ type: type,
2132
+ init: reference instanceof shaka.media.InitSegmentReference,
2133
+ duration: reference.endTime - reference.startTime,
2134
+ mimeType: mimeType,
2135
+ codecs: codecs,
2136
+ bandwidth: bandwidth,
2137
+ },
2138
+ );
2139
+
2100
2140
  // TODO(vaage): The need to do fall back requests is not likely to be unique
2101
2141
  // to here. It would be nice if the fallback(s) could be included into
2102
2142
  // the same abortable operation as the original request.
@@ -2144,12 +2184,14 @@ shaka.hls.HlsParser = class {
2144
2184
  * @param {!shaka.hls.Segment} segment
2145
2185
  * @param {!Map.<string, string>} variables
2146
2186
  * @param {string} type
2187
+ * @param {string} codecs
2188
+ * @param {number|undefined} bandwidth
2147
2189
  * @return {!Promise.<number>}
2148
2190
  * @private
2149
2191
  */
2150
2192
  async getStartTime_(
2151
2193
  verbatimMediaPlaylistUri, initSegmentRef, mimeType, mediaSequenceNumber,
2152
- isDiscontinuity, segment, variables, type) {
2194
+ isDiscontinuity, segment, variables, type, codecs, bandwidth) {
2153
2195
  const segmentRef = this.createSegmentReference_(
2154
2196
  initSegmentRef,
2155
2197
  /* previousReference= */ null,
@@ -2217,10 +2259,22 @@ shaka.hls.HlsParser = class {
2217
2259
  if (mimeType == 'video/mp4' || mimeType == 'audio/mp4') {
2218
2260
  // We also need the init segment to get the correct timescale. But if the
2219
2261
  // stream is self-initializing, use the same response for both.
2220
- const fetches = [this.fetchStartOfSegment_(segmentRef)];
2262
+ const fetches = [this.fetchStartOfSegment_(
2263
+ segmentRef,
2264
+ type,
2265
+ mimeType,
2266
+ codecs,
2267
+ bandwidth,
2268
+ )];
2221
2269
 
2222
2270
  if (initSegmentRef) {
2223
- fetches.push(this.fetchStartOfSegment_(initSegmentRef));
2271
+ fetches.push(this.fetchStartOfSegment_(
2272
+ initSegmentRef,
2273
+ type,
2274
+ mimeType,
2275
+ codecs,
2276
+ bandwidth,
2277
+ ));
2224
2278
  }
2225
2279
 
2226
2280
  const responses = await Promise.all(fetches);
@@ -2236,7 +2290,13 @@ shaka.hls.HlsParser = class {
2236
2290
  }
2237
2291
 
2238
2292
  if (mimeType == 'video/mp2t') {
2239
- const response = await this.fetchStartOfSegment_(segmentRef);
2293
+ const response = await this.fetchStartOfSegment_(
2294
+ segmentRef,
2295
+ type,
2296
+ mimeType,
2297
+ codecs,
2298
+ bandwidth,
2299
+ );
2240
2300
  goog.asserts.assert(response.data, 'Should have a response body!');
2241
2301
  return this.getStartTimeFromTsSegment_(
2242
2302
  verbatimMediaPlaylistUri, response.uri, response.data);
@@ -2614,6 +2674,9 @@ shaka.hls.HlsParser = class {
2614
2674
  const request = shaka.net.NetworkingEngine.makeRequest(
2615
2675
  [absoluteUri], this.config_.retryParameters);
2616
2676
 
2677
+ const format = shaka.util.CmcdManager.StreamingFormat.HLS;
2678
+ this.playerInterface_.modifyManifestRequest(request, {format: format});
2679
+
2617
2680
  return this.makeNetworkRequest_(request, requestType);
2618
2681
  }
2619
2682
 
@@ -0,0 +1,291 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ goog.provide('shaka.media.QualityObserver');
8
+
9
+ goog.require('shaka.media.IPlayheadObserver');
10
+ goog.require('shaka.log');
11
+
12
+ /**
13
+ * Monitors the quality of content being appended to the source
14
+ * buffers and fires onQualityChange events when the media quality
15
+ * at the playhead changes.
16
+ *
17
+ * @implements {shaka.media.IPlayheadObserver}
18
+ * @final
19
+ */
20
+ shaka.media.QualityObserver = class {
21
+ /**
22
+ * Creates a new QualityObserver.
23
+ *
24
+ * @param {!function():!shaka.extern.BufferedInfo} getBufferedInfo
25
+ * Buffered info is needed to purge QualityChanges that are no
26
+ * longer relevant.
27
+ */
28
+ constructor(getBufferedInfo) {
29
+ /**
30
+ * @private {!Map.<string,!shaka.media.QualityObserver.ContentTypeState>}
31
+ */
32
+ this.contentTypeStates_ = new Map();
33
+
34
+ /** @private {shaka.media.QualityObserver.EventListener} */
35
+ this.onQualityChange_ = (mediaQuality, position) => {};
36
+
37
+ /** @private function():!shaka.extern.BufferedInfo */
38
+ this.getBufferedInfo_ = getBufferedInfo;
39
+ }
40
+
41
+ /**
42
+ * Set all the listeners. This overrides any previous calls to |setListeners|.
43
+ *
44
+ * @param {shaka.media.QualityObserver.EventListener} onQualityChange
45
+ */
46
+ setListeners(onQualityChange) {
47
+ this.onQualityChange_ = onQualityChange;
48
+ }
49
+
50
+ /** @override */
51
+ release() {
52
+ this.contentTypeStates_.clear();
53
+ }
54
+
55
+ /**
56
+ * Get the ContenTypeState for a contentType, creating a new
57
+ * one if necessary.
58
+ *
59
+ * @param {!string} contentType
60
+ * The contend type e.g. "video" or "audio".
61
+ * @return {!shaka.media.QualityObserver.ContentTypeState}
62
+ * @private
63
+ */
64
+ getContentTypeState_(contentType) {
65
+ let contentTypeState = this.contentTypeStates_.get(contentType);
66
+ if (!contentTypeState) {
67
+ contentTypeState = {
68
+ qualityChangePositions: [],
69
+ currentQuality: null,
70
+ contentType: contentType,
71
+ };
72
+ this.contentTypeStates_.set(contentType, contentTypeState);
73
+ }
74
+ return contentTypeState;
75
+ }
76
+
77
+ /**
78
+ * Adds a QualityChangePosition for the contentType identified by
79
+ * the mediaQuality.contentType.
80
+ *
81
+ * @param {!shaka.extern.MediaQualityInfo} mediaQuality
82
+ * @param {!number} position
83
+ * Position in seconds of the quality change.
84
+ */
85
+ addMediaQualityChange(mediaQuality, position) {
86
+ const contentTypeState =
87
+ this.getContentTypeState_(mediaQuality.contentType);
88
+
89
+ // Remove unneeded QualityChangePosition(s) before adding the new one
90
+ this.purgeQualityChangePositions_(contentTypeState);
91
+
92
+ const newChangePosition = {
93
+ mediaQuality: mediaQuality,
94
+ position: position,
95
+ };
96
+
97
+ const changePositions = contentTypeState.qualityChangePositions;
98
+ const insertBeforeIndex = changePositions.findIndex(
99
+ (qualityChange) => (qualityChange.position >= position));
100
+
101
+ if (insertBeforeIndex >= 0) {
102
+ const duplicatePositions =
103
+ (changePositions[insertBeforeIndex].position == position) ? 1 : 0;
104
+ changePositions.splice(
105
+ insertBeforeIndex, duplicatePositions, newChangePosition);
106
+ } else {
107
+ changePositions.push(newChangePosition);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Determines the media quality at a specific position in the source buffer.
113
+ *
114
+ * @param {!number} position
115
+ * Position in seconds
116
+ * @param {!shaka.media.QualityObserver.ContentTypeState} contentTypeState
117
+ * @return {?shaka.extern.MediaQualityInfo}
118
+ * @private
119
+ */
120
+ static getMediaQualityAtPosition_(position, contentTypeState) {
121
+ // The qualityChangePositions must be ordered by position ascending
122
+ // Find the last QualityChangePosition prior to the position
123
+ const changePositions = contentTypeState.qualityChangePositions;
124
+ for (let i = changePositions.length - 1; i >= 0; i--) {
125
+ const qualityChange = changePositions[i];
126
+ if (qualityChange.position <= position) {
127
+ return qualityChange.mediaQuality;
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+
133
+ /**
134
+ * Determines if two MediaQualityInfo objects are the same or not.
135
+ *
136
+ * @param {?shaka.extern.MediaQualityInfo} mq1
137
+ * @param {?shaka.extern.MediaQualityInfo} mq2
138
+ * @return {boolean}
139
+ * @private
140
+ */
141
+ static mediaQualitiesAreTheSame_(mq1, mq2) {
142
+ if (mq1 === mq2) {
143
+ return true;
144
+ }
145
+ if (!mq1 || !mq2) {
146
+ return false;
147
+ }
148
+ return (mq1.bandwidth == mq2.bandwidth) &&
149
+ (mq1.audioSamplingRate == mq2.audioSamplingRate) &&
150
+ (mq1.codecs == mq2.codecs) &&
151
+ (mq1.contentType == mq2.contentType) &&
152
+ (mq1.frameRate == mq2.frameRate) &&
153
+ (mq1.height == mq2.height) &&
154
+ (mq1.mimeType == mq2.mimeType) &&
155
+ (mq1.channelsCount == mq2.channelsCount) &&
156
+ (mq1.pixelAspectRatio == mq2.pixelAspectRatio) &&
157
+ (mq1.width == mq2.width);
158
+ }
159
+
160
+ /** @override */
161
+ poll(positionInSeconds, wasSeeking) {
162
+ for (const contentTypeState of this.contentTypeStates_.values()) {
163
+ const qualityAtPosition =
164
+ shaka.media.QualityObserver.getMediaQualityAtPosition_(
165
+ positionInSeconds, contentTypeState);
166
+ if (qualityAtPosition &&
167
+ !shaka.media.QualityObserver.mediaQualitiesAreTheSame_(
168
+ contentTypeState.currentQuality, qualityAtPosition)) {
169
+ if (this.positionIsBuffered_(
170
+ positionInSeconds, qualityAtPosition.contentType)) {
171
+ contentTypeState.currentQuality = qualityAtPosition;
172
+ shaka.log.debug('Media quality changed at position ' +
173
+ positionInSeconds + ' ' + JSON.stringify(qualityAtPosition));
174
+ this.onQualityChange_(
175
+ qualityAtPosition, positionInSeconds);
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Determine if a position is buffered for a given content type.
183
+ *
184
+ * @param {!number} position
185
+ * @param {!string} contentType
186
+ * @private
187
+ */
188
+ positionIsBuffered_(position, contentType) {
189
+ const bufferedInfo = this.getBufferedInfo_();
190
+ const bufferedRanges = bufferedInfo[contentType];
191
+ if (bufferedRanges && bufferedRanges.length > 0) {
192
+ const bufferStart = bufferedRanges[0].start;
193
+ const bufferEnd = bufferedRanges[bufferedRanges.length - 1].end;
194
+ if (position >= bufferStart && position < bufferEnd) {
195
+ return true;
196
+ }
197
+ }
198
+ return false;
199
+ }
200
+
201
+ /**
202
+ * Removes the QualityChangePosition(s) that are not relevant to the buffered
203
+ * content of the specified contentType. Note that this function is
204
+ * invoked just before adding the quality change info associated with
205
+ * the next media segment to be appended.
206
+ *
207
+ * @param {!shaka.media.QualityObserver.ContentTypeState} contentTypeState
208
+ * @private
209
+ */
210
+ purgeQualityChangePositions_(contentTypeState) {
211
+ const bufferedInfo = this.getBufferedInfo_();
212
+ const bufferedRanges = bufferedInfo[contentTypeState.contentType];
213
+
214
+ if (bufferedRanges && bufferedRanges.length > 0) {
215
+ const bufferStart = bufferedRanges[0].start;
216
+ const bufferEnd = bufferedRanges[bufferedRanges.length - 1].end;
217
+ const oldChangePositions = contentTypeState.qualityChangePositions;
218
+ contentTypeState.qualityChangePositions =
219
+ oldChangePositions.filter(
220
+ (qualityChange, index) => {
221
+ // Remove all but last quality change before bufferStart.
222
+ if ((qualityChange.position <= bufferStart) &&
223
+ (index + 1 < oldChangePositions.length) &&
224
+ (oldChangePositions[index + 1].position <= bufferStart)) {
225
+ return false;
226
+ }
227
+ // Remove all quality changes after bufferEnd.
228
+ if (qualityChange.position >= bufferEnd) {
229
+ return false;
230
+ }
231
+ return true;
232
+ });
233
+ } else {
234
+ // Nothing is buffered; so remove all quality changes.
235
+ contentTypeState.qualityChangePositions = [];
236
+ }
237
+ }
238
+ };
239
+
240
+ /**
241
+ * @typedef {function(shaka.extern.MediaQualityInfo, number)}
242
+ *
243
+ * @description
244
+ * A callback function used to notify the player when media quality changes
245
+ * are detected at the playhead.
246
+ *
247
+ * The first argument is information about media quality at the playhead
248
+ * position.
249
+ *
250
+ * The second argument is the playhead position in seconds.
251
+ */
252
+ shaka.media.QualityObserver.EventListener;
253
+
254
+ /**
255
+ * @typedef {{
256
+ * mediaQuality: !shaka.extern.MediaQualityInfo,
257
+ * position: !number
258
+ * }}
259
+ *
260
+ * @description
261
+ * Identifies the position of a media quality change in the
262
+ * source buffer.
263
+ *
264
+ * @property {shaka.extern.MediaQualityInfo} !mediaQuality
265
+ * The new media quality for content after position in the source buffer.
266
+ * @property {number} !position
267
+ * A position in seconds in the source buffer
268
+ */
269
+ shaka.media.QualityObserver.QualityChangePosition;
270
+
271
+ /**
272
+ * @typedef {{
273
+ * qualityChangePositions:
274
+ * !Array.<shaka.media.QualityObserver.QualityChangePosition>,
275
+ * currentQuality: ?shaka.extern.MediaQualityInfo,
276
+ * contentType: !string
277
+ * }}
278
+ *
279
+ * @description
280
+ * Contains media quality information for a specific content type
281
+ * e.g video or audio.
282
+ *
283
+ * @property {!Array.<shaka.media.QualityObserver.QualityChangePosition>}
284
+ * qualityChangePositions
285
+ * Quality changes ordered by position ascending.
286
+ * @property {?shaka.media.MediaQualityInfo} currentMediaQuality
287
+ * The media quality at the playhead position.
288
+ * @property {string} contentType
289
+ * The contentType e.g. 'video' or 'audio'
290
+ */
291
+ shaka.media.QualityObserver.ContentTypeState;
@@ -427,6 +427,12 @@ shaka.media.SegmentIndex = class {
427
427
  return new shaka.media.SegmentIterator(this, index, partialSegmentIndex);
428
428
  }
429
429
 
430
+ /**
431
+ * @return {boolean}
432
+ */
433
+ isEmpty() {
434
+ return this.references.length == 0;
435
+ }
430
436
 
431
437
  /**
432
438
  * Create a SegmentIndex for a single segment of the given start time and