shaka-player 4.2.8 → 4.2.10

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 (104) hide show
  1. package/.github/workflows/build-and-test.yaml +4 -4
  2. package/.github/workflows/demo-version-index.yaml +6 -0
  3. package/.github/workflows/selenium-lab-tests.yaml +78 -17
  4. package/.github/workflows/update-screenshots.yaml +142 -0
  5. package/AUTHORS +2 -1
  6. package/CHANGELOG.md +50 -0
  7. package/CONTRIBUTORS +4 -1
  8. package/demo/common/asset.js +15 -0
  9. package/demo/common/assets.js +4 -2
  10. package/demo/common/message_ids.js +1 -0
  11. package/demo/custom.js +19 -0
  12. package/demo/locales/en.json +1 -0
  13. package/demo/locales/source.json +4 -0
  14. package/demo/main.js +14 -0
  15. package/dist/controls.css +1 -1
  16. package/dist/demo.compiled.debug.js +170 -290
  17. package/dist/demo.compiled.debug.map +4 -4
  18. package/dist/demo.compiled.js +170 -290
  19. package/dist/demo.compiled.map +4 -4
  20. package/dist/deps.js +2 -2
  21. package/dist/locales.js +30 -30
  22. package/dist/receiver.compiled.debug.js +52 -81
  23. package/dist/receiver.compiled.debug.map +4 -4
  24. package/dist/receiver.compiled.js +52 -81
  25. package/dist/receiver.compiled.map +4 -4
  26. package/dist/shaka-player.compiled.d.ts +19 -3
  27. package/dist/shaka-player.compiled.debug.d.ts +19 -3
  28. package/dist/shaka-player.compiled.debug.externs.js +17 -12
  29. package/dist/shaka-player.compiled.debug.js +181 -176
  30. package/dist/shaka-player.compiled.debug.map +4 -4
  31. package/dist/shaka-player.compiled.externs.js +17 -12
  32. package/dist/shaka-player.compiled.js +825 -823
  33. package/dist/shaka-player.compiled.map +3 -3
  34. package/dist/shaka-player.ui.d.ts +19 -3
  35. package/dist/shaka-player.ui.debug.d.ts +19 -3
  36. package/dist/shaka-player.ui.debug.externs.js +5 -0
  37. package/dist/shaka-player.ui.debug.js +191 -186
  38. package/dist/shaka-player.ui.debug.map +3 -3
  39. package/dist/shaka-player.ui.externs.js +5 -0
  40. package/dist/shaka-player.ui.js +955 -951
  41. package/dist/shaka-player.ui.map +3 -3
  42. package/docs/tutorials/a11y.md +0 -4
  43. package/docs/tutorials/ad_monetization.md +0 -4
  44. package/docs/tutorials/basic-usage.md +0 -5
  45. package/docs/tutorials/config.md +0 -5
  46. package/docs/tutorials/debugging.md +0 -5
  47. package/docs/tutorials/drm-config.md +0 -5
  48. package/docs/tutorials/errors.md +94 -0
  49. package/docs/tutorials/fairplay.md +4 -4
  50. package/docs/tutorials/index.json +2 -0
  51. package/docs/tutorials/license-server-auth.md +0 -5
  52. package/docs/tutorials/license-wrapping.md +0 -5
  53. package/docs/tutorials/network-and-buffering-config.md +0 -5
  54. package/docs/tutorials/ui-customization.md +0 -5
  55. package/docs/tutorials/ui.md +0 -7
  56. package/docs/tutorials/welcome.md +0 -5
  57. package/docs/tutorials/widevine-service-certs.md +45 -0
  58. package/externs/ima.js +9 -0
  59. package/externs/network_information.js +7 -2
  60. package/externs/shaka/abr_manager.js +6 -0
  61. package/externs/shaka/net.js +1 -0
  62. package/externs/shaka/player.js +13 -7
  63. package/lib/abr/simple_abr_manager.js +27 -3
  64. package/lib/ads/client_side_ad_manager.js +11 -5
  65. package/lib/dash/dash_parser.js +49 -48
  66. package/lib/debug/log.js +9 -13
  67. package/lib/hls/hls_parser.js +2 -0
  68. package/lib/media/drm_engine.js +4 -2
  69. package/lib/media/media_source_engine.js +11 -4
  70. package/lib/media/segment_index.js +2 -0
  71. package/lib/media/streaming_engine.js +8 -3
  72. package/lib/media/transmuxer.js +13 -7
  73. package/lib/media/video_wrapper.js +3 -3
  74. package/lib/net/http_fetch_plugin.js +79 -57
  75. package/lib/net/networking_engine.js +1 -0
  76. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  77. package/lib/player.js +24 -3
  78. package/lib/polyfill/media_capabilities.js +15 -8
  79. package/lib/polyfill/mediasource.js +24 -2
  80. package/lib/text/mp4_vtt_parser.js +4 -0
  81. package/lib/text/text_engine.js +1 -0
  82. package/lib/text/ui_text_displayer.js +1 -0
  83. package/lib/text/vtt_text_parser.js +4 -5
  84. package/lib/util/manifest_parser_utils.js +1 -2
  85. package/lib/util/mime_utils.js +12 -1
  86. package/lib/util/periods.js +5 -5
  87. package/lib/util/stream_utils.js +28 -16
  88. package/maintained-branches.md +3 -4
  89. package/package.json +1 -1
  90. package/test/media/content_workarounds_unit.js +43 -0
  91. package/test/media/drm_engine_integration.js +4 -17
  92. package/test/media/streaming_engine_unit.js +6 -1
  93. package/test/net/networking_engine_unit.js +6 -1
  94. package/test/player_integration.js +39 -0
  95. package/test/player_unit.js +57 -0
  96. package/test/polyfill/media_capabilities_unit.js +20 -23
  97. package/test/test/assets/text-clip-alt.vtt +63 -0
  98. package/test/test/util/fake_drm_engine.js +3 -0
  99. package/test/test/util/simple_fakes.js +6 -0
  100. package/test/test/util/test_scheme.js +9 -14
  101. package/test/text/mp4_vtt_parser_unit.js +8 -0
  102. package/test/text/vtt_text_parser_unit.js +26 -2
  103. package/test/text/web_vtt_layout_integration.js +0 -3
  104. package/test/util/mime_utils_unit.js +10 -0
