Source: lib/offline/download_manager.js

  1. /**
  2. * @license
  3. * Copyright 2016 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. goog.provide('shaka.offline.DownloadManager');
  18. goog.require('shaka.net.NetworkingEngine');
  19. goog.require('shaka.util.Error');
  20. goog.require('shaka.util.IDestroyable');
  21. goog.require('shaka.util.MapUtils');
  22. /**
  23. * @typedef {{
  24. * request: shakaExtern.Request,
  25. * estimatedByteLength: number,
  26. * onDownloaded: function(!ArrayBuffer):!Promise
  27. * }}
  28. *
  29. * @property {shakaExtern.Request}
  30. * The network request that will give us the bytes we want to download.
  31. * @property {number} estimatedByteLength
  32. * The size of the segment as estimated by the bandwidth and segment duration.
  33. * @property {function(!ArrayBuffer):!Promise} onDownloaded
  34. * A callback for when a request has been downloaded and can be used by
  35. * the caller. Callback should return a promise so that downloading will
  36. * not continue until we are done with the current response.
  37. */
  38. shaka.offline.DownloadRequest;
  39. /**
  40. * This manages downloading segments.
  41. *
  42. * @param {function(number, number)} onProgress
  43. *
  44. * @constructor
  45. * @implements {shaka.util.IDestroyable}
  46. */
  47. shaka.offline.DownloadManager = function(onProgress) {
  48. /**
  49. * We sill store download requests in groups. The groups will be downloaded in
  50. * parallel but the segments in each group will be done serially.
  51. *
  52. * @private {!Object.<number, !Array.<shaka.offline.DownloadRequest>>}
  53. */
  54. this.groups_ = {};
  55. /** @private {!Promise} */
  56. this.promise_ = Promise.resolve();
  57. /** @private {boolean} */
  58. this.destroyed_ = false;
  59. /**
  60. * Callback for after a segment has been downloaded. The first parameter
  61. * will be the progress of the download. It will be a number between 0.0
  62. * (0% complete) and 1.0 (100% complete). The second parameter will be the
  63. * the total number of bytes that have been downloaded.
  64. *
  65. * @private {function(number, number)}
  66. */
  67. this.onProgress_ = onProgress;
  68. /**
  69. * When a segment is downloaded, the estimated size of that segment will be
  70. * added to this value. It will allow us to track how much progress we have
  71. * made in downloading all segments.
  72. *
  73. * @private {number}
  74. */
  75. this.downloadedEstimatedBytes_ = 0;
  76. /**
  77. * When we queue a segment to be downloaded, the estimated size of that
  78. * segment will be added to this value. This will allow us to estimate
  79. * how many bytes of content we plan to download.
  80. *
  81. * @private {number}
  82. */
  83. this.expectedEstimatedBytes_ = 0;
  84. /**
  85. * When a segment is downloaded, the actual size of the segment will be added
  86. * to this value so that we know exactly how many bytes we have downloaded.
  87. *
  88. * @private {number}
  89. */
  90. this.downloadedBytes_ = 0;
  91. };
  92. /** @override */
  93. shaka.offline.DownloadManager.prototype.destroy = function() {
  94. this.destroyed_ = true;
  95. const noop = () => {};
  96. let wait = this.promise_.catch(noop);
  97. this.promise_ = Promise.resolve();
  98. this.requests_ = [];
  99. return wait;
  100. };
  101. /**
  102. * Add a request to be downloaded as part of a group.
  103. *
  104. * @param {number} group The group to add this segment to. If the group does
  105. * not exists, a new group will be created.
  106. * @param {shakaExtern.Request} request
  107. * @param {number} estimatedByteLength
  108. * @param {function(!ArrayBuffer):!Promise} onDownloaded
  109. * A callback for when a request has been downloaded and can be used by
  110. * the caller. Callback should return a promise so that downloading will
  111. * not continue until we are done with the current response.
  112. */
  113. shaka.offline.DownloadManager.prototype.queue = function(
  114. group, request, estimatedByteLength, onDownloaded) {
  115. this.groups_[group] = this.groups_[group] || [];
  116. this.groups_[group].push({
  117. request: request,
  118. estimatedByteLength: estimatedByteLength,
  119. onDownloaded: onDownloaded
  120. });
  121. };
  122. /**
  123. * @param {!shaka.net.NetworkingEngine} net
  124. * @return {!Promise}
  125. */
  126. shaka.offline.DownloadManager.prototype.download = function(net) {
  127. let groups = shaka.util.MapUtils.values(this.groups_);
  128. this.groups_ = {}; // Clear the map to create a clean slate.
  129. groups.forEach((segments) => {
  130. segments.forEach((segment) => {
  131. this.expectedEstimatedBytes_ += segment.estimatedByteLength;
  132. });
  133. });
  134. /** @type {!Promise.<number>} */
  135. let p = Promise.resolve().then(() => {
  136. this.checkDestroyed_();
  137. return Promise.all(groups.map((group) => this.downloadGroup_(net, group)));
  138. });
  139. // Amend our new promise chain to our internal promise so that when we destroy
  140. // the download manger we will wait for all the downloads to stop.
  141. this.promise_ = this.promise_.then(() => p);
  142. return p;
  143. };
  144. /**
  145. * @param {!shaka.net.NetworkingEngine} net
  146. * @param {!Array.<shaka.offline.DownloadRequest>} group
  147. * @return {!Promise}
  148. * @private
  149. */
  150. shaka.offline.DownloadManager.prototype.downloadGroup_ = function(net, group) {
  151. let p = Promise.resolve();
  152. group.forEach((segment) => {
  153. p = p.then(() => {
  154. this.checkDestroyed_();
  155. return this.downloadSegment_(net, segment);
  156. });
  157. });
  158. return p;
  159. };
  160. /**
  161. * @param {!shaka.net.NetworkingEngine} net
  162. * @param {shaka.offline.DownloadRequest} segment
  163. * @return {!Promise}
  164. * @private
  165. */
  166. shaka.offline.DownloadManager.prototype.downloadSegment_ = function(
  167. net, segment) {
  168. return Promise.resolve().then(() => {
  169. this.checkDestroyed_();
  170. let type = shaka.net.NetworkingEngine.RequestType.SEGMENT;
  171. return net.request(type, segment.request).promise;
  172. }).then((response) => {
  173. this.checkDestroyed_();
  174. // Update all our internal stats.
  175. this.downloadedEstimatedBytes_ += segment.estimatedByteLength;
  176. this.downloadedBytes_ += response.data.byteLength;
  177. let progress =
  178. this.expectedEstimatedBytes_ ?
  179. this.downloadedEstimatedBytes_ / this.expectedEstimatedBytes_ :
  180. 0;
  181. this.onProgress_(progress, this.downloadedBytes_);
  182. return segment.onDownloaded(response.data);
  183. });
  184. };
  185. /**
  186. * Check if the download manager has been destroyed. If so, throw an error to
  187. * kill the promise chain.
  188. * @private
  189. */
  190. shaka.offline.DownloadManager.prototype.checkDestroyed_ = function() {
  191. if (this.destroyed_) {
  192. throw new shaka.util.Error(
  193. shaka.util.Error.Severity.CRITICAL,
  194. shaka.util.Error.Category.STORAGE,
  195. shaka.util.Error.Code.OPERATION_ABORTED);
  196. }
  197. };