Source: lib/offline/manifest_converter.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.ManifestConverter');
  18. goog.require('goog.asserts');
  19. goog.require('shaka.media.InitSegmentReference');
  20. goog.require('shaka.media.PresentationTimeline');
  21. goog.require('shaka.media.SegmentIndex');
  22. goog.require('shaka.media.SegmentReference');
  23. goog.require('shaka.offline.OfflineUri');
  24. goog.require('shaka.util.ManifestParserUtils');
  25. goog.require('shaka.util.MapUtils');
  26. /**
  27. * Utility class for converting database manifest objects back to normal
  28. * player-ready objects. Used by the offline system to convert on-disk
  29. * objects back to the in-memory objects.
  30. */
  31. shaka.offline.ManifestConverter = class {
  32. /**
  33. * Create a new manifest converter. Need to know the mechanism and cell that
  34. * the manifest is from so that all segments paths can be created.
  35. *
  36. * @param {string} mechanism
  37. * @param {string} cell
  38. */
  39. constructor(mechanism, cell) {
  40. /** @private {string} */
  41. this.mechanism_ = mechanism;
  42. /** @private {string} */
  43. this.cell_ = cell;
  44. }
  45. /**
  46. * Convert a |shakaExtern.ManifestDB| object to a |shakaExtern.Manifest|
  47. * object.
  48. *
  49. * @param {shakaExtern.ManifestDB} manifestDB
  50. * @return {shakaExtern.Manifest}
  51. */
  52. fromManifestDB(manifestDB) {
  53. let timeline = new shaka.media.PresentationTimeline(null, 0);
  54. timeline.setDuration(manifestDB.duration);
  55. let periods = manifestDB.periods.map((period) =>
  56. this.fromPeriodDB(period, timeline));
  57. let drmInfos = manifestDB.drmInfo ? [manifestDB.drmInfo] : [];
  58. if (manifestDB.drmInfo) {
  59. periods.forEach((period) => {
  60. period.variants.forEach((variant) => { variant.drmInfos = drmInfos; });
  61. });
  62. }
  63. return {
  64. presentationTimeline: timeline,
  65. minBufferTime: 2,
  66. offlineSessionIds: manifestDB.sessionIds,
  67. periods: periods
  68. };
  69. }
  70. /**
  71. * Create a period object from a database period.
  72. *
  73. * @param {shakaExtern.PeriodDB} period
  74. * @param {shaka.media.PresentationTimeline} timeline
  75. * @return {shakaExtern.Period}
  76. */
  77. fromPeriodDB(period, timeline) {
  78. /** @type {!Array.<shakaExtern.StreamDB>} */
  79. let audioStreams = period.streams.filter((stream) => this.isAudio_(stream));
  80. /** @type {!Array.<shakaExtern.StreamDB>} */
  81. let videoStreams = period.streams.filter((stream) => this.isVideo_(stream));
  82. /** @type {!Array.<shakaExtern.Variant>} */
  83. let variants = this.createVariants(audioStreams, videoStreams);
  84. /** @type {!Array.<shakaExtern.Stream>} */
  85. let textStreams = period.streams
  86. .filter((stream) => this.isText_(stream))
  87. .map((stream) => this.fromStreamDB_(stream));
  88. period.streams.forEach((stream, i) => {
  89. /** @type {!Array.<!shaka.media.SegmentReference>} */
  90. let refs = stream.segments.map((segment, index) => {
  91. return this.fromSegmentDB_(index, segment);
  92. });
  93. timeline.notifySegments(refs, i == 0);
  94. });
  95. return {
  96. startTime: period.startTime,
  97. variants: variants,
  98. textStreams: textStreams
  99. };
  100. }
  101. /**
  102. * Recreates Variants from audio and video StreamDB collections.
  103. *
  104. * @param {!Array.<!shakaExtern.StreamDB>} audios
  105. * @param {!Array.<!shakaExtern.StreamDB>} videos
  106. * @return {!Array.<!shakaExtern.Variant>}
  107. */
  108. createVariants(audios, videos) {
  109. const MapUtils = shaka.util.MapUtils;
  110. // Create a variant for each variant id.
  111. /** @type {!Object.<number, shakaExtern.Variant>} */
  112. let variantMap = {};
  113. let allStreams = [];
  114. allStreams.push.apply(allStreams, audios);
  115. allStreams.push.apply(allStreams, videos);
  116. // Create a variant for each variant id across all the streams.
  117. allStreams.forEach((stream) => {
  118. stream.variantIds.forEach((id) => {
  119. variantMap[id] = variantMap[id] || this.createEmptyVariant_(id);
  120. });
  121. });
  122. // Assign each audio stream to its variants.
  123. audios.forEach((audio) => {
  124. /** @type {shakaExtern.Stream} */
  125. let stream = this.fromStreamDB_(audio);
  126. audio.variantIds.forEach((id) => {
  127. let variant = variantMap[id];
  128. goog.asserts.assert(
  129. !variant.audio, 'A variant should only have one audio stream');
  130. variant.language = stream.language;
  131. variant.primary = variant.primary || stream.primary;
  132. variant.audio = stream;
  133. });
  134. });
  135. // Assign each video stream to its variants.
  136. videos.forEach((video) => {
  137. /** @type {shakaExtern.Stream} */
  138. let stream = this.fromStreamDB_(video);
  139. video.variantIds.forEach((id) => {
  140. let variant = variantMap[id];
  141. goog.asserts.assert(
  142. !variant.video, 'A variant should only have one video stream');
  143. variant.primary = variant.primary || stream.primary;
  144. variant.video = stream;
  145. });
  146. });
  147. return MapUtils.values(variantMap);
  148. }
  149. /**
  150. * @param {shakaExtern.StreamDB} streamDB
  151. * @return {shakaExtern.Stream}
  152. * @private
  153. */
  154. fromStreamDB_(streamDB) {
  155. /** @type {!Array.<!shaka.media.SegmentReference>} */
  156. let segments = streamDB.segments.map((segment, index) =>
  157. this.fromSegmentDB_(index, segment));
  158. /** @type {!shaka.media.SegmentIndex} */
  159. let segmentIndex = new shaka.media.SegmentIndex(segments);
  160. /** @type {shakaExtern.Stream} */
  161. let stream = {
  162. id: streamDB.id,
  163. createSegmentIndex: () => Promise.resolve(),
  164. findSegmentPosition: (index) => segmentIndex.find(index),
  165. getSegmentReference: (index) => segmentIndex.get(index),
  166. initSegmentReference: null,
  167. presentationTimeOffset: streamDB.presentationTimeOffset,
  168. mimeType: streamDB.mimeType,
  169. codecs: streamDB.codecs,
  170. width: streamDB.width || undefined,
  171. height: streamDB.height || undefined,
  172. frameRate: streamDB.frameRate || undefined,
  173. kind: streamDB.kind,
  174. encrypted: streamDB.encrypted,
  175. keyId: streamDB.keyId,
  176. language: streamDB.language,
  177. label: streamDB.label || null,
  178. type: streamDB.contentType,
  179. primary: streamDB.primary,
  180. trickModeVideo: null,
  181. // TODO(modmaker): Store offline?
  182. containsEmsgBoxes: false,
  183. roles: [],
  184. channelsCount: null
  185. };
  186. if (streamDB.initSegmentKey != null) {
  187. stream.initSegmentReference =
  188. this.fromInitSegmentDB_(streamDB.initSegmentKey);
  189. }
  190. return stream;
  191. }
  192. /**
  193. * @param {number} index
  194. * @param {shakaExtern.SegmentDB} segmentDB
  195. * @return {!shaka.media.SegmentReference}
  196. * @private
  197. */
  198. fromSegmentDB_(index, segmentDB) {
  199. /** @type {!shaka.offline.OfflineUri} */
  200. let uri = shaka.offline.OfflineUri.segment(
  201. this.mechanism_, this.cell_, segmentDB.dataKey);
  202. return new shaka.media.SegmentReference(
  203. index,
  204. segmentDB.startTime,
  205. segmentDB.endTime,
  206. () => [uri.toString()],
  207. 0 /* startByte */,
  208. null /* endByte */);
  209. }
  210. /**
  211. * @param {number} key
  212. * @return {!shaka.media.InitSegmentReference}
  213. * @private
  214. */
  215. fromInitSegmentDB_(key) {
  216. /** @type {!shaka.offline.OfflineUri} */
  217. let uri = shaka.offline.OfflineUri.segment(
  218. this.mechanism_, this.cell_, key);
  219. return new shaka.media.InitSegmentReference(
  220. () => [uri.toString()],
  221. 0 /* startBytes*/,
  222. null /* endBytes */);
  223. }
  224. /**
  225. * @param {shakaExtern.StreamDB} stream
  226. * @return {boolean}
  227. * @private
  228. */
  229. isAudio_(stream) {
  230. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  231. return stream.contentType == ContentType.AUDIO;
  232. }
  233. /**
  234. * @param {shakaExtern.StreamDB} stream
  235. * @return {boolean}
  236. * @private
  237. */
  238. isVideo_(stream) {
  239. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  240. return stream.contentType == ContentType.VIDEO;
  241. }
  242. /**
  243. * @param {shakaExtern.StreamDB} stream
  244. * @return {boolean}
  245. * @private
  246. */
  247. isText_(stream) {
  248. const ContentType = shaka.util.ManifestParserUtils.ContentType;
  249. return stream.contentType == ContentType.TEXT;
  250. }
  251. /**
  252. * Creates an empty Variant.
  253. *
  254. * @param {number} id
  255. * @return {!shakaExtern.Variant}
  256. * @private
  257. */
  258. createEmptyVariant_(id) {
  259. return {
  260. id: id,
  261. language: '',
  262. primary: false,
  263. audio: null,
  264. video: null,
  265. bandwidth: 0,
  266. drmInfos: [],
  267. allowedByApplication: true,
  268. allowedByKeySystem: true
  269. };
  270. }
  271. };