shaka-player 3.3.18 → 3.3.19

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 (50) hide show
  1. package/.github/workflows/selenium-lab-tests.yaml +23 -1
  2. package/AUTHORS +2 -1
  3. package/CHANGELOG.md +22 -0
  4. package/CONTRIBUTORS +4 -1
  5. package/dist/controls.css +1 -1
  6. package/dist/locales.js +29 -29
  7. package/dist/shaka-player.compiled.d.ts +6 -2
  8. package/dist/shaka-player.compiled.debug.d.ts +6 -2
  9. package/dist/shaka-player.compiled.debug.js +132 -130
  10. package/dist/shaka-player.compiled.debug.map +3 -3
  11. package/dist/shaka-player.compiled.js +299 -297
  12. package/dist/shaka-player.compiled.map +3 -3
  13. package/dist/shaka-player.ui.d.ts +6 -2
  14. package/dist/shaka-player.ui.debug.d.ts +6 -2
  15. package/dist/shaka-player.ui.debug.js +196 -194
  16. package/dist/shaka-player.ui.debug.map +3 -3
  17. package/dist/shaka-player.ui.js +482 -480
  18. package/dist/shaka-player.ui.map +3 -3
  19. package/docs/tutorials/fairplay.md +2 -2
  20. package/externs/shaka/net.js +1 -0
  21. package/externs/shaka/player.js +13 -7
  22. package/lib/ads/client_side_ad_manager.js +6 -3
  23. package/lib/dash/dash_parser.js +49 -48
  24. package/lib/media/media_source_engine.js +9 -4
  25. package/lib/media/streaming_engine.js +8 -3
  26. package/lib/net/http_fetch_plugin.js +79 -57
  27. package/lib/net/networking_engine.js +1 -0
  28. package/lib/player.js +17 -2
  29. package/lib/polyfill/media_capabilities.js +13 -0
  30. package/lib/text/mp4_vtt_parser.js +4 -0
  31. package/lib/text/text_engine.js +1 -0
  32. package/lib/util/manifest_parser_utils.js +1 -2
  33. package/lib/util/mime_utils.js +11 -0
  34. package/lib/util/periods.js +5 -5
  35. package/lib/util/stream_utils.js +2 -2
  36. package/maintained-branches.md +3 -4
  37. package/package.json +1 -1
  38. package/test/media/content_workarounds_unit.js +43 -0
  39. package/test/media/drm_engine_integration.js +4 -17
  40. package/test/media/streaming_engine_unit.js +6 -1
  41. package/test/net/networking_engine_unit.js +6 -1
  42. package/test/player_integration.js +39 -0
  43. package/test/player_unit.js +54 -0
  44. package/test/polyfill/media_capabilities_unit.js +0 -1
  45. package/test/test/assets/text-clip-alt.vtt +63 -0
  46. package/test/test/util/fake_drm_engine.js +3 -0
  47. package/test/test/util/simple_fakes.js +3 -0
  48. package/test/test/util/test_scheme.js +9 -14
  49. package/test/text/mp4_vtt_parser_unit.js +8 -0
  50. package/test/util/mime_utils_unit.js +10 -0
@@ -25,14 +25,14 @@ is used by the browser to generate the license request. If you don't use the
25
25
  default content ID derivation, you need to specify a custom init data transform:
26
26
 
27
27
  ```js
28
- player.configure('drm.initDataTransform', (initData, initDataType) => {
28
+ player.configure('drm.initDataTransform', (initData, initDataType, drmInfo) => {
29
29
  if (initDataType != 'skd')
30
30
  return initData;
31
31
 
32
32
  // 'initData' is a buffer containing an 'skd://' URL as a UTF-8 string.
33
33
  const skdUri = shaka.util.StringUtils.fromBytesAutoDetect(initData);
34
34
  const contentId = getMyContentId(skdUri);
35
- const cert = player.drmInfo().serverCertificate;
35
+ const cert = drmInfo.serverCertificate;
36
36
  return shaka.util.FairPlayUtils.initDataTransform(initData, contentId, cert);
37
37
  });
38
38
  ```