@@ -2903,6 +2903,7 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
2903
2903
  'cmfa': 'audio/mp4',
2904
2904
  // MPEG2-TS also uses video/ for audio: https://bit.ly/TsMse
2905
2905
  'ts': 'video/mp2t',
2906
+ 'tsa': 'video/mp2t',
2906
2907
  };
2907
2908
 
2908
2909
 
@@ -2919,6 +2920,7 @@ shaka.hls.HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_ = {
2919
2920
  'm4f': 'video/mp4',
2920
2921
  'cmfv': 'video/mp4',
2921
2922
  'ts': 'video/mp2t',
2923
+ 'tsv': 'video/mp2t',
2922
2924
  };
2923
2925
 
2924
2926
 
@@ -480,8 +480,10 @@ shaka.media.DrmEngine = class {
480
480
  () => this.closeOpenSessions_());
481
481
  }
482
482
 
483
- const manifestInitData = this.currentDrmInfo_.initData.find(
484
- (initDataOverride) => initDataOverride.initData.length > 0);
483
+ const manifestInitData = this.currentDrmInfo_ ?
484
+ this.currentDrmInfo_.initData.find(
485
+ (initDataOverride) => initDataOverride.initData.length > 0,
486
+ ) : null;
485
487
 
486
488
  /**
487
489
  * We can attach media keys before the playback actually begins when:
@@ -192,6 +192,9 @@ shaka.media.MediaSourceEngine = class {
192
192
  'audio/mp4; codecs="ec-3"',
193
193
  'audio/mp4; codecs="opus"',
194
194
  'audio/mp4; codecs="flac"',
195
+ 'audio/mp4; codecs="dtsc"', // DTS Digital Surround
196
+ 'audio/mp4; codecs="dtse"', // DTS Express
197
+ 'audio/mp4; codecs="dtsx"', // DTS:X
195
198
  // WebM types
196
199
  'video/webm; codecs="vp8"',
197
200
  'video/webm; codecs="vp9"',
@@ -556,7 +559,8 @@ shaka.media.MediaSourceEngine = class {
556
559
  // For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
557
560
  // the video stream, so textEngine may not have been initialized.
558
561
  if (!this.textEngine_) {
559
- this.reinitText('text/vtt', this.sequenceMode_);
562
+ this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE,
563
+ this.sequenceMode_);
560
564
  }
561
565
 
562
566
  if (transmuxedData.metadata) {
@@ -584,7 +588,8 @@ shaka.media.MediaSourceEngine = class {
584
588
  data = transmuxedData.data;
585
589
  } else if (hasClosedCaptions) {
586
590
  if (!this.textEngine_) {
587
- this.reinitText('text/vtt', this.sequenceMode_);
591
+ this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE,
592
+ this.sequenceMode_);
588
593
  }
589
594
  // If it is the init segment for closed captions, initialize the closed
590
595
  // caption parser.
@@ -910,8 +915,10 @@ shaka.media.MediaSourceEngine = class {
910
915
  async endOfStream(reason) {
911
916
  await this.enqueueBlockingOperation_(() => {
912
917
  // If endOfStream() has already been called on the media source,
913
- // don't call it again.
914
- if (this.ended()) {
918
+ // don't call it again. Also do not call if readyState is
919
+ // 'closed' (not attached to video element) since it is not a
920
+ // valid operation.
921
+ if (this.ended() || this.mediaSource_.readyState === 'closed') {
915
922
  return;
916
923
  }
917
924
  // Tizen won't let us pass undefined, but it will let us omit the
@@ -363,6 +363,8 @@ shaka.media.SegmentIndex = class {
363
363
  lastReference.status,
364
364
  lastReference.hlsAes128Key,
365
365
  );
366
+ this.references[this.references.length - 1].discontinuitySequence =
367
+ lastReference.discontinuitySequence;
366
368
  }
367
369
 
368
370
 
@@ -77,6 +77,9 @@ shaka.media.StreamingEngine = class {
77
77
  /** @private {?shaka.extern.Stream} */
