Source: lib/offline/manifest_converter.js

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