@@ -100,6 +100,7 @@ shaka.extern.Request;
100
100
  /**
101
101
  * @typedef {{
102
102
  * uri: string,
103
+ * originalUri: string,
103
104
  * data: BufferSource,
104
105
  * status: (number|undefined),
105
106
  * headers: !Object.<string, string>,
@@ -613,9 +613,7 @@ shaka.extern.AdvancedDrmConfiguration;
613
613
  * clearKeys: !Object.<string, string>,
614
614
  * delayLicenseRequestUntilPlayed: boolean,
615
615
  * advanced: Object.<string, shaka.extern.AdvancedDrmConfiguration>,
616
- * initDataTransform:
617
- * ((function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array)|
618
- * undefined),
616
+ * initDataTransform:(shaka.extern.InitDataTransform|undefined),
619
617
  * logLicenseExchange: boolean,
620
618
  * updateExpirationTime: number,
621
619
  * preferredKeySystems: !Array.<string>
@@ -639,10 +637,7 @@ shaka.extern.AdvancedDrmConfiguration;
639
637
  * <i>Optional.</i> <br>
640
638
  * A dictionary which maps key system IDs to advanced DRM configuration for
641
639
  * those key systems.
642
- * @property
643
- * {((function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array)|
644
- * undefined)}
645
- * initDataTransform
640
+ * @property {shaka.extern.InitDataTransform|undefined} initDataTransform
646
641
  * <i>Optional.</i><br>
647
642
  * If given, this function is called with the init data from the
648
643
  * manifest/media and should return the (possibly transformed) init data to
@@ -664,6 +659,17 @@ shaka.extern.AdvancedDrmConfiguration;
664
659
  */
665
660
  shaka.extern.DrmConfiguration;
666
661
 
662
+ /**
663
+ * @typedef {function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array}
664
+ *
665
+ * @description
666
+ * A callback function to handle custom content ID signaling for FairPlay
667
+ * content.
668
+ *
669
+ * @exportDoc
670
+ */
671
+ shaka.extern.InitDataTransform;
672
+
667
673
 
668
674
  /**
669
675
  * @typedef {{
@@ -212,9 +212,9 @@ shaka.ads.ClientSideAdManager = class {
212
212
  // seen the ads actually play until requestAds() is called.
213
213
  // Note: We listen for a play event to avoid autoplay issues that might
214
214
  // crash IMA.
215
- this.video_.addEventListener('play', () => {
215
+ this.eventManager_.listenOnce(this.video_, 'play', () => {
216
216
  this.imaAdsManager_.start();
217
- }, {once: true});
217
+ });
218
218
  } catch (adError) {
219
219
  // If there was a problem with the VAST response,
220
220
  // we we won't be getting an ad. Hide ad UI if we showed it already
@@ -402,7 +402,10 @@ shaka.ads.ClientSideAdManager = class {
402
402
  if (this.ad_.isLinear()) {
403
403
  this.adContainer_.setAttribute('ad-active', 'true');
404
404
  this.video_.pause();
405
- this.ad_.setVolume(this.video_.muted ? 0 : this.video_.volume);
405
+ this.ad_.setVolume(this.video_.volume);
406
+ if (this.video_.muted) {
407
+ this.ad_.setMuted(true);
408
+ }
406
409
  }
407
410
  }
408
411
 
@@ -1278,55 +1278,56 @@ shaka.dash.DashParser = class {
1278
1278
  context.period.id + ',' + context.representation.id : '';
1279
1279
 
1280
1280
  /** @type {shaka.extern.Stream} */