78
78
  this.currentTextStream_ = null;
79
79
 
80
+ /** @private {number} */
81
+ this.textStreamSequenceId_ = 0;
82
+
80
83
  /**
81
84
  * Maps a content type, e.g., 'audio', 'video', or 'text', to a MediaState.
82
85
  *
@@ -220,6 +223,8 @@ shaka.media.StreamingEngine = class {
220
223
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
221
224
  goog.asserts.assert(!this.mediaStates_.has(ContentType.TEXT),
222
225
  'Should not call loadNewTextStream_ while streaming text!');
226
+ this.textStreamSequenceId_++;
227
+ const currentSequenceId = this.textStreamSequenceId_;
223
228
 
224
229
  try {
225
230
  // Clear MediaSource's buffered text, so that the new text stream will
@@ -242,7 +247,7 @@ shaka.media.StreamingEngine = class {
242
247
  const streamText =
243
248
  textDisplayer.isTextVisible() || this.config_.alwaysStreamText;
244
249
 
245
- if (streamText) {
250
+ if (streamText && (this.textStreamSequenceId_ == currentSequenceId)) {
246
251
  const state = this.createMediaState_(stream);
247
252
  this.mediaStates_.set(ContentType.TEXT, state);
248
253
  this.scheduleUpdate_(state, 0);
@@ -1559,10 +1564,10 @@ shaka.media.StreamingEngine = class {
1559
1564
  'waiting for another stream to recover...');
1560
1565
  }
1561
1566
 
1562
- // QuotaExceededError gets thrown if evication didn't help to make room
1567
+ // QuotaExceededError gets thrown if eviction didn't help to make room
1563
1568
  // for a segment. We want to wait for a while (4 seconds is just an
1564
1569
  // arbitrary number) before updating to give the playhead a chance to
1565
- // advance, so we don't immidiately throw again.
1570
+ // advance, so we don't immediately throw again.
1566
1571
  this.scheduleUpdate_(mediaState, 4);
1567
1572
  }
1568
1573
 
@@ -29,9 +29,7 @@ shaka.media.Transmuxer = class {
29
29
  this.muxjs_ = shaka.dependencies.muxjs();
30
30
 
31
31
  /** @private {muxjs.mp4.Transmuxer} */
32
- this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
33
- 'keepOriginalTimestamps': true,
34
- });
32
+ this.muxTransmuxer_ = null;
35
33
 
36
34
  /** @private {shaka.util.PublicPromise} */
37
35
  this.transmuxPromise_ = null;
@@ -48,16 +46,22 @@ shaka.media.Transmuxer = class {
48
46
  /** @private {boolean} */
49
47
  this.isTransmuxing_ = false;
50
48
 
51
- this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
52
-
53
- this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
49
+ if (this.muxjs_) {
50
+ this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
51
+ 'keepOriginalTimestamps': true,
52
+ });
53
+ this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
54
+ this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
55
+ }
54
56
  }
55
57
 
56
58
  /**
57
59
  * @override
58
60
  */
