shaka-player 4.12.4 → 4.12.6

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 (55) hide show
  1. package/dist/deps.js +2 -2
  2. package/dist/locales.js +35 -35
  3. package/dist/shaka-player.compiled.d.ts +11 -3
  4. package/dist/shaka-player.compiled.debug.d.ts +11 -3
  5. package/dist/shaka-player.compiled.debug.externs.js +8 -5
  6. package/dist/shaka-player.compiled.debug.js +340 -331
  7. package/dist/shaka-player.compiled.debug.map +3 -3
  8. package/dist/shaka-player.compiled.externs.js +8 -5
  9. package/dist/shaka-player.compiled.js +1383 -1378
  10. package/dist/shaka-player.compiled.map +4 -4
  11. package/dist/shaka-player.ui.d.ts +11 -3
  12. package/dist/shaka-player.ui.debug.d.ts +11 -3
  13. package/dist/shaka-player.ui.debug.externs.js +8 -5
  14. package/dist/shaka-player.ui.debug.js +357 -348
  15. package/dist/shaka-player.ui.debug.map +3 -3
  16. package/dist/shaka-player.ui.externs.js +8 -5
  17. package/dist/shaka-player.ui.js +1605 -1599
  18. package/dist/shaka-player.ui.map +3 -3
  19. package/externs/shaka/codecs.js +21 -0
  20. package/externs/shaka/offline.js +4 -0
  21. package/lib/cast/cast_utils.js +1 -0
  22. package/lib/dash/dash_parser.js +3 -0
  23. package/lib/hls/hls_parser.js +26 -5
  24. package/lib/media/drm_engine.js +70 -29
  25. package/lib/media/manifest_filterer.js +2 -1
  26. package/lib/media/media_source_engine.js +138 -129
  27. package/lib/media/segment_utils.js +50 -44
  28. package/lib/media/streaming_engine.js +9 -1
  29. package/lib/offline/download_info.js +5 -0
  30. package/lib/offline/download_manager.js +54 -0
  31. package/lib/offline/manifest_converter.js +1 -0
  32. package/lib/offline/storage.js +12 -3
  33. package/lib/player.js +52 -29
  34. package/lib/polyfill/media_capabilities.js +1 -1
  35. package/lib/text/simple_text_displayer.js +9 -1
  36. package/lib/text/ui_text_displayer.js +2 -14
  37. package/lib/transmuxer/aac_transmuxer.js +11 -3
  38. package/lib/transmuxer/ac3_transmuxer.js +11 -3
  39. package/lib/transmuxer/ec3_transmuxer.js +11 -3
  40. package/lib/transmuxer/h264.js +143 -70
  41. package/lib/transmuxer/mp3_transmuxer.js +11 -3
  42. package/lib/transmuxer/ts_transmuxer.js +68 -28
  43. package/lib/util/drm_utils.js +20 -0
  44. package/lib/util/mime_utils.js +3 -0
  45. package/lib/util/object_utils.js +83 -0
  46. package/lib/util/platform.js +48 -40
  47. package/lib/util/stream_utils.js +37 -113
  48. package/lib/util/ts_parser.js +111 -85
  49. package/package.json +1 -1
  50. package/ui/ad_counter.js +5 -0
  51. package/ui/controls.js +4 -2
  52. package/ui/fast_forward_button.js +7 -0
  53. package/ui/play_button.js +1 -1
  54. package/ui/playback_rate_selection.js +1 -1
  55. package/ui/rewind_button.js +7 -0
