shaka-player 4.9.26 → 4.9.28

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 (89) hide show
  1. package/dist/controls.css +2 -2
  2. package/dist/controls.css.map +1 -1
  3. package/dist/deps.js +1 -1
  4. package/dist/locales.js +54 -54
  5. package/dist/shaka-player.compiled.d.ts +5 -1
  6. package/dist/shaka-player.compiled.debug.d.ts +5 -1
  7. package/dist/shaka-player.compiled.debug.externs.js +6 -0
  8. package/dist/shaka-player.compiled.debug.js +124 -115
  9. package/dist/shaka-player.compiled.debug.map +4 -4
  10. package/dist/shaka-player.compiled.externs.js +6 -0
  11. package/dist/shaka-player.compiled.js +1113 -1105
  12. package/dist/shaka-player.compiled.map +4 -4
  13. package/dist/shaka-player.ui.d.ts +5 -1
  14. package/dist/shaka-player.ui.debug.d.ts +5 -1
  15. package/dist/shaka-player.ui.debug.externs.js +6 -0
  16. package/dist/shaka-player.ui.debug.js +367 -355
  17. package/dist/shaka-player.ui.debug.map +4 -4
  18. package/dist/shaka-player.ui.externs.js +6 -0
  19. package/dist/shaka-player.ui.js +1285 -1276
  20. package/dist/shaka-player.ui.map +4 -4
  21. package/externs/ima.js +22 -2
  22. package/externs/shaka/player.js +13 -2
  23. package/lib/dash/dash_parser.js +17 -9
  24. package/lib/dash/mpd_utils.js +0 -3
  25. package/lib/hls/hls_parser.js +18 -4
  26. package/lib/media/gap_jumping_controller.js +4 -2
  27. package/lib/media/segment_index.js +18 -6
  28. package/lib/media/streaming_engine.js +22 -9
  29. package/lib/net/networking_utils.js +5 -0
  30. package/lib/offline/storage.js +12 -10
  31. package/lib/player.js +13 -4
  32. package/lib/text/simple_text_displayer.js +1 -1
  33. package/lib/text/text_utils.js +4 -0
  34. package/lib/text/ttml_text_parser.js +26 -5
  35. package/lib/util/periods.js +60 -4
  36. package/lib/util/player_configuration.js +5 -0
  37. package/lib/util/tXml.js +17 -9
  38. package/package.json +1 -1
  39. package/ui/controls.js +14 -2
  40. package/ui/less/overflow_menu.less +1 -0
  41. package/ui/less/tooltip.less +1 -1
  42. package/ui/locales/README.md +16 -0
  43. package/ui/locales/ar-XB.json +1 -1
  44. package/ui/locales/ar.json +1 -1
  45. package/ui/locales/bg.json +1 -1
  46. package/ui/locales/ca.json +5 -5
  47. package/ui/locales/cs.json +1 -1
  48. package/ui/locales/da.json +1 -1
  49. package/ui/locales/de.json +1 -1
  50. package/ui/locales/el.json +1 -1
  51. package/ui/locales/en-GB.json +1 -1
  52. package/ui/locales/en-XA.json +1 -1
  53. package/ui/locales/es-419.json +4 -4
  54. package/ui/locales/es.json +3 -3
  55. package/ui/locales/fa.json +1 -1
  56. package/ui/locales/fi.json +1 -1
  57. package/ui/locales/fil.json +1 -1
  58. package/ui/locales/fr.json +6 -6
  59. package/ui/locales/hi.json +1 -1
  60. package/ui/locales/hr.json +1 -1
  61. package/ui/locales/hu.json +1 -1
  62. package/ui/locales/id.json +1 -1
  63. package/ui/locales/it.json +1 -1
  64. package/ui/locales/iw.json +1 -1
  65. package/ui/locales/ja.json +1 -1
  66. package/ui/locales/ko.json +1 -1
  67. package/ui/locales/lt.json +1 -1
  68. package/ui/locales/lv.json +1 -1
  69. package/ui/locales/nl.json +1 -1
  70. package/ui/locales/no.json +1 -1
  71. package/ui/locales/pl.json +6 -6
  72. package/ui/locales/pt-BR.json +6 -6
  73. package/ui/locales/pt-PT.json +7 -7
  74. package/ui/locales/ro.json +1 -1
  75. package/ui/locales/ru.json +1 -1
  76. package/ui/locales/sjn-translations.yaml +50 -4
  77. package/ui/locales/sjn.json +8 -1
  78. package/ui/locales/sk.json +1 -1
  79. package/ui/locales/sl.json +1 -1
  80. package/ui/locales/sr.json +1 -1
  81. package/ui/locales/sv.json +1 -1
  82. package/ui/locales/sync-sjn.py +41 -0
  83. package/ui/locales/th.json +1 -1
  84. package/ui/locales/tr.json +1 -1
  85. package/ui/locales/uk.json +1 -1
  86. package/ui/locales/vi.json +1 -1
  87. package/ui/locales/zh-HK.json +1 -1
  88. package/ui/locales/zh-TW.json +1 -1
  89. package/ui/locales/zh.json +1 -1