59
61
  destroy() {
60
- this.muxTransmuxer_.dispose();
62
+ if (this.muxTransmuxer_) {
63
+ this.muxTransmuxer_.dispose();
64
+ }
61
65
  this.muxTransmuxer_ = null;
62
66
  return Promise.resolve();
63
67
  }
@@ -156,6 +160,8 @@ shaka.media.Transmuxer = class {
156
160
  * metadata: !Array.<!Object>}>}
157
161
  */
158
162
  transmux(data) {
163
+ goog.asserts.assert(this.muxTransmuxer_,
164
+ 'mux.js should be available.');
159
165
  goog.asserts.assert(!this.isTransmuxing_,
160
166
  'No transmuxing should be in progress.');
161
167
  this.isTransmuxing_ = true;
@@ -135,15 +135,15 @@ shaka.media.VideoWrapper = class {
135
135
  // calling |Player.load|, meaning that |currentTime| is more meaningful than
136
136
  // |startTime|.
137
137
  //
138
- // Seeking to the current time is a work around for Issue 1298. If we don't
139
- // do this, the video may get stuck and not play.
138
+ // Seeking to the current time is a work around for Issue 1298 and 4888.
139
+ // If we don't do this, the video may get stuck and not play.
140
140
  //
141
141
  // TODO: Need further investigation why it happens. Before and after
142
142
  // setting the current time, video.readyState is 1, video.paused is true,
143
143
  // and video.buffered's TimeRanges length is 0.
144
144
  // See: https://github.com/shaka-project/shaka-player/issues/1298
145
145
  this.mover_.moveTo(
146
- this.video_.currentTime == 0 ?
146
+ (!this.video_.currentTime || this.video_.currentTime == 0) ?
147
147
  startTime :
148
148
  this.video_.currentTime);
149
149
  }
@@ -120,65 +120,77 @@ shaka.net.HttpFetchPlugin = class {
120
120
  // not the body yet.
121
121
  headersReceived(shaka.net.HttpFetchPlugin.headersToGenericObject_(
122
122
  response.headers));
123
- // Getting the reader in this way allows us to observe the process of
124
- // downloading the body, instead of just waiting for an opaque promise to
125
- // resolve.
126
- // We first clone the response because calling getReader locks the body
127
- // stream; if we didn't clone it here, we would be unable to get the
128
- // response's arrayBuffer later.
129
- const reader = response.clone().body.getReader();
130
-
131
- const contentLengthRaw = response.headers.get('Content-Length');
132
- const contentLength =
133
- contentLengthRaw ? parseInt(contentLengthRaw, 10) : 0;
134
-
135
- const start = (controller) => {
136
- const push = async () => {
137
- let readObj;
138
- try {
139
- readObj = await reader.read();
140
- } catch (e) {
141
- // If we abort the request, we'll get an error here. Just ignore it
142
- // since real errors will be reported when we read the buffer below.
143
- shaka.log.v1('error reading from stream', e.message);
144
- return;
145
- }
146
-
147
- if (!readObj.done) {
148
- loaded += readObj.value.byteLength;
149
- if (streamDataCallback) {
150
- await streamDataCallback(readObj.value);
123
+
124
+ // In new versions of Chromium, HEAD requests now have a response body
125
+ // that is null.
126
+ // So just don't try to download the body at all, if it's a HEAD request,
127
+ // to avoid null reference errors.
128
+ // See: https://crbug.com/1297060
129
+ if (init.method != 'HEAD') {
130
+ goog.asserts.assert(response.body,
131
+ 'non-HEAD responses should have a body');
132
+
133
+ // Getting the reader in this way allows us to observe the process of
134
+ // downloading the body, instead of just waiting for an opaque promise
135
+ // to resolve.
136
+ // We first clone the response because calling getReader locks the body
137
+ // stream; if we didn't clone it here, we would be unable to get the
138
+ // response's arrayBuffer later.
139
+ const reader = response.clone().body.getReader();
140
+
141
+ const contentLengthRaw = response.headers.get('Content-Length');
142
+ const contentLength =
143
+ contentLengthRaw ? parseInt(contentLengthRaw, 10) : 0;
144
+
145
+ const start = (controller) => {
146
+ const push = async () => {
147
+ let readObj;
148
+ try {
149
+ readObj = await reader.read();
150
+ } catch (e) {
151
+ // If we abort the request, we'll get an error here. Just ignore
152
+ // it since real errors will be reported when we read the buffer
153
+ // below.
154
+ shaka.log.v1('error reading from stream', e.message);
155
+ return;
156
+ }
157
+
158
+ if (!readObj.done) {
159
+ loaded += readObj.value.byteLength;
160
+ if (streamDataCallback) {
161
+ await streamDataCallback(readObj.value);
162
+ }
163
+ }
164
+
165
+ const currentTime = Date.now();
166
+ // If the time between last time and this time we got progress event
167
+ // is long enough, or if a whole segment is downloaded, call
168
+ // progressUpdated().
169
+ if (currentTime - lastTime > 100 || readObj.done) {
170
+ progressUpdated(currentTime - lastTime, loaded - lastLoaded,
171
+ contentLength - loaded);
172
+ lastLoaded = loaded;
173
+ lastTime = currentTime;
174
+ }
175
+
176
+ if (readObj.done) {
177
+ goog.asserts.assert(!readObj.value,
178
+ 'readObj should be unset when "done" is true.');
179
+ controller.close();
180
+ } else {
181
+ controller.enqueue(readObj.value);
182
+ push();
151
183
  }
152
- }
153
-
154
- const currentTime = Date.now();
155
- // If the time between last time and this time we got progress event
156
- // is long enough, or if a whole segment is downloaded, call
157
- // progressUpdated().
158
- if (currentTime - lastTime > 100 || readObj.done) {
159
- progressUpdated(currentTime - lastTime, loaded - lastLoaded,
160
- contentLength - loaded);
161
- lastLoaded = loaded;
162
- lastTime = currentTime;
163
- }
164
-
165
- if (readObj.done) {
166
- goog.asserts.assert(!readObj.value,
167
- 'readObj should be unset when "done" is true.');
168
- controller.close();
169
- } else {
170
- controller.enqueue(readObj.value);
171
- push();
172
- }
184
+ };
185
+ push();
173
186
  };
174
- push();
175
- };
176
- // Create a ReadableStream to use the reader. We don't need to use the
177
- // actual stream for anything, though, as we are using the response's
178
- // arrayBuffer method to get the body, so we don't store the
179
- // ReadableStream.
180
- new ReadableStream({start}); // eslint-disable-line no-new
181
- arrayBuffer = await response.arrayBuffer();
187
+ // Create a ReadableStream to use the reader. We don't need to use the
188
+ // actual stream for anything, though, as we are using the response's
189
+ // arrayBuffer method to get the body, so we don't store the
190
+ // ReadableStream.
191
+ new ReadableStream({start}); // eslint-disable-line no-new
192
+ arrayBuffer = await response.arrayBuffer();
193
+ }
182
194
  } catch (error) {
183
195
  if (abortStatus.canceled) {
184
196
  throw new shaka.util.Error(
@@ -243,6 +255,16 @@ shaka.net.HttpFetchPlugin = class {
243
255
  } else {
244
256
  return false;
245
257
  }
258
+ // Old fetch implementations hasn't body and ReadableStream implementation
259
+ // See: https://github.com/shaka-project/shaka-player/issues/5088
260
+ if (window.Response) {
261
+ const response = new Response('');
262
+ if (!response.body) {
263
+ return false;
264
+ }
265
+ } else {
266
+ return false;
267
+ }
246
268
  return !!(window.fetch && window.AbortController);
247
269
  }
248
270
  };
@@ -312,6 +312,7 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
312
312
  if (!responseAndGotProgress.gotProgress &&
313
313
  this.onProgressUpdated_ &&
314
314
  !response.fromCache &&
315
+ request.method != 'HEAD' &&
315
316
  type == shaka.net.NetworkingEngine.RequestType.SEGMENT) {
316
317
  this.onProgressUpdated_(response.timeMs, response.data.byteLength);
317
318
  }
@@ -67,7 +67,7 @@ shaka.offline.indexeddb.StorageMechanism = class {
67
67
  shaka.util.Error.Category.STORAGE,
68
68
  shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));
69
69
  });
70
- timeOutTimer.tickAfter(2);
70
+ timeOutTimer.tickAfter(5);
71
71
 
72
72
  const open = window.indexedDB.open(name, version);
73
73
  open.onsuccess = (event) => {
package/lib/player.js CHANGED
@@ -806,7 +806,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
806
806
  }
807
807
 
808
808
  this.abrManagerFactory_ = null;
809
- this.abrManager_ = null;
810
809
  this.config_ = null;
811
810
  this.stats_ = null;
812
811
  this.videoContainer_ = null;
@@ -817,6 +816,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
817
816
  this.networkingEngine_ = null;
818
817
  }
