shaka-player 4.2.11 → 4.2.13

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 (73) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +11 -0
  2. package/.github/workflows/appspot.yaml +39 -0
  3. package/.github/workflows/build-and-test.yaml +15 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +3 -2
  5. package/.github/workflows/update-issues.yaml +1 -0
  6. package/CHANGELOG.md +42 -0
  7. package/CONTRIBUTORS +1 -0
  8. package/demo/custom.js +1 -1
  9. package/dist/demo.compiled.debug.js +2 -2
  10. package/dist/demo.compiled.debug.map +2 -2
  11. package/dist/demo.compiled.js +2 -2
  12. package/dist/demo.compiled.map +2 -2
  13. package/dist/deps.js +3 -3
  14. package/dist/locales.js +33 -33
  15. package/dist/shaka-player.compiled.d.ts +22 -0
  16. package/dist/shaka-player.compiled.debug.d.ts +22 -0
  17. package/dist/shaka-player.compiled.debug.js +233 -232
  18. package/dist/shaka-player.compiled.debug.map +3 -3
  19. package/dist/shaka-player.compiled.js +738 -738
  20. package/dist/shaka-player.compiled.map +3 -3
  21. package/dist/shaka-player.ui.d.ts +22 -0
  22. package/dist/shaka-player.ui.debug.d.ts +22 -0
  23. package/dist/shaka-player.ui.debug.js +259 -258
  24. package/dist/shaka-player.ui.debug.map +3 -3
  25. package/dist/shaka-player.ui.js +917 -916
  26. package/dist/shaka-player.ui.map +4 -4
  27. package/docs/tutorials/drm-config.md +9 -6
  28. package/docs/tutorials/selenium-grid-config.md +60 -8
  29. package/externs/ima.js +34 -0
  30. package/karma.conf.js +50 -7
  31. package/lib/abr/simple_abr_manager.js +6 -6
  32. package/lib/cea/mp4_cea_parser.js +30 -14
  33. package/lib/dash/dash_parser.js +18 -3
  34. package/lib/hls/hls_parser.js +13 -2
  35. package/lib/media/drm_engine.js +14 -8
  36. package/lib/media/gap_jumping_controller.js +24 -2
  37. package/lib/media/media_source_engine.js +5 -3
  38. package/lib/media/playhead.js +11 -0
  39. package/lib/media/region_observer.js +19 -1
  40. package/lib/media/streaming_engine.js +7 -3
  41. package/lib/media/video_wrapper.js +6 -1
  42. package/lib/player.js +9 -8
  43. package/lib/polyfill/abort_controller.js +7 -0
  44. package/lib/polyfill/orientation.js +34 -7
  45. package/lib/text/vtt_text_parser.js +3 -1
  46. package/lib/util/manifest_parser_utils.js +5 -3
  47. package/lib/util/mp4_box_parsers.js +8 -3
  48. package/lib/util/platform.js +14 -0
  49. package/lib/util/stream_utils.js +70 -26
  50. package/package.json +2 -3
  51. package/test/cast/cast_utils_unit.js +4 -1
  52. package/test/cea/mp4_cea_parser_unit.js +26 -3
  53. package/test/dash/dash_parser_manifest_unit.js +120 -0
  54. package/test/hls/hls_parser_unit.js +222 -0
  55. package/test/media/drm_engine_integration.js +2 -2
  56. package/test/media/region_observer_unit.js +2 -1
  57. package/test/media/segment_index_unit.js +3 -3
  58. package/test/media/streaming_engine_integration.js +14 -11
  59. package/test/offline/offline_integration.js +6 -3
  60. package/test/player_external.js +19 -16
  61. package/test/player_integration.js +35 -48
  62. package/test/player_src_equals_integration.js +9 -5
  63. package/test/test/assets/multiple-trun-init.mp4 +0 -0
  64. package/test/test/assets/multiple-trun-segment.mp4 +0 -0
  65. package/test/test/util/fake_demo_main.js +7 -3
  66. package/test/test/util/layout_tests.js +2 -2
  67. package/test/test/util/ui_utils.js +9 -2
  68. package/test/test/util/util.js +6 -27
  69. package/test/test/util/waiter.js +73 -3
  70. package/test/ui/ad_ui_unit.js +9 -9
  71. package/test/util/stream_utils_unit.js +46 -0
  72. package/ui/range_element.js +5 -0
  73. package/ui/resolution_selection.js +7 -1