package/externs/ima.js CHANGED
@@ -269,7 +269,6 @@ google.ima.Ad = class {
269
269
  getMediaUrl() {}
270
270
  };
271
271
 
272
-
273
272
  /** @const */
274
273
  google.ima.AdPodInfo = class {
275
274
  /** @return {number} */
@@ -366,7 +365,7 @@ google.ima.AdEvent.Type = {
366
365
  * @description Request for the ad server
367
366
  * @property {string|undefined} adTagUrl
368
367
  * Specifies the ad tag url that is requested from the ad server.
369
- * This parameter is optional if adsReponse is given.
368
+ * This parameter is optional if adsResponse is given.
370
369
  * @property {string|undefined} adsResponse
371
370
  * Specifies a VAST 2.0 document to be used as the ads response instead of
372
371
  * making a request via an ad tag url. This can be useful for debugging
@@ -518,6 +517,9 @@ google.ima.dai.api.Ad = class {
518
517
 
519
518
  /** @return {string} */
520
519
  getCreativeAdId() {}
520
+
521
+ /** @return {!Array.<!google.ima.dai.api.CompanionAd>} */
522
+ getCompanionAds() {}
521
523
  };
522
524
 
523
525
 
@@ -536,6 +538,24 @@ google.ima.dai.api.AdPodInfo = class {
536
538
  getPodIndex() {}
537
539
  };
538
540
 
541
+ /** @const */
542
+ google.ima.dai.api.CompanionAd = class {
543
+ /** @return {?string} */
544
+ getAdSlotId() {}
545
+
546
+ /** @return {string} */
547
+ getContent() {}
548
+
549
+ /** @return {?string} */
550
+ getContentType() {}
551
+
552
+ /** @return {number} */
553
+ getHeight() {}
554
+
555
+ /** @return {?number} */
556
+ getWidth() {}
557
+ };
558
+
539
559
 
540
560
  /** @const */
541
561
  google.ima.dai.api.CuePoint = class {};
@@ -1239,6 +1239,7 @@ shaka.extern.ManifestConfiguration;
1239
1239
  * alwaysStreamText: boolean,
1240
1240
  * startAtSegmentBoundary: boolean,
1241
1241
  * gapDetectionThreshold: number,
1242
+ * gapPadding: number,
1242
1243
  * gapJumpTimerTime: number,
1243
1244
  * durationBackoff: number,
1244
1245
  * safeSeekOffset: number,
@@ -1323,7 +1324,16 @@ shaka.extern.ManifestConfiguration;
1323
1324
  * to <code>false</code>.
1324
1325
  * @property {number} gapDetectionThreshold
1325
1326
  * The maximum distance (in seconds) before a gap when we'll automatically
1326
- * jump. This value defaults to <code>0.5</code>.
1327
+ * jump.
1328
+ * <br>
1329
+ * Defaults to <code>0.5</code>.
1330
+ * @property {number} gapPadding
1331
+ * Padding added only for Xbox, Legacy Edge and Tizen.
1332
+ * Based on our research (specific to Tizen), the gapPadding value must be
1333
+ * greater than your GOP length.
1334
+ * It’s crucial to verify this value according to your actual stream.
1335
+ * <br>
1336
+ * Defaults to <code>0.01</code> for Xbox and Legacy Edge, Tizen at 2.
1327
1337
  * @property {number} gapJumpTimerTime
1328
1338
  * The polling time in seconds to check for gaps in the media. This value
1329
1339
  * defaults to <code>0.25</code>.
@@ -1837,7 +1847,8 @@ shaka.extern.LcevcConfiguration;
1837
1847
  * view.
1838
1848
  * Defaults to <code>true</code>.
1839
1849
  * @property {number} numberOfParallelDownloads
1840
- * Number of parallel downloads.
1850
+ * Number of parallel downloads. If the value is 0, downloads will be
1851
+ * sequential for each stream.
1841
1852
  * Note: normally browsers limit to 5 request in parallel, so putting a
1842
1853
  * number higher than this will not help it download faster.
1843
1854
  * Defaults to <code>5</code>.
@@ -163,6 +163,9 @@ shaka.dash.DashParser = class {
163
163
 
164
164
  /** @private {boolean} */
165
165
  this.isTransitionFromDynamicToStatic_ = false;
166
+
167
+ /** @private {?Array.<string>} */
168
+ this.lastCalculatedBaseUris_ = [];
166
169
  }
167
170
 
168
171
  /**
@@ -551,7 +554,6 @@ shaka.dash.DashParser = class {
551
554
  }
552
555
 
553
556
  const uriObjs = TXml.findChildren(mpd, 'BaseURL');
554
- let calculatedBaseUris;
555
557
  let someLocationValid = false;
556
558
  if (this.contentSteeringManager_) {
557
559
  for (const uriObj of uriObjs) {
@@ -564,9 +566,10 @@ shaka.dash.DashParser = class {
564
566
  }
565
567
  }
566
568
  }
569
+ this.lastCalculatedBaseUris_ = null;
567
570
  if (!someLocationValid || !this.contentSteeringManager_) {
568
571
  const uris = uriObjs.map(TXml.getContents);
569
- calculatedBaseUris = shaka.util.ManifestParserUtils.resolveUris(
572
+ this.lastCalculatedBaseUris_ = shaka.util.ManifestParserUtils.resolveUris(
570
573
  manifestBaseUris, uris);
571
574
  }
572
575
 
@@ -574,8 +577,8 @@ shaka.dash.DashParser = class {
574
577
  if (this.contentSteeringManager_ && someLocationValid) {
575
578
  return this.contentSteeringManager_.getLocations('BaseURL');
576
579
  }
577
- if (calculatedBaseUris) {
578
- return calculatedBaseUris;
580
+ if (this.lastCalculatedBaseUris_) {
581
+ return this.lastCalculatedBaseUris_;
579
582
  }
580
583
  return [];
581
584
  };
@@ -1430,14 +1433,14 @@ shaka.dash.DashParser = class {
1430
1433
  */
1431
1434
  cleanStreamMap_() {
1432
1435
  const oldPeriodIds = Object.keys(this.indexStreamMap_);
1433
- if (oldPeriodIds.length === this.lastManifestUpdatePeriodIds_.length) {
1434
- return;
1435
- }
1436
1436
  const diffPeriodsIDs = oldPeriodIds.filter((pId) => {
1437
1437
  return !this.lastManifestUpdatePeriodIds_.includes(pId);
1438
1438
  });
1439
1439
  for (const pId of diffPeriodsIDs) {
1440
1440
  for (const contextId of this.indexStreamMap_[pId]) {
1441
+ if (this.periodCombiner_) {
1442
+ this.periodCombiner_.deleteStream(this.streamMap_[contextId], pId);
1443
+ }
1441
1444
  delete this.streamMap_[contextId];
1442
1445
  }
1443
1446
  delete this.indexStreamMap_[pId];
@@ -2804,6 +2807,8 @@ shaka.dash.DashParser = class {
2804
2807
  const schemeIdUri = elem.attributes['schemeIdUri'] || '';
2805
2808
  const value = elem.attributes['value'] || '';
2806
2809
  const timescale = TXml.parseAttr(elem, 'timescale', parseNumber) || 1;
2810
+ const presentationTimeOffset =
2811
+ TXml.parseAttr(elem, 'presentationTimeOffset', parseNumber) || 0;
2807
2812
 
2808
2813
  for (const eventNode of TXml.findChildren(elem, 'Event')) {
2809
2814
  const presentationTime =
@@ -2811,7 +2816,10 @@ shaka.dash.DashParser = class {
2811
2816
  const duration =
2812
2817
  TXml.parseAttr(eventNode, 'duration', parseNumber) || 0;
2813
2818
 
2814
- let startTime = presentationTime / timescale + periodStart;
2819
+ // Ensure start time won't be lower than period start.
2820
+ let startTime = Math.max(
2821
+ (presentationTime - presentationTimeOffset) / timescale + periodStart,
2822
+ periodStart);
2815
2823
  let endTime = startTime + (duration / timescale);
2816
2824
  if (periodDuration != null) {
2817
2825
  // An event should not go past the Period, even if the manifest says so.
@@ -2833,7 +2841,7 @@ shaka.dash.DashParser = class {
2833
2841
  endTime: endTime,
2834
2842
  id: eventNode.attributes['id'] || '',
2835
2843
  eventElement: TXml.txmlNodeToDomElement(eventNode),
2836
- eventNode: eventNode,
2844
+ eventNode: TXml.cloneNode(eventNode),
2837
2845
  };
2838
2846
 
2839
2847
  this.playerInterface_.onTimelineRegionAdded(region);
@@ -481,9 +481,6 @@ shaka.dash.MpdUtils = class {
481
481
  // Move the children of the loaded xml into the current element.
482
482
  while (rootElem.children.length) {
483
483
  const child = rootElem.children.shift();
484
- if (TXml.isNode(child)) {
485
- child.parent = element;
486
- }
487
484
  element.children.push(child);
488
485
  }
489
486
 
@@ -147,6 +147,14 @@ shaka.hls.HlsParser = class {
147
147
  */
148
148
  this.lowestSyncTime_ = Infinity;
149
149
 
150
+ /**
151
+ * Flag to indicate if any of the media playlists use
152
+ * EXT-X-PROGRAM-DATE-TIME.
153
+ *
154
+ * @private {boolean}
155
+ */
156
+ this.usesProgramDateTime_ = false;
157
+
150
158
  /**
151
159
  * Whether the streams have previously been "finalized"; that is to say,
152
160
  * whether we have loaded enough streams to know information about the asset
@@ -545,9 +553,10 @@ shaka.hls.HlsParser = class {
545
553
  * Align the streams by sequence number by dropping early segments. Then
546
554
  * offset the streams to begin at presentation time 0.
547
555
  * @param {!Array.<!shaka.hls.HlsParser.StreamInfo>} streamInfos
556
+ * @param {boolean=} force
548
557
  * @private
549
558
  */
550
- syncStreamsWithSequenceNumber_(streamInfos) {
559
+ syncStreamsWithSequenceNumber_(streamInfos, force = false) {
551
560
  // We assume that, when this is first called, we have enough info to
552
561
  // determine how to use the program date times (e.g. we have both a video
553
562
  // and an audio, and all other videos and audios match those).
@@ -602,7 +611,7 @@ shaka.hls.HlsParser = class {
602
611
  this.minSequenceNumber_);
603
612
 
604
613
  for (const streamInfo of streamInfos) {
605
- if (!this.ignoreManifestProgramDateTimeFor_(streamInfo.type)) {
614
+ if (!this.ignoreManifestProgramDateTimeFor_(streamInfo.type) && !force) {
606
615
  continue;
607
616
  }
608
617
  const segmentIndex = streamInfo.stream.segmentIndex;
@@ -2573,8 +2582,12 @@ shaka.hls.HlsParser = class {
2573
2582
  }
2574
2583
  }
2575
2584
  this.notifySegmentsForStreams_(streamInfos.map((s) => s.stream));
2576
- if (this.config_.hls.ignoreManifestProgramDateTime) {
2577
- this.syncStreamsWithSequenceNumber_(streamInfos);
2585
+ const liveWithNoProgramaDateTime =
2586
+ this.isLive_() && !this.usesProgramDateTime_;
2587
+ if (this.config_.hls.ignoreManifestProgramDateTime ||
2588
+ liveWithNoProgramaDateTime) {
2589
+ this.syncStreamsWithSequenceNumber_(
2590
+ streamInfos, liveWithNoProgramaDateTime);
2578
2591
  } else {
2579
2592
  this.syncStreamsWithProgramDateTime_(streamInfos);
2580
2593
  if (this.config_.hls.ignoreManifestProgramDateTimeForTypes.length > 0) {
@@ -3367,6 +3380,7 @@ shaka.hls.HlsParser = class {
3367
3380
  syncTime = shaka.util.TXml.parseDate(dateTimeTag.value);
3368
3381
  goog.asserts.assert(syncTime != null,
3369
3382
  'EXT-X-PROGRAM-DATE-TIME format not valid');
3383
+ this.usesProgramDateTime_ = true;
3370
3384
  }
3371
3385
  }
3372
3386
 
@@ -223,8 +223,10 @@ shaka.media.GapJumpingController = class {
223
223
  // often rounds value we want to set as currentTime and we are not able
224
224
  // to jump over the gap.
225
225
  if (shaka.util.Platform.isLegacyEdge() ||
226
- shaka.util.Platform.isXboxOne()) {
227
- jumpTo = Math.ceil((jumpTo + 0.01) * 100) / 100;
226
+ shaka.util.Platform.isXboxOne() ||
227
+ shaka.util.Platform.isTizen()) {
228
+ const gapPadding = this.config_.gapPadding;
229
+ jumpTo = Math.ceil((jumpTo + gapPadding) * 100) / 100;
228
230
  }
229
231
  const seekEnd = this.timeline_.getSeekRangeEnd();
230
232
  if (jumpTo >= seekEnd) {
@@ -700,6 +700,17 @@ shaka.media.MetaSegmentIndex = class extends shaka.media.SegmentIndex {
700
700
  this.indexes_ = [];
701
701
  }
702
702
 
703
+ /** Evicts all old SegmentIndexes in this MetaSegmentIndex that are empty. */
704
+ evictEmpty() {
705
+ while (this.indexes_.length > 1 && this.indexes_[0].isEmpty()) {
706
+ const index = this.indexes_.shift();
707
+ // In the case of this class, this.numEvicted_ represents the evicted
708
+ // segments that were in indexes that were entirely evicted.
709
+ this.numEvicted_ += index.getNumEvicted();
710
+ index.release();
711
+ }
712
+ }
713
+
703
714
  /**
704
715
  * Append a SegmentIndex to this MetaSegmentIndex. This effectively stitches
705
716
  * the underlying Stream onto the end of the multi-Period Stream represented
@@ -724,6 +735,7 @@ shaka.media.MetaSegmentIndex = class extends shaka.media.SegmentIndex {
724
735
  // Be careful to clone the Array. We don't want to share the reference with
725
736
  // our clone and affect each other accidentally.
726
737
  clone.indexes_ = this.indexes_.slice();
738
+ clone.numEvicted_ = this.numEvicted_;
727
739
  return clone;
728
740
  }
729
741
 
@@ -754,7 +766,7 @@ shaka.media.MetaSegmentIndex = class extends shaka.media.SegmentIndex {
754
766
  * @export
755
767
  */
756
768
  find(time) {
757
- let numPassedInEarlierIndexes = 0;
769
+ let numPassedInEarlierIndexes = this.numEvicted_;
758
770
 
759
771
  for (const index of this.indexes_) {
760
772
  const position = index.find(time);
@@ -775,7 +787,7 @@ shaka.media.MetaSegmentIndex = class extends shaka.media.SegmentIndex {
775
787
  * @export
776
788
  */
777
789
  get(position) {
778
- let numPassedInEarlierIndexes = 0;
790
+ let numPassedInEarlierIndexes = this.numEvicted_;
779
791
  let sawSegments = false;
780
792
 
781
793
  for (const index of this.indexes_) {
@@ -822,10 +834,10 @@ shaka.media.MetaSegmentIndex = class extends shaka.media.SegmentIndex {
822
834
  * @export
823
835
  */
824
836
  evict(time) {
825
- // evict() is only used internally by the DASH and HLS parser on
826
- // SegmentIndexes, but never on MetaSegmentIndex.
827
- goog.asserts.assert(
828
- false, 'evict() should not be used in MetaSegmentIndex!');
837
+ for (const index of this.indexes_) {
838
+ index.evict(time);
839
+ }
840
+ this.evictEmpty();
829
841
  }
830
842
 
831
843
  /**
@@ -15,6 +15,7 @@ goog.require('shaka.log');
15
15
  goog.require('shaka.media.InitSegmentReference');
16
16
  goog.require('shaka.media.ManifestParser');
17
17
  goog.require('shaka.media.MediaSourceEngine');
18
+ goog.require('shaka.media.MetaSegmentIndex');
18
19
  goog.require('shaka.media.SegmentIterator');
19
20
  goog.require('shaka.media.SegmentReference');
20
21
  goog.require('shaka.media.SegmentPrefetch');
@@ -1513,9 +1514,11 @@ shaka.media.StreamingEngine = class {
1513
1514
  logPrefix, 'looking up segment from new stream endTime:', time);
1514
1515
 
1515
1516
  const reverse = this.playerInterface_.getPlaybackRate() < 0;
1516
- mediaState.segmentIterator =
1517
- mediaState.stream.segmentIndex.getIteratorForTime(
1518
- time, /* allowNonIndepedent= */ false, reverse);
1517
+ if (mediaState.stream.segmentIndex) {
1518
+ mediaState.segmentIterator =
1519
+ mediaState.stream.segmentIndex.getIteratorForTime(
1520
+ time, /* allowNonIndepedent= */ false, reverse);
1521
+ }
1519
1522
  const ref = mediaState.segmentIterator &&
1520
1523
  mediaState.segmentIterator.next().value;
1521
1524
  if (ref == null) {
@@ -1539,18 +1542,22 @@ shaka.media.StreamingEngine = class {
1539
1542
  const reverse = this.playerInterface_.getPlaybackRate() < 0;
1540
1543
  let ref = null;
1541
1544
  if (inaccurateTolerance) {
1542
- mediaState.segmentIterator =
1543
- mediaState.stream.segmentIndex.getIteratorForTime(
1544
- lookupTime, /* allowNonIndepedent= */ false, reverse);
1545
+ if (mediaState.stream.segmentIndex) {
1546
+ mediaState.segmentIterator =
1547
+ mediaState.stream.segmentIndex.getIteratorForTime(
1548
+ lookupTime, /* allowNonIndepedent= */ false, reverse);
1549
+ }
1545
1550
  ref = mediaState.segmentIterator &&
1546
1551
  mediaState.segmentIterator.next().value;
1547
1552
  }
1548
1553
  if (!ref) {
1549
1554
  // If we can't find a valid segment with the drifted time, look for a
1550
1555
  // segment with the presentation time.
1551
- mediaState.segmentIterator =
1552
- mediaState.stream.segmentIndex.getIteratorForTime(
1553
- presentationTime, /* allowNonIndepedent= */ false, reverse);
1556
+ if (mediaState.stream.segmentIndex) {
1557
+ mediaState.segmentIterator =
1558
+ mediaState.stream.segmentIndex.getIteratorForTime(
1559
+ presentationTime, /* allowNonIndepedent= */ false, reverse);
1560
+ }
1554
1561
  ref = mediaState.segmentIterator &&
1555
1562
  mediaState.segmentIterator.next().value;
1556
1563
  }
@@ -2465,6 +2472,12 @@ shaka.media.StreamingEngine = class {
2465
2472
  * @private
2466
2473
  */
2467
2474
  async evict_(mediaState, presentationTime) {
2475
+ const segmentIndex = mediaState.stream.segmentIndex;
2476
+ if (segmentIndex instanceof shaka.media.MetaSegmentIndex) {
2477
+ segmentIndex.evict(
2478
+ this.manifest_.presentationTimeline.getSeekRangeStart());
2479
+ }
2480
+
2468
2481
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
2469
2482
  shaka.log.v2(logPrefix, 'checking buffer length');
2470
2483
 
@@ -103,4 +103,9 @@ shaka.net.NetworkingUtils.EXTENSIONS_TO_MIME_TYPES_ = {
103
103
  'lrc': 'application/x-subtitle-lrc',
104
104
  'ssa': 'text/x-ssa',
105
105
  'ass': 'text/x-ssa',
106
+ 'jpg': 'image/jpeg',
107
+ 'png': 'image/png',
108
+ 'svg': 'image/svg+xml',
109
+ 'webp': 'image/webp',
110
+ 'avif': 'image/avif',
106
111
  };
@@ -1343,7 +1343,7 @@ shaka.offline.Storage = class {
1343
1343
  manifest.presentationTimeline.getSegmentAvailabilityStart();
1344
1344
 
1345
1345
  const numberOfParallelDownloads = config.offline.numberOfParallelDownloads;
1346
- let groupId = 0;
1346
+ let groupId = numberOfParallelDownloads === 0 ? stream.id : 0;
1347
1347
 
1348
1348
  shaka.offline.Storage.forEachSegment_(stream, startTime, (segment) => {
1349
1349
  const pendingSegmentRefId =
@@ -1395,7 +1395,9 @@ shaka.offline.Storage = class {
1395
1395
  codecs: segment.codecs,
1396
1396
  };
1397
1397
  streamDb.segments.push(segmentDB);
1398
- groupId = (groupId + 1) % numberOfParallelDownloads;
1398
+ if (numberOfParallelDownloads !== 0) {
1399
+ groupId = (groupId + 1) % numberOfParallelDownloads;
1400
+ }
1399
1401
  });
1400
1402
 
1401
1403
  return streamDb;
@@ -1590,14 +1592,6 @@ shaka.offline.Storage = class {
1590
1592
  /** @type {!Set.<shaka.extern.Stream>} */
1591
1593
  const set = new Set();
1592
1594
 
1593
- for (const text of manifest.textStreams) {
1594
- set.add(text);
1595
- }
1596
-
1597
- for (const image of manifest.imageStreams) {
1598
- set.add(image);
1599
- }
1600
-
1601
1595
  for (const variant of manifest.variants) {
1602
1596
  if (variant.audio) {
1603
1597
  set.add(variant.audio);
@@ -1607,6 +1601,14 @@ shaka.offline.Storage = class {
1607
1601
  }
1608
1602
  }
1609
1603
 
1604
+ for (const text of manifest.textStreams) {
1605
+ set.add(text);
1606
+ }
1607
+
1608
+ for (const image of manifest.imageStreams) {
1609
+ set.add(image);
1610
+ }
1611
+
1610
1612
  return set;
1611
1613
  }
1612
1614
 
package/lib/player.js CHANGED
@@ -5566,6 +5566,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5566
5566
  references.push(reference);
5567
5567
  }
5568
5568
 
5569
+ let segmentMimeType = mimeType;
5570
+ if (references.length) {
5571
+ segmentMimeType = await shaka.net.NetworkingUtils.getMimeType(
5572
+ references[0].getUris()[0],
5573
+ this.networkingEngine_, this.config_.manifest.retryParameters);
5574
+ }
5575
+
5569
5576
  /** @type {shaka.extern.Stream} */
5570
5577
  const stream = {
5571
5578
  id: this.nextExternalStreamId_++,
@@ -5573,7 +5580,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5573
5580
  groupId: null,
5574
5581
  createSegmentIndex: () => Promise.resolve(),
5575
5582
  segmentIndex: new shaka.media.SegmentIndex(references),
5576
- mimeType: mimeType || '',
5583
+ mimeType: segmentMimeType || '',
5577
5584
  codecs: '',
5578
5585
  kind: '',
5579
5586
  encrypted: false,
@@ -5597,7 +5604,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5597
5604
  external: true,
5598
5605
  fastSwitching: false,
5599
5606
  fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType(
5600
- mimeType || '', '')]),
5607
+ segmentMimeType || '', '')]),
5601
5608
  };
5602
5609
 
5603
5610
  if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
@@ -6740,11 +6747,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6740
6747
  this.playhead_.notifyOfBufferingChange();
6741
6748
  // Skip the initial buffer gap
6742
6749
  const startTime = this.mediaSourceEngine_.bufferStart(contentType);
6750
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
6743
6751
  if (
6744
6752
  !this.isLive() &&
6745
6753
  // If not paused then GapJumpingController will handle this gap.
6746
6754
  this.video_.paused &&
6747
6755
  startTime != null &&
6756
+ contentType != ContentType.TEXT &&
6748
6757
  startTime > 0 &&
6749
6758
  this.playhead_.getTime() < startTime
6750
6759
  ) {
@@ -7517,8 +7526,8 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7517
7526
  * @define {string} A version number taken from git at compile time.
7518
7527
  * @export
7519
7528
  */
7520
- // eslint-disable-next-line no-useless-concat
7521
- shaka.Player.version = 'v4.9.26' + '-uncompiled'; // x-release-please-version
7529
+ // eslint-disable-next-line no-useless-concat, max-len
7530
+ shaka.Player.version = 'v4.9.28' + '-uncompiled'; // x-release-please-version
7522
7531
 
7523
7532
  // Initialize the deprecation system using the version string we just set
7524
7533
  // on the player.
@@ -112,7 +112,7 @@ shaka.text.SimpleTextDisplayer = class {
112
112
  return false;
113
113
  });
114
114
 
115
- if (!containsCue) {
115
+ if (!containsCue && inCue.payload) {
116
116
  const cue =
117
117
  shaka.text.Utils.mapShakaCueToNativeCue(inCue);
118
118
  if (cue) {
@@ -31,6 +31,10 @@ shaka.text.Utils = class {
31
31
  }).join('');
32
32
  }
33
33
 
34
+ if (!cue.payload) {
35
+ return cue.payload;
36
+ }
37
+
34
38
  // Handle bold, italics and underline
35
39
  const openStyleTags = [];
36
40
  const bold = cue.fontWeight >= shaka.text.Cue.fontWeight.BOLD;
@@ -65,7 +65,7 @@ shaka.text.TtmlTextParser = class {
65
65
  return cues;
66
66
  }
67
67
 
68
- const tt = TXml.parseXmlString(str, 'tt');
68
+ const tt = TXml.parseXmlString(str, 'tt', /* includeParent= */ true);
69
69
  if (!tt) {
70
70
  throw new shaka.util.Error(
71
71
  shaka.util.Error.Severity.CRITICAL,
@@ -108,7 +108,8 @@ shaka.text.TtmlTextParser = class {
108
108
  TtmlTextParser.getCellResolution_(cellResolution);
109
109
 
110
110
  const metadata = TXml.getElementsByTagName(tt, 'metadata')[0];
111
- const metadataElements = metadata ? metadata.children : [];
111
+ const metadataElements =
112
+ (metadata ? metadata.children : []).filter((c) => c != '\n');
112
113
  const styles = TXml.getElementsByTagName(tt, 'style');
113
114
  const regionElements = TXml.getElementsByTagName(tt, 'region');
114
115
 
@@ -508,6 +509,24 @@ shaka.text.TtmlTextParser = class {
508
509
  return region;
509
510
  }
510
511
 
512
+ /**
513
+ * Ensures any TTML RGBA's alpha range of 0-255 is converted to 0-1.
514
+ * @param {string} color
515
+ * @return {string}
516
+ * @private
517
+ */
518
+ static convertTTMLrgbaToHTMLrgba_(color) {
519
+ const rgba = color.match(/rgba\(([^)]+)\)/);
520
+ if (rgba) {
521
+ const values = rgba[1].split(',');
522
+ if (values.length == 4) {
523
+ values[3] = String(Number(values[3]) / 255);
524
+ return 'rgba(' + values.join(',') + ')';
525
+ }
526
+ }
527
+ return color;
528
+ }
529
+
511
530
  /**
512
531
  * Adds applicable style properties to a cue.
513
532
  *
@@ -586,7 +605,7 @@ shaka.text.TtmlTextParser = class {
586
605
  const color = TtmlTextParser.getStyleAttribute_(
587
606
  cueElement, region, styles, 'color', shouldInheritRegionStyles);
588
607
  if (color) {
589
- cue.color = color;
608
+ cue.color = TtmlTextParser.convertTTMLrgbaToHTMLrgba_(color);
590
609
  }
591
610
 
592
611
  // Background color should not be set on a container. If this is a nested
@@ -600,7 +619,8 @@ shaka.text.TtmlTextParser = class {
600
619
  cueElement, region, styles, 'backgroundColor',
601
620
  shouldInheritRegionStyles);
602
621
  if (backgroundColor) {
603
- cue.backgroundColor = backgroundColor;
622
+ cue.backgroundColor =
623
+ TtmlTextParser.convertTTMLrgbaToHTMLrgba_(backgroundColor);
604
624
  }
605
625
 
606
626
  const border = TtmlTextParser.getStyleAttribute_(
@@ -709,7 +729,8 @@ shaka.text.TtmlTextParser = class {
709
729
  // There is no defined color, so default to the text color.
710
730
  cue.textStrokeColor = cue.color;
711
731
  } else {
712
- cue.textStrokeColor = split[0];
732
+ cue.textStrokeColor =
733
+ TtmlTextParser.convertTTMLrgbaToHTMLrgba_(split[0]);
713
734
  split.shift();
714
735
  }
715
736
  if (split[0] && split[0].match(TtmlTextParser.unitValues_)) {