819
818
 
819
+ if (this.abrManager_) {
820
+ this.abrManager_.release();
821
+ this.abrManager_ = null;
822
+ }
823
+
820
824
  // FakeEventTarget implements IReleasable
821
825
  super.release();
822
826
  }
@@ -1232,6 +1236,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1232
1236
  */
1233
1237
  shouldUseSrcEquals_(payload) {
1234
1238
  const Platform = shaka.util.Platform;
1239
+ const MimeUtils = shaka.util.MimeUtils;
1235
1240
 
1236
1241
  // If we are using a platform that does not support media source, we will
1237
1242
  // fall back to src= to handle all playback.
@@ -1286,7 +1291,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1286
1291
  // version there.
1287
1292
 
1288
1293
  // Native HLS can be preferred on any platform via this flag:
1289
- if (this.config_.streaming.preferNativeHls) {
1294
+ if (MimeUtils.isHlsType(mimeType) &&
1295
+ this.config_.streaming.preferNativeHls) {
1290
1296
  return true;
1291
1297
  }
1292
1298
 
@@ -2433,6 +2439,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2433
2439
  // goes wrong.
2434
2440
  new shaka.util.Timer(resolve).tickAfter(1);
2435
2441
  });
2442
+ } else if (textTracks.length > 0) {
2443
+ this.isTextVisible_ = true;
2436
2444
  }