@@ -333,6 +333,11 @@ shaka.media.MediaSourceEngine = class {
333
333
  for (const ref of stream.segmentIndex) {
334
334
  const mimeType = ref.mimeType || stream.mimeType || '';
335
335
  let codecs = ref.codecs || stream.codecs || '';
336
+ // Optimization for the case where the codecs and mimetype of the stream
337
+ // match the reference.
338
+ if (mimeType == stream.mimeType && codecs == stream.codecs) {
339
+ continue;
340
+ }
336
341
  // Don't check the same combination of mimetype + codecs twice.
337
342
  const combo = mimeType + ':' + codecs;
338
343
  if (seenCombos.has(combo)) {
@@ -466,12 +471,12 @@ shaka.media.MediaSourceEngine = class {
466
471
  cleanup.push(this.textEngine_.destroy());
467
472
  }
468
473
 
474
+ await Promise.all(cleanup);
475
+
469
476
  for (const contentType in this.transmuxers_) {
470
- cleanup.push(this.transmuxers_[contentType].destroy());
477
+ this.transmuxers_[contentType].destroy();
471
478
  }
472
479
 
473
-
474
- await Promise.all(cleanup);
475
480
  if (this.eventManager_) {
476
481
  this.eventManager_.release();
477
482
  this.eventManager_ = null;
@@ -551,6 +556,13 @@ shaka.media.MediaSourceEngine = class {
551
556
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
552
557
 
553
558
  await this.mediaSourceOpen_;
559
+ if (this.ended() || this.closed()) {
560
+ shaka.log.alwaysError('Expected MediaSource to be open during init(); ' +
561
+ 'reopening the media source.');
562
+ this.mediaSourceOpen_ = new shaka.util.PublicPromise();
563
+ this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
564
+ await this.mediaSourceOpen_;
565
+ }
554
566
 
555
567
  this.sequenceMode_ = sequenceMode;
556
568
  this.manifestType_ = manifestType;
@@ -692,7 +704,8 @@ shaka.media.MediaSourceEngine = class {
692
704
  * @return {boolean}
693
705
  */
694
706
  isStreamingAllowed() {
695
- return this.streamingAllowed_ && !this.usingRemotePlayback_;
707
+ return this.streamingAllowed_ && !this.usingRemotePlayback_ &&
708
+ !this.reloadingMediaSource_;
696
709
  }
697
710
 
698
711
  /**
@@ -723,6 +736,17 @@ shaka.media.MediaSourceEngine = class {
723
736
  return this.mediaSource_ ? this.mediaSource_.readyState == 'ended' : true;
724
737
  }
725
738
 
739
+ /**
740
+ * @return {boolean} True if the MediaSource is in an "closed" state, or if
741
+ * the object has been destroyed.
742
+ */
743
+ closed() {
744
+ if (this.reloadingMediaSource_) {
745
+ return false;
746
+ }
747
+ return this.mediaSource_ ? this.mediaSource_.readyState == 'closed' : true;
748
+ }
749
+
726
750
  /**
727
751
  * Gets the first timestamp in buffer for the given content type.
728
752
  *
@@ -1209,7 +1233,16 @@ shaka.media.MediaSourceEngine = class {
1209
1233
  this.audioCompensation_.resolve(compensation);
1210
1234
  }
1211
1235
  }
1212
- const calculatedTimestampOffset = reference.startTime - timestamp;
1236
+ let realTimestamp = timestamp;
1237
+ const RAW_FORMATS = shaka.util.MimeUtils.RAW_FORMATS;
1238
+ // For formats without containers and using segments mode, we need to
1239
+ // adjust TimestampOffset relative to 0 because segments do not have
1240
+ // any timestamp information.
1241
+ if (!this.sequenceMode_ &&
1242
+ RAW_FORMATS.includes(this.sourceBufferTypes_[contentType])) {
1243
+ realTimestamp = 0;
1244
+ }
1245
+ const calculatedTimestampOffset = reference.startTime - realTimestamp;
1213
1246
  const timestampOffsetDifference =
1214
1247
  Math.abs(timestampOffset - calculatedTimestampOffset);
1215
1248
  if ((timestampOffsetDifference >= 0.001 || seeked || adaptation) &&
@@ -1566,6 +1599,13 @@ shaka.media.MediaSourceEngine = class {
1566
1599
  return;
1567
1600
  }
1568
1601
 
1602
+ // Avoid changing timestampOffset when the difference is less than 100 ms
1603
+ // from the end of the current buffer.
1604
+ const bufferEnd = this.bufferEnd(contentType);
1605
+ if (bufferEnd && Math.abs(bufferEnd - timestampOffset) < 0.1) {
1606
+ return;
1607
+ }
1608
+
1569
1609
  // Queue an abort() to help MSE splice together overlapping segments.
1570
1610
  // We set appendWindowEnd when we change periods in DASH content, and the
1571
1611
  // period transition may result in overlap.
@@ -1607,7 +1647,7 @@ shaka.media.MediaSourceEngine = class {
1607
1647
  // don't call it again. Also do not call if readyState is
1608
1648
  // 'closed' (not attached to video element) since it is not a
1609
1649
  // valid operation.
1610
- if (this.ended() || this.mediaSource_.readyState === 'closed') {
1650
+ if (this.ended() || this.closed()) {
1611
1651
  return;
1612
1652
  }
1613
1653
  // Tizen won't let us pass undefined, but it will let us omit the
@@ -1859,11 +1899,13 @@ shaka.media.MediaSourceEngine = class {
1859
1899
 
1860
1900
  /** @type {!Array.<!shaka.util.PublicPromise>} */
1861
1901
  const allWaiters = [];
1902
+ /** @type {!Array.<!shaka.util.ManifestParserUtils.ContentType>} */
1903
+ const contentTypes = Object.keys(this.sourceBuffers_);
1862
1904
 
1863
1905
  // Enqueue a 'wait' operation onto each queue.
1864
1906
  // This operation signals its readiness when it starts.
1865
1907
  // When all wait operations are ready, the real operation takes place.
1866
- for (const contentType in this.sourceBuffers_) {
1908
+ for (const contentType of contentTypes) {
1867
1909
  const ready = new shaka.util.PublicPromise();
1868
1910
  const operation = {
1869
1911
  start: () => ready.resolve(),
@@ -1893,7 +1935,7 @@ shaka.media.MediaSourceEngine = class {
1893
1935
  // assert at the end of destroy passes. In compiled mode, the queues
1894
1936
  // are wiped in destroy.
1895
1937
  if (goog.DEBUG) {
1896
- for (const contentType in this.sourceBuffers_) {
1938
+ for (const contentType of contentTypes) {
1897
1939
  if (this.queues_[contentType].length) {
1898
1940
  goog.asserts.assert(
1899
1941
  this.queues_[contentType].length == 1,
@@ -1910,7 +1952,7 @@ shaka.media.MediaSourceEngine = class {
1910
1952
 
1911
1953
  if (goog.DEBUG) {
1912
1954
  // If we did it correctly, nothing is updating.
1913
- for (const contentType in this.sourceBuffers_) {
1955
+ for (const contentType of contentTypes) {
1914
1956
  goog.asserts.assert(
1915
1957
  this.sourceBuffers_[contentType].updating == false,
1916
1958
  'SourceBuffers should not be updating after a blocking op!');
@@ -1930,7 +1972,7 @@ shaka.media.MediaSourceEngine = class {
1930
1972
  null);
1931
1973
  } finally {
1932
1974
  // Unblock the queues.
1933
- for (const contentType in this.sourceBuffers_) {
1975
+ for (const contentType of contentTypes) {
1934
1976
  this.popFromQueue_(contentType);
1935
1977
  }
1936
1978
  }
@@ -1942,6 +1984,7 @@ shaka.media.MediaSourceEngine = class {
1942
1984
  * @private
1943
1985
  */
1944
1986
  popFromQueue_(contentType) {
1987
+ goog.asserts.assert(this.queues_[contentType], 'Queue should exist');
1945
1988
  // Remove the in-progress operation, which is now complete.
1946
1989
  this.queues_[contentType].shift();
1947
1990
  this.startOperation_(contentType);
@@ -2099,48 +2142,6 @@ shaka.media.MediaSourceEngine = class {
2099
2142
  null);
2100
2143
  }
2101
2144
 
2102
- /**
2103
- * Returns the source buffer parameters
2104
- * @param {shaka.util.ManifestParserUtils.ContentType} contentType
2105
- * @return {?shaka.media.MediaSourceEngine.SourceBufferParams}
2106
- * @private
2107
- */
2108
- getSourceBufferParams_(contentType) {
2109
- if (!this.sourceBuffers_[contentType]) {
2110
- return null;
2111
- }
2112
- return {
2113
- timestampOffset: this.sourceBuffers_[contentType].timestampOffset,
2114
- appendWindowStart: this.sourceBuffers_[contentType].appendWindowStart,
2115
- appendWindowEnd: this.sourceBuffers_[contentType].appendWindowEnd,
2116
- };
2117
- }
2118
-
2119
- /**
2120
- * Restore source buffer parameters
2121
- * @param {shaka.util.ManifestParserUtils.ContentType} contentType
2122
- * @param {?shaka.media.MediaSourceEngine.SourceBufferParams} params
2123
- * @private
2124
- */
2125
- restoreSourceBufferParams_(contentType, params) {
2126
- if (!params) {
2127
- return;
2128
- }
2129
-
2130
- if (!this.sourceBuffers_[contentType]) {
2131
- shaka.log.warning('Attempted to restore a non-existent source buffer');
2132
- return;
2133
- }
2134
-
2135
- this.sourceBuffers_[contentType].timestampOffset =
2136
- params.timestampOffset;
2137
- // `end` needs to be set before `start`
2138
- this.sourceBuffers_[contentType].appendWindowEnd =
2139
- params.appendWindowEnd;
2140
- this.sourceBuffers_[contentType].appendWindowStart =
2141
- params.appendWindowStart;
2142
- }
2143
-
2144
2145
  /**
2145
2146
  * Resets the MediaSource and re-adds source buffers due to codec mismatch
2146
2147
  *
@@ -2149,7 +2150,6 @@ shaka.media.MediaSourceEngine = class {
2149
2150
  * @private
2150
2151
  */
2151
2152
  async reset_(streamsByType) {
2152
- const Functional = shaka.util.Functional;
2153
2153
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
2154
2154
  this.reloadingMediaSource_ = true;
2155
2155
  this.needSplitMuxedContent_ = false;
@@ -2171,36 +2171,19 @@ shaka.media.MediaSourceEngine = class {
2171
2171
  try {
2172
2172
  this.eventManager_.removeAll();
2173
2173
 
2174
- const cleanup = [];
2175
2174
  for (const contentType in this.transmuxers_) {
2176
- cleanup.push(this.transmuxers_[contentType].destroy());
2177
- }
2178
- for (const contentType in this.queues_) {
2179
- // Make a local copy of the queue and the first item.
2180
- const q = this.queues_[contentType];
2181
- const inProgress = q[0];
2182
-
2183
- // Drop everything else out of the original queue.
2184
- this.queues_[contentType] = q.slice(0, 1);
2185
-
2186
- // We will wait for this item to complete/fail.
2187
- if (inProgress) {
2188
- cleanup.push(inProgress.p.catch(Functional.noop));
2189
- }
2190
-
2191
- // The rest will be rejected silently if possible.
2192
- for (const item of q.slice(1)) {
2193
- item.p.reject(shaka.util.Destroyer.destroyedError());
2194
- }
2175
+ this.transmuxers_[contentType].destroy();
2195
2176
  }
2196
2177
  for (const contentType in this.sourceBuffers_) {
2197
2178
  const sourceBuffer = this.sourceBuffers_[contentType];
2198
2179
  try {
2199
2180
  this.mediaSource_.removeSourceBuffer(sourceBuffer);
2200
- } catch (e) {}
2181
+ } catch (e) {
2182
+ shaka.log.debug('Exception on removeSourceBuffer', e);
2183
+ }
2201
2184
  }
2202
- await Promise.all(cleanup);
2203
2185
  this.transmuxers_ = {};
2186
+ this.sourceBuffers_ = {};
2204
2187
 
2205
2188
  const previousDuration = this.mediaSource_.duration;
2206
2189
  this.mediaSourceOpen_ = new shaka.util.PublicPromise();
@@ -2231,23 +2214,17 @@ shaka.media.MediaSourceEngine = class {
2231
2214
  onSourceBufferAdded);
2232
2215
 
2233
2216
  for (const contentType of streamsByType.keys()) {
2234
- const previousParams = this.getSourceBufferParams_(contentType);
2235
2217
  const stream = streamsByType.get(contentType);
2236
2218
  // eslint-disable-next-line no-await-in-loop
2237
2219
  await this.initSourceBuffer_(contentType, stream, stream.codecs);
2238
- if (this.needSplitMuxedContent_) {
2239
- this.queues_[ContentType.AUDIO] = [];
2240
- this.queues_[ContentType.VIDEO] = [];
2241
- } else {
2242
- this.queues_[contentType] = [];
2243
- }
2244
-
2245
- this.restoreSourceBufferParams_(contentType, previousParams);
2246
2220
  }
2247
2221
  const audio = streamsByType.get(ContentType.AUDIO);
2248
2222
  if (audio && audio.isAudioMuxedInVideo) {
2249
2223
  this.needSplitMuxedContent_ = true;
2250
2224
  }
2225
+ if (this.needSplitMuxedContent_ && !this.queues_[ContentType.AUDIO]) {
2226
+ this.queues_[ContentType.AUDIO] = [];
2227
+ }
2251
2228
 
2252
2229
  // Fake a seek to catchup the playhead.
2253
2230
  this.video_.currentTime = currentTime;
@@ -2286,32 +2263,32 @@ shaka.media.MediaSourceEngine = class {
2286
2263
  }
2287
2264
 
2288
2265
  /**
2289
- * Codec switch if necessary, this will not resolve until the codec
2290
- * switch is over.
2291
2266
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
2292
2267
  * @param {string} mimeType
2293
2268
  * @param {string} codecs
2294
- * @param {!Map.<shaka.util.ManifestParserUtils.ContentType,
2295
- * shaka.extern.Stream>} streamsByType
2296
- * @return {!Promise.<boolean>} true if there was a codec switch,
2297
- * false otherwise.
2269
+ * @return {{transmuxer: ?shaka.extern.Transmuxer,
2270
+ * transmuxerMuxed: boolean, basicType: string, codec: string,
2271
+ * mimeType: string}}
2298
2272
  * @private
2299
2273
  */
2300
- async codecSwitchIfNecessary_(contentType, mimeType, codecs, streamsByType) {
2274
+ getRealInfo_(contentType, mimeType, codecs) {
2301
2275
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
2302
- if (contentType == ContentType.TEXT) {
2303
- return false;
2304
- }
2305
2276
  const MimeUtils = shaka.util.MimeUtils;
2306
- const currentCodec = MimeUtils.getNormalizedCodec(
2307
- MimeUtils.getCodecs(this.sourceBufferTypes_[contentType]));
2308
- const currentBasicType = MimeUtils.getBasicType(
2309
- this.sourceBufferTypes_[contentType]);
2310
-
2311
2277
  /** @type {?shaka.extern.Transmuxer} */
2312
2278
  let transmuxer;
2313
2279
  let transmuxerMuxed = false;
2314
- let newMimeType = shaka.util.MimeUtils.getFullType(mimeType, codecs);
2280
+ const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
2281
+ ContentType.AUDIO, (codecs || '').split(','));
2282
+ const videoCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
2283
+ ContentType.VIDEO, (codecs || '').split(','));
2284
+ let codec = videoCodec;
2285
+ if (contentType == ContentType.AUDIO) {
2286
+ codec = audioCodec;
2287
+ }
2288
+ if (!codec) {
2289
+ codec = codecs;
2290
+ }
2291
+ let newMimeType = shaka.util.MimeUtils.getFullType(mimeType, codec);
2315
2292
  let needTransmux = this.config_.forceTransmux;
2316
2293
  if (!shaka.media.Capabilities.isTypeSupported(newMimeType) ||
2317
2294
  (!this.sequenceMode_ &&
@@ -2321,36 +2298,79 @@ shaka.media.MediaSourceEngine = class {
2321
2298
  const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
2322
2299
  if (needTransmux) {
2323
2300
  const newMimeTypeWithAllCodecs =
2324
- shaka.util.MimeUtils.getFullTypeWithAllCodecs(mimeType, codecs);
2301
+ shaka.util.MimeUtils.getFullTypeWithAllCodecs(mimeType, codec);
2325
2302
  const transmuxerPlugin =
2326
2303
  TransmuxerEngine.findTransmuxer(newMimeTypeWithAllCodecs);
2327
2304
  if (transmuxerPlugin) {
2328
2305
  transmuxer = transmuxerPlugin();
2329
- const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
2330
- ContentType.AUDIO, (codecs || '').split(','));
2331
- const videoCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
2332
- ContentType.VIDEO, (codecs || '').split(','));
2333
2306
  if (audioCodec && videoCodec) {
2334
2307
  transmuxerMuxed = true;
2335
- let codec = videoCodec;
2336
- if (contentType == ContentType.AUDIO) {
2337
- codec = audioCodec;
2338
- }
2339
- newMimeType = transmuxer.convertCodecs(contentType,
2340
- shaka.util.MimeUtils.getFullTypeWithAllCodecs(mimeType, codec));
2341
- } else {
2342
- newMimeType =
2343
- transmuxer.convertCodecs(contentType, newMimeTypeWithAllCodecs);
2344
2308
  }
2309
+ newMimeType =
2310
+ transmuxer.convertCodecs(contentType, newMimeTypeWithAllCodecs);
2345
2311
  }
2346
2312
  }
2347
2313
 
2348
2314
  const newCodec = MimeUtils.getNormalizedCodec(
2349
2315
  MimeUtils.getCodecs(newMimeType));
2350
2316
  const newBasicType = MimeUtils.getBasicType(newMimeType);
2317
+ return {
2318
+ transmuxer,
2319
+ transmuxerMuxed,
2320
+ basicType: newBasicType,
2321
+ codec: newCodec,
2322
+ mimeType: newMimeType,
2323
+ };
2324
+ }
2325
+
2326
+ /**
2327
+ * Codec switch if necessary, this will not resolve until the codec
2328
+ * switch is over.
2329
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
2330
+ * @param {string} mimeType
2331
+ * @param {string} codecs
2332
+ * @param {!Map.<shaka.util.ManifestParserUtils.ContentType,
2333
+ * shaka.extern.Stream>} streamsByType
2334
+ * @return {!Promise.<boolean>} true if there was a codec switch,
2335
+ * false otherwise.
2336
+ * @private
2337
+ */
2338
+ async codecSwitchIfNecessary_(contentType, mimeType, codecs, streamsByType) {
2339
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
2340
+ if (contentType == ContentType.TEXT) {
2341
+ return false;
2342
+ }
2343
+ const MimeUtils = shaka.util.MimeUtils;
2344
+ const currentCodec = MimeUtils.getNormalizedCodec(
2345
+ MimeUtils.getCodecs(this.sourceBufferTypes_[contentType]));
2346
+ const currentBasicType = MimeUtils.getBasicType(
2347
+ this.sourceBufferTypes_[contentType]);
2348
+
2349
+ const realInfo = this.getRealInfo_(contentType, mimeType, codecs);
2350
+ const transmuxer = realInfo.transmuxer;
2351
+ const transmuxerMuxed = realInfo.transmuxerMuxed;
2352
+ const newBasicType = realInfo.basicType;
2353
+ const newCodec = realInfo.codec;
2354
+ const newMimeType = realInfo.mimeType;
2355
+
2356
+ let muxedContentCheck = true;
2357
+ if (transmuxerMuxed) {
2358
+ const muxedRealInfo =
2359
+ this.getRealInfo_(ContentType.AUDIO, mimeType, codecs);
2360
+ const muxedCurrentCodec = MimeUtils.getNormalizedCodec(
2361
+ MimeUtils.getCodecs(this.sourceBufferTypes_[ContentType.AUDIO]));
2362
+ const muxedCurrentBasicType = MimeUtils.getBasicType(
2363
+ this.sourceBufferTypes_[ContentType.AUDIO]);
2364
+ muxedContentCheck = muxedCurrentCodec == muxedRealInfo.codec &&
2365
+ muxedCurrentBasicType == muxedRealInfo.basicType;
2366
+ if (muxedRealInfo.transmuxer) {
2367
+ muxedRealInfo.transmuxer.destroy();
2368
+ }
2369
+ }
2351
2370
 
2352
2371
  // Current/new codecs base and basic type match then no need to switch
2353
- if (currentCodec === newCodec && currentBasicType === newBasicType) {
2372
+ if (currentCodec === newCodec && currentBasicType === newBasicType &&
2373
+ muxedContentCheck) {
2354
2374
  if (this.transmuxers_[contentType] && !transmuxer) {
2355
2375
  this.transmuxers_[contentType].destroy();
2356
2376
  delete this.transmuxers_[contentType];
@@ -2393,13 +2413,12 @@ shaka.media.MediaSourceEngine = class {
2393
2413
  * Returns true if it's necessary codec switch to load the new stream.
2394
2414
  *
2395
2415
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
2396
- * @param {shaka.extern.Stream} stream
2397
2416
  * @param {string} refMimeType
2398
2417
  * @param {string} refCodecs
2399
2418
  * @return {boolean}
2400
2419
  * @private
2401
2420
  */
2402
- isCodecSwitchNecessary_(contentType, stream, refMimeType, refCodecs) {
2421
+ isCodecSwitchNecessary_(contentType, refMimeType, refCodecs) {
2403
2422
  if (contentType == shaka.util.ManifestParserUtils.ContentType.TEXT) {
2404
2423
  return false;
2405
2424
  }
@@ -2443,13 +2462,12 @@ shaka.media.MediaSourceEngine = class {
2443
2462
  * new stream.
2444
2463
  *
2445
2464
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
2446
- * @param {shaka.extern.Stream} stream
2447
2465
  * @param {string} mimeType
2448
2466
  * @param {string} codecs
2449
2467
  * @return {boolean}
2450
2468
  */
2451
- isResetMediaSourceNecessary(contentType, stream, mimeType, codecs) {
2452
- if (!this.isCodecSwitchNecessary_(contentType, stream, mimeType, codecs)) {
2469
+ isResetMediaSourceNecessary(contentType, mimeType, codecs) {
2470
+ if (!this.isCodecSwitchNecessary_(contentType, mimeType, codecs)) {
2453
2471
  return false;
2454
2472
  }
2455
2473
 
@@ -2540,12 +2558,3 @@ shaka.media.MediaSourceEngine.SourceBufferMode_ = {
2540
2558
  * Called when an embedded 'emsg' box should trigger a manifest update.
2541
2559
  */
2542
2560
  shaka.media.MediaSourceEngine.PlayerInterface;
2543
-
2544
- /**
2545
- * @typedef {{
2546
- * timestampOffset: number,
2547
- * appendWindowStart: number,
2548
- * appendWindowEnd: number
2549
- * }}
2550
- */
2551
- shaka.media.MediaSourceEngine.SourceBufferParams;
@@ -48,9 +48,11 @@ shaka.media.SegmentUtils = class {
48
48
 
49
49
  /**
50
50
  * @param {!BufferSource} data
51
+ * @param {boolean=} disableAudio
52
+ * @param {boolean=} disableVideo
51
53
  * @return {?shaka.media.SegmentUtils.BasicInfo}
52
54
  */
53
- static getBasicInfoFromTs(data) {
55
+ static getBasicInfoFromTs(data, disableAudio = false, disableVideo = false) {
54
56
  const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
55
57
  const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
56
58
  const tsCodecs = tsParser.getCodecs();
@@ -58,50 +60,54 @@ shaka.media.SegmentUtils = class {
58
60
  const codecs = [];
59
61
  let hasAudio = false;
60
62
  let hasVideo = false;
61
- switch (tsCodecs.audio) {
62
- case 'aac':
63
- case 'aac-loas':
64
- codecs.push('mp4a.40.2');
65
- hasAudio = true;
66
- break;
67
- case 'mp3':
68
- codecs.push('mp4a.40.34');
69
- hasAudio = true;
70
- break;
71
- case 'ac3':
72
- codecs.push('ac-3');
73
- hasAudio = true;
74
- break;
75
- case 'ec3':
76
- codecs.push('ec-3');
77
- hasAudio = true;
78
- break;
79
- case 'opus':
80
- codecs.push('opus');
81
- hasAudio = true;
82
- break;
63
+ if (!disableAudio) {
64
+ switch (tsCodecs.audio) {
65
+ case 'aac':
66
+ case 'aac-loas':
67
+ codecs.push('mp4a.40.2');
68
+ hasAudio = true;
69
+ break;
70
+ case 'mp3':
71
+ codecs.push('mp4a.40.34');
72
+ hasAudio = true;
73
+ break;
74
+ case 'ac3':
75
+ codecs.push('ac-3');
76
+ hasAudio = true;
77
+ break;
78
+ case 'ec3':
79
+ codecs.push('ec-3');
80
+ hasAudio = true;
81
+ break;
82
+ case 'opus':
83
+ codecs.push('opus');
84
+ hasAudio = true;
85
+ break;
86
+ }
83
87
  }
84
- switch (tsCodecs.video) {
85
- case 'avc':
86
- if (videoInfo.codec) {
87
- codecs.push(videoInfo.codec);
88
- } else {
89
- codecs.push('avc1.42E01E');
90
- }
91
- hasVideo = true;
92
- break;
93
- case 'hvc':
94
- if (videoInfo.codec) {
95
- codecs.push(videoInfo.codec);
96
- } else {
97
- codecs.push('hvc1.1.6.L93.90');
98
- }
99
- hasVideo = true;
100
- break;
101
- case 'av1':
102
- codecs.push('av01.0.01M.08');
103
- hasVideo = true;
104
- break;
88
+ if (!disableVideo) {
89
+ switch (tsCodecs.video) {
90
+ case 'avc':
91
+ if (videoInfo.codec) {
92
+ codecs.push(videoInfo.codec);
93
+ } else {
94
+ codecs.push('avc1.42E01E');
95
+ }
96
+ hasVideo = true;
97
+ break;
98
+ case 'hvc':
99
+ if (videoInfo.codec) {
100
+ codecs.push(videoInfo.codec);
101
+ } else {
102
+ codecs.push('hvc1.1.6.L93.90');
103
+ }
104
+ hasVideo = true;
105
+ break;
106
+ case 'av1':
107
+ codecs.push('av01.0.01M.08');
108
+ hasVideo = true;
109
+ break;
110
+ }
105
111
  }
106
112
  if (!codecs.length) {
107
113
  return null;
@@ -2011,7 +2011,7 @@ shaka.media.StreamingEngine = class {
2011
2011
  const isResetMediaSourceNecessary =
2012
2012
  mediaState.lastCodecs && mediaState.lastMimeType &&
2013
2013
  this.playerInterface_.mediaSourceEngine.isResetMediaSourceNecessary(
2014
- mediaState.type, mediaState.stream, mimeType, fullCodecs);
2014
+ mediaState.type, mimeType, fullCodecs);
2015
2015
  if (isResetMediaSourceNecessary) {
2016
2016
  let otherState = null;
2017
2017
  if (mediaState.type === ContentType.VIDEO) {
@@ -2025,6 +2025,10 @@ shaka.media.StreamingEngine = class {
2025
2025
  // Then clear our cache of the last init segment, since MSE will be
2026
2026
  // reloaded and no init segment will be there post-reload.
2027
2027
  otherState.lastInitSegmentReference = null;
2028
+ // Clear cache of append window start and end, since they will need
2029
+ // to be reapplied post-reload by streaming engine.
2030
+ otherState.lastAppendWindowStart = null;
2031
+ otherState.lastAppendWindowEnd = null;
2028
2032
  // Now force the existing buffer to be cleared. It is not necessary
2029
2033
  // to perform the MSE clear operation, but this has the side-effect
2030
2034
  // that our state for that stream will then match MSE's post-reload
@@ -2729,6 +2733,8 @@ shaka.media.StreamingEngine = class {
2729
2733
  const audioMediaState = this.mediaStates_.get(ContentType.AUDIO);
2730
2734
  if (audioMediaState) {
2731
2735
  audioMediaState.lastInitSegmentReference = null;
2736
+ audioMediaState.lastAppendWindowStart = null;
2737
+ audioMediaState.lastAppendWindowEnd = null;
2732
2738
  if (clearBuffer) {
2733
2739
  this.forceClearBuffer_(audioMediaState);
2734
2740
  }
@@ -2740,6 +2746,8 @@ shaka.media.StreamingEngine = class {
2740
2746
  const videoMediaState = this.mediaStates_.get(ContentType.VIDEO);
2741
2747
  if (videoMediaState) {
2742
2748
  videoMediaState.lastInitSegmentReference = null;
2749
+ videoMediaState.lastAppendWindowStart = null;
2750
+ videoMediaState.lastAppendWindowEnd = null;
2743
2751
  if (clearBuffer) {
2744
2752
  this.forceClearBuffer_(videoMediaState);
2745
2753
  }
@@ -7,6 +7,7 @@
7
7
  goog.provide('shaka.offline.DownloadInfo');
8
8
 
9
9
  goog.require('shaka.util.Networking');
10
+ goog.require('shaka.util.Uint8ArrayUtils');
10
11
  goog.requireType('shaka.media.InitSegmentReference');
11
12
  goog.requireType('shaka.media.SegmentReference');
12
13
 
@@ -43,6 +44,10 @@ shaka.offline.DownloadInfo = class {
43
44
  * @return {string}
44
45
  */
45
46
  static idForSegmentRef(ref) {
47
+ const segmentData = ref.getSegmentData();
48
+ if (segmentData) {
49
+ return shaka.util.Uint8ArrayUtils.toBase64(segmentData);
50
+ }
46
51
  // Escape the URIs using encodeURI, to make sure that a weirdly formed URI
47
52
  // cannot cause two unrelated refs to be considered equivalent.
48
53
  const removeSprites = (uri) => {