@@ -251,6 +251,7 @@ shaka.media.MediaSourcePlayhead = class {
251
251
  this.videoWrapper_ = new shaka.media.VideoWrapper(
252
252
  mediaElement,
253
253
  () => this.onSeeking_(),
254
+ (realStartTime) => this.onStarted_(realStartTime),
254
255
  this.getStartTime_(startTime));
255
256
 
256
257
  /** @type {shaka.util.Timer} */
@@ -390,6 +391,16 @@ shaka.media.MediaSourcePlayhead = class {
390
391
  }
391
392
  }
392
393
 
394
+ /**
395
+ * Called when the video element has started up and is listening for new seeks
396
+ *
397
+ * @param {number} startTime
398
+ * @private
399
+ */
400
+ onStarted_(startTime) {
401
+ this.gapController_.onStarted(startTime);
402
+ }
403
+
393
404
  /**
394
405
  * Handles when a seek happens on the video.
395
406
  *
@@ -27,13 +27,24 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
27
27
  * destroy the timeline.
28
28
  *
29
29
  * @param {!shaka.media.RegionTimeline} timeline
30
+ * @param {boolean} startsPastZero
30
31
  */
31
- constructor(timeline) {
32
+ constructor(timeline, startsPastZero) {
32
33
  super();
33
34
 
34
35
  /** @private {shaka.media.RegionTimeline} */
35
36
  this.timeline_ = timeline;
36
37
 
38
+ /**
39
+ * Whether the asset is expected to start at a time beyond 0 seconds.
40
+ * For example, if the asset is a live stream.
41
+ * If true, we will not start polling for regions until the playhead has
42
+ * moved past 0 seconds, to avoid bad behaviors where the current time is
43
+ * briefly 0 before we have enough data to play.
44
+ * @private {boolean}
45
+ */
46
+ this.startsPastZero_ = startsPastZero;
47
+
37
48
  /**
38
49
  * A mapping between a region and where we previously were relative to it.
39
50
  * When the value here differs from what we calculate, it means we moved and
@@ -121,6 +132,13 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
121
132
  /** @override */
122
133
  poll(positionInSeconds, wasSeeking) {
123
134
  const RegionObserver = shaka.media.RegionObserver;
135
+ if (this.startsPastZero_ && positionInSeconds == 0) {
136
+ // Don't start checking regions until the timeline has begun moving.
137
+ return;
138
+ }
139
+ // Now that we have seen the playhead go past 0, it's okay if it goes
140
+ // back there (e.g. seeking back to the start).
141
+ this.startsPastZero_ = false;
124
142
 
125
143
  for (const region of this.timeline_.regions()) {
126
144
  const previousPosition = this.oldPosition_.get(region);
@@ -1308,7 +1308,8 @@ shaka.media.StreamingEngine = class {
1308
1308
  const dataToAppend = remaining.subarray(0, offset);
1309
1309
  remaining = remaining.subarray(offset);
1310
1310
  await this.append_(
1311
- mediaState, presentationTime, stream, reference, dataToAppend);
1311
+ mediaState, presentationTime, stream, reference, dataToAppend,
1312
+ /* isChunkedData= */ true);
1312
1313
  }
1313
1314
  };
1314
1315
 
@@ -1696,10 +1697,12 @@ shaka.media.StreamingEngine = class {
1696
1697
  * @param {shaka.extern.Stream} stream
1697
1698
  * @param {!shaka.media.SegmentReference} reference
1698
1699
  * @param {BufferSource} segment
1700
+ * @param {boolean=} isChunkedData
1699
1701
  * @return {!Promise}
1700
1702
  * @private
1701
1703
  */
1702
- async append_(mediaState, presentationTime, stream, reference, segment) {
1704
+ async append_(mediaState, presentationTime, stream, reference, segment,
1705
+ isChunkedData = false) {
1703
1706
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
1704
1707
 
1705
1708
  const hasClosedCaptions = stream.closedCaptions &&
@@ -1732,7 +1735,8 @@ shaka.media.StreamingEngine = class {
1732
1735
  reference,
1733
1736
  hasClosedCaptions,
1734
1737
  seeked,
1735
- adaptation);
1738
+ adaptation,
1739
+ isChunkedData);
1736
1740
  this.destroyer_.ensureNotDestroyed();
1737
1741
  shaka.log.v2(logPrefix, 'appended media segment');
1738
1742
  }
@@ -27,15 +27,19 @@ shaka.media.VideoWrapper = class {
27
27
  /**
28
28
  * @param {!HTMLMediaElement} video
29
29
  * @param {function()} onSeek Called when the video seeks.
30
+ * @param {function(number)} onStarted Called when the video has started.
30
31
  * @param {number} startTime The time to start at.
31
32
  */
32
- constructor(video, onSeek, startTime) {
33
+ constructor(video, onSeek, onStarted, startTime) {
33
34
  /** @private {HTMLMediaElement} */
34
35
  this.video_ = video;
35
36
 
36
37
  /** @private {function()} */
37
38
  this.onSeek_ = onSeek;
38
39
 
40
+ /** @private {function(number)} */
41
+ this.onStarted_ = onStarted;
42
+
39
43
  /** @private {number} */
40
44
  this.startTime_ = startTime;
41
45
 
@@ -165,6 +169,7 @@ shaka.media.VideoWrapper = class {
165
169
  this.started_ = true;
166
170
 
167
171
  this.eventManager_.listen(this.video_, 'seeking', () => this.onSeek_());
172
+ this.onStarted_(this.video_.currentTime);
168
173
  }
169
174
  };
170
175
 
package/lib/player.js CHANGED
@@ -1996,7 +1996,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1996
1996
  this.abrManager_.setMediaElement(mediaElement);
1997
1997
 
1998
1998
  this.playhead_ = this.createPlayhead(has.startTime);
1999
- this.playheadObservers_ = this.createPlayheadObserversForMSE_();
1999
+ this.playheadObservers_ =
2000
+ this.createPlayheadObserversForMSE_(has.startTimeOfLoad);
2000
2001
 
2001
2002
  // We need to start the buffer management code near the end because it will
2002
2003
  // set the initial buffering state and that depends on other components
@@ -2068,10 +2069,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2068
2069
 
2069
2070
  this.playhead_.setStartTime(adjustedTime);
2070
2071
  }
2071
-
2072
- // Since the first streams just became active, send an adaptation event.
2073
- this.onAdaptation_(null,
2074
- shaka.util.StreamUtils.variantToTrack(initialVariant));
2075
2072
  }
2076
2073
 
2077
2074
  this.playhead_.ready();
@@ -2761,17 +2758,21 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2761
2758
  * Create the observers for MSE playback. These observers are responsible for
2762
2759
  * notifying the app and player of specific events during MSE playback.
2763
2760
  *
2761
+ * @param {number} startTimeOfLoad
2764
2762
  * @return {!shaka.media.PlayheadObserverManager}
2765
2763
  * @private
2766
2764
  */
2767
- createPlayheadObserversForMSE_() {
2765
+ createPlayheadObserversForMSE_(startTimeOfLoad) {
2768
2766
  goog.asserts.assert(this.manifest_, 'Must have manifest');
2769
2767
  goog.asserts.assert(this.regionTimeline_, 'Must have region timeline');
2770
2768
  goog.asserts.assert(this.video_, 'Must have video element');
2771
2769
 
2770
+ const startsPastZero = this.isLive() || startTimeOfLoad > 0;
2771
+
2772
2772
  // Create the region observer. This will allow us to notify the app when we
2773
2773
  // move in and out of timeline regions.
2774
- const regionObserver = new shaka.media.RegionObserver(this.regionTimeline_);
2774
+ const regionObserver = new shaka.media.RegionObserver(
2775
+ this.regionTimeline_, startsPastZero);
2775
2776
 
2776
2777
  regionObserver.addEventListener('enter', (event) => {
2777
2778
  /** @type {shaka.extern.TimelineRegionInfo} */
@@ -6630,7 +6631,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6630
6631
  * @define {string} A version number taken from git at compile time.
6631
6632
  * @export
6632
6633
  */
6633
- shaka.Player.version = 'v4.2.11-uncompiled';
6634
+ shaka.Player.version = 'v4.2.13-uncompiled';
6634
6635
 
6635
6636
  // Initialize the deprecation system using the version string we just set
6636
6637
  // on the player.
@@ -86,6 +86,13 @@ class extends shaka.util.FakeEventTarget {
86
86
  return this.reason_;
87
87
  }
88
88
 
89
+ /** @return {void} */
90
+ throwIfAborted() {
91
+ if (this.aborted_) {
92
+ throw this.reason_;
93
+ }
94
+ }
95
+
89
96
  /**
90
97
  * @param {*} reason
91
98
  * @private
@@ -6,6 +6,7 @@
6
6
 
7
7
  goog.provide('shaka.polyfill.Orientation');
8
8
 
9
+ goog.require('shaka.log');
9
10
  goog.require('shaka.util.FakeEvent');
10
11
  goog.require('shaka.util.FakeEventTarget');
11
12
  goog.require('shaka.polyfill');
@@ -23,21 +24,47 @@ shaka.polyfill.Orientation = class {
23
24
  * @export
24
25
  */
25
26
  static install() {
26
- if (screen.orientation) {
27
+ if (screen.orientation && screen.orientation.unlock) {
27
28
  // Not needed.
29
+ shaka.log.info('Using native screen.orientation');
28
30
  return;
29
31
  }
30
32
 
31
- // There is no way to check to see if the 'orientationchange' event exists
32
- // on window, which could theoretically lead to this making a
33
- // screen.orientation object that doesn't actually work.
34
- // However, it looks like all platforms that support the deprecated
35
- // window.orientation feature also support that event.
36
- if (window.orientation != undefined) {
33
+ if (screen.orientation != undefined) {
34
+ // There are some platforms where screen.orientation is defined but
35
+ // incomplete (e.g. Safari).
36
+ // Install a very simple polyfill in that case.
37
+ shaka.polyfill.Orientation.installBasedOnScreenMethods_();
38
+ } else if (window.orientation != undefined) {
39
+ // There is no way to check to see if the 'orientationchange' event exists
40
+ // on window, which could theoretically lead to this making a
41
+ // screen.orientation object that doesn't actually work.
42
+ // However, it looks like all platforms that support the deprecated
43
+ // window.orientation feature also support that event.
37
44
  shaka.polyfill.Orientation.installBasedOnWindowMethods_();
38
45
  }
39
46
  }
40
47
 
48
+ /**
49
+ * Modifies screen.orientation to add no-ops for missing methods.
50
+ * Meant for cases where screen.orientation is defined, but has missing
51
+ * methods that cannot be properly polyfilled.
52
+ * @private
53
+ */
54
+ static installBasedOnScreenMethods_() {
55
+ if (screen.orientation.lock === undefined) {
56
+ screen.orientation.lock = (orientation) => {
57
+ shaka.log.info('screen.orientation.lock is a no-op');
58
+ return Promise.resolve();
59
+ };
60
+ }
61
+ if (screen.orientation.unlock === undefined) {
62
+ screen.orientation.unlock = () => {
63
+ shaka.log.info('screen.orientation.unlock is a no-op');
64
+ };
65
+ }
66
+ }
67
+
41
68
  /**
42
69
  * Makes a polyfill for orientation, based on window methods.
43
70
  * Note that some of the features this is based on are deprecated, so this
@@ -674,6 +674,7 @@ shaka.text.VttTextParser = class {
674
674
  cue.fontWeight = refCue.fontWeight;
675
675
  cue.fontStyle = refCue.fontStyle;
676
676
  cue.opacity = refCue.opacity;
677
+ cue.textShadow = refCue.textShadow;
677
678
  cue.wrapLine = refCue.wrapLine;
678
679
  }
679
680
 
@@ -785,7 +786,8 @@ shaka.text.VttTextParser = class {
785
786
  } else if ((results = /^size:([\d.]+)%$/.exec(word))) {
786
787
  cue.size = Number(results[1]);
787
788
  } else if ((results =
788
- /^position:([\d.]+)%(?:,(line-left|line-right|center|start|end))?$/
789
+ // eslint-disable-next-line max-len
790
+ /^position:([\d.]+)%(?:,(line-left|line-right|middle|center|start|end))?$/
789
791
  .exec(word))) {
790
792
  cue.position = Number(results[1]);
791
793
  if (results[2]) {
@@ -168,7 +168,7 @@ shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_ = [
168
168
  /^hvc/,
169
169
  /^vp0?[89]/,
170
170
  /^av01/,
171
- /^dvhe/,
171
+ /^dvh/,
172
172
  ];
173
173
 
174
174
 
@@ -180,8 +180,10 @@ shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_ = [
180
180
  */
181
181
  shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_ = [
182
182
  /^vorbis$/,
183
- /^opus$/,
184
- /^flac$/,
183
+ /^Opus$/, // correct codec string according to RFC 6381 section 3.3
184
+ /^opus$/, // some manifests wrongfully use this
185
+ /^fLaC$/, // correct codec string according to RFC 6381 section 3.3
186
+ /^flac$/, // some manifests wrongfully use this
185
187
  /^mp4a/,
186
188
  /^[ae]c-3$/,
187
189
  /^ac-4$/,
@@ -112,10 +112,11 @@ shaka.util.Mp4BoxParsers = class {
112
112
  static parseTRUN(reader, version, flags) {
113
113
  const sampleCount = reader.readUint32();
114
114
  const sampleData = [];
115
+ let dataOffset = null;
115
116
 
116
- // Skip "data_offset" if present.
117
+ // "data_offset"
117
118
  if (flags & 0x000001) {
118
- reader.skip(4);
119
+ dataOffset = reader.readUint32();
119
120
  }
120
121
 
121
122
  // Skip "first_sample_flags" if present.
@@ -159,6 +160,7 @@ shaka.util.Mp4BoxParsers = class {
159
160
  return {
160
161
  sampleCount,
161
162
  sampleData,
163
+ dataOffset,
162
164
  };
163
165
  }
164
166
 
@@ -252,13 +254,16 @@ shaka.util.ParsedTREXBox;
252
254
  /**
253
255
  * @typedef {{
254
256
  * sampleCount: number,
255
- * sampleData: !Array.<shaka.util.ParsedTRUNSample>
257
+ * sampleData: !Array.<shaka.util.ParsedTRUNSample>,
258
+ * dataOffset: ?number
256
259
  * }}
257
260
  *
258
261
  * @property {number} sampleCount
259
262
  * As per the spec: the number of samples being added in this run;
260
263
  * @property {!Array.<shaka.util.ParsedTRUNSample>} sampleData
261
264
  * An array of size <sampleCount> containing data for each sample
265
+ * @property {?number} dataOffset
266
+ * If specified via flags, this indicate the offset of the sample in bytes.
262
267
  *
263
268
  * @exportDoc
264
269
  */
@@ -346,6 +346,20 @@ shaka.util.Platform = class {
346
346
  return shaka.util.Platform.isApple() && navigator.maxTouchPoints > 1;
347
347
  }
348
348
 
349
+ /**
350
+ * Return true if the platform is a Mac, regardless of the browser.
351
+ *
352
+ * @return {boolean}
353
+ */
354
+ static isMac() {
355
+ // Try the newer standard first.
356
+ if (navigator.userAgentData && navigator.userAgentData.platform) {
357
+ return navigator.userAgentData.platform.toLowerCase() == 'macos';
358
+ }
359
+ // Fall back to the old API, with less strict matching.
360
+ return navigator.platform.toLowerCase().includes('mac');
361
+ }
362
+
349
363
  /**
350
364
  * Check if the user agent contains a key. This is the best way we know of
351
365
  * right now to detect platforms. If there is a better way, please send a
@@ -436,47 +436,61 @@ shaka.util.StreamUtils = class {
436
436
  manifest.variants = manifest.variants.filter((variant) => {
437
437
  // See: https://github.com/shaka-project/shaka-player/issues/3860
438
438
  const video = variant.video;
439
+
439
440
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
441
+ const ManifestParserUtils = shaka.util.ManifestParserUtils;
442
+ const MimeUtils = shaka.util.MimeUtils;
443
+ const StreamUtils = shaka.util.StreamUtils;
444
+
440
445
  if (video) {
441
- let videoCodecs =
442
- shaka.util.StreamUtils.getCorrectVideoCodecs_(video.codecs);
446
+ let videoCodecs = StreamUtils.getCorrectVideoCodecs_(video.codecs);
447
+
443
448
  // For multiplexed streams. Here we must check the audio of the
444
449
  // stream to see if it is compatible.
445
450
  if (video.codecs.includes(',')) {
446
451
  const allCodecs = video.codecs.split(',');
447
- videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
452
+
453
+ videoCodecs = ManifestParserUtils.guessCodecs(
448
454
  ContentType.VIDEO, allCodecs);
449
- videoCodecs =
450
- shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
451
- let audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
455
+ videoCodecs = StreamUtils.getCorrectVideoCodecs_(videoCodecs);
456
+
457
+ let audioCodecs = ManifestParserUtils.guessCodecs(
452
458
  ContentType.AUDIO, allCodecs);
453
- audioCodecs =
454
- shaka.util.StreamUtils.getCorrectAudioCodecs_(audioCodecs);
455
- const audioFullType = shaka.util.MimeUtils.getFullOrConvertedType(
459
+ audioCodecs = StreamUtils.getCorrectAudioCodecs_(audioCodecs);
460
+
461
+ const audioFullType = MimeUtils.getFullOrConvertedType(
456
462
  video.mimeType, audioCodecs, ContentType.AUDIO);
463
+
457
464
  if (!MediaSource.isTypeSupported(audioFullType)) {
458
465
  return false;
459
466
  }
467
+
460
468
  // Update the codec string with the (possibly) converted codecs.
461
469
  videoCodecs = [videoCodecs, audioCodecs].join(',');
462
470
  }
463
- const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
471
+
472
+ const fullType = MimeUtils.getFullOrConvertedType(
464
473
  video.mimeType, videoCodecs, ContentType.VIDEO);
474
+
465
475
  if (!MediaSource.isTypeSupported(fullType)) {
466
476
  return false;
467
477
  }
478
+
468
479
  // Update the codec string with the (possibly) converted codecs.
469
480
  video.codecs = videoCodecs;
470
481
  }
482
+
471
483
  const audio = variant.audio;
484
+
472
485
  if (audio) {
473
- const codecs =
474
- shaka.util.StreamUtils.getCorrectAudioCodecs_(audio.codecs);
475
- const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
486
+ const codecs = StreamUtils.getCorrectAudioCodecs_(audio.codecs);
487
+ const fullType = MimeUtils.getFullOrConvertedType(
476
488
  audio.mimeType, codecs, ContentType.AUDIO);
489
+
477
490
  if (!MediaSource.isTypeSupported(fullType)) {
478
491
  return false;
479
492
  }
493
+
480
494
  // Update the codec string with the (possibly) converted codecs.
481
495
  audio.codecs = codecs;
482
496
  }
@@ -488,7 +502,7 @@ shaka.util.StreamUtils = class {
488
502
  (video.codecs.includes('avc1.') ||
489
503
  video.codecs.includes('avc3.'))) {
490
504
  shaka.log.debug('Dropping variant - not compatible with platform',
491
- shaka.util.StreamUtils.getVariantSummaryString_(variant));
505
+ StreamUtils.getVariantSummaryString_(variant));
492
506
  return false;
493
507
  }
494
508
 
@@ -498,7 +512,7 @@ shaka.util.StreamUtils = class {
498
512
  // Filter out all unsupported variants.
499
513
  if (!supported) {
500
514
  shaka.log.debug('Dropping variant - not compatible with platform',
501
- shaka.util.StreamUtils.getVariantSummaryString_(variant));
515
+ StreamUtils.getVariantSummaryString_(variant));
502
516
  }
503
517
  return supported;
504
518
  });
@@ -562,7 +576,11 @@ shaka.util.StreamUtils = class {
562
576
  static getDecodingConfigs_(variant, usePersistentLicenses, srcEquals) {
563
577
  const audio = variant.audio;
564
578
  const video = variant.video;
579
+
565
580
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
581
+ const ManifestParserUtils = shaka.util.ManifestParserUtils;
582
+ const MimeUtils = shaka.util.MimeUtils;
583
+ const StreamUtils = shaka.util.StreamUtils;
566
584
 
567
585
  /** @type {!MediaDecodingConfiguration} */
568
586
  const mediaDecodingConfig = {
@@ -571,19 +589,23 @@ shaka.util.StreamUtils = class {
571
589
 
572
590
  if (video) {
573
591
  let videoCodecs = video.codecs;
592
+
574
593
  // For multiplexed streams with audio+video codecs, the config should have
575
594
  // AudioConfiguration and VideoConfiguration.
576
595
  if (video.codecs.includes(',')) {
577
596
  const allCodecs = video.codecs.split(',');
578
- videoCodecs = shaka.util.ManifestParserUtils.guessCodecs(
597
+
598
+ videoCodecs = ManifestParserUtils.guessCodecs(
579
599
  ContentType.VIDEO, allCodecs);
580
- videoCodecs =
581
- shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
582
- const audioCodecs = shaka.util.ManifestParserUtils.guessCodecs(
600
+ videoCodecs = StreamUtils.getCorrectVideoCodecs_(videoCodecs);
601
+
602
+ let audioCodecs = ManifestParserUtils.guessCodecs(
583
603
  ContentType.AUDIO, allCodecs);
604
+ audioCodecs = StreamUtils.getCorrectAudioCodecs_(audioCodecs);
584
605
 
585
- const audioFullType = shaka.util.MimeUtils.getFullOrConvertedType(
606
+ const audioFullType = MimeUtils.getFullOrConvertedType(
586
607
  video.mimeType, audioCodecs, ContentType.AUDIO);
608
+
587
609
  mediaDecodingConfig.audio = {
588
610
  contentType: audioFullType,
589
611
  channels: 2,
@@ -592,9 +614,11 @@ shaka.util.StreamUtils = class {
592
614
  spatialRendering: false,
593
615
  };
594
616
  }
595
- videoCodecs = shaka.util.StreamUtils.getCorrectVideoCodecs_(videoCodecs);
596
- const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
617
+
618
+ videoCodecs = StreamUtils.getCorrectVideoCodecs_(videoCodecs);
619
+ const fullType = MimeUtils.getFullOrConvertedType(
597
620
  video.mimeType, videoCodecs, ContentType.VIDEO);
621
+
598
622
  // VideoConfiguration
599
623
  mediaDecodingConfig.video = {
600
624
  contentType: fullType,
@@ -627,9 +651,8 @@ shaka.util.StreamUtils = class {
627
651
  }
628
652
  }
629
653
  if (audio) {
630
- const codecs =
631
- shaka.util.StreamUtils.getCorrectAudioCodecs_(audio.codecs);
632
- const fullType = shaka.util.MimeUtils.getFullOrConvertedType(
654
+ const codecs = StreamUtils.getCorrectAudioCodecs_(audio.codecs);
655
+ const fullType = MimeUtils.getFullOrConvertedType(
633
656
  audio.mimeType, codecs, ContentType.AUDIO);
634
657
 
635
658
  // AudioConfiguration
@@ -760,6 +783,20 @@ shaka.util.StreamUtils = class {
760
783
  * @private
761
784
  */
762
785
  static getCorrectAudioCodecs_(codecs) {
786
+ // According to RFC 6381 section 3.3, 'fLaC' is actually the correct
787
+ // codec string. We still need to map it to 'flac', as some browsers
788
+ // currently don't support 'fLaC', while 'flac' is supported by most
789
+ // major browsers.
790
+ // See https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
791
+ if (codecs === 'fLaC') {
792
+ return 'flac';
793
+ }
794
+
795
+ // The same is true for 'Opus'.
796
+ if (codecs === 'Opus') {
797
+ return 'opus';
798
+ }
799
+
763
800
  // Some Tizen devices seem to misreport AC-3 support, but correctly
764
801
  // report EC-3 support. So query EC-3 as a fallback for AC-3.
765
802
  // See https://github.com/shaka-project/shaka-player/issues/2989 for
@@ -1066,6 +1103,7 @@ shaka.util.StreamUtils = class {
1066
1103
  track.frameRate = video.frameRate || null;
1067
1104
  track.pixelAspectRatio = video.pixelAspectRatio || null;
1068
1105
  track.videoBandwidth = video.bandwidth || null;
1106
+ track.hdr = video.hdr || null;
1069
1107
  }
1070
1108
 
1071
1109
  if (audio) {
@@ -1237,6 +1275,9 @@ shaka.util.StreamUtils = class {
1237
1275
  if (textTrack.kind == 'captions') {
1238
1276
  track.mimeType = CLOSED_CAPTION_MIMETYPE;
1239
1277
  }
1278
+ if (textTrack.kind == 'subtitles') {
1279
+ track.mimeType = 'text/vtt';
1280
+ }
1240
1281
  if (textTrack.kind) {
1241
1282
  track.roles = [textTrack.kind];
1242
1283
  }
@@ -1284,13 +1325,15 @@ shaka.util.StreamUtils = class {
1284
1325
  * @private
1285
1326
  */
1286
1327
  static html5TrackToGenericShakaTrack_(html5Track) {
1328
+ const language = html5Track.language;
1329
+
1287
1330
  /** @type {shaka.extern.Track} */
1288
1331
  const track = {
1289
1332
  id: shaka.util.StreamUtils.html5TrackId(html5Track),
1290
1333
  active: false,
1291
1334
  type: '',
1292
1335
  bandwidth: 0,
1293
- language: shaka.util.LanguageUtils.normalize(html5Track.language),
1336
+ language: shaka.util.LanguageUtils.normalize(language || 'und'),
1294
1337
  label: html5Track.label,
1295
1338
  kind: html5Track.kind,
1296
1339
  width: null,
@@ -1320,6 +1363,7 @@ shaka.util.StreamUtils = class {
1320
1363
  originalAudioId: null,
1321
1364
  originalTextId: null,
1322
1365
  originalImageId: null,
1366
+ originalLanguage: language,
1323
1367
  };
1324
1368
 
1325
1369
  return track;
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.2.11",
4
+ "version": "4.2.13",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -42,7 +42,7 @@
42
42
  "karma-coverage": "^2.2.0",
43
43
  "karma-jasmine": "^4.0.1",
44
44
  "karma-jasmine-ajax": "^0.1.13",
45
- "karma-local-wd-launcher": "^1.6.1",
45
+ "karma-local-wd-launcher": "^1.6.4",
46
46
  "karma-opera-launcher": "^1.0.0",
47
47
  "karma-sourcemap-loader": "^0.3.8",
48
48
  "karma-spec-reporter": "^0.0.34",
@@ -67,7 +67,6 @@
67
67
  "demoDeps": [
68
68
  "awesomplete/awesomplete.css",
69
69
  "awesomplete/awesomplete.min.js",
70
- "codem-isoboxer/dist/iso_boxer.min.js",
71
70
  "dialog-polyfill/dialog-polyfill.css",
72
71
  "dialog-polyfill/dist/dialog-polyfill.js",
73
72
  "eme-encryption-scheme-polyfill/index.js",
@@ -171,7 +171,10 @@ describe('CastUtils', () => {
171
171
  }
172
172
  });
173
173
 
174
- describe('TimeRanges', () => {
174
+ // Disable because these tests are flakey on ChromeLinux, and this whole
175
+ // module will be removed in
176
+ // https://github.com/shaka-project/shaka-player/issues/4214
177
+ xdescribe('TimeRanges', () => {
175
178
  /** @type {!HTMLVideoElement} */
176
179
  let video;
177
180
  /** @type {!shaka.util.EventManager} */