2437
2445
 
2438
2446
  // If we have moved on to another piece of content while waiting for
@@ -4084,6 +4092,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4084
4092
  firstVariantWithLabel.language, '', 0, label);
4085
4093
 
4086
4094
  this.chooseVariantAndSwitch_();
4095
+ } else if (this.video_ && this.video_.audioTracks) {
4096
+ const audioTracks = Array.from(this.video_.audioTracks);
4097
+
4098
+ let trackMatch = null;
4099
+
4100
+ for (const audioTrack of audioTracks) {
4101
+ if (audioTrack.label == label) {
4102
+ trackMatch = audioTrack;
4103
+ }
4104
+ }
4105
+ if (trackMatch) {
4106
+ this.switchHtml5Track_(trackMatch);
4107
+ }
4087
4108
  }
4088
4109
  }
4089
4110
 
@@ -6605,7 +6626,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6605
6626
  * @define {string} A version number taken from git at compile time.
6606
6627
  * @export
6607
6628
  */
6608
- shaka.Player.version = 'v4.2.8-uncompiled';
6629
+ shaka.Player.version = 'v4.2.10-uncompiled';
6609
6630
 
6610
6631
  // Initialize the deprecation system using the version string we just set
6611
6632
  // on the player.
@@ -8,7 +8,6 @@ goog.provide('shaka.polyfill.MediaCapabilities');
8
8
 
9
9
  goog.require('shaka.log');
10
10
  goog.require('shaka.polyfill');
11
- goog.require('shaka.util.Error');
12
11
  goog.require('shaka.util.Platform');
13
12
 
14
13
 