1281
- const stream = {
1282
- id: this.globalId_++,
1283
- originalId: context.representation.id,
1284
- createSegmentIndex: async () => {
1285
- // If we have a stream with the same context id stored in the map
1286
- // that has no segmentIndex, we should set the segmentIndex for it.
1287
- const storedInMap = contextId && context.dynamic &&
1288
- this.streamMap_[contextId];
1289
-
1290
- const currentStream = storedInMap ? this.streamMap_[contextId] : stream;
1291
- if (!currentStream.segmentIndex) {
1292
- currentStream.segmentIndex = await streamInfo.generateSegmentIndex();
1293
- }
1294
- },
1281
+ let stream;
1295
1282
 
1296
- closeSegmentIndex: () => {
1297
- if (stream.segmentIndex) {
1298
- stream.segmentIndex.release();
1299
- stream.segmentIndex = null;
1300
- }
1301
- },
1302
- segmentIndex: null,
1303
- mimeType: context.representation.mimeType,
1304
- codecs: context.representation.codecs,
1305
- frameRate: context.representation.frameRate,
1306
- pixelAspectRatio: context.representation.pixelAspectRatio,
1307
- bandwidth: context.bandwidth,
1308
- width: context.representation.width,
1309
- height: context.representation.height,
1310
- kind,
1311
- encrypted: contentProtection.drmInfos.length > 0,
1312
- drmInfos: contentProtection.drmInfos,
1313
- keyIds,
1314
- language,
1315
- label,
1316
- type: context.adaptationSet.contentType,
1317
- primary: isPrimary,
1318
- trickModeVideo: null,
1319
- emsgSchemeIdUris:
1320
- context.representation.emsgSchemeIdUris,
1321
- roles,
1322
- forced: forced,
1323
- channelsCount: context.representation.numChannels,
1324
- audioSamplingRate: context.representation.audioSamplingRate,
1325
- spatialAudio: spatialAudio,
1326
- closedCaptions,
1327
- hdr,
1328
- tilesLayout,
1329
- matchedStreams: [],
1283
+ if (contextId && this.streamMap_[contextId]) {
1284
+ stream = this.streamMap_[contextId];
1285
+ } else {
1286
+ stream = {
1287
+ id: this.globalId_++,
1288
+ originalId: context.representation.id,
1289
+ createSegmentIndex: () => Promise.resolve(),
1290
+ closeSegmentIndex: () => {
1291
+ if (stream.segmentIndex) {
1292
+ stream.segmentIndex.release();
1293
+ stream.segmentIndex = null;
1294
+ }
1295
+ },
1296
+ segmentIndex: null,
1297
+ mimeType: context.representation.mimeType,
1298
+ codecs: context.representation.codecs,
1299
+ frameRate: context.representation.frameRate,
1300
+ pixelAspectRatio: context.representation.pixelAspectRatio,
1301
+ bandwidth: context.bandwidth,
1302
+ width: context.representation.width,
1303
+ height: context.representation.height,
1304
+ kind,
1305
+ encrypted: contentProtection.drmInfos.length > 0,
1306
+ drmInfos: contentProtection.drmInfos,
1307
+ keyIds,
1308
+ language,
1309
+ label,
1310
+ type: context.adaptationSet.contentType,
1311
+ primary: isPrimary,
1312
+ trickModeVideo: null,
1313
+ emsgSchemeIdUris:
1314
+ context.representation.emsgSchemeIdUris,
1315
+ roles,
1316
+ forced,
1317
+ channelsCount: context.representation.numChannels,
1318
+ audioSamplingRate: context.representation.audioSamplingRate,
1319
+ spatialAudio,
1320
+ closedCaptions,
1321
+ hdr,
1322
+ tilesLayout,
1323
+ matchedStreams: [],
1324
+ };
1325
+ }
1326
+
1327
+ stream.createSegmentIndex = async () => {
1328
+ if (!stream.segmentIndex) {
1329
+ stream.segmentIndex = await streamInfo.generateSegmentIndex();
1330
+ }
1330
1331
  };
1331
1332
 
1332
1333
  if (contextId && context.dynamic && !this.streamMap_[contextId]) {
@@ -182,6 +182,9 @@ shaka.media.MediaSourceEngine = class {
182
182
  'audio/mp4; codecs="ec-3"',
183
183
  'audio/mp4; codecs="opus"',
184
184
  'audio/mp4; codecs="flac"',
185
+ 'audio/mp4; codecs="dtsc"', // DTS Digital Surround
186
+ 'audio/mp4; codecs="dtse"', // DTS Express
187
+ 'audio/mp4; codecs="dtsx"', // DTS:X
185
188
  // WebM types
186
189
  'video/webm; codecs="vp8"',
187
190
  'video/webm; codecs="vp9"',
@@ -515,7 +518,7 @@ shaka.media.MediaSourceEngine = class {
515
518
  // For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
516
519
  // the video stream, so textEngine may not have been initialized.
517
520
  if (!this.textEngine_) {
518
- this.reinitText('text/vtt');
521
+ this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE);
519
522
  }
520
523
 
521
524
  if (transmuxedData.metadata) {
@@ -542,7 +545,7 @@ shaka.media.MediaSourceEngine = class {
542
545
  contentType, () => this.append_(contentType, transmuxedSegment));
543
546
  } else if (hasClosedCaptions) {
544
547
  if (!this.textEngine_) {
545
- this.reinitText('text/vtt');
548
+ this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE);
546
549
  }
547
550
  // If it is the init segment for closed captions, initialize the closed
548
551
  // caption parser.
@@ -711,8 +714,10 @@ shaka.media.MediaSourceEngine = class {
711
714
  async endOfStream(reason) {
712
715
  await this.enqueueBlockingOperation_(() => {
713
716
  // If endOfStream() has already been called on the media source,
714
- // don't call it again.
715
- if (this.ended()) {
717
+ // don't call it again. Also do not call if readyState is
718
+ // 'closed' (not attached to video element) since it is not a
719
+ // valid operation.
720
+ if (this.ended() || this.mediaSource_.readyState === 'closed') {
716
721
  return;
717
722
  }
718
723
  // Tizen won't let us pass undefined, but it will let us omit the
@@ -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
@@ -241,7 +246,7 @@ shaka.media.StreamingEngine = class {
241
246
  const streamText =
242
247
  textDisplayer.isTextVisible() || this.config_.alwaysStreamText;
243
248
 
244
- if (streamText) {
249
+ if (streamText && (this.textStreamSequenceId_ == currentSequenceId)) {
245
250
  const state = this.createMediaState_(stream);
246
251
  this.mediaStates_.set(ContentType.TEXT, state);
247
252
  this.scheduleUpdate_(state, 0);
@@ -1488,10 +1493,10 @@ shaka.media.StreamingEngine = class {
1488
1493
  'waiting for another stream to recover...');
1489
1494
  }
1490
1495
 
1491
- // QuotaExceededError gets thrown if evication didn't help to make room
1496
+ // QuotaExceededError gets thrown if eviction didn't help to make room
1492
1497
  // for a segment. We want to wait for a while (4 seconds is just an
1493
1498
  // arbitrary number) before updating to give the playhead a chance to
1494
- // advance, so we don't immidiately throw again.
1499
+ // advance, so we don't immediately throw again.
1495
1500
  this.scheduleUpdate_(mediaState, 4);
1496
1501
  }
1497
1502
 
@@ -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
  };
@@ -309,6 +309,7 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
309
309
  if (!responseAndGotProgress.gotProgress &&
310
310
  this.onProgressUpdated_ &&
311
311
  !response.fromCache &&
312
+ request.method != 'HEAD' &&
312
313
  type == shaka.net.NetworkingEngine.RequestType.SEGMENT) {
313
314
  this.onProgressUpdated_(response.timeMs, response.data.byteLength);
314
315
  }
package/lib/player.js CHANGED
@@ -1225,6 +1225,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1225
1225
  */
1226
1226
  shouldUseSrcEquals_(payload) {
1227
1227
  const Platform = shaka.util.Platform;
1228
+ const MimeUtils = shaka.util.MimeUtils;
1228
1229
 
1229
1230
  // If we are using a platform that does not support media source, we will
1230
1231
  // fall back to src= to handle all playback.
@@ -1279,7 +1280,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1279
1280
  // version there.
1280
1281
 
1281
1282
  // Native HLS can be preferred on any platform via this flag:
1282
- if (this.config_.streaming.preferNativeHls) {
1283
+ if (MimeUtils.isHlsType(mimeType) &&
1284
+ this.config_.streaming.preferNativeHls) {
1283
1285
  return true;
1284
1286
  }
1285
1287
 
@@ -4062,6 +4064,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4062
4064
  firstVariantWithLabel.language, '', 0, label);
4063
4065
 
4064
4066
  this.chooseVariantAndSwitch_();
4067
+ } else if (this.video_ && this.video_.audioTracks) {
4068
+ const audioTracks = Array.from(this.video_.audioTracks);
4069
+
4070
+ let trackMatch = null;
4071
+
4072
+ for (const audioTrack of audioTracks) {
4073
+ if (audioTrack.label == label) {
4074
+ trackMatch = audioTrack;
4075
+ }
4076
+ }
4077
+ if (trackMatch) {
4078
+ this.switchHtml5Track_(trackMatch);
4079
+ }
4065
4080
  }
4066
4081
  }
4067
4082
 
@@ -6665,7 +6680,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6665
6680
  * @define {string} A version number taken from git at compile time.
6666
6681
  * @export
6667
6682
  */
6668
- shaka.Player.version = 'v3.3.18-uncompiled';
6683
+ shaka.Player.version = 'v3.3.19-uncompiled';
6669
6684
 
6670
6685
  // Initialize the deprecation system using the version string we just set
6671
6686
  // on the player.
@@ -101,6 +101,19 @@ shaka.polyfill.MediaCapabilities = class {
101
101
  if (shaka.util.Platform.isChromecast()) {
102
102
  isSupported =
103
103
  shaka.polyfill.MediaCapabilities.canCastDisplayType_(videoConfig);
104
+ } else if (shaka.util.Platform.isTizen()) {
105
+ let extendedType = videoConfig.contentType;
106
+ if (videoConfig.width && videoConfig.height) {
107
+ extendedType += `; width=${videoConfig.width}`;
108
+ extendedType += `; height=${videoConfig.height}`;
109
+ }
110
+ if (videoConfig.framerate) {
111
+ extendedType += `; framerate=${videoConfig.framerate}`;
112
+ }
113
+ if (videoConfig.bitrate) {
114
+ extendedType += `; bitrate=${videoConfig.bitrate}`;
115
+ }
116
+ isSupported = MediaSource.isTypeSupported(extendedType);
104
117
  } else {
105
118
  isSupported = MediaSource.isTypeSupported(videoConfig.contentType);
106
119
  }
@@ -89,6 +89,10 @@ shaka.text.Mp4VttParser = class {
89
89
  * @export
90
90
  */
91
91
  parseMedia(data, time) {
92
+ if (!data.length) {
93
+ return [];
94
+ }
95
+
92
96
  if (!this.timescale_) {
93
97
  // Missing timescale for VTT content. We should have seen the init
94
98
  // segment.
@@ -134,6 +134,7 @@ shaka.text.TextEngine = class {
134
134
  // into TextEngine and TextDisplayer.
135
135
  if (mimeType == shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE ||
136
136
  mimeType == shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE) {
137
+ this.parser_ = null;
137
138
  return;
138
139
  }
139
140
 
@@ -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
 
@@ -194,6 +194,17 @@ shaka.util.MimeUtils = class {
194
194
  return value;
195
195
  }
196
196
 
197
+ /**
198
+ * Checks if the given MIME type is HLS MIME type.
199
+ *
200
+ * @param {string} mimeType
201
+ * @return {boolean}
202
+ */
203
+ static isHlsType(mimeType) {
204
+ return mimeType === 'application/x-mpegurl' ||
205
+ mimeType === 'application/vnd.apple.mpegurl';
206
+ }
207
+
197
208
  /**
198
209
  * Get the base and profile of a codec string. Where [0] will be the codec
199
210
  * 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;
@@ -821,7 +821,7 @@ shaka.util.StreamUtils = class {
821
821
  const video = variant.video;
822
822
  if (audio && currentVariant && currentVariant.audio) {
823
823
  if (!StreamUtils.areStreamsCompatible_(audio, currentVariant.audio)) {
824
- shaka.log.debug('Droping variant - not compatible with active audio',
824
+ shaka.log.debug('Dropping variant - not compatible with active audio',
825
825
  'active audio',
826
826
  StreamUtils.getStreamSummaryString_(currentVariant.audio),
827
827
  'variant.audio',
@@ -832,7 +832,7 @@ shaka.util.StreamUtils = class {
832
832
 
833
833
  if (video && currentVariant && currentVariant.video) {
834
834
  if (!StreamUtils.areStreamsCompatible_(video, currentVariant.video)) {
835
- shaka.log.debug('Droping variant - not compatible with active video',
835
+ shaka.log.debug('Dropping variant - not compatible with active video',
836
836
  'active video',
837
837
  StreamUtils.getStreamSummaryString_(currentVariant.video),
838
838
  'variant.video',
@@ -2,10 +2,9 @@
2
2
 
3
3
  We are currently maintaining the following branches of Shaka Player:
4
4
 
5
- - v4.2 (latest)
6
- - v4.1 (previous)
7
- - v3.3 (LTS until April 30, 2023)
8
- - v3.2 (in use by the Cast Application Framework)
5
+ - v4.3 (latest, also in use by the Cast Application Framework)
6
+ - v4.2 (previous)
7
+ - No active LTS branches at this time
9
8
 
10
9
  Other branches are no longer receiving bug fixes, and we recommend you upgrade
11
10
  to a maintained branch. For details on upgrading, please see our
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": "3.3.18",
4
+ "version": "3.3.19",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [