shaka-player 3.1.4 → 3.3.0

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 (214) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
  3. package/.github/ISSUE_TEMPLATE/question.md +1 -1
  4. package/AUTHORS +6 -5
  5. package/CHANGELOG.md +363 -0
  6. package/CONTRIBUTORS +4 -2
  7. package/README.md +4 -4
  8. package/build/types/core +3 -0
  9. package/build/types/offline +1 -0
  10. package/build/types/polyfill +2 -0
  11. package/build/types/ui +2 -0
  12. package/demo/common/asset.js +9 -7
  13. package/demo/common/assets.js +28 -1
  14. package/demo/common/message_ids.js +15 -2
  15. package/demo/config.js +35 -2
  16. package/demo/index.html +1 -1
  17. package/demo/locales/en.json +14 -1
  18. package/demo/locales/source.json +56 -4
  19. package/demo/main.js +2 -1
  20. package/dist/controls.css +26 -2
  21. package/dist/controls.css.map +1 -1
  22. package/dist/demo.compiled.debug.js +24 -21
  23. package/dist/demo.compiled.debug.map +3 -3
  24. package/dist/demo.compiled.js +24 -21
  25. package/dist/demo.compiled.map +3 -3
  26. package/dist/deps.js +33 -25
  27. package/dist/locales.js +31 -29
  28. package/dist/receiver.compiled.debug.js +14 -12
  29. package/dist/receiver.compiled.debug.map +3 -3
  30. package/dist/receiver.compiled.js +14 -12
  31. package/dist/receiver.compiled.map +3 -3
  32. package/dist/shaka-player.compiled.d.ts +258 -20
  33. package/dist/shaka-player.compiled.debug.d.ts +258 -20
  34. package/dist/shaka-player.compiled.debug.externs.js +1005 -695
  35. package/dist/shaka-player.compiled.debug.js +720 -640
  36. package/dist/shaka-player.compiled.debug.map +4 -4
  37. package/dist/shaka-player.compiled.externs.js +1005 -695
  38. package/dist/shaka-player.compiled.js +850 -817
  39. package/dist/shaka-player.compiled.map +4 -4
  40. package/dist/shaka-player.ui.d.ts +274 -21
  41. package/dist/shaka-player.ui.debug.d.ts +274 -21
  42. package/dist/shaka-player.ui.debug.externs.js +766 -429
  43. package/dist/shaka-player.ui.debug.js +863 -765
  44. package/dist/shaka-player.ui.debug.map +4 -4
  45. package/dist/shaka-player.ui.externs.js +766 -429
  46. package/dist/shaka-player.ui.js +1042 -996
  47. package/dist/shaka-player.ui.map +4 -4
  48. package/docs/design/bg-fetch-after.gv +29 -0
  49. package/docs/design/bg-fetch-after.gv.png +0 -0
  50. package/docs/design/bg-fetch-before.gv +18 -0
  51. package/docs/design/bg-fetch-before.gv.png +0 -0
  52. package/docs/design/bg-fetch.md +134 -0
  53. package/docs/tutorials/blob-url.md +15 -0
  54. package/docs/tutorials/index.json +1 -0
  55. package/docs/tutorials/ui-customization.md +66 -0
  56. package/docs/tutorials/ui.md +5 -5
  57. package/externs/aria.js +6 -0
  58. package/externs/ima.js +3 -0
  59. package/externs/shaka/ads.js +29 -1
  60. package/externs/shaka/manifest.js +18 -2
  61. package/externs/shaka/manifest_parser.js +10 -1
  62. package/externs/shaka/net.js +20 -4
  63. package/externs/shaka/offline.js +36 -2
  64. package/externs/shaka/player.js +182 -13
  65. package/externs/shaka/text.js +24 -10
  66. package/externs/texttrack.js +3 -0
  67. package/externs/webcrypto.js +18 -0
  68. package/lib/abr/ewma.js +14 -0
  69. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  70. package/lib/abr/simple_abr_manager.js +6 -0
  71. package/lib/ads/ad_manager.js +16 -15
  72. package/lib/ads/client_side_ad.js +8 -0
  73. package/lib/ads/client_side_ad_manager.js +14 -6
  74. package/lib/ads/server_side_ad.js +7 -0
  75. package/lib/ads/server_side_ad_manager.js +19 -2
  76. package/lib/cast/cast_proxy.js +8 -0
  77. package/lib/cast/cast_utils.js +7 -1
  78. package/lib/cea/cea608_data_channel.js +56 -56
  79. package/lib/cea/cea608_memory.js +33 -33
  80. package/lib/cea/cea708_service.js +43 -43
  81. package/lib/cea/cea708_window.js +38 -38
  82. package/lib/cea/cea_decoder.js +13 -14
  83. package/lib/cea/cea_utils.js +23 -23
  84. package/lib/cea/dtvcc_packet_builder.js +7 -7
  85. package/lib/cea/i_caption_decoder.js +2 -3
  86. package/lib/cea/i_cea_parser.js +2 -2
  87. package/lib/cea/mp4_cea_parser.js +6 -6
  88. package/lib/dash/dash_parser.js +53 -14
  89. package/lib/dash/segment_base.js +25 -1
  90. package/lib/dash/segment_list.js +18 -9
  91. package/lib/dash/segment_template.js +23 -10
  92. package/lib/hls/hls_parser.js +210 -30
  93. package/lib/media/closed_caption_parser.js +1 -2
  94. package/lib/media/drm_engine.js +162 -192
  95. package/lib/media/quality_observer.js +291 -0
  96. package/lib/media/segment_index.js +9 -1
  97. package/lib/media/segment_reference.js +37 -5
  98. package/lib/media/streaming_engine.js +48 -10
  99. package/lib/net/http_fetch_plugin.js +31 -11
  100. package/lib/net/http_plugin_utils.js +1 -0
  101. package/lib/net/http_xhr_plugin.js +40 -14
  102. package/lib/net/networking_engine.js +82 -18
  103. package/lib/offline/download_info.js +68 -0
  104. package/lib/offline/download_manager.js +14 -7
  105. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  106. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  107. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  108. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  109. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  110. package/lib/offline/manifest_converter.js +3 -1
  111. package/lib/offline/storage.js +404 -297
  112. package/lib/offline/stored_content_utils.js +2 -0
  113. package/lib/player.js +622 -153
  114. package/lib/polyfill/aria.js +82 -0
  115. package/lib/polyfill/random_uuid.js +52 -0
  116. package/lib/routing/walker.js +2 -3
  117. package/lib/text/cue.js +12 -0
  118. package/lib/text/srt_text_parser.js +4 -4
  119. package/lib/text/text_engine.js +2 -2
  120. package/lib/text/ttml_text_parser.js +25 -2
  121. package/lib/text/ui_text_displayer.js +2 -2
  122. package/lib/text/vtt_text_parser.js +2 -2
  123. package/lib/text/web_vtt_generator.js +55 -12
  124. package/lib/util/cmcd_manager.js +826 -0
  125. package/lib/util/error.js +12 -0
  126. package/lib/util/language_utils.js +12 -0
  127. package/lib/util/manifest_parser_utils.js +1 -0
  128. package/lib/util/mp4_box_parsers.js +20 -20
  129. package/lib/util/multi_map.js +8 -0
  130. package/lib/util/mutex.js +52 -0
  131. package/lib/util/periods.js +98 -64
  132. package/lib/util/player_configuration.js +31 -1
  133. package/lib/util/stream_utils.js +140 -85
  134. package/lib/util/xml_utils.js +31 -12
  135. package/package.json +1 -1
  136. package/roadmap.md +24 -22
  137. package/shaka-player.uncompiled.js +4 -0
  138. package/test/cast/cast_utils_unit.js +1 -0
  139. package/test/cea/cea608_memory_unit.js +1 -1
  140. package/test/cea/cea708_service_unit.js +8 -8
  141. package/test/cea/cea708_window_unit.js +6 -6
  142. package/test/cea/cea_decoder_unit.js +1 -1
  143. package/test/cea/dtvcc_packet_builder_unit.js +2 -2
  144. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  145. package/test/dash/dash_parser_live_unit.js +101 -1
  146. package/test/dash/dash_parser_manifest_unit.js +78 -2
  147. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  148. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  149. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  150. package/test/hls/hls_live_unit.js +2 -0
  151. package/test/hls/hls_parser_unit.js +252 -0
  152. package/test/media/drm_engine_integration.js +86 -12
  153. package/test/media/drm_engine_unit.js +310 -450
  154. package/test/media/quality_observer_unit.js +139 -0
  155. package/test/media/segment_reference_unit.js +16 -1
  156. package/test/media/streaming_engine_integration.js +2 -1
  157. package/test/media/streaming_engine_unit.js +2 -0
  158. package/test/net/http_plugin_unit.js +20 -15
  159. package/test/offline/manifest_convert_unit.js +2 -0
  160. package/test/offline/storage_integration.js +117 -23
  161. package/test/player_integration.js +151 -0
  162. package/test/player_src_equals_integration.js +52 -0
  163. package/test/player_unit.js +11 -33
  164. package/test/test/assets/chapters.srt +11 -0
  165. package/test/test/assets/chapters.vtt +10 -0
  166. package/test/test/util/cea_utils.js +12 -12
  167. package/test/test/util/dash_parser_util.js +4 -0
  168. package/test/test/util/fake_ad.js +7 -0
  169. package/test/test/util/fake_ad_manager.js +7 -0
  170. package/test/test/util/fake_drm_engine.js +9 -2
  171. package/test/test/util/manifest_generator.js +5 -2
  172. package/test/test/util/manifest_parser_util.js +3 -1
  173. package/test/test/util/test_scheme.js +14 -1
  174. package/test/text/ttml_text_parser_unit.js +47 -0
  175. package/test/text/web_vtt_generator_unit.js +81 -7
  176. package/test/ui/ui_unit.js +250 -0
  177. package/test/util/cmcd_manager_unit.js +277 -0
  178. package/test/util/stream_utils_unit.js +118 -10
  179. package/ui/airplay_button.js +2 -1
  180. package/ui/audio_language_selection.js +9 -5
  181. package/ui/cast_button.js +5 -6
  182. package/ui/constants.js +0 -3
  183. package/ui/context_menu.js +109 -0
  184. package/ui/controls.js +41 -7
  185. package/ui/controls.less +1 -0
  186. package/ui/enums.js +4 -0
  187. package/ui/externs/ui.js +28 -1
  188. package/ui/fast_forward_button.js +16 -8
  189. package/ui/fullscreen_button.js +2 -3
  190. package/ui/language_utils.js +1 -1
  191. package/ui/less/buttons.less +0 -4
  192. package/ui/less/containers.less +74 -0
  193. package/ui/less/overflow_menu.less +0 -10
  194. package/ui/less/tooltip.less +97 -0
  195. package/ui/locales/en.json +1 -0
  196. package/ui/locales/source.json +4 -0
  197. package/ui/loop_button.js +6 -3
  198. package/ui/mute_button.js +3 -4
  199. package/ui/overflow_menu.js +7 -28
  200. package/ui/pip_button.js +10 -7
  201. package/ui/play_button.js +2 -2
  202. package/ui/playback_rate_selection.js +11 -15
  203. package/ui/presentation_time.js +1 -3
  204. package/ui/resolution_selection.js +11 -7
  205. package/ui/rewind_button.js +17 -9
  206. package/ui/seek_bar.js +6 -6
  207. package/ui/settings_menu.js +36 -4
  208. package/ui/small_play_button.js +1 -0
  209. package/ui/spacer.js +1 -1
  210. package/ui/statistics_button.js +253 -0
  211. package/ui/text_selection.js +19 -15
  212. package/ui/ui.js +30 -0
  213. package/ui/ui_utils.js +1 -1
  214. package/ui/volume_bar.js +2 -4
@@ -13,6 +13,7 @@ goog.require('shaka.log');
13
13
  goog.require('shaka.media.DrmEngine');
14
14
  goog.require('shaka.media.ManifestParser');
15
15
  goog.require('shaka.net.NetworkingEngine');
16
+ goog.require('shaka.offline.DownloadInfo');
16
17
  goog.require('shaka.offline.DownloadManager');
17
18
  goog.require('shaka.offline.OfflineUri');
18
19
  goog.require('shaka.offline.SessionDeleter');
@@ -28,11 +29,10 @@ goog.require('shaka.util.Functional');
28
29
  goog.require('shaka.util.IDestroyable');
29
30
  goog.require('shaka.util.Iterables');
30
31
  goog.require('shaka.util.MimeUtils');
31
- goog.require('shaka.util.Networking');
32
+ goog.require('shaka.util.Mutex');
32
33
  goog.require('shaka.util.Platform');
33
34
  goog.require('shaka.util.PlayerConfiguration');
34
35
  goog.require('shaka.util.StreamUtils');
35
- goog.requireType('shaka.media.InitSegmentReference');
36
36
  goog.requireType('shaka.media.SegmentReference');
37
37
  goog.requireType('shaka.offline.StorageCellHandle');
38
38
 
@@ -95,14 +95,6 @@ shaka.offline.Storage = class {
95
95
  this.networkingEngine_ = new shaka.net.NetworkingEngine();
96
96
  }
97
97
 
98
- /**
99
- * A list of segment ids for all the segments that were added during the
100
- * current store. If the store fails or is aborted, these need to be
101
- * removed from storage.
102
- * @private {!Array.<number>}
103
- */
104
- this.segmentsFromStore_ = [];
105
-
106
98
  /**
107
99
  * A list of open operations that are being performed by this instance of
108
100
  * |shaka.offline.Storage|.
@@ -285,7 +277,7 @@ shaka.offline.Storage = class {
285
277
  store(uri, appMetadata, mimeType) {
286
278
  goog.asserts.assert(
287
279
  this.networkingEngine_,
288
- 'Cannot call |downloadManifest_| after calling |destroy|.');
280
+ 'Cannot call |store| after calling |destroy|.');
289
281
 
290
282
  // Get a copy of the current config.
291
283
  const config = this.getConfiguration();
@@ -371,6 +363,8 @@ shaka.offline.Storage = class {
371
363
  const muxer = new shaka.offline.StorageMuxer();
372
364
  /** @type {?shaka.offline.StorageCellHandle} */
373
365
  let activeHandle = null;
366
+ /** @type {?number} */
367
+ let manifestId = null;
374
368
 
375
369
  // This will be used to store any errors from drm engine. Whenever drm
376
370
  // engine is passed to another function to do work, we should check if this
@@ -398,6 +392,7 @@ shaka.offline.Storage = class {
398
392
  uri);
399
393
  }
400
394
 
395
+ // Create the DRM engine, and load the keys in the manifest.
401
396
  drmEngine = await this.createDrmEngine(
402
397
  manifest,
403
398
  (e) => { drmError = drmError || e; },
@@ -422,38 +417,38 @@ shaka.offline.Storage = class {
422
417
  this.ensureNotDestroyed_();
423
418
 
424
419
  goog.asserts.assert(drmEngine, 'drmEngine should be non-null here.');
420
+ const {manifestDB, toDownload} = this.makeManifestDB_(
421
+ drmEngine, manifest, uri, appMetadata, config, downloader);
425
422
 
426
- const manifestDB = await this.downloadManifest_(
427
- activeHandle.cell, drmEngine, manifest, uri, appMetadata, config,
428
- downloader);
423
+ // Store the empty manifest, before downloading the segments.
424
+ const ids = await activeHandle.cell.addManifests([manifestDB]);
425
+ this.ensureNotDestroyed_();
426
+ manifestId = ids[0];
427
+
428
+ goog.asserts.assert(drmEngine, 'drmEngine should be non-null here.');
429
429
  this.ensureNotDestroyed_();
430
430
  if (drmError) {
431
431
  throw drmError;
432
432
  }
433
433
 
434
- const ids = await activeHandle.cell.addManifests([manifestDB]);
434
+ await this.downloadSegments_(toDownload, manifestId, manifestDB,
435
+ downloader, config, activeHandle.cell, manifest, drmEngine);
435
436
  this.ensureNotDestroyed_();
436
437
 
437
438
  const offlineUri = shaka.offline.OfflineUri.manifest(
438
- activeHandle.path.mechanism, activeHandle.path.cell, ids[0]);
439
+ activeHandle.path.mechanism, activeHandle.path.cell, manifestId);
439
440
 
440
441
  return shaka.offline.StoredContentUtils.fromManifestDB(
441
442
  offlineUri, manifestDB);
442
443
  } catch (e) {
443
- // If we did start saving some data, we need to remove it all to avoid
444
- // wasting storage. However if the muxer did not manage to initialize,
445
- // then we won't have an active cell to remove the segments from.
446
- if (activeHandle) {
447
- await activeHandle.cell.removeSegments(
448
- this.segmentsFromStore_, () => {});
444
+ if (manifestId != null) {
445
+ await shaka.offline.Storage.cleanStoredManifest(manifestId);
449
446
  }
450
447
 
451
448
  // If we already had an error, ignore this error to avoid hiding
452
449
  // the original error.
453
450
  throw drmError || e;
454
451
  } finally {
455
- this.segmentsFromStore_ = [];
456
-
457
452
  await muxer.destroy();
458
453
 
459
454
  if (parser) {
@@ -466,6 +461,215 @@ shaka.offline.Storage = class {
466
461
  }
467
462
  }
468
463
 
464
+ /**
465
+ * Download and then store the contents of each segment.
466
+ * The promise this returns will wait for local downloads.
467
+ *
468
+ * @param {!Array.<!shaka.offline.DownloadInfo>} toDownload
469
+ * @param {number} manifestId
470
+ * @param {shaka.extern.ManifestDB} manifestDB
471
+ * @param {!shaka.offline.DownloadManager} downloader
472
+ * @param {shaka.extern.PlayerConfiguration} config
473
+ * @param {shaka.extern.StorageCell} storage
474
+ * @param {shaka.extern.Manifest} manifest
475
+ * @param {!shaka.media.DrmEngine} drmEngine
476
+ * @return {!Promise}
477
+ * @private
478
+ */
479
+ async downloadSegments_(
480
+ toDownload, manifestId, manifestDB, downloader, config, storage,
481
+ manifest, drmEngine) {
482
+ /**
483
+ * @param {!Array.<!shaka.offline.DownloadInfo>} toDownload
484
+ * @param {boolean} updateDRM
485
+ */
486
+ const download = async (toDownload, updateDRM) => {
487
+ const throwIfAbortedFn = () => {
488
+ this.ensureNotDestroyed_();
489
+ };
490
+ for (const download of toDownload) {
491
+ /** @param {?BufferSource} data */
492
+ let data;
493
+ const request = download.makeSegmentRequest(config);
494
+ const estimateId = download.estimateId;
495
+ const isInitSegment = download.isInitSegment;
496
+ const onDownloaded = (d) => {
497
+ data = d;
498
+ return Promise.resolve();
499
+ };
500
+ downloader.queue(download.groupId,
501
+ request, estimateId, isInitSegment, onDownloaded);
502
+ downloader.queueWork(download.groupId, async () => {
503
+ goog.asserts.assert(data, 'We should have loaded data by now');
504
+ goog.asserts.assert(data instanceof ArrayBuffer,
505
+ 'The data should be an ArrayBuffer');
506
+
507
+ const ref = /** @type {!shaka.media.SegmentReference} */ (
508
+ download.ref);
509
+ manifestDB = (await shaka.offline.Storage.assignStreamToManifest(
510
+ manifestId, ref, {data}, throwIfAbortedFn)) || manifestDB;
511
+ });
512
+ }
513
+ await downloader.waitToFinish();
514
+
515
+ if (updateDRM) {
516
+ // Re-store the manifest, to attach session IDs.
517
+ // These were (maybe) discovered inside the downloader; we can only add
518
+ // them now, at the end, since the manifestDB is in flux during the
519
+ // process of downloading and storing, and assignStreamToManifest does
520
+ // not know about the DRM engine.
521
+ this.ensureNotDestroyed_();
522
+ this.setManifestDrmFields_(manifest, manifestDB, drmEngine, config);
523
+ await storage.updateManifest(manifestId, manifestDB);
524
+ }
525
+ };
526
+
527
+ const usingBgFetch = false; // TODO: Get.
528
+
529
+ if (this.getManifestIsEncrypted_(manifest) && usingBgFetch &&
530
+ !this.getManifestIncludesInitData_(manifest)) {
531
+ // Background fetch can't make DRM sessions, so if we have to get the
532
+ // init data from the init segments, download those first before anything
533
+ // else.
534
+ await download(toDownload.filter((info) => info.isInitSegment), true);
535
+ this.ensureNotDestroyed_();
536
+ toDownload = toDownload.filter((info) => !info.isInitSegment);
537
+ }
538
+
539
+ if (!usingBgFetch) {
540
+ await download(toDownload, false);
541
+ this.ensureNotDestroyed_();
542
+
543
+ goog.asserts.assert(
544
+ !manifestDB.isIncomplete, 'The manifest should be complete by now');
545
+ } else {
546
+ // TODO: Send the request to the service worker. Don't await the result.
547
+ }
548
+ }
549
+
550
+ /**
551
+ * Removes all of the contents for a given manifest, statelessly.
552
+ *
553
+ * @param {number} manifestId
554
+ * @return {!Promise}
555
+ */
556
+ static async cleanStoredManifest(manifestId) {
557
+ const muxer = new shaka.offline.StorageMuxer();
558
+ await muxer.init();
559
+ const activeHandle = await muxer.getActive();
560
+ const uri = shaka.offline.OfflineUri.manifest(
561
+ activeHandle.path.mechanism,
562
+ activeHandle.path.cell,
563
+ manifestId);
564
+ await muxer.destroy();
565
+ const storage = new shaka.offline.Storage();
566
+ await storage.remove(uri.toString());
567
+ }
568
+
569
+ /**
570
+ * Load the given manifest, modifies it by assigning the given data to the
571
+ * segments corresponding to "ref", then re-stores the manifest.
572
+ * The parts of this function that modify the manifest are protected by a
573
+ * mutex, to prevent race conditions; specifically, it prevents two parallel
574
+ * instances of this method from both loading the manifest into memory at the
575
+ * same time, which would result in the slower/later call overwriting the
576
+ * changes of the other.
577
+ *
578
+ * @param {number} manifestId
579
+ * @param {!shaka.media.SegmentReference} ref
580
+ * @param {shaka.extern.SegmentDataDB} data
581
+ * @param {function()} throwIfAbortedFn A function that should throw if the
582
+ * download has been aborted.
583
+ * @return {!Promise.<?shaka.extern.ManifestDB>}
584
+ */
585
+ static async assignStreamToManifest(manifestId, ref, data, throwIfAbortedFn) {
586
+ /** @type {shaka.offline.StorageMuxer} */
587
+ const muxer = new shaka.offline.StorageMuxer();
588
+
589
+ const idForRef = shaka.offline.DownloadInfo.idForSegmentRef(ref);
590
+ let manifestUpdated = false;
591
+ let dataKey;
592
+ let activeHandle;
593
+ /** @type {!shaka.extern.ManifestDB} */
594
+ let manifestDB;
595
+
596
+ let mutexId = 0;
597
+
598
+ try {
599
+ await muxer.init();
600
+ activeHandle = await muxer.getActive();
601
+
602
+ // Store the data.
603
+ const dataKeys = await activeHandle.cell.addSegments([data]);
604
+ dataKey = dataKeys[0];
605
+ throwIfAbortedFn();
606
+
607
+ // Acquire the mutex before accessing the manifest, since there could be
608
+ // multiple instances of this method running at once.
609
+ mutexId = await shaka.offline.Storage.mutex_.acquire();
610
+ throwIfAbortedFn();
611
+
612
+ // Load the manifest.
613
+ const manifests = await activeHandle.cell.getManifests([manifestId]);
614
+ throwIfAbortedFn();
615
+ manifestDB = manifests[0];
616
+
617
+ // Assign the stored data to the manifest.
618
+ let complete = true;
619
+ for (const stream of manifestDB.streams) {
620
+ for (const segment of stream.segments) {
621
+ if (segment.pendingSegmentRefId == idForRef) {
622
+ segment.dataKey = dataKey;
623
+ // Now that the segment has been associated with the appropriate
624
+ // dataKey, the pendingSegmentRefId is no longer necessary.
625
+ segment.pendingSegmentRefId = undefined;
626
+ }
627
+ if (segment.pendingInitSegmentRefId == idForRef) {
628
+ segment.initSegmentKey = dataKey;
629
+ // Now that the init segment has been associated with the
630
+ // appropriate initSegmentKey, the pendingInitSegmentRefId is no
631
+ // longer necessary.
632
+ segment.pendingInitSegmentRefId = undefined;
633
+ }
634
+ if (segment.pendingSegmentRefId) {
635
+ complete = false;
636
+ }
637
+ if (segment.pendingInitSegmentRefId) {
638
+ complete = false;
639
+ }
640
+ }
641
+ }
642
+
643
+ // Update the size of the manifest.
644
+ manifestDB.size += data.data.byteLength;
645
+
646
+ // Mark the manifest as complete, if all segments are downloaded.
647
+ if (complete) {
648
+ manifestDB.isIncomplete = false;
649
+ }
650
+
651
+ // Re-store the manifest.
652
+ await activeHandle.cell.updateManifest(manifestId, manifestDB);
653
+ manifestUpdated = true;
654
+ throwIfAbortedFn();
655
+ } catch (e) {
656
+ await shaka.offline.Storage.cleanStoredManifest(manifestId);
657
+
658
+ if (activeHandle && !manifestUpdated && dataKey) {
659
+ // The cleanStoredManifest method will not "see" any segments that have
660
+ // been downloaded but not assigned to the manifest yet. So un-store
661
+ // them separately.
662
+ await activeHandle.cell.removeSegments([dataKey], (key) => {});
663
+ }
664
+
665
+ throw e;
666
+ } finally {
667
+ await muxer.destroy();
668
+ shaka.offline.Storage.mutex_.release(mutexId);
669
+ }
670
+ return manifestDB;
671
+ }
672
+
469
673
  /**
470
674
  * Filter |manifest| such that it will only contain the variants and text
471
675
  * streams that we want to store and can actually play.
@@ -484,25 +688,23 @@ shaka.offline.Storage = class {
484
688
  const maxHwRes = {width: Infinity, height: Infinity};
485
689
  StreamUtils.filterByRestrictions(manifest, config.restrictions, maxHwRes);
486
690
 
487
- // Filter the manifest based on what we know media source will be able to
488
- // play later (no point storing something we can't play).
489
- if (config.useMediaCapabilities) {
490
- StreamUtils.filterManifestByMediaCapabilities(
491
- manifest, config.offline.usePersistentLicense);
492
- } else {
493
- StreamUtils.filterManifestByMediaSource(manifest);
494
- // Filter the manifest based on what we know our drm system will support
495
- // playing later.
496
- StreamUtils.filterManifestByDrm(manifest, drmEngine);
497
- }
691
+ // Filter the manifest based on what we know MediaCapabilities will be able
692
+ // to play later (no point storing something we can't play).
693
+ StreamUtils.filterManifestByMediaCapabilities(
694
+ manifest, config.offline.usePersistentLicense);
498
695
 
499
696
  // Gather all tracks.
500
697
  const allTracks = [];
501
698
 
502
699
  // Choose the codec that has the lowest average bandwidth.
503
700
  const preferredAudioChannelCount = config.preferredAudioChannelCount;
701
+ const preferredDecodingAttributes = config.preferredDecodingAttributes;
702
+ const preferredVideoCodecs = config.preferredVideoCodecs;
703
+ const preferredAudioCodecs = config.preferredAudioCodecs;
704
+
504
705
  StreamUtils.chooseCodecsAndFilterManifest(
505
- manifest, preferredAudioChannelCount);
706
+ manifest, preferredVideoCodecs, preferredAudioCodecs,
707
+ preferredAudioChannelCount, preferredDecodingAttributes);
506
708
 
507
709
  for (const variant of manifest.variants) {
508
710
  goog.asserts.assert(
@@ -586,19 +788,21 @@ shaka.offline.Storage = class {
586
788
 
587
789
  /**
588
790
  * Create a download manager and download the manifest.
791
+ * This also sets up download infos for each segment to be downloaded.
589
792
  *
590
- * @param {shaka.extern.StorageCell} storage
591
793
  * @param {!shaka.media.DrmEngine} drmEngine
592
794
  * @param {shaka.extern.Manifest} manifest
593
795
  * @param {string} uri
594
796
  * @param {!Object} metadata
595
797
  * @param {shaka.extern.PlayerConfiguration} config
596
798
  * @param {!shaka.offline.DownloadManager} downloader
597
- * @return {!Promise.<shaka.extern.ManifestDB>}
799
+ * @return {{
800
+ * manifestDB: shaka.extern.ManifestDB,
801
+ * toDownload: !Array.<!shaka.offline.DownloadInfo>
802
+ * }}
598
803
  * @private
599
804
  */
600
- async downloadManifest_(
601
- storage, drmEngine, manifest, uri, metadata, config, downloader) {
805
+ makeManifestDB_(drmEngine, manifest, uri, metadata, config, downloader) {
602
806
  const pendingContent = shaka.offline.StoredContentUtils.fromManifest(
603
807
  uri, manifest, /* size= */ 0, metadata);
604
808
  // In https://github.com/google/shaka-player/issues/2652, we found that this
@@ -621,12 +825,73 @@ shaka.offline.Storage = class {
621
825
  };
622
826
  downloader.setCallbacks(onProgress, onInitData);
623
827
 
624
- const isEncrypted = manifest.variants.some((variant) => {
828
+ const needsInitData = this.getManifestIsEncrypted_(manifest) &&
829
+ !this.getManifestIncludesInitData_(manifest);
830
+
831
+ let currentSystemId = null;
832
+ if (needsInitData) {
833
+ const drmInfo = drmEngine.getDrmInfo();
834
+ currentSystemId =
835
+ shaka.offline.Storage.defaultSystemIds_.get(drmInfo.keySystem);
836
+ }
837
+
838
+ // Make the estimator, which is used to make the download registries.
839
+ const estimator = new shaka.offline.StreamBandwidthEstimator();
840
+ for (const stream of manifest.textStreams) {
841
+ estimator.addText(stream);
842
+ }
843
+ for (const stream of manifest.imageStreams) {
844
+ estimator.addImage(stream);
845
+ }
846
+ for (const variant of manifest.variants) {
847
+ estimator.addVariant(variant);
848
+ }
849
+ const {streams, toDownload} = this.createStreams_(
850
+ downloader, estimator, drmEngine, manifest, config);
851
+
852
+ const drmInfo = drmEngine.getDrmInfo();
853
+ const usePersistentLicense = config.offline.usePersistentLicense;
854
+ if (drmInfo && usePersistentLicense) {
855
+ // Don't store init data, since we have stored sessions.
856
+ drmInfo.initData = [];
857
+ }
858
+
859
+ const manifestDB = {
860
+ creationTime: Date.now(),
861
+ originalManifestUri: uri,
862
+ duration: manifest.presentationTimeline.getDuration(),
863
+ size: 0,
864
+ expiration: drmEngine.getExpiration(),
865
+ streams,
866
+ sessionIds: usePersistentLicense ? drmEngine.getSessionIds() : [],
867
+ drmInfo,
868
+ appMetadata: metadata,
869
+ isIncomplete: true,
870
+ };
871
+
872
+ return {manifestDB, toDownload};
873
+ }
874
+
875
+ /**
876
+ * @param {shaka.extern.Manifest} manifest
877
+ * @return {boolean}
878
+ * @private
879
+ */
880
+ getManifestIsEncrypted_(manifest) {
881
+ return manifest.variants.some((variant) => {
625
882
  const videoEncrypted = variant.video && variant.video.encrypted;
626
883
  const audioEncrypted = variant.audio && variant.audio.encrypted;
627
884
  return videoEncrypted || audioEncrypted;
628
885
  });
629
- const includesInitData = manifest.variants.some((variant) => {
886
+ }
887
+
888
+ /**
889
+ * @param {shaka.extern.Manifest} manifest
890
+ * @return {boolean}
891
+ * @private
892
+ */
893
+ getManifestIncludesInitData_(manifest) {
894
+ return manifest.variants.some((variant) => {
630
895
  const videoDrmInfos = variant.video ? variant.video.drmInfos : [];
631
896
  const audioDrmInfos = variant.audio ? variant.audio.drmInfos : [];
632
897
  const drmInfos = videoDrmInfos.concat(audioDrmInfos);
@@ -634,36 +899,28 @@ shaka.offline.Storage = class {
634
899
  return drmInfos.initData && drmInfos.initData.length;
635
900
  });
636
901
  });
637
- const needsInitData = isEncrypted && !includesInitData;
638
-
639
- let currentSystemId = null;
640
- if (needsInitData) {
641
- const drmInfo = drmEngine.getDrmInfo();
642
- currentSystemId =
643
- shaka.offline.Storage.defaultSystemIds_.get(drmInfo.keySystem);
644
- }
645
-
646
- try {
647
- const manifestDB = this.createOfflineManifest_(
648
- downloader, storage, drmEngine, manifest, uri, metadata, config);
902
+ }
649
903
 
650
- manifestDB.size = await downloader.waitToFinish();
651
- manifestDB.expiration = drmEngine.getExpiration();
652
- const sessions = drmEngine.getSessionIds();
653
- manifestDB.sessionIds = config.offline.usePersistentLicense ?
654
- sessions : [];
904
+ /**
905
+ * @param {shaka.extern.Manifest} manifest
906
+ * @param {shaka.extern.ManifestDB} manifestDB
907
+ * @param {!shaka.media.DrmEngine} drmEngine
908
+ * @param {shaka.extern.PlayerConfiguration} config
909
+ * @private
910
+ */
911
+ setManifestDrmFields_(manifest, manifestDB, drmEngine, config) {
912
+ manifestDB.expiration = drmEngine.getExpiration();
655
913
 
656
- if (isEncrypted && config.offline.usePersistentLicense &&
657
- !sessions.length) {
658
- throw new shaka.util.Error(
659
- shaka.util.Error.Severity.CRITICAL,
660
- shaka.util.Error.Category.STORAGE,
661
- shaka.util.Error.Code.NO_INIT_DATA_FOR_OFFLINE);
662
- }
914
+ const sessions = drmEngine.getSessionIds();
915
+ manifestDB.sessionIds = config.offline.usePersistentLicense ?
916
+ sessions : [];
663
917
 
664
- return manifestDB;
665
- } finally {
666
- await downloader.destroy();
918
+ if (this.getManifestIsEncrypted_(manifest) &&
919
+ config.offline.usePersistentLicense && !sessions.length) {
920
+ throw new shaka.util.Error(
921
+ shaka.util.Error.Severity.CRITICAL,
922
+ shaka.util.Error.Category.STORAGE,
923
+ shaka.util.Error.Code.NO_INIT_DATA_FOR_OFFLINE);
667
924
  }
668
925
  }
669
926
 
@@ -766,7 +1023,7 @@ shaka.offline.Storage = class {
766
1023
  * @return {!Promise}
767
1024
  * @private
768
1025
  */
769
- removeFromStorage_( storage, uri, manifest) {
1026
+ removeFromStorage_(storage, uri, manifest) {
770
1027
  /** @type {!Array.<number>} */
771
1028
  const segmentIds = shaka.offline.Storage.getAllSegmentIds_(manifest);
772
1029
 
@@ -923,6 +1180,10 @@ shaka.offline.Storage = class {
923
1180
  const playerInterface = {
924
1181
  networkingEngine: networkingEngine,
925
1182
 
1183
+ // No need to apply CMCD data for offline requests
1184
+ modifyManifestRequest: (request, manifestInfo) => {},
1185
+ modifySegmentRequest: (request, segmentInfo) => {},
1186
+
926
1187
  // Don't bother filtering now. We will do that later when we have all the
927
1188
  // information we need to filter.
928
1189
  filter: () => Promise.resolve(),
@@ -1001,8 +1262,7 @@ shaka.offline.Storage = class {
1001
1262
 
1002
1263
  drmEngine.configure(config.drm);
1003
1264
  await drmEngine.initForStorage(
1004
- manifest.variants, config.offline.usePersistentLicense,
1005
- config.useMediaCapabilities);
1265
+ manifest.variants, config.offline.usePersistentLicense);
1006
1266
  await drmEngine.setServerCertificate();
1007
1267
  await drmEngine.createOrLoad();
1008
1268
 
@@ -1010,100 +1270,23 @@ shaka.offline.Storage = class {
1010
1270
  }
1011
1271
 
1012
1272
  /**
1013
- * Creates an offline 'manifest' for the real manifest. This does not store
1014
- * the segments yet, only adds them to the download manager through
1015
- * createStreams_.
1016
- *
1017
- * @param {!shaka.offline.DownloadManager} downloader
1018
- * @param {shaka.extern.StorageCell} storage
1019
- * @param {!shaka.media.DrmEngine} drmEngine
1020
- * @param {shaka.extern.Manifest} manifest
1021
- * @param {string} originalManifestUri
1022
- * @param {!Object} metadata
1023
- * @param {shaka.extern.PlayerConfiguration} config
1024
- * @return {shaka.extern.ManifestDB}
1025
- * @private
1026
- */
1027
- createOfflineManifest_(
1028
- downloader, storage, drmEngine, manifest, originalManifestUri, metadata,
1029
- config) {
1030
- const estimator = new shaka.offline.StreamBandwidthEstimator();
1031
-
1032
- const streams = this.createStreams_(
1033
- downloader, storage, estimator, drmEngine, manifest, config);
1034
-
1035
- const usePersistentLicense = config.offline.usePersistentLicense;
1036
- const drmInfo = drmEngine.getDrmInfo();
1037
-
1038
- if (drmInfo && usePersistentLicense) {
1039
- // Don't store init data, since we have stored sessions.
1040
- drmInfo.initData = [];
1041
- }
1042
-
1043
- return {
1044
- creationTime: Date.now(),
1045
- originalManifestUri: originalManifestUri,
1046
- duration: manifest.presentationTimeline.getDuration(),
1047
- size: 0,
1048
- expiration: drmEngine.getExpiration(),
1049
- streams: streams,
1050
- sessionIds: usePersistentLicense ? drmEngine.getSessionIds() : [],
1051
- drmInfo: drmInfo,
1052
- appMetadata: metadata,
1053
- };
1054
- }
1055
-
1056
- /**
1057
- * Converts manifest Streams to database Streams. This will use the current
1058
- * configuration to get the tracks to use, then it will search each segment
1059
- * index and add all the segments to the download manager through
1060
- * createStream_.
1273
+ * Converts manifest Streams to database Streams.
1061
1274
  *
1062
1275
  * @param {!shaka.offline.DownloadManager} downloader
1063
- * @param {shaka.extern.StorageCell} storage
1064
1276
  * @param {shaka.offline.StreamBandwidthEstimator} estimator
1065
1277
  * @param {!shaka.media.DrmEngine} drmEngine
1066
1278
  * @param {shaka.extern.Manifest} manifest
1067
1279
  * @param {shaka.extern.PlayerConfiguration} config
1068
- * @return {!Array.<shaka.extern.StreamDB>}
1280
+ * @return {{
1281
+ * streams: !Array.<shaka.extern.StreamDB>,
1282
+ * toDownload: !Array.<!shaka.offline.DownloadInfo>
1283
+ * }}
1069
1284
  * @private
1070
1285
  */
1071
- createStreams_(downloader, storage, estimator, drmEngine, manifest, config) {
1072
- // Pass all variants and text streams to the estimator so that we can
1073
- // get the best estimate for each stream later.
1074
- for (const variant of manifest.variants) {
1075
- estimator.addVariant(variant);
1076
- }
1077
- for (const text of manifest.textStreams) {
1078
- estimator.addText(text);
1079
- }
1080
- for (const image of manifest.imageStreams) {
1081
- estimator.addImage(image);
1082
- }
1083
-
1084
- // TODO(joeyparrish): Break out stack-based state and method params into a
1085
- // separate class to clean up. See:
1086
- // https://github.com/google/shaka-player/issues/2781#issuecomment-678438039
1087
-
1088
- /**
1089
- * A cache mapping init segment references to Promises to their DB key.
1090
- *
1091
- * @type {!Map.<shaka.media.InitSegmentReference, !Promise.<?number>>}
1092
- */
1093
- const initSegmentDbKeyCache = new Map();
1094
-
1095
- // A null init segment reference always maps to a null DB key.
1096
- initSegmentDbKeyCache.set(
1097
- null, /** @type {!Promise.<?number>} */(Promise.resolve(null)));
1098
-
1099
- /**
1100
- * A cache mapping equivalent segment references to Promises to their DB
1101
- * key. The key in this map is a string of the form
1102
- * "<URI>-<startByte>-<endByte>".
1103
- *
1104
- * @type {!Map.<string, !Promise.<number>>}
1105
- */
1106
- const segmentDbKeyCache = new Map();
1286
+ createStreams_(downloader, estimator, drmEngine, manifest, config) {
1287
+ // Download infos are stored based on their refId, to dedup them.
1288
+ /** @type {!Map.<string, !shaka.offline.DownloadInfo>} */
1289
+ const toDownload = new Map();
1107
1290
 
1108
1291
  // Find the streams we want to download and create a stream db instance
1109
1292
  // for each of them.
@@ -1113,8 +1296,7 @@ shaka.offline.Storage = class {
1113
1296
 
1114
1297
  for (const stream of streamSet) {
1115
1298
  const streamDB = this.createStream_(
1116
- downloader, storage, estimator, manifest, stream, config,
1117
- initSegmentDbKeyCache, segmentDbKeyCache);
1299
+ downloader, estimator, manifest, stream, config, toDownload);
1118
1300
  streamDBs.set(stream.id, streamDB);
1119
1301
  }
1120
1302
 
@@ -1128,27 +1310,26 @@ shaka.offline.Storage = class {
1128
1310
  }
1129
1311
  }
1130
1312
 
1131
- return Array.from(streamDBs.values());
1313
+ return {
1314
+ streams: Array.from(streamDBs.values()),
1315
+ toDownload: Array.from(toDownload.values()),
1316
+ };
1132
1317
  }
1133
1318
 
1134
1319
  /**
1135
1320
  * Converts a manifest stream to a database stream. This will search the
1136
- * segment index and add all the segments to the download manager.
1321
+ * segment index and add all the segments to the download infos.
1137
1322
  *
1138
1323
  * @param {!shaka.offline.DownloadManager} downloader
1139
- * @param {shaka.extern.StorageCell} storage
1140
1324
  * @param {shaka.offline.StreamBandwidthEstimator} estimator
1141
1325
  * @param {shaka.extern.Manifest} manifest
1142
1326
  * @param {shaka.extern.Stream} stream
1143
1327
  * @param {shaka.extern.PlayerConfiguration} config
1144
- * @param {!Map.<shaka.media.InitSegmentReference, !Promise.<?number>>}
1145
- * initSegmentDbKeyCache
1146
- * @param {!Map.<string, !Promise.<number>>} segmentDbKeyCache
1328
+ * @param {!Map.<string, !shaka.offline.DownloadInfo>} toDownload
1147
1329
  * @return {shaka.extern.StreamDB}
1148
1330
  * @private
1149
1331
  */
1150
- createStream_(downloader, storage, estimator, manifest, stream, config,
1151
- initSegmentDbKeyCache, segmentDbKeyCache) {
1332
+ createStream_(downloader, estimator, manifest, stream, config, toDownload) {
1152
1333
  /** @type {shaka.extern.StreamDB} */
1153
1334
  const streamDb = {
1154
1335
  id: stream.id,
@@ -1178,137 +1359,60 @@ shaka.offline.Storage = class {
1178
1359
  tilesLayout: stream.tilesLayout,
1179
1360
  };
1180
1361
 
1181
- // Download each stream in parallel.
1182
- const downloadGroup = stream.id;
1183
-
1184
1362
  const startTime =
1185
1363
  manifest.presentationTimeline.getSegmentAvailabilityStart();
1186
1364
 
1187
1365
  shaka.offline.Storage.forEachSegment_(stream, startTime, (segment) => {
1188
- const initSegmentKeyPromise = this.getInitSegmentDbKey_(
1189
- downloader, downloadGroup, stream.id, storage, estimator,
1190
- segment.initSegmentReference, config, initSegmentDbKeyCache);
1191
-
1192
- const segmentKeyPromise = this.getSegmentDbKey_(
1193
- downloader, downloadGroup, stream.id, storage, estimator, segment,
1194
- config, segmentDbKeyCache);
1195
-
1196
- downloader.queueWork(downloadGroup, async () => {
1197
- const initSegmentKey = await initSegmentKeyPromise;
1198
- const dataKey = await segmentKeyPromise;
1199
-
1200
- streamDb.segments.push({
1201
- initSegmentKey,
1202
- startTime: segment.startTime,
1203
- endTime: segment.endTime,
1204
- appendWindowStart: segment.appendWindowStart,
1205
- appendWindowEnd: segment.appendWindowEnd,
1206
- timestampOffset: segment.timestampOffset,
1207
- dataKey,
1208
- });
1209
- });
1210
- });
1211
-
1212
- return streamDb;
1213
- }
1214
-
1215
- /**
1216
- * Get a Promise to the DB key for a given init segment reference.
1217
- *
1218
- * The return values will be cached so that multiple calls with the same init
1219
- * segment reference will only trigger one request.
1220
- *
1221
- * @param {!shaka.offline.DownloadManager} downloader
1222
- * @param {number} downloadGroup
1223
- * @param {number} streamId
1224
- * @param {shaka.extern.StorageCell} storage
1225
- * @param {shaka.offline.StreamBandwidthEstimator} estimator
1226
- * @param {shaka.media.InitSegmentReference} initSegmentReference
1227
- * @param {shaka.extern.PlayerConfiguration} config
1228
- * @param {!Map.<shaka.media.InitSegmentReference, !Promise.<?number>>}
1229
- * initSegmentDbKeyCache
1230
- * @return {!Promise.<?number>}
1231
- * @private
1232
- */
1233
- getInitSegmentDbKey_(
1234
- downloader, downloadGroup, streamId, storage, estimator,
1235
- initSegmentReference, config, initSegmentDbKeyCache) {
1236
- if (initSegmentDbKeyCache.has(initSegmentReference)) {
1237
- return initSegmentDbKeyCache.get(initSegmentReference);
1238
- }
1239
-
1240
- const request = shaka.util.Networking.createSegmentRequest(
1241
- initSegmentReference.getUris(),
1242
- initSegmentReference.startByte,
1243
- initSegmentReference.endByte,
1244
- config.streaming.retryParameters);
1245
-
1246
- const promise = downloader.queue(
1247
- downloadGroup,
1248
- request,
1249
- estimator.getInitSegmentEstimate(streamId),
1250
- /* isInitSegment= */ true,
1251
- async (data) => {
1252
- /** @type {!Array.<number>} */
1253
- const ids = await storage.addSegments([{data: data}]);
1254
- this.segmentsFromStore_.push(ids[0]);
1255
- return ids[0];
1256
- });
1257
-
1258
- initSegmentDbKeyCache.set(initSegmentReference, promise);
1259
- return promise;
1260
- }
1366
+ const pendingSegmentRefId =
1367
+ shaka.offline.DownloadInfo.idForSegmentRef(segment);
1368
+ let pendingInitSegmentRefId = undefined;
1369
+
1370
+ // Set up the download for the segment, which will be downloaded later,
1371
+ // perhaps in a service worker.
1372
+ if (!toDownload.has(pendingSegmentRefId)) {
1373
+ const estimateId = downloader.addDownloadEstimate(
1374
+ estimator.getSegmentEstimate(stream.id, segment));
1375
+ const segmentDownload = new shaka.offline.DownloadInfo(
1376
+ segment,
1377
+ estimateId,
1378
+ stream.id,
1379
+ /* isInitSegment= */ false);
1380
+ toDownload.set(pendingSegmentRefId, segmentDownload);
1381
+ }
1261
1382
 
1262
- /**
1263
- * Get a Promise to the DB key for a given segment reference.
1264
- *
1265
- * The return values will be cached so that multiple calls with the same
1266
- * segment reference will only trigger one request.
1267
- *
1268
- * @param {!shaka.offline.DownloadManager} downloader
1269
- * @param {number} downloadGroup
1270
- * @param {number} streamId
1271
- * @param {shaka.extern.StorageCell} storage
1272
- * @param {shaka.offline.StreamBandwidthEstimator} estimator
1273
- * @param {shaka.media.SegmentReference} segmentReference
1274
- * @param {shaka.extern.PlayerConfiguration} config
1275
- * @param {!Map.<string, !Promise.<number>>} segmentDbKeyCache
1276
- * @return {!Promise.<number>}
1277
- * @private
1278
- */
1279
- getSegmentDbKey_(
1280
- downloader, downloadGroup, streamId, storage, estimator,
1281
- segmentReference, config, segmentDbKeyCache) {
1282
- const mapKey = [
1283
- segmentReference.getUris()[0],
1284
- segmentReference.startByte,
1285
- segmentReference.endByte,
1286
- ].join('-');
1287
-
1288
- if (segmentDbKeyCache.has(mapKey)) {
1289
- return segmentDbKeyCache.get(mapKey);
1290
- }
1383
+ // Set up the download for the init segment, similarly, if there is one.
1384
+ if (segment.initSegmentReference) {
1385
+ pendingInitSegmentRefId = shaka.offline.DownloadInfo.idForSegmentRef(
1386
+ segment.initSegmentReference);
1387
+ if (!toDownload.has(pendingInitSegmentRefId)) {
1388
+ const estimateId = downloader.addDownloadEstimate(
1389
+ estimator.getInitSegmentEstimate(stream.id));
1390
+ const initDownload = new shaka.offline.DownloadInfo(
1391
+ segment.initSegmentReference,
1392
+ estimateId,
1393
+ stream.id,
1394
+ /* isInitSegment= */ true);
1395
+ toDownload.set(pendingInitSegmentRefId, initDownload);
1396
+ }
1397
+ }
1291
1398
 
1292
- const request = shaka.util.Networking.createSegmentRequest(
1293
- segmentReference.getUris(),
1294
- segmentReference.startByte,
1295
- segmentReference.endByte,
1296
- config.streaming.retryParameters);
1297
-
1298
- const promise = downloader.queue(
1299
- downloadGroup,
1300
- request,
1301
- estimator.getSegmentEstimate(streamId, segmentReference),
1302
- /* isInitSegment= */ false,
1303
- async (data) => {
1304
- /** @type {!Array.<number>} */
1305
- const ids = await storage.addSegments([{data: data}]);
1306
- this.segmentsFromStore_.push(ids[0]);
1307
- return ids[0];
1308
- });
1399
+ /** @type {!shaka.extern.SegmentDB} */
1400
+ const segmentDB = {
1401
+ pendingInitSegmentRefId,
1402
+ initSegmentKey: pendingInitSegmentRefId ? 0 : null,
1403
+ startTime: segment.startTime,
1404
+ endTime: segment.endTime,
1405
+ appendWindowStart: segment.appendWindowStart,
1406
+ appendWindowEnd: segment.appendWindowEnd,
1407
+ timestampOffset: segment.timestampOffset,
1408
+ tilesLayout: segment.tilesLayout,
1409
+ pendingSegmentRefId,
1410
+ dataKey: 0,
1411
+ };
1412
+ streamDb.segments.push(segmentDB);
1413
+ });
1309
1414
 
1310
- segmentDbKeyCache.set(mapKey, promise);
1311
- return promise;
1415
+ return streamDb;
1312
1416
  }
1313
1417
 
1314
1418
  /**
@@ -1410,21 +1514,21 @@ shaka.offline.Storage = class {
1410
1514
  * @private
1411
1515
  */
1412
1516
  static getAllSegmentIds_(manifest) {
1413
- /** @type {!Array.<number>} */
1414
- const ids = [];
1517
+ /** @type {!Set.<number>} */
1518
+ const ids = new Set();
1415
1519
 
1416
1520
  // Get every segment for every stream in the manifest.
1417
1521
  for (const stream of manifest.streams) {
1418
1522
  for (const segment of stream.segments) {
1419
1523
  if (segment.initSegmentKey != null) {
1420
- ids.push(segment.initSegmentKey);
1524
+ ids.add(segment.initSegmentKey);
1421
1525
  }
1422
1526
 
1423
- ids.push(segment.dataKey);
1527
+ ids.add(segment.dataKey);
1424
1528
  }
1425
1529
  }
1426
1530
 
1427
- return ids;
1531
+ return Array.from(ids);
1428
1532
  }
1429
1533
 
1430
1534
  /**
@@ -1559,6 +1663,9 @@ shaka.offline.Storage = class {
1559
1663
  }
1560
1664
  };
1561
1665
 
1666
+ /** @private {!shaka.util.Mutex} */
1667
+ shaka.offline.Storage.mutex_ = new shaka.util.Mutex();
1668
+
1562
1669
  shaka.offline.Storage.defaultSystemIds_ = new Map()
1563
1670
  .set('org.w3.clearkey', '1077efecc0b24d02ace33c1e52e2fb4b')
1564
1671
  .set('com.widevine.alpha', 'edef8ba979d64acea3c827dcd51d21ed')