@@ -116,6 +115,19 @@ shaka.polyfill.MediaCapabilities = class {
116
115
  if (shaka.util.Platform.isChromecast()) {
117
116
  isSupported =
118
117
  shaka.polyfill.MediaCapabilities.canCastDisplayType_(videoConfig);
118
+ } else if (shaka.util.Platform.isTizen()) {
119
+ let extendedType = videoConfig.contentType;
120
+ if (videoConfig.width && videoConfig.height) {
121
+ extendedType += `; width=${videoConfig.width}`;
122
+ extendedType += `; height=${videoConfig.height}`;
123
+ }
124
+ if (videoConfig.framerate) {
125
+ extendedType += `; framerate=${videoConfig.framerate}`;
126
+ }
127
+ if (videoConfig.bitrate) {
128
+ extendedType += `; bitrate=${videoConfig.bitrate}`;
129
+ }
130
+ isSupported = MediaSource.isTypeSupported(extendedType);
119
131
  } else {
120
132
  isSupported = MediaSource.isTypeSupported(videoConfig.contentType);
121
133
  }
@@ -226,13 +238,8 @@ shaka.polyfill.MediaCapabilities = class {
226
238
  * @private
227
239
  */
228
240
  static canCastDisplayType_(videoConfig) {
229
- if (!(window.cast)) {
230
- shaka.log.error('Expected cast namespace to be available!');
231
- throw new shaka.util.Error(
232
- shaka.util.Error.Severity.CRITICAL,
233
- shaka.util.Error.Category.CAST,
234
- shaka.util.Error.Code.CAST_API_UNAVAILABLE);
235
- } else if (!(cast.__platform__ && cast.__platform__.canDisplayType)) {
241
+ if (!(window.cast &&
242
+ cast.__platform__ && cast.__platform__.canDisplayType)) {
236
243
  shaka.log.warning('Expected cast APIs to be available! Falling back to ' +
237
244
  'MediaSource.isTypeSupported() for type support.');
238
245
  return MediaSource.isTypeSupported(videoConfig.contentType);
@@ -53,14 +53,19 @@ shaka.polyfill.MediaSource = class {
53
53
  // Offsetting the end of the removal range seems to help.
54
54
  // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=177884
55
55
  shaka.polyfill.MediaSource.patchRemovalRange_();
56
- } else {
57
- shaka.log.info('Patching Safari 13 MSE bugs.');
56
+ } else if (safariVersion <= 15) {
57
+ shaka.log.info('Patching Safari 13 & 14 & 15 MSE bugs.');
58
58
  // Safari 13 does not correctly implement abort() on SourceBuffer.
59
59
  // Calling abort() before appending a segment causes that segment to be
60
60
  // incomplete in the buffer.
61
61
  // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
62
62
  shaka.polyfill.MediaSource.stubAbort_();
63
63
  }
64
+ } else if (shaka.util.Platform.isEdge()) {
65
+ shaka.log.info('Rejecting TS container.');
66
+ // TS content is broken on Edge in general.
67
+ // See https://github.com/shaka-project/shaka-player/issues/4955
68
+ shaka.polyfill.MediaSource.rejectContainer_('mp2t');
64
69
  } else if (shaka.util.Platform.isTizen2() ||
65
70
  shaka.util.Platform.isTizen3() ||
66
71
  shaka.util.Platform.isTizen4()) {
@@ -122,6 +127,23 @@ shaka.polyfill.MediaSource = class {
122
127
  };
123
128
  }
124
129
 
130
+ /**
131
+ * Patch |MediaSource.isTypeSupported| to always reject |container|. This is
132
+ * used when we know that we are on a platform that does not work well with
133
+ * a given container.
134
+ *
135
+ * @param {string} container
136
+ * @private
137
+ */
138
+ static rejectContainer_(container) {
139
+ const isTypeSupported = MediaSource.isTypeSupported;
140
+
141
+ MediaSource.isTypeSupported = (mimeType) => {
142
+ const actualContainer = shaka.util.MimeUtils.getContainerType(mimeType);
143
+ return actualContainer != container && isTypeSupported(mimeType);
144
+ };
145
+ }
146
+
125
147
  /**
126
148
  * Patch |MediaSource.isTypeSupported| to always reject |codec|. This is used
127
149
  * when we know that we are on a platform that does not work well with a given
@@ -97,6 +97,10 @@ shaka.text.Mp4VttParser = class {
97
97
  * @export
98
98
  */
99
99
  parseMedia(data, time) {
100
+ if (!data.length) {
101
+ return [];
102
+ }
103
+
100
104
  if (!this.timescale_) {
101
105
  // Missing timescale for VTT content. We should have seen the init
102
106
  // segment.
@@ -139,6 +139,7 @@ shaka.text.TextEngine = class {
139
139
  // into TextEngine and TextDisplayer.
140
140
  if (mimeType == shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE ||
141
141
  mimeType == shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE) {
142
+ this.parser_ = null;
142
143
  return;
143
144
  }
144
145
 
@@ -474,6 +474,7 @@ shaka.text.UITextDisplayer = class {
474
474
  wrapper = shaka.util.Dom.createHTMLElement('span');
475
475
  wrapper.classList.add('shaka-text-wrapper');
476
476
  wrapper.style.backgroundColor = cue.backgroundColor;
477
+ wrapper.style.lineHeight = 'normal';
477
478
  cueElement.appendChild(wrapper);
478
479
  }
479
480
 
@@ -391,8 +391,7 @@ shaka.text.VttTextParser = class {
391
391
  end += timeOffset;
392
392
 
393
393
  // Get the payload.
394
- const payload = VttTextParser.htmlUnescape_(
395
- text.slice(1).join('\n').trim());
394
+ const payload = text.slice(1).join('\n').trim();
396
395
 
397
396
  let cue = null;
398
397
  if (styles.has('global')) {
@@ -450,7 +449,7 @@ shaka.text.VttTextParser = class {
450
449
  const childNode = childNodes[0];
451
450
  if (childNode.nodeType == Node.TEXT_NODE ||
452
451
  childNode.nodeType == Node.CDATA_SECTION_NODE) {
453
- rootCue.payload = payload;
452
+ rootCue.payload = VttTextParser.htmlUnescape_(payload);
454
453
  return;
455
454
  }
456
455
  }
@@ -461,7 +460,7 @@ shaka.text.VttTextParser = class {
461
460
  rootCue.nestedCues = cues;
462
461
  } else {
463
462
  shaka.log.warning('The cue\'s markup could not be parsed: ', payload);
464
- rootCue.payload = payload;
463
+ rootCue.payload = VttTextParser.htmlUnescape_(payload);
465
464
  }
466
465
  }
467
466
 
@@ -755,7 +754,7 @@ shaka.text.VttTextParser = class {
755
754
  }
756
755
  if (text.length > 0) {
757
756
  const textCue = nestedCue.clone();
758
- textCue.payload = text;
757
+ textCue.payload = VttTextParser.htmlUnescape_(text);
759
758
  cues.push(textCue);
760
759
  }
761
760
  isFirst = false;
@@ -185,8 +185,7 @@ shaka.util.ManifestParserUtils.AUDIO_CODEC_REGEXPS_ = [
185
185
  /^mp4a/,
186
186
  /^[ae]c-3$/,
187
187
  /^ac-4$/,
188
- /^dts[cx]$/, // DTS Surround Sound
189
- /^[du]dts$/, // DTS Surround Sound
188
+ /^dts[cex]$/, // DTS Digital Surround (dtsc), DTS Express (dtse), DTS:X (dtsx)
190
189
  ];
191
190
 
192
191
 
@@ -52,7 +52,7 @@ shaka.util.MimeUtils = class {
52
52
  return shaka.media.Transmuxer.convertTsCodecs(
53
53
  contentType, fullMimeType);
54
54
  }
55
- } else if (contentType == ContentType.AUDIO) {
55
+ } else if (mimeType != 'video/mp2t' && contentType == ContentType.AUDIO) {
56
56
  // video/mp2t is the correct mime type for TS audio, so only replace the
57
57
  // word "video" with "audio" for non-TS audio content.
58
58
  return fullMimeType.replace('video', 'audio');
@@ -196,6 +196,17 @@ shaka.util.MimeUtils = class {
196
196
  return value;
197
197
  }
198
198
 
199
+ /**
200
+ * Checks if the given MIME type is HLS MIME type.
201
+ *
202
+ * @param {string} mimeType
203
+ * @return {boolean}
204
+ */
205
+ static isHlsType(mimeType) {
206
+ return mimeType === 'application/x-mpegurl' ||
207
+ mimeType === 'application/vnd.apple.mpegurl';
208
+ }
209
+
199
210
  /**
200
211
  * Get the base and profile of a codec string. Where [0] will be the codec
201
212
  * base and [1] will be the profile.
@@ -671,11 +671,11 @@ shaka.util.PeriodCombiner = class {
671
671
  // Also checks if the segmentIndex is still valid after the async
672
672
  // operations, to make sure we stop if the active stream has changed.
673
673
  if (outputStream.segmentIndex instanceof shaka.media.MetaSegmentIndex) {
674
- for (let i = 0; i < streams.length; i++) {
674
+ for (let i = firstNewPeriodIndex; i < streams.length; i++) {
675
675
  const match = streams[i];
676
- if (match.segmentIndex && i >= firstNewPeriodIndex) {
677
- goog.asserts.assert(match.segmentIndex,
678
- 'stream should have a segmentIndex.');
676
+ goog.asserts.assert(match.segmentIndex,
677
+ 'stream should have a segmentIndex.');
678
+ if (match.segmentIndex) {
679
679
  outputStream.segmentIndex.appendSegmentIndex(match.segmentIndex);
680
680
  }
681
681
  }
@@ -728,7 +728,7 @@ shaka.util.PeriodCombiner = class {
728
728
  // in advance. concat() will add them to the output's MetaSegmentIndex.
729
729
  }
730
730
 
731
- if (!outputStream.matchedStreams) {
731
+ if (!outputStream.matchedStreams || !outputStream.matchedStreams.length) {
732
732
  // This is not a stream we can build output from, but it may become part
733
733
  // of another output based on another period's stream.
734
734
  return null;